added option to test the compatibility of the compile time vm with the actual runtime behaviour
This commit is contained in:
5
SPEC.md
5
SPEC.md
@@ -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
17
main.py
@@ -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
|
||||||
|
|||||||
3
test.py
3
test.py
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user