diff --git a/lib/std/json/scanner.zig b/lib/std/json/scanner.zig index 54b661113b00419db87eb27d9f62dd9dec539d00..c452f0ee9789d9825d49702415dc6543d9179196 100644 --- a/lib/std/json/scanner.zig +++ b/lib/std/json/scanner.zig @@ -196,7 +196,12 @@ pub const Diagnostics = struct { line_number: u64 = 1, line_start_cursor: usize = @as(usize, @bitCast(@as(isize, -1))), // Start just "before" the input buffer to get a 1-based column for line 1. total_bytes_before_current_input: u64 = 0, - cursor_pointer: *const usize = undefined, + /// While the source is operational, this is a pointer into it. + /// If the source is destroyed, this becomes a literal value. + cursor: union(enum) { + pointer: *const usize, + value: usize, + } = undefined, /// Starts at 1. pub fn getLine(self: *const @This()) u64 { @@ -204,11 +209,22 @@ pub const Diagnostics = struct { } /// Starts at 1. pub fn getColumn(self: *const @This()) u64 { - return self.cursor_pointer.* -% self.line_start_cursor; + return self.getCursor() -% self.line_start_cursor; } /// Starts at 0. Measures the byte offset since the start of the input. pub fn getByteOffset(self: *const @This()) u64 { - return self.total_bytes_before_current_input + self.cursor_pointer.*; + return self.total_bytes_before_current_input + self.getCursor(); + } + + fn getCursor(self: *const @This()) usize { + return switch (self.cursor) { + .pointer => |p| p.*, + .value => |v| v, + }; + } + fn saveCursor(self: *@This()) void { + const value = self.getCursor(); + self.cursor = .{ .value = value }; } }; @@ -244,6 +260,10 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type { pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { self.scanner.enableDiagnostics(diagnostics); } + /// Calls `std.json.Scanner.saveDiagnostics`. + pub fn saveDiagnostics(self: *const @This()) void { + self.scanner.saveDiagnostics(); + } pub const NextError = ReaderType.Error || Error || Allocator.Error; pub const SkipError = NextError; @@ -446,10 +466,20 @@ pub const Scanner = struct { self.* = undefined; } + /// See also `saveDiagnostics()`. pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { - diagnostics.cursor_pointer = &self.cursor; + diagnostics.cursor = .{ .pointer = &self.cursor }; + std.log.warn("cursor(enableDiagnostics): {}", .{diagnostics.getCursor()}); self.diagnostics = diagnostics; } + /// Call this just before `deinit()` to make the diagnostics available after the `deinit()`. + pub fn saveDiagnostics(self: *const @This()) void { + if (self.diagnostics) |diag| { + std.log.warn("cursor(deinit presave): {}", .{diag.getCursor()}); + diag.saveCursor(); + std.log.warn("cursor(deinit postsave): {}", .{diag.getCursor()}); + } + } /// Call this whenever you get `error.BufferUnderrun` from `next()`. /// When there is no more input to provide, call `endInput()`. diff --git a/lib/std/json/static.zig b/lib/std/json/static.zig index ea0bb6c0f2c12ee42eef33e9e60113eb41b648e7..001fabf6b2ceb38064cc76fe848d857cfa3ad1f9 100644 --- a/lib/std/json/static.zig +++ b/lib/std/json/static.zig @@ -7,6 +7,7 @@ const ArrayList = std.ArrayList; const Scanner = @import("./scanner.zig").Scanner; const Token = @import("./scanner.zig").Token; 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; @@ -42,6 +43,10 @@ pub const ParseOptions = struct { /// The default with a `*std.json.Reader` input is `.alloc_always`. /// Ignored for `parseFromValue` and `parseFromValueLeaky`. allocate: ?AllocWhen = null, + + /// Enable diagnostics with `var diagnostics: json.Diagnostics = undefined;`, + /// and set this option to `&diagnostics`. + diagnostics: ?*Diagnostics = null, }; pub fn Parsed(comptime T: type) type { @@ -136,6 +141,14 @@ pub fn parseFromTokenSourceLeaky( resolved_options.allocate = .alloc_always; } } + if (resolved_options.diagnostics) |diag| { + scanner_or_reader.enableDiagnostics(diag); + } + defer { + if (resolved_options.diagnostics) |_| { + scanner_or_reader.saveDiagnostics(); + } + } const value = try innerParse(T, allocator, scanner_or_reader, resolved_options); diff --git a/lib/std/json/static_test.zig b/lib/std/json/static_test.zig index 1c89e05226ed066dd6eecdbdeebd19c93b3650aa..5ba3a1e797adced80a066af02a6154200299489a 100644 --- a/lib/std/json/static_test.zig +++ b/lib/std/json/static_test.zig @@ -925,3 +925,18 @@ test "parse at comptime" { }; comptime testing.expectEqual(@as(u64, 9999), config.uptime) catch unreachable; } + +test "parse with diagnostics" { + const doc = + \\{ + \\ "common": "mistake", + \\} + ; + var diagnostics: Diagnostics = undefined; + try testing.expectError(error.SyntaxError, parseFromSlice(Value, testing.allocator, doc, .{ + .diagnostics = &diagnostics, + })); + try std.testing.expectEqual(3, diagnostics.getLine()); + try std.testing.expectEqual(1, diagnostics.getColumn()); + try std.testing.expectEqual(25, diagnostics.getByteOffset()); +}