aboutsummaryrefslogtreecommitdiff
path: root/spec/practical
diff options
context:
space:
mode:
authorMatthew Hall <hallmatthew314@gmail.com>2023-03-31 23:00:50 +1300
committerMatthew Hall <hallmatthew314@gmail.com>2023-03-31 23:00:50 +1300
commit42e04f13d0968ff8d40d7e7ad3fd51c4e70eeed0 (patch)
treec97c8e1ce3fe3ef344b9b9080a0cab7085a469af /spec/practical
parent5d023a1ed0419e63615e41fd14004373e44f87ed (diff)
json example refactoring
Diffstat (limited to 'spec/practical')
-rw-r--r--spec/practical/json_spec.cr92
1 files changed, 48 insertions, 44 deletions
diff --git a/spec/practical/json_spec.cr b/spec/practical/json_spec.cr
index e4705e8..9d47b93 100644
--- a/spec/practical/json_spec.cr
+++ b/spec/practical/json_spec.cr
@@ -25,41 +25,39 @@ def assoc_to_hash(arr : Array({K, V})) : Hash(K, V) forall K, V
h
end
+def float_or_int(x : Float64) : Float64 | Int64
+ (x % 1).zero? ? x.to_i64 : x
+end
+
describe "example: JSON parsing", tags: "example" do
- json_null = Parser.token_sequence("null".chars).map_const(JSONValue.new(nil))
+ json_null = Parser.token_sequence("null".chars)
+ .map_const(JSONValue.new(nil))
+ .named("json_null")
t = Parser.token_sequence("true".chars).map_const(true)
f = Parser.token_sequence("false".chars).map_const(false)
- json_bool = (t | f).map { |b| JSONValue.new(b) }
+ json_bool = (t | f).map { |b| JSONValue.new(b) }.named("json_bool")
- digits = Parser(Char, Char).satisfy(&.ascii_number?).many
+ digits = Parser(Char, Char).satisfy(&.number?).many
sign = Parser.token('-').map_const(-1).recover(1)
point = Parser.token('.').optional
- json_number = sign.and_then do |s|
- digits.and_then do |front|
- point.and_then do |p|
- digits.and_then do |back|
- if front.empty? && back.empty?
- Parser(Char, JSONValue).flunk
- else
- x =
- case
- when front.empty? && !p.nil? # {[], '.', _} # 0 <= x < 1
- "0.#{back.join}".to_f64
- when back.empty? #{_, _, []} # whole number
- front.join.to_i64
- else #when !front.empty? && !p.nil? && !back.empty? # some other float
- "#{front.join}.#{back.join}".to_f64
- #else
- # raise "json_number, Should be unreachable: #{front} #{p} #{back}"
- end
- x = x.to_i64 if x.is_a?(Float64) && (x % 1).zero?
- Parser(Char, JSONValue).pure(JSONValue.new(x * s))
- end
- end
- end
- end
- end
+
+ json_number = parser_chain "json_number", Char, JSONValue,
+ {s, sign},
+ {front, digits},
+ {p, point},
+ {back, digits},
+ finally:
+ case {front.empty?, p.nil?, back.empty?}
+ when {true, false, false}
+ Parser(Char, Float64).pure("0.#{back.join}".to_f64)
+ when {false, _, true}
+ Parser(Char, Float64).pure(front.join.to_f64)
+ when {false, false, false}
+ Parser(Char, Float64).pure("#{front.join}.#{back.join}".to_f64)
+ else
+ Parser(Char, Float64).flunk("Could not parse '#{front.join}#{p}#{back.join}' as a number")
+ end.map { |x| JSONValue.new(float_or_int(x) * s) }
q = Parser.token('"')
s_char = Parser(Char, Char).first_of([
@@ -67,7 +65,7 @@ describe "example: JSON parsing", tags: "example" do
Parser(Char, Char).satisfy(&.!=('"')),
])
string_literal = (q >> s_char.many.map(&.join) << q)
- json_string = string_literal.map { |s| JSONValue.new(s) }
+ json_string = string_literal.map { |s| JSONValue.new(s) }.named("json_string")
# Forward-declared here to allow for mutual recursion
json_value = uninitialized Parser(Char, JSONValue)
@@ -77,27 +75,33 @@ describe "example: JSON parsing", tags: "example" do
a_sep = ws >> Parser.token(',') >> ws
a_front = Parser.token('[') >> ws
a_back = ws >> Parser.token(']')
- json_array = Parser(Char, JSONValue).new("json_array") do |tokens|
- # Has to be created and re-created here to allow mutual recursion
+
+ # Has to be created this way to allow for mutual recursion
+ non_empty_elements = Parser(Char, Array(JSONValue)).new("non_empty_elements") do |tokens|
# `a_sep.optional` matches a trailing comma in this expression
- non_empty_elements = p_json_value.value.sep_by(a_sep) << a_sep.optional
- elements = non_empty_elements.recover([] of JSONValue)
- p = (a_front >> elements << a_back).map { |es| JSONValue.new(es) }
- p.parse(tokens)
+ (p_json_value.value.sep_by(a_sep) << a_sep.optional).parse(tokens)
end
+ elements = non_empty_elements.recover([] of JSONValue)
+
+ json_array = (a_front >> elements << a_back).map { |es| JSONValue.new(es) }
+ .named("json_value")
kv_sep = ws >> Parser.token(':') >> ws
- o_sep = ws >> Parser.token(',') >> ws
+ # Has to be created this way to allow for mutual recursion
+ kv_pair = Parser(Char, {String, JSONValue}).new("kv_pair") do |tokens|
+ (string_literal + (kv_sep >> p_json_value.value)).parse(tokens)
+ end
+
+ o_sep = ws >> Parser.token(',') >> ws
o_front = Parser.token('{') >> ws
o_back = ws >> Parser.token('}')
- json_object = Parser(Char, JSONValue).new("json_object") do |tokens|
- # Has to be created and re-created here to allow mutual recursion
- kv_pair = string_literal + (kv_sep >> p_json_value.value)
- # `a_sep.optional` matches a trailing comma in this expression
- attributes = kv_pair.sep_by(o_sep) << o_sep.optional
- p = o_front >> attributes.recover([] of {String, JSONValue}) << o_back
- p.map { |pairs| JSONValue.new(assoc_to_hash(pairs)) }.parse(tokens)
- end
+
+ # `a_sep.optional` matches a trailing comma in this expression
+ non_empty_attributes = kv_pair.sep_by(o_sep) << o_sep.optional
+ o_attributes = non_empty_attributes.recover([] of {String, JSONValue})
+ json_object = (o_front >> o_attributes << o_back).map do |pairs|
+ JSONValue.new(assoc_to_hash(pairs))
+ end.named("json_object")
json_value = Parser(Char, JSONValue).new("json_value") do |tokens|
Parser(Char, JSONValue).first_of([