From 7634947b2274bf7297709d90522093dcba7b9f3c Mon Sep 17 00:00:00 2001 From: Matthew Hall Date: Sun, 26 Mar 2023 16:29:41 +1300 Subject: Fully implement json_number --- spec/practical/json_spec.cr | 118 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 106 insertions(+), 12 deletions(-) diff --git a/spec/practical/json_spec.cr b/spec/practical/json_spec.cr index 95795e2..43455da 100644 --- a/spec/practical/json_spec.cr +++ b/spec/practical/json_spec.cr @@ -30,12 +30,45 @@ def json_bool end # Only standard decimal ints for now +#def json_number +# base_num = Parser(Char, Char).satisfy(&.ascii_number?).some.map do |cs| +# cs.join.to_i64 +# end +# sign = Parser.token('-').map_const(-1).recover(1) +# (sign + base_num).map { |s, n| JSONValue.new(s.to_i64 * n) } +#end + def json_number - base_num = Parser(Char, Char).satisfy(&.ascii_number?).some.map do |cs| - cs.join.to_i64 - end + digits = Parser(Char, Char).satisfy(&.ascii_number?).many sign = Parser.token('-').map_const(-1).recover(1) - (sign + base_num).map { |s, n| JSONValue.new(s.to_i64 * n) } + point = Parser.token('.').optional + + 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 end def json_value : Parser(Char, JSONValue) @@ -72,15 +105,62 @@ describe "example: JSON parsing" do end describe "json_number" do - it "parses strings of digits and other key characters and converts to number" do - result = json_number.parse(Tokens.from_string("42")) - result.value.data.should eq(42) - result.tokens.empty?.should be_true + it "parses positive and negative integers" do + {"42", "42.", "42.000000"}.each do |s| + result = json_number.parse(Tokens.from_string(s)) + result.value.data.should eq(42) + result.value.data.should be_a(Int64) + result.tokens.empty?.should be_true + + result = json_number.parse(Tokens.from_string("-#{s}")) + result.value.data.should eq(-42) + result.value.data.should be_a(Int64) + result.tokens.empty?.should be_true + end + end - result = json_number.parse(Tokens.from_string("-42")) - result.value.data.should eq(-42) - result.tokens.empty?.should be_true + it "parses positive and negative floats between -1 and 1" do + {"0.1234", ".1234", ".12340000"}.each do |s| + result = json_number.parse(Tokens.from_string(s)) + result.value.data.should eq(0.1234) + result.value.data.should be_a(Float64) + result.tokens.empty?.should be_true + + result = json_number.parse(Tokens.from_string("-#{s}")) + result.value.data.should eq(-0.1234) + result.value.data.should be_a(Float64) + result.tokens.empty?.should be_true + end + end + it "parses positive and negative floats with a whole-number component" do + {"12.34", "0012.3400"}.each do |s| + result = json_number.parse(Tokens.from_string(s)) + result.value.data.should eq(12.34) + result.value.data.should be_a(Float64) + result.tokens.empty?.should be_true + + result = json_number.parse(Tokens.from_string("-#{s}")) + result.value.data.should eq(-12.34) + result.value.data.should be_a(Float64) + result.tokens.empty?.should be_true + end + end + + it "parses 0 as an int in various forms" do + {"0", "0.0", "0000", ".000", "000.", "-0", "-0.", "-.0", "-0.0"}.each do |s| + result = json_number.parse(Tokens.from_string(s)) + result.value.data.should eq(0) + result.value.data.should be_a(Int64) + result.tokens.empty?.should be_true + end + end + + it "does not parse '.' or '-.' as 0" do + expect_raises(ParserFail) { json_number.parse(Tokens.from_string(".")) } + end + + it "fails when input if not a number" do expect_raises(ParserFail) { json_number.parse(Tokens.from_string("")) } expect_raises(ParserFail) { json_number.parse(Tokens.from_string("foo")) } end @@ -103,13 +183,27 @@ describe "example: JSON parsing" do result.tokens.empty?.should be_true end - it "parsers ints" do + it "parses ints" do result = json_value.parse(Tokens.from_string("42")) result.value.data.should eq(42) + result.value.data.should be_a(Int64) result.tokens.empty?.should be_true result = json_value.parse(Tokens.from_string("-42")) result.value.data.should eq(-42) + result.value.data.should be_a(Int64) + result.tokens.empty?.should be_true + end + + it "parses floats" do + result = json_value.parse(Tokens.from_string("12.34")) + result.value.data.should eq(12.34) + result.value.data.should be_a(Float64) + result.tokens.empty?.should be_true + + result = json_value.parse(Tokens.from_string("-12.34")) + result.value.data.should eq(-12.34) + result.value.data.should be_a(Float64) result.tokens.empty?.should be_true end end -- cgit v1.2.1