added more debug info when compiling in debug mode, now the debug info contains mapping to the original .sl file

This commit is contained in:
IgorCielniak
2026-01-18 19:55:59 +01:00
parent 725fec9d73
commit 805f8e26a5

86
main.py
View File

@@ -60,6 +60,13 @@ class Token:
return f"Token({self.lexeme!r}@{self.line}:{self.column})" return f"Token({self.lexeme!r}@{self.line}:{self.column})"
@dataclass(frozen=True)
class SourceLocation:
path: Path
line: int
column: int
class Reader: class Reader:
"""Default reader; users can swap implementations at runtime.""" """Default reader; users can swap implementations at runtime."""
@@ -199,6 +206,7 @@ class Op:
op: str op: str
data: Any = None data: Any = None
loc: Optional[SourceLocation] = None
@dataclass @dataclass
@@ -378,12 +386,12 @@ class Parser:
self.custom_bss: Optional[List[str]] = None self.custom_bss: Optional[List[str]] = None
self._pending_inline_definition: bool = False self._pending_inline_definition: bool = False
def location_for_token(self, token: Token) -> Tuple[str, int, int]: def location_for_token(self, token: Token) -> SourceLocation:
for span in self.file_spans: for span in self.file_spans:
if span.start_line <= token.line < span.end_line: if span.start_line <= token.line < span.end_line:
local_line = span.local_start_line + (token.line - span.start_line) local_line = span.local_start_line + (token.line - span.start_line)
return (span.path.name, local_line, token.column) return SourceLocation(span.path, local_line, token.column)
return ("<source>", token.line, token.column) return SourceLocation(Path("<source>"), token.line, token.column)
def inject_token_objects(self, tokens: Sequence[Token]) -> None: def inject_token_objects(self, tokens: Sequence[Token]) -> None:
"""Insert tokens at the current parse position.""" """Insert tokens at the current parse position."""
@@ -1061,7 +1069,11 @@ class Parser:
def _py_exec_namespace(self) -> Dict[str, Any]: def _py_exec_namespace(self) -> Dict[str, Any]:
return dict(PY_EXEC_GLOBALS) return dict(PY_EXEC_GLOBALS)
def _append_op(self, node: Op) -> None: def _append_op(self, node: Op, token: Optional[Token] = None) -> None:
if node.loc is None:
tok = token or self._last_token
if tok is not None:
node.loc = self.location_for_token(tok)
target = self.context_stack[-1] target = self.context_stack[-1]
if isinstance(target, Module): if isinstance(target, Module):
target.forms.append(node) target.forms.append(node)
@@ -1602,8 +1614,29 @@ class Emission:
class FunctionEmitter: class FunctionEmitter:
"""Utility for emitting per-word assembly.""" """Utility for emitting per-word assembly."""
def __init__(self, text: List[str]) -> None: def __init__(self, text: List[str], debug_enabled: bool = False) -> None:
self.text = text self.text = text
self.debug_enabled = debug_enabled
self._current_loc: Optional[SourceLocation] = None
self._generated_debug_path = "<generated>"
def _emit_line_directive(self, line: int, path: str, increment: int) -> None:
escaped = path.replace("\\", "\\\\").replace('"', '\\"')
self.text.append(f'%line {line}+{increment} "{escaped}"')
def set_location(self, loc: Optional[SourceLocation]) -> None:
if not self.debug_enabled:
return
if loc is None:
if self._current_loc is None:
return
self._emit_line_directive(1, self._generated_debug_path, increment=1)
self._current_loc = None
return
if self._current_loc == loc:
return
self._emit_line_directive(loc.line, str(loc.path), increment=0)
self._current_loc = loc
def emit(self, line: str) -> None: def emit(self, line: str) -> None:
self.text.append(line) self.text.append(line)
@@ -1827,7 +1860,7 @@ class Assembler:
for name in externs: for name in externs:
text.append(f"extern {name}") text.append(f"extern {name}")
def emit(self, module: Module) -> Emission: def emit(self, module: Module, debug: bool = False) -> Emission:
emission = Emission() emission = Emission()
self._emit_externs(emission.text) self._emit_externs(emission.text)
prelude_lines = module.prelude if module.prelude is not None else self._runtime_prelude() prelude_lines = module.prelude if module.prelude is not None else self._runtime_prelude()
@@ -1857,7 +1890,7 @@ class Assembler:
runtime_defs = [defn for defn in runtime_defs if not getattr(defn, "inline", False)] runtime_defs = [defn for defn in runtime_defs if not getattr(defn, "inline", False)]
for definition in runtime_defs: for definition in runtime_defs:
self._emit_definition(definition, emission.text) self._emit_definition(definition, emission.text, debug=debug)
self._emit_variables(module.variables) self._emit_variables(module.variables)
@@ -1933,10 +1966,16 @@ class Assembler:
self._float_literals[value] = label self._float_literals[value] = label
return label return label
def _emit_definition(self, definition: Union[Definition, AsmDefinition], text: List[str]) -> None: def _emit_definition(
self,
definition: Union[Definition, AsmDefinition],
text: List[str],
*,
debug: bool = False,
) -> None:
label = sanitize_label(definition.name) label = sanitize_label(definition.name)
text.append(f"{label}:") text.append(f"{label}:")
builder = FunctionEmitter(text) builder = FunctionEmitter(text, debug_enabled=debug)
self._emit_stack.append(definition.name) self._emit_stack.append(definition.name)
try: try:
if isinstance(definition, Definition): if isinstance(definition, Definition):
@@ -2017,6 +2056,7 @@ class Assembler:
def _emit_node(self, node: Op, builder: FunctionEmitter) -> None: def _emit_node(self, node: Op, builder: FunctionEmitter) -> None:
kind = node.op kind = node.op
data = node.data data = node.data
builder.set_location(node.loc)
def ctx() -> str: def ctx() -> str:
return f" while emitting '{self._emit_stack[-1]}'" if self._emit_stack else "" return f" while emitting '{self._emit_stack[-1]}'" if self._emit_stack else ""
@@ -3189,8 +3229,8 @@ def macro_here(ctx: MacroContext) -> Optional[List[Op]]:
tok = ctx.parser._last_token tok = ctx.parser._last_token
if tok is None: if tok is None:
return [Op(op="literal", data="<source>:0:0")] return [Op(op="literal", data="<source>:0:0")]
file_name, line, col = ctx.parser.location_for_token(tok) loc = ctx.parser.location_for_token(tok)
return [Op(op="literal", data=f"{file_name}:{line}:{col}")] return [Op(op="literal", data=f"{loc.path.name}:{loc.line}:{loc.column}")]
def bootstrap_dictionary() -> Dictionary: def bootstrap_dictionary() -> Dictionary:
@@ -3235,15 +3275,21 @@ class Compiler:
include_paths = [Path("."), Path("./stdlib")] include_paths = [Path("."), Path("./stdlib")]
self.include_paths: List[Path] = [p.expanduser().resolve() for p in include_paths] self.include_paths: List[Path] = [p.expanduser().resolve() for p in include_paths]
def compile_source(self, source: str, spans: Optional[List[FileSpan]] = None) -> Emission: def compile_source(
self,
source: str,
spans: Optional[List[FileSpan]] = None,
*,
debug: bool = False,
) -> Emission:
self.parser.file_spans = spans or [] self.parser.file_spans = spans or []
tokens = self.reader.tokenize(source) tokens = self.reader.tokenize(source)
module = self.parser.parse(tokens, source) module = self.parser.parse(tokens, source)
return self.assembler.emit(module) return self.assembler.emit(module, debug=debug)
def compile_file(self, path: Path) -> Emission: def compile_file(self, path: Path, *, debug: bool = False) -> Emission:
source, spans = self._load_with_imports(path.resolve()) source, spans = self._load_with_imports(path.resolve())
return self.compile_source(source, spans=spans) return self.compile_source(source, spans=spans, debug=debug)
def _resolve_import_target(self, importing_file: Path, target: str) -> Path: def _resolve_import_target(self, importing_file: Path, target: str) -> Path:
raw = Path(target) raw = Path(target)
@@ -3436,7 +3482,7 @@ class Compiler:
def run_nasm(asm_path: Path, obj_path: Path, debug: bool = False) -> None: def run_nasm(asm_path: Path, obj_path: Path, debug: bool = False) -> None:
cmd = ["nasm", "-f", "elf64"] cmd = ["nasm", "-f", "elf64"]
if debug: if debug:
cmd.append("-g") cmd.extend(["-g", "-F", "dwarf"])
cmd += ["-o", str(obj_path), str(asm_path)] cmd += ["-o", str(obj_path), str(asm_path)]
subprocess.run(cmd, check=True) subprocess.run(cmd, check=True)
@@ -3677,7 +3723,7 @@ def run_repl(
temp_defs_repl = [*user_defs_repl, f"word main\n {word_name}\nend"] temp_defs_repl = [*user_defs_repl, f"word main\n {word_name}\nend"]
builder_source = _repl_build_source(imports, user_defs_files, temp_defs_repl, [], True, force_synthetic=False) builder_source = _repl_build_source(imports, user_defs_files, temp_defs_repl, [], True, force_synthetic=False)
src_path.write_text(builder_source) src_path.write_text(builder_source)
emission = compiler.compile_file(src_path) emission = compiler.compile_file(src_path, debug=debug)
compiler.assembler.write_asm(emission, asm_path) compiler.assembler.write_asm(emission, asm_path)
run_nasm(asm_path, obj_path, debug=debug) run_nasm(asm_path, obj_path, debug=debug)
run_linker(obj_path, exe_path, debug=debug, libs=list(libs)) run_linker(obj_path, exe_path, debug=debug, libs=list(libs))
@@ -3736,7 +3782,7 @@ def run_repl(
) )
try: try:
snippet_src.write_text(snippet_source) snippet_src.write_text(snippet_source)
emission = compiler.compile_file(snippet_src) emission = compiler.compile_file(snippet_src, debug=debug)
compiler.assembler.write_asm(emission, snippet_asm) compiler.assembler.write_asm(emission, snippet_asm)
run_nasm(snippet_asm, snippet_obj, debug=debug) run_nasm(snippet_asm, snippet_obj, debug=debug)
run_linker(snippet_obj, snippet_exe, debug=debug, libs=list(libs)) run_linker(snippet_obj, snippet_exe, debug=debug, libs=list(libs))
@@ -3775,7 +3821,7 @@ def run_repl(
try: try:
src_path.write_text(source) src_path.write_text(source)
emission = compiler.compile_file(src_path) emission = compiler.compile_file(src_path, debug=debug)
except (ParseError, CompileError, CompileTimeError) as exc: except (ParseError, CompileError, CompileTimeError) as exc:
print(f"[error] {exc}") print(f"[error] {exc}")
continue continue
@@ -3869,7 +3915,7 @@ def cli(argv: Sequence[str]) -> int:
if args.repl: if args.repl:
return run_repl(compiler, args.temp_dir, args.libs, debug=args.debug, initial_source=args.source) return run_repl(compiler, args.temp_dir, args.libs, debug=args.debug, initial_source=args.source)
emission = compiler.compile_file(args.source) emission = compiler.compile_file(args.source, debug=args.debug)
except (ParseError, CompileError, CompileTimeError) as exc: except (ParseError, CompileError, CompileTimeError) as exc:
print(f"[error] {exc}") print(f"[error] {exc}")
return 1 return 1