python-plugin: RAII (PyOwned/PyBorrowed) + autodecode enum; crate: ny-llvmc --emit exe with NyRT link; tools: build_llvm.sh crate-exe path + crate_exe_smoke; CURRENT_TASK update

This commit is contained in:
Selfhosting Dev
2025-09-18 03:57:25 +09:00
parent 1b12b1eb7d
commit 5d51086530
27 changed files with 2802 additions and 2484 deletions

View File

@ -5,6 +5,55 @@ Summary
- PyVM is the semantic reference engine; llvmlite is used for AOT and parity checks. - 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を完了。 - GC: user modes defined; controller実装rc+cycle skeleton + metrics/diagnosticsに移行。LLVM safepoint輸出/NyRT配線と自動挿入envゲートONを完了。
Refactoring — Code Quality (High Priority, 20250917 夜間)
- MIR instruction meta deboilerplate:
- 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).
- File `src/mir/instruction_kinds/mod.rs` shrank ~100 lines; behavior unchanged (introspection only).
- Tests safety pass (unwrap elimination):
- `src/tests/typebox_tlv_diff.rs` now uses `with_host` + `EnvGuard` + `inv_{ok,some,void}` helpers.
- Removed all `.unwrap()` from the file; failures carry context via `expect` or helper panic messages.
- Tokenizer duplication cut:
- Centralized singlechar token mapping via `single_char_token()`; kept longestmatch logic for multichar ops.
- Box operators deduplicate:
- Added `impl_static_numeric_ops!` for Integer/Float static ops (Add/Sub/Mul/Div), preserved zerodivision checks.
- Introduced `concat_result` and `can_repeat`; simplified Dynamic* impls and `OperatorResolver` via helper functions.
- Net plugin modularization (unsafe/TLV/state split):
- New: `plugins/nyash-net-plugin/src/ffi.rs` (CStr/ptr helpers), `tlv.rs` (encode/decode), `state.rs` (global maps/IDs).
- `lib.rs` delegates to these modules; unsafe is centralized, TLV logic unified, globals encapsulated.
— Python plugin refactor (Phase15)
- ffi 分離: `plugins/nyash-python-plugin/src/ffi.rs` に CPython ローダ/シンボル/`ensure_cpython()` を移設(挙動等価)。
- GILGuard 適用拡大: `eval/import/getattr/str/call/callKw` の全パスを `gil::GILGuard` で保護。手動 Ensure/Release を撤去。
- pytypes 導入: `plugins/nyash-python-plugin/src/pytypes.rs`
- TLV→Py 変換を集約: `count_tlv_args/tuple_from_tlv/kwargs_from_tlv`(内部 `fill_*` で unsafe を局在化)。
- `take_py_error_string` を移設し、lib 側からの呼び出しを置換。
- 参照ヘルパ(`incref/decref`)と CString/CStr ヘルパを用意(段階移行用)。
- 旧ロジックの削除/整理:
- `lib.rs``fill_kwargs_from_tlv` を削除pytypes へ移行済み)。
-`tlv_count_args``fill_tuple_from_tlv` は廃止(コメント化→撤去予定)。
- ビルド: `cargo check -p nyash-python-plugin` 警告ゼロ、Net プラグインも警告ゼロを維持。
Done (20250918)
- Python plugin refactor 完了
- RAII: `PyOwned`/`PyBorrowed` 導入+ `OBJ_PTRS` 撤去。参照は型で管理Drop=DecRef
- autodecode を `pytypes` に移設(`DecodedValue`)。呼び出し側は TLV 書き戻しのみ。
- CString/CStr/bytes 変換を `pytypes` に統一。
- ログ出力を `NYASH_PY_LOG` でガードし既定静音化。
- クリーンアップ: 過剰 `allow(dead_code)` の縮小とコメント整理。
- ny-llvmc: EXE 出力対応
- `--emit exe/obj``--nyrt <dir>``--libs` を実装。`.o` 生成後に NyRT とリンクして実行可能に。
- 代表ケースで EXE 実行exit codeを確認。
Next (Immediate)
- tools/build_llvm.sh の crate→EXE 統合
- `NYASH_LLVM_COMPILER=crate` かつ `NYASH_LLVM_EMIT=exe` の場合、`ny-llvmc --emit exe` を呼び出し、手動リンクをスキップ。
- `NYASH_LLVM_NYRT`/`NYASH_LLVM_LIBS` 環境変数でリンク先/追加フラグを指定可能にLinux 既定は `-ldl -lpthread -lm`)。
- Selfhost/EXE スモークの整備
- 代表3ケースconst/binop/branch など)の JSON→ny-llvmc→EXE→実行をワンショットで検証するスクリプトLinux
- 既存 `exe_first_smoke.sh`/`build_compiler_exe.sh` の補助として crate 直結経路を並行維持。
- CI 追補
- Linux ジョブに crateEXE ルートの最小スモークを追加exit code 判定のみ)。
What Changed (recent) What Changed (recent)
- MIR13 default enabled - MIR13 default enabled
- `mir_no_phi()` default set to true (can disable via `NYASH_MIR_NO_PHI=0`). - `mir_no_phi()` default set to true (can disable via `NYASH_MIR_NO_PHI=0`).
@ -173,28 +222,31 @@ Plugin ABI v2 updates (20250917)
- `tools/build_llvm.sh``NYASH_LLVM_COMPILER=crate|harness` を追加(`crate``ny-llvmc`。JSON は `NYASH_LLVM_MIR_JSON` 指定) - `tools/build_llvm.sh``NYASH_LLVM_COMPILER=crate|harness` を追加(`crate``ny-llvmc`。JSON は `NYASH_LLVM_MIR_JSON` 指定)
- JSON スキーマ検証を可能なら実行(`tools/validate_mir_json.py` - JSON スキーマ検証を可能なら実行(`tools/validate_mir_json.py`
Plugin ABI v2 updates (20250917 — Python family + Net smoke + JSON emit) Plugin ABI v2 updates — 完了報告(20250917
- v2 migrationPython 系 完了) - v2 migration全 firstparty 完了)
- `plugins/nyash-python-plugin`: `nyash_typebox_PyRuntimeBox` / `nyash_typebox_PyObjectBox` を追加resolve/invoke_id 実装。既存 v1 は残存) - Python 系: `PyRuntimeBox`/`PyObjectBox`/`PythonParserBox`/`PythonCompilerBox` を v2 化
- `plugins/nyash-python-parser-plugin`: `nyash_typebox_PythonParserBox` を追加birth/parse/fini - 既存 firstpartyFile/Path/Math/Time/Regex/Net/String/Array/Map/Integer/Consoleを v2 化
- `plugins/nyash-python-compiler-plugin`: `nyash_typebox_PythonCompilerBox` を追加birth/compile/fini - Encoding/TOML も v2 追加(`EncodingBox`/`TOMLBox`)し `nyash.toml` に登録
- `nyash.toml` に Python 系 3 ライブラリを登録type_id: 40/41/60/61 - Legacy 撤去
- Net 往復スモーク(最小) - 旧ローダ(`src/runtime/plugin_loader_legacy.rs`と旧CABI FileBox を削除
- 追加: `apps/tests/net_roundtrip.nyash`Server.start→Client.get→Server.accept/respond→Client.readBody - 全プラグインの v1 エクスポートabi/init/invokeを物理削除v2専用化
- 追加: `tools/plugin_v2_smoke.sh` に Net 機能スモークを条件付きで実行CI常時ジョブに内包 - スモーク/CI
- nyash → MIR JSON emit フラグ - v2 ロード機能Regex/Response/Net往復スモークを常時
- CLI `--emit-mir-json <path>` を追加(`src/cli.rs`)。`runner.execute_mir_module` でファイル出力→即終了を実装。 - `ny-llvmc`crateで .o 生成するCIジョブを追加Linux
- これにより `ny-llvmc` へ JSON を直結しやすくなった(次の CI 経路で使用予定) - nyash → MIR JSON emit
- CLI `--emit-mir-json <path>` を追加し、`ny-llvmc` 直結導線を整備
Plan after restart次の計画 Next — SelfHosting/EXEcrate 直結
- Python 系プラグインの v2 化parser/compiler/python-plugin - ny-llvmc 機能拡張(.exe 出力
- Docs 追記Net/Regex のメソッド表、型/戻りTLVの簡易表 - `ny-llvmc --emit exe --out <path>` を実装(`.o` + NyRT リンク)。`--nyrt <dir>`/`--libs <extra>` を受理
- スモーク強化 - 既存 `tools/build_llvm.sh` の crate 経路と統合env: `NYASH_LLVM_COMPILER=crate`
- Net: `ServerBox.start -> Client.get -> Request.respond -> Response.readBody` の往復最小ケースを追加 - Linux でのリンクフラグ最小化(`-Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -ldl -lpthread -lm`
- 主要 v2 Box の軽機能String/Array/Map/Regex/Path/Math/Timeを 1 ジョブで走らせる - CI 拡張
- LLVM 共通化 - `.o` 生成に加え、`.exe` 生成実行exit code 検証ジョブを追加Linux
- `nyash` からの JSON emit コマンド/フラグ導入(`--emit-mir-json <path>` など)→ `ny-llvmc` 直結 - 代表3ケースconst/binop/branchで EXE を起動し `0` 戻りを確認
- CI に `ny-llvmc` 実 JSON 経路を追加Linux 常時) - Selfhost pipeline 寄り
- NyRT 整理(軽 - nyash CLI `--emit-mir-json` を EXE-first パスにも活用JSON → ny-llvmc → exe → 実行
- TLV/エラー定数を `include/nyash_abi.h` と整合させる(ヘッダ経由参照) - 将来: PyVM/llvmlite パリティベンチ(小規模)→ EXE でも同値を継続確認
- (必要時)`nyrt_last_error()` の追加検討 - Docs/Guides 更新
- `docs/LLVM_HARNESS.md` に ny-llvmc の exe 出力手順を追記
- `docs/guides/selfhost-pilot.md` に crate 直結(.o/.exe手順とトラブルシュート

View File

@ -7,13 +7,16 @@ use anyhow::{bail, Context, Result};
use clap::{ArgAction, Parser}; use clap::{ArgAction, Parser};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "ny-llvmc", about = "Nyash LLVM compiler (llvmlite harness wrapper)")] #[command(
name = "ny-llvmc",
about = "Nyash LLVM compiler (llvmlite harness wrapper)"
)]
struct Args { struct Args {
/// MIR JSON input file path (use '-' to read from stdin). When omitted with --dummy, a dummy ny_main is emitted. /// MIR JSON input file path (use '-' to read from stdin). When omitted with --dummy, a dummy ny_main is emitted.
#[arg(long = "in", value_name = "FILE", default_value = "-")] #[arg(long = "in", value_name = "FILE", default_value = "-")]
infile: String, infile: String,
/// Output object file (.o) /// Output path. For `--emit obj`, this is an object (.o). For `--emit exe`, this is an executable path.
#[arg(long, value_name = "FILE")] #[arg(long, value_name = "FILE")]
out: PathBuf, out: PathBuf,
@ -24,6 +27,18 @@ struct Args {
/// Path to Python harness script (defaults to tools/llvmlite_harness.py in CWD) /// Path to Python harness script (defaults to tools/llvmlite_harness.py in CWD)
#[arg(long, value_name = "FILE")] #[arg(long, value_name = "FILE")]
harness: Option<PathBuf>, harness: Option<PathBuf>,
/// Emit kind: 'obj' (default) or 'exe'.
#[arg(long, value_name = "{obj|exe}", default_value = "obj")]
emit: String,
/// Path to directory containing libnyrt.a when emitting an executable. If omitted, searches target/release then crates/nyrt/target/release.
#[arg(long, value_name = "DIR")]
nyrt: Option<PathBuf>,
/// Extra linker libs/flags appended when emitting an executable (single string, space-separated).
#[arg(long, value_name = "FLAGS")]
libs: Option<String>,
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@ -41,10 +56,27 @@ fn main() -> Result<()> {
PathBuf::from("tools/llvmlite_harness.py") PathBuf::from("tools/llvmlite_harness.py")
}; };
// Determine emit kind
let emit_exe = matches!(args.emit.as_str(), "exe" | "EXE");
if args.dummy { if args.dummy {
run_harness_dummy(&harness_path, &args.out) // Dummy ny_main: always go through harness to produce an object then link if requested
let obj_path = if emit_exe {
// derive a temporary .o path next to output
let mut p = args.out.clone();
p.set_extension("o");
p
} else {
args.out.clone()
};
run_harness_dummy(&harness_path, &obj_path)
.with_context(|| "failed to run harness in dummy mode")?; .with_context(|| "failed to run harness in dummy mode")?;
println!("[ny-llvmc] dummy object written: {}", args.out.display()); if emit_exe {
link_executable(&obj_path, &args.out, args.nyrt.as_ref(), args.libs.as_deref())?;
println!("[ny-llvmc] executable written: {}", args.out.display());
} else {
println!("[ny-llvmc] dummy object written: {}", obj_path.display());
}
return Ok(()); return Ok(());
} }
@ -56,8 +88,8 @@ fn main() -> Result<()> {
.read_to_string(&mut buf) .read_to_string(&mut buf)
.context("reading MIR JSON from stdin")?; .context("reading MIR JSON from stdin")?;
// Basic sanity check that it's JSON // Basic sanity check that it's JSON
let _: serde_json::Value = serde_json::from_str(&buf) let _: serde_json::Value =
.context("stdin does not contain valid JSON")?; serde_json::from_str(&buf).context("stdin does not contain valid JSON")?;
let tmp = std::env::temp_dir().join("ny_llvmc_stdin.json"); let tmp = std::env::temp_dir().join("ny_llvmc_stdin.json");
let mut f = File::create(&tmp).context("create temp json file")?; let mut f = File::create(&tmp).context("create temp json file")?;
f.write_all(buf.as_bytes()).context("write temp json")?; f.write_all(buf.as_bytes()).context("write temp json")?;
@ -71,9 +103,27 @@ fn main() -> Result<()> {
bail!("input JSON not found: {}", input_path.display()); bail!("input JSON not found: {}", input_path.display());
} }
run_harness_in(&harness_path, &input_path, &args.out) // Produce object first
.with_context(|| format!("failed to compile MIR JSON via harness: {}", input_path.display()))?; let obj_path = if emit_exe {
println!("[ny-llvmc] object written: {}", args.out.display()); let mut p = args.out.clone();
p.set_extension("o");
p
} else {
args.out.clone()
};
run_harness_in(&harness_path, &input_path, &obj_path).with_context(|| {
format!(
"failed to compile MIR JSON via harness: {}",
input_path.display()
)
})?;
if emit_exe {
link_executable(&obj_path, &args.out, args.nyrt.as_ref(), args.libs.as_deref())?;
println!("[ny-llvmc] executable written: {}", args.out.display());
} else {
println!("[ny-llvmc] object written: {}", obj_path.display());
}
// Cleanup temp file if used // Cleanup temp file if used
if let Some(p) = temp_path { if let Some(p) = temp_path {
@ -120,3 +170,39 @@ fn ensure_python() -> Result<()> {
} }
} }
fn link_executable(obj: &Path, out_exe: &Path, nyrt_dir_opt: Option<&PathBuf>, extra_libs: Option<&str>) -> Result<()> {
// Resolve nyRT static lib
let nyrt_dir = if let Some(dir) = nyrt_dir_opt {
dir.clone()
} else {
// try target/release then crates/nyrt/target/release
let a = PathBuf::from("target/release");
let b = PathBuf::from("crates/nyrt/target/release");
if a.join("libnyrt.a").exists() { a } else { b }
};
let libnyrt = nyrt_dir.join("libnyrt.a");
if !libnyrt.exists() {
bail!("libnyrt.a not found in {} (use --nyrt to specify)", nyrt_dir.display());
}
// Choose a C linker
let linker = ["cc", "clang", "gcc"].into_iter().find(|c| Command::new(c).arg("--version").output().map(|o| o.status.success()).unwrap_or(false)).unwrap_or("cc");
let mut cmd = Command::new(linker);
cmd.arg("-o").arg(out_exe);
cmd.arg(obj);
// Whole-archive libnyrt to ensure all objects are linked
cmd.arg("-Wl,--whole-archive").arg(&libnyrt).arg("-Wl,--no-whole-archive");
// Common libs on Linux
cmd.arg("-ldl").arg("-lpthread").arg("-lm");
if let Some(extras) = extra_libs {
for tok in extras.split_whitespace() {
cmd.arg(tok);
}
}
let status = cmd.status().context("failed to invoke system linker")?;
if !status.success() {
bail!("linker exited with status: {:?}", status.code());
}
Ok(())
}

View File

@ -677,7 +677,9 @@ pub extern "C" fn main() -> i32 {
// Choose GC hooks based on env (default dev: Counting for observability unless explicitly off) // Choose GC hooks based on env (default dev: Counting for observability unless explicitly off)
let mut rt_builder = nyash_rust::runtime::NyashRuntimeBuilder::new(); let mut rt_builder = nyash_rust::runtime::NyashRuntimeBuilder::new();
let gc_mode = nyash_rust::runtime::gc_mode::GcMode::from_env(); let gc_mode = nyash_rust::runtime::gc_mode::GcMode::from_env();
let controller = std::sync::Arc::new(nyash_rust::runtime::gc_controller::GcController::new(gc_mode)); let controller = std::sync::Arc::new(nyash_rust::runtime::gc_controller::GcController::new(
gc_mode,
));
rt_builder = rt_builder.with_gc_hooks(controller); rt_builder = rt_builder.with_gc_hooks(controller);
let rt_hooks = rt_builder.build(); let rt_hooks = rt_builder.build();
nyash_rust::runtime::global_hooks::set_from_runtime(&rt_hooks); nyash_rust::runtime::global_hooks::set_from_runtime(&rt_hooks);
@ -720,16 +722,23 @@ pub extern "C" fn main() -> i32 {
let want_json = std::env::var("NYASH_GC_METRICS_JSON").ok().as_deref() == Some("1"); 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"); let want_text = std::env::var("NYASH_GC_METRICS").ok().as_deref() == Some("1");
if want_json || want_text { if want_json || want_text {
let (sp, br, bw) = rt_hooks let (sp, br, bw) = rt_hooks.gc.snapshot_counters().unwrap_or((0, 0, 0));
.gc
.snapshot_counters()
.unwrap_or((0, 0, 0));
let handles = nyash_rust::jit::rt::handles::len(); let handles = nyash_rust::jit::rt::handles::len();
let gc_mode_s = gc_mode.as_str(); let gc_mode_s = gc_mode.as_str();
// Include allocation totals if controller is used // Include allocation totals if controller is used
let any_gc: &dyn std::any::Any = &*rt_hooks.gc; let any_gc: &dyn std::any::Any = &*rt_hooks.gc;
let (alloc_count, alloc_bytes, trial_nodes, trial_edges, collect_total, collect_sp, collect_alloc, last_ms, last_reason) = if let Some(ctrl) = any_gc let (
.downcast_ref::<nyash_rust::runtime::gc_controller::GcController>() alloc_count,
alloc_bytes,
trial_nodes,
trial_edges,
collect_total,
collect_sp,
collect_alloc,
last_ms,
last_reason,
) = if let Some(ctrl) =
any_gc.downcast_ref::<nyash_rust::runtime::gc_controller::GcController>()
{ {
let (ac, ab) = ctrl.alloc_totals(); let (ac, ab) = ctrl.alloc_totals();
let (tn, te) = ctrl.trial_reachability_last(); let (tn, te) = ctrl.trial_reachability_last();
@ -741,9 +750,18 @@ pub extern "C" fn main() -> i32 {
(0, 0, 0, 0, 0, 0, 0, 0, 0) (0, 0, 0, 0, 0, 0, 0, 0, 0)
}; };
// Settings snapshot (env) // Settings snapshot (env)
let sp_interval = std::env::var("NYASH_GC_COLLECT_SP").ok().and_then(|s| s.parse::<u64>().ok()).unwrap_or(0); let sp_interval = std::env::var("NYASH_GC_COLLECT_SP")
let alloc_thresh = std::env::var("NYASH_GC_COLLECT_ALLOC").ok().and_then(|s| s.parse::<u64>().ok()).unwrap_or(0); .ok()
let auto_sp = std::env::var("NYASH_LLVM_AUTO_SAFEPOINT").ok().map(|v| v == "1").unwrap_or(true); .and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
let alloc_thresh = std::env::var("NYASH_GC_COLLECT_ALLOC")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
let auto_sp = std::env::var("NYASH_LLVM_AUTO_SAFEPOINT")
.ok()
.map(|v| v == "1")
.unwrap_or(true);
if want_json { if want_json {
// Minimal JSON assembly to avoid extra deps in nyrt // Minimal JSON assembly to avoid extra deps in nyrt
println!( println!(

View File

@ -23,13 +23,11 @@ pub extern "C" fn nyash_plugin_invoke3_i64(
let nargs = argc.max(0) as usize; let nargs = argc.max(0) as usize;
let mut buf = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(nargs as u16); let mut buf = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(nargs as u16);
// Encode legacy VM arg at position into provided buffer (avoid capturing &mut buf) // Encode legacy VM arg at position into provided buffer (avoid capturing &mut buf)
let mut encode_from_legacy_into = |dst: &mut Vec<u8>, arg_pos: usize| { let mut encode_from_legacy_into =
nyrt_encode_from_legacy_at(dst, arg_pos) |dst: &mut Vec<u8>, arg_pos: usize| nyrt_encode_from_legacy_at(dst, arg_pos);
};
// Encode argument value or fallback to legacy slot (avoid capturing &mut buf) // Encode argument value or fallback to legacy slot (avoid capturing &mut buf)
let mut encode_arg_into = |dst: &mut Vec<u8>, val: i64, pos: usize| { let mut encode_arg_into =
nyrt_encode_arg_or_legacy(dst, val, pos) |dst: &mut Vec<u8>, val: i64, pos: usize| nyrt_encode_arg_or_legacy(dst, val, pos);
};
if nargs >= 1 { if nargs >= 1 {
encode_arg_into(&mut buf, a1, 1); encode_arg_into(&mut buf, a1, 1);
} }
@ -203,9 +201,8 @@ pub extern "C" fn nyash_plugin_invoke3_f64(
} }
}); });
}; };
let mut encode_arg = |val: i64, pos: usize| { let mut encode_arg =
crate::encode::nyrt_encode_arg_or_legacy(&mut buf, val, pos) |val: i64, pos: usize| crate::encode::nyrt_encode_arg_or_legacy(&mut buf, val, pos);
};
if nargs >= 1 { if nargs >= 1 {
encode_arg(a1, 1); encode_arg(a1, 1);
} }
@ -218,7 +215,8 @@ pub extern "C" fn nyash_plugin_invoke3_f64(
} }
} }
// Invoke via shared helper // Invoke via shared helper
let (mut tag_ret, mut sz_ret, mut payload_ret): (u8, usize, Vec<u8>) = match invoke_core::plugin_invoke_call( let (mut tag_ret, mut sz_ret, mut payload_ret): (u8, usize, Vec<u8>) =
match invoke_core::plugin_invoke_call(
invoke.unwrap(), invoke.unwrap(),
type_id as u32, type_id as u32,
method_id as u32, method_id as u32,
@ -425,10 +423,17 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64
&mut out_len, &mut out_len,
) )
}; };
if rc != 0 { return 0; } if rc != 0 {
return 0;
}
let out_slice = &out[..out_len]; let out_slice = &out[..out_len];
if let Some((tag, sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(out_slice) { if let Some((tag, sz, payload)) =
if let Some(v) = super::invoke_core::decode_entry_to_i64(tag, sz, payload, invoke.unwrap()) { return v; } nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(out_slice)
{
if let Some(v) = super::invoke_core::decode_entry_to_i64(tag, sz, payload, invoke.unwrap())
{
return v;
}
} }
0 0
} }
@ -667,8 +672,12 @@ pub extern "C" fn nyash_plugin_invoke3_tagged_i64(
if rc != 0 { if rc != 0 {
return 0; return 0;
} }
if let Some((tag, sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { if let Some((tag, sz, payload)) =
if let Some(v) = invoke_core::decode_entry_to_i64(tag, sz, payload, invoke.unwrap()) { return v; } nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len])
{
if let Some(v) = invoke_core::decode_entry_to_i64(tag, sz, payload, invoke.unwrap()) {
return v;
}
} }
0 0
} }
@ -759,8 +768,12 @@ pub extern "C" fn nyash_plugin_invoke_tagged_v_i64(
if rc != 0 { if rc != 0 {
return 0; return 0;
} }
if let Some((tag, sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { if let Some((tag, sz, payload)) =
if let Some(v) = invoke_core::decode_entry_to_i64(tag, sz, payload, invoke.unwrap()) { return v; } nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len])
{
if let Some(v) = invoke_core::decode_entry_to_i64(tag, sz, payload, invoke.unwrap()) {
return v;
}
} }
0 0
} }

View File

@ -8,8 +8,7 @@ use nyash_rust::runtime::plugin_loader_v2::PluginBoxV2;
pub struct Receiver { pub struct Receiver {
pub instance_id: u32, pub instance_id: u32,
pub real_type_id: u32, pub real_type_id: u32,
pub invoke: pub invoke: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32,
unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32,
} }
/// Resolve receiver from a0: prefer handle registry; fallback to legacy VM args when allowed. /// Resolve receiver from a0: prefer handle registry; fallback to legacy VM args when allowed.
@ -27,9 +26,7 @@ pub fn resolve_receiver_for_a0(a0: i64) -> Option<Receiver> {
} }
} }
// 2) Legacy VM args (index by a0) unless handle-only is enforced // 2) Legacy VM args (index by a0) unless handle-only is enforced
if a0 >= 0 if a0 >= 0 && std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1") {
&& std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1")
{
nyash_rust::jit::rt::with_legacy_vm_args(|args| { nyash_rust::jit::rt::with_legacy_vm_args(|args| {
let idx = a0 as usize; let idx = a0 as usize;
if let Some(nyash_rust::backend::vm::VMValue::BoxRef(b)) = args.get(idx) { if let Some(nyash_rust::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
@ -104,7 +101,15 @@ pub fn decode_entry_to_i64(
tag: u8, tag: u8,
sz: usize, sz: usize,
payload: &[u8], payload: &[u8],
fallback_invoke: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, fallback_invoke: unsafe extern "C" fn(
u32,
u32,
u32,
*const u8,
usize,
*mut u8,
*mut usize,
) -> i32,
) -> Option<i64> { ) -> Option<i64> {
match tag { match tag {
2 => nyash_rust::runtime::plugin_ffi_common::decode::i32(payload).map(|v| v as i64), 2 => nyash_rust::runtime::plugin_ffi_common::decode::i32(payload).map(|v| v as i64),
@ -154,8 +159,10 @@ pub fn decode_entry_to_i64(
} }
None None
} }
1 => nyash_rust::runtime::plugin_ffi_common::decode::bool(payload) 1 => {
.map(|b| if b { 1 } else { 0 }), nyash_rust::runtime::plugin_ffi_common::decode::bool(payload)
.map(|b| if b { 1 } else { 0 })
}
5 => { 5 => {
if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") && sz == 8 { if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") && sz == 8 {
let mut b = [0u8; 8]; let mut b = [0u8; 8];
@ -192,8 +199,13 @@ pub fn decode_entry_to_f64(tag: u8, sz: usize, payload: &[u8]) -> Option<f64> {
} }
None None
} }
1 => nyash_rust::runtime::plugin_ffi_common::decode::bool(payload) 1 => nyash_rust::runtime::plugin_ffi_common::decode::bool(payload).map(|b| {
.map(|b| if b { 1.0 } else { 0.0 }), if b {
1.0
} else {
0.0
}
}),
_ => None, _ => None,
} }
} }

View File

@ -158,7 +158,9 @@ unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr; use std::ffi::CStr;
extern "C" fn encoding_resolve(name: *const std::os::raw::c_char) -> u32 { extern "C" fn encoding_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; } if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() { match s.as_ref() {
"toUtf8Bytes" => M_TO_UTF8_BYTES, "toUtf8Bytes" => M_TO_UTF8_BYTES,
@ -184,42 +186,91 @@ extern "C" fn encoding_invoke_id(
unsafe { unsafe {
match method_id { match method_id {
M_BIRTH => { M_BIRTH => {
if result_len.is_null() { return E_ARGS; } if result_len.is_null() {
if preflight(result, result_len, 4) { return E_SHORT; } return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() { m.insert(id, EncInstance); } else { return E_PLUGIN; } if let Ok(mut m) = INST.lock() {
m.insert(id, EncInstance);
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes(); let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4; OK *result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
} }
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
M_TO_UTF8_BYTES => { M_TO_UTF8_BYTES => {
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; let s = match read_arg_string(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
write_tlv_bytes(s.as_bytes(), result, result_len) write_tlv_bytes(s.as_bytes(), result, result_len)
} }
M_FROM_UTF8_BYTES => { M_FROM_UTF8_BYTES => {
let bytes = match read_arg_bytes(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; let bytes = match read_arg_bytes(args, args_len, 0) {
match String::from_utf8(bytes) { Ok(s) => write_tlv_string(&s, result, result_len), Err(_) => write_tlv_string("", result, result_len) } Some(v) => v,
None => return E_ARGS,
};
match String::from_utf8(bytes) {
Ok(s) => write_tlv_string(&s, result, result_len),
Err(_) => write_tlv_string("", result, result_len),
}
} }
M_BASE64_ENC => { M_BASE64_ENC => {
if let Some(b) = read_arg_bytes(args, args_len, 0) { let s = base64::encode(b); return write_tlv_string(&s, result, result_len); } if let Some(b) = read_arg_bytes(args, args_len, 0) {
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; let s = base64::encode(b);
return write_tlv_string(&s, result, result_len);
}
let s = match read_arg_string(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
let enc = base64::encode(s.as_bytes()); let enc = base64::encode(s.as_bytes());
write_tlv_string(&enc, result, result_len) write_tlv_string(&enc, result, result_len)
} }
M_BASE64_DEC => { M_BASE64_DEC => {
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; let s = match read_arg_string(args, args_len, 0) {
match base64::decode(s.as_bytes()) { Ok(b) => write_tlv_bytes(&b, result, result_len), Err(_) => write_tlv_bytes(&[], result, result_len) } Some(v) => v,
None => return E_ARGS,
};
match base64::decode(s.as_bytes()) {
Ok(b) => write_tlv_bytes(&b, result, result_len),
Err(_) => write_tlv_bytes(&[], result, result_len),
}
} }
M_HEX_ENC => { M_HEX_ENC => {
if let Some(b) = read_arg_bytes(args, args_len, 0) { let s = hex::encode(b); return write_tlv_string(&s, result, result_len); } if let Some(b) = read_arg_bytes(args, args_len, 0) {
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; let s = hex::encode(b);
return write_tlv_string(&s, result, result_len);
}
let s = match read_arg_string(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
let enc = hex::encode(s.as_bytes()); let enc = hex::encode(s.as_bytes());
write_tlv_string(&enc, result, result_len) write_tlv_string(&enc, result, result_len)
} }
M_HEX_DEC => { M_HEX_DEC => {
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; let s = match read_arg_string(args, args_len, 0) {
match hex::decode(s.as_bytes()) { Ok(b) => write_tlv_bytes(&b, result, result_len), Err(_) => write_tlv_bytes(&[], result, result_len) } Some(v) => v,
None => return E_ARGS,
};
match hex::decode(s.as_bytes()) {
Ok(b) => write_tlv_bytes(&b, result, result_len),
Err(_) => write_tlv_bytes(&[], result, result_len),
}
} }
_ => E_METHOD, _ => E_METHOD,
} }

View File

@ -681,7 +681,11 @@ extern "C" fn filebox_invoke_id(
if let Ok(mut map) = INSTANCES.lock() { if let Ok(mut map) = INSTANCES.lock() {
map.insert( map.insert(
id, id,
FileBoxInstance { file: None, path: String::new(), buffer: None }, FileBoxInstance {
file: None,
path: String::new(),
buffer: None,
},
); );
} else { } else {
return NYB_E_PLUGIN_ERROR; return NYB_E_PLUGIN_ERROR;
@ -900,7 +904,11 @@ extern "C" fn filebox_invoke_id(
if let Ok(mut map) = INSTANCES.lock() { if let Ok(mut map) = INSTANCES.lock() {
map.insert( map.insert(
new_id, new_id,
FileBoxInstance { file: None, path: String::new(), buffer: None }, 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) // Return Handle TLV (type_id from config resolves host-side; we encode (6,new_id) here if needed)

View File

@ -127,7 +127,9 @@ unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr; use std::ffi::CStr;
extern "C" fn mathbox_resolve(name: *const std::os::raw::c_char) -> u32 { extern "C" fn mathbox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; } if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() { match s.as_ref() {
"sqrt" => M_SQRT, "sqrt" => M_SQRT,
@ -140,7 +142,9 @@ extern "C" fn mathbox_resolve(name: *const std::os::raw::c_char) -> u32 {
} }
} }
extern "C" fn timebox_resolve(name: *const std::os::raw::c_char) -> u32 { extern "C" fn timebox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; } if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() { match s.as_ref() {
"now" => T_NOW, "now" => T_NOW,

View File

@ -0,0 +1,19 @@
use std::ffi::CStr;
use std::os::raw::c_char;
// Safe wrapper: convert C string pointer to owned String.
// Safety details are contained within; caller gets a safe String.
pub fn cstr_to_string(ptr: *const c_char) -> String {
if ptr.is_null() {
return String::new();
}
unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned()
}
// Re-export a safe view over a raw byte slice pointer.
// This function is unsafe since it trusts the pointer/length.
pub unsafe fn slice<'a>(p: *const u8, len: usize) -> &'a [u8] {
std::slice::from_raw_parts(p, len)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
use super::{
ClientState, RequestState, ResponseState, ServerState, SockClientState, SockConnState,
SockServerState,
};
pub(crate) static SERVER_INSTANCES: Lazy<Mutex<HashMap<u32, ServerState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(crate) static SERVER_START_SEQ: AtomicU32 = AtomicU32::new(1);
pub(crate) static ACTIVE_SERVER_ID: Lazy<Mutex<Option<u32>>> = Lazy::new(|| Mutex::new(None));
pub(crate) static LAST_ACCEPTED_REQ: Lazy<Mutex<Option<u32>>> = Lazy::new(|| Mutex::new(None));
pub(crate) static REQUESTS: Lazy<Mutex<HashMap<u32, RequestState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(crate) static RESPONSES: Lazy<Mutex<HashMap<u32, ResponseState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(crate) static CLIENTS: Lazy<Mutex<HashMap<u32, ClientState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(crate) static SERVER_ID: AtomicU32 = AtomicU32::new(1);
pub(crate) static REQUEST_ID: AtomicU32 = AtomicU32::new(1);
pub(crate) static RESPONSE_ID: AtomicU32 = AtomicU32::new(1);
pub(crate) static CLIENT_ID: AtomicU32 = AtomicU32::new(1);
pub(crate) static SOCK_SERVER_ID: AtomicU32 = AtomicU32::new(1);
pub(crate) static SOCK_CONN_ID: AtomicU32 = AtomicU32::new(1);
pub(crate) static SOCK_CLIENT_ID: AtomicU32 = AtomicU32::new(1);
pub(crate) static SOCK_SERVERS: Lazy<Mutex<HashMap<u32, SockServerState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(crate) static SOCK_CONNS: Lazy<Mutex<HashMap<u32, SockConnState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub(crate) static SOCK_CLIENTS: Lazy<Mutex<HashMap<u32, SockClientState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
#[inline]
pub(crate) fn next_server_id() -> u32 {
SERVER_ID.fetch_add(1, Ordering::Relaxed)
}
#[inline]
pub(crate) fn next_server_start_seq() -> u32 {
SERVER_START_SEQ.fetch_add(1, Ordering::Relaxed)
}
#[inline]
pub(crate) fn next_request_id() -> u32 {
REQUEST_ID.fetch_add(1, Ordering::Relaxed)
}
#[inline]
pub(crate) fn next_response_id() -> u32 {
RESPONSE_ID.fetch_add(1, Ordering::Relaxed)
}
#[inline]
pub(crate) fn next_client_id() -> u32 {
CLIENT_ID.fetch_add(1, Ordering::Relaxed)
}
#[inline]
pub(crate) fn next_sock_server_id() -> u32 {
SOCK_SERVER_ID.fetch_add(1, Ordering::Relaxed)
}
#[inline]
pub(crate) fn next_sock_conn_id() -> u32 {
SOCK_CONN_ID.fetch_add(1, Ordering::Relaxed)
}
#[inline]
pub(crate) fn next_sock_client_id() -> u32 {
SOCK_CLIENT_ID.fetch_add(1, Ordering::Relaxed)
}

View File

@ -0,0 +1,181 @@
use super::{E_INV_ARGS, E_SHORT, OK};
#[inline]
fn tlv_result_size(payloads: &[(u8, &[u8])]) -> usize {
4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>()
}
#[inline]
pub fn ensure_result_capacity(res: *mut u8, res_len: *mut usize, need: usize) -> Result<(), i32> {
if res_len.is_null() {
return Err(E_INV_ARGS);
}
unsafe {
if res.is_null() || *res_len < need {
*res_len = need;
return Err(E_SHORT);
}
}
Ok(())
}
#[inline]
unsafe fn write_bytes_unchecked(bytes: &[u8], res: *mut u8, res_len: *mut usize) {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), res, bytes.len());
*res_len = bytes.len();
}
pub fn write_u32(v: u32, res: *mut u8, res_len: *mut usize) -> i32 {
let bytes = v.to_le_bytes();
if let Err(err) = ensure_result_capacity(res, res_len, bytes.len()) {
return err;
}
unsafe {
write_bytes_unchecked(&bytes, res, res_len);
}
OK
}
pub fn write_tlv_result(payloads: &[(u8, &[u8])], res: *mut u8, res_len: *mut usize) -> i32 {
let need = tlv_result_size(payloads);
if let Err(err) = ensure_result_capacity(res, res_len, need) {
return err;
}
let mut buf = Vec::with_capacity(need);
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, p) in payloads {
buf.push(*tag);
buf.push(0);
buf.extend_from_slice(&(p.len() as u16).to_le_bytes());
buf.extend_from_slice(p);
}
unsafe {
write_bytes_unchecked(&buf, res, res_len);
}
OK
}
pub fn write_tlv_void(res: *mut u8, res_len: *mut usize) -> i32 {
write_tlv_result(&[(9u8, &[])], res, res_len)
}
pub fn write_tlv_string(s: &str, res: *mut u8, res_len: *mut usize) -> i32 {
write_tlv_result(&[(6u8, s.as_bytes())], res, res_len)
}
pub fn write_tlv_bytes(b: &[u8], res: *mut u8, res_len: *mut usize) -> i32 {
write_tlv_result(&[(7u8, b)], res, res_len)
}
pub fn write_tlv_i32(v: i32, res: *mut u8, res_len: *mut usize) -> i32 {
write_tlv_result(&[(2u8, &v.to_le_bytes())], res, res_len)
}
pub fn write_tlv_handle(t: u32, id: u32, res: *mut u8, res_len: *mut usize) -> i32 {
let mut payload = [0u8; 8];
payload[0..4].copy_from_slice(&t.to_le_bytes());
payload[4..8].copy_from_slice(&id.to_le_bytes());
write_tlv_result(&[(8u8, &payload)], res, res_len)
}
pub fn tlv_parse_header(data: &[u8]) -> Result<(u16, u16, usize), ()> {
if data.len() < 4 {
return Err(());
}
let ver = u16::from_le_bytes([data[0], data[1]]);
let argc = u16::from_le_bytes([data[2], data[3]]);
if ver != 1 {
return Err(());
}
Ok((ver, argc, 4))
}
pub fn tlv_parse_string(data: &[u8]) -> Result<String, ()> {
let (_, argc, pos) = tlv_parse_header(data)?;
if argc < 1 {
return Err(());
}
let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?;
if tag != 6 {
return Err(());
}
Ok(std::str::from_utf8(&data[p..p + size])
.map_err(|_| ())?
.to_string())
}
pub fn tlv_parse_two_strings(data: &[u8]) -> Result<(String, String), ()> {
let (_, argc, mut pos) = tlv_parse_header(data)?;
if argc < 2 {
return Err(());
}
let (tag1, size1, p1) = tlv_parse_entry_hdr(data, pos)?;
if tag1 != 6 {
return Err(());
}
let s1 = std::str::from_utf8(&data[p1..p1 + size1])
.map_err(|_| ())?
.to_string();
pos = p1 + size1;
let (tag2, size2, p2) = tlv_parse_entry_hdr(data, pos)?;
if tag2 != 6 {
return Err(());
}
let s2 = std::str::from_utf8(&data[p2..p2 + size2])
.map_err(|_| ())?
.to_string();
Ok((s1, s2))
}
pub fn tlv_parse_bytes(data: &[u8]) -> Result<Vec<u8>, ()> {
let (_, argc, pos) = tlv_parse_header(data)?;
if argc < 1 {
return Err(());
}
let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?;
if tag != 6 && tag != 7 {
return Err(());
}
Ok(data[p..p + size].to_vec())
}
pub fn tlv_parse_i32(data: &[u8]) -> Result<i32, ()> {
let (_, argc, pos) = tlv_parse_header(data)?;
if argc < 1 {
return Err(());
}
let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?;
match (tag, size) {
(2, 4) => {
let mut b = [0u8; 4];
b.copy_from_slice(&data[p..p + 4]);
Ok(i32::from_le_bytes(b))
}
(5, 8) => {
let mut b = [0u8; 8];
b.copy_from_slice(&data[p..p + 8]);
Ok(i64::from_le_bytes(b) as i32)
}
_ => Err(()),
}
}
pub fn tlv_parse_handle(data: &[u8]) -> Result<(u32, u32), ()> {
let (_, argc, pos) = tlv_parse_header(data)?;
if argc < 1 {
return Err(());
}
let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?;
if tag != 8 || size != 8 {
return Err(());
}
let mut t = [0u8; 4];
let mut i = [0u8; 4];
t.copy_from_slice(&data[p..p + 4]);
i.copy_from_slice(&data[p + 4..p + 8]);
Ok((u32::from_le_bytes(t), u32::from_le_bytes(i)))
}
pub fn tlv_parse_entry_hdr(data: &[u8], pos: usize) -> Result<(u8, usize, usize), ()> {
if pos + 4 > data.len() {
return Err(());
}
let tag = data[pos];
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
let p = pos + 4;
if p + size > data.len() {
return Err(());
}
Ok((tag, size, p))
}

View File

@ -135,7 +135,9 @@ unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr; use std::ffi::CStr;
extern "C" fn pycompiler_resolve(name: *const std::os::raw::c_char) -> u32 { extern "C" fn pycompiler_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; } if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() { match s.as_ref() {
"birth" => METHOD_BIRTH, "birth" => METHOD_BIRTH,
@ -156,48 +158,77 @@ extern "C" fn pycompiler_invoke_id(
match method_id { match method_id {
METHOD_BIRTH => unsafe { METHOD_BIRTH => unsafe {
let mut id_g = NEXT_ID.lock().unwrap(); let mut id_g = NEXT_ID.lock().unwrap();
let id = *id_g; *id_g += 1; let id = *id_g;
if result_len.is_null() { return NYB_E_SHORT_BUFFER; } *id_g += 1;
if result_len.is_null() {
return NYB_E_SHORT_BUFFER;
}
let need = 4usize; let need = 4usize;
if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; } if *result_len < need {
*result_len = need;
return NYB_E_SHORT_BUFFER;
}
let out = std::slice::from_raw_parts_mut(result, *result_len); let out = std::slice::from_raw_parts_mut(result, *result_len);
out[0..4].copy_from_slice(&(id as u32).to_le_bytes()); out[0..4].copy_from_slice(&(id as u32).to_le_bytes());
*result_len = need; *result_len = need;
NYB_SUCCESS NYB_SUCCESS
}, },
METHOD_COMPILE => unsafe { METHOD_COMPILE => unsafe {
let ir = if args.is_null() || args_len < 8 { None } else { let ir = if args.is_null() || args_len < 8 {
None
} else {
let buf = std::slice::from_raw_parts(args, args_len); let buf = std::slice::from_raw_parts(args, args_len);
let tag = u16::from_le_bytes([buf[4], buf[5]]); let tag = u16::from_le_bytes([buf[4], buf[5]]);
let len = u16::from_le_bytes([buf[6], buf[7]]) as usize; let len = u16::from_le_bytes([buf[6], buf[7]]) as usize;
if tag == 6 && 8 + len <= buf.len() { if tag == 6 && 8 + len <= buf.len() {
std::str::from_utf8(&buf[8..8+len]).ok().map(|s| s.to_string()) std::str::from_utf8(&buf[8..8 + len])
} else { None } .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()) { let nyash_source = if let Some(s) = ir.or_else(|| std::env::var("NYASH_PY_IR").ok()) {
match serde_json::from_str::<Json>(&s).ok() { match serde_json::from_str::<Json>(&s).ok() {
Some(Json::Object(map)) => { Some(Json::Object(map)) => {
if let Some(Json::String(src)) = map.get("nyash_source") { src.clone() } if let Some(Json::String(src)) = map.get("nyash_source") {
else if let Some(module) = map.get("module") { src.clone()
} else if let Some(module) = map.get("module") {
let mut ret_expr = "0".to_string(); let mut ret_expr = "0".to_string();
if let Some(funcs) = module.get("functions").and_then(|v| v.as_array()) { if let Some(funcs) = module.get("functions").and_then(|v| v.as_array())
{
if let Some(fun0) = funcs.get(0) { if let Some(fun0) = funcs.get(0) {
if let Some(retv) = fun0.get("return_value") { if let Some(retv) = fun0.get("return_value") {
if retv.is_number() { ret_expr = retv.to_string(); } if retv.is_number() {
else if let Some(s) = retv.as_str() { ret_expr = s.to_string(); } 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() } 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(), _ => "static box Generated { main() { return 0 } }".to_string(),
} }
} else { "static box Generated { main() { return 0 } }".to_string() }; } else {
"static box Generated { main() { return 0 } }".to_string()
};
let bytes = nyash_source.as_bytes(); let bytes = nyash_source.as_bytes();
if result_len.is_null() { return NYB_E_SHORT_BUFFER; } if result_len.is_null() {
return NYB_E_SHORT_BUFFER;
}
let need = 4 + bytes.len(); let need = 4 + bytes.len();
if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; } if *result_len < need {
*result_len = need;
return NYB_E_SHORT_BUFFER;
}
let out = std::slice::from_raw_parts_mut(result, *result_len); let out = std::slice::from_raw_parts_mut(result, *result_len);
out[0..2].copy_from_slice(&6u16.to_le_bytes()); out[0..2].copy_from_slice(&6u16.to_le_bytes());
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes()); out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());

View File

@ -122,7 +122,9 @@ const METHOD_FINI: u32 = u32::MAX;
use std::ffi::CStr; use std::ffi::CStr;
extern "C" fn pyparser_resolve(name: *const std::os::raw::c_char) -> u32 { extern "C" fn pyparser_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; } if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() { match s.as_ref() {
"birth" => METHOD_BIRTH, "birth" => METHOD_BIRTH,
@ -143,7 +145,9 @@ extern "C" fn pyparser_invoke_id(
match method_id { match method_id {
METHOD_BIRTH => unsafe { METHOD_BIRTH => unsafe {
let instance_id = 1u32; // simple singleton let instance_id = 1u32; // simple singleton
if result_len.is_null() { return -1; } if result_len.is_null() {
return -1;
}
if *result_len < 4 { if *result_len < 4 {
*result_len = 4; *result_len = 4;
return -1; return -1;
@ -157,26 +161,35 @@ extern "C" fn pyparser_invoke_id(
// Decode TLV string from args if present, else env fallback // Decode TLV string from args if present, else env fallback
let code = unsafe { let code = unsafe {
if args.is_null() || args_len < 4 { if args.is_null() || args_len < 4 {
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) std::env::var("NYASH_PY_CODE")
.unwrap_or_else(|_| "def main():\n return 0".to_string())
} else { } else {
let buf = std::slice::from_raw_parts(args, args_len); let buf = std::slice::from_raw_parts(args, args_len);
if args_len >= 8 { if args_len >= 8 {
let tag = u16::from_le_bytes([buf[0], buf[1]]); let tag = u16::from_le_bytes([buf[0], buf[1]]);
let len = u16::from_le_bytes([buf[2], buf[3]]) as usize; let len = u16::from_le_bytes([buf[2], buf[3]]) as usize;
if tag == 6 && 4 + len <= args_len { 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()) } match std::str::from_utf8(&buf[4..4 + len]) {
} else { Ok(s) => s.to_string(),
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) Err(_) => std::env::var("NYASH_PY_CODE")
.unwrap_or_else(|_| "def main():\n return 0".to_string()),
} }
} else { } else {
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) 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)); let parse_result = Python::with_gil(|py| parse_python_code(py, &code));
match serde_json::to_string(&parse_result) { match serde_json::to_string(&parse_result) {
Ok(json) => unsafe { Ok(json) => unsafe {
if result_len.is_null() { return -1; } if result_len.is_null() {
return -1;
}
let bytes = json.as_bytes(); let bytes = json.as_bytes();
let need = 4 + bytes.len(); let need = 4 + bytes.len();
if *result_len < need { if *result_len < need {

View File

@ -0,0 +1,284 @@
#![allow(non_snake_case, non_camel_case_types, dead_code)]
use libloading::Library;
use once_cell::sync::Lazy;
use std::os::raw::{c_char, c_int, c_long, c_void};
use std::sync::Mutex;
pub type PyObject = c_void;
pub type PyGILState_STATE = c_int;
pub struct CPython {
pub(crate) _lib: Library,
pub(crate) Py_Initialize: unsafe extern "C" fn(),
pub(crate) Py_Finalize: unsafe extern "C" fn(),
pub(crate) Py_IsInitialized: unsafe extern "C" fn() -> c_int,
pub(crate) PyGILState_Ensure: unsafe extern "C" fn() -> PyGILState_STATE,
pub(crate) PyGILState_Release: unsafe extern "C" fn(PyGILState_STATE),
pub(crate) PyRun_StringFlags: unsafe extern "C" fn(
*const c_char,
c_int,
*mut PyObject,
*mut PyObject,
*mut c_void,
) -> *mut PyObject,
pub(crate) PyImport_AddModule: unsafe extern "C" fn(*const c_char) -> *mut PyObject,
pub(crate) PyModule_GetDict: unsafe extern "C" fn(*mut PyObject) -> *mut PyObject,
pub(crate) PyImport_ImportModule: unsafe extern "C" fn(*const c_char) -> *mut PyObject,
pub(crate) PyObject_Str: unsafe extern "C" fn(*mut PyObject) -> *mut PyObject,
pub(crate) PyUnicode_AsUTF8: unsafe extern "C" fn(*mut PyObject) -> *const c_char,
pub(crate) Py_DecRef: unsafe extern "C" fn(*mut PyObject),
pub(crate) Py_IncRef: unsafe extern "C" fn(*mut PyObject),
pub(crate) PyObject_GetAttrString:
unsafe extern "C" fn(*mut PyObject, *const c_char) -> *mut PyObject,
pub(crate) PyObject_CallObject:
unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject,
pub(crate) PyObject_Call:
unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject,
pub(crate) PyTuple_New: unsafe extern "C" fn(isize) -> *mut PyObject,
pub(crate) PyTuple_SetItem: unsafe extern "C" fn(*mut PyObject, isize, *mut PyObject) -> c_int,
pub(crate) PyLong_FromLongLong: unsafe extern "C" fn(i64) -> *mut PyObject,
pub(crate) PyUnicode_FromString: unsafe extern "C" fn(*const c_char) -> *mut PyObject,
pub(crate) PyBool_FromLong: unsafe extern "C" fn(c_long: c_long) -> *mut PyObject,
pub(crate) PyFloat_FromDouble: unsafe extern "C" fn(f64) -> *mut PyObject,
pub(crate) PyFloat_AsDouble: unsafe extern "C" fn(*mut PyObject) -> f64,
pub(crate) PyLong_AsLongLong: unsafe extern "C" fn(*mut PyObject) -> i64,
pub(crate) PyBytes_FromStringAndSize:
unsafe extern "C" fn(*const c_char, isize) -> *mut PyObject,
pub(crate) PyBytes_AsStringAndSize:
unsafe extern "C" fn(*mut PyObject, *mut *mut c_char, *mut isize) -> c_int,
pub(crate) PyDict_New: unsafe extern "C" fn() -> *mut PyObject,
pub(crate) PyDict_SetItemString:
unsafe extern "C" fn(*mut PyObject, *const c_char, *mut PyObject) -> c_int,
pub(crate) PyErr_Occurred: unsafe extern "C" fn() -> *mut PyObject,
pub(crate) PyErr_Fetch:
unsafe extern "C" fn(*mut *mut PyObject, *mut *mut PyObject, *mut *mut PyObject),
pub(crate) PyErr_Clear: unsafe extern "C" fn(),
}
pub static CPY: Lazy<Mutex<Option<CPython>>> = Lazy::new(|| Mutex::new(None));
pub fn try_load_cpython() -> Result<(), ()> {
let mut candidates: Vec<String> = vec![
// Linux
"libpython3.12.so".into(),
"libpython3.12.so.1.0".into(),
"libpython3.11.so".into(),
"libpython3.11.so.1.0".into(),
"libpython3.10.so".into(),
"libpython3.10.so.1.0".into(),
"libpython3.9.so".into(),
"libpython3.9.so.1.0".into(),
// macOS
"libpython3.12.dylib".into(),
"libpython3.11.dylib".into(),
"libpython3.10.dylib".into(),
"libpython3.9.dylib".into(),
];
if cfg!(target_os = "windows") {
let dlls = [
"python312.dll",
"python311.dll",
"python310.dll",
"python39.dll",
];
for d in dlls {
candidates.push(d.into());
}
if let Ok(pyhome) = std::env::var("PYTHONHOME") {
for d in [
"python312.dll",
"python311.dll",
"python310.dll",
"python39.dll",
]
.iter()
{
let p = std::path::Path::new(&pyhome).join(d);
if p.exists() {
candidates.push(p.to_string_lossy().to_string());
}
}
}
}
for name in candidates.into_iter() {
if let Ok(lib) = unsafe { Library::new(&name) } {
unsafe {
let Py_Initialize = *lib
.get::<unsafe extern "C" fn()>(b"Py_Initialize\0")
.map_err(|_| ())?;
let Py_Finalize = *lib
.get::<unsafe extern "C" fn()>(b"Py_Finalize\0")
.map_err(|_| ())?;
let Py_IsInitialized = *lib
.get::<unsafe extern "C" fn() -> c_int>(b"Py_IsInitialized\0")
.map_err(|_| ())?;
let PyGILState_Ensure = *lib
.get::<unsafe extern "C" fn() -> PyGILState_STATE>(b"PyGILState_Ensure\0")
.map_err(|_| ())?;
let PyGILState_Release = *lib
.get::<unsafe extern "C" fn(PyGILState_STATE)>(b"PyGILState_Release\0")
.map_err(|_| ())?;
let PyRun_StringFlags = *lib
.get::<unsafe extern "C" fn(
*const c_char,
c_int,
*mut PyObject,
*mut PyObject,
*mut c_void,
) -> *mut PyObject>(b"PyRun_StringFlags\0")
.map_err(|_| ())?;
let PyImport_AddModule = *lib
.get::<unsafe extern "C" fn(*const c_char) -> *mut PyObject>(
b"PyImport_AddModule\0",
)
.map_err(|_| ())?;
let PyModule_GetDict = *lib
.get::<unsafe extern "C" fn(*mut PyObject) -> *mut PyObject>(
b"PyModule_GetDict\0",
)
.map_err(|_| ())?;
let PyImport_ImportModule = *lib
.get::<unsafe extern "C" fn(*const c_char) -> *mut PyObject>(
b"PyImport_ImportModule\0",
)
.map_err(|_| ())?;
let PyObject_Str = *lib
.get::<unsafe extern "C" fn(*mut PyObject) -> *mut PyObject>(b"PyObject_Str\0")
.map_err(|_| ())?;
let PyUnicode_AsUTF8 = *lib
.get::<unsafe extern "C" fn(*mut PyObject) -> *const c_char>(
b"PyUnicode_AsUTF8\0",
)
.map_err(|_| ())?;
let Py_DecRef = *lib
.get::<unsafe extern "C" fn(*mut PyObject)>(b"Py_DecRef\0")
.map_err(|_| ())?;
let Py_IncRef = *lib
.get::<unsafe extern "C" fn(*mut PyObject)>(b"Py_IncRef\0")
.map_err(|_| ())?;
let PyObject_GetAttrString = *lib
.get::<unsafe extern "C" fn(*mut PyObject, *const c_char) -> *mut PyObject>(
b"PyObject_GetAttrString\0",
)
.map_err(|_| ())?;
let PyObject_CallObject = *lib
.get::<unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject>(
b"PyObject_CallObject\0",
)
.map_err(|_| ())?;
let PyObject_Call = *lib
.get::<unsafe extern "C" fn(
*mut PyObject,
*mut PyObject,
*mut PyObject,
) -> *mut PyObject>(b"PyObject_Call\0")
.map_err(|_| ())?;
let PyTuple_New = *lib
.get::<unsafe extern "C" fn(isize) -> *mut PyObject>(b"PyTuple_New\0")
.map_err(|_| ())?;
let PyTuple_SetItem = *lib
.get::<unsafe extern "C" fn(*mut PyObject, isize, *mut PyObject) -> c_int>(
b"PyTuple_SetItem\0",
)
.map_err(|_| ())?;
let PyLong_FromLongLong = *lib
.get::<unsafe extern "C" fn(i64) -> *mut PyObject>(b"PyLong_FromLongLong\0")
.map_err(|_| ())?;
let PyUnicode_FromString = *lib
.get::<unsafe extern "C" fn(*const c_char) -> *mut PyObject>(
b"PyUnicode_FromString\0",
)
.map_err(|_| ())?;
let PyBool_FromLong = *lib
.get::<unsafe extern "C" fn(c_long: c_long) -> *mut PyObject>(
b"PyBool_FromLong\0",
)
.map_err(|_| ())?;
let PyFloat_FromDouble = *lib
.get::<unsafe extern "C" fn(f64) -> *mut PyObject>(b"PyFloat_FromDouble\0")
.map_err(|_| ())?;
let PyFloat_AsDouble = *lib
.get::<unsafe extern "C" fn(*mut PyObject) -> f64>(b"PyFloat_AsDouble\0")
.map_err(|_| ())?;
let PyLong_AsLongLong = *lib
.get::<unsafe extern "C" fn(*mut PyObject) -> i64>(b"PyLong_AsLongLong\0")
.map_err(|_| ())?;
let PyBytes_FromStringAndSize = *lib
.get::<unsafe extern "C" fn(*const c_char, isize) -> *mut PyObject>(
b"PyBytes_FromStringAndSize\0",
)
.map_err(|_| ())?;
let PyBytes_AsStringAndSize = *lib.get::<unsafe extern "C" fn(*mut PyObject, *mut *mut c_char, *mut isize) -> c_int>(b"PyBytes_AsStringAndSize\0").map_err(|_| ())?;
let PyDict_New = *lib
.get::<unsafe extern "C" fn() -> *mut PyObject>(b"PyDict_New\0")
.map_err(|_| ())?;
let PyDict_SetItemString = *lib.get::<unsafe extern "C" fn(*mut PyObject, *const c_char, *mut PyObject) -> c_int>(b"PyDict_SetItemString\0").map_err(|_| ())?;
let PyErr_Occurred = *lib
.get::<unsafe extern "C" fn() -> *mut PyObject>(b"PyErr_Occurred\0")
.map_err(|_| ())?;
let PyErr_Fetch =
*lib.get::<unsafe extern "C" fn(
*mut *mut PyObject,
*mut *mut PyObject,
*mut *mut PyObject,
)>(b"PyErr_Fetch\0")
.map_err(|_| ())?;
let PyErr_Clear = *lib
.get::<unsafe extern "C" fn()>(b"PyErr_Clear\0")
.map_err(|_| ())?;
let cpy = CPython {
_lib: lib,
Py_Initialize,
Py_Finalize,
Py_IsInitialized,
PyGILState_Ensure,
PyGILState_Release,
PyRun_StringFlags,
PyImport_AddModule,
PyModule_GetDict,
PyImport_ImportModule,
PyObject_Str,
PyUnicode_AsUTF8,
Py_DecRef,
Py_IncRef,
PyObject_GetAttrString,
PyObject_CallObject,
PyObject_Call,
PyTuple_New,
PyTuple_SetItem,
PyLong_FromLongLong,
PyUnicode_FromString,
PyBool_FromLong,
PyFloat_FromDouble,
PyFloat_AsDouble,
PyLong_AsLongLong,
PyBytes_FromStringAndSize,
PyBytes_AsStringAndSize,
PyDict_New,
PyDict_SetItemString,
PyErr_Occurred,
PyErr_Fetch,
PyErr_Clear,
};
*CPY.lock().unwrap() = Some(cpy);
return Ok(());
}
}
}
Err(())
}
pub fn ensure_cpython() -> Result<(), ()> {
if CPY.lock().unwrap().is_none() {
try_load_cpython()?;
unsafe {
if let Some(cpy) = &*CPY.lock().unwrap() {
if (cpy.Py_IsInitialized)() == 0 {
(cpy.Py_Initialize)();
}
}
}
}
Ok(())
}

View File

@ -0,0 +1,19 @@
use crate::ffi::{CPython, PyGILState_STATE};
pub struct GILGuard<'a> {
cpy: &'a CPython,
state: PyGILState_STATE,
}
impl<'a> GILGuard<'a> {
pub fn acquire(cpy: &'a CPython) -> Self {
let state = unsafe { (cpy.PyGILState_Ensure)() };
GILGuard { cpy, state }
}
}
impl<'a> Drop for GILGuard<'a> {
fn drop(&mut self) {
unsafe { (self.cpy.PyGILState_Release)(self.state) };
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,461 @@
use crate::ffi::{self, CPython, PyObject};
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::os::raw::c_char;
use std::ptr::NonNull;
pub enum DecodedValue {
Float(f64),
Int(i64),
Str(String),
Bytes(Vec<u8>),
}
pub struct PyOwned {
ptr: NonNull<PyObject>,
}
#[allow(dead_code)]
pub struct PyBorrowed<'a> {
ptr: NonNull<PyObject>,
_marker: PhantomData<&'a PyObject>,
}
impl PyOwned {
pub unsafe fn from_new(ptr: *mut PyObject) -> Option<Self> {
NonNull::new(ptr).map(|ptr| PyOwned { ptr })
}
pub unsafe fn from_raw(ptr: *mut PyObject) -> Option<Self> {
NonNull::new(ptr).map(|ptr| PyOwned { ptr })
}
#[allow(dead_code)]
pub unsafe fn from_borrowed(cpy: &CPython, borrowed: PyBorrowed<'_>) -> Self {
(cpy.Py_IncRef)(borrowed.ptr.as_ptr());
PyOwned { ptr: borrowed.ptr }
}
pub fn as_ptr(&self) -> *mut PyObject {
self.ptr.as_ptr()
}
#[allow(dead_code)]
pub fn borrow(&self) -> PyBorrowed<'_> {
PyBorrowed {
ptr: self.ptr,
_marker: PhantomData,
}
}
#[allow(dead_code)]
pub fn clone_ref(&self, cpy: &CPython) -> Self {
unsafe {
(cpy.Py_IncRef)(self.ptr.as_ptr());
}
PyOwned { ptr: self.ptr }
}
pub fn into_raw(self) -> *mut PyObject {
let ptr = self.ptr.as_ptr();
std::mem::forget(self);
ptr
}
}
impl Drop for PyOwned {
fn drop(&mut self) {
if let Ok(guard) = ffi::CPY.lock() {
if let Some(cpy) = guard.as_ref() {
unsafe {
(cpy.Py_DecRef)(self.ptr.as_ptr());
}
}
}
}
}
impl Clone for PyOwned {
fn clone(&self) -> Self {
let guard = ffi::CPY.lock().expect("CPython state poisoned");
let cpy = guard.as_ref().expect("CPython not initialized");
unsafe {
(cpy.Py_IncRef)(self.ptr.as_ptr());
}
PyOwned { ptr: self.ptr }
}
}
unsafe impl Send for PyOwned {}
unsafe impl Sync for PyOwned {}
#[allow(dead_code)]
impl<'a> PyBorrowed<'a> {
pub unsafe fn new(ptr: *mut PyObject) -> Option<Self> {
NonNull::new(ptr).map(|ptr| PyBorrowed {
ptr,
_marker: PhantomData,
})
}
pub fn as_ptr(&self) -> *mut PyObject {
self.ptr.as_ptr()
}
}
impl<'a> Copy for PyBorrowed<'a> {}
impl<'a> Clone for PyBorrowed<'a> {
fn clone(&self) -> Self {
*self
}
}
pub fn autodecode(cpy: &CPython, obj: *mut PyObject) -> Option<DecodedValue> {
unsafe {
let f = (cpy.PyFloat_AsDouble)(obj);
if (cpy.PyErr_Occurred)().is_null() {
return Some(DecodedValue::Float(f));
}
(cpy.PyErr_Clear)();
let i = (cpy.PyLong_AsLongLong)(obj);
if (cpy.PyErr_Occurred)().is_null() {
return Some(DecodedValue::Int(i));
}
(cpy.PyErr_Clear)();
let u = (cpy.PyUnicode_AsUTF8)(obj);
if (cpy.PyErr_Occurred)().is_null() && !u.is_null() {
let s = CStr::from_ptr(u).to_string_lossy().to_string();
return Some(DecodedValue::Str(s));
}
(cpy.PyErr_Clear)();
let mut ptr: *mut c_char = std::ptr::null_mut();
let mut sz: isize = 0;
if (cpy.PyBytes_AsStringAndSize)(obj, &mut ptr, &mut sz) == 0 {
let slice = std::slice::from_raw_parts(ptr as *const u8, sz as usize);
return Some(DecodedValue::Bytes(slice.to_vec()));
}
if !(cpy.PyErr_Occurred)().is_null() {
(cpy.PyErr_Clear)();
}
}
None
}
pub fn cstring_from_str(s: &str) -> Result<CString, ()> {
CString::new(s).map_err(|_| ())
}
pub unsafe fn cstr_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
Some(CStr::from_ptr(ptr).to_string_lossy().to_string())
}
#[allow(dead_code)]
pub unsafe fn incref(cpy: &CPython, obj: *mut PyObject) {
(cpy.Py_IncRef)(obj);
}
#[allow(dead_code)]
pub unsafe fn decref(cpy: &CPython, obj: *mut PyObject) {
(cpy.Py_DecRef)(obj);
}
pub fn take_py_error_string(cpy: &CPython) -> Option<String> {
unsafe {
if (cpy.PyErr_Occurred)().is_null() {
return None;
}
let mut ptype: *mut PyObject = std::ptr::null_mut();
let mut pvalue: *mut PyObject = std::ptr::null_mut();
let mut ptrace: *mut PyObject = std::ptr::null_mut();
(cpy.PyErr_Fetch)(&mut ptype, &mut pvalue, &mut ptrace);
let s = if !pvalue.is_null() {
let sobj = (cpy.PyObject_Str)(pvalue);
if sobj.is_null() {
(cpy.PyErr_Clear)();
return Some("Python error".to_string());
}
let cstr = (cpy.PyUnicode_AsUTF8)(sobj);
let msg = if cstr.is_null() {
"Python error".to_string()
} else {
CStr::from_ptr(cstr).to_string_lossy().to_string()
};
(cpy.Py_DecRef)(sobj);
msg
} else {
"Python error".to_string()
};
(cpy.PyErr_Clear)();
Some(s)
}
}
pub fn count_tlv_args(args: *const u8, args_len: usize) -> usize {
if args.is_null() || args_len < 4 {
return 0;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
if buf.len() < 4 {
return 0;
}
u16::from_le_bytes([buf[2], buf[3]]) as usize
}
pub fn tuple_from_tlv(
cpy: &CPython,
args: *const u8,
args_len: usize,
) -> Result<*mut PyObject, ()> {
let argc = count_tlv_args(args, args_len) as isize;
let tuple = unsafe { (cpy.PyTuple_New)(argc) };
if tuple.is_null() {
return Err(());
}
if argc == 0 {
return Ok(tuple);
}
if !fill_tuple_from_tlv(cpy, tuple, args, args_len) {
unsafe {
(cpy.Py_DecRef)(tuple);
}
return Err(());
}
Ok(tuple)
}
pub fn kwargs_from_tlv(
cpy: &CPython,
args: *const u8,
args_len: usize,
) -> Result<*mut PyObject, ()> {
let dict = unsafe { (cpy.PyDict_New)() };
if dict.is_null() {
return Err(());
}
if !fill_kwargs_from_tlv(cpy, dict, args, args_len) {
unsafe {
(cpy.Py_DecRef)(dict);
}
return Err(());
}
Ok(dict)
}
pub fn fill_tuple_from_tlv(
cpy: &CPython,
tuple: *mut PyObject,
args: *const u8,
args_len: usize,
) -> bool {
if args.is_null() || args_len < 4 {
return true;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize;
let mut idx: isize = 0;
while off + 4 <= buf.len() {
let tag = buf[off];
let _rsv = buf[off + 1];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if off + 4 + size > buf.len() {
return false;
}
let payload = &buf[off + 4..off + 4 + size];
let mut obj: *mut PyObject = std::ptr::null_mut();
unsafe {
let mut owned_transfer: Option<PyOwned> = None;
match tag {
1 => {
let v = if size >= 1 && payload[0] != 0 { 1 } else { 0 };
obj = (cpy.PyBool_FromLong)(v);
}
2 => {
if size != 4 {
return false;
}
let mut b = [0u8; 4];
b.copy_from_slice(payload);
obj = (cpy.PyLong_FromLongLong)(i32::from_le_bytes(b) as i64);
}
3 => {
if size != 8 {
return false;
}
let mut b = [0u8; 8];
b.copy_from_slice(payload);
obj = (cpy.PyLong_FromLongLong)(i64::from_le_bytes(b));
}
5 => {
if size != 8 {
return false;
}
let mut b = [0u8; 8];
b.copy_from_slice(payload);
obj = (cpy.PyFloat_FromDouble)(f64::from_le_bytes(b));
}
6 => {
let c = match CString::new(payload) {
Ok(c) => c,
Err(_) => return false,
};
obj = (cpy.PyUnicode_FromString)(c.as_ptr());
}
7 => {
let ptr = if size > 0 {
payload.as_ptr() as *const c_char
} else {
std::ptr::null()
};
obj = (cpy.PyBytes_FromStringAndSize)(ptr, size as isize);
}
8 => {
if size != 8 {
return false;
}
let mut i = [0u8; 4];
i.copy_from_slice(&payload[4..8]);
let inst_id = u32::from_le_bytes(i);
let Some(handle) = super::PY_HANDLES.lock().unwrap().get(&inst_id).cloned()
else {
return false;
};
owned_transfer = Some(handle);
}
_ => return false,
}
let raw = if let Some(handle) = owned_transfer.take() {
handle.into_raw()
} else {
obj
};
if (cpy.PyTuple_SetItem)(tuple, idx, raw) != 0 {
if let Some(rewrap) = PyOwned::from_raw(raw) {
drop(rewrap);
}
return false;
}
idx += 1;
}
off += 4 + size;
}
true
}
pub fn fill_kwargs_from_tlv(
cpy: &CPython,
dict: *mut PyObject,
args: *const u8,
args_len: usize,
) -> bool {
if args.is_null() || args_len < 4 {
return true;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize;
while off + 4 <= buf.len() {
// key (string)
if buf[off] != 6 {
return false;
}
let key_size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if off + 4 + key_size > buf.len() {
return false;
}
let key_slice = &buf[off + 4..off + 4 + key_size];
let key_c = match CString::new(key_slice) {
Ok(c) => c,
Err(_) => return false,
};
off += 4 + key_size;
if off + 4 > buf.len() {
return false;
}
let tag_v = buf[off];
let _rsv = buf[off + 1];
let size_v = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if off + 4 + size_v > buf.len() {
return false;
}
let val_payload = &buf[off + 4..off + 4 + size_v];
let obj: *mut PyObject;
unsafe {
match tag_v {
1 => {
let v = if size_v >= 1 && val_payload[0] != 0 {
1
} else {
0
};
obj = (cpy.PyBool_FromLong)(v);
}
2 => {
if size_v != 4 {
return false;
}
let mut b = [0u8; 4];
b.copy_from_slice(val_payload);
obj = (cpy.PyLong_FromLongLong)(i32::from_le_bytes(b) as i64);
}
3 => {
if size_v != 8 {
return false;
}
let mut b = [0u8; 8];
b.copy_from_slice(val_payload);
obj = (cpy.PyLong_FromLongLong)(i64::from_le_bytes(b));
}
5 => {
if size_v != 8 {
return false;
}
let mut b = [0u8; 8];
b.copy_from_slice(val_payload);
obj = (cpy.PyFloat_FromDouble)(f64::from_le_bytes(b));
}
6 => {
let c = match CString::new(val_payload) {
Ok(c) => c,
Err(_) => return false,
};
obj = (cpy.PyUnicode_FromString)(c.as_ptr());
}
7 => {
let ptr = if size_v > 0 {
val_payload.as_ptr() as *const c_char
} else {
std::ptr::null()
};
obj = (cpy.PyBytes_FromStringAndSize)(ptr, size_v as isize);
}
8 => {
if size_v != 8 {
return false;
}
let mut i = [0u8; 4];
i.copy_from_slice(&val_payload[4..8]);
let inst_id = u32::from_le_bytes(i);
let Some(handle) = super::PY_HANDLES.lock().unwrap().get(&inst_id).cloned()
else {
return false;
};
obj = handle.into_raw();
}
_ => return false,
}
let rc = (cpy.PyDict_SetItemString)(dict, key_c.as_ptr(), obj);
(cpy.Py_DecRef)(obj);
if rc != 0 {
return false;
}
}
off += 4 + size_v;
}
true
}

View File

@ -220,7 +220,9 @@ unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr; use std::ffi::CStr;
extern "C" fn regex_resolve(name: *const std::os::raw::c_char) -> u32 { extern "C" fn regex_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; } if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() { match s.as_ref() {
"compile" => M_COMPILE, "compile" => M_COMPILE,
@ -246,66 +248,148 @@ extern "C" fn regex_invoke_id(
match method_id { match method_id {
M_BIRTH => { M_BIRTH => {
// mirror v1: birth may take optional pattern // mirror v1: birth may take optional pattern
if result_len.is_null() { return E_ARGS; } if result_len.is_null() {
if preflight(result, result_len, 4) { return E_SHORT; } return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let inst = if let Some(pat) = read_arg_string(args, args_len, 0) { 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 } } match Regex::new(&pat) {
} else { RegexInstance { re: None } }; Ok(re) => RegexInstance { re: Some(re) },
if let Ok(mut m) = INST.lock() { m.insert(id, inst); } else { return E_PLUGIN; } 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(); let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4; OK *result_len = 4;
OK
} }
M_FINI => { M_FINI => {
if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
} }
M_COMPILE => { M_COMPILE => {
let pat = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; 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 Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) { inst.re = Regex::new(&pat).ok(); OK } else { E_HANDLE } if let Some(inst) = m.get_mut(&instance_id) {
} else { E_PLUGIN } inst.re = Regex::new(&pat).ok();
OK
} else {
E_HANDLE
}
} else {
E_PLUGIN
}
} }
M_IS_MATCH => { M_IS_MATCH => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; let text = match read_arg_string(args, args_len, 0) {
if let Ok(m) = INST.lock() { Some(s) => s,
if let Some(inst) = m.get(&instance_id) { None => return E_ARGS,
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 Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) { if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re { 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_bool(re.is_match(&text), result, result_len);
return write_tlv_string(&s, result, result_len); } else {
} else { return write_tlv_string("", result, result_len); } return write_tlv_bool(false, result, result_len);
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
} }
M_REPLACE_ALL => { } else {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; return E_HANDLE;
let repl = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS }; }
} 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 Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) { 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); } if let Some(re) = &inst.re {
} else { return E_HANDLE; } let s = re
} else { return E_PLUGIN; } .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 => { M_SPLIT => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; 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); let limit = read_arg_i64(args, args_len, 1).unwrap_or(0);
if let Ok(m) = INST.lock() { if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) { if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re { if let Some(re) = &inst.re {
let parts: Vec<String> = 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 parts: Vec<String> = 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"); let out = parts.join("\n");
return write_tlv_string(&out, result, result_len); return write_tlv_string(&out, result, result_len);
} else { return write_tlv_string(&text, result, result_len); } } else {
} else { return E_HANDLE; } return write_tlv_string(&text, result, result_len);
} else { return E_PLUGIN; } }
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
} }
_ => E_METHOD, _ => E_METHOD,
} }

View File

@ -163,7 +163,9 @@ unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr; use std::ffi::CStr;
extern "C" fn toml_resolve(name: *const std::os::raw::c_char) -> u32 { extern "C" fn toml_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; } if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() { match s.as_ref() {
"parse" => M_PARSE, "parse" => M_PARSE,
@ -186,47 +188,93 @@ extern "C" fn toml_invoke_id(
unsafe { unsafe {
match method_id { match method_id {
M_BIRTH => { M_BIRTH => {
if result_len.is_null() { return E_ARGS; } if result_len.is_null() {
if preflight(result, result_len, 4) { return E_SHORT; } return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() { m.insert(id, TomlInstance { value: None }); } else { return E_PLUGIN; } if let Ok(mut m) = INST.lock() {
m.insert(id, TomlInstance { value: None });
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes(); let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4; OK *result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
} }
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
M_PARSE => { M_PARSE => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; let text = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(mut m) = INST.lock() { if let Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) { if let Some(inst) = m.get_mut(&instance_id) {
inst.value = toml::from_str::<toml::Value>(&text).ok(); inst.value = toml::from_str::<toml::Value>(&text).ok();
return write_tlv_bool(inst.value.is_some(), result, result_len); return write_tlv_bool(inst.value.is_some(), result, result_len);
} else { return E_HANDLE; } } else {
} else { return E_PLUGIN; } return E_HANDLE;
}
} else {
return E_PLUGIN;
}
} }
M_GET => { M_GET => {
let path = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; let path = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(m) = INST.lock() { if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) { if let Some(inst) = m.get(&instance_id) {
let mut cur = match &inst.value { Some(v) => v, None => { return write_tlv_string("", result, result_len); } }; let mut cur = match &inst.value {
Some(v) => v,
None => {
return write_tlv_string("", result, result_len);
}
};
if !path.is_empty() { if !path.is_empty() {
for seg in path.split('.') { for seg in path.split('.') {
match cur.get(seg) { Some(v) => cur = v, None => { return write_tlv_string("", result, result_len); } } match cur.get(seg) {
Some(v) => cur = v,
None => {
return write_tlv_string("", result, result_len);
}
}
} }
} }
return write_tlv_string(&cur.to_string(), result, result_len); return write_tlv_string(&cur.to_string(), result, result_len);
} else { return E_HANDLE; } } else {
} else { return E_PLUGIN; } return E_HANDLE;
}
} else {
return E_PLUGIN;
}
} }
M_TO_JSON => { M_TO_JSON => {
if let Ok(m) = INST.lock() { if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) { if let Some(inst) = m.get(&instance_id) {
if let Some(v) = &inst.value { if let Some(v) = &inst.value {
if let Ok(s) = serde_json::to_string(v) { return write_tlv_string(&s, result, result_len); } if let Ok(s) = serde_json::to_string(v) {
return write_tlv_string(&s, result, result_len);
}
} }
return write_tlv_string("{}", result, result_len); return write_tlv_string("{}", result, result_len);
} else { return E_HANDLE; } } else {
} else { return E_PLUGIN; } return E_HANDLE;
}
} else {
return E_PLUGIN;
}
} }
_ => E_METHOD, _ => E_METHOD,
} }

View File

@ -16,47 +16,57 @@ use crate::operator_traits::{
OperatorError, OperatorError,
}; };
// ===== IntegerBox Operator Implementations ===== // Small helpers to reduce duplication in dynamic operators
#[inline]
fn concat_result(left: &dyn NyashBox, right: &dyn NyashBox) -> Box<dyn NyashBox> {
let l = left.to_string_box();
let r = right.to_string_box();
Box::new(StringBox::new(format!("{}{}", l.value, r.value)))
}
/// IntegerBox + IntegerBox -> IntegerBox #[inline]
impl NyashAdd<IntegerBox> for IntegerBox { fn can_repeat(times: i64) -> bool {
type Output = IntegerBox; (0..=10_000).contains(&times)
}
fn add(self, rhs: IntegerBox) -> Self::Output { // ===== Static numeric operators (macro-generated) =====
IntegerBox::new(self.value + rhs.value) macro_rules! impl_static_numeric_ops {
($ty:ty, $zero:expr) => {
impl NyashAdd<$ty> for $ty {
type Output = $ty;
fn add(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value + rhs.value)
} }
} }
/// IntegerBox - IntegerBox -> IntegerBox impl NyashSub<$ty> for $ty {
impl NyashSub<IntegerBox> for IntegerBox { type Output = $ty;
type Output = IntegerBox; fn sub(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value - rhs.value)
fn sub(self, rhs: IntegerBox) -> Self::Output {
IntegerBox::new(self.value - rhs.value)
} }
} }
/// IntegerBox * IntegerBox -> IntegerBox impl NyashMul<$ty> for $ty {
impl NyashMul<IntegerBox> for IntegerBox { type Output = $ty;
type Output = IntegerBox; fn mul(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value * rhs.value)
fn mul(self, rhs: IntegerBox) -> Self::Output {
IntegerBox::new(self.value * rhs.value)
} }
} }
/// IntegerBox / IntegerBox -> IntegerBox (with zero check) impl NyashDiv<$ty> for $ty {
impl NyashDiv<IntegerBox> for IntegerBox { type Output = Result<$ty, OperatorError>;
type Output = Result<IntegerBox, OperatorError>; fn div(self, rhs: $ty) -> Self::Output {
if rhs.value == $zero {
fn div(self, rhs: IntegerBox) -> Self::Output {
if rhs.value == 0 {
Err(OperatorError::DivisionByZero) Err(OperatorError::DivisionByZero)
} else { } else {
Ok(IntegerBox::new(self.value / rhs.value)) Ok(< $ty >::new(self.value / rhs.value))
} }
} }
} }
};
}
impl_static_numeric_ops!(IntegerBox, 0);
/// Dynamic dispatch implementation for IntegerBox /// Dynamic dispatch implementation for IntegerBox
impl DynamicAdd for IntegerBox { impl DynamicAdd for IntegerBox {
@ -73,14 +83,8 @@ impl DynamicAdd for IntegerBox {
))); )));
} }
// Fallback: Convert both to strings and concatenate // Fallback: Convert both to strings and concatenate (existing AddBox behavior)
// This preserves the existing AddBox behavior Some(concat_result(self, other))
let left_str = self.to_string_box();
let right_str = other.to_string_box();
Some(Box::new(StringBox::new(format!(
"{}{}",
left_str.value, right_str.value
))))
} }
fn can_add_with(&self, other_type: &str) -> bool { fn can_add_with(&self, other_type: &str) -> bool {
@ -126,8 +130,7 @@ impl DynamicMul for IntegerBox {
// IntegerBox * StringBox -> Repeated string // IntegerBox * StringBox -> Repeated string
if let Some(other_str) = other.as_any().downcast_ref::<StringBox>() { if let Some(other_str) = other.as_any().downcast_ref::<StringBox>() {
if self.value >= 0 && self.value <= 10000 { if can_repeat(self.value) {
// Safety limit
let repeated = other_str.value.repeat(self.value as usize); let repeated = other_str.value.repeat(self.value as usize);
return Some(Box::new(StringBox::new(repeated))); return Some(Box::new(StringBox::new(repeated)));
} }
@ -171,47 +174,7 @@ impl DynamicDiv for IntegerBox {
} }
} }
// ===== FloatBox Operator Implementations ===== impl_static_numeric_ops!(FloatBox, 0.0);
/// FloatBox + FloatBox -> FloatBox
impl NyashAdd<FloatBox> for FloatBox {
type Output = FloatBox;
fn add(self, rhs: FloatBox) -> Self::Output {
FloatBox::new(self.value + rhs.value)
}
}
/// FloatBox - FloatBox -> FloatBox
impl NyashSub<FloatBox> for FloatBox {
type Output = FloatBox;
fn sub(self, rhs: FloatBox) -> Self::Output {
FloatBox::new(self.value - rhs.value)
}
}
/// FloatBox * FloatBox -> FloatBox
impl NyashMul<FloatBox> for FloatBox {
type Output = FloatBox;
fn mul(self, rhs: FloatBox) -> Self::Output {
FloatBox::new(self.value * rhs.value)
}
}
/// FloatBox / FloatBox -> FloatBox (with zero check)
impl NyashDiv<FloatBox> for FloatBox {
type Output = Result<FloatBox, OperatorError>;
fn div(self, rhs: FloatBox) -> Self::Output {
if rhs.value == 0.0 {
Err(OperatorError::DivisionByZero)
} else {
Ok(FloatBox::new(self.value / rhs.value))
}
}
}
// ===== FloatBox Dynamic Operator Implementations ===== // ===== FloatBox Dynamic Operator Implementations =====
@ -228,12 +191,7 @@ impl DynamicAdd for FloatBox {
} }
// Fallback: Convert both to strings and concatenate // Fallback: Convert both to strings and concatenate
let left_str = self.to_string_box(); Some(concat_result(self, other))
let right_str = other.to_string_box();
Some(Box::new(StringBox::new(format!(
"{}{}",
left_str.value, right_str.value
))))
} }
fn can_add_with(&self, other_type: &str) -> bool { fn can_add_with(&self, other_type: &str) -> bool {
@ -344,11 +302,7 @@ impl DynamicAdd for StringBox {
} }
// StringBox + any other type -> Convert to string and concatenate // StringBox + any other type -> Convert to string and concatenate
let other_str = other.to_string_box(); Some(concat_result(self, other))
Some(Box::new(StringBox::new(format!(
"{}{}",
self.value, other_str.value
))))
} }
fn can_add_with(&self, _other_type: &str) -> bool { fn can_add_with(&self, _other_type: &str) -> bool {
@ -370,8 +324,7 @@ impl DynamicMul for StringBox {
fn try_mul(&self, other: &dyn NyashBox) -> Option<Box<dyn NyashBox>> { fn try_mul(&self, other: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
// StringBox * IntegerBox -> Repeated string // StringBox * IntegerBox -> Repeated string
if let Some(other_int) = other.as_any().downcast_ref::<IntegerBox>() { if let Some(other_int) = other.as_any().downcast_ref::<IntegerBox>() {
if other_int.value >= 0 && other_int.value <= 10000 { if can_repeat(other_int.value) {
// Safety limit
let repeated = self.value.repeat(other_int.value as usize); let repeated = self.value.repeat(other_int.value as usize);
return Some(Box::new(StringBox::new(repeated))); return Some(Box::new(StringBox::new(repeated)));
} }
@ -505,6 +458,67 @@ impl DynamicDiv for BoolBox {
pub struct OperatorResolver; pub struct OperatorResolver;
impl OperatorResolver { impl OperatorResolver {
#[inline]
fn try_dyn_left_add(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_add(right) { return Some(result); }
}
if let Some(str_box) = left.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if let Some(result) = str_box.try_add(right) { return Some(result); }
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_add(right) { return Some(result); }
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_add(right) { return Some(result); }
}
None
}
#[inline]
fn try_dyn_left_sub(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_sub(right) { return Some(result); }
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_sub(right) { return Some(result); }
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_sub(right) { return Some(result); }
}
None
}
#[inline]
fn try_dyn_left_mul(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_mul(right) { return Some(result); }
}
if let Some(str_box) = left.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if let Some(result) = str_box.try_mul(right) { return Some(result); }
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_mul(right) { return Some(result); }
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_mul(right) { return Some(result); }
}
None
}
#[inline]
fn try_dyn_left_div(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
return int_box.try_div(right);
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
return float_box.try_div(right);
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
return bool_box.try_div(right);
}
None
}
/// Resolve addition operation with hybrid dispatch /// Resolve addition operation with hybrid dispatch
pub fn resolve_add( pub fn resolve_add(
left: &dyn NyashBox, left: &dyn NyashBox,
@ -513,33 +527,7 @@ impl OperatorResolver {
// Try to cast to concrete types first and use their DynamicAdd implementation // Try to cast to concrete types first and use their DynamicAdd implementation
// This approach uses the concrete types rather than trait objects // This approach uses the concrete types rather than trait objects
// Check if left implements DynamicAdd by trying common types if let Some(result) = Self::try_dyn_left_add(left, right) { return Ok(result); }
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_add(right) {
return Ok(result);
}
}
if let Some(str_box) = left.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if let Some(result) = str_box.try_add(right) {
return Ok(result);
}
}
if let Some(float_box) = right
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_add(right) {
return Ok(result);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_add(right) {
return Ok(result);
}
}
Err(OperatorError::UnsupportedOperation { Err(OperatorError::UnsupportedOperation {
operator: "+".to_string(), operator: "+".to_string(),
@ -553,21 +541,7 @@ impl OperatorResolver {
left: &dyn NyashBox, left: &dyn NyashBox,
right: &dyn NyashBox, right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> { ) -> Result<Box<dyn NyashBox>, OperatorError> {
// Try concrete types for DynamicSub if let Some(result) = Self::try_dyn_left_sub(left, right) { return Ok(result); }
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_sub(right) {
return Ok(result);
}
}
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_sub(right) {
return Ok(result);
}
}
Err(OperatorError::UnsupportedOperation { Err(OperatorError::UnsupportedOperation {
operator: "-".to_string(), operator: "-".to_string(),
@ -581,33 +555,7 @@ impl OperatorResolver {
left: &dyn NyashBox, left: &dyn NyashBox,
right: &dyn NyashBox, right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> { ) -> Result<Box<dyn NyashBox>, OperatorError> {
// Try concrete types for DynamicMul if let Some(result) = Self::try_dyn_left_mul(left, right) { return Ok(result); }
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_mul(right) {
return Ok(result);
}
}
if let Some(str_box) = left.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if let Some(result) = str_box.try_mul(right) {
return Ok(result);
}
}
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_mul(right) {
return Ok(result);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_mul(right) {
return Ok(result);
}
}
Err(OperatorError::UnsupportedOperation { Err(OperatorError::UnsupportedOperation {
operator: "*".to_string(), operator: "*".to_string(),
@ -621,35 +569,7 @@ impl OperatorResolver {
left: &dyn NyashBox, left: &dyn NyashBox,
right: &dyn NyashBox, right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> { ) -> Result<Box<dyn NyashBox>, OperatorError> {
// Try concrete types for DynamicDiv if let Some(result) = Self::try_dyn_left_div(left, right) { return Ok(result); }
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_div(right) {
return Ok(result);
} else {
// If try_div returns None, it might be division by zero
return Err(OperatorError::DivisionByZero);
}
}
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_div(right) {
return Ok(result);
} else {
// If try_div returns None, it might be division by zero
return Err(OperatorError::DivisionByZero);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_div(right) {
return Ok(result);
} else {
return Err(OperatorError::DivisionByZero);
}
}
Err(OperatorError::UnsupportedOperation { Err(OperatorError::UnsupportedOperation {
operator: "/".to_string(), operator: "/".to_string(),

View File

@ -11,6 +11,37 @@ use crate::mir::instruction::{
TypeOpKind as MirTypeOpKind, WeakRefOp as MirWeakRefOp, TypeOpKind as MirTypeOpKind, WeakRefOp as MirWeakRefOp,
}; };
// Local macro utilities for generating InstructionMeta boilerplate.
// This macro is intentionally scoped to this module to avoid polluting the crate namespace.
macro_rules! inst_meta {
(
$(
pub struct $name:ident { $($field:ident : $fty:ty),* $(,)? }
=> {
from_mir = |$i:ident| $from_expr:expr;
effects = $effects:expr;
dst = $dst:expr;
used = $used:expr;
}
)+
) => {
$(
#[derive(Debug, Clone)]
pub struct $name { $(pub $field: $fty),* }
impl $name {
pub fn from_mir($i: &MirInstruction) -> Option<Self> { $from_expr }
}
impl InstructionMeta for $name {
fn effects(&self) -> EffectMask { ($effects)(self) }
fn dst(&self) -> Option<ValueId> { ($dst)(self) }
fn used(&self) -> Vec<ValueId> { ($used)(self) }
}
)+
};
}
pub trait InstructionMeta { pub trait InstructionMeta {
fn effects(&self) -> EffectMask; fn effects(&self) -> EffectMask;
fn dst(&self) -> Option<ValueId>; fn dst(&self) -> Option<ValueId>;
@ -130,499 +161,289 @@ pub fn used_via_meta(i: &MirInstruction) -> Option<Vec<ValueId>> {
None None
} }
// ---- BarrierRead ---- // ---- BarrierRead ---- (macro-generated)
#[derive(Debug, Clone, Copy)] inst_meta! {
pub struct BarrierReadInst { pub ptr: ValueId } pub struct BarrierReadInst { ptr: ValueId }
=> {
impl BarrierReadInst { from_mir = |i| match i { MirInstruction::BarrierRead { ptr } => Some(BarrierReadInst { ptr: *ptr }), _ => None };
pub fn from_mir(i: &MirInstruction) -> Option<Self> { effects = |_: &Self| EffectMask::READ.add(Effect::Barrier);
match i { MirInstruction::BarrierRead { ptr } => Some(BarrierReadInst { ptr: *ptr }), _ => None } dst = |_: &Self| None;
used = |s: &Self| vec![s.ptr];
} }
} }
impl InstructionMeta for BarrierReadInst { // ---- BarrierWrite ---- (macro-generated)
fn effects(&self) -> EffectMask { EffectMask::READ.add(Effect::Barrier) } inst_meta! {
fn dst(&self) -> Option<ValueId> { None } pub struct BarrierWriteInst { ptr: ValueId }
fn used(&self) -> Vec<ValueId> { vec![self.ptr] } => {
} from_mir = |i| match i { MirInstruction::BarrierWrite { ptr } => Some(BarrierWriteInst { ptr: *ptr }), _ => None };
effects = |_: &Self| EffectMask::WRITE.add(Effect::Barrier);
// ---- BarrierWrite ---- dst = |_: &Self| None;
#[derive(Debug, Clone, Copy)] used = |s: &Self| vec![s.ptr];
pub struct BarrierWriteInst { pub ptr: ValueId }
impl BarrierWriteInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::BarrierWrite { ptr } => Some(BarrierWriteInst { ptr: *ptr }), _ => None }
} }
} }
impl InstructionMeta for BarrierWriteInst { // ---- Barrier (unified) ---- (macro-generated)
fn effects(&self) -> EffectMask { EffectMask::WRITE.add(Effect::Barrier) } inst_meta! {
fn dst(&self) -> Option<ValueId> { None } pub struct BarrierInst { op: MirBarrierOp, ptr: ValueId }
fn used(&self) -> Vec<ValueId> { vec![self.ptr] } => {
} from_mir = |i| match i { MirInstruction::Barrier { op, ptr } => Some(BarrierInst { op: *op, ptr: *ptr }), _ => None };
effects = |s: &Self| match s.op { MirBarrierOp::Read => EffectMask::READ.add(Effect::Barrier), MirBarrierOp::Write => EffectMask::WRITE.add(Effect::Barrier) };
// ---- Barrier (unified) ---- dst = |_: &Self| None;
#[derive(Debug, Clone, Copy)] used = |s: &Self| vec![s.ptr];
pub struct BarrierInst { pub op: MirBarrierOp, pub ptr: ValueId }
impl BarrierInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::Barrier { op, ptr } => Some(BarrierInst { op: *op, ptr: *ptr }), _ => None }
} }
} }
impl InstructionMeta for BarrierInst { // ---- Ref ops ---- (macro-generated)
fn effects(&self) -> EffectMask { inst_meta! {
match self.op { pub struct RefNewInst { dst: ValueId, box_val: ValueId }
MirBarrierOp::Read => EffectMask::READ.add(Effect::Barrier), => {
MirBarrierOp::Write => EffectMask::WRITE.add(Effect::Barrier), from_mir = |i| match i { MirInstruction::RefNew { dst, box_val } => Some(RefNewInst { dst: *dst, box_val: *box_val }), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |s: &Self| Some(s.dst);
used = |s: &Self| vec![s.box_val];
} }
} }
fn dst(&self) -> Option<ValueId> { None } inst_meta! {
fn used(&self) -> Vec<ValueId> { vec![self.ptr] } pub struct RefGetInst { dst: ValueId, reference: ValueId }
} => {
from_mir = |i| match i { MirInstruction::RefGet { dst, reference, .. } => Some(RefGetInst { dst: *dst, reference: *reference }), _ => None };
// ---- Ref ops ---- effects = |_: &Self| EffectMask::READ;
#[derive(Debug, Clone, Copy)] dst = |s: &Self| Some(s.dst);
pub struct RefNewInst { pub dst: ValueId, pub box_val: ValueId } used = |s: &Self| vec![s.reference];
impl RefNewInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::RefNew { dst, box_val } => Some(RefNewInst { dst: *dst, box_val: *box_val }), _ => None }
} }
} }
impl InstructionMeta for RefNewInst { inst_meta! {
fn effects(&self) -> EffectMask { EffectMask::PURE } pub struct RefSetInst { reference: ValueId, value: ValueId }
fn dst(&self) -> Option<ValueId> { Some(self.dst) } => {
fn used(&self) -> Vec<ValueId> { vec![self.box_val] } from_mir = |i| match i { MirInstruction::RefSet { reference, value, .. } => Some(RefSetInst { reference: *reference, value: *value }), _ => None };
} effects = |_: &Self| EffectMask::WRITE;
dst = |_: &Self| None;
#[derive(Debug, Clone, Copy)] used = |s: &Self| vec![s.reference, s.value];
pub struct RefGetInst { pub dst: ValueId, pub reference: ValueId }
impl RefGetInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::RefGet { dst, reference, .. } => Some(RefGetInst { dst: *dst, reference: *reference }), _ => None }
}
}
impl InstructionMeta for RefGetInst {
fn effects(&self) -> EffectMask { EffectMask::READ }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.reference] }
}
#[derive(Debug, Clone, Copy)]
pub struct RefSetInst { pub reference: ValueId, pub value: ValueId }
impl RefSetInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::RefSet { reference, value, .. } => Some(RefSetInst { reference: *reference, value: *value }), _ => None }
}
}
impl InstructionMeta for RefSetInst {
fn effects(&self) -> EffectMask { EffectMask::WRITE }
fn dst(&self) -> Option<ValueId> { None }
fn used(&self) -> Vec<ValueId> { vec![self.reference, self.value] }
}
// ---- Weak ops ----
#[derive(Debug, Clone, Copy)]
pub struct WeakNewInst { pub dst: ValueId, pub box_val: ValueId }
impl WeakNewInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::WeakNew { dst, box_val } => Some(WeakNewInst { dst: *dst, box_val: *box_val }), _ => None }
}
}
impl InstructionMeta for WeakNewInst {
fn effects(&self) -> EffectMask { EffectMask::PURE }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.box_val] }
}
#[derive(Debug, Clone, Copy)]
pub struct WeakLoadInst { pub dst: ValueId, pub weak_ref: ValueId }
impl WeakLoadInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::WeakLoad { dst, weak_ref } => Some(WeakLoadInst { dst: *dst, weak_ref: *weak_ref }), _ => None }
}
}
impl InstructionMeta for WeakLoadInst {
fn effects(&self) -> EffectMask { EffectMask::READ }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.weak_ref] }
}
#[derive(Debug, Clone, Copy)]
pub struct WeakRefInst { pub dst: ValueId, pub op: MirWeakRefOp, pub value: ValueId }
impl WeakRefInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::WeakRef { dst, op, value } => Some(WeakRefInst { dst: *dst, op: *op, value: *value }), _ => None }
}
}
impl InstructionMeta for WeakRefInst {
fn effects(&self) -> EffectMask {
match self.op {
MirWeakRefOp::New => EffectMask::PURE,
MirWeakRefOp::Load => EffectMask::READ,
}
}
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.value] }
}
// ---- Future ops ----
#[derive(Debug, Clone, Copy)]
pub struct FutureNewInst { pub dst: ValueId, pub value: ValueId }
impl FutureNewInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::FutureNew { dst, value } => Some(FutureNewInst { dst: *dst, value: *value }), _ => None }
}
}
impl InstructionMeta for FutureNewInst {
fn effects(&self) -> EffectMask { EffectMask::PURE.add(Effect::Alloc) }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.value] }
}
#[derive(Debug, Clone, Copy)]
pub struct FutureSetInst { pub future: ValueId, pub value: ValueId }
impl FutureSetInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::FutureSet { future, value } => Some(FutureSetInst { future: *future, value: *value }), _ => None }
}
}
impl InstructionMeta for FutureSetInst {
fn effects(&self) -> EffectMask { EffectMask::WRITE }
fn dst(&self) -> Option<ValueId> { None }
fn used(&self) -> Vec<ValueId> { vec![self.future, self.value] }
}
#[derive(Debug, Clone, Copy)]
pub struct AwaitInst { pub dst: ValueId, pub future: ValueId }
impl AwaitInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::Await { dst, future } => Some(AwaitInst { dst: *dst, future: *future }), _ => None }
}
}
impl InstructionMeta for AwaitInst {
fn effects(&self) -> EffectMask { EffectMask::READ.add(Effect::Async) }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.future] }
}
// ---- UnaryOp ----
#[derive(Debug, Clone, Copy)]
pub struct UnaryOpInst {
pub dst: ValueId,
pub operand: ValueId,
}
impl UnaryOpInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::UnaryOp { dst, operand, .. } => Some(UnaryOpInst { dst: *dst, operand: *operand }),
_ => None,
}
} }
} }
impl InstructionMeta for UnaryOpInst { // ---- Weak ops ---- (macro-generated)
fn effects(&self) -> EffectMask { EffectMask::PURE } inst_meta! {
fn dst(&self) -> Option<ValueId> { Some(self.dst) } pub struct WeakNewInst { dst: ValueId, box_val: ValueId }
fn used(&self) -> Vec<ValueId> { vec![self.operand] } => {
} from_mir = |i| match i { MirInstruction::WeakNew { dst, box_val } => Some(WeakNewInst { dst: *dst, box_val: *box_val }), _ => None };
effects = |_: &Self| EffectMask::PURE;
// ---- Compare ---- dst = |s: &Self| Some(s.dst);
#[derive(Debug, Clone, Copy)] used = |s: &Self| vec![s.box_val];
pub struct CompareInst {
pub dst: ValueId,
pub lhs: ValueId,
pub rhs: ValueId,
}
impl CompareInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::Compare { dst, lhs, rhs, .. } => Some(CompareInst { dst: *dst, lhs: *lhs, rhs: *rhs }),
_ => None,
} }
} }
} inst_meta! {
pub struct WeakLoadInst { dst: ValueId, weak_ref: ValueId }
impl InstructionMeta for CompareInst { => {
fn effects(&self) -> EffectMask { EffectMask::PURE } from_mir = |i| match i { MirInstruction::WeakLoad { dst, weak_ref } => Some(WeakLoadInst { dst: *dst, weak_ref: *weak_ref }), _ => None };
fn dst(&self) -> Option<ValueId> { Some(self.dst) } effects = |_: &Self| EffectMask::READ;
fn used(&self) -> Vec<ValueId> { vec![self.lhs, self.rhs] } dst = |s: &Self| Some(s.dst);
} used = |s: &Self| vec![s.weak_ref];
// ---- Load ----
#[derive(Debug, Clone, Copy)]
pub struct LoadInst {
pub dst: ValueId,
pub ptr: ValueId,
}
impl LoadInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::Load { dst, ptr } => Some(LoadInst { dst: *dst, ptr: *ptr }),
_ => None,
} }
} }
} inst_meta! {
pub struct WeakRefInst { dst: ValueId, op: MirWeakRefOp, value: ValueId }
impl InstructionMeta for LoadInst { => {
fn effects(&self) -> EffectMask { EffectMask::READ } from_mir = |i| match i { MirInstruction::WeakRef { dst, op, value } => Some(WeakRefInst { dst: *dst, op: *op, value: *value }), _ => None };
fn dst(&self) -> Option<ValueId> { Some(self.dst) } effects = |s: &Self| match s.op { MirWeakRefOp::New => EffectMask::PURE, MirWeakRefOp::Load => EffectMask::READ };
fn used(&self) -> Vec<ValueId> { vec![self.ptr] } dst = |s: &Self| Some(s.dst);
} used = |s: &Self| vec![s.value];
// ---- Cast ----
#[derive(Debug, Clone)]
pub struct CastInst {
pub dst: ValueId,
pub value: ValueId,
pub target_type: MirType,
}
impl CastInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::Cast { dst, value, target_type } =>
Some(CastInst { dst: *dst, value: *value, target_type: target_type.clone() }),
_ => None,
}
}
}
impl InstructionMeta for CastInst {
fn effects(&self) -> EffectMask { EffectMask::PURE }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.value] }
}
// ---- TypeOp ----
#[derive(Debug, Clone)]
pub struct TypeOpInst {
pub dst: ValueId,
pub op: MirTypeOpKind,
pub value: ValueId,
pub ty: MirType,
}
impl TypeOpInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::TypeOp { dst, op, value, ty } =>
Some(TypeOpInst { dst: *dst, op: *op, value: *value, ty: ty.clone() }),
_ => None,
}
}
}
impl InstructionMeta for TypeOpInst {
fn effects(&self) -> EffectMask { EffectMask::PURE }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.value] }
}
// ---- ArrayGet ----
#[derive(Debug, Clone, Copy)]
pub struct ArrayGetInst {
pub dst: ValueId,
pub array: ValueId,
pub index: ValueId,
}
impl ArrayGetInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::ArrayGet { dst, array, index } =>
Some(ArrayGetInst { dst: *dst, array: *array, index: *index }),
_ => None,
}
}
}
impl InstructionMeta for ArrayGetInst {
fn effects(&self) -> EffectMask { EffectMask::READ }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { vec![self.array, self.index] }
}
// ---- Phi ----
#[derive(Debug, Clone)]
pub struct PhiInst { pub dst: ValueId, pub inputs: Vec<(BasicBlockId, ValueId)> }
impl PhiInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::Phi { dst, inputs } => Some(PhiInst { dst: *dst, inputs: inputs.clone() }),
_ => None,
}
}
}
impl InstructionMeta for PhiInst {
fn effects(&self) -> EffectMask { EffectMask::PURE }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { self.inputs.iter().map(|(_, v)| *v).collect() }
}
// ---- NewBox ----
#[derive(Debug, Clone)]
pub struct NewBoxInst {
pub dst: ValueId,
pub args: Vec<ValueId>,
}
impl NewBoxInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::NewBox { dst, args, .. } =>
Some(NewBoxInst { dst: *dst, args: args.clone() }),
_ => None,
}
}
}
impl InstructionMeta for NewBoxInst {
fn effects(&self) -> EffectMask { EffectMask::PURE.add(Effect::Alloc) }
fn dst(&self) -> Option<ValueId> { Some(self.dst) }
fn used(&self) -> Vec<ValueId> { self.args.clone() }
}
// ---- Store ----
#[derive(Debug, Clone, Copy)]
pub struct StoreInst {
pub value: ValueId,
pub ptr: ValueId,
}
impl StoreInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::Store { value, ptr } => Some(StoreInst { value: *value, ptr: *ptr }),
_ => None,
}
}
}
impl InstructionMeta for StoreInst {
fn effects(&self) -> EffectMask { EffectMask::WRITE }
fn dst(&self) -> Option<ValueId> { None }
fn used(&self) -> Vec<ValueId> { vec![self.value, self.ptr] }
}
// ---- ArraySet ----
#[derive(Debug, Clone, Copy)]
pub struct ArraySetInst {
pub array: ValueId,
pub index: ValueId,
pub value: ValueId,
}
impl ArraySetInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::ArraySet { array, index, value } =>
Some(ArraySetInst { array: *array, index: *index, value: *value }),
_ => None,
}
}
}
impl InstructionMeta for ArraySetInst {
fn effects(&self) -> EffectMask { EffectMask::WRITE }
fn dst(&self) -> Option<ValueId> { None }
fn used(&self) -> Vec<ValueId> { vec![self.array, self.index, self.value] }
}
// ---- Return ----
#[derive(Debug, Clone, Copy)]
pub struct ReturnInst { pub value: Option<ValueId> }
impl ReturnInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::Return { value } => Some(ReturnInst { value: *value }),
_ => None,
}
}
}
impl InstructionMeta for ReturnInst {
fn effects(&self) -> EffectMask { EffectMask::PURE }
fn dst(&self) -> Option<ValueId> { None }
fn used(&self) -> Vec<ValueId> { self.value.map(|v| vec![v]).unwrap_or_default() }
}
// ---- Branch ----
#[derive(Debug, Clone, Copy)]
pub struct BranchInst { pub condition: ValueId }
impl BranchInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::Branch { condition, .. } => Some(BranchInst { condition: *condition }),
_ => None,
}
}
}
impl InstructionMeta for BranchInst {
fn effects(&self) -> EffectMask { EffectMask::PURE }
fn dst(&self) -> Option<ValueId> { None }
fn used(&self) -> Vec<ValueId> { vec![self.condition] }
}
// ---- Jump ----
#[derive(Debug, Clone, Copy)]
pub struct JumpInst;
impl JumpInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i { MirInstruction::Jump { .. } => Some(JumpInst), _ => None }
} }
} }
impl InstructionMeta for JumpInst { // ---- Future ops ---- (macro-generated)
fn effects(&self) -> EffectMask { EffectMask::PURE } inst_meta! {
fn dst(&self) -> Option<ValueId> { None } pub struct FutureNewInst { dst: ValueId, value: ValueId }
fn used(&self) -> Vec<ValueId> { Vec::new() } => {
} from_mir = |i| match i { MirInstruction::FutureNew { dst, value } => Some(FutureNewInst { dst: *dst, value: *value }), _ => None };
effects = |_: &Self| EffectMask::PURE.add(Effect::Alloc);
// ---- Print ---- dst = |s: &Self| Some(s.dst);
#[derive(Debug, Clone, Copy)] used = |s: &Self| vec![s.value];
pub struct PrintInst { pub value: ValueId, pub effects_mask: EffectMask }
impl PrintInst {
pub fn from_mir(i: &MirInstruction) -> Option<Self> {
match i {
MirInstruction::Print { value, effects } => Some(PrintInst { value: *value, effects_mask: *effects }),
_ => None,
} }
} }
inst_meta! {
pub struct FutureSetInst { future: ValueId, value: ValueId }
=> {
from_mir = |i| match i { MirInstruction::FutureSet { future, value } => Some(FutureSetInst { future: *future, value: *value }), _ => None };
effects = |_: &Self| EffectMask::WRITE;
dst = |_: &Self| None;
used = |s: &Self| vec![s.future, s.value];
} }
impl InstructionMeta for PrintInst {
fn effects(&self) -> EffectMask { self.effects_mask }
fn dst(&self) -> Option<ValueId> { None }
fn used(&self) -> Vec<ValueId> { vec![self.value] }
} }
inst_meta! {
// ---- Debug ---- pub struct AwaitInst { dst: ValueId, future: ValueId }
#[derive(Debug, Clone, Copy)] => {
pub struct DebugInst { pub value: ValueId } from_mir = |i| match i { MirInstruction::Await { dst, future } => Some(AwaitInst { dst: *dst, future: *future }), _ => None };
effects = |_: &Self| EffectMask::READ.add(Effect::Async);
impl DebugInst { dst = |s: &Self| Some(s.dst);
pub fn from_mir(i: &MirInstruction) -> Option<Self> { used = |s: &Self| vec![s.future];
match i { MirInstruction::Debug { value, .. } => Some(DebugInst { value: *value }), _ => None }
} }
} }
impl InstructionMeta for DebugInst { // ---- UnaryOp ---- (macro-generated)
fn effects(&self) -> EffectMask { EffectMask::PURE.add(Effect::Debug) } inst_meta! {
fn dst(&self) -> Option<ValueId> { None } pub struct UnaryOpInst { dst: ValueId, operand: ValueId }
fn used(&self) -> Vec<ValueId> { vec![self.value] } => {
from_mir = |i| match i { MirInstruction::UnaryOp { dst, operand, .. } => Some(UnaryOpInst { dst: *dst, operand: *operand }), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |s: &Self| Some(s.dst);
used = |s: &Self| vec![s.operand];
}
}
// ---- Compare ---- (macro-generated)
inst_meta! {
pub struct CompareInst { dst: ValueId, lhs: ValueId, rhs: ValueId }
=> {
from_mir = |i| match i { MirInstruction::Compare { dst, lhs, rhs, .. } => Some(CompareInst { dst: *dst, lhs: *lhs, rhs: *rhs }), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |s: &Self| Some(s.dst);
used = |s: &Self| vec![s.lhs, s.rhs];
}
}
// ---- Load ---- (macro-generated)
inst_meta! {
pub struct LoadInst { dst: ValueId, ptr: ValueId }
=> {
from_mir = |i| match i { MirInstruction::Load { dst, ptr } => Some(LoadInst { dst: *dst, ptr: *ptr }), _ => None };
effects = |_: &Self| EffectMask::READ;
dst = |s: &Self| Some(s.dst);
used = |s: &Self| vec![s.ptr];
}
}
// ---- Cast ---- (macro-generated)
inst_meta! {
pub struct CastInst { dst: ValueId, value: ValueId, target_type: MirType }
=> {
from_mir = |i| match i { MirInstruction::Cast { dst, value, target_type } => Some(CastInst { dst: *dst, value: *value, target_type: target_type.clone() }), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |s: &Self| Some(s.dst);
used = |s: &Self| vec![s.value];
}
}
// ---- TypeOp ---- (macro-generated)
inst_meta! {
pub struct TypeOpInst { dst: ValueId, op: MirTypeOpKind, value: ValueId, ty: MirType }
=> {
from_mir = |i| match i { MirInstruction::TypeOp { dst, op, value, ty } => Some(TypeOpInst { dst: *dst, op: *op, value: *value, ty: ty.clone() }), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |s: &Self| Some(s.dst);
used = |s: &Self| vec![s.value];
}
}
// ---- ArrayGet ---- (macro-generated)
inst_meta! {
pub struct ArrayGetInst { dst: ValueId, array: ValueId, index: ValueId }
=> {
from_mir = |i| match i { MirInstruction::ArrayGet { dst, array, index } => Some(ArrayGetInst { dst: *dst, array: *array, index: *index }), _ => None };
effects = |_: &Self| EffectMask::READ;
dst = |s: &Self| Some(s.dst);
used = |s: &Self| vec![s.array, s.index];
}
}
// ---- Phi ---- (macro-generated)
inst_meta! {
pub struct PhiInst { dst: ValueId, inputs: Vec<(BasicBlockId, ValueId)> }
=> {
from_mir = |i| match i { MirInstruction::Phi { dst, inputs } => Some(PhiInst { dst: *dst, inputs: inputs.clone() }), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |s: &Self| Some(s.dst);
used = |s: &Self| s.inputs.iter().map(|(_, v)| *v).collect();
}
}
// ---- NewBox ---- (macro-generated)
inst_meta! {
pub struct NewBoxInst { dst: ValueId, args: Vec<ValueId> }
=> {
from_mir = |i| match i { MirInstruction::NewBox { dst, args, .. } => Some(NewBoxInst { dst: *dst, args: args.clone() }), _ => None };
effects = |_: &Self| EffectMask::PURE.add(Effect::Alloc);
dst = |s: &Self| Some(s.dst);
used = |s: &Self| s.args.clone();
}
}
// ---- Store ---- (macro-generated)
inst_meta! {
pub struct StoreInst { value: ValueId, ptr: ValueId }
=> {
from_mir = |i| match i { MirInstruction::Store { value, ptr } => Some(StoreInst { value: *value, ptr: *ptr }), _ => None };
effects = |_: &Self| EffectMask::WRITE;
dst = |_: &Self| None;
used = |s: &Self| vec![s.value, s.ptr];
}
}
// ---- ArraySet ---- (macro-generated)
inst_meta! {
pub struct ArraySetInst { array: ValueId, index: ValueId, value: ValueId }
=> {
from_mir = |i| match i { MirInstruction::ArraySet { array, index, value } => Some(ArraySetInst { array: *array, index: *index, value: *value }), _ => None };
effects = |_: &Self| EffectMask::WRITE;
dst = |_: &Self| None;
used = |s: &Self| vec![s.array, s.index, s.value];
}
}
// ---- Return ---- (macro-generated)
inst_meta! {
pub struct ReturnInst { value: Option<ValueId> }
=> {
from_mir = |i| match i { MirInstruction::Return { value } => Some(ReturnInst { value: *value }), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |_: &Self| None;
used = |s: &Self| s.value.map(|v| vec![v]).unwrap_or_default();
}
}
// ---- Branch ---- (macro-generated)
inst_meta! {
pub struct BranchInst { condition: ValueId }
=> {
from_mir = |i| match i { MirInstruction::Branch { condition, .. } => Some(BranchInst { condition: *condition }), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |_: &Self| None;
used = |s: &Self| vec![s.condition];
}
}
// ---- Jump ---- (macro-generated)
inst_meta! {
pub struct JumpInst { }
=> {
from_mir = |i| match i { MirInstruction::Jump { .. } => Some(JumpInst {}), _ => None };
effects = |_: &Self| EffectMask::PURE;
dst = |_: &Self| None;
used = |_: &Self| Vec::new();
}
}
// ---- Print ---- (macro-generated)
inst_meta! {
pub struct PrintInst { value: ValueId, effects_mask: EffectMask }
=> {
from_mir = |i| match i { MirInstruction::Print { value, effects } => Some(PrintInst { value: *value, effects_mask: *effects }), _ => None };
effects = |s: &Self| s.effects_mask;
dst = |_: &Self| None;
used = |s: &Self| vec![s.value];
}
}
// ---- Debug ---- (macro-generated)
inst_meta! {
pub struct DebugInst { value: ValueId }
=> {
from_mir = |i| match i { MirInstruction::Debug { value, .. } => Some(DebugInst { value: *value }), _ => None };
effects = |_: &Self| EffectMask::PURE.add(Effect::Debug);
dst = |_: &Self| None;
used = |s: &Self| vec![s.value];
}
} }
// ---- Call-like (dst/used only; effects fallback in MirInstruction) ---- // ---- Call-like (dst/used only; effects fallback in MirInstruction) ----

View File

@ -3,18 +3,88 @@ mod tests {
use crate::box_trait::{IntegerBox, NyashBox, StringBox}; use crate::box_trait::{IntegerBox, NyashBox, StringBox};
use crate::boxes::array::ArrayBox; use crate::boxes::array::ArrayBox;
use crate::boxes::math_box::FloatBox; use crate::boxes::math_box::FloatBox;
use crate::runtime::plugin_loader_unified::PluginHost;
use std::env; use std::env;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
// RAII: environment variable guard (restores on drop)
struct EnvGuard {
key: &'static str,
prev: Option<String>,
}
impl EnvGuard {
fn set(key: &'static str, val: &str) -> Self {
let prev = env::var(key).ok();
env::set_var(key, val);
EnvGuard { key, prev }
}
fn remove(key: &'static str) -> Self {
let prev = env::var(key).ok();
env::remove_var(key);
EnvGuard { key, prev }
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
match &self.prev {
Some(v) => env::set_var(self.key, v),
None => env::remove_var(self.key),
}
}
}
// Helper: read-lock the global plugin host and pass immutable ref to closure
fn with_host<R>(f: impl FnOnce(&PluginHost) -> R) -> R {
let host = crate::runtime::get_global_plugin_host();
let guard = host.read().expect("plugin host RwLock poisoned");
f(&*guard)
}
// ---- Test helpers (invoke wrappers) ----
fn inv_ok(
h: &PluginHost,
box_ty: &str,
method: &str,
id: u32,
args: &[Box<dyn NyashBox>],
) -> Option<Box<dyn NyashBox>> {
h.invoke_instance_method(box_ty, method, id, args)
.expect(&format!("invoke {}::{}", box_ty, method))
}
fn inv_some(
h: &PluginHost,
box_ty: &str,
method: &str,
id: u32,
args: &[Box<dyn NyashBox>],
) -> Box<dyn NyashBox> {
inv_ok(h, box_ty, method, id, args)
.unwrap_or_else(|| panic!("{}::{} returned None", box_ty, method))
}
fn inv_void(
h: &PluginHost,
box_ty: &str,
method: &str,
id: u32,
args: &[Box<dyn NyashBox>],
) {
let _ = h
.invoke_instance_method(box_ty, method, id, args)
.expect(&format!("invoke {}::{}", box_ty, method));
}
fn ensure_host() { fn ensure_host() {
let _ = crate::runtime::init_global_plugin_host("nyash.toml"); let _ = crate::runtime::init_global_plugin_host("nyash.toml");
} }
fn create_plugin_instance(box_type: &str) -> (String, u32, Box<dyn NyashBox>) { fn create_plugin_instance(box_type: &str) -> (String, u32, Box<dyn NyashBox>) {
let host = crate::runtime::get_global_plugin_host(); let bx = with_host(|h| h.create_box(box_type, &[]).expect("create_box"));
let host = host.read().unwrap();
let bx = host.create_box(box_type, &[]).expect("create_box");
// Downcast to PluginBoxV2 to get instance_id // Downcast to PluginBoxV2 to get instance_id
if let Some(p) = bx if let Some(p) = bx
.as_any() .as_any()
@ -30,58 +100,25 @@ mod tests {
#[ignore = "MIR13 parity: MapBox TLV vs TypeBox under unified BoxCall/TypeOp pending"] #[ignore = "MIR13 parity: MapBox TLV vs TypeBox under unified BoxCall/TypeOp pending"]
fn mapbox_get_set_size_tlv_vs_typebox() { fn mapbox_get_set_size_tlv_vs_typebox() {
ensure_host(); ensure_host();
let host = crate::runtime::get_global_plugin_host(); // TLV path: disable typebox (restored automatically)
let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
// TLV path: disable typebox
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("MapBox"); let (bt1, id1, _hold1) = create_plugin_instance("MapBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt1, "set", id1, &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))]);
// set("k", 42) let sz = inv_some(h, &bt1, "size", id1, &[]);
let _ = h let gv = inv_some(h, &bt1, "get", id1, &[Box::new(StringBox::new("k"))]);
.invoke_instance_method(
&bt1,
"set",
id1,
&[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))],
)
.expect("set tlv");
// size()
let sz = h
.invoke_instance_method(&bt1, "size", id1, &[])
.expect("size tlv")
.unwrap();
// get("k")
let gv = h
.invoke_instance_method(&bt1, "get", id1, &[Box::new(StringBox::new("k"))])
.expect("get tlv")
.unwrap();
(sz.to_string_box().value, gv.to_string_box().value) (sz.to_string_box().value, gv.to_string_box().value)
}; });
// TypeBox path: enable typebox // TypeBox path: enable typebox
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("MapBox"); let (bt2, id2, _hold2) = create_plugin_instance("MapBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt2, "set", id2, &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))]);
let _ = h let sz = inv_some(h, &bt2, "size", id2, &[]);
.invoke_instance_method( let gv = inv_some(h, &bt2, "get", id2, &[Box::new(StringBox::new("k"))]);
&bt2,
"set",
id2,
&[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))],
)
.expect("set tb");
let sz = h
.invoke_instance_method(&bt2, "size", id2, &[])
.expect("size tb")
.unwrap();
let gv = h
.invoke_instance_method(&bt2, "get", id2, &[Box::new(StringBox::new("k"))])
.expect("get tb")
.unwrap();
(sz.to_string_box().value, gv.to_string_box().value) (sz.to_string_box().value, gv.to_string_box().value)
}; });
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match"); assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match");
} }
@ -90,53 +127,24 @@ mod tests {
#[ignore = "MIR13 parity: ArrayBox len/get under unified ops pending"] #[ignore = "MIR13 parity: ArrayBox len/get under unified ops pending"]
fn arraybox_set_get_len_tlv_vs_typebox() { fn arraybox_set_get_len_tlv_vs_typebox() {
ensure_host(); ensure_host();
let host = crate::runtime::get_global_plugin_host(); // TLV path (guarded)
// TLV path let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("ArrayBox"); let (bt1, id1, _hold1) = create_plugin_instance("ArrayBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt1, "set", id1, &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))]);
let _ = h let ln = inv_some(h, &bt1, "len", id1, &[]);
.invoke_instance_method( let gv = inv_some(h, &bt1, "get", id1, &[Box::new(IntegerBox::new(0))]);
&bt1,
"set",
id1,
&[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))],
)
.expect("set tlv");
let ln = h
.invoke_instance_method(&bt1, "len", id1, &[])
.expect("len tlv")
.unwrap();
let gv = h
.invoke_instance_method(&bt1, "get", id1, &[Box::new(IntegerBox::new(0))])
.expect("get tlv")
.unwrap();
(ln.to_string_box().value, gv.to_string_box().value) (ln.to_string_box().value, gv.to_string_box().value)
}; });
// TypeBox path // TypeBox path (guarded)
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("ArrayBox"); let (bt2, id2, _hold2) = create_plugin_instance("ArrayBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt2, "set", id2, &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))]);
let _ = h let ln = inv_some(h, &bt2, "length", id2, &[]);
.invoke_instance_method( let gv = inv_some(h, &bt2, "get", id2, &[Box::new(IntegerBox::new(0))]);
&bt2,
"set",
id2,
&[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))],
)
.expect("set tb");
let ln = h
.invoke_instance_method(&bt2, "length", id2, &[])
.expect("len tb")
.unwrap();
let gv = h
.invoke_instance_method(&bt2, "get", id2, &[Box::new(IntegerBox::new(0))])
.expect("get tb")
.unwrap();
(ln.to_string_box().value, gv.to_string_box().value) (ln.to_string_box().value, gv.to_string_box().value)
}; });
assert_eq!( assert_eq!(
out_tlv, out_tb, out_tlv, out_tb,
"TLV vs TypeBox results should match (ArrayBox)" "TLV vs TypeBox results should match (ArrayBox)"
@ -149,36 +157,22 @@ mod tests {
ensure_host(); ensure_host();
let host = crate::runtime::get_global_plugin_host(); let host = crate::runtime::get_global_plugin_host();
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("StringBox"); let (bt1, id1, _hold1) = create_plugin_instance("StringBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap();
// birth with init string: use fromUtf8 via set of arg in create? Current loader birth() no-arg, so concat // birth with init string: use fromUtf8 via set of arg in create? Current loader birth() no-arg, so concat
let _ = h inv_void(h, &bt1, "concat", id1, &[Box::new(StringBox::new("ab"))]);
.invoke_instance_method(&bt1, "concat", id1, &[Box::new(StringBox::new("ab"))]) let ln = inv_some(h, &bt1, "length", id1, &[]);
.expect("concat tlv")
.unwrap();
let ln = h
.invoke_instance_method(&bt1, "length", id1, &[])
.expect("len tlv")
.unwrap();
(ln.to_string_box().value) (ln.to_string_box().value)
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("StringBox"); let (bt2, id2, _hold2) = create_plugin_instance("StringBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt2, "concat", id2, &[Box::new(StringBox::new("ab"))]);
let _ = h let ln = inv_some(h, &bt2, "length", id2, &[]);
.invoke_instance_method(&bt2, "concat", id2, &[Box::new(StringBox::new("ab"))])
.expect("concat tb")
.unwrap();
let ln = h
.invoke_instance_method(&bt2, "length", id2, &[])
.expect("len tb")
.unwrap();
(ln.to_string_box().value) (ln.to_string_box().value)
}; });
assert_eq!( assert_eq!(
out_tlv, out_tb, out_tlv, out_tb,
"TLV vs TypeBox results should match (StringBox)" "TLV vs TypeBox results should match (StringBox)"
@ -191,35 +185,21 @@ mod tests {
ensure_host(); ensure_host();
let host = crate::runtime::get_global_plugin_host(); let host = crate::runtime::get_global_plugin_host();
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("IntegerBox"); let (bt1, id1, _hold1) = create_plugin_instance("IntegerBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt1, "set", id1, &[Box::new(IntegerBox::new(123))]);
let _ = h let gv = inv_some(h, &bt1, "get", id1, &[]);
.invoke_instance_method(&bt1, "set", id1, &[Box::new(IntegerBox::new(123))])
.expect("set tlv")
.unwrap();
let gv = h
.invoke_instance_method(&bt1, "get", id1, &[])
.expect("get tlv")
.unwrap();
gv.to_string_box().value gv.to_string_box().value
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("IntegerBox"); let (bt2, id2, _hold2) = create_plugin_instance("IntegerBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt2, "set", id2, &[Box::new(IntegerBox::new(123))]);
let _ = h let gv = inv_some(h, &bt2, "get", id2, &[]);
.invoke_instance_method(&bt2, "set", id2, &[Box::new(IntegerBox::new(123))])
.expect("set tb")
.unwrap();
let gv = h
.invoke_instance_method(&bt2, "get", id2, &[])
.expect("get tb")
.unwrap();
gv.to_string_box().value gv.to_string_box().value
}; });
assert_eq!( assert_eq!(
out_tlv, out_tb, out_tlv, out_tb,
"TLV vs TypeBox results should match (IntegerBox)" "TLV vs TypeBox results should match (IntegerBox)"
@ -232,25 +212,23 @@ mod tests {
ensure_host(); ensure_host();
let host = crate::runtime::get_global_plugin_host(); let host = crate::runtime::get_global_plugin_host();
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("ConsoleBox"); let (bt1, id1, _hold1) = create_plugin_instance("ConsoleBox");
let out_tlv_is_none = { let out_tlv_is_none = with_host(|h| {
let h = host.read().unwrap();
let rv = h let rv = h
.invoke_instance_method(&bt1, "println", id1, &[Box::new(StringBox::new("hello"))]) .invoke_instance_method(&bt1, "println", id1, &[Box::new(StringBox::new("hello"))])
.expect("println tlv"); .expect("println tlv");
rv.is_none() rv.is_none()
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("ConsoleBox"); let (bt2, id2, _hold2) = create_plugin_instance("ConsoleBox");
let out_tb_is_none = { let out_tb_is_none = with_host(|h| {
let h = host.read().unwrap();
let rv = h let rv = h
.invoke_instance_method(&bt2, "println", id2, &[Box::new(StringBox::new("hello"))]) .invoke_instance_method(&bt2, "println", id2, &[Box::new(StringBox::new("hello"))])
.expect("println tb"); .expect("println tb");
rv.is_none() rv.is_none()
}; });
assert!( assert!(
out_tlv_is_none && out_tb_is_none, out_tlv_is_none && out_tb_is_none,
"println should return void/None in both modes" "println should return void/None in both modes"
@ -264,62 +242,36 @@ mod tests {
let host = crate::runtime::get_global_plugin_host(); let host = crate::runtime::get_global_plugin_host();
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("MathBox"); let (bt1, id1, _hold1) = create_plugin_instance("MathBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap(); let s1 = inv_some(h, &bt1, "sqrt", id1, &[Box::new(IntegerBox::new(9))]);
let s1 = h let s2 = inv_some(h, &bt1, "sin", id1, &[Box::new(IntegerBox::new(0))]);
.invoke_instance_method(&bt1, "sqrt", id1, &[Box::new(IntegerBox::new(9))]) let s3 = inv_some(h, &bt1, "cos", id1, &[Box::new(IntegerBox::new(0))]);
.expect("sqrt tlv") let s4 = inv_some(h, &bt1, "round", id1, &[Box::new(IntegerBox::new(26))]);
.unwrap();
let s2 = h
.invoke_instance_method(&bt1, "sin", id1, &[Box::new(IntegerBox::new(0))])
.expect("sin tlv")
.unwrap();
let s3 = h
.invoke_instance_method(&bt1, "cos", id1, &[Box::new(IntegerBox::new(0))])
.expect("cos tlv")
.unwrap();
let s4 = h
.invoke_instance_method(&bt1, "round", id1, &[Box::new(IntegerBox::new(26))])
.expect("round tlv")
.unwrap();
( (
s1.to_string_box().value, s1.to_string_box().value,
s2.to_string_box().value, s2.to_string_box().value,
s3.to_string_box().value, s3.to_string_box().value,
s4.to_string_box().value, s4.to_string_box().value,
) )
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("MathBox"); let (bt2, id2, _hold2) = create_plugin_instance("MathBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); let s1 = inv_some(h, &bt2, "sqrt", id2, &[Box::new(IntegerBox::new(9))]);
let s1 = h let s2 = inv_some(h, &bt2, "sin", id2, &[Box::new(IntegerBox::new(0))]);
.invoke_instance_method(&bt2, "sqrt", id2, &[Box::new(IntegerBox::new(9))]) let s3 = inv_some(h, &bt2, "cos", id2, &[Box::new(IntegerBox::new(0))]);
.expect("sqrt tb") let s4 = inv_some(h, &bt2, "round", id2, &[Box::new(IntegerBox::new(26))]);
.unwrap();
let s2 = h
.invoke_instance_method(&bt2, "sin", id2, &[Box::new(IntegerBox::new(0))])
.expect("sin tb")
.unwrap();
let s3 = h
.invoke_instance_method(&bt2, "cos", id2, &[Box::new(IntegerBox::new(0))])
.expect("cos tb")
.unwrap();
let s4 = h
.invoke_instance_method(&bt2, "round", id2, &[Box::new(IntegerBox::new(26))])
.expect("round tb")
.unwrap();
( (
s1.to_string_box().value, s1.to_string_box().value,
s2.to_string_box().value, s2.to_string_box().value,
s3.to_string_box().value, s3.to_string_box().value,
s4.to_string_box().value, s4.to_string_box().value,
) )
}; });
assert_eq!( assert_eq!(
out_tlv, out_tb, out_tlv, out_tb,
"TLV vs TypeBox results should match (MathBox)" "TLV vs TypeBox results should match (MathBox)"
@ -341,46 +293,34 @@ mod tests {
}; };
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("EncodingBox"); let (bt1, id1, _hold1) = create_plugin_instance("EncodingBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap(); let b64 = inv_some(
let b64 = h h,
.invoke_instance_method(
&bt1, &bt1,
"base64Encode", "base64Encode",
id1, id1,
&[Box::new(StringBox::new("hi"))], &[Box::new(StringBox::new("hi"))],
) );
.expect("b64 tlv") let hex = inv_some(h, &bt1, "hexEncode", id1, &[Box::new(StringBox::new("hi"))]);
.unwrap();
let hex = h
.invoke_instance_method(&bt1, "hexEncode", id1, &[Box::new(StringBox::new("hi"))])
.expect("hex tlv")
.unwrap();
(b64.to_string_box().value, hex.to_string_box().value) (b64.to_string_box().value, hex.to_string_box().value)
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("EncodingBox"); let (bt2, id2, _hold2) = create_plugin_instance("EncodingBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); let b64 = inv_some(
let b64 = h h,
.invoke_instance_method(
&bt2, &bt2,
"base64Encode", "base64Encode",
id2, id2,
&[Box::new(StringBox::new("hi"))], &[Box::new(StringBox::new("hi"))],
) );
.expect("b64 tb") let hex = inv_some(h, &bt2, "hexEncode", id2, &[Box::new(StringBox::new("hi"))]);
.unwrap();
let hex = h
.invoke_instance_method(&bt2, "hexEncode", id2, &[Box::new(StringBox::new("hi"))])
.expect("hex tb")
.unwrap();
(b64.to_string_box().value, hex.to_string_box().value) (b64.to_string_box().value, hex.to_string_box().value)
}; });
assert_eq!( assert_eq!(
out_tlv, out_tb, out_tlv, out_tb,
"TLV vs TypeBox results should match (EncodingBox)" "TLV vs TypeBox results should match (EncodingBox)"
@ -394,42 +334,24 @@ mod tests {
let host = crate::runtime::get_global_plugin_host(); let host = crate::runtime::get_global_plugin_host();
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("RegexBox"); let (bt1, id1, _hold1) = create_plugin_instance("RegexBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt1, "compile", id1, &[Box::new(StringBox::new("h.+o"))]);
let _ = h let m = inv_some(h, &bt1, "isMatch", id1, &[Box::new(StringBox::new("hello"))]);
.invoke_instance_method(&bt1, "compile", id1, &[Box::new(StringBox::new("h.+o"))]) let f = inv_some(h, &bt1, "find", id1, &[Box::new(StringBox::new("hello"))]);
.expect("compile tlv");
let m = h
.invoke_instance_method(&bt1, "isMatch", id1, &[Box::new(StringBox::new("hello"))])
.expect("isMatch tlv")
.unwrap();
let f = h
.invoke_instance_method(&bt1, "find", id1, &[Box::new(StringBox::new("hello"))])
.expect("find tlv")
.unwrap();
(m.to_string_box().value, f.to_string_box().value) (m.to_string_box().value, f.to_string_box().value)
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("RegexBox"); let (bt2, id2, _hold2) = create_plugin_instance("RegexBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); inv_void(h, &bt2, "compile", id2, &[Box::new(StringBox::new("h.+o"))]);
let _ = h let m = inv_some(h, &bt2, "isMatch", id2, &[Box::new(StringBox::new("hello"))]);
.invoke_instance_method(&bt2, "compile", id2, &[Box::new(StringBox::new("h.+o"))]) let f = inv_some(h, &bt2, "find", id2, &[Box::new(StringBox::new("hello"))]);
.expect("compile tb");
let m = h
.invoke_instance_method(&bt2, "isMatch", id2, &[Box::new(StringBox::new("hello"))])
.expect("isMatch tb")
.unwrap();
let f = h
.invoke_instance_method(&bt2, "find", id2, &[Box::new(StringBox::new("hello"))])
.expect("find tb")
.unwrap();
(m.to_string_box().value, f.to_string_box().value) (m.to_string_box().value, f.to_string_box().value)
}; });
assert_eq!( assert_eq!(
out_tlv, out_tb, out_tlv, out_tb,
"TLV vs TypeBox results should match (RegexBox)" "TLV vs TypeBox results should match (RegexBox)"
@ -443,12 +365,11 @@ mod tests {
let host = crate::runtime::get_global_plugin_host(); let host = crate::runtime::get_global_plugin_host();
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("PathBox"); let (bt1, id1, _hold1) = create_plugin_instance("PathBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap(); let j = inv_some(
let j = h h,
.invoke_instance_method(
&bt1, &bt1,
"join", "join",
id1, id1,
@ -456,51 +377,30 @@ mod tests {
Box::new(StringBox::new("/a/b")), Box::new(StringBox::new("/a/b")),
Box::new(StringBox::new("c.txt")), Box::new(StringBox::new("c.txt")),
], ],
) );
.expect("join tlv") let d = inv_some(h, &bt1, "dirname", id1, &[Box::new(StringBox::new("/a/b/c.txt"))]);
.unwrap(); let b = inv_some(h, &bt1, "basename", id1, &[Box::new(StringBox::new("/a/b/c.txt"))]);
let d = h let n = inv_some(
.invoke_instance_method( h,
&bt1,
"dirname",
id1,
&[Box::new(StringBox::new("/a/b/c.txt"))],
)
.expect("dirname tlv")
.unwrap();
let b = h
.invoke_instance_method(
&bt1,
"basename",
id1,
&[Box::new(StringBox::new("/a/b/c.txt"))],
)
.expect("basename tlv")
.unwrap();
let n = h
.invoke_instance_method(
&bt1, &bt1,
"normalize", "normalize",
id1, id1,
&[Box::new(StringBox::new("/a/./b/../b/c"))], &[Box::new(StringBox::new("/a/./b/../b/c"))],
) );
.expect("normalize tlv")
.unwrap();
( (
j.to_string_box().value, j.to_string_box().value,
d.to_string_box().value, d.to_string_box().value,
b.to_string_box().value, b.to_string_box().value,
n.to_string_box().value, n.to_string_box().value,
) )
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("PathBox"); let (bt2, id2, _hold2) = create_plugin_instance("PathBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); let j = inv_some(
let j = h h,
.invoke_instance_method(
&bt2, &bt2,
"join", "join",
id2, id2,
@ -508,43 +408,23 @@ mod tests {
Box::new(StringBox::new("/a/b")), Box::new(StringBox::new("/a/b")),
Box::new(StringBox::new("c.txt")), Box::new(StringBox::new("c.txt")),
], ],
) );
.expect("join tb") let d = inv_some(h, &bt2, "dirname", id2, &[Box::new(StringBox::new("/a/b/c.txt"))]);
.unwrap(); let b = inv_some(h, &bt2, "basename", id2, &[Box::new(StringBox::new("/a/b/c.txt"))]);
let d = h let n = inv_some(
.invoke_instance_method( h,
&bt2,
"dirname",
id2,
&[Box::new(StringBox::new("/a/b/c.txt"))],
)
.expect("dirname tb")
.unwrap();
let b = h
.invoke_instance_method(
&bt2,
"basename",
id2,
&[Box::new(StringBox::new("/a/b/c.txt"))],
)
.expect("basename tb")
.unwrap();
let n = h
.invoke_instance_method(
&bt2, &bt2,
"normalize", "normalize",
id2, id2,
&[Box::new(StringBox::new("/a/./b/../b/c"))], &[Box::new(StringBox::new("/a/./b/../b/c"))],
) );
.expect("normalize tb")
.unwrap();
( (
j.to_string_box().value, j.to_string_box().value,
d.to_string_box().value, d.to_string_box().value,
b.to_string_box().value, b.to_string_box().value,
n.to_string_box().value, n.to_string_box().value,
) )
}; });
assert_eq!( assert_eq!(
out_tlv, out_tb, out_tlv, out_tb,
"TLV vs TypeBox results should match (PathBox)" "TLV vs TypeBox results should match (PathBox)"
@ -559,54 +439,48 @@ mod tests {
let toml_text = "[package]\nname=\"nyash\"\n[deps]\nregex=\"1\"\n"; let toml_text = "[package]\nname=\"nyash\"\n[deps]\nregex=\"1\"\n";
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("TOMLBox"); let (bt1, id1, _hold1) = create_plugin_instance("TOMLBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap(); inv_void(
let _ = h h,
.invoke_instance_method(&bt1, "parse", id1, &[Box::new(StringBox::new(toml_text))]) &bt1,
.expect("parse tlv") "parse",
.unwrap(); id1,
let name = h &[Box::new(StringBox::new(toml_text))],
.invoke_instance_method( );
let name = inv_some(
h,
&bt1, &bt1,
"get", "get",
id1, id1,
&[Box::new(StringBox::new("package.name"))], &[Box::new(StringBox::new("package.name"))],
) );
.expect("get tlv") let json = inv_some(h, &bt1, "toJson", id1, &[]);
.unwrap();
let json = h
.invoke_instance_method(&bt1, "toJson", id1, &[])
.expect("toJson tlv")
.unwrap();
(name.to_string_box().value, json.to_string_box().value) (name.to_string_box().value, json.to_string_box().value)
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("TOMLBox"); let (bt2, id2, _hold2) = create_plugin_instance("TOMLBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap(); inv_void(
let _ = h h,
.invoke_instance_method(&bt2, "parse", id2, &[Box::new(StringBox::new(toml_text))]) &bt2,
.expect("parse tb") "parse",
.unwrap(); id2,
let name = h &[Box::new(StringBox::new(toml_text))],
.invoke_instance_method( );
let name = inv_some(
h,
&bt2, &bt2,
"get", "get",
id2, id2,
&[Box::new(StringBox::new("package.name"))], &[Box::new(StringBox::new("package.name"))],
) );
.expect("get tb") let json = inv_some(h, &bt2, "toJson", id2, &[]);
.unwrap();
let json = h
.invoke_instance_method(&bt2, "toJson", id2, &[])
.expect("toJson tb")
.unwrap();
(name.to_string_box().value, json.to_string_box().value) (name.to_string_box().value, json.to_string_box().value)
}; });
assert_eq!( assert_eq!(
out_tlv, out_tb, out_tlv, out_tb,
"TLV vs TypeBox results should match (TOMLBox)" "TLV vs TypeBox results should match (TOMLBox)"
@ -620,28 +494,20 @@ mod tests {
let host = crate::runtime::get_global_plugin_host(); let host = crate::runtime::get_global_plugin_host();
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("TimeBox"); let (bt1, id1, _hold1) = create_plugin_instance("TimeBox");
let t_tlv = { let t_tlv = with_host(|h| {
let h = host.read().unwrap(); let v = inv_some(h, &bt1, "now", id1, &[]);
let v = h
.invoke_instance_method(&bt1, "now", id1, &[])
.expect("now tlv")
.unwrap();
v.to_string_box().value.parse::<i64>().unwrap_or(0) v.to_string_box().value.parse::<i64>().unwrap_or(0)
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("TimeBox"); let (bt2, id2, _hold2) = create_plugin_instance("TimeBox");
let t_tb = { let t_tb = with_host(|h| {
let h = host.read().unwrap(); let v = inv_some(h, &bt2, "now", id2, &[]);
let v = h
.invoke_instance_method(&bt2, "now", id2, &[])
.expect("now tb")
.unwrap();
v.to_string_box().value.parse::<i64>().unwrap_or(0) v.to_string_box().value.parse::<i64>().unwrap_or(0)
}; });
let diff = (t_tb - t_tlv).abs(); let diff = (t_tb - t_tlv).abs();
assert!(diff < 5_000, "TimeBox.now difference too large: {}ms", diff); assert!(diff < 5_000, "TimeBox.now difference too large: {}ms", diff);
@ -654,49 +520,31 @@ mod tests {
let host = crate::runtime::get_global_plugin_host(); let host = crate::runtime::get_global_plugin_host();
// TLV path: verify get->inc->get increases by 1 // TLV path: verify get->inc->get increases by 1
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("CounterBox"); let (bt1, id1, _hold1) = create_plugin_instance("CounterBox");
let (a1, b1) = { let (a1, b1) = with_host(|h| {
let h = host.read().unwrap(); let a = inv_some(h, &bt1, "get", id1, &[]);
let a = h inv_void(h, &bt1, "inc", id1, &[]);
.invoke_instance_method(&bt1, "get", id1, &[]) let b = inv_some(h, &bt1, "get", id1, &[]);
.expect("get tlv")
.unwrap();
let _ = h
.invoke_instance_method(&bt1, "inc", id1, &[])
.expect("inc tlv");
let b = h
.invoke_instance_method(&bt1, "get", id1, &[])
.expect("get2 tlv")
.unwrap();
( (
a.to_string_box().value.parse::<i64>().unwrap_or(0), a.to_string_box().value.parse::<i64>().unwrap_or(0),
b.to_string_box().value.parse::<i64>().unwrap_or(0), b.to_string_box().value.parse::<i64>().unwrap_or(0),
) )
}; });
assert_eq!(b1 - a1, 1, "CounterBox TLV should increment by 1"); assert_eq!(b1 - a1, 1, "CounterBox TLV should increment by 1");
// TypeBox path: verify same delta behavior (not comparing absolute values due to singleton) // TypeBox path: verify same delta behavior (not comparing absolute values due to singleton)
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("CounterBox"); let (bt2, id2, _hold2) = create_plugin_instance("CounterBox");
let (a2, b2) = { let (a2, b2) = with_host(|h| {
let h = host.read().unwrap(); let a = inv_some(h, &bt2, "get", id2, &[]);
let a = h inv_void(h, &bt2, "inc", id2, &[]);
.invoke_instance_method(&bt2, "get", id2, &[]) let b = inv_some(h, &bt2, "get", id2, &[]);
.expect("get tb")
.unwrap();
let _ = h
.invoke_instance_method(&bt2, "inc", id2, &[])
.expect("inc tb");
let b = h
.invoke_instance_method(&bt2, "get", id2, &[])
.expect("get2 tb")
.unwrap();
( (
a.to_string_box().value.parse::<i64>().unwrap_or(0), a.to_string_box().value.parse::<i64>().unwrap_or(0),
b.to_string_box().value.parse::<i64>().unwrap_or(0), b.to_string_box().value.parse::<i64>().unwrap_or(0),
) )
}; });
assert_eq!(b2 - a2, 1, "CounterBox TypeBox should increment by 1"); assert_eq!(b2 - a2, 1, "CounterBox TypeBox should increment by 1");
} }
@ -718,8 +566,7 @@ mod tests {
// TLV path // TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1"); env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("FileBox"); let (bt1, id1, _hold1) = create_plugin_instance("FileBox");
let out_tlv = { let out_tlv = with_host(|h| {
let h = host.read().unwrap();
let _ = h let _ = h
.invoke_instance_method( .invoke_instance_method(
&bt1, &bt1,
@ -749,21 +596,17 @@ mod tests {
], ],
) )
.expect("open2 tlv"); .expect("open2 tlv");
let rd = h let rd = inv_some(h, &bt1, "read", id1, &[]);
.invoke_instance_method(&bt1, "read", id1, &[])
.expect("read tlv")
.unwrap();
let _ = h let _ = h
.invoke_instance_method(&bt1, "close", id1, &[]) .invoke_instance_method(&bt1, "close", id1, &[])
.expect("close2 tlv"); .expect("close2 tlv");
rd.to_string_box().value rd.to_string_box().value
}; });
// TypeBox path // TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX"); let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("FileBox"); let (bt2, id2, _hold2) = create_plugin_instance("FileBox");
let out_tb = { let out_tb = with_host(|h| {
let h = host.read().unwrap();
let _ = h let _ = h
.invoke_instance_method( .invoke_instance_method(
&bt2, &bt2,
@ -792,15 +635,12 @@ mod tests {
], ],
) )
.expect("open2 tb"); .expect("open2 tb");
let rd = h let rd = inv_some(h, &bt2, "read", id2, &[]);
.invoke_instance_method(&bt2, "read", id2, &[])
.expect("read tb")
.unwrap();
let _ = h let _ = h
.invoke_instance_method(&bt2, "close", id2, &[]) .invoke_instance_method(&bt2, "close", id2, &[])
.expect("close2 tb"); .expect("close2 tb");
rd.to_string_box().value rd.to_string_box().value
}; });
// Cleanup best-effort // Cleanup best-effort
let _ = fs::remove_file(&path_str); let _ = fs::remove_file(&path_str);
@ -813,7 +653,9 @@ mod tests {
fn rand_id() -> u64 { fn rand_id() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("SystemTime before UNIX_EPOCH");
now.as_micros() as u64 now.as_micros() as u64
} }
} }

View File

@ -359,86 +359,11 @@ impl NyashTokenizer {
self.advance(); self.advance();
return Ok(Token::new(TokenType::PipeForward, start_line, start_column)); return Ok(Token::new(TokenType::PipeForward, start_line, start_column));
} }
Some('<') => { // 単文字トークンはテーブル駆動で処理
Some(c) if self.single_char_token(c).is_some() => {
let tt = self.single_char_token(c).unwrap();
self.advance(); self.advance();
Ok(Token::new(TokenType::LESS, start_line, start_column)) Ok(Token::new(tt, start_line, start_column))
}
Some('>') => {
self.advance();
Ok(Token::new(TokenType::GREATER, start_line, start_column))
}
Some('&') => {
self.advance();
Ok(Token::new(TokenType::BitAnd, start_line, start_column))
}
Some('|') => {
self.advance();
Ok(Token::new(TokenType::BitOr, start_line, start_column))
}
Some('^') => {
self.advance();
Ok(Token::new(TokenType::BitXor, start_line, start_column))
}
Some('=') => {
self.advance();
Ok(Token::new(TokenType::ASSIGN, start_line, start_column))
}
Some('+') => {
self.advance();
Ok(Token::new(TokenType::PLUS, start_line, start_column))
}
Some('-') => {
self.advance();
Ok(Token::new(TokenType::MINUS, start_line, start_column))
}
Some('*') => {
self.advance();
Ok(Token::new(TokenType::MULTIPLY, start_line, start_column))
}
Some('/') => {
self.advance();
Ok(Token::new(TokenType::DIVIDE, start_line, start_column))
}
Some('%') => {
self.advance();
Ok(Token::new(TokenType::MODULO, start_line, start_column))
}
Some('.') => {
self.advance();
Ok(Token::new(TokenType::DOT, start_line, start_column))
}
Some('(') => {
self.advance();
Ok(Token::new(TokenType::LPAREN, start_line, start_column))
}
Some(')') => {
self.advance();
Ok(Token::new(TokenType::RPAREN, start_line, start_column))
}
Some('[') => {
self.advance();
Ok(Token::new(TokenType::LBRACK, start_line, start_column))
}
Some(']') => {
self.advance();
Ok(Token::new(TokenType::RBRACK, start_line, start_column))
}
Some('{') => {
self.advance();
Ok(Token::new(TokenType::LBRACE, start_line, start_column))
}
Some('}') => {
self.advance();
Ok(Token::new(TokenType::RBRACE, start_line, start_column))
}
Some(',') => {
self.advance();
Ok(Token::new(TokenType::COMMA, start_line, start_column))
}
// '?' and ':' are handled earlier (including variants); avoid duplicate arms
Some('\n') => {
self.advance();
Ok(Token::new(TokenType::NEWLINE, start_line, start_column))
} }
Some(c) => Err(TokenizeError::UnexpectedCharacter { Some(c) => Err(TokenizeError::UnexpectedCharacter {
char: c, char: c,
@ -449,6 +374,34 @@ impl NyashTokenizer {
} }
} }
// 単文字トークンのマップ(最長一致系は呼び出し元で処理済み)
fn single_char_token(&self, c: char) -> Option<TokenType> {
// '?' は上位で分岐済み、':' も同様。ここでは純粋な1文字を扱う。
match c {
'<' => Some(TokenType::LESS),
'>' => Some(TokenType::GREATER),
'&' => Some(TokenType::BitAnd),
'|' => Some(TokenType::BitOr),
'^' => Some(TokenType::BitXor),
'=' => Some(TokenType::ASSIGN),
'+' => Some(TokenType::PLUS),
'-' => Some(TokenType::MINUS),
'*' => Some(TokenType::MULTIPLY),
'/' => Some(TokenType::DIVIDE),
'%' => Some(TokenType::MODULO),
'.' => Some(TokenType::DOT),
'(' => Some(TokenType::LPAREN),
')' => Some(TokenType::RPAREN),
'[' => Some(TokenType::LBRACK),
']' => Some(TokenType::RBRACK),
'{' => Some(TokenType::LBRACE),
'}' => Some(TokenType::RBRACE),
',' => Some(TokenType::COMMA),
'\n' => Some(TokenType::NEWLINE),
_ => None,
}
}
/// 文字列リテラルを読み取り /// 文字列リテラルを読み取り
fn read_string(&mut self) -> Result<String, TokenizeError> { fn read_string(&mut self) -> Result<String, TokenizeError> {
let start_line = self.line; let start_line = self.line;

View File

@ -84,7 +84,18 @@ if [[ "${NYASH_LLVM_SKIP_EMIT:-0}" != "1" ]]; then
fi fi
fi fi
fi fi
if [[ "${NYASH_LLVM_EMIT:-obj}" == "exe" ]]; then
echo " emitting EXE via ny-llvmc (crate) ..." >&2
# Ensure NyRT is built (for libnyrt.a)
if [[ ! -f crates/nyrt/target/release/libnyrt.a && "${NYASH_LLVM_SKIP_NYRT_BUILD:-0}" != "1" ]]; then
( cd crates/nyrt && cargo build --release -j 24 >/dev/null )
fi
NYRT_DIR_HINT="${NYASH_LLVM_NYRT:-crates/nyrt/target/release}"
./target/release/ny-llvmc --in "$NYASH_LLVM_MIR_JSON" --out "$OUT" --emit exe --nyrt "$NYRT_DIR_HINT" ${NYASH_LLVM_LIBS:+--libs "$NYASH_LLVM_LIBS"}
echo "✅ Done: $OUT"; echo " (runtime may require nyash.toml and plugins depending on app)"; exit 0
else
./target/release/ny-llvmc --in "$NYASH_LLVM_MIR_JSON" --out "$OBJ" ./target/release/ny-llvmc --in "$NYASH_LLVM_MIR_JSON" --out "$OBJ"
fi
;; ;;
esac esac
if [[ "$COMPILER_MODE" == "harness" ]]; then if [[ "$COMPILER_MODE" == "harness" ]]; then

47
tools/crate_exe_smoke.sh Normal file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]]; then
set -x
fi
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")"/.. && pwd)
cd "$ROOT_DIR"
BIN=./target/release/nyash
NYLL=./target/release/ny-llvmc
if [[ ! -x "$BIN" ]]; then
echo "[build] nyash ..." >&2
cargo build --release >/dev/null
fi
if [[ ! -x "$NYLL" ]]; then
echo "[build] ny-llvmc ..." >&2
cargo build --release -p nyash-llvm-compiler >/dev/null
fi
APP="${1:-apps/tests/ternary_basic.nyash}"
OUT="${2:-tmp/crate_exe_smoke}"
mkdir -p tmp
JSON=tmp/crate_exe_smoke.json
echo "[1/3] Emitting MIR JSON via CLI ..." >&2
"$BIN" --emit-mir-json "$JSON" --backend mir "$APP" >/dev/null
echo "[2/3] Building EXE via ny-llvmc (crate) ..." >&2
NYASH_LLVM_NYRT_DIR=${NYASH_LLVM_NYRT:-target/release}
if [[ ! -f "$NYASH_LLVM_NYRT_DIR/libnyrt.a" ]]; then
( cd crates/nyrt && cargo build --release >/dev/null )
fi
"$NYLL" --in "$JSON" --out "$OUT" --emit exe --nyrt "$NYASH_LLVM_NYRT_DIR" ${NYASH_LLVM_LIBS:+--libs "$NYASH_LLVM_LIBS"}
echo "[3/3] Running EXE ..." >&2
set +e
"$OUT" >/dev/null 2>&1
CODE=$?
set -e
echo "exit=$CODE"
echo "✅ crate_exe_smoke OK: $OUT (exit=$CODE)"
exit 0