From 34448791e8be6a530401e334c94c422f208235b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domas=20Tama=C5=A1auskas?= Date: Thu, 23 Jul 2026 19:41:03 +0300 Subject: [PATCH] Add error reporting to the parser --- ast/ast.odin | 6 ++-- ast/parser.odin | 33 +++++++++++++++++---- tests/ast/ast.odin | 71 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/ast/ast.odin b/ast/ast.odin index 7b3467d..ce62b61 100644 --- a/ast/ast.odin +++ b/ast/ast.odin @@ -2,6 +2,8 @@ package ast import "../token" +Error :: distinct string + Program :: struct { statements: [dynamic]Statement, } @@ -23,9 +25,9 @@ Let_Statement :: struct { value: Expression, } -parse :: proc(tokens: []token.Token, allocator := context.allocator) -> Program { +parse :: proc(tokens: []token.Token, allocator := context.allocator) -> (Program, [dynamic]Error) { parser := init_parser(tokens) statements := make([dynamic]Statement, allocator) for statement in next(&parser, allocator) do append(&statements, statement) - return Program{statements = statements} + return Program{statements = statements}, parser.errors } diff --git a/ast/parser.odin b/ast/parser.odin index 1baa1ec..8c33ee3 100644 --- a/ast/parser.odin +++ b/ast/parser.odin @@ -2,24 +2,40 @@ package ast import "../token" +import "base:runtime" +import "core:fmt" Parser :: struct { input: []token.Token, left, right: int, value: token.Token, + allocator: runtime.Allocator, + errors: [dynamic]Error, } -init_parser :: proc(input: []token.Token) -> Parser { +init_parser :: proc(input: []token.Token, allocator := context.allocator) -> Parser { parser := Parser { - input = input, + input = input, + allocator = allocator, + errors = make([dynamic]Error, allocator), } read_token(&parser) return parser } +error :: proc(got, want: token.Type, allocator := context.allocator) -> Error { + return Error(fmt.aprintf("expected %s, but got %s", want, got, allocator = allocator)) +} + peek_token :: proc(p: ^Parser, expected: token.Type) -> bool { - if p.right >= len(p.input) do return false - return p.input[p.right].type == expected + if p.right >= len(p.input) { + append(&p.errors, error(.Illegal, expected, p.allocator)) + return false + } + + current := p.input[p.right].type + if current != expected do append(&p.errors, error(current, expected, p.allocator)) + return current == expected } read_token :: proc(p: ^Parser) { @@ -59,6 +75,13 @@ parse_let_statement :: proc( identifier := new(Identifier) identifier.value = literal - for p.value.type != .Semicolon do read_token(p) + peek_token(p, .Assignment) or_return + read_token(p) + + peek_token(p, .Integer) or_return + read_token(p) + + peek_token(p, .Semicolon) or_return + read_token(p) return Let_Statement{name = identifier}, true } diff --git a/tests/ast/ast.odin b/tests/ast/ast.odin index eca6372..20ccc6a 100644 --- a/tests/ast/ast.odin +++ b/tests/ast/ast.odin @@ -14,11 +14,80 @@ let_statement :: proc(t: ^testing.T) { {type = .Semicolon}, } - program := ast.parse(tokens) + program, errors := ast.parse(tokens) defer free_all() testing.expect_value(t, len(program.statements), 1) + testing.expect_value(t, len(errors), 0) statement, ok := program.statements[0].(ast.Let_Statement) testing.expect(t, ok) testing.expect_value(t, statement.name^, ast.Identifier{value = "x"}) } + +@(test) +let_without_assignment :: proc(t: ^testing.T) { + tokens := []token.Token { + {type = .Let}, + {type = .Identifier, literal = "x"}, + {type = .Integer, literal = "5"}, + {type = .Semicolon}, + } + + program, errors := ast.parse(tokens) + defer free_all() + + testing.expect_value(t, len(program.statements), 0) + testing.expect_value(t, len(errors), 1) + testing.expect_value(t, errors[0], "expected Assignment, but got Integer") +} + +@(test) +let_without_identifier :: proc(t: ^testing.T) { + tokens := []token.Token { + {type = .Let}, + {type = .Assignment}, + {type = .Integer, literal = "5"}, + {type = .Semicolon}, + } + + program, errors := ast.parse(tokens) + defer free_all() + + testing.expect_value(t, len(program.statements), 0) + testing.expect_value(t, len(errors), 1) + testing.expect_value(t, errors[0], "expected Identifier, but got Assignment") +} + +@(test) +let_without_integer :: proc(t: ^testing.T) { + tokens := []token.Token { + {type = .Let}, + {type = .Identifier, literal = "x"}, + {type = .Assignment}, + {type = .Semicolon}, + } + + program, errors := ast.parse(tokens) + defer free_all() + + testing.expect_value(t, len(program.statements), 0) + testing.expect_value(t, len(errors), 1) + testing.expect_value(t, errors[0], "expected Integer, but got Semicolon") +} + +@(test) +let_without_semicolon :: proc(t: ^testing.T) { + tokens := []token.Token { + {type = .Let}, + {type = .Identifier, literal = "x"}, + {type = .Assignment}, + {type = .Integer, literal = "5"}, + } + + program, errors := ast.parse(tokens) + defer free_all() + + testing.expect_value(t, len(program.statements), 0) + testing.expect_value(t, len(errors), 1) + testing.expect_value(t, errors[0], "expected Semicolon, but got Illegal") +} -- 2.51.2