From 8aa01668ff3d8251fdece78d9953ec25227b2448 Mon Sep 17 00:00:00 2001 From: Selfhosting Dev Date: Wed, 17 Sep 2025 22:01:29 +0900 Subject: [PATCH] =?UTF-8?q?feat(plugins):=20migrate=20Python=20family=20to?= =?UTF-8?q?=20TypeBox=20v2;=20complete=20first=E2=80=91party=20v2=20set=20?= =?UTF-8?q?(Regex/Net/Path/Math/Time/File)\n\nfeat(smoke):=20add=20Net=20r?= =?UTF-8?q?ound=E2=80=91trip=20sample=20and=20wire=20to=20plugin=20v2=20sm?= =?UTF-8?q?oke\n\nfeat(cli):=20add=20--emit-mir-json=20and=20runner=20emit?= =?UTF-8?q?+exit=20path\n\nchore(config):=20register=20Python=20v2=20boxes?= =?UTF-8?q?=20in=20nyash.toml\n\nchore(loader):=20improve=20v2=20diagnosti?= =?UTF-8?q?cs=20and=20ensure=20v2=20shim=20dispatch\n\nchore(build):=20int?= =?UTF-8?q?egrate=20ny-llvmc=20env=20gate=20in=20build=5Fllvm.sh\n\nchore(?= =?UTF-8?q?tasks):=20update=20CURRENT=5FTASK=20with=20v2=20Python/Net/emit?= =?UTF-8?q?=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/plugin-v2-smoke.yml | 26 ++ CURRENT_TASK.md | 43 +++ apps/tests/net_roundtrip.nyash | 25 ++ apps/tests/plugin_v2_functional.nyash | 12 + nyash.toml | 237 +++++++++++++ plugins/nyash-filebox-plugin/src/lib.rs | 315 ++++++++++++++++++ plugins/nyash-math-plugin/src/lib.rs | 99 ++++++ plugins/nyash-net-plugin/src/lib.rs | 254 ++++++++++++++ plugins/nyash-path-plugin/src/lib.rs | 149 +++++++++ .../nyash-python-compiler-plugin/src/lib.rs | 101 ++++++ plugins/nyash-python-parser-plugin/src/lib.rs | 107 ++++++ plugins/nyash-python-plugin/src/lib.rs | 97 +++++- plugins/nyash-regex-plugin/src/lib.rs | 117 +++++++ src/cli.rs | 10 + src/runner/dispatch.rs | 10 + .../plugin_loader_v2/enabled/loader.rs | 42 ++- src/runtime/plugin_loader_v2/enabled/mod.rs | 1 - tools/build_llvm.sh | 42 ++- tools/plugin_v2_smoke.sh | 114 +++++++ 19 files changed, 1782 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/plugin-v2-smoke.yml create mode 100644 apps/tests/net_roundtrip.nyash create mode 100644 apps/tests/plugin_v2_functional.nyash create mode 100644 tools/plugin_v2_smoke.sh diff --git a/.github/workflows/plugin-v2-smoke.yml b/.github/workflows/plugin-v2-smoke.yml new file mode 100644 index 00000000..ce1c2654 --- /dev/null +++ b/.github/workflows/plugin-v2-smoke.yml @@ -0,0 +1,26 @@ +name: plugin-v2-smoke + +on: + push: + branches: [ main, master, "**" ] + pull_request: + branches: [ "**" ] + +jobs: + linux-smoke: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + + - name: Run plugin v2 smoke + run: | + bash tools/plugin_v2_smoke.sh + diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index f30250e0..bac400cf 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -155,3 +155,46 @@ Notes / Policies - No full tracing/moving GC yet; handles/Arc lifetimes govern object retention. Safepoint/barrier/roots are staging utilities. - GC mode UX: keep user‑facing modes minimal (rc+cycle, minorgen); advanced modes are opt‑in for language dev. - Legacy Interpreter/VM は段階的にアーカイブへ。日常の意味論確認は PyVM を基準として継続。 + +Plugin ABI v2 updates (2025‑09‑17) +- v2 migration (TypeBox) 完了/進捗 + - 完了: FileBox / PathBox / RegexBox / MathBox / TimeBox + - Net 完了: ClientBox / ResponseBox / RequestBox / ServerBox / SockServerBox / SockClientBox / SockConnBox + - 既存: ConsoleBox / StringBox / IntegerBox / MapBox は v2 実装あり +- ローダ診断強化 + - `NYASH_DEBUG_PLUGIN=1` で TypeBox 未検出/ABI不一致/invoke_id未定義の詳細をログ出力 +- Docs 追補 + - `docs/reference/plugin-abi/nyash_abi_v2.md` に命名規約・例(Regex/Net)を追記 + - `include/nyash_abi.h`(Cヘッダ)追加済み +- 設定/スモーク/CI + - `nyash.toml` に各 v2 Box を登録(type_id/method_id 定義) + - スモーク: `tools/plugin_v2_smoke.sh`(Linux常時)。全 v2 プラグインのロード確認+簡易機能スモーク(`apps/tests/plugin_v2_functional.nyash`) +- LLVM 共通化の足場 + - `tools/build_llvm.sh` に `NYASH_LLVM_COMPILER=crate|harness` を追加(`crate` は `ny-llvmc`。JSON は `NYASH_LLVM_MIR_JSON` 指定) + - JSON スキーマ検証を可能なら実行(`tools/validate_mir_json.py`) + +Plugin ABI v2 updates (2025‑09‑17 — Python family + Net smoke + JSON emit) +- v2 migration(Python 系 完了) + - `plugins/nyash-python-plugin`: `nyash_typebox_PyRuntimeBox` / `nyash_typebox_PyObjectBox` を追加(resolve/invoke_id 実装。既存 v1 は残存) + - `plugins/nyash-python-parser-plugin`: `nyash_typebox_PythonParserBox` を追加(birth/parse/fini) + - `plugins/nyash-python-compiler-plugin`: `nyash_typebox_PythonCompilerBox` を追加(birth/compile/fini) + - `nyash.toml` に Python 系 3 ライブラリを登録(type_id: 40/41/60/61) +- Net 往復スモーク(最小) + - 追加: `apps/tests/net_roundtrip.nyash`(Server.start→Client.get→Server.accept/respond→Client.readBody) + - 追加: `tools/plugin_v2_smoke.sh` に Net 機能スモークを条件付きで実行(CI常時ジョブに内包) +- nyash → MIR JSON emit フラグ + - CLI `--emit-mir-json ` を追加(`src/cli.rs`)。`runner.execute_mir_module` でファイル出力→即終了を実装。 + - これにより `ny-llvmc` へ JSON を直結しやすくなった(次の CI 経路で使用予定) + +Plan after restart(次の計画) +- Python 系プラグインの v2 化(parser/compiler/python-plugin) +- Docs 追記(Net/Regex のメソッド表、型/戻りTLVの簡易表) +- スモーク強化 + - Net: `ServerBox.start -> Client.get -> Request.respond -> Response.readBody` の往復最小ケースを追加 + - 主要 v2 Box の軽機能(String/Array/Map/Regex/Path/Math/Time)を 1 ジョブで走らせる +- LLVM 共通化 + - `nyash` からの JSON emit コマンド/フラグ導入(`--emit-mir-json ` など)→ `ny-llvmc` 直結 + - CI に `ny-llvmc` 実 JSON 経路を追加(Linux 常時) +- NyRT 整理(軽) + - TLV/エラー定数を `include/nyash_abi.h` と整合させる(ヘッダ経由参照) + - (必要時)`nyrt_last_error()` の追加検討 diff --git a/apps/tests/net_roundtrip.nyash b/apps/tests/net_roundtrip.nyash new file mode 100644 index 00000000..d34bb836 --- /dev/null +++ b/apps/tests/net_roundtrip.nyash @@ -0,0 +1,25 @@ +// Net round-trip smoke: ServerBox ↔ ClientBox via HTTP over localhost +// Steps: +// 1) start server on port 18081 +// 2) client GET http://127.0.0.1:18081/hello and get a ResponseBox handle +// 3) server accept(), respond with status=200 and body "ok" +// 4) client read status/body and print values + +local srv = new ServerBox() +srv.start(18081) + +local cli = new ClientBox() +local resp_c = cli.get("http://127.0.0.1:18081/hello") + +local req = srv.accept() +local resp_s = new ResponseBox() +resp_s.setStatus(200) +resp_s.setHeader("Content-Type", "text/plain") +resp_s.write("ok") +req.respond(resp_s) + +print(resp_c.getStatus()) +print(resp_c.readBody()) + +// Clean up +srv.stop() diff --git a/apps/tests/plugin_v2_functional.nyash b/apps/tests/plugin_v2_functional.nyash new file mode 100644 index 00000000..7af41384 --- /dev/null +++ b/apps/tests/plugin_v2_functional.nyash @@ -0,0 +1,12 @@ +// Plugin v2 functional smoke (regex + response only) +local r = new RegexBox() +r.compile("h.llo") +print(r.isMatch("hello")) +print(r.isMatch("HALLO")) + +local resp = new ResponseBox() +resp.setStatus(201) +resp.setHeader("X-Test", "ok") +resp.write("body-123") +print(resp.getStatus()) +print(resp.readBody()) diff --git a/nyash.toml b/nyash.toml index bed090fe..e0834b3c 100644 --- a/nyash.toml +++ b/nyash.toml @@ -10,3 +10,240 @@ 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" +# v2 Plugin libraries (loader reads these for TypeBox ABI) +[libraries] +[libraries."libnyash_filebox_plugin.so"] +boxes = ["FileBox"] +path = "plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin.so" + +[libraries."libnyash_filebox_plugin.so".FileBox] +type_id = 6 +abi_version = 1 +singleton = false + +[libraries."libnyash_filebox_plugin.so".FileBox.methods] +birth = { method_id = 0 } +open = { method_id = 1 } +read = { method_id = 2 } +write = { method_id = 3 } +close = { method_id = 4 } +exists = { method_id = 5 } +copyFrom = { method_id = 7 } +cloneSelf = { method_id = 8 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_path_plugin.so"] +boxes = ["PathBox"] +path = "plugins/nyash-path-plugin/target/release/libnyash_path_plugin.so" + +[libraries."libnyash_path_plugin.so".PathBox] +type_id = 55 +abi_version = 1 +singleton = false + +[libraries."libnyash_path_plugin.so".PathBox.methods] +birth = { method_id = 0 } +join = { method_id = 1 } +dirname = { method_id = 2 } +basename = { method_id = 3 } +extname = { method_id = 4 } +isAbs = { method_id = 5 } +normalize = { method_id = 6 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_math_plugin.so"] +boxes = ["MathBox", "TimeBox"] +path = "plugins/nyash-math-plugin/target/release/libnyash_math_plugin.so" + +[libraries."libnyash_math_plugin.so".MathBox] +type_id = 50 +abi_version = 1 +singleton = false + +[libraries."libnyash_math_plugin.so".MathBox.methods] +birth = { method_id = 0 } +sqrt = { method_id = 1 } +sin = { method_id = 2 } +cos = { method_id = 3 } +round = { method_id = 4 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_math_plugin.so".TimeBox] +type_id = 51 +abi_version = 1 +singleton = false + +[libraries."libnyash_math_plugin.so".TimeBox.methods] +birth = { method_id = 0 } +now = { method_id = 1 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_regex_plugin.so"] +boxes = ["RegexBox"] +path = "plugins/nyash-regex-plugin/target/release/libnyash_regex_plugin.so" + +[libraries."libnyash_regex_plugin.so".RegexBox] +type_id = 52 +abi_version = 1 +singleton = false + +[libraries."libnyash_regex_plugin.so".RegexBox.methods] +birth = { method_id = 0 } +compile = { method_id = 1 } +isMatch = { method_id = 2 } +find = { method_id = 3 } +replaceAll = { method_id = 4 } +split = { method_id = 5 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_net_plugin.so"] +boxes = ["ClientBox", "ResponseBox", "RequestBox", "ServerBox", "SockServerBox", "SockClientBox", "SockConnBox"] +path = "plugins/nyash-net-plugin/target/release/libnyash_net_plugin.so" + +[libraries."libnyash_net_plugin.so".ClientBox] +type_id = 23 +abi_version = 1 +singleton = false + +[libraries."libnyash_net_plugin.so".ClientBox.methods] +birth = { method_id = 0 } +get = { method_id = 1 } +post = { method_id = 2 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_net_plugin.so".ResponseBox] +type_id = 22 +abi_version = 1 +singleton = false + +[libraries."libnyash_net_plugin.so".ResponseBox.methods] +birth = { method_id = 0 } +setStatus = { method_id = 1 } +setHeader = { method_id = 2 } +write = { method_id = 3 } +readBody = { method_id = 4 } +getStatus = { method_id = 5 } +getHeader = { method_id = 6 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_net_plugin.so".RequestBox] +type_id = 21 +abi_version = 1 +singleton = false + +[libraries."libnyash_net_plugin.so".RequestBox.methods] +birth = { method_id = 0 } +path = { method_id = 1 } +readBody = { method_id = 2 } +respond = { method_id = 3 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_net_plugin.so".ServerBox] +type_id = 20 +abi_version = 1 +singleton = false + +[libraries."libnyash_net_plugin.so".ServerBox.methods] +birth = { method_id = 0 } +start = { method_id = 1 } +stop = { method_id = 2 } +accept = { method_id = 3 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_net_plugin.so".SockServerBox] +type_id = 30 +abi_version = 1 +singleton = false + +[libraries."libnyash_net_plugin.so".SockServerBox.methods] +birth = { method_id = 0 } +start = { method_id = 1 } +stop = { method_id = 2 } +accept = { method_id = 3 } +acceptTimeout = { method_id = 4 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_net_plugin.so".SockClientBox] +type_id = 32 +abi_version = 1 +singleton = false + +[libraries."libnyash_net_plugin.so".SockClientBox.methods] +birth = { method_id = 0 } +connect = { method_id = 1 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_net_plugin.so".SockConnBox] +type_id = 31 +abi_version = 1 +singleton = false + +[libraries."libnyash_net_plugin.so".SockConnBox.methods] +birth = { method_id = 0 } +send = { method_id = 1 } +recv = { method_id = 2 } +close = { method_id = 3 } +recvTimeout = { method_id = 4 } +fini = { method_id = 4294967295 } + +# Python (v2 TypeBox) plugins +[libraries."libnyash_python_plugin.so"] +boxes = ["PyRuntimeBox", "PyObjectBox"] +path = "plugins/nyash-python-plugin/target/release/libnyash_python_plugin.so" + +[libraries."libnyash_python_plugin.so".PyRuntimeBox] +type_id = 40 +abi_version = 1 +singleton = false + +[libraries."libnyash_python_plugin.so".PyRuntimeBox.methods] +birth = { method_id = 0 } +eval = { method_id = 1 } +import = { method_id = 2 } +evalR = { method_id = 11 } +importR = { method_id = 12 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_python_plugin.so".PyObjectBox] +type_id = 41 +abi_version = 1 +singleton = false + +[libraries."libnyash_python_plugin.so".PyObjectBox.methods] +birth = { method_id = 0 } +getattr = { method_id = 1 } +call = { method_id = 2 } +str = { method_id = 3 } +callKw = { method_id = 5 } +getattrR = { method_id = 11 } +callR = { method_id = 12 } +callKwR = { method_id = 15 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_python_parser_plugin.so"] +boxes = ["PythonParserBox"] +path = "plugins/nyash-python-parser-plugin/target/release/libnyash_python_parser_plugin.so" + +[libraries."libnyash_python_parser_plugin.so".PythonParserBox] +type_id = 60 +abi_version = 1 +singleton = false + +[libraries."libnyash_python_parser_plugin.so".PythonParserBox.methods] +birth = { method_id = 0 } +parse = { method_id = 1 } +fini = { method_id = 4294967295 } + +[libraries."libnyash_python_compiler_plugin.so"] +boxes = ["PythonCompilerBox"] +path = "plugins/nyash-python-compiler-plugin/target/release/libnyash_python_compiler_plugin.so" + +[libraries."libnyash_python_compiler_plugin.so".PythonCompilerBox] +type_id = 61 +abi_version = 1 +singleton = false + +[libraries."libnyash_python_compiler_plugin.so".PythonCompilerBox.methods] +birth = { method_id = 0 } +compile = { method_id = 1 } +fini = { method_id = 4294967295 } diff --git a/plugins/nyash-filebox-plugin/src/lib.rs b/plugins/nyash-filebox-plugin/src/lib.rs index e0cdfa4c..caba88e4 100644 --- a/plugins/nyash-filebox-plugin/src/lib.rs +++ b/plugins/nyash-filebox-plugin/src/lib.rs @@ -3,6 +3,7 @@ //! Provides file I/O operations as a Nyash plugin use std::collections::HashMap; +use std::ffi::CStr; use std::os::raw::c_char; // std::ptr削除(未使用) use std::io::{Read, Seek, SeekFrom, Write}; @@ -633,3 +634,317 @@ pub extern "C" fn nyash_plugin_shutdown() { // ============ Unified Plugin API ============ // Note: Metadata (Box types, methods) now comes from nyash.toml // This plugin provides only the actual processing functions + +// ===== TypeBox ABI v2 (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' (0x54594258) + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const c_char, // C string, e.g., "FileBox\0" + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +extern "C" fn filebox_resolve(name: *const c_char) -> u32 { + if name.is_null() { + return 0; + } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + // lifecycle (optional to expose via resolve) + "birth" => METHOD_BIRTH, + "fini" => METHOD_FINI, + // methods + "open" => METHOD_OPEN, + "read" => METHOD_READ, + "write" => METHOD_WRITE, + "close" => METHOD_CLOSE, + "exists" => METHOD_EXISTS, + "copyFrom" => METHOD_COPY_FROM, + "cloneSelf" => METHOD_CLONE_SELF, + _ => 0, + } +} + +extern "C" fn filebox_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { + match method_id { + // Support birth through per-Box invoke for v2 shim + METHOD_BIRTH => { + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + if preflight(result, result_len, 4) { + return NYB_E_SHORT_BUFFER; + } + let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed); + if let Ok(mut map) = INSTANCES.lock() { + map.insert( + id, + FileBoxInstance { file: None, path: String::new(), buffer: None }, + ); + } else { + return NYB_E_PLUGIN_ERROR; + } + let b = id.to_le_bytes(); + std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); + *result_len = 4; + NYB_SUCCESS + } + METHOD_FINI => { + if let Ok(mut map) = INSTANCES.lock() { + map.remove(&instance_id); + NYB_SUCCESS + } else { + NYB_E_PLUGIN_ERROR + } + } + METHOD_OPEN => { + // args: TLV { String path, String mode } + let slice = std::slice::from_raw_parts(args, args_len); + match tlv_parse_two_strings(slice) { + Ok((path, mode)) => { + if preflight(result, result_len, 8) { + return NYB_E_SHORT_BUFFER; + } + if let Ok(mut map) = INSTANCES.lock() { + if let Some(inst) = map.get_mut(&instance_id) { + match open_file(&mode, &path) { + Ok(file) => { + inst.file = Some(file); + return write_tlv_void(result, result_len); + } + Err(_) => return NYB_E_PLUGIN_ERROR, + } + } else { + return NYB_E_INVALID_HANDLE; + } + } else { + return NYB_E_PLUGIN_ERROR; + } + } + Err(_) => NYB_E_INVALID_ARGS, + } + } + METHOD_READ => { + let slice = std::slice::from_raw_parts(args, args_len); + if args_len > 0 { + match tlv_parse_string(slice) { + Ok(path) => match open_file("r", &path) { + Ok(mut file) => { + let mut buf = Vec::new(); + if let Err(_) = file.read_to_end(&mut buf) { + return NYB_E_PLUGIN_ERROR; + } + let need = 8usize.saturating_add(buf.len()); + if preflight(result, result_len, need) { + return NYB_E_SHORT_BUFFER; + } + return write_tlv_bytes(&buf, result, result_len); + } + Err(_) => return NYB_E_PLUGIN_ERROR, + }, + Err(_) => return NYB_E_INVALID_ARGS, + } + } else { + if let Ok(mut map) = INSTANCES.lock() { + if let Some(inst) = map.get_mut(&instance_id) { + if let Some(file) = inst.file.as_mut() { + let _ = file.seek(SeekFrom::Start(0)); + let mut buf = Vec::new(); + match file.read_to_end(&mut buf) { + Ok(_) => { + let need = 8usize.saturating_add(buf.len()); + if preflight(result, result_len, need) { + return NYB_E_SHORT_BUFFER; + } + return write_tlv_bytes(&buf, result, result_len); + } + Err(_) => return NYB_E_PLUGIN_ERROR, + } + } else { + return NYB_E_INVALID_HANDLE; + } + } else { + return NYB_E_PLUGIN_ERROR; + } + } else { + return NYB_E_PLUGIN_ERROR; + } + } + } + METHOD_WRITE => { + let slice = std::slice::from_raw_parts(args, args_len); + match tlv_parse_optional_string_and_bytes(slice) { + Ok((Some(path), data)) => { + if preflight(result, result_len, 12) { + return NYB_E_SHORT_BUFFER; + } + match open_file("w", &path) { + Ok(mut file) => { + if let Err(_) = file.write_all(&data) { + return NYB_E_PLUGIN_ERROR; + } + if let Err(_) = file.flush() { + return NYB_E_PLUGIN_ERROR; + } + return write_tlv_i32(data.len() as i32, result, result_len); + } + Err(_) => return NYB_E_PLUGIN_ERROR, + } + } + Ok((None, data)) => { + if preflight(result, result_len, 12) { + return NYB_E_SHORT_BUFFER; + } + if let Ok(mut map) = INSTANCES.lock() { + if let Some(inst) = map.get_mut(&instance_id) { + if let Some(file) = inst.file.as_mut() { + match file.write(&data) { + Ok(n) => { + if let Err(_) = file.flush() { + return NYB_E_PLUGIN_ERROR; + } + inst.buffer = Some(data.clone()); + return write_tlv_i32(n as i32, result, result_len); + } + Err(_) => return NYB_E_PLUGIN_ERROR, + } + } else { + return NYB_E_INVALID_HANDLE; + } + } else { + return NYB_E_PLUGIN_ERROR; + } + } else { + return NYB_E_PLUGIN_ERROR; + } + } + Err(_) => NYB_E_INVALID_ARGS, + } + } + METHOD_CLOSE => { + if preflight(result, result_len, 8) { + return NYB_E_SHORT_BUFFER; + } + if let Ok(mut map) = INSTANCES.lock() { + if let Some(inst) = map.get_mut(&instance_id) { + inst.file = None; + return write_tlv_void(result, result_len); + } else { + return NYB_E_PLUGIN_ERROR; + } + } else { + return NYB_E_PLUGIN_ERROR; + } + } + METHOD_COPY_FROM => { + let slice = std::slice::from_raw_parts(args, args_len); + match tlv_parse_handle(slice) { + Ok((_type_id, other_id)) => { + if preflight(result, result_len, 8) { + return NYB_E_SHORT_BUFFER; + } + if let Ok(mut map) = INSTANCES.lock() { + // Extract data from src + let mut data: Vec = Vec::new(); + if let Some(src) = map.get(&other_id) { + let mut read_ok = false; + if let Some(file) = src.file.as_ref() { + if let Ok(mut f) = file.try_clone() { + let _ = f.seek(SeekFrom::Start(0)); + if f.read_to_end(&mut data).is_ok() { + read_ok = true; + } + } + } + if !read_ok { + if let Some(buf) = src.buffer.as_ref() { + data.extend_from_slice(buf); + read_ok = true; + } + } + if !read_ok { + return NYB_E_PLUGIN_ERROR; + } + } else { + return NYB_E_INVALID_HANDLE; + } + // Write into dst + if let Some(dst) = map.get_mut(&instance_id) { + if let Some(fdst) = dst.file.as_mut() { + let _ = fdst.seek(SeekFrom::Start(0)); + if fdst.write_all(&data).is_err() { + return NYB_E_PLUGIN_ERROR; + } + let _ = fdst.set_len(data.len() as u64); + let _ = fdst.flush(); + } + dst.buffer = Some(data); + return write_tlv_void(result, result_len); + } else { + return NYB_E_INVALID_HANDLE; + } + } else { + return NYB_E_PLUGIN_ERROR; + } + } + Err(_) => NYB_E_INVALID_ARGS, + } + } + METHOD_CLONE_SELF => { + if preflight(result, result_len, 16) { + return NYB_E_SHORT_BUFFER; + } + let new_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed); + if let Ok(mut map) = INSTANCES.lock() { + map.insert( + new_id, + FileBoxInstance { file: None, path: String::new(), buffer: None }, + ); + } + // Return Handle TLV (type_id from config resolves host-side; we encode (6,new_id) here if needed) + let mut payload = [0u8; 8]; + // We don’t embed type_id here (host can ignore); keep zero for neutrality + payload[0..4].copy_from_slice(&0u32.to_le_bytes()); + payload[4..8].copy_from_slice(&new_id.to_le_bytes()); + return write_tlv_result(&[(8u8, &payload)], result, result_len); + } + METHOD_EXISTS => { + let slice = std::slice::from_raw_parts(args, args_len); + match tlv_parse_string(slice) { + Ok(path) => { + let exists = std::path::Path::new(&path).exists(); + if preflight(result, result_len, 9) { + return NYB_E_SHORT_BUFFER; + } + return write_tlv_bool(exists, result, result_len); + } + Err(_) => NYB_E_INVALID_ARGS, + } + } + _ => NYB_E_INVALID_METHOD, + } + } +} + +#[no_mangle] +pub static nyash_typebox_FileBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, // 'TYBX' + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"FileBox\0".as_ptr() as *const c_char, + resolve: Some(filebox_resolve), + invoke_id: Some(filebox_invoke_id), + capabilities: 0, +}; diff --git a/plugins/nyash-math-plugin/src/lib.rs b/plugins/nyash-math-plugin/src/lib.rs index 7b89167f..b4f650a2 100644 --- a/plugins/nyash-math-plugin/src/lib.rs +++ b/plugins/nyash-math-plugin/src/lib.rs @@ -118,6 +118,105 @@ pub extern "C" fn nyash_plugin_invoke( } } +// ===== TypeBox ABI v2 (resolve/invoke_id per box) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const std::os::raw::c_char, + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +use std::ffi::CStr; +extern "C" fn mathbox_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "sqrt" => M_SQRT, + "sin" => M_SIN, + "cos" => M_COS, + "round" => M_ROUND, + "birth" => M_BIRTH, + "fini" => M_FINI, + _ => 0, + } +} +extern "C" fn timebox_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "now" => T_NOW, + "birth" => M_BIRTH, + "fini" => M_FINI, + _ => 0, + } +} + +extern "C" fn mathbox_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { + match method_id { + M_BIRTH => birth(TID_MATH, &MATH_INST, result, result_len), + M_FINI => fini(&MATH_INST, instance_id), + M_SQRT => sqrt_call(args, args_len, result, result_len), + M_SIN => trig_call(args, args_len, result, result_len, true), + M_COS => trig_call(args, args_len, result, result_len, false), + M_ROUND => round_call(args, args_len, result, result_len), + _ => E_METHOD, + } + } +} + +extern "C" fn timebox_invoke_id( + instance_id: u32, + method_id: u32, + _args: *const u8, + _args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { + match method_id { + M_BIRTH => birth(TID_TIME, &TIME_INST, result, result_len), + M_FINI => fini(&TIME_INST, instance_id), + T_NOW => now_call(result, result_len), + _ => E_METHOD, + } + } +} + +#[no_mangle] +pub static nyash_typebox_MathBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"MathBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(mathbox_resolve), + invoke_id: Some(mathbox_invoke_id), + capabilities: 0, +}; + +#[no_mangle] +pub static nyash_typebox_TimeBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"TimeBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(timebox_resolve), + invoke_id: Some(timebox_invoke_id), + capabilities: 0, +}; + unsafe fn birth( tid: u32, map: &Lazy>>, diff --git a/plugins/nyash-net-plugin/src/lib.rs b/plugins/nyash-net-plugin/src/lib.rs index fa2603ee..f31b171a 100644 --- a/plugins/nyash-net-plugin/src/lib.rs +++ b/plugins/nyash-net-plugin/src/lib.rs @@ -213,6 +213,260 @@ pub extern "C" fn nyash_plugin_invoke( } } +// ===== TypeBox ABI v2 (per-Box resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const std::os::raw::c_char, + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +use std::ffi::CStr; +extern "C" fn responsebox_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "setStatus" => M_RESP_SET_STATUS, + "setHeader" => M_RESP_SET_HEADER, + "write" => M_RESP_WRITE, + "readBody" => M_RESP_READ_BODY, + "getStatus" => M_RESP_GET_STATUS, + "getHeader" => M_RESP_GET_HEADER, + "birth" => M_BIRTH, + "fini" => u32::MAX, + _ => 0, + } +} +extern "C" fn clientbox_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "get" => M_CLIENT_GET, + "post" => M_CLIENT_POST, + "birth" => M_BIRTH, + "fini" => u32::MAX, + _ => 0, + } +} + +extern "C" fn responsebox_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { response_invoke(method_id, instance_id, args, args_len, result, result_len) } +} + +extern "C" fn clientbox_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { client_invoke(method_id, instance_id, args, args_len, result, result_len) } +} + +#[no_mangle] +pub static nyash_typebox_ResponseBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"ResponseBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(responsebox_resolve), + invoke_id: Some(responsebox_invoke_id), + capabilities: 0, +}; + +#[no_mangle] +pub static nyash_typebox_ClientBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"ClientBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(clientbox_resolve), + invoke_id: Some(clientbox_invoke_id), + capabilities: 0, +}; + +// --- ServerBox --- +extern "C" fn serverbox_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "start" => M_SERVER_START, + "stop" => M_SERVER_STOP, + "accept" => M_SERVER_ACCEPT, + "birth" => M_BIRTH, + "fini" => u32::MAX, + _ => 0, + } +} +extern "C" fn serverbox_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { server_invoke(method_id, instance_id, args, args_len, result, result_len) } +} +#[no_mangle] +pub static nyash_typebox_ServerBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"ServerBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(serverbox_resolve), + invoke_id: Some(serverbox_invoke_id), + capabilities: 0, +}; + +// --- SockServerBox --- +extern "C" fn sockserver_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "start" => M_SRV_START, + "stop" => M_SRV_STOP, + "accept" => M_SRV_ACCEPT, + "acceptTimeout" => M_SRV_ACCEPT_TIMEOUT, + "birth" => M_SRV_BIRTH, + "fini" => u32::MAX, + _ => 0, + } +} +extern "C" fn sockserver_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { sock_server_invoke(method_id, instance_id, args, args_len, result, result_len) } +} +#[no_mangle] +pub static nyash_typebox_SockServerBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"SockServerBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(sockserver_resolve), + invoke_id: Some(sockserver_invoke_id), + capabilities: 0, +}; + +// --- SockClientBox --- +extern "C" fn sockclient_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "connect" => M_SC_CONNECT, + "birth" => M_SC_BIRTH, + "fini" => u32::MAX, + _ => 0, + } +} +extern "C" fn sockclient_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { sock_client_invoke(method_id, instance_id, args, args_len, result, result_len) } +} +#[no_mangle] +pub static nyash_typebox_SockClientBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"SockClientBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(sockclient_resolve), + invoke_id: Some(sockclient_invoke_id), + capabilities: 0, +}; + +// --- SockConnBox --- +extern "C" fn sockconn_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "send" => M_CONN_SEND, + "recv" => M_CONN_RECV, + "close" => M_CONN_CLOSE, + "recvTimeout" => M_CONN_RECV_TIMEOUT, + "birth" => M_CONN_BIRTH, + "fini" => u32::MAX, + _ => 0, + } +} +extern "C" fn sockconn_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { sock_conn_invoke(method_id, instance_id, args, args_len, result, result_len) } +} +#[no_mangle] +pub static nyash_typebox_SockConnBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"SockConnBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(sockconn_resolve), + invoke_id: Some(sockconn_invoke_id), + capabilities: 0, +}; +extern "C" fn requestbox_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "path" => M_REQ_PATH, + "readBody" => M_REQ_READ_BODY, + "respond" => M_REQ_RESPOND, + "birth" => M_BIRTH, + "fini" => u32::MAX, + _ => 0, + } +} +extern "C" fn requestbox_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { request_invoke(method_id, instance_id, args, args_len, result, result_len) } +} + +#[no_mangle] +pub static nyash_typebox_RequestBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"RequestBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(requestbox_resolve), + invoke_id: Some(requestbox_invoke_id), + capabilities: 0, +}; + unsafe fn server_invoke( m: u32, id: u32, diff --git a/plugins/nyash-path-plugin/src/lib.rs b/plugins/nyash-path-plugin/src/lib.rs index 4d9407cb..1ad78871 100644 --- a/plugins/nyash-path-plugin/src/lib.rs +++ b/plugins/nyash-path-plugin/src/lib.rs @@ -2,6 +2,7 @@ use once_cell::sync::Lazy; use std::collections::HashMap; +use std::ffi::CStr; use std::path::{Component, Path}; use std::sync::{ atomic::{AtomicU32, Ordering}, @@ -152,6 +153,154 @@ pub extern "C" fn nyash_plugin_invoke( } } +// ===== TypeBox ABI (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const std::os::raw::c_char, // C string + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +extern "C" fn pathbox_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { + return 0; + } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "join" => M_JOIN, + "dirname" => M_DIRNAME, + "basename" => M_BASENAME, + "extname" => M_EXTNAME, + "isAbs" | "is_absolute" => M_IS_ABS, + "normalize" => M_NORMALIZE, + "birth" => M_BIRTH, + "fini" => M_FINI, + _ => 0, + } +} + +extern "C" fn pathbox_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { + match method_id { + M_BIRTH => { + if result_len.is_null() { + return E_ARGS; + } + if preflight(result, result_len, 4) { + return E_SHORT; + } + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + if let Ok(mut m) = INST.lock() { + m.insert(id, PathInstance); + } else { + return E_PLUGIN; + } + let b = id.to_le_bytes(); + std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); + *result_len = 4; + OK + } + M_FINI => { + if let Ok(mut m) = INST.lock() { + m.remove(&instance_id); + OK + } else { + E_PLUGIN + } + } + M_JOIN => { + let base = match read_arg_string(args, args_len, 0) { + Some(s) => s, + None => return E_ARGS, + }; + let rest = match read_arg_string(args, args_len, 1) { + Some(s) => s, + None => return E_ARGS, + }; + let joined = if base.ends_with('/') || base.ends_with('\\') { + format!("{}{}", base, rest) + } else { + format!("{}/{}", base, rest) + }; + write_tlv_string(&joined, result, result_len) + } + M_DIRNAME => { + let p = match read_arg_string(args, args_len, 0) { + Some(s) => s, + None => return E_ARGS, + }; + let d = Path::new(&p) + .parent() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_else(|| "".to_string()); + write_tlv_string(&d, result, result_len) + } + M_BASENAME => { + let p = match read_arg_string(args, args_len, 0) { + Some(s) => s, + None => return E_ARGS, + }; + let b = Path::new(&p) + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_else(|| "".to_string()); + write_tlv_string(&b, result, result_len) + } + M_EXTNAME => { + let p = match read_arg_string(args, args_len, 0) { + Some(s) => s, + None => return E_ARGS, + }; + let ext = Path::new(&p) + .extension() + .map(|x| format!(".{}", x.to_string_lossy())) + .unwrap_or_else(|| "".to_string()); + write_tlv_string(&ext, result, result_len) + } + M_IS_ABS => { + let p = match read_arg_string(args, args_len, 0) { + Some(s) => s, + None => return E_ARGS, + }; + let abs = Path::new(&p).is_absolute() || p.contains(":\\"); + write_tlv_bool(abs, result, result_len) + } + M_NORMALIZE => { + let p = match read_arg_string(args, args_len, 0) { + Some(s) => s, + None => return E_ARGS, + }; + let norm = path_clean::PathClean::clean(Path::new(&p)); + write_tlv_string(norm.to_string_lossy().as_ref(), result, result_len) + } + _ => E_METHOD, + } + } +} + +#[no_mangle] +pub static nyash_typebox_PathBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, // 'TYBX' + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"PathBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(pathbox_resolve), + invoke_id: Some(pathbox_invoke_id), + capabilities: 0, +}; + fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { unsafe { if result_len.is_null() { diff --git a/plugins/nyash-python-compiler-plugin/src/lib.rs b/plugins/nyash-python-compiler-plugin/src/lib.rs index 295e8be6..5ce7a0e1 100644 --- a/plugins/nyash-python-compiler-plugin/src/lib.rs +++ b/plugins/nyash-python-compiler-plugin/src/lib.rs @@ -125,3 +125,104 @@ pub extern "C" fn nyash_plugin_invoke( _ => NYB_E_INVALID_METHOD, } } + +// ===== TypeBox ABI v2 (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const std::os::raw::c_char, + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +use std::ffi::CStr; +extern "C" fn pycompiler_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "birth" => METHOD_BIRTH, + "compile" => METHOD_COMPILE, + "fini" => METHOD_FINI, + _ => 0, + } +} + +extern "C" fn pycompiler_invoke_id( + _instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + match method_id { + METHOD_BIRTH => unsafe { + let mut id_g = NEXT_ID.lock().unwrap(); + let id = *id_g; *id_g += 1; + if result_len.is_null() { return NYB_E_SHORT_BUFFER; } + let need = 4usize; + if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; } + let out = std::slice::from_raw_parts_mut(result, *result_len); + out[0..4].copy_from_slice(&(id as u32).to_le_bytes()); + *result_len = need; + NYB_SUCCESS + }, + METHOD_COMPILE => unsafe { + let ir = if args.is_null() || args_len < 8 { None } else { + let buf = std::slice::from_raw_parts(args, args_len); + let tag = u16::from_le_bytes([buf[4], buf[5]]); + let len = u16::from_le_bytes([buf[6], buf[7]]) as usize; + if tag == 6 && 8 + len <= buf.len() { + std::str::from_utf8(&buf[8..8+len]).ok().map(|s| s.to_string()) + } else { None } + }; + let nyash_source = if let Some(s) = ir.or_else(|| std::env::var("NYASH_PY_IR").ok()) { + match serde_json::from_str::(&s).ok() { + Some(Json::Object(map)) => { + if let Some(Json::String(src)) = map.get("nyash_source") { src.clone() } + else if let Some(module) = map.get("module") { + let mut ret_expr = "0".to_string(); + if let Some(funcs) = module.get("functions").and_then(|v| v.as_array()) { + if let Some(fun0) = funcs.get(0) { + if let Some(retv) = fun0.get("return_value") { + if retv.is_number() { ret_expr = retv.to_string(); } + else if let Some(s) = retv.as_str() { ret_expr = s.to_string(); } + } + } + } + format!("static box Generated {\n main() {\n return {}\n }\n}}", ret_expr) + } else { "static box Generated { main() { return 0 } }".to_string() } + } + _ => "static box Generated { main() { return 0 } }".to_string(), + } + } else { "static box Generated { main() { return 0 } }".to_string() }; + let bytes = nyash_source.as_bytes(); + if result_len.is_null() { return NYB_E_SHORT_BUFFER; } + let need = 4 + bytes.len(); + if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; } + let out = std::slice::from_raw_parts_mut(result, *result_len); + out[0..2].copy_from_slice(&6u16.to_le_bytes()); + out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes()); + out[4..4+bytes.len()].copy_from_slice(bytes); + *result_len = need; + NYB_SUCCESS + }, + METHOD_FINI => NYB_SUCCESS, + _ => NYB_E_INVALID_METHOD, + } +} + +#[no_mangle] +pub static nyash_typebox_PythonCompilerBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"PythonCompilerBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(pycompiler_resolve), + invoke_id: Some(pycompiler_invoke_id), + capabilities: 0, +}; diff --git a/plugins/nyash-python-parser-plugin/src/lib.rs b/plugins/nyash-python-parser-plugin/src/lib.rs index f68b6c20..975c3766 100644 --- a/plugins/nyash-python-parser-plugin/src/lib.rs +++ b/plugins/nyash-python-parser-plugin/src/lib.rs @@ -208,6 +208,113 @@ fn parse_python_code(py: Python, code: &str) -> ParseResult { result } +// ===== TypeBox ABI v2 (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const std::os::raw::c_char, + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +const TYPE_ID_PARSER: u32 = 60; +const METHOD_BIRTH: u32 = 0; +const METHOD_PARSE: u32 = 1; +const METHOD_FINI: u32 = u32::MAX; + +use std::ffi::CStr; +extern "C" fn pyparser_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "birth" => METHOD_BIRTH, + "parse" => METHOD_PARSE, + "fini" => METHOD_FINI, + _ => 0, + } +} + +extern "C" fn pyparser_invoke_id( + _instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + match method_id { + METHOD_BIRTH => unsafe { + let instance_id = 1u32; // simple singleton + if result_len.is_null() { return -1; } + if *result_len < 4 { + *result_len = 4; + return -1; + } + let out = std::slice::from_raw_parts_mut(result, *result_len); + out[0..4].copy_from_slice(&instance_id.to_le_bytes()); + *result_len = 4; + 0 + }, + METHOD_PARSE => { + // Decode TLV string from args if present, else env fallback + let code = unsafe { + if args.is_null() || args_len < 4 { + std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) + } else { + let buf = std::slice::from_raw_parts(args, args_len); + if args_len >= 8 { + let tag = u16::from_le_bytes([buf[0], buf[1]]); + let len = u16::from_le_bytes([buf[2], buf[3]]) as usize; + if tag == 6 && 4 + len <= args_len { + match std::str::from_utf8(&buf[4..4 + len]) { Ok(s) => s.to_string(), Err(_) => std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) } + } else { + std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) + } + } else { + std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) + } + } + }; + let parse_result = Python::with_gil(|py| parse_python_code(py, &code)); + match serde_json::to_string(&parse_result) { + Ok(json) => unsafe { + if result_len.is_null() { return -1; } + let bytes = json.as_bytes(); + let need = 4 + bytes.len(); + if *result_len < need { + *result_len = need; + return -1; + } + let out = std::slice::from_raw_parts_mut(result, *result_len); + out[0..2].copy_from_slice(&6u16.to_le_bytes()); + out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes()); + out[4..4 + bytes.len()].copy_from_slice(bytes); + *result_len = need; + 0 + }, + Err(_) => -4, + } + } + METHOD_FINI => 0, + _ => -3, + } +} + +#[no_mangle] +pub static nyash_typebox_PythonParserBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"PythonParserBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(pyparser_resolve), + invoke_id: Some(pyparser_invoke_id), + capabilities: 0, +}; + fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) { result.counts.total_nodes += 1; diff --git a/plugins/nyash-python-plugin/src/lib.rs b/plugins/nyash-python-plugin/src/lib.rs index a25cf09d..bb028f69 100644 --- a/plugins/nyash-python-plugin/src/lib.rs +++ b/plugins/nyash-python-plugin/src/lib.rs @@ -1,11 +1,6 @@ -//! Nyash Python Plugin (Phase 10.5a scaffold) -//! - ABI v1 compatible entry points -//! - Defines two Box types: PyRuntimeBox (TYPE_ID=40) and PyObjectBox (TYPE_ID=41) -//! - Currently stubs; returns NYB_E_INVALID_METHOD for unimplemented routes -//! -//! This crate intentionally does not link to CPython yet. 10.5a focuses on -//! ABI alignment and loader wiring. Future subphases (10.5b–d) will implement -//! actual CPython embedding and conversion. +//! Nyash Python Plugin (Phase 15): +//! - ABI v1 compatible entry points + ABI v2 TypeBox exports +//! - Two Box types: PyRuntimeBox (TYPE_ID=40) and PyObjectBox (TYPE_ID=41) use libloading::Library; use once_cell::sync::Lazy; @@ -806,6 +801,92 @@ fn handle_py_object( } // ===== Minimal TLV helpers (copy from other plugins for consistency) ===== +// ===== TypeBox ABI v2 (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const std::os::raw::c_char, + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +extern "C" fn pyruntime_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "birth" => PY_METHOD_BIRTH, + "eval" | "evalR" => { if s.as_ref() == "evalR" { PY_METHOD_EVAL_R } else { PY_METHOD_EVAL } } + "import" | "importR" => { if s.as_ref() == "importR" { PY_METHOD_IMPORT_R } else { PY_METHOD_IMPORT } } + "fini" => PY_METHOD_FINI, + _ => 0, + } +} + +extern "C" fn pyruntime_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + handle_py_runtime(method_id, instance_id, args, args_len, result, result_len) +} + +extern "C" fn pyobject_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "getattr" | "getAttr" | "getattrR" | "getAttrR" => { + if s.ends_with('R') { PYO_METHOD_GETATTR_R } else { PYO_METHOD_GETATTR } + } + "call" | "callR" => { if s.ends_with('R') { PYO_METHOD_CALL_R } else { PYO_METHOD_CALL } } + "callKw" | "callKW" | "call_kw" | "callKwR" | "callKWR" => { + if s.to_lowercase().ends_with('r') { PYO_METHOD_CALL_KW_R } else { PYO_METHOD_CALL_KW } + } + "str" | "toString" => PYO_METHOD_STR, + "birth" => PYO_METHOD_BIRTH, + "fini" => PYO_METHOD_FINI, + _ => 0, + } +} + +extern "C" fn pyobject_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + handle_py_object(method_id, instance_id, args, args_len, result, result_len) +} + +#[no_mangle] +pub static nyash_typebox_PyRuntimeBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"PyRuntimeBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(pyruntime_resolve), + invoke_id: Some(pyruntime_invoke_id), + capabilities: 0, +}; + +#[no_mangle] +pub static nyash_typebox_PyObjectBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"PyObjectBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(pyobject_resolve), + invoke_id: Some(pyobject_invoke_id), + capabilities: 0, +}; fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { unsafe { if result_len.is_null() { diff --git a/plugins/nyash-regex-plugin/src/lib.rs b/plugins/nyash-regex-plugin/src/lib.rs index 039edce8..2dd76e53 100644 --- a/plugins/nyash-regex-plugin/src/lib.rs +++ b/plugins/nyash-regex-plugin/src/lib.rs @@ -211,6 +211,123 @@ pub extern "C" fn nyash_plugin_invoke( } } +// ===== TypeBox ABI v2 (resolve/invoke_id) ===== +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const std::os::raw::c_char, + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} +unsafe impl Sync for NyashTypeBoxFfi {} + +use std::ffi::CStr; +extern "C" fn regex_resolve(name: *const std::os::raw::c_char) -> u32 { + if name.is_null() { return 0; } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + "compile" => M_COMPILE, + "isMatch" | "is_match" => M_IS_MATCH, + "find" => M_FIND, + "replaceAll" | "replace_all" => M_REPLACE_ALL, + "split" => M_SPLIT, + "birth" => M_BIRTH, + "fini" => M_FINI, + _ => 0, + } +} + +extern "C" fn regex_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { + match method_id { + M_BIRTH => { + // mirror v1: birth may take optional pattern + if result_len.is_null() { return E_ARGS; } + if preflight(result, result_len, 4) { return E_SHORT; } + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + let inst = if let Some(pat) = read_arg_string(args, args_len, 0) { + match Regex::new(&pat) { Ok(re) => RegexInstance { re: Some(re) }, Err(_) => RegexInstance { re: None } } + } else { RegexInstance { re: None } }; + if let Ok(mut m) = INST.lock() { m.insert(id, inst); } else { return E_PLUGIN; } + let b = id.to_le_bytes(); + std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); + *result_len = 4; OK + } + M_FINI => { + if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } + } + M_COMPILE => { + let pat = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; + if let Ok(mut m) = INST.lock() { + if let Some(inst) = m.get_mut(&instance_id) { inst.re = Regex::new(&pat).ok(); OK } else { E_HANDLE } + } else { E_PLUGIN } + } + M_IS_MATCH => { + let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; + if let Ok(m) = INST.lock() { + if let Some(inst) = m.get(&instance_id) { + if let Some(re) = &inst.re { return write_tlv_bool(re.is_match(&text), result, result_len); } else { return write_tlv_bool(false, result, result_len); } + } else { return E_HANDLE; } + } else { return E_PLUGIN; } + } + M_FIND => { + let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; + if let Ok(m) = INST.lock() { + if let Some(inst) = m.get(&instance_id) { + if let Some(re) = &inst.re { + let s = re.find(&text).map(|m| m.as_str().to_string()).unwrap_or_else(|| "".to_string()); + return write_tlv_string(&s, result, result_len); + } else { return write_tlv_string("", result, result_len); } + } else { return E_HANDLE; } + } else { return E_PLUGIN; } + } + M_REPLACE_ALL => { + let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; + let repl = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS }; + if let Ok(m) = INST.lock() { + if let Some(inst) = m.get(&instance_id) { + if let Some(re) = &inst.re { let out = re.replace_all(&text, repl.as_str()).to_string(); return write_tlv_string(&out, result, result_len); } else { return write_tlv_string(&text, result, result_len); } + } else { return E_HANDLE; } + } else { return E_PLUGIN; } + } + M_SPLIT => { + let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; + let limit = read_arg_i64(args, args_len, 1).unwrap_or(0); + if let Ok(m) = INST.lock() { + if let Some(inst) = m.get(&instance_id) { + if let Some(re) = &inst.re { + let parts: Vec = if limit > 0 { re.splitn(&text, limit as usize).map(|s| s.to_string()).collect() } else { re.split(&text).map(|s| s.to_string()).collect() }; + let out = parts.join("\n"); + return write_tlv_string(&out, result, result_len); + } else { return write_tlv_string(&text, result, result_len); } + } else { return E_HANDLE; } + } else { return E_PLUGIN; } + } + _ => E_METHOD, + } + } +} + +#[no_mangle] +pub static nyash_typebox_RegexBox: NyashTypeBoxFfi = NyashTypeBoxFfi { + abi_tag: 0x54594258, + version: 1, + struct_size: std::mem::size_of::() as u16, + name: b"RegexBox\0".as_ptr() as *const std::os::raw::c_char, + resolve: Some(regex_resolve), + invoke_id: Some(regex_invoke_id), + capabilities: 0, +}; fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { unsafe { if result_len.is_null() { diff --git a/src/cli.rs b/src/cli.rs index 07996f4c..4b9f22ad 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -69,6 +69,8 @@ pub struct CliConfig { pub build_target: Option, // Using (CLI) pub cli_usings: Vec, + // Emit MIR JSON to a file and exit (bridge mode) + pub emit_mir_json: Option, } impl CliConfig { @@ -131,6 +133,12 @@ impl CliConfig { .value_name("FILE") .help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter") ) + .arg( + Arg::new("emit-mir-json") + .long("emit-mir-json") + .value_name("FILE") + .help("Emit MIR JSON v0 to file (validation-friendly) and exit") + ) .arg( Arg::new("stage3") .long("stage3") @@ -482,6 +490,7 @@ impl CliConfig { .get_many::("using") .map(|v| v.cloned().collect()) .unwrap_or_else(|| Vec::new()), + emit_mir_json: matches.get_one::("emit-mir-json").cloned(), } } } @@ -536,6 +545,7 @@ impl Default for CliConfig { build_profile: None, build_target: None, cli_usings: Vec::new(), + emit_mir_json: None, } } } diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index a4ddf525..2851758c 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -171,6 +171,16 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { impl NyashRunner { pub(crate) fn execute_mir_module(&self, module: &crate::mir::MirModule) { + // If CLI requested MIR JSON emit, write to file and exit immediately. + if let Some(path) = self.config.emit_mir_json.as_ref() { + let p = std::path::Path::new(path); + if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(module, p) { + eprintln!("❌ MIR JSON emit error: {}", e); + std::process::exit(1); + } + println!("MIR JSON written: {}", p.display()); + std::process::exit(0); + } use crate::backend::MirInterpreter; use crate::box_trait::{BoolBox, IntegerBox, StringBox}; use crate::boxes::FloatBox; diff --git a/src/runtime/plugin_loader_v2/enabled/loader.rs b/src/runtime/plugin_loader_v2/enabled/loader.rs index c16167c7..5f82a2cc 100644 --- a/src/runtime/plugin_loader_v2/enabled/loader.rs +++ b/src/runtime/plugin_loader_v2/enabled/loader.rs @@ -159,6 +159,16 @@ impl PluginLoaderV2 { let abi_ok = st.abi_tag == 0x5459_4258 && st.struct_size as usize >= std::mem::size_of::(); if !abi_ok { + if dbg_on() { + eprintln!( + "[PluginLoaderV2] WARN: invalid TypeBox ABI for {}.{} (abi_tag=0x{:08x} size={} need>={})", + lib_name, + box_type, + st.abi_tag, + st.struct_size, + std::mem::size_of::() + ); + } continue; } // Remember invoke_id in box_specs for (lib_name, box_type) @@ -172,7 +182,19 @@ impl PluginLoaderV2 { invoke_id: None, }); entry.invoke_id = Some(invoke_id); + } else if dbg_on() { + eprintln!( + "[PluginLoaderV2] WARN: TypeBox present but no invoke_id for {}.{} — plugin should export per-Box invoke", + lib_name, box_type + ); } + } else if dbg_on() { + eprintln!( + "[PluginLoaderV2] NOTE: TypeBox symbol not found for {}.{} (symbol='{}'). Migrate plugin to Nyash ABI v2 to enable per-Box dispatch.", + lib_name, + box_type, + sym_name.trim_end_matches('\0') + ); } } } @@ -228,8 +250,24 @@ impl PluginLoaderV2 { let (lib_name, box_type) = self.find_box_by_type_id(config, &toml_value, type_id)?; let key = (lib_name.to_string(), box_type.to_string()); let map = self.box_specs.read().ok()?; - let spec = map.get(&key)?; - spec.invoke_id + let spec = map.get(&key); + if let Some(s) = spec { + if s.invoke_id.is_none() && dbg_on() { + eprintln!( + "[PluginLoaderV2] WARN: no per-Box invoke for {}.{} (type_id={}). Calls will fail with E_PLUGIN (-5) until plugin migrates to v2.", + lib_name, box_type, type_id + ); + } + s.invoke_id + } else { + if dbg_on() { + eprintln!( + "[PluginLoaderV2] INFO: no TypeBox spec loaded for {}.{} (type_id={}).", + lib_name, box_type, type_id + ); + } + None + } } pub fn metadata_for_type_id(&self, type_id: u32) -> Option { diff --git a/src/runtime/plugin_loader_v2/enabled/mod.rs b/src/runtime/plugin_loader_v2/enabled/mod.rs index 83a4225b..df5f6fed 100644 --- a/src/runtime/plugin_loader_v2/enabled/mod.rs +++ b/src/runtime/plugin_loader_v2/enabled/mod.rs @@ -25,7 +25,6 @@ pub fn box_invoke_for_type_id(type_id: u32) -> Option/dev/null || true - else - # Harness path - NYASH_LLVM_OBJ_OUT="$OBJ" NYASH_LLVM_USE_HARNESS=1 LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ - ./target/release/nyash --backend llvm "$INPUT" >/dev/null || true + COMPILER_MODE=${NYASH_LLVM_COMPILER:-harness} + case "$COMPILER_MODE" in + crate) + # Use crates/nyash-llvm-compiler (ny-llvmc): requires pre-generated MIR JSON path in NYASH_LLVM_MIR_JSON + if [[ -z "${NYASH_LLVM_MIR_JSON:-}" ]]; then + echo "[warn] NYASH_LLVM_COMPILER=crate but NYASH_LLVM_MIR_JSON is not set; falling back to harness" >&2 + COMPILER_MODE="harness" + else + echo " using ny-llvmc (crate) with JSON: $NYASH_LLVM_MIR_JSON" >&2 + cargo build --release -p nyash-llvm-compiler >/dev/null + # Optional schema validation if tool is available + if [[ -f tools/validate_mir_json.py ]]; then + if ! python3 -m jsonschema --version >/dev/null 2>&1; then + echo "[warn] jsonschema not available; skipping schema validation" >&2 + else + echo " validating MIR JSON schema ..." >&2 + python3 tools/validate_mir_json.py "$NYASH_LLVM_MIR_JSON" || { + echo "error: MIR JSON validation failed" >&2; exit 3; } + fi + fi + ./target/release/ny-llvmc --in "$NYASH_LLVM_MIR_JSON" --out "$OBJ" + fi + ;; + esac + if [[ "$COMPILER_MODE" == "harness" ]]; then + if [[ "${NYASH_LLVM_FEATURE:-llvm}" == "llvm-inkwell-legacy" ]]; then + # Legacy path: do not use harness + NYASH_LLVM_OBJ_OUT="$OBJ" LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ + ./target/release/nyash --backend llvm "$INPUT" >/dev/null || true + else + # Harness path (Python llvmlite) + NYASH_LLVM_OBJ_OUT="$OBJ" NYASH_LLVM_USE_HARNESS=1 LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ + ./target/release/nyash --backend llvm "$INPUT" >/dev/null || true + fi fi fi if [[ ! -f "$OBJ" ]]; then diff --git a/tools/plugin_v2_smoke.sh b/tools/plugin_v2_smoke.sh new file mode 100644 index 00000000..fa663fea --- /dev/null +++ b/tools/plugin_v2_smoke.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." &>/dev/null && pwd) +BIN="$ROOT_DIR/target/release/nyash" +APP="$ROOT_DIR/test_filebox_simple.nyash" + +echo "[plugin-v2-smoke] building nyash (release)…" >&2 +cargo build --release -j 2 >/dev/null + +echo "[plugin-v2-smoke] building FileBox plugin (release)…" >&2 +pushd "$ROOT_DIR/plugins/nyash-filebox-plugin" >/dev/null +cargo build --release -j 2 >/dev/null +popd >/dev/null + +echo "[plugin-v2-smoke] building PathBox plugin (release)…" >&2 +pushd "$ROOT_DIR/plugins/nyash-path-plugin" >/dev/null +cargo build --release -j 2 >/dev/null +popd >/dev/null + +echo "[plugin-v2-smoke] building Math/Time plugin (release)…" >&2 +pushd "$ROOT_DIR/plugins/nyash-math-plugin" >/dev/null +cargo build --release -j 2 >/dev/null +popd >/dev/null + +echo "[plugin-v2-smoke] building Regex plugin (release)…" >&2 +pushd "$ROOT_DIR/plugins/nyash-regex-plugin" >/dev/null +cargo build --release -j 2 >/dev/null +popd >/dev/null + +echo "[plugin-v2-smoke] building Net plugin (release)…" >&2 +pushd "$ROOT_DIR/plugins/nyash-net-plugin" >/dev/null +cargo build --release -j 2 >/dev/null +popd >/dev/null + +if [[ ! -x "$BIN" ]]; then + echo "error: nyash binary not found at $BIN" >&2 + exit 1 +fi + +export NYASH_CLI_VERBOSE=${NYASH_CLI_VERBOSE:-1} +export NYASH_DEBUG_PLUGIN=${NYASH_DEBUG_PLUGIN:-1} +export NYASH_VM_USE_PY=0 +unset NYASH_DISABLE_PLUGINS + +echo "[plugin-v2-smoke] running: $BIN --backend vm $APP" >&2 +set +e +timeout -s KILL 25s "$BIN" --backend vm "$APP" > /tmp/nyash-plugin-v2-smoke.out 2>&1 +code=$? +set -e +echo "--- plugin v2 smoke output ---" +tail -n 80 /tmp/nyash-plugin-v2-smoke.out || true +echo "-------------------------------" + +if [[ $code -ne 0 ]]; then + echo "plugin-v2-smoke: nyash exited with code $code" >&2 + exit $code +fi + +# Basic assertions: plugin loader ran and configured; program completed +if ! grep -q "\[PluginLoaderV2\] load_plugin:" /tmp/nyash-plugin-v2-smoke.out; then + echo "plugin-v2-smoke: plugin loader did not log load_plugin (NYASH_DEBUG_PLUGIN=1 required)" >&2 + exit 1 +fi + +# Confirm both FileBox and PathBox libraries were attempted +grep -q "libnyash_filebox_plugin.so" /tmp/nyash-plugin-v2-smoke.out || { + echo "plugin-v2-smoke: missing FileBox load log" >&2; exit 1; } +grep -q "libnyash_path_plugin.so" /tmp/nyash-plugin-v2-smoke.out || { + echo "plugin-v2-smoke: missing PathBox load log" >&2; exit 1; } +grep -q "libnyash_math_plugin.so" /tmp/nyash-plugin-v2-smoke.out || { + echo "plugin-v2-smoke: missing Math/Time load log" >&2; exit 1; } +grep -q "libnyash_regex_plugin.so" /tmp/nyash-plugin-v2-smoke.out || { + echo "plugin-v2-smoke: missing Regex load log" >&2; exit 1; } +grep -q "libnyash_net_plugin.so" /tmp/nyash-plugin-v2-smoke.out || { + echo "plugin-v2-smoke: missing Net load log" >&2; exit 1; } + +echo "plugin-v2-smoke: OK" >&2 + +# Optional functional sample (regex + response only) +FUNC_APP="$ROOT_DIR/apps/tests/plugin_v2_functional.nyash" +if [[ -f "$FUNC_APP" ]]; then + echo "[plugin-v2-smoke] functional: $BIN --backend vm $FUNC_APP" >&2 + set +e + timeout -s KILL 25s "$BIN" --backend vm "$FUNC_APP" > /tmp/nyash-plugin-v2-func.out 2>&1 + code=$? + set -e + tail -n 50 /tmp/nyash-plugin-v2-func.out || true + if [[ $code -eq 0 ]]; then + # We don't assert printed values strictly (PyVM path may differ), only that it ran to completion. + echo "plugin-v2-functional: OK" >&2 + else + echo "plugin-v2-functional: nyash exited with code $code" >&2 + exit $code + fi +fi + +# Optional functional sample (net round-trip) +NET_APP="$ROOT_DIR/apps/tests/net_roundtrip.nyash" +if [[ -f "$NET_APP" ]]; then + echo "[plugin-v2-smoke] functional (net): $BIN --backend vm $NET_APP" >&2 + set +e + timeout -s KILL 25s "$BIN" --backend vm "$NET_APP" > /tmp/nyash-plugin-v2-net.out 2>&1 + code=$? + set -e + tail -n 60 /tmp/nyash-plugin-v2-net.out || true + if [[ $code -eq 0 ]]; then + echo "plugin-v2-net-functional: OK" >&2 + else + echo "plugin-v2-net-functional: nyash exited with code $code" >&2 + exit $code + fi +fi +exit 0