Add syntax error reporting
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
kayomn 2023-07-22 14:44:33 +01:00
parent 5d06e61564
commit b311c73c43
6 changed files with 66 additions and 42 deletions

View File

@ -1,4 +1,5 @@
// Test comment.
@log_info("game is loading") @log_info("game is loading")
return { return {

View File

@ -118,5 +118,7 @@ pub fn stack_as_writer(self: *ByteStack) io.Writer {
} }
fn write_stack(stack: *ByteStack, bytes: []const io.Byte) ?usize { fn write_stack(stack: *ByteStack, bytes: []const io.Byte) ?usize {
return stack.push_all(bytes) catch null; stack.push_all(bytes) catch return null;
return bytes.len;
} }

View File

@ -30,7 +30,9 @@ pub fn max_int(comptime int: std.builtin.Type.Int) comptime_int {
} }
pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int { pub fn min_int(comptime int: std.builtin.Type.Int) comptime_int {
if (int.signedness == .unsigned) return 0; if (int.signedness == .unsigned) {
return 0;
}
const bit_count = int.bits; const bit_count = int.bits;

View File

@ -200,7 +200,7 @@ pub const RuntimeEnv = struct {
name: []const coral.io.Byte, name: []const coral.io.Byte,
data: []const coral.io.Byte, data: []const coral.io.Byte,
) RuntimeError!?*RuntimeRef { ) RuntimeError!?*RuntimeRef {
var ast = Ast.make(self.allocator); var ast = Ast.make(self.allocator, name);
defer ast.free(); defer ast.free();
@ -208,7 +208,7 @@ pub const RuntimeEnv = struct {
var tokenizer = tokens.Tokenizer{.source = data}; var tokenizer = tokens.Tokenizer{.source = data};
ast.parse(&tokenizer) catch |parse_error| switch (parse_error) { ast.parse(&tokenizer) catch |parse_error| switch (parse_error) {
error.BadSyntax => return self.raise(error.BadSyntax, ast.error_message), error.BadSyntax => return self.raise(error.BadSyntax, ast.error_message()),
error.OutOfMemory => return error.OutOfMemory, error.OutOfMemory => return error.OutOfMemory,
}; };
} }

View File

@ -2,10 +2,11 @@ const coral = @import("coral");
const tokens = @import("./tokens.zig"); const tokens = @import("./tokens.zig");
name: []const coral.io.Byte,
allocator: coral.io.Allocator, allocator: coral.io.Allocator,
arena: coral.arena.Stacking, arena: coral.arena.Stacking,
statements: Statement.List, statements: Statement.List,
error_message: []const coral.io.Byte, error_buffer: coral.list.ByteStack,
pub const Expression = union (enum) { pub const Expression = union (enum) {
nil_literal, nil_literal,
@ -114,7 +115,7 @@ fn binary_operation_parser(
tokenizer.step(); tokenizer.step();
if (tokenizer.token == null) { if (tokenizer.token == null) {
return self.raise("expected other half of expression after `" ++ comptime token.text() ++ "`"); return self.report(tokenizer, "expected other half of expression after `" ++ comptime token.text() ++ "`");
} }
expression = .{ expression = .{
@ -134,22 +135,32 @@ fn binary_operation_parser(
return BinaryOperationParser.parse; return BinaryOperationParser.parse;
} }
pub fn error_message(self: Self) []const coral.io.Byte {
return self.error_buffer.values;
}
pub fn free(self: *Self) void { pub fn free(self: *Self) void {
self.arena.free(); self.arena.free();
self.statements.free(); self.statements.free();
self.error_buffer.free();
} }
pub fn make(allocator: coral.io.Allocator) Self { pub fn make(allocator: coral.io.Allocator, ast_name: []const coral.io.Byte) Self {
return Self{ return Self{
.arena = coral.arena.Stacking.make(allocator, 4096), .arena = coral.arena.Stacking.make(allocator, 4096),
.allocator = allocator, .error_buffer = coral.list.ByteStack.make(allocator),
.statements = Statement.List.make(allocator), .statements = Statement.List.make(allocator),
.error_message = "", .allocator = allocator,
.name = ast_name,
}; };
} }
fn raise(self: *Self, message: []const u8) ParseError { fn report(self: *Self, tokenizer: *tokens.Tokenizer, message: []const coral.io.Byte) ParseError {
self.error_message = message; coral.utf8.print_formatted(coral.list.stack_as_writer(&self.error_buffer), "{name}@{line}: {message}", .{
.name = self.name,
.line = tokenizer.lines_stepped,
.message = message,
}) catch return error.OutOfMemory;
return error.BadSyntax; return error.BadSyntax;
} }
@ -165,14 +176,12 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
var has_returned = false; var has_returned = false;
while (true) { while (true) {
const no_effect_message = "statement has no effect";
tokenizer.skip(.newline); tokenizer.skip(.newline);
switch (tokenizer.token orelse return) { switch (tokenizer.token orelse return) {
.keyword_return => { .keyword_return => {
if (has_returned) { if (has_returned) {
return self.raise("multiple returns in function scope but expected only one"); return self.report(tokenizer, "multiple returns in function scope but expected only one");
} }
try self.statements.push_one(get_statement: { try self.statements.push_one(get_statement: {
@ -183,7 +192,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
} }
if (!tokenizer.is_token_null_or(.newline)) { if (!tokenizer.is_token_null_or(.newline)) {
return self.raise("unexpected token after return"); return self.report(tokenizer, "unexpected token after return");
} }
break: get_statement .return_nothing; break: get_statement .return_nothing;
@ -195,14 +204,16 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
.identifier => |identifier| { .identifier => |identifier| {
tokenizer.step(); tokenizer.step();
switch (tokenizer.token orelse return self.raise(no_effect_message)) { const no_effect_message = "statement has no effect";
.newline => return self.raise(no_effect_message),
switch (tokenizer.token orelse return self.report(tokenizer, no_effect_message)) {
.newline => return self.report(tokenizer, no_effect_message),
.symbol_equals => { .symbol_equals => {
tokenizer.step(); tokenizer.step();
if (tokenizer.token == null) { if (tokenizer.token == null) {
return self.raise("expected expression after `=`"); return self.report(tokenizer, "expected expression after `=`");
} }
try self.statements.push_one(.{ try self.statements.push_one(.{
@ -213,19 +224,21 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
}); });
if (!tokenizer.is_token_null_or(.newline)) { if (!tokenizer.is_token_null_or(.newline)) {
return self.raise("unexpected token after assignment"); return self.report(tokenizer, "unexpected token after assignment");
} }
}, },
else => return self.raise("expected `=` after local"), else => return self.report(tokenizer, "expected `=` after local"),
} }
}, },
.special_identifier => |identifier| { .special_identifier => |identifier| {
tokenizer.step(); tokenizer.step();
switch (tokenizer.token orelse return self.raise(no_effect_message)) { const missing_arguments_message = "system call is missing arguments";
.newline => return self.raise(no_effect_message),
switch (tokenizer.token orelse return self.report(tokenizer, missing_arguments_message)) {
.newline => return self.report(tokenizer, missing_arguments_message),
.symbol_paren_left => { .symbol_paren_left => {
tokenizer.step(); tokenizer.step();
@ -239,10 +252,10 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
try expressions_list.push_one(try self.parse_expression(tokenizer)); try expressions_list.push_one(try self.parse_expression(tokenizer));
switch (tokenizer.token orelse return self.raise("unexpected end after after `(`")) { switch (tokenizer.token orelse return self.report(tokenizer, "unexpected end after after `(`")) {
.symbol_comma => continue, .symbol_comma => continue,
.symbol_paren_right => break, .symbol_paren_right => break,
else => return self.raise("expected `)` or argument after `(`"), else => return self.report(tokenizer, "expected `)` or argument after `(`"),
} }
} }
@ -256,11 +269,11 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
}); });
}, },
else => return self.raise("expected `=` after local"), else => return self.report(tokenizer, "expected `=` after local"),
} }
}, },
else => return self.raise("invalid statement"), else => return self.report(tokenizer, "invalid statement"),
} }
} }
} }
@ -284,18 +297,18 @@ const parse_expression = binary_operation_parser(parse_equality, &.{
fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression { fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression {
const allocator = self.arena.as_allocator(); const allocator = self.arena.as_allocator();
switch (tokenizer.token orelse return self.raise("expected operand after operator")) { switch (tokenizer.token orelse return self.report(tokenizer, "expected operand after operator")) {
.symbol_paren_left => { .symbol_paren_left => {
tokenizer.skip(.newline); tokenizer.skip(.newline);
if (tokenizer.token == null) { if (tokenizer.token == null) {
return self.raise("expected an expression after `(`"); return self.report(tokenizer, "expected an expression after `(`");
} }
const expression = try self.parse_expression(tokenizer); const expression = try self.parse_expression(tokenizer);
if (!tokenizer.is_token(.symbol_paren_right)) { if (!tokenizer.is_token(.symbol_paren_right)) {
return self.raise("expected a closing `)` after expression"); return self.report(tokenizer, "expected a closing `)` after expression");
} }
tokenizer.step(); tokenizer.step();
@ -339,7 +352,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
var expression_list = Expression.List.make(allocator); var expression_list = Expression.List.make(allocator);
while (true) { while (true) {
switch (tokenizer.token orelse return self.raise("expected expression or `)` after `(`")) { switch (tokenizer.token orelse return self.report(tokenizer, "expected expression or `)` after `(`")) {
.symbol_paren_right => { .symbol_paren_right => {
tokenizer.step(); tokenizer.step();
@ -354,7 +367,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
else => { else => {
try expression_list.push_one(try self.parse_expression(tokenizer)); try expression_list.push_one(try self.parse_expression(tokenizer));
switch (tokenizer.token orelse return self.raise("expected `,` or `)` after argument")) { switch (tokenizer.token orelse return self.report(tokenizer, "expected `,` or `)` after argument")) {
.symbol_comma => continue, .symbol_comma => continue,
.symbol_paren_right => { .symbol_paren_right => {
@ -368,7 +381,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
}; };
}, },
else => return self.raise("expected `,` or `)` after argument"), else => return self.report(tokenizer, "expected `,` or `)` after argument"),
} }
}, },
} }
@ -387,7 +400,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
tokenizer.skip(.newline); tokenizer.skip(.newline);
while (true) { while (true) {
switch (tokenizer.token orelse return self.raise("unexpected end of table literal")) { switch (tokenizer.token orelse return self.report(tokenizer, "unexpected end of table literal")) {
.symbol_brace_right => { .symbol_brace_right => {
tokenizer.step(); tokenizer.step();
@ -398,13 +411,13 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
tokenizer.skip(.newline); tokenizer.skip(.newline);
if (!tokenizer.is_token(.symbol_equals)) { if (!tokenizer.is_token(.symbol_equals)) {
return self.raise("expected `=` after identifier"); return self.report(tokenizer, "expected `=` after identifier");
} }
tokenizer.skip(.newline); tokenizer.skip(.newline);
if (tokenizer.token == null) { if (tokenizer.token == null) {
return self.raise("unexpected end after `=`"); return self.report(tokenizer, "unexpected end after `=`");
} }
try table_fields.push_one(.{ try table_fields.push_one(.{
@ -412,7 +425,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
.identifier = identifier, .identifier = identifier,
}); });
switch (tokenizer.token orelse return self.raise("unexpected end of table")) { switch (tokenizer.token orelse return self.report(tokenizer, "unexpected end of table")) {
.symbol_comma => tokenizer.skip(.newline), .symbol_comma => tokenizer.skip(.newline),
.symbol_brace_right => { .symbol_brace_right => {
@ -421,11 +434,11 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
return Expression{.table_literal = table_fields}; return Expression{.table_literal = table_fields};
}, },
else => return self.raise("expected `,` or `}` after expression"), else => return self.report(tokenizer, "expected `,` or `}` after expression"),
} }
}, },
else => return self.raise("expected `}` or fields in table literal"), else => return self.report(tokenizer, "expected `}` or fields in table literal"),
} }
} }
}, },
@ -434,7 +447,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
tokenizer.skip(.newline); tokenizer.skip(.newline);
if (tokenizer.token == null) { if (tokenizer.token == null) {
return self.raise("expected expression after numeric negation (`-`)"); return self.report(tokenizer, "expected expression after numeric negation (`-`)");
} }
return Expression{ return Expression{
@ -449,7 +462,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
tokenizer.skip(.newline); tokenizer.skip(.newline);
if (tokenizer.token == null) { if (tokenizer.token == null) {
return self.raise("expected expression after boolean negation (`!`)"); return self.report(tokenizer, "expected expression after boolean negation (`!`)");
} }
return Expression{ return Expression{
@ -460,7 +473,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
}; };
}, },
else => return self.raise("unexpected token in expression"), else => return self.report(tokenizer, "unexpected token in expression"),
} }
} }

View File

@ -11,6 +11,8 @@ const heap = @import("./heap.zig");
const kym = @import("./kym.zig"); const kym = @import("./kym.zig");
fn kym_handle_errors(info: kym.ErrorInfo) void { fn kym_handle_errors(info: kym.ErrorInfo) void {
app.log_fail(info.message);
var remaining_frames = info.frames.len; var remaining_frames = info.frames.len;
while (remaining_frames != 0) { while (remaining_frames != 0) {
@ -62,6 +64,10 @@ pub fn run_app(file_access: file.Access) void {
.name = "log_info", .name = "log_info",
.caller = kym.Caller.from(kym_log_info), .caller = kym.Caller.from(kym_log_info),
}, },
.{
.name = "log_warn",
.caller = kym.Caller.from(kym_log_warn),
},
.{ .{
.name = "log_fail", .name = "log_fail",
.caller = kym.Caller.from(kym_log_fail), .caller = kym.Caller.from(kym_log_fail),