diff --git a/lib/std/json/scanner.zig b/lib/std/json/scanner.zig index 11f47f93703b295fb57f0a2b6aa002929a374245..e94c98809dd3729b3350459bc825f64c8082718e 100644 --- a/lib/std/json/scanner.zig +++ b/lib/std/json/scanner.zig @@ -35,6 +35,7 @@ const ArrayList = std.ArrayList; const ArrayListUnmanaged = std.ArrayListUnmanaged; const assert = std.debug.assert; const BitStack = std.BitStack; +const BoundedArray = std.BoundedArray; /// Scan the input and check for malformed JSON. /// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`. @@ -204,7 +205,7 @@ pub const Diagnostics = struct { current_input: []const u8 = undefined, // updated by recordContext(). - context_stack: ArrayListUnmanaged([]const u8) = .{}, + context_stack: BoundedArray([]const u8, 8) = .{}, /// Starts at 1. pub fn getLine(self: *const @This()) u64 { @@ -219,15 +220,22 @@ pub const Diagnostics = struct { return self.total_bytes_before_current_input + self.cursor_in_current_input; } - pub fn recordContext(self: *@This(), allocator: Allocator, context: []const u8) Allocator.Error!void { - return self.context_stack.append(allocator, context); + /// Attemps to push a human-readable string onto the context stack. + /// Only works up to a maximum number of times, after which this does nothing. + pub fn recordContext(self: *@This(), context: []const u8) void { + self.context_stack.append(context) catch {}; } /// Pretty-print diagnostic information to the given writer, such as `std.io.getStdErr().writer()`. - /// file_name if non-null will be printed in a line with the line and column numbers; + /// displayed_file_name if non-null will be printed in a line with the line and column numbers; /// it is purely aesthetic and is not touched on any actual file system. - pub fn dump(self: *const @This(), writer: anytype, err: anyerror, file_name: ?[]const u8) !void { - try writer.print("{s}:{}:{}: {s}\n", .{ file_name orelse "", self.getLine(), self.getColumn(), @errorName(err) }); + pub fn dump(self: *const @This(), writer: anytype, err: anyerror, displayed_file_name: ?[]const u8) !void { + try writer.print("{s}:{}:{}: {s}\n", .{ + displayed_file_name orelse "", + self.getLine(), + self.getColumn(), + @errorName(err), + }); // Show a "line" of context, or in case of very long lines, just an excerpt of the line. // (Very long lines are common in minified JSON such as in an HTTP API or other machine-to-machine contexts.) @@ -262,18 +270,12 @@ pub const Diagnostics = struct { try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start); try writer.writeAll("^\n"); - for (self.context_stack.items) |item| { + for (self.context_stack.slice()) |item| { try writer.print(" in {s}\n", .{item}); } } }; -pub inline fn maybeRecordDiagnosticContext(allocator: Allocator, maybe_diagnostics: ?*Diagnostics, context: []const u8) void { - if (maybe_diagnostics) |diag| { - diag.recordContext(allocator, context) catch {}; - } -} - /// See the documentation for `std.json.Token`. pub const AllocWhen = enum { alloc_if_needed, alloc_always }; diff --git a/lib/std/json/static.zig b/lib/std/json/static.zig index 8269c35104ff700625044ca32cd1e61b668d4107..62ebb4b7297ccfd616caf8eb7a766a392732b7a6 100644 --- a/lib/std/json/static.zig +++ b/lib/std/json/static.zig @@ -11,7 +11,6 @@ const AllocWhen = @import("./scanner.zig").AllocWhen; const Diagnostics = @import("./scanner.zig").Diagnostics; const default_max_value_len = @import("./scanner.zig").default_max_value_len; const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; -const maybeRecordDiagnosticContext = @import("./scanner.zig").maybeRecordDiagnosticContext; const Value = @import("./dynamic.zig").Value; const Array = @import("./dynamic.zig").Array; @@ -220,13 +219,13 @@ pub fn innerParse( options: ParseOptions, ) ParseError(@TypeOf(source.*))!T { errdefer source.saveDiagnostics(); - errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T)); + errdefer if (options.diagnostics) |diag| diag.recordContext(@typeName(T)); switch (@typeInfo(T)) { .Bool => { return switch (try source.next()) { .true => true, .false => false, - else => |t| return typeError(allocator, options.diagnostics, t, "bool"), + else => |t| return typeError(options.diagnostics, t, "bool"), }; }, .Float, .ComptimeFloat => { @@ -234,7 +233,7 @@ pub fn innerParse( defer freeAllocated(allocator, token); const slice = switch (token) { inline .number, .allocated_number, .string, .allocated_string => |slice| slice, - else => |t| return typeError(allocator, options.diagnostics, t, "float"), + else => |t| return typeError(options.diagnostics, t, "float"), }; return try std.fmt.parseFloat(T, slice); }, @@ -243,7 +242,7 @@ pub fn innerParse( defer freeAllocated(allocator, token); const slice = switch (token) { inline .number, .allocated_number, .string, .allocated_string => |slice| slice, - else => |t| return typeError(allocator, options.diagnostics, t, "int"), + else => |t| return typeError(options.diagnostics, t, "int"), }; return sliceToInt(T, slice); }, @@ -267,7 +266,7 @@ pub fn innerParse( defer freeAllocated(allocator, token); const slice = switch (token) { inline .number, .allocated_number, .string, .allocated_string => |slice| slice, - else => |t| return typeError(allocator, options.diagnostics, t, "enum (number or string)"), + else => |t| return typeError(options.diagnostics, t, "enum (number or string)"), }; return sliceToEnum(T, slice); }, @@ -280,7 +279,7 @@ pub fn innerParse( switch (try source.next()) { .object_begin => {}, - else => |t| return typeError(allocator, options.diagnostics, t, "union (object with one field)"), + else => |t| return typeError(options.diagnostics, t, "union (object with one field)"), } var result: ?T = null; @@ -300,7 +299,7 @@ pub fn innerParse( // void isn't really a json type, but we can support void payload union tags with {} as a value. switch (try source.next()) { .object_begin => {}, - else => |t| return typeError(allocator, options.diagnostics, t, "void payload ('{}')"), + else => |t| return typeError(options.diagnostics, t, "void payload ('{}')"), } if (.object_end != try source.next()) return error.UnknownField; result = @unionInit(T, u_field.name, {}); @@ -324,7 +323,7 @@ pub fn innerParse( if (structInfo.is_tuple) { switch (try source.next()) { .array_begin => {}, - else => |t| return typeError(allocator, options.diagnostics, t, "tuple (array of values)"), + else => |t| return typeError(options.diagnostics, t, "tuple (array of values)"), } var r: T = undefined; @@ -344,7 +343,7 @@ pub fn innerParse( switch (try source.next()) { .object_begin => {}, - else => |t| return typeError(allocator, options.diagnostics, t, "struct ('{...}')"), + else => |t| return typeError(options.diagnostics, t, "struct ('{...}')"), } var r: T = undefined; @@ -366,7 +365,7 @@ pub fn innerParse( // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object. // (Recursing into innerParse() might trigger more allocations.) freeAllocated(allocator, name_token.?); - errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T) ++ "." ++ field.name); + errdefer if (options.diagnostics) |diag| diag.recordContext(@typeName(T) ++ "." ++ field.name); name_token = null; if (fields_seen[i]) { switch (options.duplicate_field_behavior) { @@ -405,7 +404,7 @@ pub fn innerParse( return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options); }, .string => { - if (arrayInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array"); + if (arrayInfo.child != u8) return typeError(options.diagnostics, .string, "array"); // Fixed-length string. var r: T = undefined; @@ -449,7 +448,7 @@ pub fn innerParse( return r; }, - else => |t| return typeError(allocator, options.diagnostics, t, "array"), + else => |t| return typeError(options.diagnostics, t, "array"), } }, @@ -458,7 +457,7 @@ pub fn innerParse( .array_begin => { return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options); }, - else => |t| return typeError(allocator, options.diagnostics, t, "array"), + else => |t| return typeError(options.diagnostics, t, "array"), } }, @@ -497,7 +496,7 @@ pub fn innerParse( return try arraylist.toOwnedSlice(); }, .string => { - if (ptrInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array"); + if (ptrInfo.child != u8) return typeError(options.diagnostics, .string, "array"); // Dynamic length string. if (ptrInfo.sentinel) |sentinel_ptr| { @@ -519,7 +518,7 @@ pub fn innerParse( } } }, - else => |t| return typeError(allocator, options.diagnostics, t, "array"), + else => |t| return typeError(options.diagnostics, t, "array"), } }, else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"), @@ -582,24 +581,21 @@ fn coerceToTokenType(token: anytype) TokenType { .end_of_document => .end_of_document, }; } -fn typeError(allocator: Allocator, diagnostics: ?*Diagnostics, token: anytype, expected: []const u8) error{UnexpectedToken} { +fn typeError(diagnostics: ?*Diagnostics, token: anytype, comptime expected: []const u8) error{UnexpectedToken} { if (diagnostics) |diag| { - if (std.fmt.allocPrint(allocator, "expected: {s}, found: {s}", .{ - expected, - switch (coerceToTokenType(token)) { - .object_begin => "'{'", - .array_begin => "'['", - .true, .false => "bool", - .null => "null", - .number => "number", - .string => "string", - .object_end => unreachable, // type errors happen at the start of a value. - .array_end => unreachable, // type errors happen at the start of a value. - .end_of_document => unreachable, // type errors happen at the start of a value. - }, - })) |s| { - diag.recordContext(allocator, s) catch {}; - } else |_| {} + const prefix = "expected: " ++ expected ++ ", found: "; + const s = switch (coerceToTokenType(token)) { + .object_begin => prefix ++ "'{'", + .array_begin => prefix ++ "'['", + .true, .false => prefix ++ "bool", + .null => prefix ++ "null", + .number => prefix ++ "number", + .string => prefix ++ "string", + .object_end => unreachable, // type errors happen at the start of a value. + .array_end => unreachable, // type errors happen at the start of a value. + .end_of_document => unreachable, // type errors happen at the start of a value. + }; + diag.recordContext(s); } return error.UnexpectedToken; }