racc: JSONパーサ

raccの練習ってことで、JSONのRFCを見ながらJSONパーサを作ってみた。

追記
yyparseを使うように修正。
こっちのほうがいい感じ。

追記2
って、よくみたら、エスケープされた文字列を元に戻してない…
後で直そう。

class JsonParser
options no_result_var
rule
  json_text       : ws object ws { val[1] }
                  | ws array ws  { val[1] }

  ws              : 
                  | ws SPACE

  value           : FALSE
                  | NULL
                  | TRUE
                  | object
                  | array
                  | NUMBER
                  | STRING

 object           : '{' ws '}' { {} }
                  | '{' ws members ws '}'
                      {
                        h = {}
                        val[2].each {|k,v| h[k] = v }
                        h
                      }

 members          : member                   { [ val[0] ]       }
                  | members ws ',' ws member { val[0] << val[4] }

 member           : STRING ws ':' ws value { [ val[0], val[4] ] }

 array            : '[' ws ']'           { []     }
                  | '[' ws values ws ']' { val[2] }

 values           : value                  { [ val[0] ]       }
                  | values ws ',' ws value { val[0] << val[4] }
end

---- header

require 'strscan'

---- inner

R_SPACE    = /\A[ \t\n\r]+/
R_RESERVED = /\A[\{\}\[\],:]/
R_NUMBER   = /\A-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][-+]?\d+)?/
R_STRING   = /\A"(?:\\["\\\/bfnrt]|\\u[\dA-Za-z]{4}|[^\\"])*"/
R_FALSE    = /\Afalse/
R_NULL     = /\Anull/
R_TRUE     = /\Atrue/

def initialize(obj)
  src = obj.is_a?(IO) ? obj.read : obj.to_s
  @s = StringScanner.new(src)
end

def scan
  piece = nil

  until @s.eos?
    if (piece = @s.scan R_SPACE)
      yield :SPACE, piece
    elsif (piece = @s.scan R_RESERVED)
      yield piece, piece
    elsif (piece = @s.scan R_NUMBER)
      yield :NUMBER, piece.to_f
    elsif (piece = @s.scan R_STRING)
      yield :STRING, piece
    elsif @s.scan R_FALSE
      yield :FALSE, false
    elsif @s.scan R_NULL
      yield :NULL, nil
    elsif @s.scan R_TRUE
      yield :TRUE, true
    else
      raise RuntimeError, 'must not happen'
    end
  end

  yield false, '$'
end

def parse
  yyparse self, :scan
end

Examplesはパースできた。
細かい単位(CRとかLFとか)のパースを、パーサにやらせるのがいいのか、正規表現でやるのがいいのか、よくわからないなぁ…