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とか)のパースを、パーサにやらせるのがいいのか、正規表現でやるのがいいのか、よくわからないなぁ…