Skip to content

Commit

Permalink
rubyquiz 115 json solutions
Browse files Browse the repository at this point in the history
  • Loading branch information
John Griffiths committed Mar 17, 2009
1 parent ea21b9a commit 141095d
Show file tree
Hide file tree
Showing 32 changed files with 2,533 additions and 0 deletions.
94 changes: 94 additions & 0 deletions solutions/Alexander Stedile/json_parser.rb
@@ -0,0 +1,94 @@
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
20 changes: 20 additions & 0 deletions solutions/Clifford Heath/interactive_json.rb
@@ -0,0 +1,20 @@
require 'treetop'
require 'json' # Note that we can require the Treetop file directly.
require 'readline'

parser = JsonParser.new
while line = Readline::readline("? ", [])
begin
tree = parser.parse(line)
if tree
p tree.obj
else
puts parser.failure_reason
end
rescue => e
puts e
p e.backtrace
p tree if tree
end
end
puts
103 changes: 103 additions & 0 deletions solutions/Clifford Heath/json.treetop
@@ -0,0 +1,103 @@
# Treetop grammar for JSON for Ruby Quiz #155 by Clifford Heath.
grammar Json
rule json
value
end

rule object
'{' s pairs:pairs? s '}' s
{ def obj
pairs.empty? ? {} : pairs.obj
end
}
end

rule pairs
member rest:(s ',' s member)*
{ def obj
rest.elements.inject({eval(member.k.text_value) => member.value.obj}) { |h, e|
h[eval(e.member.k.text_value)] = e.member.value.obj
h
}
end
}
end

rule member # key/value pair of an object
k:string s ':' s value
end

rule array
'[' s e:elements? s ']'
{ def obj
e.empty? ? [] : e.obj
end
}
end

rule elements # elements of an array
value rest:(s ',' s value)*
{ def obj
rest.elements.inject([value.obj]) { |a, e|
a << e.value.obj
}
end
}
end

rule value
s alt:(string / number / object / array
/ 'true' { def obj; true; end }
/ 'false' { def obj; false; end }
/ 'null' { def obj; nil; end }
)
{ def obj; alt.obj; end }
end

rule string
'"' char* '"' { def obj
eval(
# Strip Unicode characters down to the chr equivalent.
# Note that I'm cheating here: '"\\u4321"' should assert,
# and there are cases that will succeed but corrupt the data.
# This should be handled in the "char" rule.
text_value.gsub(/\\u..../) { |unicode|
eval("0x"+unicode[2..-1]).chr
}
)
end
}
end

rule char
'\\' [\"\\\/bfnrt]
/ '\\u' hex hex hex hex
/ (![\\"] .)
end

rule hex
[0-9A-Fa-f]
end

rule number
int frac? exp? { def obj; eval(text_value); end }
end

rule int # Any integer
'-'? ([1-9] [0-9]* / '0')
{ def obj; eval(text_value); end }
end

rule frac # The fractional part of a floating-point number
'.' [0-9]+
end

rule exp # An exponent
[eE] [-+]? [0-9]+
end

rule s # Any amount of whtespace
[ \t\n\t]*
end

end
8 changes: 8 additions & 0 deletions solutions/Clifford Heath/json_parser.rb
@@ -0,0 +1,8 @@
class JSONParser
def parse(text)
parser = JsonParser.new
p = parser.parse(text)
raise parser.failure_reason unless p
p.obj
end
end

0 comments on commit 141095d

Please sign in to comment.