------ art_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 sonParser.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 ext_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 <james / grayproductions.net> 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 SONParser.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
>
>
------ art_570_22697686.1202048084623--