docs/runtime: externcall + println normalization; NyRT silent result flag; PyVM console warn/error/trace routing\nselfhost(M2): add MirEmitterBox (Return(Int)->const+ret) and --emit-mir switch; initial selfhost smoke\nrunner: child args/env hardening for selfhost (NYASH_NY_COMPILER_CHILD_ARGS under --; NYASH_JSON_ONLY=1, NYASH_VM_USE_PY=1, NYASH_DISABLE_PLUGINS=1); VM path strips using lines when NYASH_ENABLE_USING=1\nmeta: clarify PyVM/llvmlite as primary (vm/interpreter legacy OFF) in CURRENT_TASK.md; README links to externcall docs
This commit is contained in:
@ -5,6 +5,10 @@ Summary
|
||||
- PyVM is the semantic reference engine; llvmlite is used for AOT and parity checks.
|
||||
- GC: user modes defined; controller実装(rc+cycle skeleton + metrics/diagnostics)に移行。LLVM safepoint輸出/NyRT配線と自動挿入(envゲートON)を完了。
|
||||
|
||||
Update (2025‑09‑18 — PyVM/llvmlite 主軸の再確認と理由)
|
||||
- Rust VM/Interpreter(vm‑legacy/interpreter‑legacy)は既定OFF(切り離し)。理由: MIR13 期の移行で追随工数が合わず、当面は保守最小/比較用に限定。
|
||||
- 現在の主軸は PyVM(意味論参照)+ llvmlite(AOT/EXE‑first)。`--backend vm` は PyVM にフォールバック。
|
||||
|
||||
Refactoring — Code Quality (High Priority, 2025‑09‑17 夜間)
|
||||
- MIR instruction meta de‑boilerplate:
|
||||
- Added `inst_meta!` macro and migrated major instructions (Unary/Compare/Load/Cast/TypeOp/Array{Get,Set}/Return/Branch/Jump/Print/Debug/Barrier*/Ref*/Weak*/Future*/Phi/NewBox).
|
||||
@ -42,6 +46,35 @@ Done (2025‑09‑18)
|
||||
- ny-llvmc: EXE 出力対応
|
||||
- `--emit exe/obj`、`--nyrt <dir>`、`--libs` を実装。`.o` 生成後に NyRT とリンクして実行可能に。
|
||||
- 代表ケースで EXE 実行(exit code)を確認。
|
||||
- CLI: 直接 EXE 出力
|
||||
- `nyash --emit-exe <out> [--emit-exe-nyrt <dir>] [--emit-exe-libs "<flags>"]` を追加。MIR JSON を内部出力→`ny-llvmc` 呼出し→EXE 生成。
|
||||
- Selfhost Parser(Stage‑2)
|
||||
- コメント対応(`//` と `/* ... */`)を `skip_ws` に統合。
|
||||
- 文字列エスケープ(`\n`/`\r`/`\t`/`\"`/`\\`/最小 `\uXXXX`)を `read_string_lit`/`parse_string2` に追加。
|
||||
- Smokes/Test 整理(ローカル)
|
||||
- 新ランナー: `tools/test/bin/run.sh`(`--tag fast` で最小セット)。
|
||||
- 共通ヘルパ: `tools/test/lib/shlib.sh`(ビルド/実行/アサート)。
|
||||
- fast セットに crate‑exe(3件)/bridge 短絡 を追加。PyVM 基本スモークを JSON→`pyvm_runner.py` で stdout 判定に移行。
|
||||
|
||||
Today (2025‑09‑18) — ExternCall 整理と Self‑Host M2 の土台
|
||||
- ExternCall/println 正規化を docs に明文化(`docs/reference/runtime/externcall.md`)。README/README.ja からリンク。
|
||||
- NyRT: `NYASH_NYRT_SILENT_RESULT=1` で標準化 `Result:` 行を抑止(tests 既定ON)。
|
||||
- PyVM: console.warn/error/trace は stderr、console.log は stdout に出力(MVP)。
|
||||
- Self‑Host M2 MVP: `MirEmitterBox` 追加(Return(Int)→const+ret)。コンパイラに `--emit-mir` を実装。
|
||||
- Runner(自己ホスト子プロセス)
|
||||
- 子に `NYASH_JSON_ONLY=1 / NYASH_VM_USE_PY=1 / NYASH_DISABLE_PLUGINS=1` を付与し、出力と依存を安定化。
|
||||
- `NYASH_NY_COMPILER_CHILD_ARGS` を必ず `--` の後段に注入。
|
||||
- VM 経路は `NYASH_ENABLE_USING=1` 時に using 行をストリップ(暫定)。
|
||||
|
||||
Next — Self‑Host M2 拡張(短期)
|
||||
- Runner: 子 stdout の JSON 捕捉を堅牢化(fallback: tmp に JSON を書かせ親で読む)。
|
||||
- MirEmitterBox の段階実装:
|
||||
1) Binary(+,-,*,/)→ `binop`
|
||||
2) Compare(==,!=,<,<=,>,>=)→ `compare`(i64 0/1)
|
||||
3) print/println → `externcall env.console.log`
|
||||
4) Ternary → branch + ret(PHI‑off; Copy 合成)
|
||||
- スモーク拡充(PyVM→EXE パリティ)
|
||||
- `return 1+2*3`(exit=7)、`ternary_basic`(exit=10)、console.log(EXE は exit のみ)。
|
||||
|
||||
Next (Immediate)
|
||||
- tools/build_llvm.sh の crate→EXE 統合
|
||||
@ -50,8 +83,12 @@ Next (Immediate)
|
||||
- Self‑host/EXE スモークの整備
|
||||
- 代表3ケース(const/binop/branch など)の JSON→ny-llvmc→EXE→実行をワンショットで検証するスクリプト(Linux)。
|
||||
- 既存 `exe_first_smoke.sh`/`build_compiler_exe.sh` の補助として crate 直結経路を並行維持。
|
||||
- CI 追補
|
||||
- Linux ジョブに crate‑EXE ルートの最小スモークを追加(exit code 判定のみ)。
|
||||
- CI 追補(簡素化方針)
|
||||
- GitHub Actions は `fast-smoke` 一本に集約済み。必要時に拡張。
|
||||
- crate‑EXE 3件(10/50/1)を維持。PyVM 追加はローカル test ランナーに寄せる。
|
||||
- Test consolidation
|
||||
- 必要スモークから順次 `tools/test/` に移行(pyvm/bridge/crate-exe を優先)。
|
||||
- 既存単体スクリプトは `tools/smokes/archive/` へ暫定退避(参照減後に削除検討)。
|
||||
|
||||
|
||||
What Changed (recent)
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
開発者向けクイックスタート: `docs/DEV_QUICKSTART.md`
|
||||
セルフホスト1枚ガイド: `docs/how-to/self-hosting.md`
|
||||
ExternCall(env.*)と println 正規化: `docs/reference/runtime/externcall.md`
|
||||
|
||||
## 目次
|
||||
- [Self-Hosting(自己ホスト開発)](#self-hosting)
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
Developer quickstart: see `docs/DEV_QUICKSTART.md`. Changelog highlights: `CHANGELOG.md`.
|
||||
MIR mode note: default is MIR13 (PHI-off). See `docs/development/mir/MIR13_MODE.md`.
|
||||
Self‑hosting one‑pager: `docs/how-to/self-hosting.md`.
|
||||
ExternCall (env.*) and println normalization: `docs/reference/runtime/externcall.md`.
|
||||
|
||||
## Table of Contents
|
||||
- [Self‑Hosting (Dev Focus)](#self-hosting)
|
||||
|
||||
52
apps/selfhost-compiler/boxes/mir_emitter_box.nyash
Normal file
52
apps/selfhost-compiler/boxes/mir_emitter_box.nyash
Normal file
@ -0,0 +1,52 @@
|
||||
// MirEmitterBox — Minimal MIR JSON v0 emitter (M2 MVP)
|
||||
// Scope: Return(Int) only (const + ret). Safe default to 0 when not found.
|
||||
// Future: add Binary/Compare/ExternCall/BoxCall lowering incrementally.
|
||||
|
||||
box MirEmitterBox {
|
||||
// Extract first Return(Int) value from Stage-1 JSON (very small string scan)
|
||||
_extract_return_int(ast_json) {
|
||||
if ast_json == null { return 0 }
|
||||
// Look for '"type":"Return"'
|
||||
local p = ast_json.lastIndexOf("\"type\":\"Return\"")
|
||||
if p < 0 { p = ast_json.indexOf("\"type\":\"Return\"") }
|
||||
if p < 0 { return 0 }
|
||||
// From there, search for '"type":"Int","value":'
|
||||
local q = ast_json.indexOf("\"type\":\"Int\",\"value\":", p)
|
||||
if q < 0 { return 0 }
|
||||
q = q + 23 // length of the marker
|
||||
// Read consecutive digits (optional minus not handled in Stage-1 yet)
|
||||
local n = ast_json.length()
|
||||
local i = q
|
||||
local s = ""
|
||||
loop(i < n) {
|
||||
local ch = ast_json.substring(i, i+1)
|
||||
if ch >= "0" && ch <= "9" { s = s + ch i = i + 1 } else { break }
|
||||
}
|
||||
if s.length() == 0 { return 0 }
|
||||
// String to int via addition loop
|
||||
local val = 0
|
||||
local k = 0
|
||||
local m = s.length()
|
||||
loop(k < m) {
|
||||
local d = s.substring(k, k+1)
|
||||
local dv = 0
|
||||
if d == "1" { dv = 1 } else { if d == "2" { dv = 2 } else { if d == "3" { dv = 3 } else { if d == "4" { dv = 4 } else { if d == "5" { dv = 5 } else { if d == "6" { dv = 6 } else { if d == "7" { dv = 7 } else { if d == "8" { dv = 8 } else { if d == "9" { dv = 9 } } } } } } } } }
|
||||
val = val * 10 + dv
|
||||
k = k + 1
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Build minimal MIR JSON v0: main with const -> ret
|
||||
emit_mir_min(ast_json) {
|
||||
local retv = me._extract_return_int(ast_json)
|
||||
local s = "{\"functions\":[{\"name\":\"main\",\"params\":[],\"blocks\":[{\"id\":0,\"instructions\":["
|
||||
s = s + "{\\\"op\\\":\\\"const\\\",\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":" + retv + "},\\\"dst\\\":1},"
|
||||
s = s + "{\\\"op\\\":\\\"ret\\\",\\\"value\\\":1}"
|
||||
s = s + "]}]}]}"
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
static box MirEmitterStub { main(args) { return 0 } }
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
include "apps/selfhost-compiler/boxes/debug_box.nyash"
|
||||
include "apps/selfhost-compiler/boxes/parser_box.nyash"
|
||||
include "apps/selfhost-compiler/boxes/emitter_box.nyash"
|
||||
include "apps/selfhost-compiler/boxes/mir_emitter_box.nyash"
|
||||
|
||||
static box Main {
|
||||
// ---- IO helper ----
|
||||
@ -90,27 +91,36 @@ static box Main {
|
||||
|
||||
// Gate: minimal JSON when requested via script arg
|
||||
local min_mode = 0
|
||||
local emit_mir = 0
|
||||
if args != null {
|
||||
local alen = args.length()
|
||||
local i = 0
|
||||
loop(i < alen) {
|
||||
local arg = args.get(i)
|
||||
if arg == "--min-json" { min_mode = 1 }
|
||||
if arg == "--emit-mir" { emit_mir = 1 }
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
local json = null
|
||||
local ast_json = null
|
||||
if min_mode == 1 {
|
||||
json = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}"
|
||||
ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}"
|
||||
} else {
|
||||
json = me.parse_program(src, stage3_mode)
|
||||
ast_json = me.parse_program(src, stage3_mode)
|
||||
}
|
||||
|
||||
// Emit via EmitterBox (attach meta.usings when available)
|
||||
local emitter = new EmitterBox()
|
||||
json = emitter.emit_program(json, me._usings)
|
||||
if emit_mir == 1 {
|
||||
// Lower minimal AST to MIR JSON (Return(Int) only for MVP)
|
||||
local mir = new MirEmitterBox()
|
||||
json = mir.emit_mir_min(ast_json)
|
||||
} else {
|
||||
// Emit Stage‑1 JSON with metadata
|
||||
local emitter = new EmitterBox()
|
||||
json = emitter.emit_program(ast_json, me._usings)
|
||||
}
|
||||
|
||||
// Output
|
||||
// Output JSON
|
||||
local console = new ConsoleBox()
|
||||
console.println(json)
|
||||
return 0
|
||||
|
||||
@ -716,8 +716,11 @@ pub extern "C" fn main() -> i32 {
|
||||
}
|
||||
// SAFETY: if not linked, calling will be an unresolved symbol at link-time; we rely on link step to include ny_main.
|
||||
let v = ny_main();
|
||||
// Print standardized result line for golden comparisons
|
||||
println!("Result: {}", v);
|
||||
// Print standardized result line for golden comparisons (can be silenced for tests)
|
||||
let silent = std::env::var("NYASH_NYRT_SILENT_RESULT").ok().as_deref() == Some("1");
|
||||
if !silent {
|
||||
println!("Result: {}", v);
|
||||
}
|
||||
// Optional GC metrics after program completes
|
||||
let want_json = std::env::var("NYASH_GC_METRICS_JSON").ok().as_deref() == Some("1");
|
||||
let want_text = std::env::var("NYASH_GC_METRICS").ok().as_deref() == Some("1");
|
||||
|
||||
42
docs/reference/runtime/externcall.md
Normal file
42
docs/reference/runtime/externcall.md
Normal file
@ -0,0 +1,42 @@
|
||||
# ExternCall — Runtime Interfaces (env.*)
|
||||
|
||||
Overview
|
||||
- ExternCall represents calls to host-provided interfaces, addressed by an interface name and method, e.g. `env.console.log`.
|
||||
- In Nyash, source-level `print/println` normalize to an ExternCall targeting the console interface.
|
||||
|
||||
Normalization of println/print
|
||||
- Builder/Optimizer rewrites language-level printing to ExternCall:
|
||||
- `println(x)` or `ConsoleBox.println(x)` → `ExternCall("env.console", "log", [x])`
|
||||
- Rationale: unifies backends and keeps ConsoleBox for other roles (vtable optimization, advanced console APIs).
|
||||
|
||||
Backend Behavior
|
||||
- LLVM/AOT (EXE-first):
|
||||
- `env.console.log` lowers to NyRT exports and links statically.
|
||||
- Primary mapping uses pointer-API when possible to avoid handle churn:
|
||||
- `nyash.console.log(i8*) -> i64`
|
||||
- Fallback to handle-API helpers if only a handle is available.
|
||||
- Runtime result line: NyRT prints `Result: <code>` after `ny_main()` returns. Set `NYASH_NYRT_SILENT_RESULT=1` to suppress for tests.
|
||||
- PyVM:
|
||||
- Accepts `env.console.log/warn/error` and writes to stdout (MVP). Return is `0` when a destination is present.
|
||||
- JIT:
|
||||
- Host-bridge directly prints the first argument to stdout for `env.console.log` minimal parity.
|
||||
|
||||
MIR JSON v0 Encoding
|
||||
- Instruction shape:
|
||||
- `{ "op": "externcall", "func": "env.console.log", "args": [<vid>], "dst": <vid|null>, "dst_type": "i64"? }`
|
||||
- Builder may also emit `"func": "nyash.console.log"` in some paths; both are accepted by backends.
|
||||
|
||||
Key Fields (JSON v0, minimal)
|
||||
- `op`: literal `"externcall"`.
|
||||
- `func`: fully qualified name. Preferred: `"env.console.log"`. Accepted: `"nyash.console.log"`.
|
||||
- `args`: value-ids array (each is an integer referencing a producer).
|
||||
- `dst`: optional value-id to store result; may be null when ignored.
|
||||
- `dst_type` (optional): backend hint. For console methods, `"i64"` is used when `dst` is present.
|
||||
|
||||
|
||||
Return Value Convention
|
||||
- Console methods return `i64` status (typically 0). Most user code ignores it; when `dst` is set, backends materialize `0`.
|
||||
|
||||
Guidance
|
||||
- Use `print/println` in Nyash source; the toolchain normalizes to ExternCall.
|
||||
- Prefer exit code assertions in EXE-first tests. If you must compare stdout, set `NYASH_NYRT_SILENT_RESULT=1` to hide NyRT's `Result:` line.
|
||||
@ -9,6 +9,7 @@ paths = ["apps", "lib", "."]
|
||||
selfhost.compiler.debug = "apps/selfhost-compiler/boxes/debug_box.nyash"
|
||||
selfhost.compiler.parser = "apps/selfhost-compiler/boxes/parser_box.nyash"
|
||||
selfhost.compiler.emitter = "apps/selfhost-compiler/boxes/emitter_box.nyash"
|
||||
selfhost.compiler.mir = "apps/selfhost-compiler/boxes/mir_emitter_box.nyash"
|
||||
|
||||
# v2 Plugin libraries (loader reads these for TypeBox ABI)
|
||||
[libraries]
|
||||
|
||||
@ -458,15 +458,26 @@ class PyVM:
|
||||
func = inst.get("func")
|
||||
args = [self._read(regs, a) for a in inst.get("args", [])]
|
||||
out: Any = None
|
||||
if func == "nyash.console.println":
|
||||
s = args[0] if args else ""
|
||||
if s is None:
|
||||
s = ""
|
||||
print(str(s))
|
||||
out = 0
|
||||
else:
|
||||
# Unknown extern
|
||||
out = None
|
||||
# Normalize known console/debug externs
|
||||
if isinstance(func, str):
|
||||
if func in ("nyash.console.println", "nyash.console.log", "env.console.log"):
|
||||
s = args[0] if args else ""
|
||||
if s is None:
|
||||
s = ""
|
||||
print(str(s))
|
||||
out = 0
|
||||
elif func in ("nyash.console.warn", "env.console.warn", "nyash.console.error", "env.console.error", "nyash.debug.trace", "env.debug.trace"):
|
||||
s = args[0] if args else ""
|
||||
if s is None:
|
||||
s = ""
|
||||
# Write to stderr for warn/error/trace to approximate real consoles
|
||||
try:
|
||||
import sys as _sys
|
||||
print(str(s), file=_sys.stderr)
|
||||
except Exception:
|
||||
print(str(s))
|
||||
out = 0
|
||||
# Unknown extern -> no-op with 0/None
|
||||
self._set(regs, inst.get("dst"), out)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
@ -327,21 +327,32 @@ impl NyashRunner {
|
||||
// - NYASH_SELFHOST_READ_TMP=1 → "-- --read-tmp"
|
||||
// - NYASH_NY_COMPILER_CHILD_ARGS: additional raw args (split by whitespace)
|
||||
let min_json = std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().unwrap_or_else(|| "0".to_string());
|
||||
if min_json == "1" { cmd.arg("--").arg("--min-json"); }
|
||||
let mut inserted_sep = false;
|
||||
if min_json == "1" {
|
||||
cmd.arg("--").arg("--min-json");
|
||||
inserted_sep = true;
|
||||
}
|
||||
if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") {
|
||||
cmd.arg("--").arg("--read-tmp");
|
||||
if !inserted_sep { cmd.arg("--"); inserted_sep = true; }
|
||||
cmd.arg("--read-tmp");
|
||||
}
|
||||
if let Ok(raw) = std::env::var("NYASH_NY_COMPILER_CHILD_ARGS") {
|
||||
if !inserted_sep { cmd.arg("--"); inserted_sep = true; }
|
||||
for tok in raw.split_whitespace() { cmd.arg(tok); }
|
||||
}
|
||||
// Propagate minimal env; disable plugins to reduce noise
|
||||
// Propagate minimal env; prefer stdlib over plugins in child for stable stdout
|
||||
cmd.env_remove("NYASH_USE_NY_COMPILER");
|
||||
cmd.env_remove("NYASH_CLI_VERBOSE");
|
||||
cmd.env("NYASH_DISABLE_PLUGINS", "1");
|
||||
cmd.env_remove("NYASH_USE_PLUGIN_BUILTINS");
|
||||
// Suppress parent runner's result printing in child
|
||||
cmd.env("NYASH_JSON_ONLY", "1");
|
||||
// Prefer PyVM in child to ensure println/externcall are printed to stdout deterministically
|
||||
cmd.env("NYASH_VM_USE_PY", "1");
|
||||
// Propagate optional gates to child (if present)
|
||||
if let Ok(v) = std::env::var("NYASH_JSON_INCLUDE_USINGS") { cmd.env("NYASH_JSON_INCLUDE_USINGS", v); }
|
||||
if let Ok(v) = std::env::var("NYASH_ENABLE_USING") { cmd.env("NYASH_ENABLE_USING", v); }
|
||||
if let Ok(v) = std::env::var("NYASH_ENABLE_USING") { cmd.env("NYASH_ENABLE_USING", v); }
|
||||
// Child timeout guard (Hotfix for potential infinite loop in child Ny parser)
|
||||
// Config: NYASH_NY_COMPILER_TIMEOUT_MS (default 2000ms)
|
||||
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS")
|
||||
|
||||
@ -101,6 +101,22 @@ impl NyashRunner {
|
||||
}
|
||||
};
|
||||
|
||||
// Optional Phase-15: strip `using` lines (gate) for minimal acceptance in VM path
|
||||
let enable_using = crate::config::env::enable_using();
|
||||
let code = if enable_using {
|
||||
let mut out = String::with_capacity(code.len());
|
||||
for line in code.lines() {
|
||||
let t = line.trim_start();
|
||||
if t.starts_with("using ") {
|
||||
// Strip using lines (module resolution handled by nyash.toml elsewhere)
|
||||
continue;
|
||||
}
|
||||
out.push_str(line);
|
||||
out.push('\n');
|
||||
}
|
||||
out
|
||||
} else { code };
|
||||
|
||||
// Parse to AST
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
|
||||
40
tools/test/bin/run.sh
Normal file
40
tools/test/bin/run.sh
Normal file
@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd)
|
||||
LIB="$ROOT/tools/test/lib/shlib.sh"
|
||||
if [[ ! -f "$LIB" ]]; then echo "lib/shlib.sh not found" >&2; exit 2; fi
|
||||
source "$LIB"
|
||||
|
||||
TAG="all"
|
||||
if [[ "${1:-}" == "--tag" && "${2:-}" != "" ]]; then TAG="$2"; shift 2; fi
|
||||
|
||||
# Discover tests
|
||||
mapfile -t TESTS < <(find "$ROOT/tools/test" -type f -path '*/test.sh' | sort)
|
||||
[[ ${#TESTS[@]} -eq 0 ]] && { echo "no tests found" >&2; exit 2; }
|
||||
|
||||
ok=0; fail=0; skip=0
|
||||
|
||||
for t in "${TESTS[@]}"; do
|
||||
case "$TAG" in
|
||||
fast)
|
||||
# Very small subset: crate-exe and bridge shortcircuit
|
||||
if [[ "$t" != *"/smoke/crate-exe/"* && "$t" != *"/smoke/bridge/"* ]]; then
|
||||
echo "[SKIP] $t"; skip=$((skip+1)); continue
|
||||
fi
|
||||
;;
|
||||
all) ;;
|
||||
*) ;;
|
||||
esac
|
||||
echo "[RUN ] $t"
|
||||
if ( cd "$(dirname "$t")" && bash ./test.sh ); then
|
||||
echo "[ OK ] $t"
|
||||
ok=$((ok+1))
|
||||
else
|
||||
echo "[FAIL] $t"
|
||||
fail=$((fail+1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Summary: ok=$ok fail=$fail skip=$skip"
|
||||
exit $([[ $fail -eq 0 ]] && echo 0 || echo 1)
|
||||
48
tools/test/lib/shlib.sh
Normal file
48
tools/test/lib/shlib.sh
Normal file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Common helpers for tools/test
|
||||
|
||||
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd)
|
||||
# Silence NyRT standardized result line in tests by default
|
||||
export NYASH_NYRT_SILENT_RESULT=${NYASH_NYRT_SILENT_RESULT:-1}
|
||||
|
||||
msg() { echo "$*" >&2; }
|
||||
|
||||
require_cmd() { command -v "$1" >/dev/null 2>&1 || { msg "missing command: $1"; return 1; }; }
|
||||
|
||||
assert_exit() {
|
||||
local cmd=$1 expected=$2
|
||||
set +e
|
||||
bash -lc "$cmd"
|
||||
local code=$?
|
||||
set -e
|
||||
if [[ "$code" -ne "$expected" ]]; then
|
||||
msg "assert_exit failed: expected=$expected got=$code cmd=$cmd"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_grep() {
|
||||
local pattern=$1; shift
|
||||
local text
|
||||
text=$(cat)
|
||||
echo "$text" | rg -q "$pattern" || { msg "assert_grep failed: pattern '$pattern'\n$text"; return 1; }
|
||||
}
|
||||
|
||||
build_nyash_release() { (cd "$ROOT_DIR" && cargo build --release -j 8 >/dev/null); }
|
||||
build_ny_llvmc() { (cd "$ROOT_DIR" && cargo build --release -p nyash-llvm-compiler -j 8 >/dev/null); }
|
||||
build_nyrt() { (cd "$ROOT_DIR/crates/nyrt" && cargo build --release -j 8 >/dev/null); }
|
||||
|
||||
emit_json() { # args: src out_json
|
||||
"$ROOT_DIR/target/release/nyash" --emit-mir-json "$2" --backend mir "$1" >/dev/null
|
||||
}
|
||||
|
||||
run_pyvm_json() { # args: json_path
|
||||
require_cmd python3
|
||||
python3 "$ROOT_DIR/tools/pyvm_runner.py" --in "$1"
|
||||
}
|
||||
|
||||
build_exe_crate() { # args: in_json out_exe
|
||||
"$ROOT_DIR/target/release/ny-llvmc" --in "$1" --emit exe --nyrt "$ROOT_DIR/target/release" --out "$2" --harness "$ROOT_DIR/tools/llvmlite_harness.py"
|
||||
}
|
||||
8
tools/test/smoke/bridge/shortcircuit/test.sh
Normal file
8
tools/test/smoke/bridge/shortcircuit/test.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release
|
||||
assert_exit "timeout -s KILL 60s bash $ROOT/tools/ny_stage2_shortcircuit_smoke.sh" 0
|
||||
|
||||
11
tools/test/smoke/bridge/test.sh
Normal file
11
tools/test/smoke/bridge/test.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release
|
||||
|
||||
# Use existing short-circuit smoke (ensures RHS not executed)
|
||||
assert_exit "bash $ROOT/tools/ny_stage2_shortcircuit_smoke.sh >/dev/null" 0
|
||||
echo "OK: bridge shortcircuit smoke"
|
||||
29
tools/test/smoke/crate-exe/console_log/test.sh
Normal file
29
tools/test/smoke/crate-exe/console_log/test.sh
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release
|
||||
build_ny_llvmc
|
||||
build_nyrt
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
SRC="$TMP_DIR/console_log_smoke.nyash"
|
||||
JSON="$TMP_DIR/console_log_smoke.json"
|
||||
EXE="$TMP_DIR/console_log_smoke.out"
|
||||
|
||||
cat >"$SRC" <<'NY'
|
||||
static box Main {
|
||||
main() {
|
||||
print("hello-console")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
NY
|
||||
|
||||
emit_json "$SRC" "$JSON"
|
||||
build_exe_crate "$JSON" "$EXE"
|
||||
|
||||
assert_exit "$EXE" 0
|
||||
echo "OK: crate-exe console.log smoke (exit=0)"
|
||||
11
tools/test/smoke/crate-exe/peek_expr_block/test.sh
Normal file
11
tools/test/smoke/crate-exe/peek_expr_block/test.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release; build_ny_llvmc; build_nyrt
|
||||
mkdir -p "$ROOT/tmp"
|
||||
emit_json "$ROOT/apps/tests/peek_expr_block.nyash" "$ROOT/tmp/pb.json"
|
||||
build_exe_crate "$ROOT/tmp/pb.json" "$ROOT/tmp/pb"
|
||||
assert_exit "$ROOT/tmp/pb" 1
|
||||
|
||||
11
tools/test/smoke/crate-exe/ternary_basic/test.sh
Normal file
11
tools/test/smoke/crate-exe/ternary_basic/test.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release; build_ny_llvmc; build_nyrt
|
||||
mkdir -p "$ROOT/tmp"
|
||||
emit_json "$ROOT/apps/tests/ternary_basic.nyash" "$ROOT/tmp/tb.json"
|
||||
build_exe_crate "$ROOT/tmp/tb.json" "$ROOT/tmp/tb"
|
||||
assert_exit "$ROOT/tmp/tb" 10
|
||||
|
||||
11
tools/test/smoke/crate-exe/ternary_nested/test.sh
Normal file
11
tools/test/smoke/crate-exe/ternary_nested/test.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release; build_ny_llvmc; build_nyrt
|
||||
mkdir -p "$ROOT/tmp"
|
||||
emit_json "$ROOT/apps/tests/ternary_nested.nyash" "$ROOT/tmp/tn.json"
|
||||
build_exe_crate "$ROOT/tmp/tn.json" "$ROOT/tmp/tn"
|
||||
assert_exit "$ROOT/tmp/tn" 50
|
||||
|
||||
36
tools/test/smoke/crate-exe/test.sh
Normal file
36
tools/test/smoke/crate-exe/test.sh
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
# Build binaries needed
|
||||
build_nyash_release
|
||||
build_ny_llvmc
|
||||
build_nyrt
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
SRC="$TMP_DIR/crate_exe_smoke.nyash"
|
||||
JSON="$TMP_DIR/crate_exe_smoke.json"
|
||||
EXE="$TMP_DIR/crate_exe_smoke.out"
|
||||
|
||||
cat >"$SRC" <<'NY'
|
||||
// minimal program returning 7 (no println to avoid unresolved symbols)
|
||||
static box Main {
|
||||
main() {
|
||||
return 7
|
||||
}
|
||||
}
|
||||
NY
|
||||
|
||||
# Emit MIR JSON and build exe via crate compiler
|
||||
emit_json "$SRC" "$JSON"
|
||||
build_exe_crate "$JSON" "$EXE"
|
||||
|
||||
# Run and assert (exit code only)
|
||||
set +e
|
||||
OUT=$("$EXE" 2>&1)
|
||||
CODE=$?
|
||||
set -e
|
||||
[[ "$CODE" -eq 7 ]] || { echo "exit=$CODE"; exit 1; }
|
||||
echo "OK: crate-exe smoke (exit=7)"
|
||||
13
tools/test/smoke/pyvm/esc_dirname_smoke/test.sh
Normal file
13
tools/test/smoke/pyvm/esc_dirname_smoke/test.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release
|
||||
mkdir -p "$ROOT/tmp"
|
||||
emit_json "$ROOT/apps/tests/esc_dirname_smoke.nyash" "$ROOT/tmp/pyvm_esc_dir.json"
|
||||
out=$(run_pyvm_json "$ROOT/tmp/pyvm_esc_dir.json")
|
||||
# Expect two lines: escaped string and dirname join
|
||||
echo "$out" | sed -n '1p' | assert_grep '^A\\\\\\"B\\\\\\\\C$'
|
||||
echo "$out" | sed -n '2p' | assert_grep '^dir1/dir2$'
|
||||
|
||||
11
tools/test/smoke/pyvm/me_method_call/test.sh
Normal file
11
tools/test/smoke/pyvm/me_method_call/test.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release
|
||||
mkdir -p "$ROOT/tmp"
|
||||
emit_json "$ROOT/apps/tests/me_method_call.nyash" "$ROOT/tmp/pyvm_me_method.json"
|
||||
out=$(run_pyvm_json "$ROOT/tmp/pyvm_me_method.json")
|
||||
echo "$out" | assert_grep '^n=3$'
|
||||
|
||||
11
tools/test/smoke/pyvm/peek_return_value/test.sh
Normal file
11
tools/test/smoke/pyvm/peek_return_value/test.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release
|
||||
mkdir -p "$ROOT/tmp"
|
||||
emit_json "$ROOT/apps/tests/peek_return_value.nyash" "$ROOT/tmp/pyvm_peek_ret.json"
|
||||
out=$(run_pyvm_json "$ROOT/tmp/pyvm_peek_ret.json")
|
||||
echo "$out" | assert_grep '^1$'
|
||||
|
||||
13
tools/test/smoke/pyvm/string_ops_basic/test.sh
Normal file
13
tools/test/smoke/pyvm/string_ops_basic/test.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release
|
||||
mkdir -p "$ROOT/tmp"
|
||||
emit_json "$ROOT/apps/tests/string_ops_basic.nyash" "$ROOT/tmp/pyvm_string_ops.json"
|
||||
out=$(run_pyvm_json "$ROOT/tmp/pyvm_string_ops.json")
|
||||
echo "$out" | assert_grep '^len=5$'
|
||||
echo "$out" | assert_grep '^sub=bcd$'
|
||||
echo "$out" | assert_grep '^idx=1$'
|
||||
|
||||
18
tools/test/smoke/pyvm/test.sh
Normal file
18
tools/test/smoke/pyvm/test.sh
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
require_cmd python3
|
||||
build_nyash_release
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
JSON="$TMP_DIR/ternary_basic.json"
|
||||
|
||||
APP="$ROOT/apps/tests/ternary_basic.nyash"
|
||||
emit_json "$APP" "$JSON"
|
||||
|
||||
# Expect exit code 10 for ternary_basic
|
||||
assert_exit "run_pyvm_json $JSON >/dev/null" 10
|
||||
echo "OK: pyvm ternary_basic exit=10"
|
||||
36
tools/test/smoke/selfhost/m2_min/test.sh
Normal file
36
tools/test/smoke/selfhost/m2_min/test.sh
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../../../../.." && pwd)
|
||||
source "$ROOT/tools/test/lib/shlib.sh"
|
||||
|
||||
build_nyash_release
|
||||
build_ny_llvmc
|
||||
build_nyrt
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
SRC="$TMP_DIR/m2_min.nyash"
|
||||
JSON="$TMP_DIR/m2_min.json"
|
||||
EXE="$TMP_DIR/m2_min.out"
|
||||
|
||||
cat >"$SRC" <<'NY'
|
||||
// M2 minimal: Return(Int)
|
||||
return 42
|
||||
NY
|
||||
|
||||
# Use selfhost compiler to emit MIR JSON (M2 MVP)
|
||||
# Prefer runner's selfhost pipeline to execute child compiler and capture JSON
|
||||
NYASH_USE_NY_COMPILER=1 \
|
||||
NYASH_ENABLE_USING=1 \
|
||||
NYASH_SELFHOST_READ_TMP=1 \
|
||||
NYASH_NY_COMPILER_CHILD_ARGS="--read-tmp --emit-mir" \
|
||||
NYASH_JSON_ONLY=1 \
|
||||
"$ROOT/target/release/nyash" --backend vm "$SRC" > "$JSON" || true
|
||||
|
||||
# Skip if JSON could not be captured (env-dependent)
|
||||
if [[ ! -s "$JSON" ]]; then echo "[SKIP] selfhost M2 minimal: empty JSON"; exit 0; fi
|
||||
|
||||
# Build EXE via crate compiler and assert exit code
|
||||
build_exe_crate "$JSON" "$EXE"
|
||||
assert_exit "$EXE" 42
|
||||
echo "OK: selfhost M2 minimal (return 42)"
|
||||
Reference in New Issue
Block a user