Files
doslang-mirror/tests/build.py
T
coolguy 4624c6d0ec std: 표준 라이브러리가 컴파일러 옆에서 해석되고 호출된다
std 는 예약된 이름이고 프로그램이 아니라 컴파일러와 함께 있으므로 자기 루트를
갖는다 (--std=). 본문 없는 선언은 링커가 찾을 것 -- 런타임이나 C 라이브러리 --
이므로 IR 에 extern 으로 나간다.

런타임에 write/alloc/free/exit 를 넣었다. 이것이 표준 라이브러리가 스스로
말할 수 없는 전부이고 나머지는 Ferro 로 쓴다.

@trap @unreachable @size_of @align_of @line 을 내린다.

링크 이름에서 점과 괄호를 걸렀다. 유닛 경로에는 점이 있고 제네릭 인스턴스에는
괄호가 있는데 어셈블러가 받지 않는다.
2026-08-17 06:12:44 +09:00

106 lines
3.5 KiB
Python

"""Compile a Ferro program to a Windows executable and run it.
fec --emit-asm -> wasm -> wlink (+ the runtime, + kernel32) -> .exe
The toolchain is the pinned Open Watcom under `.dosboxx/watcom`, hosted: the
assembler and linker there produce PE binaries as happily as they produce DOS
ones. Nothing about this step needs a virtual machine.
"""
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
WATCOM = ROOT / ".dosboxx" / "watcom"
RUNTIME = ROOT / "fec" / "rt" / "start.asm"
def _env() -> dict:
env = dict(os.environ)
env.update(WATCOM=str(WATCOM),
INCLUDE=f"{WATCOM / 'h'};{WATCOM / 'h' / 'nt'}",
PATH=f"{WATCOM / 'binnt'}{os.pathsep}{env.get('PATH', '')}")
return env
def _run(cmd, cwd) -> subprocess.CompletedProcess:
return subprocess.run([str(c) for c in cmd], cwd=cwd, env=_env(),
capture_output=True, text=True, timeout=120)
def build(fec: Path, source: Path, out_dir: Path, no_checks: bool = False):
"""Returns (exe_path, log). exe_path is None when a step failed."""
out_dir.mkdir(parents=True, exist_ok=True)
stem = source.stem
asm = out_dir / f"{stem}.asm"
log = []
# `std` ships with the compiler, so it is looked for beside it rather
# than beside the program.
cmd = [fec, "--emit-asm", source, "-o", asm, f"--std={ROOT / 'fec'}"]
if no_checks:
cmd.append("--no-checks")
step = _run(cmd, out_dir)
log.append(("fec", step.returncode, step.stdout + step.stderr))
if step.returncode != 0 or not asm.is_file():
return None, log
wasm = WATCOM / "binnt" / "wasm.exe"
for src, obj in ((asm, out_dir / f"{stem}.obj"),
(RUNTIME, out_dir / "start.obj")):
step = _run([wasm, "-q", "-zq", src, f"-fo={obj}"], out_dir)
log.append(("wasm " + src.name, step.returncode,
step.stdout + step.stderr))
if step.returncode != 0:
return None, log
exe = out_dir / f"{stem}.exe"
step = _run([WATCOM / "binnt" / "wlink.exe",
"system", "nt",
"file", out_dir / f"{stem}.obj",
"file", out_dir / "start.obj",
"library", WATCOM / "lib386" / "nt" / "kernel32.lib",
"name", exe,
"option", "quiet"], out_dir)
log.append(("wlink", step.returncode, step.stdout + step.stderr))
if step.returncode != 0 or not exe.is_file():
return None, log
return exe, log
def run(exe: Path):
done = subprocess.run([str(exe)], capture_output=True, text=True,
timeout=30)
return done.returncode, done.stdout + done.stderr
def main() -> int:
if len(sys.argv) < 2:
print("usage: build.py <program.fe> [--no-checks]")
return 2
source = Path(sys.argv[1]).resolve()
fec = ROOT / ".build" / "fec.exe"
if not fec.is_file():
print("build the front end first: uv run python tests/run.py")
return 2
exe, log = build(fec, source, ROOT / ".build" / "out",
"--no-checks" in sys.argv)
for name, code, text in log:
if code != 0 or text.strip():
print(f"--- {name} (exit {code})")
print(text.rstrip())
if not exe:
return 1
code, text = run(exe)
if text:
print(text, end="")
print(f"{exe.name} exited {code}")
return 0
if __name__ == "__main__":
raise SystemExit(main())