Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Created April 22, 2020 20:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/47eca3e9b8f48af2020c42233951dec2 to your computer and use it in GitHub Desktop.
Save JoshCheek/47eca3e9b8f48af2020c42233951dec2 to your computer and use it in GitHub Desktop.
Not sure why, but I suddenly felt compelled to make a JSON parser 🤷‍♂️
def json_parse(json)
success, index, _ws = json_parse_optional_whitespace(0, json)
success, index, value = json_parse_value(index, json)
raise "Could not parse" unless success
value
end
def json_parse_value(index, json)
%I[
json_parse_null
json_parse_true
json_parse_false
json_parse_float
json_parse_int
json_parse_string
json_parse_object
json_parse_array
].each do |name|
success, index, value = send(name, index, json)
return true, index, value if success
end
return false, index, nil
end
def json_parse_optional_whitespace(index, json)
whitespace = ""
loop do
char = json[index]
break unless char && char.match?(/\s/)
whitespace << char
index += 1
end
return whitespace != "", index, whitespace
end
def json_parse_null(index, json)
json_parse_literal index, json, "null", nil
end
def json_parse_true(index, json)
json_parse_literal index, json, "true", true
end
def json_parse_false(index, json)
json_parse_literal index, json, "false", false
end
def json_parse_literal(index, json, literal, success_value, failure_value=success_value)
success, index = json_match_literal(index, json, literal)
return success, index, success ? success_value : failure_value
end
def json_match_literal(index, json, literal)
success = literal == json[index, literal.size] # inefficient, but easy implementation
return success, index + (success ? literal.size : 0)
end
def json_parse_float(index, json)
success, maybe_index, int = json_parse_int(index, json)
return false, index, nil unless success
success, maybe_index = json_match_literal maybe_index, json, "."
return false, index, nil unless success
success, maybe_index, decimal = json_parse_unsigned_int(maybe_index, json)
return false, index, nil unless success
return true, maybe_index, "#{int}.#{decimal}".to_f # stupid impl, but w/e
end
def json_parse_int(index, json)
success, index, sign = json_parse_literal(index, json, "-", -1, 1)
success, index, value = json_parse_unsigned_int(index, json)
return success, index, sign*value
end
def json_parse_unsigned_int(index, json)
int = 0
i = index
loop do
char = json[i]
break if !char || char < '0' || '9' < char
int *= 10
int += char.to_i
i += 1
end
return i != index, i, int
end
def json_parse_string(index, json)
i = index
success, i = json_match_literal i, json, '"'
return false, index, "" unless success
str, escaped = "", false
loop do
char = json[i]
return false, index, "" unless char
if escaped
i += 1
escaped = false
case char
when "n" then str << "\n" # newline
when "t" then str << "\t" # tab
when "\\" then str << "\\" # backslash
else str << char # there are more, but I don't feel like looking them up rn
end
next
end
break if char == '"'
i += 1
if char == "\\"
escaped = true
else
str << char
end
end
success, i = json_match_literal i, json, '"'
return false, index, "" unless success
return true, i, str
end
def json_parse_object(index, json)
i = index
success, i = json_match_literal i, json, '{'
return false, index, {} unless success
# not actually sure if you can have `{"a":1,}`, this impl would allow it
object = {}
loop do
success, i, _ws = json_parse_optional_whitespace(i, json)
success, i, key = json_parse_string(i, json)
break unless success
success, i, _ws = json_parse_optional_whitespace(i, json)
success, i = json_match_literal(i, json, ':')
return false, index, {} unless success
success, i, _ws = json_parse_optional_whitespace(i, json)
success, i, val = json_parse_value(i, json)
return false, index, {} unless success
object[key] = val
success, i, _ws = json_parse_optional_whitespace(i, json)
success, i = json_match_literal(i, json, ',')
break unless success
end
success, i = json_match_literal(i, json, '}')
return false, index, {} unless success
return true, i, object
end
def json_parse_array(index, json)
i = index
success, i = json_match_literal i, json, '['
return false, index, [] unless success
# not actually sure if you can have `{"a":1,}`, this impl would allow it
array = []
loop do
success, i, _ws = json_parse_optional_whitespace(i, json)
success, i, val = json_parse_value(i, json)
break unless success
array << val
success, i, _ws = json_parse_optional_whitespace(i, json)
success, i = json_match_literal(i, json, ',')
break unless success
end
success, i = json_match_literal(i, json, ']')
return false, index, [] unless success
return true, i, array
end
require 'json'
json = JSON.dump({
a: [1, 10, true, false, nil, [], [2,3], {}, {x: "y"}],
b: "str",
c: {obj: "val"},
d: {},
e: [],
f: 123.456,
g: true,
h: false,
i: nil,
j: -123,
k: -123.456,
l: 'abc"def',
m: 'abc\\"def',
})
JSON.parse(json) == json_parse(json) # => true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment