diff options
Diffstat (limited to 'spec/practical')
| -rw-r--r-- | spec/practical/json_spec.cr | 92 |
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([ |
