module MiniJSON

Constants

EMPTY_BYTES
ESCAPE_TO_VALUE
KEY_REGEXP
NUMBER_REGEXP
VERSION

Public Class Methods

load(str)
Alias for: parse
parse(str) click to toggle source
# File lib/minijson.rb, line 7
def parse(str)
  parser(lexer(str))
end
Also aliased as: load

Private Class Methods

is_empty?(tok) click to toggle source
# File lib/minijson.rb, line 52
def is_empty?(tok)
  tok.each_char.all? { |i| EMPTY_BYTES.include? i }
end
lexer(str) click to toggle source
# File lib/minijson.rb, line 30
def lexer(str)
  str.scan(lexer_regexp).map(&:first)
end
lexer_regexp() click to toggle source
# File lib/minijson.rb, line 15
def lexer_regexp
  @lexer_regexp ||= begin
    meaningful_characters = /[()\[\]{}",:]/
    string_escapes = /\\(?:[\\\/"bfnrt]|u[0-9a-fA-F]{4})/
    numbers = /-?[0-9]+(\.[0-9]*)?([eE][+-]?[0-9]+)?/
    space = /[ \r\n\t]+/
    rest = /[^"\\: \r\n\t]+/
    very_rest = /.+/

    /(#{
      [meaningful_characters, string_escapes, numbers, space, rest, very_rest].join('|')
    })/
  end
end
parser(toks) click to toggle source
# File lib/minijson.rb, line 60
def parser(toks)
  state = :value

  # popping is cheaper than shifting
  toks = toks.reverse

  value = nil
  finalizers = [proc { |i| value = i }]
  structs = []
  hash_keys = []

  finalizer = proc { |i| finalizers.pop.(i) }

  until toks.empty?
    tok = toks.pop

    case state
    when :value, :top_value, :struct_value
      case tok
      when '{'
        structs << {}
        toks << ','
      when '['
        structs << []
        toks << ','
      when '}', ']'
        parser_error(tok) if structs.empty?
        parser_error(tok) if structs.last.class == Array && tok != ']'
        parser_error(tok) if structs.last.class == Hash && tok != '}'
        finalizer.(structs.pop)
      when ','
        # warning: [,,,,] will cause a weird behavior, but will be caught
        case structs.last
        when nil
          parser_error(tok)
        when Array
          finalizers << proc do |i|
            parser_error(tok) unless structs.last
            structs.last << i
          end
          state = :struct_value
        when Hash
          finalizers << proc do |i|
            parser_error(tok) unless structs.last && hash_keys.last
            structs.last[hash_keys.pop] = i
          end
          state = :key
        end
      when 'true'
        finalizer.(true)
      when 'false'
        finalizer.(false)
      when 'null'
        finalizer.(nil)
      when method(:is_empty?).to_proc
        # nothing
      when '"'
        finalizer.(receive_string(toks))
      when NUMBER_REGEXP
        finalizer.(receive_number(tok))
      else
        parser_error(tok)
      end
    when :key
      case tok
      when '"'
        hash_keys << receive_string(toks)
      when method(:is_empty?).to_proc
        next
      when KEY_REGEXP
        hash_keys << tok
      else
        parser_error(tok)
      end
      state = :colon
    when :colon
      case tok
      when ':'
        state = :value
      when method(:is_empty?).to_proc
        # nothing
      else
        parser_error(tok)
      end
    end
  end

  parser_error("END") unless [finalizers, structs, hash_keys].all?(&:empty?)

  value
end
parser_error(tok) click to toggle source
# File lib/minijson.rb, line 56
def parser_error(tok)
  raise ParserError, "unexpected token at '#{tok}'"
end
receive_number(tok) click to toggle source
# File lib/minijson.rb, line 169
def receive_number(tok)
  if tok.match?(/[e.]/)
    tok.to_f
  else
    tok.to_i
  end
end
receive_string(toks) click to toggle source
# File lib/minijson.rb, line 152
def receive_string(toks)
  str = []
  while true
    tok = toks.pop
    if tok == nil
      parser_error('"'+toks.join)
    elsif tok == '"'
      break
    elsif tok.start_with? '\\'
      str << ESCAPE_TO_VALUE[tok]
    else
      str << tok
    end      
  end
  str.join
end