Created
May 17, 2011 23:57
-
-
Save statianzo/977699 to your computer and use it in GitHub Desktop.
A json parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "strscan" | |
class JSONParser | |
AST = Struct.new(:value) | |
def parse(input) | |
@input = StringScanner.new(input) | |
parse_value.value | |
ensure | |
@input.eos? or error("Unexpected data") | |
end | |
private | |
#comment is back | |
def parse_value | |
trim_space | |
parse_object or | |
parse_array or | |
parse_string or | |
parse_number or | |
parse_keyword or | |
error("Invalid Data") | |
ensure | |
trim_space | |
end | |
#Parses colon separated object hashes | |
def parse_object | |
if @input.scan(/\s*\{/) | |
obj = Hash.new | |
more_parts = false | |
while key = parse_string | |
@input.scan(/\s*:\s*/) or error("Expected : separator") | |
obj[key.value] = parse_value.value | |
more_parts = @input.scan(/\s*,\s*/) or break | |
end | |
error("Missing object pair") if more_parts | |
@input.scan(/\s*}/) or error("Unclosed object") | |
AST.new(obj) | |
else | |
false | |
end | |
end | |
def parse_array | |
if @input.scan(/\s*\[/) #Arrays start with [ | |
a = Array.new | |
more_items = false | |
while current = parse_value | |
a << current.value | |
more_items = @input.scan(/\s*,\s*/) or break | |
end | |
error("Missing value") if more_items | |
@input.scan(/s*\]/) or error("Unclosed array") | |
AST.new(a) | |
end | |
end | |
#Parses string objects | |
def parse_string | |
if @input.scan(/"/) | |
s = String.new | |
while current = parse_string_content || parse_string_escape | |
s << current.value | |
end | |
@input.scan(/"/) or error('Unclosed String') | |
AST.new(s) | |
else | |
false | |
end | |
end | |
def parse_string_content | |
@input.scan(/[^\\"]+/) and AST.new(@input.matched) | |
end | |
def parse_string_escape | |
if @input.scan(%r{\\["\\/]}) #slashes and quotations | |
AST.new(@input.matched[-1]) | |
elsif @input.scan(/\\[bfnrt]/) #newlines, tabs, etc | |
AST.new(eval(%Q{"#{@input.matched}"})) | |
elsif @input.scan(/\\u[0-9a-fA-F]{4}/) #Hex integers | |
AST.new([Integer("0x#{@input.matched[2..-1]}")].pack("U")) | |
else | |
false | |
end | |
end | |
def parse_number | |
@input.scan(/-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?\b/) and | |
AST.new(eval(@input.matched)) | |
end | |
def parse_keyword | |
@input.scan(/\b(?:true|false|null)\b/) and | |
AST.new(eval(@input.matched.sub("null","nil"))) | |
end | |
def trim_space | |
@input.scan(/\s+/) | |
end | |
def error(message) | |
raise "#{message}: #{@input.peek(@input.string.length)}" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment