From: steve Date: 2008-02-03T23:14:38+09:00 Subject: Re: [QUIZ] Parsing JSON (#155) ------=_Part_570_22697686.1202048084623 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Content-Disposition: inline Hey guys This is my first parser. I used Nathan Sobo's Treetop parsing library ( http://treetop.rubyforge.org/, gem install treetop): http://pastie.caboo.se/146906 require 'treetop' File.open("json.treetop", "w") {|f| f.write GRAMMAR } Treetop.load "json" parser = JsonParser.new pp parser.parse(STDIN.read).value if $0 == __FILE__ BEGIN { GRAMMAR = %q{ grammar Json rule json space json_value space { def value; json_value.value; end } end rule json_value string / numeric / keyword / object / array end rule string '"' chars:char* '"' { def value chars.elements.map {|e| e.value }.join end } end rule char !'"' ('\\\\' ( ( [nbfrt"] / '\\\\' / '/' ) / 'u' hex hex hex hex ) / !'\\\\' .) { def value if text_value[0..0] == '\\\\' case c = text_value[1..1] when /[nbfrt]/ {'n' => "\n", 'b' => "\b", 'f' => "\f", 'r' => "\r", 't' => "\t"}[c] when 'u' [text_value[2,4].to_i(16)].pack("L").gsub(/\0*$/,'') else c end else text_value end end } end rule hex [0-9a-fA-F] end rule numeric exp / float / integer end rule exp (float / integer) ('e' / 'E') ('+' / '-')? integer { def value; text_value.to_f; end } end rule float integer '.' [0-9]+ { def value; text_value.to_f; end } end rule integer '-'? ('0' / [1-9] [0-9]*) { def value; text_value.to_i; end } end rule keyword ('true' / 'false' / 'null') { def value { 'true' => true, 'false' => false, 'null' => nil }[text_value] end } end rule object '{' space pairs:pair* space '}' { def value pairs.elements.map {|p| p.value }.inject({}) {|h,p| h.merge p } end } end rule pair space string space ':' space json_value space (',' &pair / !pair) { def value { string.value => json_value.value } end } end rule array '[' space array_values:array_value* space ']' { def value array_values.elements.map {|e| e.value } end } end rule array_value space json_value space (',' &array_value / !array_value) { def value json_value.value end } end rule space [ \t\r\n]* end end } } - steve On Feb 1, 2008 8:55 PM, Ruby Quiz wrote: > The three rules of Ruby Quiz: > > 1. Please do not post any solutions or spoiler discussion for this quiz > until > 48 hours have passed from the time on this message. > > 2. Support Ruby Quiz by submitting ideas as often as you can: > > http://www.rubyquiz.com/ > > 3. Enjoy! > > Suggestion: A [QUIZ] in the subject of emails about the problem helps > everyone > on Ruby Talk follow the discussion. Please reply to the original quiz > message, > if you can. > > > -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= > > There has been a lot of talk recently about parsing with Ruby. We're > seeing > some parser generator libraries pop up that make the task that much easier > and > they've been stirring up interest. > > In honor of that, this week's Ruby Quiz is to write a parser for JSON. > > JSON turns out to turns out to be a great little example for writing > parsers for > two reasons. First, it's pretty easy stuff. You can hand-roll a JSON > parser in > under 100 lines of Ruby. The second advantage is that the data format is > wonderfully documented: > > http://json.org/ > > Since JSON is just a data format and Ruby supports all of the data types, > I vote > we just use Ruby itself as the abstract syntax tree produced by the parse. > > Feel free to show off your favorite parser generator, if you don't want to > roll > your own. Anything goes. > > Here are a few tests to get you started: > > require "test/unit" > > class TestJSONParser < Test::Unit::TestCase > def setup > @parser = JSONParser.new > end > > def test_keyword_parsing > assert_equal(true, @parser.parse("true")) > assert_equal(false, @parser.parse("false")) > assert_equal(nil, @parser.parse("null")) > end > > def test_number_parsing > assert_equal(42, @parser.parse("42")) > assert_equal(-13, @parser.parse("-13")) > assert_equal(3.1415, @parser.parse("3.1415")) > assert_equal(-0.01, @parser.parse("-0.01")) > > assert_equal(0.2e1, @parser.parse("0.2e1")) > assert_equal(0.2e+1, @parser.parse("0.2e+1")) > assert_equal(0.2e-1, @parser.parse("0.2e-1")) > assert_equal(0.2E1, @parser.parse("0.2e1")) > end > > def test_string_parsing > assert_equal(String.new, @parser.parse(%Q{""})) > assert_equal("JSON", @parser.parse(%Q{"JSON"})) > > assert_equal( %Q{nested "quotes"}, > @parser.parse('"nested \"quotes\""') ) > assert_equal("\n", @parser.parse(%Q{"\\n"})) > assert_equal( "a", > @parser.parse(%Q{"\\u#{"%04X" % ?a}"}) ) > end > > def test_array_parsing > assert_equal(Array.new, @parser.parse(%Q{[]})) > assert_equal( ["JSON", 3.1415, true], > @parser.parse(%Q{["JSON", 3.1415, true]}) ) > assert_equal([1, [2, [3]]], @parser.parse(%Q{[1, [2, [3]]]})) > end > > def test_object_parsing > assert_equal(Hash.new, @parser.parse(%Q{{}})) > assert_equal( {"JSON" => 3.1415, "data" => true}, > @parser.parse(%Q{{"JSON": 3.1415, "data": true}}) > ) > assert_equal( { "Array" => [1, 2, 3], > "Object" => {"nested" => "objects"} }, > @parser.parse(<<-END_OBJECT) ) > {"Array": [1, 2, 3], "Object": {"nested": "objects"}} > END_OBJECT > end > > def test_parse_errors > assert_raise(RuntimeError) { @parser.parse("{") } > assert_raise(RuntimeError) { @parser.parse(%q{{"key": true > false}}) } > > assert_raise(RuntimeError) { @parser.parse("[") } > assert_raise(RuntimeError) { @parser.parse("[1,,2]") } > > assert_raise(RuntimeError) { @parser.parse(%Q{"}) } > assert_raise(RuntimeError) { @parser.parse(%Q{"\\i"}) } > > assert_raise(RuntimeError) { @parser.parse("$1,000") } > assert_raise(RuntimeError) { @parser.parse("1_000") } > assert_raise(RuntimeError) { @parser.parse("1K") } > > assert_raise(RuntimeError) { @parser.parse("unknown") } > end > end > > ------=_Part_570_22697686.1202048084623--