# Please Forward: Ruby Quiz Submission

**URL:** <https://rubytalk.org/t/please-forward-ruby-quiz-submission/44258>\
**Category:** ruby-talk\
**Created:** [4 February 2008 18:58 UTC](https://rubytalk.org/t/please-forward-ruby-quiz-submission/44258 "2008-02-04T18:58:19Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [4 February 2008 18:58 UTC](https://rubytalk.org/t/please-forward-ruby-quiz-submission/44258/1 "2008-02-04T18:58:19Z")

</div>

> 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'.  
> &nbsp;&nbsp;&nbsp;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  
> &nbsp;&nbsp;&nbsp;memb = # list of members identified  
> &nbsp;&nbsp;&nbsp;delims = # stack of delimiters  
> &nbsp;&nbsp;&nbsp;split('').each { |c|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memb \<\< "" if memb.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case delims.last  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when '"' # quote mode  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;c == '\\' and delims.push c  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;c == '"' and delims.pop  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when '\\' # escape mode  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delims.pop  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case c  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when '{', '[', '"' then delims.push c  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when ',' then ( memb \<\< ""; c="" ) if delims.empty? # next element  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when '}' then delims.pop == '{' or raise RuntimeError, "Non-matching }."  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when ']' then delims.pop == '[' or raise RuntimeError, "Non-matching]."  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memb[-1] += c  
> &nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;delims.empty? or raise RuntimeError, "No closing delimiter for #{delims.join(', ')}."  
> &nbsp;&nbsp;&nbsp;memb  
> end  
> end
> 
> class JSONParser
> 
> NUM\_FORMAT = /^(-)?(0|[1-9][0-9]\*)(\.[0-9]+)?(E[+-]?([0-9]+))?$/i
> 
> # parse\_value  
> def parse(code)  
> &nbsp;&nbsp;&nbsp;code.strip!  
> &nbsp;&nbsp;&nbsp;case code[0,1]  
> &nbsp;&nbsp;&nbsp;when '"' then parse\_string(code)  
> &nbsp;&nbsp;&nbsp;when /[-0-9]/ then parse\_number(code)  
> &nbsp;&nbsp;&nbsp;when '{' then parse\_object(code)  
> &nbsp;&nbsp;&nbsp;when '[' then parse\_array(code)  
> &nbsp;&nbsp;&nbsp;else parse\_keyword(code)  
> &nbsp;&nbsp;&nbsp;end  
> end
> 
> def parse\_string(code)  
> &nbsp;&nbsp;&nbsp;code =~ /^"(.\*)"$/ or raise RuntimeError, "String has no closing quotation mark."  
> &nbsp;&nbsp;&nbsp;$\_ = $1  
> &nbsp;&nbsp;&nbsp;$\_ =~ /([^\\]|(\\\\)+)"/ and raise RuntimeError, "Non-escaped \" not allowed in string #{$\_}."  
> &nbsp;&nbsp;&nbsp;gsub(/\\(.)/) { |m|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case $1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when 'b', 'f', 'n', 'r', 't'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;eval('"\\%s"' % $1)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when 'u'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m # no change, handled later  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when '"', '/', '\\'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$1 # strip \ character  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise RuntimeError, "No such escape sequence \\#{$1}."  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;gsub(/\\u([A-F0-9]{4})/i) { "%c" % $1.hex }  
> end
> 
> def parse\_number(code)  
> &nbsp;&nbsp;&nbsp;code =~ NUM\_FORMAT or raise RuntimeError, "Invalid number #{code}."  
> &nbsp;&nbsp;&nbsp;eval code  
> end
> 
> def parse\_array(code)  
> &nbsp;&nbsp;&nbsp;code =~ /^\[(.\*)\]$/ or raise RuntimeError, "No closing bracket for array #{code}."  
> &nbsp;&nbsp;&nbsp;$1.split\_stateful.collect { |m| parse(m) }  
> end
> 
> def parse\_object(code)  
> &nbsp;&nbsp;&nbsp;code =~ /^\{(.\*)\}$/ or raise RuntimeError, "No closing bracket for object #{code}."  
> &nbsp;&nbsp;&nbsp;object = {}  
> &nbsp;&nbsp;&nbsp;$1.split\_stateful.each do |m|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;key, value = m.split(":", 2)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;object[parse\_string(key.strip)] = parse(value)  
> &nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;object  
> end
> 
> def parse\_keyword(code)  
> &nbsp;&nbsp;&nbsp;case code  
> &nbsp;&nbsp;&nbsp;when 'true', 'false' then eval(code)  
> &nbsp;&nbsp;&nbsp;when 'null' then nil  
> &nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise RuntimeError, "Syntax error: #{code}."  
> &nbsp;&nbsp;&nbsp;end  
> end  
> end

> **···**
>
> Begin forwarded message:
