| ... | ... | @@ -32,6 +32,7 @@ const std = @import("std"); |
| 32 | 32 | |
| 33 | 33 | const Allocator = std.mem.Allocator; |
| 34 | 34 | const ArrayList = std.ArrayList; |
| 35 | const ArrayListUnmanaged = std.ArrayListUnmanaged; |
| 35 | 36 | const assert = std.debug.assert; |
| 36 | 37 | const BitStack = std.BitStack; |
| 37 | 38 | |
| ... | ... | @@ -193,15 +194,17 @@ pub const TokenType = enum { |
| 193 | 194 | /// At any time, notably just after an error, call `getLine()`, `getColumn()`, and/or `getByteOffset()` |
| 194 | 195 | /// to get meaningful information from this. |
| 195 | 196 | pub const Diagnostics = struct { |
| 197 | // continually updated by Scanner: |
| 196 | 198 | line_number: u64 = 1, |
| 197 | | 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. |
| 199 | line_start_cursor: usize = @bitCast(@as(isize, -1)), // Start just "before" the input buffer to get a 1-based column for line 1. |
| 198 | 200 | total_bytes_before_current_input: u64 = 0, |
| 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, |
| 201 | |
| 202 | // updated by Scanner.saveDiagnostics: |
| 203 | cursor_in_current_input: usize = undefined, |
| 204 | current_input: []const u8 = undefined, |
| 205 | |
| 206 | // updated by recordContext(). |
| 207 | context_stack: ArrayListUnmanaged([]const u8) = .{}, |
| 205 | 208 | |
| 206 | 209 | /// Starts at 1. |
| 207 | 210 | pub fn getLine(self: *const @This()) u64 { |
| ... | ... | @@ -209,25 +212,68 @@ pub const Diagnostics = struct { |
| 209 | 212 | } |
| 210 | 213 | /// Starts at 1. |
| 211 | 214 | pub fn getColumn(self: *const @This()) u64 { |
| 212 | | return self.getCursor() -% self.line_start_cursor; |
| 215 | return self.cursor_in_current_input -% self.line_start_cursor; |
| 213 | 216 | } |
| 214 | 217 | /// Starts at 0. Measures the byte offset since the start of the input. |
| 215 | 218 | pub fn getByteOffset(self: *const @This()) u64 { |
| 216 | | return self.total_bytes_before_current_input + self.getCursor(); |
| 219 | return self.total_bytes_before_current_input + self.cursor_in_current_input; |
| 217 | 220 | } |
| 218 | 221 | |
| 219 | | fn getCursor(self: *const @This()) usize { |
| 220 | | return switch (self.cursor) { |
| 221 | | .pointer => |p| p.*, |
| 222 | | .value => |v| v, |
| 223 | | }; |
| 222 | pub fn recordContext(self: *@This(), allocator: Allocator, context: []const u8) Allocator.Error!void { |
| 223 | return self.context_stack.append(allocator, context); |
| 224 | 224 | } |
| 225 | | fn saveCursor(self: *@This()) void { |
| 226 | | const value = self.getCursor(); |
| 227 | | self.cursor = .{ .value = value }; |
| 225 | |
| 226 | /// Pretty-print diagnostic information to the given writer, such as `std.io.getStdErr().writer()`. |
| 227 | /// file_name if non-null will be printed in a line with the line and column numbers; |
| 228 | /// it is purely aesthetic and is not touched on any actual file system. |
| 229 | pub fn dump(self: *const @This(), writer: anytype, err: anyerror, file_name: ?[]const u8) !void { |
| 230 | try writer.print("{s}:{}:{}: {s}\n", .{file_name orelse "<json>", self.getLine(), self.getColumn(), @errorName(err)}); |
| 231 | |
| 232 | // Show a "line" of context, or in case of very long lines, just an excerpt of the line. |
| 233 | // (Very long lines are common in minified JSON such as in an HTTP API or other machine-to-machine contexts.) |
| 234 | var start = self.cursor_in_current_input; |
| 235 | var start_elipsis: []const u8 = ""; |
| 236 | while (true) { |
| 237 | if (start == 0 or self.current_input[start - 1] == '\n') break; // found start of line. |
| 238 | if (start + 40 <= self.cursor_in_current_input) { |
| 239 | // Too far into the line. Show part of the line. |
| 240 | start_elipsis = "..."; |
| 241 | break; |
| 242 | } |
| 243 | start -= 1; |
| 244 | } |
| 245 | var end = start; |
| 246 | var end_elipsis: []const u8 = ""; |
| 247 | while (true) { |
| 248 | if (end + 1 < self.current_input.len and self.current_input[end + 1] == '\n') break; // found end of line. |
| 249 | if (end == self.current_input.len) { |
| 250 | // found end of input. |
| 251 | // TODO: put elipsis when not is_end_of_input. |
| 252 | break; |
| 253 | } |
| 254 | if (end >= start + 70) { |
| 255 | // Line is too long. Show part of it. |
| 256 | end_elipsis = "..."; |
| 257 | break; |
| 258 | } |
| 259 | end += 1; |
| 260 | } |
| 261 | try writer.print("{s}{s}{s}\n", .{start_elipsis, self.current_input[start..end], end_elipsis}); |
| 262 | try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start); |
| 263 | try writer.writeAll("^\n"); |
| 264 | |
| 265 | for (self.context_stack.items) |item| { |
| 266 | try writer.print(" in {s}\n", .{item}); |
| 267 | } |
| 228 | 268 | } |
| 229 | 269 | }; |
| 230 | 270 | |
| 271 | pub inline fn maybeRecordDiagnosticContext(allocator: Allocator, maybe_diagnostics: ?*Diagnostics, context: []const u8) Allocator.Error!void { |
| 272 | if (maybe_diagnostics) |diag| { |
| 273 | try diag.recordContext(allocator, context); |
| 274 | } |
| 275 | } |
| 276 | |
| 231 | 277 | /// See the documentation for `std.json.Token`. |
| 232 | 278 | pub const AllocWhen = enum { alloc_if_needed, alloc_always }; |
| 233 | 279 | |
| ... | ... | @@ -260,10 +306,6 @@ pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type { |
| 260 | 306 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { |
| 261 | 307 | self.scanner.enableDiagnostics(diagnostics); |
| 262 | 308 | } |
| 263 | | /// Calls `std.json.Scanner.saveDiagnostics`. |
| 264 | | pub fn saveDiagnostics(self: *const @This()) void { |
| 265 | | self.scanner.saveDiagnostics(); |
| 266 | | } |
| 267 | 309 | |
| 268 | 310 | pub const NextError = ReaderType.Error || Error || Allocator.Error; |
| 269 | 311 | pub const SkipError = NextError; |
| ... | ... | @@ -466,18 +508,18 @@ pub const Scanner = struct { |
| 466 | 508 | self.* = undefined; |
| 467 | 509 | } |
| 468 | 510 | |
| 469 | | /// See also `saveDiagnostics()`. |
| 470 | 511 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { |
| 471 | | diagnostics.cursor = .{ .pointer = &self.cursor }; |
| 472 | | std.log.warn("cursor(enableDiagnostics): {}", .{diagnostics.getCursor()}); |
| 473 | 512 | self.diagnostics = diagnostics; |
| 474 | 513 | } |
| 475 | | /// Call this just before `deinit()` to make the diagnostics available after the `deinit()`. |
| 514 | /// For performance reasons, the diagnostics (see `enableDiagnostics`) are not kept up to date continually. |
| 515 | /// Call this method to update the diagnostics with the latest information. |
| 516 | /// Because diagnostics are usually consulted in case of an error, it is common to call this in an errdefer. |
| 517 | /// It is safe to call this regardless of whether diagnostics have been enabled. |
| 518 | /// This is already called in an errdefer block in every relevant public method of this class. |
| 476 | 519 | pub fn saveDiagnostics(self: *const @This()) void { |
| 477 | 520 | 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()}); |
| 521 | diag.cursor_in_current_input = self.cursor; |
| 522 | diag.current_input = self.input; |
| 481 | 523 | } |
| 482 | 524 | } |
| 483 | 525 | |
| ... | ... | @@ -520,6 +562,7 @@ pub const Scanner = struct { |
| 520 | 562 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. |
| 521 | 563 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) AllocError!Token { |
| 522 | 564 | assert(self.is_end_of_input); // This function is not available in streaming mode. |
| 565 | errdefer self.saveDiagnostics(); |
| 523 | 566 | const token_type = self.peekNextTokenType() catch |e| switch (e) { |
| 524 | 567 | error.BufferUnderrun => unreachable, |
| 525 | 568 | else => |err| return err, |
| ... | ... | @@ -577,6 +620,7 @@ pub const Scanner = struct { |
| 577 | 620 | /// This method does not indicate whether the token content being returned is for a `.number` or `.string` token type; |
| 578 | 621 | /// the caller of this method is expected to know which type of token is being processed. |
| 579 | 622 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) AllocIntoArrayListError!?[]const u8 { |
| 623 | errdefer self.saveDiagnostics(); |
| 580 | 624 | while (true) { |
| 581 | 625 | const token = try self.next(); |
| 582 | 626 | switch (token) { |
| ... | ... | @@ -642,6 +686,7 @@ pub const Scanner = struct { |
| 642 | 686 | /// see `peekNextTokenType()`. |
| 643 | 687 | pub fn skipValue(self: *@This()) SkipError!void { |
| 644 | 688 | assert(self.is_end_of_input); // This function is not available in streaming mode. |
| 689 | errdefer self.saveDiagnostics(); |
| 645 | 690 | switch (self.peekNextTokenType() catch |e| switch (e) { |
| 646 | 691 | error.BufferUnderrun => unreachable, |
| 647 | 692 | else => |err| return err, |
| ... | ... | @@ -686,6 +731,7 @@ pub const Scanner = struct { |
| 686 | 731 | /// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height. |
| 687 | 732 | /// Unlike `skipValue()`, this function is available in streaming mode. |
| 688 | 733 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { |
| 734 | errdefer self.saveDiagnostics(); |
| 689 | 735 | while (true) { |
| 690 | 736 | switch (try self.next()) { |
| 691 | 737 | .object_end, .array_end => { |
| ... | ... | @@ -705,11 +751,13 @@ pub const Scanner = struct { |
| 705 | 751 | /// Pre allocate memory to hold the given number of nesting levels. |
| 706 | 752 | /// `stackHeight()` up to the given number will not cause allocations. |
| 707 | 753 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { |
| 754 | errdefer self.saveDiagnostics(); |
| 708 | 755 | try self.stack.ensureTotalCapacity(height); |
| 709 | 756 | } |
| 710 | 757 | |
| 711 | 758 | /// See `std.json.Token` for documentation of this function. |
| 712 | 759 | pub fn next(self: *@This()) NextError!Token { |
| 760 | errdefer self.saveDiagnostics(); |
| 713 | 761 | state_loop: while (true) { |
| 714 | 762 | switch (self.state) { |
| 715 | 763 | .value => { |
| ... | ... | @@ -1463,6 +1511,7 @@ pub const Scanner = struct { |
| 1463 | 1511 | /// determines which type of token will be returned from the next `next*()` call. |
| 1464 | 1512 | /// This function is idempotent, only advancing past commas, colons, and inter-token whitespace. |
| 1465 | 1513 | pub fn peekNextTokenType(self: *@This()) PeekError!TokenType { |
| 1514 | errdefer self.saveDiagnostics(); |
| 1466 | 1515 | state_loop: while (true) { |
| 1467 | 1516 | switch (self.state) { |
| 1468 | 1517 | .value => { |