error messages for all parse from source errors

This commit is contained in:
Josh Wolfe 2024-06-02 11:07:55 -04:00
parent 896e36f935
commit 44a35b9c50
2 changed files with 60 additions and 23 deletions

View File

@ -204,6 +204,8 @@ pub const Diagnostics = struct {
cursor_in_current_input: usize = undefined,
current_input: []const u8 = undefined,
// updated setMessage().
message: []const u8 = "",
// updated by recordContext().
context_stack: BoundedArray([]const u8, 8) = .{},
@ -220,6 +222,13 @@ pub const Diagnostics = struct {
return self.total_bytes_before_current_input + self.cursor_in_current_input;
}
/// Set the headline message of the problem.
/// Must not be called multiple times.
pub fn setMessage(self: *@This(), message: []const u8) void {
assert(message.len != 0);
assert(self.message.len == 0); // already set message.
self.message = message;
}
/// 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 {
@ -270,11 +279,11 @@ pub const Diagnostics = struct {
try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start);
try writer.writeAll("^\n");
if (self.context_stack.len > 0) {
try writer.print("{s}\n", .{self.context_stack.slice()[0]});
for (self.context_stack.slice()[1..]) |item| {
try writer.print(" in {s}\n", .{item});
}
if (self.message.len > 0) {
try writer.print("{s}\n", .{self.message});
}
for (self.context_stack.slice()) |item| {
try writer.print(" in {s}\n", .{item});
}
}
};

View File

@ -287,7 +287,7 @@ pub fn innerParse(
const field_name = switch (name_token.?) {
inline .string, .allocated_string => |slice| slice,
else => {
if (options.diagnostics) |diag| diag.recordContext("tagged union requires a field to identify the active tag");
if (options.diagnostics) |diag| diag.setMessage("tagged union requires a field to identify the active tag");
return error.MissingField;
},
};
@ -305,7 +305,7 @@ pub fn innerParse(
else => |t| return typeError(options.diagnostics, t, "void payload ('{}')"),
}
if (.object_end != try source.next()) {
if (options.diagnostics) |diag| diag.recordContext("void payload '{}' should have no fields");
if (options.diagnostics) |diag| diag.setMessage("void payload '{}' should have no fields");
return error.UnknownField;
}
result = @unionInit(T, u_field.name, {});
@ -317,12 +317,12 @@ pub fn innerParse(
}
} else {
// Didn't match anything.
if (options.diagnostics) |diag| diag.recordContext("unrecognized tag name");
if (options.diagnostics) |diag| diag.setMessage("unrecognized tag name");
return error.UnknownField;
}
if (.object_end != try source.next()) {
if (options.diagnostics) |diag| diag.recordContext("tagged union requires only one field");
if (options.diagnostics) |diag| diag.setMessage("tagged union requires only one field");
return error.UnknownField;
}
@ -339,7 +339,7 @@ pub fn innerParse(
var r: T = undefined;
inline for (0..structInfo.fields.len) |i| {
if (.array_end == try source.peekNextTokenType()) {
if (options.diagnostics) |diag| diag.recordContext(std.fmt.comptimePrint(
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint(
"tuple too short. expected length: {}",
.{structInfo.fields.len},
));
@ -349,7 +349,7 @@ pub fn innerParse(
}
if (.array_end != try source.next()) {
if (options.diagnostics) |diag| diag.recordContext(std.fmt.comptimePrint(
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint(
"tuple too long. expected length: {}",
.{structInfo.fields.len},
));
@ -398,7 +398,7 @@ pub fn innerParse(
break;
},
.@"error" => {
if (options.diagnostics) |diag| diag.recordContext("duplicate field name: " ++ field.name);
if (options.diagnostics) |diag| diag.setMessage("duplicate field name: " ++ field.name);
return error.DuplicateField;
},
.use_last => {},
@ -414,7 +414,7 @@ pub fn innerParse(
if (options.ignore_unknown_fields) {
try source.skipValue();
} else {
if (options.diagnostics) |diag| diag.recordContext("unrecognized field name");
if (options.diagnostics) |diag| diag.setMessage("unrecognized field name");
return error.UnknownField;
}
}
@ -438,32 +438,54 @@ pub fn innerParse(
while (true) {
switch (try source.next()) {
.string => |slice| {
if (i + slice.len != r.len) return error.LengthMismatch;
if (i + slice.len > r.len) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len}));
return error.LengthMismatch;
}
if (i + slice.len < r.len) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too short for fixed length array. expected length: {}", .{r.len}));
return error.LengthMismatch;
}
@memcpy(r[i..][0..slice.len], slice);
break;
},
.partial_string => |slice| {
if (i + slice.len > r.len) return error.LengthMismatch;
if (i + slice.len > r.len) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len}));
return error.LengthMismatch;
}
@memcpy(r[i..][0..slice.len], slice);
i += slice.len;
},
.partial_string_escaped_1 => |arr| {
if (i + arr.len > r.len) return error.LengthMismatch;
if (i + arr.len > r.len) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len}));
return error.LengthMismatch;
}
@memcpy(r[i..][0..arr.len], arr[0..]);
i += arr.len;
},
.partial_string_escaped_2 => |arr| {
if (i + arr.len > r.len) return error.LengthMismatch;
if (i + arr.len > r.len) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len}));
return error.LengthMismatch;
}
@memcpy(r[i..][0..arr.len], arr[0..]);
i += arr.len;
},
.partial_string_escaped_3 => |arr| {
if (i + arr.len > r.len) return error.LengthMismatch;
if (i + arr.len > r.len) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len}));
return error.LengthMismatch;
}
@memcpy(r[i..][0..arr.len], arr[0..]);
i += arr.len;
},
.partial_string_escaped_4 => |arr| {
if (i + arr.len > r.len) return error.LengthMismatch;
if (i + arr.len > r.len) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("string too long for fixed length array. expected length: {}", .{r.len}));
return error.LengthMismatch;
}
@memcpy(r[i..][0..arr.len], arr[0..]);
i += arr.len;
},
@ -568,11 +590,17 @@ fn internalParseArray(
var r: T = undefined;
var i: usize = 0;
while (i < len) : (i += 1) {
if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch;
if (.array_end == try source.peekNextTokenType()) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("not enough items for fixed length array. expected length: {}", .{len}));
return error.LengthMismatch;
}
r[i] = try innerParse(Child, allocator, source, options);
}
if (.array_end != try source.next()) return error.LengthMismatch;
if (.array_end != try source.next()) {
if (options.diagnostics) |diag| diag.setMessage(std.fmt.comptimePrint("too many items for fixed length array. expected length: {}", .{len}));
return error.LengthMismatch;
}
return r;
}
@ -622,7 +650,7 @@ fn typeError(diagnostics: ?*Diagnostics, token: anytype, comptime expected: []co
.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);
diag.setMessage(s);
}
return error.UnexpectedToken;
}
@ -880,7 +908,7 @@ fn fillDefaultStructValues(comptime T: type, options: ParseOptions, r: *T, field
const default = @as(*align(1) const field.type, @ptrCast(default_ptr)).*;
@field(r, field.name) = default;
} else {
if (options.diagnostics) |diag| diag.recordContext("missing field: " ++ @typeName(T) ++ "." ++ field.name);
if (options.diagnostics) |diag| diag.setMessage("missing field: " ++ @typeName(T) ++ "." ++ field.name);
return error.MissingField;
}
}