On Feb 4, 2008, at 7:29 PM, James Gray wrote:

> Here's my own recursive descent parser (based on the not-quite- 
> correct quiz tests):

This is a version I built some time ago when experimenting with peggy:

#!/usr/bin/env ruby -wKU

require "lib/parser"
require "lib/builder"

class JSONParser < Peggy::Builder
   KEYWORDS = {"true" => true, "false" => false, "null" => nil}
   ESCAPES  = Hash[*%W[b \b f \f n \n r \r t \t]]

   def self.parse(json_string)
     parser = self.new

     parser.source_text = json_string
     parser.parse?(:value) or raise "Failed to parse:   
#{json_string.inspect}"

     parser.to_ruby
   end

   def initialize
     super

     self.ignore_productions = [:space]
     space { lit /\s+/ }

     value {
       seq {
         opt { space }
         one {
           object
           array
           string
           number
           keyword
         }
         opt { space }
       }
     }

     object {
       seq {
         lit /\{\s*/
         one {
           seq {
             opt { many { seq { string; lit /\s*:/; value; lit /, 
\s*/ } } }
                          seq { string; lit /\s*:/; value             }
             lit "}"
           }
           lit "}"
         }
       }
     }

     array {
       seq {
         lit "["
         one {
           seq {
             opt { many { seq { value; lit "," } } }; value; lit "]"
           }
           lit "]"
         }
       }
     }

     string {
       seq {
         lit '"'
         one {
           lit '"'
           seq {
             many {
               one {
                 seq { string_content; opt { escape         } }
                 seq { escape;         opt { string_content } }
               }
             }
             lit '"'
           }
         }
       }
     }
     string_content { lit(/[^\\"]+/) }
     escape {
       one {
         escape_literal
         escape_sequence
         escape_unicode
       }
     }

     escape_literal  { lit(%r{\\["\\/]})      }
     escape_sequence { lit(/\\[bfnrt]/)       }
     escape_unicode  { lit(/\\u[0-9a-f]{4}/i) }

     number  { lit(/-?(?:0|[1-9]\d*)(?:\.\d+(?:[eE][+-]?\d+)?)?\b/) }
     keyword { lit(/\b(?:true|false|null)\b/)                       }
   end

   def to_ruby(from = parse_results.keys.min)
     kind = parse_results[from][:found_order].first
     to   = parse_results[from][kind]
     send("to_ruby_#{kind}", from, to)
   end

   private

   def to_ruby_object(from, to)
     p parse_results
     object   = Hash.new
     skip_to  = nil
     last_key = nil
     parse_results.keys.select { |k| k > from and k < to }.sort.each  
do |key|
       content = parse_results[key]
       next if skip_to and key < skip_to
       next unless content[:found_order]                      and
                   ( ( content[:found_order].size == 2        and
                       content[:found_order][1]   == :value ) or
                     content[:found_order]        == [:string] )
       if content[:found_order] == [:string]
         last_key = to_ruby_string(key, content[:string])
       else
         case content[:found_order].first
         when :object
           object[last_key] = to_ruby_object(key, content[:object])
           skip_to = content[:object]
         when :array
           object[last_key] = to_ruby_array(key, content[:array])
           skip_to = content[:array]
         else
           object[last_key] = to_ruby(key)
         end
       end
     end
     object
   end

   def to_ruby_array(from, to)
     array   = Array.new
     skip_to = nil
     parse_results.keys.select { |k| k > from and k < to }.sort.each  
do |key|
       content = parse_results[key]
       next if skip_to and key < skip_to
       next unless content[:found_order]                and
                   content[:found_order].size == 2      and
                   content[:found_order][1]   == :value
       case content[:found_order].first
       when :object
         array << to_ruby_object(key, content[:object])
         skip_to = content[:object]
       when :array
         array << to_ruby_array(key, content[:array])
         skip_to = content[:array]
       else
         array << to_ruby(key)
       end
     end
     array
   end

   def to_ruby_string(from, to)
     string = String.new
     parse_results.keys.select { |k| k > from and k < to }.sort.each  
do |key|
       content = parse_results[key]
       next unless content[:found_order]
       case content[:found_order].first
       when :string_content
         string << source_text[key...content[:string_content]]
       when :escape_literal
         string << source_text[content[:escape_literal] - 1, 1]
       when :escape_sequence
         string << ESCAPES[source_text[content[:escape_sequence] - 1,  
1]]
       when :escape_unicode
         string << [Integer("0x#{source_text[key + 2, 4]}")].pack("U")
       end
     end
     string
   end

   def to_ruby_number(from, to)
     num = source_text[from...to]
     num.include?(".") ? Float(num) : Integer(num)
   end

   def to_ruby_keyword(from, to)
     KEYWORDS[source_text[from...to]]
   end
end

__END__

I guess you can see why that library didn't win me over.  :)

James Edward Gray II