diff --git a/json.zig b/json.zig index b57e44a7f4d2eed8c530ca3f8593ecf0b2a32435..3aaf89000306fb8feedd1b5750c7c1e135e31d63 100644 --- a/json.zig +++ b/json.zig @@ -6,13 +6,13 @@ const tracer = @import("tracer"); const Error = std.mem.Allocator.Error; const ObjectHashMap = std.AutoArrayHashMapUnmanaged(StringIndex, ValueIndex); -pub fn parse(alloc: std.mem.Allocator, path: string, inreader: anytype) anyerror!Document { +pub fn parse(alloc: std.mem.Allocator, path: string, inreader: anytype, options: Parser.Options) anyerror!Document { const t = tracer.trace(@src(), "", .{}); defer t.end(); _ = path; - var p = Parser.init(alloc, inreader.any()); + var p = Parser.init(alloc, inreader.any(), options); defer p.temp.deinit(alloc); defer p.strings_map.deinit(alloc); errdefer p.extras.deinit(alloc); @@ -66,17 +66,22 @@ fn parseObject(alloc: std.mem.Allocator, p: *Parser) anyerror!?ValueIndex { const t = tracer.trace(@src(), "", .{}); defer t.end(); + p.depth += 1; + defer p.depth -= 1; + if (p.depth > p.maximum_depth) return error.JsonExpectedTODO; + _ = try p.eatByte('{') orelse return null; + try parseWs(p); var sfa = std.heap.stackFallback(std.mem.page_size, alloc); const alloc_local = sfa.get(); var members = ObjectHashMap{}; defer members.deinit(alloc_local); + if (try p.eatByte('}')) |_| { + return try p.addObject(alloc, &members); + } while (true) { - try parseWs(p); - if (try p.eatByte('}')) |_| break; - const key = try parseString(alloc, p) orelse return error.JsonExpectedObjectKey; try parseWs(p); _ = try p.eatByte(':') orelse return error.JsonExpectedObjectColon; @@ -89,6 +94,7 @@ fn parseObject(alloc: std.mem.Allocator, p: *Parser) anyerror!?ValueIndex { break; }; try parseWs(p); + if (!p.support_trailing_commas) continue; if (try p.eatByte('}')) |_| break; } return try p.addObject(alloc, &members); @@ -98,17 +104,22 @@ fn parseArray(alloc: std.mem.Allocator, p: *Parser) anyerror!?ValueIndex { const t = tracer.trace(@src(), "", .{}); defer t.end(); + p.depth += 1; + defer p.depth -= 1; + if (p.depth > p.maximum_depth) return error.JsonExpectedTODO; + _ = try p.eatByte('[') orelse return null; + try parseWs(p); var sfa = std.heap.stackFallback(std.mem.page_size, alloc); const alloc_local = sfa.get(); var elements = std.ArrayListUnmanaged(ValueIndex){}; defer elements.deinit(alloc_local); + if (try p.eatByte(']')) |_| { + return try p.addArray(alloc, elements.items); + } while (true) { - try parseWs(p); - if (try p.eatByte(']')) |_| break; - const elem = try parseValue(alloc, p); try elements.append(alloc_local, elem); try parseWs(p); @@ -117,6 +128,7 @@ fn parseArray(alloc: std.mem.Allocator, p: *Parser) anyerror!?ValueIndex { break; }; try parseWs(p); + if (!p.support_trailing_commas) continue; if (try p.eatByte(']')) |_| break; } return try p.addArray(alloc, elements.items); @@ -237,14 +249,25 @@ const Parser = struct { col: usize = 1, extras: std.ArrayListUnmanaged(u8) = .{}, strings_map: std.StringArrayHashMapUnmanaged(StringIndex) = .{}, + depth: u16 = 0, - pub fn init(allocator: std.mem.Allocator, any: std.io.AnyReader) Parser { + support_trailing_commas: bool, + maximum_depth: u16, + + pub fn init(allocator: std.mem.Allocator, any: std.io.AnyReader, options: Options) Parser { return .{ .any = any, .arena = allocator, + .support_trailing_commas = options.support_trailing_commas, + .maximum_depth = options.maximum_depth, }; } + pub const Options = struct { + support_trailing_commas: bool, + maximum_depth: u16, + }; + pub fn avail(p: *Parser) usize { return p.temp.items.len - p.idx; } diff --git a/test.zig b/test.zig index 90c1aec2750f31a0ff93f7ad561cc923105e48e7..1ac0ad0d617a4f194680d2f95a8f2ac5095414b8 100644 --- a/test.zig +++ b/test.zig @@ -4,7 +4,7 @@ const json = @import("json"); fn parse_full(buffer: []const u8) !void { const alloc = std.testing.allocator; var fbs = std.io.fixedBufferStream(buffer); - var doc = try json.parse(alloc, "", fbs.reader()); + var doc = try json.parse(alloc, "", fbs.reader(), .{ .support_trailing_commas = true, .maximum_depth = 100 }); defer doc.deinit(alloc); } @@ -264,7 +264,7 @@ fn expectPass(path: []const u8) !void { const alloc = std.testing.allocator; var file = try std.fs.cwd().openFile(path, .{}); defer file.close(); - var doc = try json.parse(alloc, path, file.reader()); + var doc = try json.parse(alloc, path, file.reader(), .{ .support_trailing_commas = false, .maximum_depth = 100 }); defer doc.deinit(alloc); } @@ -370,7 +370,7 @@ fn expectFail(path: []const u8) !void { const alloc = std.testing.allocator; var file = try std.fs.cwd().openFile(path, .{}); defer file.close(); - var doc = json.parse(alloc, path, file.reader()) catch return; + var doc = json.parse(alloc, path, file.reader(), .{ .support_trailing_commas = false, .maximum_depth = 100 }) catch return; defer doc.deinit(alloc); try std.testing.expect(false); } @@ -384,7 +384,7 @@ test { try expectFail(JSONTestSuite_root ++ "/n_array_comma_and_number.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_double_comma.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_double_extra_comma.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_extra_close.json"); } -test { try expectPass(JSONTestSuite_root ++ "/n_array_extra_comma.json"); } +test { try expectFail(JSONTestSuite_root ++ "/n_array_extra_comma.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_incomplete_invalid_value.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_incomplete.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_inner_array_no_comma.json"); } @@ -394,7 +394,7 @@ test { try expectFail(JSONTestSuite_root ++ "/n_array_just_comma.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_just_minus.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_missing_value.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_newlines_unclosed.json"); } -test { try expectPass(JSONTestSuite_root ++ "/n_array_number_and_comma.json"); } +test { try expectFail(JSONTestSuite_root ++ "/n_array_number_and_comma.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_number_and_several_commas.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_spaces_vertical_tab_formfeed.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_array_star_inside.json"); } @@ -475,7 +475,7 @@ test { try expectFail(JSONTestSuite_root ++ "/n_object_non_string_key.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_object_repeated_null_null.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_object_several_trailing_commas.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_object_single_quote.json"); } -test { try expectPass(JSONTestSuite_root ++ "/n_object_trailing_comma.json"); } +test { try expectFail(JSONTestSuite_root ++ "/n_object_trailing_comma.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_object_trailing_comment.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_object_trailing_comment_open.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_object_trailing_comment_slash_open_incomplete.json"); } @@ -515,7 +515,7 @@ test { try expectFail(JSONTestSuite_root ++ "/n_string_unescaped_newline.json"); test { try expectFail(JSONTestSuite_root ++ "/n_string_unescaped_tab.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_string_unicode_CapitalU.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_string_with_trailing_garbage.json"); } -// test { try expectFail(JSONTestSuite_root ++ "/n_structure_100000_opening_arrays.json"); } +test { try expectFail(JSONTestSuite_root ++ "/n_structure_100000_opening_arrays.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_structure_angle_bracket_..json"); } test { try expectFail(JSONTestSuite_root ++ "/n_structure_angle_bracket_null.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_structure_array_trailing_garbage.json"); } @@ -539,7 +539,7 @@ test { try expectFail(JSONTestSuite_root ++ "/n_structure_object_with_comment.js test { try expectFail(JSONTestSuite_root ++ "/n_structure_object_with_trailing_garbage.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_structure_open_array_apostrophe.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_structure_open_array_comma.json"); } -// test { try expectFail(JSONTestSuite_root ++ "/n_structure_open_array_object.json"); } +test { try expectFail(JSONTestSuite_root ++ "/n_structure_open_array_object.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_structure_open_array_open_object.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_structure_open_array_open_string.json"); } test { try expectFail(JSONTestSuite_root ++ "/n_structure_open_array_string.json"); }