lib/rvs/parse.rb

Summary

Maintainability
C
1 day
Test Coverage
require 'strscan'
require 'bigdecimal'
require 'yajl'

module RVS
class Parser
  def initialize(str)
    @scan = StringScanner.new(str)
  end

  def run
    parse_item
  end

  def parse_item
    next_char = @scan.getch
    if next_char == '['
      parse_array
    elsif next_char == '{'
      parse_hash
    elsif next_char == '"'
      if @scan.peek(1) == '"'
        get_chars(1)
        ''
      else
        strval = '"' + @scan.scan(/.*?[^\\]+?\"/)
        Yajl::Parser.new.parse(strval)
      end
    elsif next_char == ':'
      @scan.scan(/[a-zA-Z_]+\w*/).to_sym
    elsif next_char == 'd'
      strval = @scan.scan(/[0-9-]+/)
      if @scan.peek(1) == ' '
        strval += get_chars(9)
        DateTime.strptime(strval, '%Y-%m-%d %H:%M:%S')
      else
        Date.strptime(strval, '%Y-%m-%d')
      end
    elsif next_char == 'f'
      if @scan.peek(1) == 'a'
        get_chars(4)
        false
      else
        @scan.scan(/-?[0-9\.]+/).to_f
      end
    elsif next_char == 't'
      if @scan.peek(1) == 'r'
        get_chars(3)
        true
      else
        Time.strptime(get_chars(19), '%Y-%m-%d %H:%M:%S')
      end
    elsif next_char == 'c'
      BigDecimal(@scan.scan(/-?[0-9\.]+/))
    elsif next_char == 'n'
      get_chars(2)
      nil
    elsif next_char =~ /[0-9-]/
      @scan.pos = @scan.pos - 1
      @scan.scan(/-?\d+/).to_i
    else
      raise "unexpected type identifier *#{next_char}*; string remainder is #{@scan.rest}"
    end
  end

  def parse_array
    retval = []
    while !@scan.eos?
      next_char = @scan.peek(1)
      if next_char == ']'
        # throw away the closing ]
        @scan.getch
        break
      end
      # throw away the ,
      @scan.getch if next_char == ','
      retval.push(parse_item)
    end
    retval
  end

  def parse_hash
    retval = {}
    curr_key = nil
    while !@scan.eos?
      next_char = @scan.peek(1)
      if next_char == '}'
        # throw away the closing }
        @scan.getch
        break
      end
      # throw away the following delimiter chars
      @scan.getch if next_char == ','
      get_chars(2) if next_char == '='

      next_value = parse_item
      if curr_key
        retval[curr_key] = next_value
        curr_key = nil
      else
        curr_key = next_value
      end
    end
    retval
  end

  def get_chars(count)
    retval = @scan.peek(count)
    @scan.pos = @scan.pos + count
    retval
  end
end

def self.parse(str)
  Parser.new(str).run
end
end