another small update to error reporting

This commit is contained in:
IgorCielniak
2026-01-09 12:33:55 +01:00
parent 95f6c1dac5
commit 315ad9ef77

163
main.py
View File

@@ -37,6 +37,10 @@ class CompileError(Exception):
"""Raised when IR cannot be turned into assembly.""" """Raised when IR cannot be turned into assembly."""
class CompileTimeError(ParseError):
"""Raised when a compile-time word fails with context."""
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Tokenizer / Reader # Tokenizer / Reader
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -447,57 +451,67 @@ class Parser:
self.custom_prelude = None self.custom_prelude = None
self.custom_bss = None self.custom_bss = None
while not self._eof(): try:
token = self._consume() while not self._eof():
self._last_token = token token = self._consume()
if self._run_token_hook(token): self._last_token = token
continue if self._run_token_hook(token):
if self._handle_macro_recording(token):
continue
lexeme = token.lexeme
if lexeme == "[":
self._handle_list_begin()
continue
if lexeme == "]":
self._handle_list_end(token)
continue
if lexeme == "word":
self._begin_definition(token, terminator="end")
continue
if lexeme == "end":
if self.control_stack:
self._handle_end_control()
continue continue
if self._try_end_definition(token): if self._handle_macro_recording(token):
continue continue
raise ParseError(f"unexpected 'end' at {token.line}:{token.column}") lexeme = token.lexeme
if lexeme == ":asm": if lexeme == "[":
self._parse_asm_definition(token) self._handle_list_begin()
continue continue
if lexeme == ":py": if lexeme == "]":
self._parse_py_definition(token) self._handle_list_end(token)
continue continue
if lexeme == "extern": if lexeme == "word":
self._parse_extern(token) self._begin_definition(token, terminator="end")
continue continue
if lexeme == "if": if lexeme == "end":
self._handle_if_control() if self.control_stack:
continue self._handle_end_control()
if lexeme == "else": continue
self._handle_else_control() if self._try_end_definition(token):
continue continue
if lexeme == "for": raise ParseError(f"unexpected 'end' at {token.line}:{token.column}")
self._handle_for_control() if lexeme == ":asm":
continue self._parse_asm_definition(token)
if lexeme == "while": continue
self._handle_while_control() if lexeme == ":py":
continue self._parse_py_definition(token)
if lexeme == "do": continue
self._handle_do_control() if lexeme == "extern":
continue self._parse_extern(token)
if self._maybe_expand_macro(token): continue
continue if lexeme == "if":
self._handle_token(token) self._handle_if_control()
continue
if lexeme == "else":
self._handle_else_control()
continue
if lexeme == "for":
self._handle_for_control()
continue
if lexeme == "while":
self._handle_while_control()
continue
if lexeme == "do":
self._handle_do_control()
continue
if self._maybe_expand_macro(token):
continue
self._handle_token(token)
except ParseError:
raise
except Exception as exc:
tok = self._last_token
if tok is None:
raise ParseError(f"unexpected error during parse: {exc}") from None
raise ParseError(
f"unexpected error near '{tok.lexeme}' at {tok.line}:{tok.column}: {exc}"
) from None
if self.macro_recording is not None: if self.macro_recording is not None:
raise ParseError("unterminated macro definition (missing ';')") raise ParseError("unterminated macro definition (missing ';')")
@@ -654,10 +668,12 @@ class Parser:
def _execute_immediate_word(self, word: Word) -> None: def _execute_immediate_word(self, word: Word) -> None:
try: try:
self.compile_time_vm.invoke(word) self.compile_time_vm.invoke(word)
except CompileTimeError:
raise
except ParseError: except ParseError:
raise raise
except Exception as exc: # pragma: no cover - defensive except Exception as exc: # pragma: no cover - defensive
raise ParseError(f"compile-time word '{word.name}' failed: {exc}") from exc raise CompileTimeError(f"compile-time word '{word.name}' failed: {exc}") from None
def _handle_macro_recording(self, token: Token) -> bool: def _handle_macro_recording(self, token: Token) -> bool:
if self.macro_recording is None: if self.macro_recording is None:
@@ -981,12 +997,14 @@ class CompileTimeVM:
self.return_stack: List[Any] = [] self.return_stack: List[Any] = []
self.loop_stack: List[Dict[str, Any]] = [] self.loop_stack: List[Dict[str, Any]] = []
self._handles = _CTHandleTable() self._handles = _CTHandleTable()
self.call_stack: List[str] = []
def reset(self) -> None: def reset(self) -> None:
self.stack.clear() self.stack.clear()
self.return_stack.clear() self.return_stack.clear()
self.loop_stack.clear() self.loop_stack.clear()
self._handles.clear() self._handles.clear()
self.call_stack.clear()
def push(self, value: Any) -> None: def push(self, value: Any) -> None:
self.stack.append(value) self.stack.append(value)
@@ -1057,17 +1075,29 @@ class CompileTimeVM:
self._call_word(word) self._call_word(word)
def _call_word(self, word: Word) -> None: def _call_word(self, word: Word) -> None:
definition = word.definition self.call_stack.append(word.name)
prefer_definition = word.compile_time_override or (isinstance(definition, Definition) and (word.immediate or word.compile_only)) try:
if not prefer_definition and word.compile_time_intrinsic is not None: definition = word.definition
word.compile_time_intrinsic(self) prefer_definition = word.compile_time_override or (isinstance(definition, Definition) and (word.immediate or word.compile_only))
return if not prefer_definition and word.compile_time_intrinsic is not None:
if definition is None: word.compile_time_intrinsic(self)
raise ParseError(f"word '{word.name}' has no compile-time definition") return
if isinstance(definition, AsmDefinition): if definition is None:
self._run_asm_definition(word) raise ParseError(f"word '{word.name}' has no compile-time definition")
return if isinstance(definition, AsmDefinition):
self._execute_nodes(definition.body) self._run_asm_definition(word)
return
self._execute_nodes(definition.body)
except CompileTimeError:
raise
except ParseError as exc:
raise CompileTimeError(f"{exc}\ncompile-time stack: {' -> '.join(self.call_stack)}") from None
except Exception as exc:
raise CompileTimeError(
f"compile-time failure in '{word.name}': {exc}\ncompile-time stack: {' -> '.join(self.call_stack)}"
) from None
finally:
self.call_stack.pop()
def _run_asm_definition(self, word: Word) -> None: def _run_asm_definition(self, word: Word) -> None:
definition = word.definition definition = word.definition
@@ -1895,7 +1925,7 @@ class Assembler:
builder.emit(" mov [r12], rax") builder.emit(" mov [r12], rax")
return return
raise CompileError(f"unsupported op {node!r}") raise CompileError(f"unsupported op {node!r}{ctx()}")
def _emit_wordref(self, name: str, builder: FunctionEmitter) -> None: def _emit_wordref(self, name: str, builder: FunctionEmitter) -> None:
word = self.dictionary.lookup(name) word = self.dictionary.lookup(name)
@@ -3302,7 +3332,14 @@ def cli(argv: Sequence[str]) -> int:
parser.error("the following arguments are required: source") parser.error("the following arguments are required: source")
compiler = Compiler(include_paths=[Path("."), Path("./stdlib"), *args.include_paths]) compiler = Compiler(include_paths=[Path("."), Path("./stdlib"), *args.include_paths])
emission = compiler.compile_file(args.source) try:
emission = compiler.compile_file(args.source)
except (ParseError, CompileError, CompileTimeError) as exc:
print(f"[error] {exc}")
return 1
except Exception as exc:
print(f"[error] unexpected failure: {exc}")
return 1
args.temp_dir.mkdir(parents=True, exist_ok=True) args.temp_dir.mkdir(parents=True, exist_ok=True)
asm_path = args.temp_dir / (args.source.stem + ".asm") asm_path = args.temp_dir / (args.source.stem + ".asm")