diff options
| author | Matthew Hall <hallmatthew314@gmail.com> | 2023-03-27 23:04:25 +1300 |
|---|---|---|
| committer | Matthew Hall <hallmatthew314@gmail.com> | 2023-03-27 23:04:25 +1300 |
| commit | 7ce8225660994f0b353d57d994819efd04463c35 (patch) | |
| tree | f671915475d06ac0022219f28cd05e16ecb2bfc3 /spec | |
| parent | 66c0cef400329f1b4fb1e265738f2a58a645b9d4 (diff) | |
Fix json_array, the answer was pointers
Diffstat (limited to 'spec')
| -rw-r--r-- | spec/practical/json_spec.cr | 147 |
1 files changed, 102 insertions, 45 deletions
diff --git a/spec/practical/json_spec.cr b/spec/practical/json_spec.cr index 0ae7920..fe31ea9 100644 --- a/spec/practical/json_spec.cr +++ b/spec/practical/json_spec.cr @@ -19,31 +19,17 @@ struct JSONValue end end -def json_null - Parser.token_sequence("null".chars).map_const(JSONValue.new(nil)) -end +describe "example: JSON parsing" do + json_null = Parser.token_sequence("null".chars).map_const(JSONValue.new(nil)) -def json_bool t = Parser.token_sequence("true".chars).map_const(true) f = Parser.token_sequence("false".chars).map_const(false) - (t | f).map { |b| JSONValue.new(b) } -end + json_bool = (t | f).map { |b| JSONValue.new(b) } -# 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 digits = Parser(Char, Char).satisfy(&.ascii_number?).many sign = Parser.token('-').map_const(-1).recover(1) point = Parser.token('.').optional - - sign.and_then do |s| + json_number = sign.and_then do |s| digits.and_then do |front| point.and_then do |p| digits.and_then do |back| @@ -61,7 +47,6 @@ def json_number #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 @@ -69,40 +54,46 @@ def json_number end end end -end -def json_string q = Parser.token('"') s_char = Parser(Char, Char).first_of([ Parser.token_sequence(['\\', '"']).map_const('"'), Parser(Char, Char).satisfy(&.!=('"')), ]) - (q >> s_char.many << q).map { |cs| JSONValue.new(cs.join) } -end - -def ws - Parser(Char, Char).satisfy(&.whitespace?).many -end + string_literal = (q >> s_char.many << q) + json_string = string_literal.map { |cs| JSONValue.new(cs.join) } + + json_array = uninitialized Parser(Char, JSONValue) + #json_object = uninitialized Parser(Char, JSONValue) + json_value = uninitialized Parser(Char, JSONValue) + + p_json_array = pointerof(json_array) + #p_json_object = pointerof(json_object) + p_json_value = pointerof(json_value) + + ws = Parser(Char, Char).satisfy(&.whitespace?).many + a_delim = 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 due to mutual recursion + # `a_delim.optional` matches a trailing comma + non_empty_elements = p_json_value.value.sep_by(a_delim) << a_delim.optional + elements = non_empty_elements.recover([] of JSONValue) + p = (a_front >> elements << a_back).map { |es| JSONValue.new(es) } + p.parse(tokens) + end -def json_array - delim = ws >> Parser.token(',') >> ws - elements = json_value.sep_by(delim) - front = Parser.token('[') >> ws - back = ws >> Parser.token(']') - (front >> elements << back).map { |es| JSONValue.new(es) } -end - -def json_value : Parser(Char, JSONValue) - Parser.first_of([ - json_null, - json_bool, - json_number, - json_string, - json_array, - ]) -end + json_value = Parser(Char, JSONValue).new("json_value") do |tokens| + Parser(Char, JSONValue).first_of([ + json_null, + json_bool, + json_number, + json_string, + json_array, + ]).parse(tokens) + end -describe "example: JSON parsing" do describe "json_null" do it "parses the string 'null' and returns nil when successful" do result = json_null.parse(Tokens.from_string("null")) @@ -209,6 +200,64 @@ describe "example: JSON parsing" do end end + describe "json_array" do + it "can parse empty arrays" do + empty = JSONValue.new([] of JSONValue) + {"[]", "[ \t\n\n ]"}.each do |s| + tokens = Tokens.from_string(s) + result = json_array.parse(tokens) + result.value.should eq(empty) + result.tokens.empty?.should be_true + end + end + + it "can parse arrays with one element" do + {"[null]", "[ \n1]", "[0.123 \n]", "[true,]", "[false , ]", "[\"a string\"]"}.each do |s| + tokens = Tokens.from_string(s) + result = json_array.parse(tokens) + result.value.data.should be_a(Array(JSONValue)) + result.value.data.as(Array(JSONValue)).size.should eq(1) + result.tokens.empty?.should be_true + end + end + + it "can parse arrays with many elements" do + {"[\"one\" , \"two\"]", "[1,2,3,4,]", "[ null\n, false\n, true\n]"}.each do |s| + tokens = Tokens.from_string(s) + # TODO: come up with better test format + result = json_array.parse(tokens) + result.value.data.as(Array(JSONValue)).size.>(1).should be_true + result.tokens.empty?.should be_true + end + end + + it "can parse nested arrays" do + expected = JSONValue.new([ + JSONValue.new([] of JSONValue), + JSONValue.new([ + JSONValue.new(nil), + JSONValue.new(false), + JSONValue.new("two"), + JSONValue.new(3_i64), + JSONValue.new(4.1_f64), + JSONValue.new([] of JSONValue), + ]), + JSONValue.new([JSONValue.new(5_i64)]), + ]) + s = "[ [], [null, false, \"two\", 3, 4.1, [],] ,[5]]" + tokens = Tokens.from_string(s) + result = json_array.parse(tokens) + result.value.should eq(expected) + result.tokens.empty?.should be_true + end + + it "does not allow trailing commas in empty arrays" do + {"[,]", "[ , ]"}.each do |s| + expect_raises(ParserFail) { json_array.parse(Tokens.from_string(s)) } + end + end + end + describe "json_value" do it "parses null" do result = json_value.parse(Tokens.from_string("null")) @@ -255,6 +304,14 @@ describe "example: JSON parsing" do result.value.data.should eq("foo") result.tokens.empty?.should be_true end + + it "parses arrays" do + {"[]", "[ null, ]", "[1, 2, 3, 4.1, 5]"}.each do |s| + result = json_value.parse(Tokens.from_string(s)) + result.value.data.should be_a(Array(JSONValue)) + result.tokens.empty?.should be_true + end + end end end |
