aboutsummaryrefslogtreecommitdiff
path: root/spec/practical/json_spec.cr
diff options
context:
space:
mode:
authorMatthew Hall <hallmatthew314@gmail.com>2023-03-27 23:04:25 +1300
committerMatthew Hall <hallmatthew314@gmail.com>2023-03-27 23:04:25 +1300
commit7ce8225660994f0b353d57d994819efd04463c35 (patch)
treef671915475d06ac0022219f28cd05e16ecb2bfc3 /spec/practical/json_spec.cr
parent66c0cef400329f1b4fb1e265738f2a58a645b9d4 (diff)
Fix json_array, the answer was pointers
Diffstat (limited to 'spec/practical/json_spec.cr')
-rw-r--r--spec/practical/json_spec.cr147
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