diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 528eaa09..2f8f9fee 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -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 `、`--libs` を実装。`.o` 生成後に NyRT とリンクして実行可能に。 - 代表ケースで EXE 実行(exit code)を確認。 + - CLI: 直接 EXE 出力 + - `nyash --emit-exe [--emit-exe-nyrt ] [--emit-exe-libs ""]` を追加。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) diff --git a/README.ja.md b/README.ja.md index 1a5aedc2..ba1506eb 100644 --- a/README.ja.md +++ b/README.ja.md @@ -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) diff --git a/README.md b/README.md index ccf9b39e..1a9ec89b 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/apps/selfhost-compiler/boxes/mir_emitter_box.nyash b/apps/selfhost-compiler/boxes/mir_emitter_box.nyash new file mode 100644 index 00000000..fa0c0740 --- /dev/null +++ b/apps/selfhost-compiler/boxes/mir_emitter_box.nyash @@ -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 } } + diff --git a/apps/selfhost-compiler/compiler.nyash b/apps/selfhost-compiler/compiler.nyash index ca4d3030..62840dad 100644 --- a/apps/selfhost-compiler/compiler.nyash +++ b/apps/selfhost-compiler/compiler.nyash @@ -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 diff --git a/crates/nyrt/src/lib.rs b/crates/nyrt/src/lib.rs index 4dc9bc18..51d47668 100644 --- a/crates/nyrt/src/lib.rs +++ b/crates/nyrt/src/lib.rs @@ -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"); diff --git a/docs/reference/runtime/externcall.md b/docs/reference/runtime/externcall.md new file mode 100644 index 00000000..f5d96e4f --- /dev/null +++ b/docs/reference/runtime/externcall.md @@ -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: ` 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": [], "dst": , "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. diff --git a/nyash.toml b/nyash.toml index 0e0bbc1e..81c6f790 100644 --- a/nyash.toml +++ b/nyash.toml @@ -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] diff --git a/src/llvm_py/pyvm/vm.py b/src/llvm_py/pyvm/vm.py index 903dc20d..a12672e7 100644 --- a/src/llvm_py/pyvm/vm.py +++ b/src/llvm_py/pyvm/vm.py @@ -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 diff --git a/src/runner/modes/common.rs b/src/runner/modes/common.rs index 99202401..de9a45c3 100644 --- a/src/runner/modes/common.rs +++ b/src/runner/modes/common.rs @@ -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") diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index 3ade446f..3f64d1ff 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -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, diff --git a/tools/test/bin/run.sh b/tools/test/bin/run.sh new file mode 100644 index 00000000..c25a128d --- /dev/null +++ b/tools/test/bin/run.sh @@ -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) diff --git a/tools/test/lib/shlib.sh b/tools/test/lib/shlib.sh new file mode 100644 index 00000000..bc6fba19 --- /dev/null +++ b/tools/test/lib/shlib.sh @@ -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" +} diff --git a/tools/test/smoke/bridge/shortcircuit/test.sh b/tools/test/smoke/bridge/shortcircuit/test.sh new file mode 100644 index 00000000..19047677 --- /dev/null +++ b/tools/test/smoke/bridge/shortcircuit/test.sh @@ -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 + diff --git a/tools/test/smoke/bridge/test.sh b/tools/test/smoke/bridge/test.sh new file mode 100644 index 00000000..d233ef82 --- /dev/null +++ b/tools/test/smoke/bridge/test.sh @@ -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" diff --git a/tools/test/smoke/crate-exe/console_log/test.sh b/tools/test/smoke/crate-exe/console_log/test.sh new file mode 100644 index 00000000..1ca3b0ed --- /dev/null +++ b/tools/test/smoke/crate-exe/console_log/test.sh @@ -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)" diff --git a/tools/test/smoke/crate-exe/peek_expr_block/test.sh b/tools/test/smoke/crate-exe/peek_expr_block/test.sh new file mode 100644 index 00000000..d2d465c6 --- /dev/null +++ b/tools/test/smoke/crate-exe/peek_expr_block/test.sh @@ -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 + diff --git a/tools/test/smoke/crate-exe/ternary_basic/test.sh b/tools/test/smoke/crate-exe/ternary_basic/test.sh new file mode 100644 index 00000000..abb00a5e --- /dev/null +++ b/tools/test/smoke/crate-exe/ternary_basic/test.sh @@ -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 + diff --git a/tools/test/smoke/crate-exe/ternary_nested/test.sh b/tools/test/smoke/crate-exe/ternary_nested/test.sh new file mode 100644 index 00000000..34350064 --- /dev/null +++ b/tools/test/smoke/crate-exe/ternary_nested/test.sh @@ -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 + diff --git a/tools/test/smoke/crate-exe/test.sh b/tools/test/smoke/crate-exe/test.sh new file mode 100644 index 00000000..318ff6a3 --- /dev/null +++ b/tools/test/smoke/crate-exe/test.sh @@ -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)" diff --git a/tools/test/smoke/pyvm/esc_dirname_smoke/test.sh b/tools/test/smoke/pyvm/esc_dirname_smoke/test.sh new file mode 100644 index 00000000..a1097f5e --- /dev/null +++ b/tools/test/smoke/pyvm/esc_dirname_smoke/test.sh @@ -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$' + diff --git a/tools/test/smoke/pyvm/me_method_call/test.sh b/tools/test/smoke/pyvm/me_method_call/test.sh new file mode 100644 index 00000000..c4546c95 --- /dev/null +++ b/tools/test/smoke/pyvm/me_method_call/test.sh @@ -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$' + diff --git a/tools/test/smoke/pyvm/peek_return_value/test.sh b/tools/test/smoke/pyvm/peek_return_value/test.sh new file mode 100644 index 00000000..6ca93e90 --- /dev/null +++ b/tools/test/smoke/pyvm/peek_return_value/test.sh @@ -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$' + diff --git a/tools/test/smoke/pyvm/string_ops_basic/test.sh b/tools/test/smoke/pyvm/string_ops_basic/test.sh new file mode 100644 index 00000000..ceecc3ea --- /dev/null +++ b/tools/test/smoke/pyvm/string_ops_basic/test.sh @@ -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$' + diff --git a/tools/test/smoke/pyvm/test.sh b/tools/test/smoke/pyvm/test.sh new file mode 100644 index 00000000..5e17a317 --- /dev/null +++ b/tools/test/smoke/pyvm/test.sh @@ -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" diff --git a/tools/test/smoke/selfhost/m2_min/test.sh b/tools/test/smoke/selfhost/m2_min/test.sh new file mode 100644 index 00000000..f27f6969 --- /dev/null +++ b/tools/test/smoke/selfhost/m2_min/test.sh @@ -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)"