Begin forwarded message: > From: Alexander Stedile <as_news / gmx.at> > Date: February 3, 2008 6:30:39 PM CST > To: submission / rubyquiz.com > Subject: Please Forward: Ruby Quiz Submission > > Hi! > > Here is my try for my second ruby quiz. I proved that it is possible > to write a JSON parser in less than 100 lines. My (handmade) > solution has 94. This would leave space for five more comment > lines. ;-) > Looking forward to seeing your parsers and of course the nice quiz > summary which I really appreciate. Thanks for that. > > By the way, I added a missing test. I had to do an extra check for > that situation to my program. The reason is that the type is decided > from the first character. And if there is a closing quotation mark > at the end, it could be mistakenly interpreted as valid string 'a" > "b'. > assert_raise(RuntimeError) { @parser.parse(%Q{"a" "b"}) } > I also do not check for control characters in strings, but anyway. > > As you see, I'm beginning to like eval. :) And my solution heavily > relies on regexp matching. The most important/tricky part is the > String#split_stateful method at the top of the code. > > Have fun, > Alex > ______ > class String > # Splits into sub-strings separated by ',' characters. Does not split > # contents within {}, [], or "". \" does not end a string, \\" does. > # Checks if closing characters match previous opening ones. > def split_stateful > memb = [] # list of members identified > delims = [] # stack of delimiters > split('').each { |c| > memb << "" if memb.empty? > case delims.last > when '"' # quote mode > c == '\\' and delims.push c > c == '"' and delims.pop > when '\\' # escape mode > delims.pop > else > case c > when '{', '[', '"' then delims.push c > when ',' then ( memb << ""; c="" ) if delims.empty? # next > element > when '}' then delims.pop == '{' or raise RuntimeError, "Non- > matching }." > when ']' then delims.pop == '[' or raise RuntimeError, "Non- > matching ]." > end > end > memb[-1] += c > } > delims.empty? or raise RuntimeError, "No closing delimiter for > #{delims.join(', ')}." > memb > end > end > > class JSONParser > > NUM_FORMAT = /^(-)?(0|[1-9][0-9]*)(\.[0-9]+)?(E[+-]?([0-9]+))?$/i > > # parse_value > def parse(code) > code.strip! > case code[0,1] > when '"' then parse_string(code) > when /[-0-9]/ then parse_number(code) > when '{' then parse_object(code) > when '[' then parse_array(code) > else parse_keyword(code) > end > end > > def parse_string(code) > code =~ /^"(.*)"$/ or raise RuntimeError, "String has no closing > quotation mark." > $_ = $1 > $_ =~ /([^\\]|(\\\\)+)"/ and raise RuntimeError, "Non-escaped \" > not allowed in string #{$_}." > gsub(/\\(.)/) { |m| > case $1 > when 'b', 'f', 'n', 'r', 't' > eval('"\\%s"' % $1) > when 'u' > m # no change, handled later > when '"', '/', '\\' > $1 # strip \ character > else > raise RuntimeError, "No such escape sequence \\#{$1}." > end > } > gsub(/\\u([A-F0-9]{4})/i) { "%c" % $1.hex } > end > > def parse_number(code) > code =~ NUM_FORMAT or raise RuntimeError, "Invalid number #{code}." > eval code > end > > def parse_array(code) > code =~ /^\[(.*)\]$/ or raise RuntimeError, "No closing bracket > for array #{code}." > $1.split_stateful.collect { |m| parse(m) } > end > > def parse_object(code) > code =~ /^\{(.*)\}$/ or raise RuntimeError, "No closing bracket > for object #{code}." > object = {} > $1.split_stateful.each do |m| > key, value = m.split(":", 2) > object[parse_string(key.strip)] = parse(value) > end > object > end > > def parse_keyword(code) > case code > when 'true', 'false' then eval(code) > when 'null' then nil > else > raise RuntimeError, "Syntax error: #{code}." > end > end > end