added option to test the compatibility of the compile time vm with the actual runtime behaviour

This commit is contained in:
igor
2026-02-16 16:06:43 +01:00
parent 1b21a0b18b
commit f4c3dca436
3 changed files with 23 additions and 2 deletions

View File

@@ -9,7 +9,7 @@ This document reflects the implementation that ships in this repository today (`
- **Unsafe by design** Memory, syscalls, inline assembly, and FFI expose raw machine power. The standard library is intentionally thin and policy-free. - **Unsafe by design** Memory, syscalls, inline assembly, and FFI expose raw machine power. The standard library is intentionally thin and policy-free.
## 2. Toolchain and Repository Layout ## 2. Toolchain and Repository Layout
- **Driver (`main.py`)** Supports `python main.py source.sl -o a.out`, `--emit-asm`, `--run`, `--dbg`, `--repl`, `--temp-dir`, `--clean`, repeated `-I/--include` paths, and repeated `-l` linker flags (either `-lfoo` or `-l libc.so.6`). Unknown `-l` flags are collected and forwarded to the linker. - **Driver (`main.py`)** Supports `python main.py source.sl -o a.out`, `--emit-asm`, `--run`, `--dbg`, `--repl`, `--temp-dir`, `--clean`, repeated `-I/--include` paths, and repeated `-l` linker flags (either `-lfoo` or `-l libc.so.6`). Unknown `-l` flags are collected and forwarded to the linker. Pass `--ct-run-main` to run the program's `main` word on the compile-time VM before NASM/ld run, which surfaces discrepancies between compile-time and runtime semantics.
- **REPL** `--repl` launches a stateful session with commands such as `:help`, `:reset`, `:load`, `:call <word>`, `:edit`, and `:show`. The REPL still emits/links entire programs for each run; it simply manages the session source for you. - **REPL** `--repl` launches a stateful session with commands such as `:help`, `:reset`, `:load`, `:call <word>`, `:edit`, and `:show`. The REPL still emits/links entire programs for each run; it simply manages the session source for you.
- **Imports** `import relative/or/absolute/path.sl` inserts the referenced file textually. Resolution order: (1) absolute path, (2) relative to the importing file, (3) each include path (defaults: project root and `./stdlib`). Each file is included at most once per compilation unit. Import lines leave blank placeholders so error spans stay meaningful. - **Imports** `import relative/or/absolute/path.sl` inserts the referenced file textually. Resolution order: (1) absolute path, (2) relative to the importing file, (3) each include path (defaults: project root and `./stdlib`). Each file is included at most once per compilation unit. Import lines leave blank placeholders so error spans stay meaningful.
- **Workspace** `stdlib/` holds library modules, `tests/` contains executable samples with `.expected` outputs, `extra_tests/` houses standalone integration demos, and `libs/` collects opt-in extensions such as `libs/fn.sl` and `libs/nob.sl`. - **Workspace** `stdlib/` holds library modules, `tests/` contains executable samples with `.expected` outputs, `extra_tests/` houses standalone integration demos, and `libs/` collects opt-in extensions such as `libs/fn.sl` and `libs/nob.sl`.
@@ -81,10 +81,11 @@ This document reflects the implementation that ships in this repository today (`
- **`stdlib.sl`** Convenience aggregator that imports `core`, `mem`, `io`, and `utils` so most programs can simply `import stdlib/stdlib.sl`. - **`stdlib.sl`** Convenience aggregator that imports `core`, `mem`, `io`, and `utils` so most programs can simply `import stdlib/stdlib.sl`.
## 9. Testing and Usage Patterns ## 9. Testing and Usage Patterns
- **Automated coverage** `python test.py` compiles every `tests/*.sl`, runs the generated binary, and compares stdout against `<name>.expected`. Optional companions include `<name>.stdin` (piped to the process), `<name>.args` (extra CLI args parsed with `shlex`), `<name>.stderr` (expected stderr), and `<name>.meta.json` (per-test knobs such as `expected_exit`, `expect_compile_error`, or `env`). The `extra_tests/` folder ships with curated demos (`extra_tests/ct_test.sl`, `extra_tests/args.sl`, `extra_tests/c_extern.sl`, `extra_tests/fn_test.sl`, `extra_tests/nob_test.sl`) that run alongside the core suite; pass `--extra path/to/foo.sl` to cover more standalone files. Use `python test.py --list` to see descriptions and `python test.py --update foo` to bless outputs after intentional changes. - **Automated coverage** `python test.py` compiles every `tests/*.sl`, runs the generated binary, and compares stdout against `<name>.expected`. Optional companions include `<name>.stdin` (piped to the process), `<name>.args` (extra CLI args parsed with `shlex`), `<name>.stderr` (expected stderr), and `<name>.meta.json` (per-test knobs such as `expected_exit`, `expect_compile_error`, or `env`). The `extra_tests/` folder ships with curated demos (`extra_tests/ct_test.sl`, `extra_tests/args.sl`, `extra_tests/c_extern.sl`, `extra_tests/fn_test.sl`, `extra_tests/nob_test.sl`) that run alongside the core suite; pass `--extra path/to/foo.sl` to cover more standalone files. Use `python test.py --list` to see descriptions and `python test.py --update foo` to bless outputs after intentional changes. Add `--ct-run-main` when invoking the harness to run each test's `main` at compile time as well; capture that stream with `<name>.compile.expected` if you want automated comparisons.
- **Common commands** - **Common commands**
- `python test.py` (run the whole suite) - `python test.py` (run the whole suite)
- `python test.py hello --update` (re-bless a single test) - `python test.py hello --update` (re-bless a single test)
- `python test.py --ct-run-main hello` (compile/run a single test while also exercising `main` on the compile-time VM)
- `python main.py tests/hello.sl -o build/hello && ./build/hello` - `python main.py tests/hello.sl -o build/hello && ./build/hello`
- `python main.py program.sl --emit-asm --temp-dir build` - `python main.py program.sl --emit-asm --temp-dir build`
- `python main.py --repl` - `python main.py --repl`

17
main.py
View File

@@ -3518,6 +3518,12 @@ class Compiler:
source, spans = self._load_with_imports(path.resolve()) source, spans = self._load_with_imports(path.resolve())
return self.compile_source(source, spans=spans, debug=debug, entry_mode=entry_mode) return self.compile_source(source, spans=spans, debug=debug, entry_mode=entry_mode)
def run_compile_time_word(self, name: str) -> None:
word = self.dictionary.lookup(name)
if word is None:
raise CompileTimeError(f"word '{name}' not defined; cannot run at compile time")
self.parser.compile_time_vm.invoke(word)
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)
tried: List[Path] = [] tried: List[Path] = []
@@ -4120,6 +4126,7 @@ def cli(argv: Sequence[str]) -> int:
parser.add_argument("--repl", action="store_true", help="interactive REPL; source file is optional") parser.add_argument("--repl", action="store_true", help="interactive REPL; source file is optional")
parser.add_argument("-l", dest="libs", action="append", default=[], help="pass library to linker (e.g. -l m or -l libc.so.6)") parser.add_argument("-l", dest="libs", action="append", default=[], help="pass library to linker (e.g. -l m or -l libc.so.6)")
parser.add_argument("--no-folding", action="store_true", help="disable constant folding optimization") parser.add_argument("--no-folding", action="store_true", help="disable constant folding optimization")
parser.add_argument("--ct-run-main", action="store_true", help="execute 'main' via the compile-time VM after parsing")
# Parse known and unknown args to allow -l flags anywhere # Parse known and unknown args to allow -l flags anywhere
args, unknown = parser.parse_known_args(argv) args, unknown = parser.parse_known_args(argv)
@@ -4138,6 +4145,9 @@ def cli(argv: Sequence[str]) -> int:
artifact_kind = args.artifact artifact_kind = args.artifact
folding_enabled = not args.no_folding folding_enabled = not args.no_folding
if args.ct_run_main and artifact_kind != "exe":
parser.error("--ct-run-main requires --artifact exe")
if artifact_kind != "exe" and (args.run or args.dbg): if artifact_kind != "exe" and (args.run or args.dbg):
parser.error("--run/--dbg are only available when --artifact exe is selected") parser.error("--run/--dbg are only available when --artifact exe is selected")
@@ -4177,6 +4187,13 @@ def cli(argv: Sequence[str]) -> int:
entry_mode = "program" if artifact_kind == "exe" else "library" entry_mode = "program" if artifact_kind == "exe" else "library"
emission = compiler.compile_file(args.source, debug=args.debug, entry_mode=entry_mode) emission = compiler.compile_file(args.source, debug=args.debug, entry_mode=entry_mode)
if args.ct_run_main:
try:
compiler.run_compile_time_word("main")
except CompileTimeError as exc:
print(f"[error] compile-time execution of 'main' failed: {exc}")
return 1
except (ParseError, CompileError, CompileTimeError) as exc: except (ParseError, CompileError, CompileTimeError) as exc:
print(f"[error] {exc}") print(f"[error] {exc}")
return 1 return 1

View File

@@ -418,6 +418,8 @@ class TestRunner:
cmd = [sys.executable, str(self.main_py), str(case.source), "-o", str(case.binary_path)] cmd = [sys.executable, str(self.main_py), str(case.source), "-o", str(case.binary_path)]
for lib in case.config.libs: for lib in case.config.libs:
cmd.extend(["-l", lib]) cmd.extend(["-l", lib])
if self.args.ct_run_main:
cmd.append("--ct-run-main")
if self.args.verbose: if self.args.verbose:
print(f"\n{format_status('CMD', 'blue')} {quote_cmd(cmd)}") print(f"\n{format_status('CMD', 'blue')} {quote_cmd(cmd)}")
return subprocess.run( return subprocess.run(
@@ -601,6 +603,7 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
parser.add_argument("--list", action="store_true", help="list tests and exit") parser.add_argument("--list", action="store_true", help="list tests and exit")
parser.add_argument("--update", action="store_true", help="update expectation files with actual output") parser.add_argument("--update", action="store_true", help="update expectation files with actual output")
parser.add_argument("--stop-on-fail", action="store_true", help="stop after the first failure") parser.add_argument("--stop-on-fail", action="store_true", help="stop after the first failure")
parser.add_argument("--ct-run-main", action="store_true", help="execute each test's 'main' via the compile-time VM during compilation")
parser.add_argument("-v", "--verbose", action="store_true", help="show compiler/runtime commands") parser.add_argument("-v", "--verbose", action="store_true", help="show compiler/runtime commands")
return parser.parse_args(argv) return parser.parse_args(argv)