authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2024-04-27 09:58:32-04:00
committergravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2024-05-29 08:22:00-04:00
loge60cd8058091e6c97743cd56206072ba5d1d8fce
tree57348386f02a72126a50dde69a99f27f5133e5b4
parentd750a78b2c0265bd7bb2b924c4c739cad5aa6666

first attempt


3 files changed, 62 insertions(+), 4 deletions(-)

lib/std/json/scanner.zig+34-4
......@@ -196,7 +196,12 @@ pub const Diagnostics = struct {
196196 line_number: u64 = 1,
197197 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.
198198 total_bytes_before_current_input: u64 = 0,
199 cursor_pointer: *const usize = undefined,
199 /// While the source is operational, this is a pointer into it.
200 /// If the source is destroyed, this becomes a literal value.
201 cursor: union(enum) {
202 pointer: *const usize,
203 value: usize,
204 } = undefined,
200205
201206 /// Starts at 1.
202207 pub fn getLine(self: *const @This()) u64 {
......@@ -204,11 +209,22 @@ pub const Diagnostics = struct {
204209 }
205210 /// Starts at 1.
206211 pub fn getColumn(self: *const @This()) u64 {
207 return self.cursor_pointer.* -% self.line_start_cursor;
212 return self.getCursor() -% self.line_start_cursor;
208213 }
209214 /// Starts at 0. Measures the byte offset since the start of the input.
210215 pub fn getByteOffset(self: *const @This()) u64 {
211 return self.total_bytes_before_current_input + self.cursor_pointer.*;
216 return self.total_bytes_before_current_input + self.getCursor();
217 }
218
219 fn getCursor(self: *const @This()) usize {
220 return switch (self.cursor) {
221 .pointer => |p| p.*,
222 .value => |v| v,
223 };
224 }
225 fn saveCursor(self: *@This()) void {
226 const value = self.getCursor();
227 self.cursor = .{ .value = value };
212228 }
213229};
214230
......@@ -244,6 +260,10 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type {
244260 pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void {
245261 self.scanner.enableDiagnostics(diagnostics);
246262 }
263 /// Calls `std.json.Scanner.saveDiagnostics`.
264 pub fn saveDiagnostics(self: *const @This()) void {
265 self.scanner.saveDiagnostics();
266 }
247267
248268 pub const NextError = ReaderType.Error || Error || Allocator.Error;
249269 pub const SkipError = NextError;
......@@ -446,10 +466,20 @@ pub const Scanner = struct {
446466 self.* = undefined;
447467 }
448468
469 /// See also `saveDiagnostics()`.
449470 pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void {
450 diagnostics.cursor_pointer = &self.cursor;
471 diagnostics.cursor = .{ .pointer = &self.cursor };
472 std.log.warn("cursor(enableDiagnostics): {}", .{diagnostics.getCursor()});
451473 self.diagnostics = diagnostics;
452474 }
475 /// Call this just before `deinit()` to make the diagnostics available after the `deinit()`.
476 pub fn saveDiagnostics(self: *const @This()) void {
477 if (self.diagnostics) |diag| {
478 std.log.warn("cursor(deinit presave): {}", .{diag.getCursor()});
479 diag.saveCursor();
480 std.log.warn("cursor(deinit postsave): {}", .{diag.getCursor()});
481 }
482 }
453483
454484 /// Call this whenever you get `error.BufferUnderrun` from `next()`.
455485 /// When there is no more input to provide, call `endInput()`.
lib/std/json/static.zig+13
......@@ -7,6 +7,7 @@ const ArrayList = std.ArrayList;
77const Scanner = @import("./scanner.zig").Scanner;
88const Token = @import("./scanner.zig").Token;
99const AllocWhen = @import("./scanner.zig").AllocWhen;
10const Diagnostics = @import("./scanner.zig").Diagnostics;
1011const default_max_value_len = @import("./scanner.zig").default_max_value_len;
1112const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
1213
......@@ -42,6 +43,10 @@ pub const ParseOptions = struct {
4243 /// The default with a `*std.json.Reader` input is `.alloc_always`.
4344 /// Ignored for `parseFromValue` and `parseFromValueLeaky`.
4445 allocate: ?AllocWhen = null,
46
47 /// Enable diagnostics with `var diagnostics: json.Diagnostics = undefined;`,
48 /// and set this option to `&diagnostics`.
49 diagnostics: ?*Diagnostics = null,
4550};
4651
4752pub fn Parsed(comptime T: type) type {
......@@ -136,6 +141,14 @@ pub fn parseFromTokenSourceLeaky(
136141 resolved_options.allocate = .alloc_always;
137142 }
138143 }
144 if (resolved_options.diagnostics) |diag| {
145 scanner_or_reader.enableDiagnostics(diag);
146 }
147 defer {
148 if (resolved_options.diagnostics) |_| {
149 scanner_or_reader.saveDiagnostics();
150 }
151 }
139152
140153 const value = try innerParse(T, allocator, scanner_or_reader, resolved_options);
141154
lib/std/json/static_test.zig+15
......@@ -925,3 +925,18 @@ test "parse at comptime" {
925925 };
926926 comptime testing.expectEqual(@as(u64, 9999), config.uptime) catch unreachable;
927927}
928
929test "parse with diagnostics" {
930 const doc =
931 \\{
932 \\ "common": "mistake",
933 \\}
934 ;
935 var diagnostics: Diagnostics = undefined;
936 try testing.expectError(error.SyntaxError, parseFromSlice(Value, testing.allocator, doc, .{
937 .diagnostics = &diagnostics,
938 }));
939 try std.testing.expectEqual(3, diagnostics.getLine());
940 try std.testing.expectEqual(1, diagnostics.getColumn());
941 try std.testing.expectEqual(25, diagnostics.getByteOffset());
942}