diff --git a/blueprintcompiler/errors.py b/blueprintcompiler/errors.py index 8a0b961..b5e8632 100644 --- a/blueprintcompiler/errors.py +++ b/blueprintcompiler/errors.py @@ -85,6 +85,11 @@ at {filename} line {line_num} column {col_num}: print() +class UnexpectedTokenError(CompileError): + def __init__(self, start, end): + super().__init__("Unexpected tokens", start, end) + + @dataclass class CodeAction: title: str diff --git a/blueprintcompiler/parse_tree.py b/blueprintcompiler/parse_tree.py index 28d6596..523acb4 100644 --- a/blueprintcompiler/parse_tree.py +++ b/blueprintcompiler/parse_tree.py @@ -25,7 +25,7 @@ from collections import defaultdict from enum import Enum from .ast import AstNode -from .errors import assert_true, CompilerBugError, CompileError +from .errors import assert_true, CompilerBugError, CompileError, UnexpectedTokenError from .tokenizer import Token, TokenType @@ -184,6 +184,22 @@ class ParseContext: token = self.tokens[self.index] return token + def skip_unexpected_token(self): + """ Skips a token and logs an "unexpected token" error. """ + + self.skip() + start = self.tokens[self.index].start + self.next_token() + self.skip() + end = self.tokens[self.index - 1].end + + if (len(self.errors) + and isinstance((err := self.errors[-1]), UnexpectedTokenError) + and err.end == start): + err.end = end + else: + self.errors.append(UnexpectedTokenError(start, end)) + def is_eof(self) -> Token: return self.index >= len(self.tokens) or self.peek_token().type == TokenType.EOF @@ -330,8 +346,7 @@ class Until(ParseNode): while not self.delimiter.parse(ctx).succeeded(): try: if not self.child.parse(ctx).matched(): - token = ctx.next_token() - ctx.errors.append(CompileError("Unexpected token", token.start, token.end)) + ctx.skip_unexpected_token() except CompileError as e: ctx.errors.append(e) ctx.next_token() diff --git a/tests/sample_errors/consecutive_unexpected_tokens.blp b/tests/sample_errors/consecutive_unexpected_tokens.blp new file mode 100644 index 0000000..4a01c19 --- /dev/null +++ b/tests/sample_errors/consecutive_unexpected_tokens.blp @@ -0,0 +1,7 @@ +using Gtk 4.0; + +Button { + visible: false; + not actually blueprint code; + Label {} +} diff --git a/tests/sample_errors/consecutive_unexpected_tokens.err b/tests/sample_errors/consecutive_unexpected_tokens.err new file mode 100644 index 0000000..697f838 --- /dev/null +++ b/tests/sample_errors/consecutive_unexpected_tokens.err @@ -0,0 +1 @@ +5,3,31,Unexpected tokens diff --git a/tests/test_samples.py b/tests/test_samples.py index 85c5455..572b706 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -71,9 +71,9 @@ class TestSamples(unittest.TestCase): raise MultipleErrors(ast.errors) except PrintableError as e: def error_str(error): - line, col = utils.idx_to_pos(error.start, blueprint) + line, col = utils.idx_to_pos(error.start + 1, blueprint) len = error.end - error.start - return ",".join([str(line + 1), str(col + 1), str(len), error.message]) + return ",".join([str(line + 1), str(col), str(len), error.message]) if isinstance(e, CompileError): actual = error_str(e) @@ -143,6 +143,7 @@ class TestSamples(unittest.TestCase): self.assert_sample_error("a11y_prop_type") self.assert_sample_error("class_assign") self.assert_sample_error("class_dne") + self.assert_sample_error("consecutive_unexpected_tokens") self.assert_sample_error("duplicate_obj_id") self.assert_sample_error("enum_member_dne") self.assert_sample_error("filters_in_non_file_filter")