smokes: add curated LLVM runner; archive legacy smokes; PHI-off unified across Bridge/Builder; LLVM resolver tracing; minimal Throw lowering; config env getters; dev profile and root cleaner; docs updated; CI workflow runs curated LLVM (PHI-on/off)
This commit is contained in:
14
.github/workflows/smoke.yml
vendored
14
.github/workflows/smoke.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Smoke (Phase 10.10)
|
||||
name: LLVM Curated Smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -20,12 +20,13 @@ on:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
llvm-curated:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
# Disable external plugins to keep CI deterministic
|
||||
NYASH_DISABLE_PLUGINS: '1'
|
||||
NYASH_LLVM_USE_HARNESS: '1'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -44,10 +45,14 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
- name: Run smoke script
|
||||
run: bash tools/smoke_phase_10_10.sh
|
||||
- name: Run curated LLVM smokes (PHI-on)
|
||||
run: bash tools/smokes/curated_llvm.sh
|
||||
|
||||
- name: Run curated LLVM smokes (PHI-off)
|
||||
run: bash tools/smokes/curated_llvm.sh --phi-off
|
||||
|
||||
jit-direct-smoke:
|
||||
if: false # archived: JIT path not maintained in current phase
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -101,6 +106,7 @@ jobs:
|
||||
timeout 15s "$BIN" --jit-direct apps/tests/mir-branch-multi/main.nyash
|
||||
|
||||
smoke-compile-events:
|
||||
if: false # archived: JIT compile-events not maintained in current phase
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
@ -13,6 +13,23 @@ What Changed (today)
|
||||
- CLI `--using` を追加(`--using "ns as Alias"` / `--using '"apps/foo.nyash" as Foo'`)。
|
||||
- フィールドは box 先頭のみルールのリンタを Runner に追加(`NYASH_FIELDS_TOP_STRICT=1` でエラー)。
|
||||
- Syntax Torture スイートの実行正規化(末行比較)。一部テスト本文を Nyash 仕様に合わせて修正。
|
||||
- JSON v0 仕様に Stage‑3 ノード(Break/Continue/Throw/Try)を追記。Parser Stage‑3 設計メモの現状/残課題を更新。
|
||||
- LLVM smoke に Stage‑3 loop サンプル(break/continue + throw/try/catch/finally 付き)を追加(`NYASH_LLVM_STAGE3_SMOKE=1`)。
|
||||
- Bridge (`json_v0`) に Stage‑3 throw/try の実稼働ルートを追加(`NYASH_BRIDGE_THROW_ENABLE=1` / `NYASH_BRIDGE_TRY_ENABLE=1` で MIR Throw/Catch を生成)。
|
||||
|
||||
Decision (Phase‑15 wrap‑up)
|
||||
- MIR13 移行(PHI 非生成): Phase‑15 の締めとして、MIR 生成層(Bridge/Builder)は PHI を生成しない方針に切替。PHI 合成は LLVM 層(llvmlite/Resolver)に集約。
|
||||
- LoopForm は次フェーズ(MIR18)で導入: まずは MIR14 を維持し、次フェーズで `LoopHeader/Enter/Latch` 等の占位命令を追加。現行 Phase‑15 は CFG パターン検知でループ搬送値を合成。
|
||||
- 例外は段階導入: Throw/Catch は現行維持(Bridge ゲートで出力可)。Try/Finally の構造化は将来の TryRegion で検討。
|
||||
|
||||
Next Focus (Throw/Try — LLVM first)
|
||||
- ブリッジ設計: `emit_degraded_throw` の差し替え方針を策定し、JSON v0 `Try` ノード → MIR 変換の仕様を決める(Stage-3 例外モデル)。
|
||||
- MIR Builder/Runtime 調査: Rust VM/PyVM の `ControlFlow::Throw` 経路と既存 TryCatch 降格の挙動を整理。必要に応じて docs と CURRENT_TASK に反映。
|
||||
- PyVM 設計: 例外モデルをどこまで Python 側に実装するか決め、最小テスト計画を用意。
|
||||
- LLVM 実装方針: Throw/Try の MIR 命令を LLVM 側がどう扱うか(panic扱い or fallback)を設計し、smoke 更新案を作る。
|
||||
- テスト計画: JSON フィクスチャと `tools/llvm_smoke.sh` を中心に Stage-3 例外用のスモーク/単体テストを整備。
|
||||
|
||||
※ Cranelift/JIT 系は当面対象外。ビルド時も LLVM のみを有効化(JIT 関連 feature/CI は無視)。
|
||||
|
||||
- llvmlite/AOT(本戦)強化 — コアコレクション配線とエントリ統一
|
||||
- Array/Map の BoxCall を NyRT ハンドルAPIに直結:
|
||||
@ -47,6 +64,7 @@ Quick Next (today)
|
||||
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_smoke.sh`
|
||||
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_bridge_smoke.sh`
|
||||
- Torture(VM中心): `(cd tests/nyash_syntax_torture_20250916 && BACKENDS="vm" NYASH_BIN=../../target/release/nyash bash run_spec_smoke.sh)`
|
||||
- LLVM Stage‑3 smoke (手動): `NYASH_LLVM_STAGE3_SMOKE=1 ./tools/llvm_smoke.sh release`
|
||||
- Runner/Bridge 実行系
|
||||
- `--ny-parser-pipe` は `NYASH_PIPE_USE_PYVM=1` で PyVM に委譲(exit code 判定に統一)。
|
||||
- 自己ホスト JSON 生成は Python MVP を優先、LLVM EXE/インラインVMを段階フォールバック。
|
||||
@ -148,6 +166,17 @@ Recommended Next (short list)
|
||||
- `mod.rs` の残置ヘルパ(usingの候補提示・環境注入ログ)を `pipeline/dispatch` へ集約し、`mod.rs` を最小のオーケストレーションに。
|
||||
- Namespaces Phase‑1(実装着手): BoxIndex 構築・3段階解決・toml aliases・曖昧エラー改善・トレース
|
||||
|
||||
Smoke Policy (Phase‑15)
|
||||
- PyVM: 一部チェックのみ(async/nowait/await/GC/sync は対象外)
|
||||
- LLVM: フル対応(llvmlite harness)。`tools/smokes/curated_llvm.sh [--phi-off]` を利用
|
||||
- JIT: 未整備(JIT向けスモークは `tools/smokes/archive/` に移管)
|
||||
|
||||
MIR13 Plan(Phase‑15 終盤)
|
||||
- Bridge/Builder: PHI を生成しない(受理は維持)。If/Loop の合流は LLVM Resolver に任せる。
|
||||
- llvmlite: Resolver を使い、BB 先頭で PHI 合成。ループは preheader/cond/body の CFG から搬送値を復元(break は exit 側でマージ)。
|
||||
- Smoke: LLVM はまず loop‑only(break/continue)を常時緑化。例外系(throw/try)は IR 降ろし込み整備後に復帰。
|
||||
- 詳細設計: `docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md` に MIR14→MIR13→MIR17 の段階的移行計画を記載。
|
||||
|
||||
Array/Map Literals Plan(Syntax Sugar)
|
||||
- Stage‑1: Array literal `[e1, e2, ...]` を実装(ゲート: `NYASH_SYNTAX_SUGAR_LEVEL=basic|full` または `NYASH_ENABLE_ARRAY_LITERAL=1`)。
|
||||
- Lowering: `new ArrayBox()` → 各要素を評価 → `.push(elem)` を左から右に順に発行 → 最後に配列値を返す。
|
||||
|
||||
BIN
app_stage3_loop
Normal file
BIN
app_stage3_loop
Normal file
Binary file not shown.
26
apps/tests/llvm_stage3_break_continue.nyash
Normal file
26
apps/tests/llvm_stage3_break_continue.nyash
Normal file
@ -0,0 +1,26 @@
|
||||
// Stage-3 regression smoke: break/continue executes once; with bridge throw/try flags enabled the catch sets 42 and finally runs.
|
||||
static box Main {
|
||||
main() {
|
||||
local break_counter = 0
|
||||
loop (true) {
|
||||
break_counter = break_counter + 1
|
||||
if break_counter == 3 {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
local caught = 0
|
||||
local finally_flag = 0
|
||||
try {
|
||||
throw 42
|
||||
} catch (Error e) {
|
||||
caught = 42
|
||||
} finally {
|
||||
finally_flag = 1
|
||||
}
|
||||
|
||||
print("Result: " + (break_counter + caught + finally_flag))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
14
apps/tests/llvm_stage3_loop_only.nyash
Normal file
14
apps/tests/llvm_stage3_loop_only.nyash
Normal file
@ -0,0 +1,14 @@
|
||||
// Stage-3 smoke (LLVM): loop control only (break/continue) — expected Result: 3
|
||||
static box Main {
|
||||
main() {
|
||||
local counter = 0
|
||||
loop (true) {
|
||||
counter = counter + 1
|
||||
if counter == 3 { break }
|
||||
continue
|
||||
}
|
||||
print("Result: " + counter)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
316
build.rs
316
build.rs
@ -40,7 +40,10 @@ type_rules = [
|
||||
]
|
||||
"#;
|
||||
fs::write(&grammar_file, minimal).expect("write minimal unified-grammar.toml");
|
||||
println!("cargo:warning=Created minimal grammar at {}", grammar_file.display());
|
||||
println!(
|
||||
"cargo:warning=Created minimal grammar at {}",
|
||||
grammar_file.display()
|
||||
);
|
||||
}
|
||||
|
||||
// Read and very light parse: collect
|
||||
@ -69,7 +72,10 @@ type_rules = [
|
||||
for line in content.lines() {
|
||||
let s = line.trim();
|
||||
if s.starts_with("[keywords.") && s.ends_with("]") {
|
||||
let name = s.trim_start_matches("[keywords.").trim_end_matches("]").to_string();
|
||||
let name = s
|
||||
.trim_start_matches("[keywords.")
|
||||
.trim_end_matches("]")
|
||||
.to_string();
|
||||
current_key = Some(name);
|
||||
in_operators_add = false;
|
||||
in_operators_sub = false;
|
||||
@ -77,25 +83,66 @@ type_rules = [
|
||||
in_operators_div = false;
|
||||
continue;
|
||||
}
|
||||
if s == "[operators.add]" { current_key = None; in_operators_add = true; in_operators_sub=false; in_operators_mul=false; in_operators_div=false; in_type_rules = false; continue; }
|
||||
if s == "[operators.sub]" { current_key = None; in_operators_add = false; in_operators_sub=true; in_operators_mul=false; in_operators_div=false; in_type_rules = false; continue; }
|
||||
if s == "[operators.mul]" { current_key = None; in_operators_add = false; in_operators_sub=false; in_operators_mul=true; in_operators_div=false; in_type_rules = false; continue; }
|
||||
if s == "[operators.div]" { current_key = None; in_operators_add = false; in_operators_sub=false; in_operators_mul=false; in_operators_div=true; in_type_rules = false; continue; }
|
||||
if s == "[operators.add]" {
|
||||
current_key = None;
|
||||
in_operators_add = true;
|
||||
in_operators_sub = false;
|
||||
in_operators_mul = false;
|
||||
in_operators_div = false;
|
||||
in_type_rules = false;
|
||||
continue;
|
||||
}
|
||||
if s == "[operators.sub]" {
|
||||
current_key = None;
|
||||
in_operators_add = false;
|
||||
in_operators_sub = true;
|
||||
in_operators_mul = false;
|
||||
in_operators_div = false;
|
||||
in_type_rules = false;
|
||||
continue;
|
||||
}
|
||||
if s == "[operators.mul]" {
|
||||
current_key = None;
|
||||
in_operators_add = false;
|
||||
in_operators_sub = false;
|
||||
in_operators_mul = true;
|
||||
in_operators_div = false;
|
||||
in_type_rules = false;
|
||||
continue;
|
||||
}
|
||||
if s == "[operators.div]" {
|
||||
current_key = None;
|
||||
in_operators_add = false;
|
||||
in_operators_sub = false;
|
||||
in_operators_mul = false;
|
||||
in_operators_div = true;
|
||||
in_type_rules = false;
|
||||
continue;
|
||||
}
|
||||
if let Some(ref key) = current_key {
|
||||
if let Some(rest) = s.strip_prefix("token") {
|
||||
if let Some(eq) = rest.find('=') {
|
||||
let val = rest[eq+1..].trim().trim_matches('"').to_string();
|
||||
let val = rest[eq + 1..].trim().trim_matches('"').to_string();
|
||||
entries.push((key.clone(), val));
|
||||
}
|
||||
}
|
||||
}
|
||||
if in_operators_add || in_operators_sub || in_operators_mul || in_operators_div {
|
||||
if s.starts_with("type_rules") && s.contains('[') { in_type_rules = true; continue; }
|
||||
if s.starts_with("type_rules") && s.contains('[') {
|
||||
in_type_rules = true;
|
||||
continue;
|
||||
}
|
||||
if in_type_rules {
|
||||
if s.starts_with(']') { in_type_rules = false; continue; }
|
||||
if s.starts_with(']') {
|
||||
in_type_rules = false;
|
||||
continue;
|
||||
}
|
||||
// Expect lines like: { left = "String", right = "String", result = "String", action = "concat" },
|
||||
if s.starts_with('{') && s.ends_with("},") || s.ends_with('}') {
|
||||
let inner = s.trim_start_matches('{').trim_end_matches('}').trim_end_matches(',');
|
||||
let inner = s
|
||||
.trim_start_matches('{')
|
||||
.trim_end_matches('}')
|
||||
.trim_end_matches(',');
|
||||
let mut left = String::new();
|
||||
let mut right = String::new();
|
||||
let mut result = String::new();
|
||||
@ -104,7 +151,7 @@ type_rules = [
|
||||
let kv = part.trim();
|
||||
if let Some(eq) = kv.find('=') {
|
||||
let key = kv[..eq].trim();
|
||||
let val = kv[eq+1..].trim().trim_matches('"').to_string();
|
||||
let val = kv[eq + 1..].trim().trim_matches('"').to_string();
|
||||
match key {
|
||||
"left" => left = val,
|
||||
"right" => right = val,
|
||||
@ -114,21 +161,35 @@ type_rules = [
|
||||
}
|
||||
}
|
||||
}
|
||||
if !left.is_empty() && !right.is_empty() && !result.is_empty() && !action.is_empty() {
|
||||
if in_operators_add { add_rules.push((left, right, result, action)); }
|
||||
else if in_operators_sub { sub_rules.push((left, right, result, action)); }
|
||||
else if in_operators_mul { mul_rules.push((left, right, result, action)); }
|
||||
else if in_operators_div { div_rules.push((left, right, result, action)); }
|
||||
if !left.is_empty()
|
||||
&& !right.is_empty()
|
||||
&& !result.is_empty()
|
||||
&& !action.is_empty()
|
||||
{
|
||||
if in_operators_add {
|
||||
add_rules.push((left, right, result, action));
|
||||
} else if in_operators_sub {
|
||||
sub_rules.push((left, right, result, action));
|
||||
} else if in_operators_mul {
|
||||
mul_rules.push((left, right, result, action));
|
||||
} else if in_operators_div {
|
||||
div_rules.push((left, right, result, action));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(rest) = s.strip_prefix("coercion_strategy") {
|
||||
if let Some(eq) = rest.find('=') {
|
||||
let val = rest[eq+1..].trim().trim_matches('"').to_string();
|
||||
if in_operators_add { add_coercion = Some(val.clone()); }
|
||||
else if in_operators_sub { sub_coercion = Some(val.clone()); }
|
||||
else if in_operators_mul { mul_coercion = Some(val.clone()); }
|
||||
else if in_operators_div { div_coercion = Some(val.clone()); }
|
||||
let val = rest[eq + 1..].trim().trim_matches('"').to_string();
|
||||
if in_operators_add {
|
||||
add_coercion = Some(val.clone());
|
||||
} else if in_operators_sub {
|
||||
sub_coercion = Some(val.clone());
|
||||
} else if in_operators_mul {
|
||||
mul_coercion = Some(val.clone());
|
||||
} else if in_operators_div {
|
||||
div_coercion = Some(val.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,27 +197,102 @@ type_rules = [
|
||||
|
||||
// Default rules if none present in TOML (keep codegen deterministic)
|
||||
if add_rules.is_empty() {
|
||||
add_rules.push(("String".into(), "String".into(), "String".into(), "concat".into()));
|
||||
add_rules.push(("String".into(), "Integer".into(), "String".into(), "concat".into()));
|
||||
add_rules.push(("Integer".into(), "String".into(), "String".into(), "concat".into()));
|
||||
add_rules.push(("String".into(), "Bool".into(), "String".into(), "concat".into()));
|
||||
add_rules.push(("Bool".into(), "String".into(), "String".into(), "concat".into()));
|
||||
add_rules.push(("String".into(), "Other".into(), "String".into(), "concat".into()));
|
||||
add_rules.push(("Other".into(), "String".into(), "String".into(), "concat".into()));
|
||||
add_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "add_i64".into()));
|
||||
add_rules.push(("Float".into(), "Float".into(), "Float".into(), "add_f64".into()));
|
||||
add_rules.push((
|
||||
"String".into(),
|
||||
"String".into(),
|
||||
"String".into(),
|
||||
"concat".into(),
|
||||
));
|
||||
add_rules.push((
|
||||
"String".into(),
|
||||
"Integer".into(),
|
||||
"String".into(),
|
||||
"concat".into(),
|
||||
));
|
||||
add_rules.push((
|
||||
"Integer".into(),
|
||||
"String".into(),
|
||||
"String".into(),
|
||||
"concat".into(),
|
||||
));
|
||||
add_rules.push((
|
||||
"String".into(),
|
||||
"Bool".into(),
|
||||
"String".into(),
|
||||
"concat".into(),
|
||||
));
|
||||
add_rules.push((
|
||||
"Bool".into(),
|
||||
"String".into(),
|
||||
"String".into(),
|
||||
"concat".into(),
|
||||
));
|
||||
add_rules.push((
|
||||
"String".into(),
|
||||
"Other".into(),
|
||||
"String".into(),
|
||||
"concat".into(),
|
||||
));
|
||||
add_rules.push((
|
||||
"Other".into(),
|
||||
"String".into(),
|
||||
"String".into(),
|
||||
"concat".into(),
|
||||
));
|
||||
add_rules.push((
|
||||
"Integer".into(),
|
||||
"Integer".into(),
|
||||
"Integer".into(),
|
||||
"add_i64".into(),
|
||||
));
|
||||
add_rules.push((
|
||||
"Float".into(),
|
||||
"Float".into(),
|
||||
"Float".into(),
|
||||
"add_f64".into(),
|
||||
));
|
||||
}
|
||||
if sub_rules.is_empty() {
|
||||
sub_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "sub_i64".into()));
|
||||
sub_rules.push(("Float".into(), "Float".into(), "Float".into(), "sub_f64".into()));
|
||||
sub_rules.push((
|
||||
"Integer".into(),
|
||||
"Integer".into(),
|
||||
"Integer".into(),
|
||||
"sub_i64".into(),
|
||||
));
|
||||
sub_rules.push((
|
||||
"Float".into(),
|
||||
"Float".into(),
|
||||
"Float".into(),
|
||||
"sub_f64".into(),
|
||||
));
|
||||
}
|
||||
if mul_rules.is_empty() {
|
||||
mul_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "mul_i64".into()));
|
||||
mul_rules.push(("Float".into(), "Float".into(), "Float".into(), "mul_f64".into()));
|
||||
mul_rules.push((
|
||||
"Integer".into(),
|
||||
"Integer".into(),
|
||||
"Integer".into(),
|
||||
"mul_i64".into(),
|
||||
));
|
||||
mul_rules.push((
|
||||
"Float".into(),
|
||||
"Float".into(),
|
||||
"Float".into(),
|
||||
"mul_f64".into(),
|
||||
));
|
||||
}
|
||||
if div_rules.is_empty() {
|
||||
div_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "div_i64".into()));
|
||||
div_rules.push(("Float".into(), "Float".into(), "Float".into(), "div_f64".into()));
|
||||
div_rules.push((
|
||||
"Integer".into(),
|
||||
"Integer".into(),
|
||||
"Integer".into(),
|
||||
"div_i64".into(),
|
||||
));
|
||||
div_rules.push((
|
||||
"Float".into(),
|
||||
"Float".into(),
|
||||
"Float".into(),
|
||||
"div_f64".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Generate Rust code
|
||||
@ -171,32 +307,56 @@ type_rules = [
|
||||
let sub_coercion_val = sub_coercion.unwrap_or_else(|| "numeric_only".to_string());
|
||||
let mul_coercion_val = mul_coercion.unwrap_or_else(|| "numeric_only".to_string());
|
||||
let div_coercion_val = div_coercion.unwrap_or_else(|| "numeric_only".to_string());
|
||||
code.push_str(&format!("\npub static OPERATORS_ADD_COERCION: &str = \"{}\";\n", add_coercion_val));
|
||||
code.push_str(&format!("pub static OPERATORS_SUB_COERCION: &str = \"{}\";\n", sub_coercion_val));
|
||||
code.push_str(&format!("pub static OPERATORS_MUL_COERCION: &str = \"{}\";\n", mul_coercion_val));
|
||||
code.push_str(&format!("pub static OPERATORS_DIV_COERCION: &str = \"{}\";\n", div_coercion_val));
|
||||
code.push_str(&format!(
|
||||
"\npub static OPERATORS_ADD_COERCION: &str = \"{}\";\n",
|
||||
add_coercion_val
|
||||
));
|
||||
code.push_str(&format!(
|
||||
"pub static OPERATORS_SUB_COERCION: &str = \"{}\";\n",
|
||||
sub_coercion_val
|
||||
));
|
||||
code.push_str(&format!(
|
||||
"pub static OPERATORS_MUL_COERCION: &str = \"{}\";\n",
|
||||
mul_coercion_val
|
||||
));
|
||||
code.push_str(&format!(
|
||||
"pub static OPERATORS_DIV_COERCION: &str = \"{}\";\n",
|
||||
div_coercion_val
|
||||
));
|
||||
// Emit add rules
|
||||
code.push_str("pub static OPERATORS_ADD_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
||||
for (l, r, res, act) in &add_rules {
|
||||
code.push_str(&format!(" (\"{}\", \"{}\", \"{}\", \"{}\"),\n", l, r, res, act));
|
||||
code.push_str(&format!(
|
||||
" (\"{}\", \"{}\", \"{}\", \"{}\"),\n",
|
||||
l, r, res, act
|
||||
));
|
||||
}
|
||||
code.push_str("];");
|
||||
// Emit sub rules
|
||||
code.push_str("\npub static OPERATORS_SUB_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
||||
for (l, r, res, act) in &sub_rules {
|
||||
code.push_str(&format!(" (\"{}\", \"{}\", \"{}\", \"{}\"),\n", l, r, res, act));
|
||||
code.push_str(&format!(
|
||||
" (\"{}\", \"{}\", \"{}\", \"{}\"),\n",
|
||||
l, r, res, act
|
||||
));
|
||||
}
|
||||
code.push_str("];");
|
||||
// Emit mul rules
|
||||
code.push_str("\npub static OPERATORS_MUL_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
||||
for (l, r, res, act) in &mul_rules {
|
||||
code.push_str(&format!(" (\"{}\", \"{}\", \"{}\", \"{}\"),\n", l, r, res, act));
|
||||
code.push_str(&format!(
|
||||
" (\"{}\", \"{}\", \"{}\", \"{}\"),\n",
|
||||
l, r, res, act
|
||||
));
|
||||
}
|
||||
code.push_str("];");
|
||||
// Emit div rules
|
||||
code.push_str("\npub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
||||
for (l, r, res, act) in &div_rules {
|
||||
code.push_str(&format!(" (\"{}\", \"{}\", \"{}\", \"{}\"),\n", l, r, res, act));
|
||||
code.push_str(&format!(
|
||||
" (\"{}\", \"{}\", \"{}\", \"{}\"),\n",
|
||||
l, r, res, act
|
||||
));
|
||||
}
|
||||
code.push_str("];");
|
||||
code.push_str(
|
||||
@ -207,7 +367,8 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
||||
}
|
||||
None
|
||||
}
|
||||
"#);
|
||||
"#,
|
||||
);
|
||||
|
||||
// --- Naive parse for syntax rules (statements/expressions) ---
|
||||
let mut syntax_statements: Vec<String> = Vec::new();
|
||||
@ -216,24 +377,43 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
||||
let mut in_syntax_expressions = false;
|
||||
for line in content.lines() {
|
||||
let s = line.trim();
|
||||
if s == "[syntax.statements]" { in_syntax_statements = true; in_syntax_expressions = false; continue; }
|
||||
if s == "[syntax.expressions]" { in_syntax_statements = false; in_syntax_expressions = true; continue; }
|
||||
if s.starts_with('[') { in_syntax_statements = false; in_syntax_expressions = false; }
|
||||
if s == "[syntax.statements]" {
|
||||
in_syntax_statements = true;
|
||||
in_syntax_expressions = false;
|
||||
continue;
|
||||
}
|
||||
if s == "[syntax.expressions]" {
|
||||
in_syntax_statements = false;
|
||||
in_syntax_expressions = true;
|
||||
continue;
|
||||
}
|
||||
if s.starts_with('[') {
|
||||
in_syntax_statements = false;
|
||||
in_syntax_expressions = false;
|
||||
}
|
||||
if in_syntax_statements {
|
||||
if let Some(rest) = s.strip_prefix("allow") {
|
||||
if let Some(eq) = rest.find('=') { let arr = rest[eq+1..].trim();
|
||||
if let Some(eq) = rest.find('=') {
|
||||
let arr = rest[eq + 1..].trim();
|
||||
// Expect [ "if", "loop", ... ] possibly spanning multiple lines; simple split for this snapshot
|
||||
for part in arr.trim_matches(&['[',']'][..]).split(',') {
|
||||
let v = part.trim().trim_matches('"'); if !v.is_empty() { syntax_statements.push(v.to_string()); }
|
||||
for part in arr.trim_matches(&['[', ']'][..]).split(',') {
|
||||
let v = part.trim().trim_matches('"');
|
||||
if !v.is_empty() {
|
||||
syntax_statements.push(v.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if in_syntax_expressions {
|
||||
if let Some(rest) = s.strip_prefix("allow_binops") {
|
||||
if let Some(eq) = rest.find('=') { let arr = rest[eq+1..].trim();
|
||||
for part in arr.trim_matches(&['[',']'][..]).split(',') {
|
||||
let v = part.trim().trim_matches('"'); if !v.is_empty() { syntax_binops.push(v.to_string()); }
|
||||
if let Some(eq) = rest.find('=') {
|
||||
let arr = rest[eq + 1..].trim();
|
||||
for part in arr.trim_matches(&['[', ']'][..]).split(',') {
|
||||
let v = part.trim().trim_matches('"');
|
||||
if !v.is_empty() {
|
||||
syntax_binops.push(v.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,9 +421,23 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
||||
}
|
||||
if syntax_statements.is_empty() {
|
||||
syntax_statements = vec![
|
||||
"box".into(), "global".into(), "function".into(), "static".into(),
|
||||
"if".into(), "loop".into(), "break".into(), "return".into(), "print".into(),
|
||||
"nowait".into(), "include".into(), "local".into(), "outbox".into(), "try".into(), "throw".into(), "using".into(), "from".into()
|
||||
"box".into(),
|
||||
"global".into(),
|
||||
"function".into(),
|
||||
"static".into(),
|
||||
"if".into(),
|
||||
"loop".into(),
|
||||
"break".into(),
|
||||
"return".into(),
|
||||
"print".into(),
|
||||
"nowait".into(),
|
||||
"include".into(),
|
||||
"local".into(),
|
||||
"outbox".into(),
|
||||
"try".into(),
|
||||
"throw".into(),
|
||||
"using".into(),
|
||||
"from".into(),
|
||||
];
|
||||
}
|
||||
if syntax_binops.is_empty() {
|
||||
@ -251,10 +445,14 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
||||
}
|
||||
// Emit syntax arrays
|
||||
code.push_str("\npub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[\n");
|
||||
for k in &syntax_statements { code.push_str(&format!(" \"{}\",\n", k)); }
|
||||
for k in &syntax_statements {
|
||||
code.push_str(&format!(" \"{}\",\n", k));
|
||||
}
|
||||
code.push_str("];");
|
||||
code.push_str("\npub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[\n");
|
||||
for k in &syntax_binops { code.push_str(&format!(" \"{}\",\n", k)); }
|
||||
for k in &syntax_binops {
|
||||
code.push_str(&format!(" \"{}\",\n", k));
|
||||
}
|
||||
code.push_str("];");
|
||||
|
||||
fs::write(&out_file, code).expect("write generated.rs");
|
||||
|
||||
@ -6,4 +6,3 @@
|
||||
pub fn version() -> &'static str {
|
||||
"0.1.0-dev"
|
||||
}
|
||||
|
||||
|
||||
@ -2,4 +2,3 @@ fn main() {
|
||||
env_logger::init();
|
||||
println!("nyash-next: workspace skeleton is ready.");
|
||||
}
|
||||
|
||||
|
||||
@ -111,14 +111,26 @@ pub extern "C" fn nyash_string_substring_hii_export(h: i64, start: i64, end: i64
|
||||
return 0;
|
||||
}
|
||||
let s = if let Some(obj) = handles::get(h as u64) {
|
||||
if let Some(sb) = obj.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
|
||||
} else { String::new() };
|
||||
if let Some(sb) = obj.as_any().downcast_ref::<StringBox>() {
|
||||
sb.value.clone()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let n = s.len() as i64;
|
||||
let mut st = if start < 0 { 0 } else { start };
|
||||
let mut en = if end < 0 { 0 } else { end };
|
||||
if st > n { st = n; }
|
||||
if en > n { en = n; }
|
||||
if en < st { std::mem::swap(&mut st, &mut en); }
|
||||
if st > n {
|
||||
st = n;
|
||||
}
|
||||
if en > n {
|
||||
en = n;
|
||||
}
|
||||
if en < st {
|
||||
std::mem::swap(&mut st, &mut en);
|
||||
}
|
||||
let (st_u, en_u) = (st as usize, en as usize);
|
||||
let sub = s.get(st_u.min(s.len())..en_u.min(s.len())).unwrap_or("");
|
||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(StringBox::new(sub.to_string()));
|
||||
@ -133,16 +145,38 @@ pub extern "C" fn nyash_string_lastindexof_hh_export(h: i64, n: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::StringBox, jit::rt::handles};
|
||||
let hay = if h > 0 {
|
||||
if let Some(o) = handles::get(h as u64) {
|
||||
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
|
||||
} else { String::new() }
|
||||
} else { String::new() };
|
||||
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() {
|
||||
sb.value.clone()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let nee = if n > 0 {
|
||||
if let Some(o) = handles::get(n as u64) {
|
||||
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
|
||||
} else { String::new() }
|
||||
} else { String::new() };
|
||||
if nee.is_empty() { return hay.len() as i64; }
|
||||
if let Some(pos) = hay.rfind(&nee) { pos as i64 } else { -1 }
|
||||
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() {
|
||||
sb.value.clone()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
if nee.is_empty() {
|
||||
return hay.len() as i64;
|
||||
}
|
||||
if let Some(pos) = hay.rfind(&nee) {
|
||||
pos as i64
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
// box.from_i8_string(ptr) -> handle
|
||||
@ -181,7 +215,10 @@ pub extern "C" fn nyash_box_from_f64(val: f64) -> i64 {
|
||||
// Helper: build an IntegerBox and return a handle
|
||||
#[export_name = "nyash.box.from_i64"]
|
||||
pub extern "C" fn nyash_box_from_i64(val: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
|
||||
use nyash_rust::{
|
||||
box_trait::{IntegerBox, NyashBox},
|
||||
jit::rt::handles,
|
||||
};
|
||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(IntegerBox::new(val));
|
||||
handles::to_handle(arc) as i64
|
||||
}
|
||||
@ -545,8 +582,6 @@ pub extern "C" fn nyash_console_birth_h_export() -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ArrayBox birth shim for AOT/JIT handle-based creation
|
||||
#[export_name = "nyash.array.birth_h"]
|
||||
pub extern "C" fn nyash_array_birth_h_export() -> i64 {
|
||||
|
||||
@ -9,28 +9,23 @@ pub extern "C" fn nyash_box_birth_h_export(type_id: i64) -> i64 {
|
||||
}
|
||||
let tid = type_id as u32;
|
||||
// Map type_id back to type name
|
||||
let name_opt = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
||||
.and_then(|m| m.into_iter().find(|(_k, v)| *v == tid).map(|(k, _v)| k));
|
||||
if let Some(box_type) = name_opt {
|
||||
if let Some(meta) = nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(tid) {
|
||||
if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() {
|
||||
if let Ok(b) = host_g.create_box(&box_type, &[]) {
|
||||
if let Ok(b) = host_g.create_box(&meta.box_type, &[]) {
|
||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
||||
std::sync::Arc::from(b);
|
||||
let h = nyash_rust::jit::rt::handles::to_handle(arc);
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!(
|
||||
"nyrt: birth_h {} (type_id={}) -> handle={}",
|
||||
box_type, tid, h
|
||||
meta.box_type, meta.type_id, h
|
||||
);
|
||||
}
|
||||
return h as i64;
|
||||
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
"nyrt: birth_h {} (type_id={}) FAILED: create_box",
|
||||
box_type, tid
|
||||
meta.box_type, tid
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -47,33 +42,19 @@ pub extern "C" fn nyash_box_birth_i64_export(type_id: i64, argc: i64, a1: i64, a
|
||||
if type_id <= 0 {
|
||||
return 0;
|
||||
}
|
||||
let mut invoke: Option<
|
||||
unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32,
|
||||
> = None;
|
||||
// Resolve invoke_fn via temporary instance
|
||||
let box_type_name = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
||||
.and_then(|m| {
|
||||
m.into_iter()
|
||||
.find(|(_k, v)| *v == (type_id as u32))
|
||||
.map(|(k, _v)| k)
|
||||
})
|
||||
.unwrap_or_else(|| "PluginBox".to_string());
|
||||
if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() {
|
||||
if let Ok(b) = host_g.create_box(&box_type_name, &[]) {
|
||||
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
|
||||
invoke = Some(p.inner.invoke_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
if invoke.is_none() {
|
||||
// Resolve invoke_fn via loader metadata
|
||||
let meta = if let Some(meta) =
|
||||
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(type_id as u32)
|
||||
{
|
||||
meta
|
||||
} else {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("nyrt: birth_i64 (type_id={}) FAILED: no invoke", type_id);
|
||||
eprintln!("nyrt: birth_i64 (type_id={}) FAILED: type map", type_id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
let box_type_name = meta.box_type.clone();
|
||||
let invoke_fn = meta.invoke_fn;
|
||||
let method_id: u32 = 0; // birth
|
||||
let instance_id: u32 = 0; // static
|
||||
// Build TLV args
|
||||
@ -186,7 +167,7 @@ pub extern "C" fn nyash_box_birth_i64_export(type_id: i64, argc: i64, a1: i64, a
|
||||
let mut out = vec![0u8; 1024];
|
||||
let mut out_len: usize = out.len();
|
||||
let rc = unsafe {
|
||||
invoke.unwrap()(
|
||||
invoke_fn(
|
||||
type_id as u32,
|
||||
method_id,
|
||||
instance_id,
|
||||
@ -219,7 +200,7 @@ pub extern "C" fn nyash_box_birth_i64_export(type_id: i64, argc: i64, a1: i64, a
|
||||
box_type_name.clone(),
|
||||
r_type,
|
||||
r_inst,
|
||||
invoke.unwrap(),
|
||||
invoke_fn,
|
||||
);
|
||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> = std::sync::Arc::new(pb);
|
||||
let h = nyash_rust::jit::rt::handles::to_handle(arc);
|
||||
|
||||
@ -197,22 +197,20 @@ pub extern "C" fn nyash_future_spawn_method_h(
|
||||
let r_type = u32::from_le_bytes(t);
|
||||
let r_inst = u32::from_le_bytes(i);
|
||||
// Map type_id -> box type name (best-effort)
|
||||
let box_type_name =
|
||||
nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host(
|
||||
)
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
||||
.and_then(|m| {
|
||||
m.into_iter().find(|(_k, v)| *v == r_type).map(|(k, _v)| k)
|
||||
})
|
||||
.unwrap_or_else(|| "PluginBox".to_string());
|
||||
let meta_opt =
|
||||
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
|
||||
let (box_type_name, invoke_ptr, fini_id) = if let Some(meta) = meta_opt
|
||||
{
|
||||
(meta.box_type.clone(), meta.invoke_fn, meta.fini_method_id)
|
||||
} else {
|
||||
("PluginBox".to_string(), inv, None)
|
||||
};
|
||||
let pb = nyash_rust::runtime::plugin_loader_v2::construct_plugin_box(
|
||||
box_type_name,
|
||||
r_type,
|
||||
inv,
|
||||
invoke_ptr,
|
||||
r_inst,
|
||||
None,
|
||||
fini_id,
|
||||
);
|
||||
fut_box.set_result(Box::new(pb));
|
||||
return;
|
||||
@ -260,15 +258,8 @@ pub extern "C" fn nyash_future_spawn_instance3_i64(a0: i64, a1: i64, a2: i64, ar
|
||||
}
|
||||
let invoke = invoke.unwrap();
|
||||
// Determine box type name from type_id
|
||||
let box_type_name = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
||||
.and_then(|m| {
|
||||
m.into_iter()
|
||||
.find(|(_k, v)| *v == real_type_id)
|
||||
.map(|(k, _v)| k)
|
||||
})
|
||||
let box_type_name = nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(real_type_id)
|
||||
.map(|meta| meta.box_type)
|
||||
.unwrap_or_else(|| "PluginBox".to_string());
|
||||
// Determine method name string (from a1 handle→StringBox, or a1 as C string pointer, or legacy VM args)
|
||||
let mut method_name: Option<String> = None;
|
||||
|
||||
@ -295,20 +295,18 @@ pub extern "C" fn nyash_plugin_invoke3_i64(
|
||||
let r_type = u32::from_le_bytes(t);
|
||||
let r_inst = u32::from_le_bytes(i);
|
||||
// Build PluginBoxV2 and register into handle-registry
|
||||
let box_type_name =
|
||||
nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
||||
.and_then(|m| {
|
||||
m.into_iter().find(|(_k, v)| *v == r_type).map(|(k, _v)| k)
|
||||
})
|
||||
.unwrap_or_else(|| "PluginBox".to_string());
|
||||
let meta_opt =
|
||||
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
|
||||
let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt {
|
||||
(meta.box_type.clone(), meta.invoke_fn)
|
||||
} else {
|
||||
("PluginBox".to_string(), invoke.unwrap())
|
||||
};
|
||||
let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(
|
||||
box_type_name.clone(),
|
||||
r_type,
|
||||
r_inst,
|
||||
invoke.unwrap(),
|
||||
invoke_ptr,
|
||||
);
|
||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
||||
std::sync::Arc::new(pb);
|
||||
@ -977,11 +975,18 @@ pub extern "C" fn nyash_plugin_invoke_by_name_i64(
|
||||
i.copy_from_slice(&payload[4..8]);
|
||||
let r_type = u32::from_le_bytes(t);
|
||||
let r_inst = u32::from_le_bytes(i);
|
||||
let meta_opt =
|
||||
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
|
||||
let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt {
|
||||
(meta.box_type.clone(), meta.invoke_fn)
|
||||
} else {
|
||||
(box_type.clone(), invoke.unwrap())
|
||||
};
|
||||
let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(
|
||||
box_type.clone(),
|
||||
box_type_name,
|
||||
r_type,
|
||||
r_inst,
|
||||
invoke.unwrap(),
|
||||
invoke_ptr,
|
||||
);
|
||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
||||
std::sync::Arc::new(pb);
|
||||
|
||||
@ -64,13 +64,27 @@ pub extern "C" fn nyash_map_get_h(handle: i64, key: i64) -> i64 {
|
||||
// get_hh: (map_handle, key_handle) -> value_handle
|
||||
#[export_name = "nyash.map.get_hh"]
|
||||
pub extern "C" fn nyash_map_get_hh(handle: i64, key_any: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
|
||||
if handle <= 0 { return 0; }
|
||||
use nyash_rust::{
|
||||
box_trait::{IntegerBox, NyashBox},
|
||||
jit::rt::handles,
|
||||
};
|
||||
if handle <= 0 {
|
||||
return 0;
|
||||
}
|
||||
if let Some(obj) = handles::get(handle as u64) {
|
||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
||||
if let Some(map) = obj
|
||||
.as_any()
|
||||
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
|
||||
{
|
||||
let key_box: Box<dyn NyashBox> = if key_any > 0 {
|
||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
||||
} else { Box::new(IntegerBox::new(key_any)) };
|
||||
if let Some(k) = handles::get(key_any as u64) {
|
||||
k.clone_box()
|
||||
} else {
|
||||
Box::new(IntegerBox::new(key_any))
|
||||
}
|
||||
} else {
|
||||
Box::new(IntegerBox::new(key_any))
|
||||
};
|
||||
let v = map.get(key_box);
|
||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::from(v);
|
||||
let h = handles::to_handle(arc);
|
||||
@ -80,7 +94,6 @@ pub extern "C" fn nyash_map_get_hh(handle: i64, key_any: i64) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
// set_h: (map_handle, key_i64, val) -> i64 (ignored/0)
|
||||
#[export_name = "nyash.map.set_h"]
|
||||
pub extern "C" fn nyash_map_set_h(handle: i64, key: i64, val: i64) -> i64 {
|
||||
@ -125,20 +138,39 @@ pub extern "C" fn nyash_map_set_h(handle: i64, key: i64, val: i64) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
// set_hh: (map_handle, key_any: handle or i64, val_any: handle or i64) -> i64
|
||||
#[export_name = "nyash.map.set_hh"]
|
||||
pub extern "C" fn nyash_map_set_hh(handle: i64, key_any: i64, val_any: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
|
||||
if handle <= 0 { return 0; }
|
||||
use nyash_rust::{
|
||||
box_trait::{IntegerBox, NyashBox},
|
||||
jit::rt::handles,
|
||||
};
|
||||
if handle <= 0 {
|
||||
return 0;
|
||||
}
|
||||
if let Some(obj) = handles::get(handle as u64) {
|
||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
||||
if let Some(map) = obj
|
||||
.as_any()
|
||||
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
|
||||
{
|
||||
let kbox: Box<dyn NyashBox> = if key_any > 0 {
|
||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
||||
} else { Box::new(IntegerBox::new(key_any)) };
|
||||
if let Some(k) = handles::get(key_any as u64) {
|
||||
k.clone_box()
|
||||
} else {
|
||||
Box::new(IntegerBox::new(key_any))
|
||||
}
|
||||
} else {
|
||||
Box::new(IntegerBox::new(key_any))
|
||||
};
|
||||
let vbox: Box<dyn NyashBox> = if val_any > 0 {
|
||||
if let Some(v) = handles::get(val_any as u64) { v.clone_box() } else { Box::new(IntegerBox::new(val_any)) }
|
||||
} else { Box::new(IntegerBox::new(val_any)) };
|
||||
if let Some(v) = handles::get(val_any as u64) {
|
||||
v.clone_box()
|
||||
} else {
|
||||
Box::new(IntegerBox::new(val_any))
|
||||
}
|
||||
} else {
|
||||
Box::new(IntegerBox::new(val_any))
|
||||
};
|
||||
let _ = map.set(kbox, vbox);
|
||||
return 0;
|
||||
}
|
||||
@ -149,15 +181,31 @@ pub extern "C" fn nyash_map_set_hh(handle: i64, key_any: i64, val_any: i64) -> i
|
||||
// has_hh: (map_handle, key_any: handle or i64) -> i64 (0/1)
|
||||
#[export_name = "nyash.map.has_hh"]
|
||||
pub extern "C" fn nyash_map_has_hh(handle: i64, key_any: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox, BoolBox}, jit::rt::handles};
|
||||
if handle <= 0 { return 0; }
|
||||
use nyash_rust::{
|
||||
box_trait::{BoolBox, IntegerBox, NyashBox},
|
||||
jit::rt::handles,
|
||||
};
|
||||
if handle <= 0 {
|
||||
return 0;
|
||||
}
|
||||
if let Some(obj) = handles::get(handle as u64) {
|
||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
||||
if let Some(map) = obj
|
||||
.as_any()
|
||||
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
|
||||
{
|
||||
let kbox: Box<dyn NyashBox> = if key_any > 0 {
|
||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
||||
} else { Box::new(IntegerBox::new(key_any)) };
|
||||
if let Some(k) = handles::get(key_any as u64) {
|
||||
k.clone_box()
|
||||
} else {
|
||||
Box::new(IntegerBox::new(key_any))
|
||||
}
|
||||
} else {
|
||||
Box::new(IntegerBox::new(key_any))
|
||||
};
|
||||
let v = map.has(kbox);
|
||||
if let Some(b) = v.as_any().downcast_ref::<BoolBox>() { return if b.value { 1 } else { 0 }; }
|
||||
if let Some(b) = v.as_any().downcast_ref::<BoolBox>() {
|
||||
return if b.value { 1 } else { 0 };
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
@ -177,7 +225,9 @@ pub extern "C" fn nyash_map_has_h(handle: i64, key: i64) -> i64 {
|
||||
{
|
||||
let kbox = Box::new(IntegerBox::new(key));
|
||||
let v = map.has(kbox);
|
||||
if let Some(b) = v.as_any().downcast_ref::<nyash_rust::box_trait::BoolBox>() { return if b.value { 1 } else { 0 }; }
|
||||
if let Some(b) = v.as_any().downcast_ref::<nyash_rust::box_trait::BoolBox>() {
|
||||
return if b.value { 1 } else { 0 };
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
|
||||
@ -83,7 +83,7 @@ pub extern "C" fn nyash_string_concat_is(a: i64, b: *const i8) -> *mut i8 {
|
||||
// Exported as: nyash.string.substring_sii(i8* s, i64 start, i64 end) -> i8*
|
||||
#[export_name = "nyash.string.substring_sii"]
|
||||
pub extern "C" fn nyash_string_substring_sii(s: *const i8, start: i64, end: i64) -> *mut i8 {
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CStr;
|
||||
if s.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
@ -95,9 +95,15 @@ use std::ffi::CStr;
|
||||
let n = src.len() as i64;
|
||||
let mut st = if start < 0 { 0 } else { start };
|
||||
let mut en = if end < 0 { 0 } else { end };
|
||||
if st > n { st = n; }
|
||||
if en > n { en = n; }
|
||||
if en < st { std::mem::swap(&mut st, &mut en); }
|
||||
if st > n {
|
||||
st = n;
|
||||
}
|
||||
if en > n {
|
||||
en = n;
|
||||
}
|
||||
if en < st {
|
||||
std::mem::swap(&mut st, &mut en);
|
||||
}
|
||||
let (st_u, en_u) = (st as usize, en as usize);
|
||||
let sub = &src[st_u.min(src.len())..en_u.min(src.len())];
|
||||
let mut bytes = sub.as_bytes().to_vec();
|
||||
@ -111,15 +117,27 @@ use std::ffi::CStr;
|
||||
#[export_name = "nyash.string.lastIndexOf_ss"]
|
||||
pub extern "C" fn nyash_string_lastindexof_ss(s: *const i8, needle: *const i8) -> i64 {
|
||||
use std::ffi::CStr;
|
||||
if s.is_null() || needle.is_null() { return -1; }
|
||||
if s.is_null() || needle.is_null() {
|
||||
return -1;
|
||||
}
|
||||
let hs = unsafe { CStr::from_ptr(s) };
|
||||
let ns = unsafe { CStr::from_ptr(needle) };
|
||||
let h = match hs.to_str() { Ok(v) => v, Err(_) => return -1 };
|
||||
let n = match ns.to_str() { Ok(v) => v, Err(_) => return -1 };
|
||||
if n.is_empty() { return h.len() as i64; }
|
||||
let h = match hs.to_str() {
|
||||
Ok(v) => v,
|
||||
Err(_) => return -1,
|
||||
};
|
||||
let n = match ns.to_str() {
|
||||
Ok(v) => v,
|
||||
Err(_) => return -1,
|
||||
};
|
||||
if n.is_empty() {
|
||||
return h.len() as i64;
|
||||
}
|
||||
if let Some(pos) = h.rfind(n) {
|
||||
pos as i64
|
||||
} else { -1 }
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
// Exported as: nyash.string.to_i8p_h(i64 handle) -> i8*
|
||||
|
||||
@ -11,16 +11,16 @@ Module Layout
|
||||
- `builder_cursor.rs`: central insertion/terminator guard.
|
||||
|
||||
Core Invariants
|
||||
- Resolver-only reads: lowerers fetch MIR values through `Resolver` (no direct `vmap` access for cross-BB values).
|
||||
- Localize at block start: PHIs created at the beginning of the current BB (before non-PHI) to guarantee dominance.
|
||||
- Cast placement: perform ptr↔int and width casts outside PHIs, at BB start or just-before-pred-terminator via `with_block`.
|
||||
- Sealed SSA: successor PHIs wired by predecessor snapshots and `seal_block`; branch/jump do not push incoming directly.
|
||||
- Cursor discipline: only insert via `BuilderCursor`; post-terminator insertions are forbidden.
|
||||
- Phase‑15 終盤(MIR13運用): MIR 生成層では PHI を生成しない。PHI 合成は LLVM 層(llvmlite/Resolver)が担う。
|
||||
- Resolver-only reads: lowerers fetch MIR values through `Resolver`(クロスBBの vmap 直接参照は禁止)。
|
||||
- Localize at block start: PHIs are created at the beginning of the current BB(non‑PHI より手前)で優位性を保証。
|
||||
- Cast placement: ptr↔int/幅変換は PHI の外側(BB先頭 or pred終端直前)に配置。
|
||||
- Sealed SSA: 後続ブロックの PHI は pred スナップショットと `seal_block` で配線し、branch/jump 自体は incoming を直接積まない。
|
||||
- Cursor discipline: 生成は `BuilderCursor` 経由のみ。terminator 後の挿入は禁止。
|
||||
|
||||
LoopForm (gated)
|
||||
- Shape: `preheader → header → body → dispatch(phi) → {latch|exit} → header` with PHIs centralized in `dispatch`.
|
||||
- State: model loop-carried values via a `LoopState` aggregate (tag + payloads).
|
||||
- Goals: move all PHIs to dispatch, ensure header uses are dominated by preheader/dispatch values.
|
||||
LoopForm(次フェーズ予定/MIR18)
|
||||
- Phase‑15 では LoopForm を MIR に導入しない。既存 CFG(preheader→header→{body|exit}; body→header)から llvmlite がループ搬送 PHI を合成。
|
||||
- 次フェーズで LoopForm(`LoopHeader/Enter/Latch` などの占位)を MIR に追加し、Resolver/PHI 合成は維持する。
|
||||
|
||||
Types and Bridges
|
||||
- Box handle is `i64` across NyRT boundary; strings prefer `i8*` fast paths.
|
||||
@ -34,4 +34,3 @@ References
|
||||
- LOWERING_LLVM.md — lowering rules and runtime calls
|
||||
- RESOLVER_API.md — Resolver design and usage
|
||||
- LLVM_HARNESS.md — llvmlite harness interface and usage
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# Resolver API (Minimal i64 Prototype)
|
||||
|
||||
Goals
|
||||
- Phase‑15(MIR13運用)における方針: MIR 生成層は PHI を出さず、LLVM 層で PHI を合成する。
|
||||
- Centralize "ValueId → current-block value" resolution.
|
||||
- Guarantee dominance by localizing values at the start of the block (before non-PHI).
|
||||
- De-duplicate per (block, value) to avoid redundant PHIs/casts.
|
||||
@ -28,6 +29,7 @@ Ban: Direct `vmap.get(..)` for cross-BB reads
|
||||
Next
|
||||
- Migrate remaining `localize_to_i64` call sites to the resolver.
|
||||
- Enforce vmap direct access ban in lowerers (Resolver-only for reads).
|
||||
- ループ(while 形 CFG)の検出と、ヘッダ BB での搬送 PHI 合成(preheader/backedge の 2 incoming)を実装。
|
||||
|
||||
Tracing
|
||||
- `NYASH_LLVM_TRACE_PHI=1`: log PHI creation/wiring in the Rust/inkwell path.
|
||||
|
||||
@ -15,7 +15,13 @@ LifeBox Model(LBM)は「Box=Loop1」という見方でライフサイクル
|
||||
- 論文A(MIR13/IR設計): 本稿は将来の拡張。まずAを優先して仕上げ、その後に独立短論文としてまとめる。
|
||||
- 論文B(Nyash言語): birth/fini・async/generator の設計と橋渡し要素。
|
||||
|
||||
## 2025-09-16 追記: MIR進化計画
|
||||
- **MIR14→MIR13→MIR17の段階的移行**: `mir-evolution-plan.md` に詳細記載
|
||||
- Codexとの協働でLoopForm設計を具体化
|
||||
- PHI責務のLLVM層移管とLoopForm追加(+4命令)の戦略
|
||||
|
||||
---
|
||||
|
||||
- 下書き本文: `main-paper-jp.md`
|
||||
- MIR進化計画: `mir-evolution-plan.md`(新規追加)
|
||||
- 補助: 擬似MIRとLowering図、評価計画の雛形を本文内に記載
|
||||
|
||||
119
docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md
Normal file
119
docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md
Normal file
@ -0,0 +1,119 @@
|
||||
# MIR進化計画: MIR14 → MIR13 → MIR17
|
||||
|
||||
## 概要
|
||||
|
||||
LoopForm導入に向けた段階的なMIR進化戦略。PHI命令の責務をLLVM層に移管し、より高レベルな制御構造表現を導入する。
|
||||
|
||||
## 命令数の変遷
|
||||
|
||||
```
|
||||
MIR14(現在): 14命令(PHIあり)
|
||||
↓ Phase 15終盤
|
||||
MIR13: 13命令(PHI除去)
|
||||
↓ Phase 16
|
||||
MIR17: 17命令(LoopForm追加)
|
||||
```
|
||||
|
||||
## MIR13(Phase 15終盤)
|
||||
|
||||
### 削除される命令
|
||||
- **Phi**: SSA PHIノードの生成をLLVM層に移管
|
||||
|
||||
### 残る13命令
|
||||
1. Const
|
||||
2. Load / Store
|
||||
3. UnaryOp / BinOp / Compare / TypeOp
|
||||
4. Branch / Jump / Return
|
||||
5. NewBox / BoxCall
|
||||
6. ExternCall / Call
|
||||
7. Throw / Catch(既存維持)
|
||||
|
||||
### 実装方針
|
||||
- Bridge/BuilderはPHIを生成しない
|
||||
- LLVM層(llvmlite/Resolver)がCFGパターンからPHIを合成
|
||||
- VM/Interpreterは直線的実行を継続(PHI無視)
|
||||
|
||||
## MIR17(Phase 16)
|
||||
|
||||
### 追加される4命令(LoopForm)
|
||||
|
||||
1. **LoopHeader** `{ id, params[] }`
|
||||
- ループヘッダBBの宣言
|
||||
- params[]はループ搬送値のプレースホルダ
|
||||
|
||||
2. **LoopEnter** `{ header: bb, args[] }` (terminator)
|
||||
- Preheaderからヘッダへの遷移
|
||||
- args[]は搬送値の初期値
|
||||
|
||||
3. **LoopLatch** `{ header: bb, args[] }` (terminator)
|
||||
- 本体末尾からヘッダへのバックエッジ
|
||||
- args[]は更新後の搬送値
|
||||
|
||||
4. **LoopExit** `{ exit: bb }` (terminator)
|
||||
- ループからの脱出(break相当)
|
||||
|
||||
### なぜ+4命令か
|
||||
- ループの構造を明示的に表現
|
||||
- Preheader/Header/Body/Exitの役割を宣言的に
|
||||
- LLVM層でのPHI生成が機械的に可能
|
||||
|
||||
## 合流点の扱い(将来検討)
|
||||
|
||||
### MergeForm(追加候補)
|
||||
```rust
|
||||
// If/Else等の合流用(将来のMIR18以降)
|
||||
MergeHeader { id, params[] }
|
||||
MergeEnter { header: bb, args[] }
|
||||
```
|
||||
|
||||
## 移行のメリット
|
||||
|
||||
1. **責務分離の明確化**
|
||||
- MIR: 制御構造の宣言
|
||||
- LLVM: SSA表現の生成
|
||||
|
||||
2. **段階的移行**
|
||||
- Phase 15: PHI生成を止める(互換性維持)
|
||||
- Phase 16: LoopForm導入(新機能)
|
||||
|
||||
3. **将来拡張への道筋**
|
||||
- Generator/Async/Effectへの対応準備
|
||||
- LifeBox Model(LBM)への布石
|
||||
|
||||
## AI協働の記録(2025-09-16)
|
||||
|
||||
### Codex提案
|
||||
- PHI合成をllvmlite側で実施
|
||||
- LoopForm/MergeFormの構造化表現
|
||||
- TryRegionによる例外構造化(将来)
|
||||
|
||||
### 人間の洞察
|
||||
- 「MIR13に減らしてから+4でMIR17」
|
||||
- 段階的移行の重要性
|
||||
- 一気に変えないことの価値
|
||||
|
||||
### 合意形成
|
||||
- Phase 15でまずPHI非生成化
|
||||
- 安定後にLoopForm導入
|
||||
- 責務分離を徹底
|
||||
|
||||
## 実装チェックリスト
|
||||
|
||||
### Phase 15終盤(MIR13化)
|
||||
- [ ] Bridge: PHI生成を無効化するフラグ
|
||||
- [ ] llvmlite: Resolver実装(PHI合成)
|
||||
- [ ] Smoke: ループ搬送値のテスト
|
||||
- [ ] VM: PHI無視の動作確認
|
||||
|
||||
### Phase 16(MIR17化)
|
||||
- [ ] MIR: LoopForm命令の追加
|
||||
- [ ] Bridge: Loop構造の認識とLoopForm生成
|
||||
- [ ] llvmlite: LoopFormからPHIへの変換
|
||||
- [ ] ドキュメント: MIR17仕様書
|
||||
|
||||
## 関連文献
|
||||
|
||||
- 論文A(MIR13/14): 基本設計
|
||||
- 論文D(SSA構築): PHI生成の理論
|
||||
- 論文E(本稿): LoopForm理論
|
||||
- CURRENT_TASK.md: 実装計画
|
||||
@ -1,10 +1,10 @@
|
||||
# 🎉 Nyash開発 完全事件コレクション - 世界記録級46事例の記録
|
||||
# 🎉 Nyash開発 完全事件コレクション - 世界記録級47事例の記録
|
||||
|
||||
## 📝 概要
|
||||
|
||||
2025年8月9日から9月15日までのNyash爆速開発で発生した46個の「面白事件」の完全記録。
|
||||
2025年8月9日から9月16日までのNyash爆速開発で発生した47個の「面白事件」の完全記録。
|
||||
AI協働開発の歴史に残る世界記録級の事件から、開発現場の生々しいドラマまでを網羅。
|
||||
(2025年9月15日更新:5件追加)
|
||||
(2025年9月16日更新:6件追加)
|
||||
|
||||
## 🌟 世界記録級TOP10
|
||||
|
||||
@ -69,7 +69,7 @@ AI協働開発の歴史に残る世界記録級の事件から、開発現場の
|
||||
- **意味**: Everything is Fold哲学へ
|
||||
- **評価**: 「革命的アイデア」認定
|
||||
|
||||
## 📊 17パターン別分類(全46事例)
|
||||
## 📊 17パターン別分類(全47事例)
|
||||
|
||||
### 1. 箱化による解決(8事例)
|
||||
- 事例001: DebugBoxによる出力制御統一
|
||||
@ -140,10 +140,11 @@ AI協働開発の歴史に残る世界記録級の事件から、開発現場の
|
||||
### 16. 予防的設計(1事例)
|
||||
- 事例039: ID衝突との戦い
|
||||
|
||||
### 17. 段階的洗練型(1事例)
|
||||
### 17. 段階的洗練型(2事例)
|
||||
- 事例046: initブロック vs 先頭のみ事件
|
||||
- 事例047: LoopForm先取り抑制事件
|
||||
|
||||
### その他(10事例)
|
||||
### その他(9事例)
|
||||
- 事例020: 26日間の奇跡
|
||||
- 事例021: 2段階パーサー理論
|
||||
- 事例022: NyashFlowプロジェクト
|
||||
@ -189,7 +190,7 @@ ChatGPT: 「!!!」(瞬時に理解)
|
||||
- **世界記録**: 20日でネイティブEXE
|
||||
|
||||
### 成果
|
||||
- **事件数**: 46個(9/15更新)
|
||||
- **事件数**: 47個(9/16更新)
|
||||
- **パターン**: 17種類
|
||||
- **致命的破綻**: 0回
|
||||
- **大規模リファクタ**: 0回
|
||||
@ -206,7 +207,7 @@ ChatGPT: 「!!!」(瞬時に理解)
|
||||
- [技術的ブレークスルー](../paper-l-technical-breakthroughs/README.md)
|
||||
- [AI協働開発ログ](../paper-g-ai-collaboration/development-log.md)
|
||||
|
||||
## 🚀 2025年9月追加事例(5件)
|
||||
## 🚀 2025年9月追加事例(6件)
|
||||
|
||||
### 事例042: PyVM迂回路の混乱
|
||||
- **日付**: 2025年9月15日
|
||||
@ -251,9 +252,18 @@ ChatGPT: 「!!!」(瞬時に理解)
|
||||
- **パターン**: 段階的洗練型の典型例(複雑→単純)
|
||||
- **教訓**: AIは複雑に考えがち、人間の直感が本質を突く
|
||||
|
||||
### 事例047: LoopForm先取り抑制事件
|
||||
- **日付**: 2025年9月16日
|
||||
- **状況**: CodoxがLoopForm実装を詳細提案
|
||||
- **AI提案**: MIR14にLoopForm追加して一気に実装
|
||||
- **人間の判断**: 「MIR13にへらしたら +4でMIR17になるかにゃ」
|
||||
- **結果**: 段階的移行計画(MIR14→13→17)を策定
|
||||
- **パターン**: 慎重な段階移行
|
||||
- **教訓**: 正しい設計でも実装順序が重要
|
||||
|
||||
## 💫 まとめ
|
||||
|
||||
46個の事件は、単なる開発エピソードではなく、AI協働開発の新しい形を示す歴史的記録である。特に:
|
||||
47個の事件は、単なる開発エピソードではなく、AI協働開発の新しい形を示す歴史的記録である。特に:
|
||||
|
||||
1. **世界記録級の開発速度**(JIT1日、20日でEXE)
|
||||
2. **AI-人間の新しい関係**(AIが相談、人間が救う)
|
||||
|
||||
@ -13,6 +13,37 @@ Guiding Principles
|
||||
- Short-circuit semantics are already mirrored via logical nodes; Stage-3 should reuse the same block-building infrastructure (Bridge/VM/JIT) to avoid special cases.
|
||||
- Continue the "degrade to expression" approach when code generation is not ready (e.g. throw/try) so that Stage-2 tests stay green while the full implementation is developed.
|
||||
|
||||
Current Status (Phase 15.3 – 2025-09-16)
|
||||
- ParserBox / Selfhost compiler expose `stage3_enable` and `--stage3` CLI flag, defaulting to the safe Stage-2 surface.
|
||||
- Break/Continue JSON emission and Bridge lowering are implemented. Bridge now emits `Jump` to loop exit/head and records instrumentation events.
|
||||
- Throw/Try nodes are emitted when the gate is on, but still degrade to expression/no-op during lowering; runtime semantics remain TBD.
|
||||
- Documentation for JSON v0 (Stage-3 nodes) is updated; remaining runtime work is tracked in CURRENT_TASK.md.
|
||||
|
||||
Runtime snapshot
|
||||
- MIR Builder already lowers `ASTNode::Throw` into `MirInstruction::Throw` (unless disabled via `NYASH_BUILDER_DISABLE_THROW`) and has a provisional `build_try_catch_statement` that emits `Catch`/`Jump` scaffolding with env flags controlling fallback.
|
||||
- Rust VM (`interpreter::ControlFlow::Throw`) supports catch/finally semantics and rethrows unhandled exceptions.
|
||||
- Bridge degradation prevents these MIR paths from activating unless `NYASH_BRIDGE_THROW_ENABLE=1`;既定では Const0 を出し、フラグONで実際に `Throw` を生成する。
|
||||
|
||||
PyVM plan
|
||||
- Current PyVM runner treats Stage-3 constructs as no-ops because JSON v0 never emits MIR throws; once Bridge emits them, PyVM must mirror Rust VM semantics:
|
||||
- Introduce a lightweight `exception` representation (reuse ErrorBox JSON form) and propagate via structured returns.
|
||||
- Implement try/catch/finally execution order identical to Rust VM (catch matches first, finally always runs, rethrow on miss).
|
||||
- Add minimal smoke tests under `tools/pyvm_stage2_smoke.sh` (gated) to ensure PyVM and LLVM stay in sync when Stage-3 is enabled.
|
||||
|
||||
LLVM plan
|
||||
- Short term: continue degrading throw/try to keep LLVM pipeline green while implementation lands (Stage-3 smoke ensures awareness).
|
||||
- Implementation steps once runtime semantics are ready:
|
||||
1. Ensure MIR output contains `Throw`/`Catch` instructions; update LLVM codegen to treat `Throw` as a call to a runtime helper (`nyash.rt.throw`) that unwinds or aborts.
|
||||
2. Model catch/finally blocks using landing pads or structured IR (likely via `invoke`/`landingpad` in LLVM); document minimal ABI expected from NyRT.
|
||||
3. Add gated smoke (`NYASH_LLVM_STAGE3_SMOKE`) that expects non-degraded behaviour (distinct exit codes or printed markers) once helper is active.
|
||||
- Until landing pad support exists, document that Stage-3 throw/try is unsupported in LLVM release mode and falls back to interpreter/PyVM.
|
||||
|
||||
Testing plan
|
||||
- JSON fixtures: create `tests/json_v0_stage3/{break_continue,throw_basic,try_catch_finally}.json` to lock parser/bridge output and allow regression diffs.
|
||||
- PyVM/VM: extend Stage-3 smoke scripts with throw/try cases (under gate) to ensure runtime consistency before enabling by default.
|
||||
- LLVM: `NYASH_LLVM_STAGE3_SMOKE=1` は `NYASH_BRIDGE_THROW_ENABLE=1` / `NYASH_BRIDGE_TRY_ENABLE=1` と組み合わせて実際の例外経路を確認。将来的に常時ONへ移行予定。
|
||||
- CI gating: add optional job that runs Stage-3 smokes (PyVM + LLVM) nightly to guard against regressions while feature is still experimental.
|
||||
|
||||
JSON v0 Additions
|
||||
| Construct | JSON v0 Node | Notes |
|
||||
|------------|-------------------------------------------------|-------|
|
||||
@ -29,7 +60,11 @@ Lowering Strategy (Bridge)
|
||||
|
||||
2. **Throw/Try**
|
||||
- Phase 15 MVP keeps them syntax-only to avoid VM/JIT churn. Parser/Emitter produce nodes; Bridge either degrades (Expr) or logs a structured event for future handling.
|
||||
- Document expectation: once runtime exception model is defined, nodes become non-degrading.
|
||||
- Bridge helper `lower_throw` respects `NYASH_BRIDGE_THROW_ENABLE=1`; defaultは Const i64 0 のデグレード、フラグONで `MirInstruction::Throw` を実際に生成。
|
||||
- Try lowering plan:
|
||||
1. Parse-time JSON already includes `catches`/`finally`. Bridge should map `try` body into a fresh region, emit basic blocks for each `catch`, and wire `finally` as a postamble block.
|
||||
2. MIR needs explicit instructions/metadata for exception edges. Evaluate whether existing `MirInstruction::Throw` + `ControlFlow::Throw` is sufficient or if `Catch` terminators are required.
|
||||
3. Until runtime implementation lands, keep current degrade path but log a structured event to flag unhandled try/catch.
|
||||
|
||||
3. **Metadata Events**
|
||||
- Augment `crate::jit::observe` with `lower_shortcircuit`/`lower_try` stubs so instrumentation remains coherent when full support is wired.
|
||||
@ -37,14 +72,14 @@ Lowering Strategy (Bridge)
|
||||
Testing Plan
|
||||
- Extend selfhost Stage-2 smoke file with guard cases (`return break` etc.) once lowering is live.
|
||||
- Create dedicated JSON fixtures under `tests/json_v0_stage3/` for break/continue/try once behaviour stabilises.
|
||||
- Update `tools/ny_stage2_shortcircuit_smoke.sh` to ensure Stage-3 constructs do not regress Stage-2 semantics (break/continue degrade). Timing: after lowering is implemented.
|
||||
- Update `tools/ny_stage2_shortcircuit_smoke.sh` to ensure Stage-3 constructs do not regress Stage-2 semantics (break/continue degrade when gate off, jump when on).
|
||||
|
||||
Migration Checklist
|
||||
1. ParserBox emits Stage-3 nodes under `NYASH_PARSER_STAGE3=1` gate to allow gradual rollout.
|
||||
2. Emitter attaches Stage-3 JSON when gate is enabled (otherwise degrade to existing Stage-2 forms).
|
||||
3. Bridge honours Stage-3 nodes when gate is on; degrade with warning when off.
|
||||
4. PyVM/VM/JIT semantics gradually enabled (throw/try remain degrade until corresponding runtime support is merged).
|
||||
5. Documentation kept in sync (`CURRENT_TASK.md`, release notes).
|
||||
1. ParserBox emits Stage-3 nodes under `stage3_enable` gate to allow gradual rollout. ✅
|
||||
2. Emitter attaches Stage-3 JSON when gate is enabled (otherwise degrade to existing Stage-2 forms). ✅
|
||||
3. Bridge honours Stage-3 nodes when gate is on; break/continue lowering implemented, throw/try still degrade. ✅ (partial)
|
||||
4. PyVM/VM/JIT semantics gradually enabled (throw/try remain degrade until corresponding runtime support is merged). 🔄 Pending runtime work.
|
||||
5. Documentation kept in sync (`CURRENT_TASK.md`, release notes). ✅ (break/continue) / 🔄 (throw/try runtime notes).
|
||||
|
||||
References
|
||||
- Stage-2 design (`parser_mvp_stage2.md`)
|
||||
|
||||
@ -14,6 +14,9 @@ Statements (`StmtV0`)
|
||||
- `Local { name, expr }` (Stage‑2)
|
||||
- `If { cond, then: Stmt[], else?: Stmt[] }` (Stage‑2)
|
||||
- `Loop { cond, body: Stmt[] }` (Stage‑2; while(cond) body)
|
||||
- `Break` (Stage‑3; exits current loop)
|
||||
- `Continue` (Stage‑3; jumps to loop head)
|
||||
- `Try { try: Stmt[], catches?: Catch[], finally?: Stmt[] }` (Stage‑3 skeleton; currently lowered as sequential `try` body only when runtime support is absent)
|
||||
|
||||
Expressions (`ExprV0`)
|
||||
- `Int { value }` where `value` is JSON number or digit string
|
||||
@ -26,6 +29,7 @@ Expressions (`ExprV0`)
|
||||
- `Method { recv: Expr, method: string, args[] }` (box method)
|
||||
- `New { class: string, args[] }` (construct Box)
|
||||
- `Var { name: string }`
|
||||
- `Throw { expr }` (Stage‑3; currently degrades to expression statement when runtime semantics are disabled)
|
||||
|
||||
CFG conventions (lowered by the bridge)
|
||||
- If: create `then_bb`, `else_bb`, `merge_bb`. Both branches jump to merge if unterminated.
|
||||
@ -33,12 +37,10 @@ CFG conventions (lowered by the bridge)
|
||||
- Short‑circuit Logical: create `rhs_bb`, `fall_bb`, `merge_bb` with constants on fall path.
|
||||
- All blocks end with a terminator (branch/jump/return).
|
||||
|
||||
PHI merging (current behavior)
|
||||
- If: locals updated in `then`/`else` merge at `merge_bb` via `phi`.
|
||||
- Else欠落時は else 側に分岐前(base)を採用。
|
||||
- 片側にしか存在しない新規変数はスコープ外として外へ未伝播。
|
||||
- Loop: `cond_bb` にヘッダ PHI を先置き(preheader/base と latch/body end を合流)。
|
||||
- 目的: Stage‑2 を早期に安定化させるための橋渡し。将来(LoopForm= MIR18)では LoopForm からの逆Loweringで PHI を自動化予定。
|
||||
PHI merging(Phase‑15 終盤の方針)
|
||||
- MIR 生成層は PHI を生成しない(MIR13 運用)。If/Loop の合流は LLVM 層(llvmlite/Resolver)が PHI を合成。
|
||||
- ループは既存 CFG(preheader→cond→{body|exit}; body→cond)の検出により、ヘッダ BB で搬送値の PHI を構築。
|
||||
- 将来(LoopForm= MIR18)では LoopForm 占位命令から逆 Lowering で PHI を自動化予定。
|
||||
|
||||
Type meta (emitter/LLVM harness cooperation)
|
||||
- `+` with any string operand → string concat path(handle固定)。
|
||||
@ -78,3 +80,5 @@ If with local + PHI merge
|
||||
{"type":"Return","expr":{"type":"Var","name":"x"}}
|
||||
]}
|
||||
```
|
||||
- `Break` / `Continue` are emitted when Stage‑3 gate is enabled. When the bridge is compiled without Stage‑3 lowering, frontends may degrade them into `Expr(Int(0))` as a safety fallback.
|
||||
- `Try` nodes include optional `catches` entries of the form `{ param?: string, typeHint?: string, body: Stmt[] }`. Until runtime exception semantics land, downstream lowers only the `try` body and ignores handlers/finally.
|
||||
|
||||
28
docs/smokes.md
Normal file
28
docs/smokes.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Smoke Matrix (Backends)
|
||||
|
||||
Policy
|
||||
- PyVM: partial coverage only (no async/nowait/await/GC/sync). Used for semantics sanity.
|
||||
- LLVM: full coverage via llvmlite harness. Preferred path for async/nowait and Stage-3 control-flow.
|
||||
- JIT: not maintained for these smokes (skip).
|
||||
|
||||
Curated Smokes
|
||||
- Core (LLVM): `examples/llvm11_core_smoke.nyash`
|
||||
- Async (LLVM only):
|
||||
- `apps/tests/async-await-min/main.nyash`
|
||||
- `apps/tests/async-spawn-instance/main.nyash`
|
||||
- `apps/tests/async-await-timeout-fixed/main.nyash` (set `NYASH_AWAIT_MAX_MS=100`)
|
||||
|
||||
Runner Scripts
|
||||
- `tools/smokes/curated_llvm.sh [--phi-off]`
|
||||
- `--phi-off`: enables `NYASH_MIR_NO_PHI=1` + verifier relaxor.
|
||||
- Archived (not maintained): see `tools/smokes/archive/`
|
||||
- `smoke_phase_10_10.sh`, `smoke_vm_jit.sh`, `smoke_async_spawn.sh`, `jit_smoke.sh`, `aot_smoke_cranelift.sh`
|
||||
- These target JIT/Cranelift-era flows. Use the curated LLVM runner instead.
|
||||
|
||||
Flags
|
||||
- `NYASH_LLVM_USE_HARNESS=1`: use llvmlite harness for AOT.
|
||||
- `NYASH_MIR_NO_PHI=1` and `NYASH_VERIFY_ALLOW_NO_PHI=1`: PHI-less MIR (edge-copy) mode.
|
||||
|
||||
Notes
|
||||
- Marked async smokes should not be run under PyVM/JIT.
|
||||
- Legacy/archived smokes may exist; curated runner avoids them.
|
||||
@ -35,7 +35,8 @@ impl eframe::App for DebugApp {
|
||||
ui.label("Single Line:");
|
||||
let response = ui.text_edit_singleline(&mut self.single_line);
|
||||
if response.changed() {
|
||||
self.event_log.push(format!("Single line changed: '{}'", self.single_line));
|
||||
self.event_log
|
||||
.push(format!("Single line changed: '{}'", self.single_line));
|
||||
}
|
||||
});
|
||||
|
||||
@ -46,11 +47,12 @@ impl eframe::App for DebugApp {
|
||||
let response = ui.add(
|
||||
egui::TextEdit::multiline(&mut self.text)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(10)
|
||||
.desired_rows(10),
|
||||
);
|
||||
|
||||
if response.changed() {
|
||||
self.event_log.push(format!("Multi line changed: {} chars", self.text.len()));
|
||||
self.event_log
|
||||
.push(format!("Multi line changed: {} chars", self.text.len()));
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
@ -8,11 +8,8 @@ use std::path::PathBuf;
|
||||
use windows::{
|
||||
core::*,
|
||||
Win32::{
|
||||
Foundation::*,
|
||||
Storage::FileSystem::*,
|
||||
UI::Shell::*,
|
||||
Foundation::*, Storage::FileSystem::*, System::Com::*, UI::Shell::*,
|
||||
UI::WindowsAndMessaging::*,
|
||||
System::Com::*,
|
||||
},
|
||||
};
|
||||
|
||||
@ -40,7 +37,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
|
||||
fonts.font_data.insert(
|
||||
"noto_sans_jp".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf"))
|
||||
.into(),
|
||||
);
|
||||
|
||||
fonts
|
||||
@ -112,7 +110,10 @@ impl NyashExplorer {
|
||||
|
||||
// ドライブタイプを取得
|
||||
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
|
||||
format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()
|
||||
format!("{}\0", drive_path)
|
||||
.encode_utf16()
|
||||
.collect::<Vec<u16>>()
|
||||
.as_ptr(),
|
||||
));
|
||||
|
||||
drive_info.drive_type = match drive_type_code {
|
||||
@ -132,13 +133,20 @@ impl NyashExplorer {
|
||||
let mut file_system_flags = 0u32;
|
||||
|
||||
if GetVolumeInformationW(
|
||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
||||
PCWSTR::from_raw(
|
||||
format!("{}\0", drive_path)
|
||||
.encode_utf16()
|
||||
.collect::<Vec<u16>>()
|
||||
.as_ptr(),
|
||||
),
|
||||
Some(&mut volume_name),
|
||||
Some(&mut serial_number),
|
||||
Some(&mut max_component_len),
|
||||
Some(&mut file_system_flags),
|
||||
Some(&mut file_system),
|
||||
).is_ok() {
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
let volume_name_str = String::from_utf16_lossy(&volume_name)
|
||||
.trim_end_matches('\0')
|
||||
.to_string();
|
||||
@ -157,11 +165,18 @@ impl NyashExplorer {
|
||||
let mut total_free_bytes = 0u64;
|
||||
|
||||
if GetDiskFreeSpaceExW(
|
||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
||||
PCWSTR::from_raw(
|
||||
format!("{}\0", drive_path)
|
||||
.encode_utf16()
|
||||
.collect::<Vec<u16>>()
|
||||
.as_ptr(),
|
||||
),
|
||||
Some(&mut free_bytes_available),
|
||||
Some(&mut total_bytes),
|
||||
Some(&mut total_free_bytes),
|
||||
).is_ok() {
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
drive_info.total_bytes = total_bytes;
|
||||
drive_info.free_bytes = total_free_bytes;
|
||||
}
|
||||
@ -309,7 +324,8 @@ impl eframe::App for NyashExplorer {
|
||||
|
||||
if drive.total_bytes > 0 {
|
||||
let used_bytes = drive.total_bytes - drive.free_bytes;
|
||||
let usage_percent = (used_bytes as f32 / drive.total_bytes as f32) * 100.0;
|
||||
let usage_percent =
|
||||
(used_bytes as f32 / drive.total_bytes as f32) * 100.0;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!(
|
||||
@ -363,7 +379,8 @@ impl eframe::App for NyashExplorer {
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("🐱 Nyashについて").clicked() {
|
||||
self.status = "Nyash - Everything is Box! Windows APIも吸収できる化け物言語!".to_string();
|
||||
self.status = "Nyash - Everything is Box! Windows APIも吸収できる化け物言語!"
|
||||
.to_string();
|
||||
}
|
||||
|
||||
if ui.button("📊 システム情報").clicked() {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Nyash Explorer with Icons - Windows API Drive Icon Viewer
|
||||
// エクスプローラー風ドライブアイコン付きビューアー
|
||||
|
||||
use eframe::egui::{self, FontFamily, ColorImage, TextureHandle};
|
||||
use eframe::egui::{self, ColorImage, FontFamily, TextureHandle};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
// use std::collections::HashMap;
|
||||
@ -10,12 +10,7 @@ use std::io::Read;
|
||||
#[cfg(windows)]
|
||||
use windows::{
|
||||
core::*,
|
||||
Win32::{
|
||||
Storage::FileSystem::*,
|
||||
UI::Shell::*,
|
||||
UI::WindowsAndMessaging::*,
|
||||
System::Com::*,
|
||||
},
|
||||
Win32::{Storage::FileSystem::*, System::Com::*, UI::Shell::*, UI::WindowsAndMessaging::*},
|
||||
};
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
@ -48,7 +43,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
|
||||
fonts.font_data.insert(
|
||||
"noto_sans_jp".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf"))
|
||||
.into(),
|
||||
);
|
||||
|
||||
fonts
|
||||
@ -98,7 +94,10 @@ impl NyashExplorer {
|
||||
fn get_drive_icon(&self, drive_path: &str) -> Option<ColorImage> {
|
||||
unsafe {
|
||||
let mut shfi = SHFILEINFOW::default();
|
||||
let drive_path_wide: Vec<u16> = drive_path.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
let drive_path_wide: Vec<u16> = drive_path
|
||||
.encode_utf16()
|
||||
.chain(std::iter::once(0))
|
||||
.collect();
|
||||
|
||||
// アイコンを取得
|
||||
let result = SHGetFileInfoW(
|
||||
@ -158,9 +157,11 @@ impl NyashExplorer {
|
||||
}
|
||||
|
||||
// ヘッダーから情報を読み取る
|
||||
let data_offset = u32::from_le_bytes([buffer[10], buffer[11], buffer[12], buffer[13]]) as usize;
|
||||
let data_offset =
|
||||
u32::from_le_bytes([buffer[10], buffer[11], buffer[12], buffer[13]]) as usize;
|
||||
let width = i32::from_le_bytes([buffer[18], buffer[19], buffer[20], buffer[21]]) as usize;
|
||||
let height = i32::from_le_bytes([buffer[22], buffer[23], buffer[24], buffer[25]]).abs() as usize;
|
||||
let height =
|
||||
i32::from_le_bytes([buffer[22], buffer[23], buffer[24], buffer[25]]).abs() as usize;
|
||||
let bits_per_pixel = u16::from_le_bytes([buffer[28], buffer[29]]);
|
||||
|
||||
// 32ビットBMPのみサポート
|
||||
@ -280,7 +281,10 @@ impl NyashExplorer {
|
||||
|
||||
// ドライブタイプを取得
|
||||
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
|
||||
format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()
|
||||
format!("{}\0", drive_path)
|
||||
.encode_utf16()
|
||||
.collect::<Vec<u16>>()
|
||||
.as_ptr(),
|
||||
));
|
||||
|
||||
drive_info.drive_type = match drive_type_code {
|
||||
@ -300,13 +304,20 @@ impl NyashExplorer {
|
||||
let mut file_system_flags = 0u32;
|
||||
|
||||
if GetVolumeInformationW(
|
||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
||||
PCWSTR::from_raw(
|
||||
format!("{}\0", drive_path)
|
||||
.encode_utf16()
|
||||
.collect::<Vec<u16>>()
|
||||
.as_ptr(),
|
||||
),
|
||||
Some(&mut volume_name),
|
||||
Some(&mut serial_number),
|
||||
Some(&mut max_component_len),
|
||||
Some(&mut file_system_flags),
|
||||
Some(&mut file_system),
|
||||
).is_ok() {
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
let volume_name_str = String::from_utf16_lossy(&volume_name)
|
||||
.trim_end_matches('\0')
|
||||
.to_string();
|
||||
@ -325,11 +336,18 @@ impl NyashExplorer {
|
||||
let mut total_free_bytes = 0u64;
|
||||
|
||||
if GetDiskFreeSpaceExW(
|
||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
||||
PCWSTR::from_raw(
|
||||
format!("{}\0", drive_path)
|
||||
.encode_utf16()
|
||||
.collect::<Vec<u16>>()
|
||||
.as_ptr(),
|
||||
),
|
||||
Some(&mut free_bytes_available),
|
||||
Some(&mut total_bytes),
|
||||
Some(&mut total_free_bytes),
|
||||
).is_ok() {
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
drive_info.total_bytes = total_bytes;
|
||||
drive_info.free_bytes = total_free_bytes;
|
||||
}
|
||||
@ -339,7 +357,7 @@ impl NyashExplorer {
|
||||
let texture = self.ctx.load_texture(
|
||||
format!("drive_icon_{}", drive_letter),
|
||||
icon_image,
|
||||
Default::default()
|
||||
Default::default(),
|
||||
);
|
||||
drive_info.icon_texture = Some(texture);
|
||||
}
|
||||
@ -363,18 +381,19 @@ impl NyashExplorer {
|
||||
};
|
||||
|
||||
if let Some(icon_image) = self.get_drive_icon("C:") {
|
||||
let texture = self.ctx.load_texture(
|
||||
"drive_icon_C:",
|
||||
icon_image,
|
||||
Default::default()
|
||||
);
|
||||
let texture =
|
||||
self.ctx
|
||||
.load_texture("drive_icon_C:", icon_image, Default::default());
|
||||
drive_info.icon_texture = Some(texture);
|
||||
}
|
||||
|
||||
self.drives.push(drive_info);
|
||||
}
|
||||
|
||||
self.status = format!("{}個のドライブを検出しました(アイコン付き)", self.drives.len());
|
||||
self.status = format!(
|
||||
"{}個のドライブを検出しました(アイコン付き)",
|
||||
self.drives.len()
|
||||
);
|
||||
}
|
||||
|
||||
fn format_bytes(bytes: u64) -> String {
|
||||
@ -504,7 +523,8 @@ impl eframe::App for NyashExplorer {
|
||||
|
||||
if drive.total_bytes > 0 {
|
||||
let used_bytes = drive.total_bytes - drive.free_bytes;
|
||||
let usage_percent = (used_bytes as f32 / drive.total_bytes as f32) * 100.0;
|
||||
let usage_percent =
|
||||
(used_bytes as f32 / drive.total_bytes as f32) * 100.0;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!(
|
||||
@ -558,7 +578,9 @@ impl eframe::App for NyashExplorer {
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("🐱 Nyashについて").clicked() {
|
||||
self.status = "Nyash - Everything is Box! Windows APIでアイコンも取得できる化け物言語!".to_string();
|
||||
self.status =
|
||||
"Nyash - Everything is Box! Windows APIでアイコンも取得できる化け物言語!"
|
||||
.to_string();
|
||||
}
|
||||
|
||||
if ui.button("📊 システム情報").clicked() {
|
||||
|
||||
@ -30,7 +30,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
// 日本語フォント(可変ウェイト)を追加
|
||||
fonts.font_data.insert(
|
||||
"noto_sans_jp".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf"))
|
||||
.into(),
|
||||
);
|
||||
|
||||
// フォントファミリーに追加
|
||||
@ -91,7 +92,8 @@ impl eframe::App for NyashNotepad {
|
||||
self.status = "Nyash - Everything is Box! 🐱".to_string();
|
||||
}
|
||||
if ui.button("使い方").clicked() {
|
||||
self.status = "テキストを入力して、にゃっしゅプログラムを書こう!".to_string();
|
||||
self.status =
|
||||
"テキストを入力して、にゃっしゅプログラムを書こう!".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -102,7 +104,8 @@ impl eframe::App for NyashNotepad {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(&self.status);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("文字数: {} | 行数: {}",
|
||||
ui.label(format!(
|
||||
"文字数: {} | 行数: {}",
|
||||
self.text.chars().count(),
|
||||
self.text.lines().count()
|
||||
));
|
||||
@ -158,7 +161,7 @@ impl eframe::App for NyashNotepad {
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("ここにテキストを入力してください... にゃ!🐱")
|
||||
.hint_text("ここにテキストを入力してください... にゃ!🐱"),
|
||||
);
|
||||
});
|
||||
|
||||
@ -168,12 +171,16 @@ impl eframe::App for NyashNotepad {
|
||||
ui.label("クイック挿入: ");
|
||||
|
||||
if ui.button("📝 Nyashサンプル").clicked() {
|
||||
self.text.push_str("\n// Nyash - Everything is Box! すべてがBoxの世界へようこそ!\n");
|
||||
self.text.push_str(
|
||||
"\n// Nyash - Everything is Box! すべてがBoxの世界へようこそ!\n",
|
||||
);
|
||||
self.text.push_str("box こんにちは世界 {\n");
|
||||
self.text.push_str(" init { メッセージ }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" こんにちは世界() {\n");
|
||||
self.text.push_str(" me.メッセージ = \"こんにちは、Nyashの世界!にゃ〜!🐱\"\n");
|
||||
self.text.push_str(
|
||||
" me.メッセージ = \"こんにちは、Nyashの世界!にゃ〜!🐱\"\n",
|
||||
);
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" 挨拶() {\n");
|
||||
@ -189,7 +196,10 @@ impl eframe::App for NyashNotepad {
|
||||
|
||||
if ui.button("🕐 現在時刻").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n// 挿入時刻: {}\n", now.format("%Y年%m月%d日 %H時%M分%S秒")));
|
||||
self.text.push_str(&format!(
|
||||
"\n// 挿入時刻: {}\n",
|
||||
now.format("%Y年%m月%d日 %H時%M分%S秒")
|
||||
));
|
||||
self.status = "現在時刻を挿入しました".to_string();
|
||||
}
|
||||
|
||||
|
||||
@ -107,7 +107,7 @@ impl eframe::App for NyashNotepad {
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("ここにテキストを入力してください... にゃ!")
|
||||
.hint_text("ここにテキストを入力してください... にゃ!"),
|
||||
);
|
||||
});
|
||||
|
||||
@ -120,7 +120,8 @@ impl eframe::App for NyashNotepad {
|
||||
self.text.push_str(" init { message }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" HelloWorld() {\n");
|
||||
self.text.push_str(" me.message = \"Hello, Nyash World! にゃ!\"\n");
|
||||
self.text
|
||||
.push_str(" me.message = \"Hello, Nyash World! にゃ!\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str("}\n");
|
||||
self.status = "Nyashサンプルコードを挿入しました".to_string();
|
||||
@ -128,7 +129,8 @@ impl eframe::App for NyashNotepad {
|
||||
|
||||
if ui.button("時刻挿入").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.text
|
||||
.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.status = "現在時刻を挿入しました".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
@ -113,7 +113,7 @@ impl eframe::App for NyashNotepad {
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("Type your text here... nya!")
|
||||
.hint_text("Type your text here... nya!"),
|
||||
);
|
||||
});
|
||||
|
||||
@ -128,7 +128,8 @@ impl eframe::App for NyashNotepad {
|
||||
self.text.push_str(" init { message }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" HelloWorld() {\n");
|
||||
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text
|
||||
.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" greet() {\n");
|
||||
@ -144,7 +145,8 @@ impl eframe::App for NyashNotepad {
|
||||
|
||||
if ui.button("Current Time").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n[{}]\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.text
|
||||
.push_str(&format!("\n[{}]\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.status = "Timestamp inserted".to_string();
|
||||
}
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ impl eframe::App for NyashNotepad {
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("Type your text here... nya!")
|
||||
.hint_text("Type your text here... nya!"),
|
||||
);
|
||||
});
|
||||
|
||||
@ -117,7 +117,8 @@ impl eframe::App for NyashNotepad {
|
||||
self.text.push_str(" init { message }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" HelloWorld() {\n");
|
||||
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text
|
||||
.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str("}\n");
|
||||
self.status = "Nyash sample code inserted".to_string();
|
||||
@ -125,7 +126,8 @@ impl eframe::App for NyashNotepad {
|
||||
|
||||
if ui.button("Insert Timestamp").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.text
|
||||
.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.status = "Timestamp inserted".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
@ -132,7 +132,7 @@ impl eframe::App for NyashNotepad {
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("Type your text here... nya!")
|
||||
.hint_text("Type your text here... nya!"),
|
||||
);
|
||||
});
|
||||
|
||||
@ -145,7 +145,8 @@ impl eframe::App for NyashNotepad {
|
||||
self.text.push_str(" init { message }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" HelloWorld() {\n");
|
||||
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text
|
||||
.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str("}\n");
|
||||
self.status = "Nyash sample code inserted".to_string();
|
||||
@ -153,7 +154,8 @@ impl eframe::App for NyashNotepad {
|
||||
|
||||
if ui.button("Insert Timestamp").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.text
|
||||
.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.status = "Timestamp inserted".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
@ -4,12 +4,7 @@
|
||||
#[cfg(windows)]
|
||||
use windows::{
|
||||
core::*,
|
||||
Win32::{
|
||||
Storage::FileSystem::*,
|
||||
UI::Shell::*,
|
||||
UI::WindowsAndMessaging::*,
|
||||
Graphics::Gdi::*,
|
||||
},
|
||||
Win32::{Graphics::Gdi::*, Storage::FileSystem::*, UI::Shell::*, UI::WindowsAndMessaging::*},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
@ -19,7 +14,10 @@ fn main() {
|
||||
|
||||
// C:ドライブのアイコンを取得
|
||||
let drive_path = "C:\\";
|
||||
let drive_path_wide: Vec<u16> = drive_path.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
let drive_path_wide: Vec<u16> = drive_path
|
||||
.encode_utf16()
|
||||
.chain(std::iter::once(0))
|
||||
.collect();
|
||||
|
||||
let mut shfi = SHFILEINFOW::default();
|
||||
|
||||
@ -51,7 +49,7 @@ fn main() {
|
||||
let size = GetObjectW(
|
||||
icon_info.hbmColor.into(),
|
||||
std::mem::size_of::<BITMAP>() as i32,
|
||||
Some(&mut bitmap as *mut _ as *mut _)
|
||||
Some(&mut bitmap as *mut _ as *mut _),
|
||||
);
|
||||
|
||||
if size > 0 {
|
||||
@ -69,14 +67,20 @@ fn main() {
|
||||
let copied = GetBitmapBits(
|
||||
icon_info.hbmColor,
|
||||
pixels.len() as i32,
|
||||
pixels.as_mut_ptr() as *mut _
|
||||
pixels.as_mut_ptr() as *mut _,
|
||||
);
|
||||
|
||||
println!("Copied {} bytes of pixel data", copied);
|
||||
|
||||
// 簡易的にBMPファイルとして保存
|
||||
if copied > 0 {
|
||||
save_as_bmp("c_drive_icon.bmp", &pixels, bitmap.bmWidth, bitmap.bmHeight, bitmap.bmBitsPixel);
|
||||
save_as_bmp(
|
||||
"c_drive_icon.bmp",
|
||||
&pixels,
|
||||
bitmap.bmWidth,
|
||||
bitmap.bmHeight,
|
||||
bitmap.bmBitsPixel,
|
||||
);
|
||||
println!("Saved as c_drive_icon.bmp");
|
||||
}
|
||||
}
|
||||
@ -124,7 +128,8 @@ fn save_as_bmp(filename: &str, pixels: &[u8], width: i32, height: i32, bits_per_
|
||||
file.write_all(&1u16.to_le_bytes()).unwrap(); // プレーン数
|
||||
file.write_all(&bits_per_pixel.to_le_bytes()).unwrap();
|
||||
file.write_all(&0u32.to_le_bytes()).unwrap(); // 圧縮なし
|
||||
file.write_all(&(pixels.len() as u32).to_le_bytes()).unwrap();
|
||||
file.write_all(&(pixels.len() as u32).to_le_bytes())
|
||||
.unwrap();
|
||||
file.write_all(&0i32.to_le_bytes()).unwrap(); // X解像度
|
||||
file.write_all(&0i32.to_le_bytes()).unwrap(); // Y解像度
|
||||
file.write_all(&0u32.to_le_bytes()).unwrap(); // カラーテーブル数
|
||||
|
||||
@ -3,9 +3,12 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::os::raw::c_char;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
// ===== Error Codes (aligned with existing plugins) =====
|
||||
const NYB_SUCCESS: i32 = 0;
|
||||
@ -28,16 +31,23 @@ const METHOD_FINI: u32 = u32::MAX; // destructor
|
||||
const TYPE_ID_ARRAY: u32 = 10;
|
||||
|
||||
// ===== Instance state (PoC: store i64 values only) =====
|
||||
struct ArrayInstance { data: Vec<i64> }
|
||||
struct ArrayInstance {
|
||||
data: Vec<i64>,
|
||||
}
|
||||
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, ArrayInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, ArrayInstance>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
NYB_SUCCESS
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -49,17 +59,25 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_ARRAY { return NYB_E_INVALID_TYPE; }
|
||||
if type_id != TYPE_ID_ARRAY {
|
||||
return NYB_E_INVALID_TYPE;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
match method_id {
|
||||
METHOD_BIRTH => {
|
||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
||||
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
|
||||
if result_len.is_null() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
map.insert(id, ArrayInstance { data: Vec::new() });
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
let bytes = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
@ -69,39 +87,71 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
map.remove(&instance_id);
|
||||
NYB_SUCCESS
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_LENGTH => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
METHOD_GET => {
|
||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
if idx < 0 { return NYB_E_INVALID_ARGS; }
|
||||
let idx = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if idx < 0 {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
let i = idx as usize;
|
||||
if i >= inst.data.len() { return NYB_E_INVALID_ARGS; }
|
||||
if i >= inst.data.len() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
return write_tlv_i64(inst.data[i], result, result_len);
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
METHOD_PUSH => {
|
||||
let val = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
let val = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
inst.data.push(val);
|
||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
METHOD_SET => {
|
||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
if idx < 0 { return NYB_E_INVALID_ARGS; }
|
||||
let idx = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
let val = match read_arg_i64(args, args_len, 1) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if idx < 0 {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
let i = idx as usize;
|
||||
@ -114,8 +164,12 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
_ => NYB_E_INVALID_METHOD,
|
||||
}
|
||||
@ -136,7 +190,9 @@ pub struct NyashTypeBoxFfi {
|
||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||
|
||||
extern "C" fn array_resolve(name: *const 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();
|
||||
match s.as_ref() {
|
||||
"len" | "length" => METHOD_LENGTH,
|
||||
@ -147,26 +203,95 @@ extern "C" fn array_resolve(name: *const c_char) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn array_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
extern "C" fn array_invoke_id(
|
||||
instance_id: u32,
|
||||
method_id: u32,
|
||||
args: *const u8,
|
||||
args_len: usize,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
match method_id {
|
||||
METHOD_LENGTH => {
|
||||
if let Ok(map) = INSTANCES.lock() { if let Some(inst) = map.get(&instance_id) { return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; }
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
METHOD_GET => {
|
||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
if idx < 0 { return NYB_E_INVALID_ARGS; }
|
||||
if let Ok(map) = INSTANCES.lock() { if let Some(inst) = map.get(&instance_id) { let i = idx as usize; if i >= inst.data.len() { return NYB_E_INVALID_ARGS; } return write_tlv_i64(inst.data[i], result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; }
|
||||
let idx = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if idx < 0 {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
let i = idx as usize;
|
||||
if i >= inst.data.len() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
return write_tlv_i64(inst.data[i], result, result_len);
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
METHOD_SET => {
|
||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
if idx < 0 { return NYB_E_INVALID_ARGS; }
|
||||
if let Ok(mut map) = INSTANCES.lock() { if let Some(inst) = map.get_mut(&instance_id) { let i = idx as usize; let len = inst.data.len(); if i < len { inst.data[i] = val; } else if i == len { inst.data.push(val); } else { return NYB_E_INVALID_ARGS; } return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; }
|
||||
let idx = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
let val = match read_arg_i64(args, args_len, 1) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if idx < 0 {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
let i = idx as usize;
|
||||
let len = inst.data.len();
|
||||
if i < len {
|
||||
inst.data[i] = val;
|
||||
} else if i == len {
|
||||
inst.data.push(val);
|
||||
} else {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
METHOD_PUSH => {
|
||||
let val = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
if let Ok(mut map) = INSTANCES.lock() { if let Some(inst) = map.get_mut(&instance_id) { inst.data.push(val); return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; }
|
||||
let val = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
inst.data.push(val);
|
||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
_ => NYB_E_INVALID_METHOD,
|
||||
}
|
||||
@ -187,7 +312,9 @@ pub static nyash_typebox_ArrayBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
||||
// ===== Minimal TLV helpers (compatible with host expectations) =====
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe {
|
||||
if result_len.is_null() { return false; }
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
@ -197,8 +324,11 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
}
|
||||
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
if result_len.is_null() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
||||
for (tag, payload) in payloads {
|
||||
@ -225,19 +355,27 @@ fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
|
||||
/// Read nth TLV argument as i64 (tag 3)
|
||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; // skip header
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 { return None; }
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let _rsv = buf[off+1];
|
||||
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
||||
if buf.len() < off + 4 + size { return None; }
|
||||
let _rsv = buf[off + 1];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag != 3 || size != 8 { return None; }
|
||||
let mut b = [0u8;8];
|
||||
b.copy_from_slice(&buf[off+4 .. off+4+8]);
|
||||
if tag != 3 || size != 8 {
|
||||
return None;
|
||||
}
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(&buf[off + 4..off + 4 + 8]);
|
||||
return Some(i64::from_le_bytes(b));
|
||||
}
|
||||
off += 4 + size;
|
||||
|
||||
@ -2,9 +2,12 @@
|
||||
//! Provides simple stdout printing via ConsoleBox
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
// ===== Error Codes (BID-1) =====
|
||||
const NYB_SUCCESS: i32 = 0;
|
||||
@ -24,58 +27,126 @@ const METHOD_FINI: u32 = u32::MAX;
|
||||
const TYPE_ID_CONSOLE_BOX: u32 = 5; // keep in sync with nyash.toml [box_types]
|
||||
|
||||
// ===== Instance management =====
|
||||
struct ConsoleInstance { /* no state for now */ }
|
||||
struct ConsoleInstance {/* no state for now */}
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, ConsoleInstance>>> = Lazy::new(|| {
|
||||
Mutex::new(HashMap::new())
|
||||
});
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, ConsoleInstance>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
// ===== TLV helpers (minimal) =====
|
||||
// TLV layout: [u16 ver=1][u16 argc][entries...]
|
||||
// Entry: [u16 tag][u16 size][payload...]
|
||||
fn parse_first_string(args: &[u8]) -> Result<String, ()> {
|
||||
if args.len() < 4 { return Err(()); }
|
||||
let argc = u16::from_le_bytes([args[2], args[3]]) as usize;
|
||||
if argc == 0 { return Err(()); }
|
||||
let mut p = 4usize;
|
||||
// first entry
|
||||
if args.len() < p + 4 { return Err(()); }
|
||||
let tag = u16::from_le_bytes([args[p], args[p+1]]); p += 2;
|
||||
let sz = u16::from_le_bytes([args[p], args[p+1]]) as usize; p += 2;
|
||||
if tag != 6 && tag != 7 { // String or Bytes
|
||||
if args.len() < 4 {
|
||||
return Err(());
|
||||
}
|
||||
if args.len() < p + sz { return Err(()); }
|
||||
let s = String::from_utf8_lossy(&args[p..p+sz]).to_string();
|
||||
let argc = u16::from_le_bytes([args[2], args[3]]) as usize;
|
||||
if argc == 0 {
|
||||
return Err(());
|
||||
}
|
||||
let mut p = 4usize;
|
||||
// first entry
|
||||
if args.len() < p + 4 {
|
||||
return Err(());
|
||||
}
|
||||
let tag = u16::from_le_bytes([args[p], args[p + 1]]);
|
||||
p += 2;
|
||||
let sz = u16::from_le_bytes([args[p], args[p + 1]]) as usize;
|
||||
p += 2;
|
||||
if tag != 6 && tag != 7 {
|
||||
// String or Bytes
|
||||
return Err(());
|
||||
}
|
||||
if args.len() < p + sz {
|
||||
return Err(());
|
||||
}
|
||||
let s = String::from_utf8_lossy(&args[p..p + sz]).to_string();
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn format_first_any(args: &[u8]) -> Option<String> {
|
||||
if args.len() < 4 { return None; }
|
||||
if args.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
let mut p = 4usize;
|
||||
if args.len() < p + 4 { return None; }
|
||||
let tag = u16::from_le_bytes([args[p], args[p+1]]); p += 2;
|
||||
let sz = u16::from_le_bytes([args[p], args[p+1]]) as usize; p += 2;
|
||||
if args.len() < p + sz { return None; }
|
||||
let payload = &args[p..p+sz];
|
||||
if args.len() < p + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = u16::from_le_bytes([args[p], args[p + 1]]);
|
||||
p += 2;
|
||||
let sz = u16::from_le_bytes([args[p], args[p + 1]]) as usize;
|
||||
p += 2;
|
||||
if args.len() < p + sz {
|
||||
return None;
|
||||
}
|
||||
let payload = &args[p..p + sz];
|
||||
match tag {
|
||||
1 => Some(if sz>0 && payload[0]!=0 { "true".into() } else { "false".into() }),
|
||||
2 => { if sz!=4 { None } else { let mut b=[0u8;4]; b.copy_from_slice(payload); Some((i32::from_le_bytes(b)).to_string()) } },
|
||||
3 => { if sz!=8 { None } else { let mut b=[0u8;8]; b.copy_from_slice(payload); Some((i64::from_le_bytes(b)).to_string()) } },
|
||||
5 => { if sz!=8 { None } else { let mut b=[0u8;8]; b.copy_from_slice(payload); Some(f64::from_le_bytes(b).to_string()) } },
|
||||
6 => { std::str::from_utf8(payload).ok().map(|s| s.to_string()) },
|
||||
1 => Some(if sz > 0 && payload[0] != 0 {
|
||||
"true".into()
|
||||
} else {
|
||||
"false".into()
|
||||
}),
|
||||
2 => {
|
||||
if sz != 4 {
|
||||
None
|
||||
} else {
|
||||
let mut b = [0u8; 4];
|
||||
b.copy_from_slice(payload);
|
||||
Some((i32::from_le_bytes(b)).to_string())
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
if sz != 8 {
|
||||
None
|
||||
} else {
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(payload);
|
||||
Some((i64::from_le_bytes(b)).to_string())
|
||||
}
|
||||
}
|
||||
5 => {
|
||||
if sz != 8 {
|
||||
None
|
||||
} else {
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(payload);
|
||||
Some(f64::from_le_bytes(b).to_string())
|
||||
}
|
||||
}
|
||||
6 => std::str::from_utf8(payload).ok().map(|s| s.to_string()),
|
||||
7 => Some(format!("<bytes:{}>", sz)),
|
||||
8 => { if sz==8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); Some(format!("<handle {}:{}>", u32::from_le_bytes(t), u32::from_le_bytes(i))) } else { None } },
|
||||
8 => {
|
||||
if sz == 8 {
|
||||
let mut t = [0u8; 4];
|
||||
t.copy_from_slice(&payload[0..4]);
|
||||
let mut i = [0u8; 4];
|
||||
i.copy_from_slice(&payload[4..8]);
|
||||
Some(format!(
|
||||
"<handle {}:{}>",
|
||||
u32::from_le_bytes(t),
|
||||
u32::from_le_bytes(i)
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Write TLV birth result: Handle(tag=8,size=8) with (type_id, instance_id)
|
||||
unsafe fn write_tlv_birth(type_id: u32, instance_id: u32, out: *mut u8, out_len: *mut usize) -> i32 {
|
||||
unsafe fn write_tlv_birth(
|
||||
type_id: u32,
|
||||
instance_id: u32,
|
||||
out: *mut u8,
|
||||
out_len: *mut usize,
|
||||
) -> i32 {
|
||||
let need = 4 + 4 + 8; // header + entry + payload
|
||||
if *out_len < need { *out_len = need; return NYB_E_SHORT_BUFFER; }
|
||||
if *out_len < need {
|
||||
*out_len = need;
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
let mut buf = Vec::with_capacity(need);
|
||||
// header
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
@ -92,7 +163,10 @@ unsafe fn write_tlv_birth(type_id: u32, instance_id: u32, out: *mut u8, out_len:
|
||||
|
||||
unsafe fn write_tlv_void(out: *mut u8, out_len: *mut usize) -> i32 {
|
||||
let need = 4 + 4; // header + entry
|
||||
if *out_len < need { *out_len = need; return NYB_E_SHORT_BUFFER; }
|
||||
if *out_len < need {
|
||||
*out_len = need;
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
let mut buf = Vec::with_capacity(need);
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
@ -105,7 +179,9 @@ unsafe fn write_tlv_void(out: *mut u8, out_len: *mut usize) -> i32 {
|
||||
|
||||
// ===== Entry points =====
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
@ -123,18 +199,24 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_CONSOLE_BOX { return NYB_E_INVALID_TYPE; }
|
||||
if type_id != TYPE_ID_CONSOLE_BOX {
|
||||
return NYB_E_INVALID_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
METHOD_BIRTH => {
|
||||
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INSTANCES.lock() {
|
||||
m.insert(id, ConsoleInstance{});
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
m.insert(id, ConsoleInstance {});
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
return write_tlv_birth(TYPE_ID_CONSOLE_BOX, id, result, result_len);
|
||||
}
|
||||
METHOD_FINI => {
|
||||
if let Ok(mut m) = INSTANCES.lock() { m.remove(&instance_id); }
|
||||
if let Ok(mut m) = INSTANCES.lock() {
|
||||
m.remove(&instance_id);
|
||||
}
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
METHOD_LOG | METHOD_PRINTLN => {
|
||||
@ -143,7 +225,11 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
Ok(s) => s,
|
||||
Err(_) => format_first_any(slice).unwrap_or_else(|| "".to_string()),
|
||||
};
|
||||
if method_id == METHOD_LOG { print!("{}", s); } else { println!("{}", s); }
|
||||
if method_id == METHOD_LOG {
|
||||
print!("{}", s);
|
||||
} else {
|
||||
println!("{}", s);
|
||||
}
|
||||
return write_tlv_void(result, result_len);
|
||||
}
|
||||
_ => NYB_E_INVALID_METHOD,
|
||||
@ -165,7 +251,9 @@ pub struct NyashTypeBoxFfi {
|
||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||
|
||||
extern "C" fn console_resolve(name: *const 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();
|
||||
match s.as_ref() {
|
||||
"log" => METHOD_LOG,
|
||||
@ -174,7 +262,14 @@ extern "C" fn console_resolve(name: *const c_char) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn console_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
extern "C" fn console_invoke_id(
|
||||
instance_id: u32,
|
||||
method_id: u32,
|
||||
args: *const u8,
|
||||
args_len: usize,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
match method_id {
|
||||
METHOD_LOG | METHOD_PRINTLN => {
|
||||
@ -183,7 +278,11 @@ extern "C" fn console_invoke_id(instance_id: u32, method_id: u32, args: *const u
|
||||
Ok(s) => s,
|
||||
Err(_) => format_first_any(slice).unwrap_or_else(|| "".to_string()),
|
||||
};
|
||||
if method_id == METHOD_LOG { print!("{}", s); } else { println!("{}", s); }
|
||||
if method_id == METHOD_LOG {
|
||||
print!("{}", s);
|
||||
} else {
|
||||
println!("{}", s);
|
||||
}
|
||||
return write_tlv_void(result, result_len);
|
||||
}
|
||||
_ => NYB_E_INVALID_METHOD,
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
//! Nyash CounterBox Plugin - BID-FFI v1 Implementation
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
@ -24,16 +27,23 @@ const METHOD_FINI: u32 = u32::MAX; // destructor
|
||||
const TYPE_ID_COUNTER: u32 = 7;
|
||||
|
||||
// ===== Instance state =====
|
||||
struct CounterInstance { count: i32 }
|
||||
struct CounterInstance {
|
||||
count: i32,
|
||||
}
|
||||
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, CounterInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, CounterInstance>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
NYB_SUCCESS
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -45,18 +55,26 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_COUNTER { return NYB_E_INVALID_TYPE; }
|
||||
if type_id != TYPE_ID_COUNTER {
|
||||
return NYB_E_INVALID_TYPE;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
match method_id {
|
||||
METHOD_BIRTH => {
|
||||
// Return new instance handle (u32 id)
|
||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
||||
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
|
||||
if result_len.is_null() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
map.insert(id, CounterInstance { count: 0 });
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
let bytes = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
@ -66,7 +84,9 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
map.remove(&instance_id);
|
||||
NYB_SUCCESS
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_INC => {
|
||||
// increments and returns new count as I32 TLV
|
||||
@ -74,18 +94,30 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
inst.count += 1;
|
||||
let v = inst.count;
|
||||
if preflight(result, result_len, 12) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(result, result_len, 12) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
return write_tlv_i32(v, result, result_len);
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
METHOD_GET => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
if preflight(result, result_len, 12) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(result, result_len, 12) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
return write_tlv_i32(inst.count, result, result_len);
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
_ => NYB_E_INVALID_METHOD,
|
||||
}
|
||||
@ -94,8 +126,11 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
|
||||
// ===== TLV helpers =====
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
if result_len.is_null() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
||||
for (tag, payload) in payloads {
|
||||
@ -122,7 +157,9 @@ fn write_tlv_i32(v: i32, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe {
|
||||
if result_len.is_null() { return false; }
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
@ -130,4 +167,3 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,13 @@
|
||||
//! - Windows GUI integration (egui/eframe) can be enabled later via `with-egui` feature
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashMap, sync::{Mutex, atomic::{AtomicU32, Ordering}}};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
// ===== Error/Status codes (BID-FFI v1 aligned) =====
|
||||
const OK: i32 = 0;
|
||||
@ -56,7 +62,9 @@ const ABI_TAG: u32 = 0x58594254; // 'T''Y''B''X' little-endian (TYBX)
|
||||
|
||||
extern "C" fn tb_resolve(name: *const std::os::raw::c_char) -> u32 {
|
||||
unsafe {
|
||||
if name.is_null() { return 0; }
|
||||
if name.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let s = std::ffi::CStr::from_ptr(name).to_string_lossy();
|
||||
match s.as_ref() {
|
||||
"birth" => M_BIRTH,
|
||||
@ -72,8 +80,23 @@ extern "C" fn tb_resolve(name: *const std::os::raw::c_char) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn tb_invoke_id(_method_id: u32, instance_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
nyash_plugin_invoke(TID_EGUI, _method_id, instance_id, args, args_len, result, result_len)
|
||||
extern "C" fn tb_invoke_id(
|
||||
_method_id: u32,
|
||||
instance_id: u32,
|
||||
args: *const u8,
|
||||
args_len: usize,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
nyash_plugin_invoke(
|
||||
TID_EGUI,
|
||||
_method_id,
|
||||
instance_id,
|
||||
args,
|
||||
args_len,
|
||||
result,
|
||||
result_len,
|
||||
)
|
||||
}
|
||||
|
||||
static TYPE_NAME: &[u8] = b"EguiBox\0";
|
||||
@ -90,10 +113,14 @@ pub static nyash_typebox_EguiBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
||||
|
||||
// ===== Plugin entry points =====
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -105,34 +132,81 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TID_EGUI { return E_TYPE; }
|
||||
if type_id != TID_EGUI {
|
||||
return E_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_BIRTH => {
|
||||
let need = 4; // instance_id (u32 LE)
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
if result.is_null() || *result_len < need { *result_len = need; return E_SHORT; }
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() { m.insert(id, EguiInstance::default()); } else { return E_FAIL; }
|
||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
if result.is_null() || *result_len < need {
|
||||
*result_len = need;
|
||||
return E_SHORT;
|
||||
}
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.insert(id, EguiInstance::default());
|
||||
} else {
|
||||
return E_FAIL;
|
||||
}
|
||||
let b = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
OK
|
||||
}
|
||||
M_FINI => {
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.remove(&instance_id);
|
||||
OK
|
||||
} else {
|
||||
E_FAIL
|
||||
}
|
||||
}
|
||||
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_FAIL } }
|
||||
M_OPEN => {
|
||||
eprintln!("[EGUI] M_OPEN invoked");
|
||||
let (w, h, title) = match tlv_read_open_args(args, args_len) { Some(v) => v, None => return E_ARGS };
|
||||
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.width=w; inst.height=h; inst.title=title; } else { return E_FAIL; } } else { return E_FAIL; }
|
||||
let (w, h, title) = match tlv_read_open_args(args, args_len) {
|
||||
Some(v) => v,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
if let Some(inst) = m.get_mut(&instance_id) {
|
||||
inst.width = w;
|
||||
inst.height = h;
|
||||
inst.title = title;
|
||||
} else {
|
||||
return E_FAIL;
|
||||
}
|
||||
} else {
|
||||
return E_FAIL;
|
||||
}
|
||||
write_tlv_void(result, result_len)
|
||||
}
|
||||
M_UI_LABEL => {
|
||||
eprintln!("[EGUI] M_UI_LABEL invoked");
|
||||
let text = match tlv_read_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.labels.push(text); } else { return E_FAIL; } } else { return E_FAIL; }
|
||||
let text = match tlv_read_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
if let Some(inst) = m.get_mut(&instance_id) {
|
||||
inst.labels.push(text);
|
||||
} else {
|
||||
return E_FAIL;
|
||||
}
|
||||
} else {
|
||||
return E_FAIL;
|
||||
}
|
||||
write_tlv_void(result, result_len)
|
||||
}
|
||||
M_UI_BUTTON => {
|
||||
eprintln!("[EGUI] M_UI_BUTTON invoked");
|
||||
// For now: stub, accept and return Void
|
||||
if tlv_read_string(args, args_len, 0).is_none() { return E_ARGS; }
|
||||
if tlv_read_string(args, args_len, 0).is_none() {
|
||||
return E_ARGS;
|
||||
}
|
||||
write_tlv_void(result, result_len)
|
||||
}
|
||||
M_POLL_EVENT => {
|
||||
@ -147,7 +221,12 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
{
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
guirun::run_window(inst.width, inst.height, &inst.title, inst.labels.clone());
|
||||
guirun::run_window(
|
||||
inst.width,
|
||||
inst.height,
|
||||
&inst.title,
|
||||
inst.labels.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,54 +244,108 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
|
||||
// ===== TLV helpers (version=1) =====
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads {
|
||||
buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload);
|
||||
buf.push(*tag);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(payload);
|
||||
}
|
||||
unsafe {
|
||||
let need = buf.len();
|
||||
if result.is_null() || *result_len < need { *result_len = need; return E_SHORT; }
|
||||
if result.is_null() || *result_len < need {
|
||||
*result_len = need;
|
||||
return E_SHORT;
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, need);
|
||||
*result_len = need;
|
||||
}
|
||||
OK
|
||||
}
|
||||
fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(9u8, &[])], result, result_len) }
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
||||
fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(9u8, &[])], result, result_len)
|
||||
}
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||
}
|
||||
|
||||
unsafe fn tlv_parse_header(data: *const u8, len: usize) -> Option<(u16,u16,usize)> {
|
||||
if data.is_null() || len < 4 { return None; }
|
||||
unsafe fn tlv_parse_header(data: *const u8, len: usize) -> Option<(u16, u16, usize)> {
|
||||
if data.is_null() || len < 4 {
|
||||
return None;
|
||||
}
|
||||
let b = std::slice::from_raw_parts(data, len);
|
||||
let ver = u16::from_le_bytes([b[0], b[1]]);
|
||||
let argc = u16::from_le_bytes([b[2], b[3]]);
|
||||
if ver != 1 { return None; }
|
||||
if ver != 1 {
|
||||
return None;
|
||||
}
|
||||
Some((ver, argc, 4))
|
||||
}
|
||||
unsafe fn tlv_read_entry_at(data: *const u8, len: usize, mut pos: usize) -> Option<(u8, usize, usize)> {
|
||||
unsafe fn tlv_read_entry_at(
|
||||
data: *const u8,
|
||||
len: usize,
|
||||
mut pos: usize,
|
||||
) -> Option<(u8, usize, usize)> {
|
||||
let b = std::slice::from_raw_parts(data, len);
|
||||
if pos + 4 > len { return None; }
|
||||
let tag = b[pos]; let _ = b[pos+1]; let size = u16::from_le_bytes([b[pos+2], b[pos+3]]) as usize; pos += 4;
|
||||
if pos + size > len { return None; }
|
||||
if pos + 4 > len {
|
||||
return None;
|
||||
}
|
||||
let tag = b[pos];
|
||||
let _ = b[pos + 1];
|
||||
let size = u16::from_le_bytes([b[pos + 2], b[pos + 3]]) as usize;
|
||||
pos += 4;
|
||||
if pos + size > len {
|
||||
return None;
|
||||
}
|
||||
Some((tag, size, pos))
|
||||
}
|
||||
unsafe fn tlv_read_i64(data: *const u8, len: usize, index: usize) -> Option<i64> {
|
||||
let (_, argc, mut pos) = tlv_parse_header(data, len)?; if argc < (index as u16 + 1) { return None; }
|
||||
for i in 0..=index { let (tag, size, p) = tlv_read_entry_at(data, len, pos)?; if tag == 3 && size == 8 { if i == index { let b = std::slice::from_raw_parts(data.add(p), 8); let mut t=[0u8;8]; t.copy_from_slice(b); return Some(i64::from_le_bytes(t)); } } pos = p + size; }
|
||||
let (_, argc, mut pos) = tlv_parse_header(data, len)?;
|
||||
if argc < (index as u16 + 1) {
|
||||
return None;
|
||||
}
|
||||
for i in 0..=index {
|
||||
let (tag, size, p) = tlv_read_entry_at(data, len, pos)?;
|
||||
if tag == 3 && size == 8 {
|
||||
if i == index {
|
||||
let b = std::slice::from_raw_parts(data.add(p), 8);
|
||||
let mut t = [0u8; 8];
|
||||
t.copy_from_slice(b);
|
||||
return Some(i64::from_le_bytes(t));
|
||||
}
|
||||
}
|
||||
pos = p + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
unsafe fn tlv_read_string(data: *const u8, len: usize, index: usize) -> Option<String> {
|
||||
let (_, argc, mut pos) = tlv_parse_header(data, len)?; if argc < (index as u16 + 1) { return None; }
|
||||
for i in 0..=index { let (tag, size, p) = tlv_read_entry_at(data, len, pos)?; if tag == 6 || tag == 7 { if i == index { let s = std::slice::from_raw_parts(data.add(p), size); return Some(String::from_utf8_lossy(s).to_string()); } } pos = p + size; }
|
||||
let (_, argc, mut pos) = tlv_parse_header(data, len)?;
|
||||
if argc < (index as u16 + 1) {
|
||||
return None;
|
||||
}
|
||||
for i in 0..=index {
|
||||
let (tag, size, p) = tlv_read_entry_at(data, len, pos)?;
|
||||
if tag == 6 || tag == 7 {
|
||||
if i == index {
|
||||
let s = std::slice::from_raw_parts(data.add(p), size);
|
||||
return Some(String::from_utf8_lossy(s).to_string());
|
||||
}
|
||||
}
|
||||
pos = p + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
unsafe fn tlv_read_open_args(args: *const u8, len: usize) -> Option<(i32,i32,String)> {
|
||||
unsafe fn tlv_read_open_args(args: *const u8, len: usize) -> Option<(i32, i32, String)> {
|
||||
let w = tlv_read_i64(args, len, 0)? as i32;
|
||||
let h = tlv_read_i64(args, len, 1)? as i32;
|
||||
let t = tlv_read_string(args, len, 2)?;
|
||||
Some((w,h,t))
|
||||
Some((w, h, t))
|
||||
}
|
||||
|
||||
// ===== GUI 実行(with-egui, クロスプラットフォーム) =====
|
||||
@ -224,7 +357,9 @@ mod guirun {
|
||||
pub fn run_window(w: i32, h: i32, title: &str, labels: Vec<String>) {
|
||||
eprintln!("[EGUI] run_window: w={} h={} title='{}'", w, h, title);
|
||||
let diag = std::env::var("NYASH_EGUI_DIAG").ok().as_deref() == Some("1");
|
||||
let scale_override = std::env::var("NYASH_EGUI_SCALE").ok().and_then(|s| s.parse::<f32>().ok());
|
||||
let scale_override = std::env::var("NYASH_EGUI_SCALE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<f32>().ok());
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([w.max(100) as f32, h.max(100) as f32])
|
||||
@ -232,7 +367,14 @@ mod guirun {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
struct App { labels: Vec<String>, diag: bool, printed: bool, init_w: i32, init_h: i32, scale: Option<f32> }
|
||||
struct App {
|
||||
labels: Vec<String>,
|
||||
diag: bool,
|
||||
printed: bool,
|
||||
init_w: i32,
|
||||
init_h: i32,
|
||||
scale: Option<f32>,
|
||||
}
|
||||
impl eframe::App for App {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
if self.diag && !self.printed {
|
||||
@ -252,13 +394,21 @@ mod guirun {
|
||||
let rect = ctx.screen_rect();
|
||||
ui.small(format!(
|
||||
"DPI: ppp={:.3} logical={:.1}x{:.1} init={}x{} scale={}",
|
||||
ppp, rect.width(), rect.height(), self.init_w, self.init_h,
|
||||
self.scale.map(|v| format!("{:.2}", v)).unwrap_or_else(|| "auto".into())
|
||||
ppp,
|
||||
rect.width(),
|
||||
rect.height(),
|
||||
self.init_w,
|
||||
self.init_h,
|
||||
self.scale
|
||||
.map(|v| format!("{:.2}", v))
|
||||
.unwrap_or_else(|| "auto".into())
|
||||
));
|
||||
}
|
||||
});
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
for s in &self.labels { ui.label(s); }
|
||||
for s in &self.labels {
|
||||
ui.label(s);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -277,7 +427,14 @@ mod guirun {
|
||||
cc.egui_ctx.set_pixels_per_point(ppp);
|
||||
eprintln!("[EGUI][diag] override pixels_per_point to {:.3}", ppp);
|
||||
}
|
||||
Box::new(App { labels, diag, printed: false, init_w, init_h, scale: scale_override })
|
||||
Box::new(App {
|
||||
labels,
|
||||
diag,
|
||||
printed: false,
|
||||
init_w,
|
||||
init_h,
|
||||
scale: scale_override,
|
||||
})
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
const OK: i32 = 0;
|
||||
const E_SHORT: i32 = -1;
|
||||
@ -30,10 +33,14 @@ static INST: Lazy<Mutex<HashMap<u32, EncInstance>>> = Lazy::new(|| Mutex::new(Ha
|
||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -45,44 +52,97 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_ENCODING { return E_TYPE; }
|
||||
if type_id != TYPE_ID_ENCODING {
|
||||
return E_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_BIRTH => {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() { m.insert(id, EncInstance); } else { return E_PLUGIN; }
|
||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return E_SHORT;
|
||||
}
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.insert(id, EncInstance);
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
let b = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
OK
|
||||
}
|
||||
M_FINI => {
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.remove(&instance_id);
|
||||
OK
|
||||
} else {
|
||||
E_PLUGIN
|
||||
}
|
||||
}
|
||||
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
|
||||
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)
|
||||
}
|
||||
M_FROM_UTF8_BYTES => {
|
||||
let bytes = match read_arg_bytes(args, args_len, 0) { 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) }
|
||||
let bytes = match read_arg_bytes(args, args_len, 0) {
|
||||
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 => {
|
||||
if let Some(b) = read_arg_bytes(args, args_len, 0) { 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 };
|
||||
if let Some(b) = read_arg_bytes(args, args_len, 0) {
|
||||
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());
|
||||
write_tlv_string(&enc, result, result_len)
|
||||
}
|
||||
M_BASE64_DEC => {
|
||||
let s = match read_arg_string(args, args_len, 0) { 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) }
|
||||
let s = match read_arg_string(args, args_len, 0) {
|
||||
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 => {
|
||||
if let Some(b) = read_arg_bytes(args, args_len, 0) { 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 };
|
||||
if let Some(b) = read_arg_bytes(args, args_len, 0) {
|
||||
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());
|
||||
write_tlv_string(&enc, result, result_len)
|
||||
}
|
||||
M_HEX_DEC => {
|
||||
let s = match read_arg_string(args, args_len, 0) { 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) }
|
||||
let s = match read_arg_string(args, args_len, 0) {
|
||||
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,
|
||||
}
|
||||
@ -90,29 +150,98 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
}
|
||||
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
|
||||
unsafe {
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
|
||||
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads {
|
||||
buf.push(*tag);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(payload);
|
||||
}
|
||||
unsafe {
|
||||
let needed = buf.len();
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return E_SHORT;
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||
*result_len = needed;
|
||||
}
|
||||
OK
|
||||
}
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
||||
fn write_tlv_bytes(b: &[u8], result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(7u8, b)], result, result_len) }
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||
}
|
||||
fn write_tlv_bytes(b: &[u8], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(7u8, b)], result, result_len)
|
||||
}
|
||||
|
||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; }
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag == 6 || tag == 7 {
|
||||
return Some(String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
fn read_arg_bytes(args: *const u8, args_len: usize, n: usize) -> Option<Vec<u8>> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 7 || tag == 6 { return Some(buf[off+4..off+4+size].to_vec()); } else { return None; } } off += 4 + size; }
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag == 7 || tag == 6 {
|
||||
return Some(buf[off + 4..off + 4 + size].to_vec());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write, Seek};
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
/// プラグインのマジックナンバー('NYAS')
|
||||
@ -88,12 +88,10 @@ pub unsafe extern "C" fn nyash_file_read(handle: *mut c_void) -> *mut c_char {
|
||||
}
|
||||
|
||||
match file_box.file.read_to_string(&mut content) {
|
||||
Ok(_) => {
|
||||
match CString::new(content) {
|
||||
Ok(_) => match CString::new(content) {
|
||||
Ok(c_str) => c_str.into_raw(),
|
||||
Err(_) => std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
@ -104,10 +102,7 @@ pub unsafe extern "C" fn nyash_file_read(handle: *mut c_void) -> *mut c_char {
|
||||
/// - handleはnyash_file_openから返された有効なポインタである必要がある
|
||||
/// - contentは有効なUTF-8のC文字列である必要がある
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn nyash_file_write(
|
||||
handle: *mut c_void,
|
||||
content: *const c_char
|
||||
) -> c_int {
|
||||
pub unsafe extern "C" fn nyash_file_write(handle: *mut c_void, content: *const c_char) -> c_int {
|
||||
if handle.is_null() || content.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
use std::collections::HashMap;
|
||||
use std::os::raw::c_char;
|
||||
// std::ptr削除(未使用)
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::io::{Read, Write, Seek, SeekFrom};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
// ============ FFI Types ============
|
||||
|
||||
@ -56,9 +59,8 @@ struct FileBoxInstance {
|
||||
|
||||
// グローバルインスタンス管理(実際の実装ではより安全な方法を使用)
|
||||
use once_cell::sync::Lazy;
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, FileBoxInstance>>> = Lazy::new(|| {
|
||||
Mutex::new(HashMap::new())
|
||||
});
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, FileBoxInstance>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
// ホスト関数テーブルは使用しない(Host VTable廃止)
|
||||
|
||||
@ -119,11 +121,14 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
// 新しいインスタンスを作成
|
||||
let instance_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
map.insert(instance_id, FileBoxInstance {
|
||||
map.insert(
|
||||
instance_id,
|
||||
FileBoxInstance {
|
||||
file: None,
|
||||
path: String::new(),
|
||||
buffer: None,
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
@ -149,7 +154,9 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
match tlv_parse_two_strings(args) {
|
||||
Ok((path, mode)) => {
|
||||
// Preflight for Void TLV: header(4) + entry(4)
|
||||
if preflight(_result, _result_len, 8) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(_result, _result_len, 8) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
log_info(&format!("OPEN path='{}' mode='{}'", path, mode));
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&_instance_id) {
|
||||
@ -161,8 +168,12 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
}
|
||||
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||
}
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
Err(_) => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
@ -172,18 +183,20 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
let args = std::slice::from_raw_parts(_args, _args_len);
|
||||
if _args_len > 0 {
|
||||
match tlv_parse_string(args) {
|
||||
Ok(path) => {
|
||||
match open_file("r", &path) {
|
||||
Ok(path) => match open_file("r", &path) {
|
||||
Ok(mut file) => {
|
||||
let mut buf = Vec::new();
|
||||
if let Err(_) = file.read_to_end(&mut buf) { return NYB_E_PLUGIN_ERROR; }
|
||||
if let Err(_) = file.read_to_end(&mut buf) {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
let need = 8usize.saturating_add(buf.len());
|
||||
if preflight(_result, _result_len, need) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(_result, _result_len, need) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
return write_tlv_bytes(&buf, _result, _result_len);
|
||||
}
|
||||
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => return NYB_E_INVALID_ARGS,
|
||||
}
|
||||
} else {
|
||||
@ -196,14 +209,22 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
Ok(n) => {
|
||||
log_info(&format!("READ {} bytes (entire file)", n));
|
||||
let need = 8usize.saturating_add(buf.len());
|
||||
if preflight(_result, _result_len, need) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(_result, _result_len, need) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
return write_tlv_bytes(&buf, _result, _result_len);
|
||||
}
|
||||
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||
}
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
METHOD_WRITE => {
|
||||
@ -211,40 +232,58 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
let args = std::slice::from_raw_parts(_args, _args_len);
|
||||
match tlv_parse_optional_string_and_bytes(args) {
|
||||
Ok((Some(path), data)) => {
|
||||
if preflight(_result, _result_len, 12) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(_result, _result_len, 12) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
match open_file("w", &path) {
|
||||
Ok(mut file) => {
|
||||
if let Err(_) = file.write_all(&data) { return NYB_E_PLUGIN_ERROR; }
|
||||
if let Err(_) = file.flush() { return NYB_E_PLUGIN_ERROR; }
|
||||
if let Err(_) = file.write_all(&data) {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
if let Err(_) = file.flush() {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
return write_tlv_i32(data.len() as i32, _result, _result_len);
|
||||
}
|
||||
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||
}
|
||||
}
|
||||
Ok((None, data)) => {
|
||||
if preflight(_result, _result_len, 12) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(_result, _result_len, 12) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&_instance_id) {
|
||||
if let Some(file) = inst.file.as_mut() {
|
||||
match file.write(&data) {
|
||||
Ok(n) => {
|
||||
if let Err(_) = file.flush() { return NYB_E_PLUGIN_ERROR; }
|
||||
if let Err(_) = file.flush() {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
log_info(&format!("WRITE {} bytes", n));
|
||||
inst.buffer = Some(data.clone());
|
||||
return write_tlv_i32(n as i32, _result, _result_len);
|
||||
}
|
||||
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||
}
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
Err(_) => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
}
|
||||
METHOD_CLOSE => {
|
||||
// Preflight for Void TLV
|
||||
if preflight(_result, _result_len, 8) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(_result, _result_len, 8) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
log_info("CLOSE");
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&_instance_id) {
|
||||
@ -262,8 +301,12 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
let args = std::slice::from_raw_parts(_args, _args_len);
|
||||
match tlv_parse_handle(args) {
|
||||
Ok((type_id, other_id)) => {
|
||||
if type_id != _type_id { return NYB_E_INVALID_TYPE; }
|
||||
if preflight(_result, _result_len, 8) { return NYB_E_SHORT_BUFFER; }
|
||||
if type_id != _type_id {
|
||||
return NYB_E_INVALID_TYPE;
|
||||
}
|
||||
if preflight(_result, _result_len, 8) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
// 1) まずsrcからデータを取り出す(不変参照のみ)
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
@ -283,21 +326,31 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
read_ok = true;
|
||||
}
|
||||
}
|
||||
if !read_ok { return NYB_E_PLUGIN_ERROR; }
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
if !read_ok {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// 2) dstへ書き込み(可変参照)
|
||||
if let Some(dst) = map.get_mut(&_instance_id) {
|
||||
if let Some(fdst) = dst.file.as_mut() {
|
||||
let _ = fdst.seek(SeekFrom::Start(0));
|
||||
if fdst.write_all(&data).is_err() { return NYB_E_PLUGIN_ERROR; }
|
||||
if fdst.write_all(&data).is_err() {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
let _ = fdst.set_len(data.len() as u64);
|
||||
let _ = fdst.flush();
|
||||
}
|
||||
dst.buffer = Some(data);
|
||||
return write_tlv_void(_result, _result_len);
|
||||
} else { return NYB_E_INVALID_HANDLE; }
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
} else {
|
||||
return NYB_E_INVALID_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
}
|
||||
Err(_) => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
@ -305,11 +358,22 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
METHOD_CLONE_SELF => {
|
||||
// Return a new instance (handle) as TLV Handle
|
||||
// Preflight for Handle TLV: header(4) + entry(4) + payload(8)
|
||||
if preflight(_result, _result_len, 16) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(_result, _result_len, 16) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
let new_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut map) = INSTANCES.lock() { map.insert(new_id, FileBoxInstance { file: None, path: String::new(), buffer: None }); }
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
map.insert(
|
||||
new_id,
|
||||
FileBoxInstance {
|
||||
file: None,
|
||||
path: String::new(),
|
||||
buffer: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
// Build TLV result
|
||||
let mut payload = [0u8;8];
|
||||
let mut payload = [0u8; 8];
|
||||
payload[0..4].copy_from_slice(&_type_id.to_le_bytes());
|
||||
payload[4..8].copy_from_slice(&new_id.to_le_bytes());
|
||||
return write_tlv_result(&[(8u8, &payload)], _result, _result_len);
|
||||
@ -320,13 +384,15 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
match tlv_parse_string(args) {
|
||||
Ok(path) => {
|
||||
let exists = std::path::Path::new(&path).exists();
|
||||
if preflight(_result, _result_len, 9) { return NYB_E_SHORT_BUFFER; }
|
||||
if preflight(_result, _result_len, 9) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
return write_tlv_bool(exists, _result, _result_len);
|
||||
}
|
||||
Err(_) => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
}
|
||||
_ => NYB_SUCCESS
|
||||
_ => NYB_SUCCESS,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -337,16 +403,27 @@ fn open_file(mode: &str, path: &str) -> Result<std::fs::File, std::io::Error> {
|
||||
use std::fs::OpenOptions;
|
||||
match mode {
|
||||
"r" => OpenOptions::new().read(true).open(path),
|
||||
"w" => OpenOptions::new().write(true).create(true).truncate(true).open(path),
|
||||
"w" => OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path),
|
||||
"a" => OpenOptions::new().append(true).create(true).open(path),
|
||||
"rw" | "r+" => OpenOptions::new().read(true).write(true).create(true).open(path),
|
||||
"rw" | "r+" => OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path),
|
||||
_ => OpenOptions::new().read(true).open(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
if result_len.is_null() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
||||
for (tag, payload) in payloads {
|
||||
@ -380,13 +457,15 @@ fn write_tlv_i32(v: i32, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
}
|
||||
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
let b = [if v {1u8} else {0u8}];
|
||||
let b = [if v { 1u8 } else { 0u8 }];
|
||||
write_tlv_result(&[(1u8, &b)], result, result_len)
|
||||
}
|
||||
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe {
|
||||
if result_len.is_null() { return false; }
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
@ -395,58 +474,90 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn tlv_parse_header(data: &[u8]) -> Result<(u16,u16,usize), ()> {
|
||||
if data.len() < 4 { return Err(()); }
|
||||
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(()); }
|
||||
if ver != 1 {
|
||||
return Err(());
|
||||
}
|
||||
Ok((ver, argc, 4))
|
||||
}
|
||||
|
||||
fn tlv_parse_two_strings(data: &[u8]) -> Result<(String, String), ()> {
|
||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||
if argc < 2 { return Err(()); }
|
||||
if argc < 2 {
|
||||
return Err(());
|
||||
}
|
||||
let s1 = tlv_parse_string_at(data, &mut pos)?;
|
||||
let s2 = tlv_parse_string_at(data, &mut pos)?;
|
||||
Ok((s1, s2))
|
||||
}
|
||||
|
||||
fn tlv_parse_string_at(data: &[u8], pos: &mut usize) -> Result<String, ()> {
|
||||
if *pos + 4 > data.len() { return Err(()); }
|
||||
let tag = data[*pos]; let _res = data[*pos+1];
|
||||
let size = u16::from_le_bytes([data[*pos+2], data[*pos+3]]) as usize;
|
||||
if *pos + 4 > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let tag = data[*pos];
|
||||
let _res = data[*pos + 1];
|
||||
let size = u16::from_le_bytes([data[*pos + 2], data[*pos + 3]]) as usize;
|
||||
*pos += 4;
|
||||
if tag != 6 || *pos + size > data.len() { return Err(()); }
|
||||
let slice = &data[*pos..*pos+size];
|
||||
if tag != 6 || *pos + size > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let slice = &data[*pos..*pos + size];
|
||||
*pos += size;
|
||||
std::str::from_utf8(slice).map(|s| s.to_string()).map_err(|_| ())
|
||||
std::str::from_utf8(slice)
|
||||
.map(|s| s.to_string())
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
fn tlv_parse_i32(data: &[u8]) -> Result<i32, ()> {
|
||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||
if argc < 1 { return Err(()); }
|
||||
if pos + 8 > data.len() { return Err(()); }
|
||||
let tag = data[pos]; let _res = data[pos+1];
|
||||
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
|
||||
if tag != 2 || size != 4 || pos + size > data.len() { return Err(()); }
|
||||
let mut b = [0u8;4]; b.copy_from_slice(&data[pos..pos+4]);
|
||||
if argc < 1 {
|
||||
return Err(());
|
||||
}
|
||||
if pos + 8 > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let tag = data[pos];
|
||||
let _res = data[pos + 1];
|
||||
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
|
||||
pos += 4;
|
||||
if tag != 2 || size != 4 || pos + size > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let mut b = [0u8; 4];
|
||||
b.copy_from_slice(&data[pos..pos + 4]);
|
||||
Ok(i32::from_le_bytes(b))
|
||||
}
|
||||
|
||||
fn tlv_parse_bytes(data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||
if argc < 1 { return Err(()); }
|
||||
if pos + 4 > data.len() { return Err(()); }
|
||||
let tag = data[pos]; let _res = data[pos+1];
|
||||
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
|
||||
if argc < 1 {
|
||||
return Err(());
|
||||
}
|
||||
if pos + 4 > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let tag = data[pos];
|
||||
let _res = data[pos + 1];
|
||||
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
|
||||
pos += 4;
|
||||
// StringタグもBytesタグも受け付ける(互換性のため)
|
||||
if (tag != 6 && tag != 7) || pos + size > data.len() { return Err(()); }
|
||||
Ok(data[pos..pos+size].to_vec())
|
||||
if (tag != 6 && tag != 7) || pos + size > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
Ok(data[pos..pos + size].to_vec())
|
||||
}
|
||||
|
||||
fn tlv_parse_string(data: &[u8]) -> Result<String, ()> {
|
||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||
if argc < 1 { return Err(()); }
|
||||
if argc < 1 {
|
||||
return Err(());
|
||||
}
|
||||
tlv_parse_string_at(data, &mut pos)
|
||||
}
|
||||
|
||||
@ -454,18 +565,30 @@ fn tlv_parse_optional_string_and_bytes(data: &[u8]) -> Result<(Option<String>, V
|
||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||
if argc == 1 {
|
||||
// only bytes
|
||||
if pos + 4 > data.len() { return Err(()); }
|
||||
let tag = data[pos]; let _res = data[pos+1];
|
||||
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
|
||||
if (tag != 6 && tag != 7) || pos + size > data.len() { return Err(()); }
|
||||
return Ok((None, data[pos..pos+size].to_vec()));
|
||||
if pos + 4 > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let tag = data[pos];
|
||||
let _res = data[pos + 1];
|
||||
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
|
||||
pos += 4;
|
||||
if (tag != 6 && tag != 7) || pos + size > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
return Ok((None, data[pos..pos + size].to_vec()));
|
||||
} else if argc >= 2 {
|
||||
let s = tlv_parse_string_at(data, &mut pos)?;
|
||||
if pos + 4 > data.len() { return Err(()); }
|
||||
let tag = data[pos]; let _res = data[pos+1];
|
||||
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
|
||||
if (tag != 6 && tag != 7) || pos + size > data.len() { return Err(()); }
|
||||
Ok((Some(s), data[pos..pos+size].to_vec()))
|
||||
if pos + 4 > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let tag = data[pos];
|
||||
let _res = data[pos + 1];
|
||||
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
|
||||
pos += 4;
|
||||
if (tag != 6 && tag != 7) || pos + size > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
Ok((Some(s), data[pos..pos + size].to_vec()))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
@ -473,13 +596,23 @@ fn tlv_parse_optional_string_and_bytes(data: &[u8]) -> Result<(Option<String>, V
|
||||
|
||||
fn tlv_parse_handle(data: &[u8]) -> Result<(u32, u32), ()> {
|
||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||
if argc < 1 { return Err(()); }
|
||||
if pos + 4 > data.len() { return Err(()); }
|
||||
let tag = data[pos]; let _res = data[pos+1];
|
||||
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
|
||||
if tag != 8 || size != 8 || pos + size > data.len() { return Err(()); }
|
||||
let mut t = [0u8;4]; t.copy_from_slice(&data[pos..pos+4]);
|
||||
let mut i = [0u8;4]; i.copy_from_slice(&data[pos+4..pos+8]);
|
||||
if argc < 1 {
|
||||
return Err(());
|
||||
}
|
||||
if pos + 4 > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let tag = data[pos];
|
||||
let _res = data[pos + 1];
|
||||
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
|
||||
pos += 4;
|
||||
if tag != 8 || size != 8 || pos + size > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let mut t = [0u8; 4];
|
||||
t.copy_from_slice(&data[pos..pos + 4]);
|
||||
let mut i = [0u8; 4];
|
||||
i.copy_from_slice(&data[pos + 4..pos + 8]);
|
||||
Ok((u32::from_le_bytes(t), u32::from_le_bytes(i)))
|
||||
}
|
||||
|
||||
|
||||
@ -3,9 +3,12 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::os::raw::c_char;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
// Error codes
|
||||
const OK: i32 = 0;
|
||||
@ -25,16 +28,22 @@ const M_FINI: u32 = u32::MAX;
|
||||
// Assigned type id (nyash.toml must match)
|
||||
const TYPE_ID_INTEGER: u32 = 12;
|
||||
|
||||
struct IntInstance { value: i64 }
|
||||
struct IntInstance {
|
||||
value: i64,
|
||||
}
|
||||
|
||||
static INST: Lazy<Mutex<HashMap<u32, IntInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -46,29 +55,65 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_INTEGER { return E_TYPE; }
|
||||
if type_id != TYPE_ID_INTEGER {
|
||||
return E_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_BIRTH => {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return E_SHORT;
|
||||
}
|
||||
// Optional initial value from first arg (i64/i32)
|
||||
let init = read_arg_i64(args, args_len, 0).unwrap_or(0);
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() { m.insert(id, IntInstance { value: init }); }
|
||||
else { return E_PLUGIN; }
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.insert(id, IntInstance { value: init });
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
let b = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
OK
|
||||
}
|
||||
M_FINI => {
|
||||
if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN }
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.remove(&instance_id);
|
||||
OK
|
||||
} else {
|
||||
E_PLUGIN
|
||||
}
|
||||
}
|
||||
M_GET => {
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
return write_tlv_i64(inst.value, result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_SET => {
|
||||
let v = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
||||
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.value = v; return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
let v = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
if let Some(inst) = m.get_mut(&instance_id) {
|
||||
inst.value = v;
|
||||
return write_tlv_i64(inst.value, result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
_ => E_METHOD,
|
||||
}
|
||||
@ -89,7 +134,9 @@ pub struct NyashTypeBoxFfi {
|
||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||
|
||||
extern "C" fn integer_resolve(name: *const 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();
|
||||
match s.as_ref() {
|
||||
"get" => M_GET,
|
||||
@ -98,15 +145,42 @@ extern "C" fn integer_resolve(name: *const c_char) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn integer_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
extern "C" fn integer_invoke_id(
|
||||
instance_id: u32,
|
||||
method_id: u32,
|
||||
args: *const u8,
|
||||
args_len: usize,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_GET => {
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
return write_tlv_i64(inst.value, result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_SET => {
|
||||
let v = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
||||
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.value = v; return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
let v = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
if let Some(inst) = m.get_mut(&instance_id) {
|
||||
inst.value = v;
|
||||
return write_tlv_i64(inst.value, result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
_ => E_METHOD,
|
||||
}
|
||||
@ -125,34 +199,76 @@ pub static nyash_typebox_IntegerBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
||||
};
|
||||
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
|
||||
unsafe {
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
|
||||
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
|
||||
for (tag, payload) in payloads {
|
||||
buf.push(*tag);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(payload);
|
||||
}
|
||||
unsafe {
|
||||
let needed = buf.len();
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return E_SHORT;
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||
*result_len = needed;
|
||||
}
|
||||
OK
|
||||
}
|
||||
|
||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
|
||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
|
||||
}
|
||||
|
||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; // header
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 { return None; }
|
||||
let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
||||
if buf.len() < off + 4 + size { return None; }
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
match (tag, size) {
|
||||
(3, 8) => { let mut b=[0u8;8]; b.copy_from_slice(&buf[off+4..off+12]); return Some(i64::from_le_bytes(b)); }
|
||||
(2, 4) => { let mut b=[0u8;4]; b.copy_from_slice(&buf[off+4..off+8]); let v = i32::from_le_bytes(b) as i64; return Some(v); }
|
||||
(3, 8) => {
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(&buf[off + 4..off + 12]);
|
||||
return Some(i64::from_le_bytes(b));
|
||||
}
|
||||
(2, 4) => {
|
||||
let mut b = [0u8; 4];
|
||||
b.copy_from_slice(&buf[off + 4..off + 8]);
|
||||
let v = i32::from_le_bytes(b) as i64;
|
||||
return Some(v);
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,12 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::os::raw::c_char;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
// Error codes
|
||||
const NYB_SUCCESS: i32 = 0;
|
||||
@ -23,10 +26,10 @@ const METHOD_SIZE: u32 = 1;
|
||||
const METHOD_GET: u32 = 2; // args: i64 key -> TLV i64
|
||||
const METHOD_HAS: u32 = 3; // args: i64 key -> TLV bool
|
||||
const METHOD_SET: u32 = 4; // args: key(int|string), value(i64) -> TLV i64 (size)
|
||||
const METHOD_REMOVE:u32 = 6; // args: key(int|string) -> TLV bool (removed)
|
||||
const METHOD_REMOVE: u32 = 6; // args: key(int|string) -> TLV bool (removed)
|
||||
const METHOD_CLEAR: u32 = 7; // args: () -> TLV i64 (size after clear=0)
|
||||
const METHOD_KEYS_S:u32 = 8; // args: () -> TLV string (newline-joined keys)
|
||||
const METHOD_GET_OR:u32 = 9; // args: key(int|string), default(i64) -> TLV i64
|
||||
const METHOD_KEYS_S: u32 = 8; // args: () -> TLV string (newline-joined keys)
|
||||
const METHOD_GET_OR: u32 = 9; // args: key(int|string), default(i64) -> TLV i64
|
||||
const METHOD_FINI: u32 = u32::MAX;
|
||||
// Extended string-key methods
|
||||
const METHOD_SET_STR: u32 = 10; // setS(name: string, val: i64) -> i64(size)
|
||||
@ -39,17 +42,21 @@ const METHOD_TO_JSON: u32 = 14; // toJson() -> string
|
||||
const TYPE_ID_MAP: u32 = 11;
|
||||
|
||||
struct MapInstance {
|
||||
data_i64: HashMap<i64,i64>,
|
||||
data_str: HashMap<String,i64>,
|
||||
data_i64: HashMap<i64, i64>,
|
||||
data_str: HashMap<String, i64>,
|
||||
}
|
||||
static INSTANCES: Lazy<Mutex<HashMap<u32, MapInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
NYB_SUCCESS
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -61,44 +68,81 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_MAP { return NYB_E_INVALID_TYPE; }
|
||||
if type_id != TYPE_ID_MAP {
|
||||
return NYB_E_INVALID_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
METHOD_BIRTH => {
|
||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
||||
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
|
||||
if result_len.is_null() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return NYB_E_SHORT_BUFFER;
|
||||
}
|
||||
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
map.insert(id, MapInstance { data_i64: HashMap::new(), data_str: HashMap::new() });
|
||||
} else { return NYB_E_PLUGIN_ERROR; }
|
||||
map.insert(
|
||||
id,
|
||||
MapInstance {
|
||||
data_i64: HashMap::new(),
|
||||
data_str: HashMap::new(),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return NYB_E_PLUGIN_ERROR;
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(id.to_le_bytes().as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
NYB_SUCCESS
|
||||
}
|
||||
METHOD_FINI => {
|
||||
if let Ok(mut map) = INSTANCES.lock() { map.remove(&instance_id); NYB_SUCCESS } else { NYB_E_PLUGIN_ERROR }
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
map.remove(&instance_id);
|
||||
NYB_SUCCESS
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_SIZE => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||
write_tlv_i64(sz as i64, result, result_len)
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_GET => {
|
||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
match inst.data_i64.get(&ik).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
match inst.data_i64.get(&ik).copied() {
|
||||
Some(v) => write_tlv_i64(v, result, result_len),
|
||||
None => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
match inst.data_str.get(&sk).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
match inst.data_str.get(&sk).copied() {
|
||||
Some(v) => write_tlv_i64(v, result, result_len),
|
||||
None => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
@ -106,35 +150,58 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
METHOD_HAS => {
|
||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len) } else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len)
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len) } else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len)
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
}
|
||||
METHOD_SET => {
|
||||
// value は i64 限定
|
||||
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
let val = match read_arg_i64(args, args_len, 1) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
inst.data_i64.insert(ik, val);
|
||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||
return write_tlv_i64(sz as i64, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
inst.data_str.insert(sk, val);
|
||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||
return write_tlv_i64(sz as i64, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
@ -143,12 +210,28 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
// try int key
|
||||
if let Some(ik) = read_arg_i64(args, args_len, 0) { return write_tlv_bool(inst.data_i64.remove(&ik).is_some(), result, result_len); }
|
||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||
return write_tlv_bool(
|
||||
inst.data_i64.remove(&ik).is_some(),
|
||||
result,
|
||||
result_len,
|
||||
);
|
||||
}
|
||||
// try string key
|
||||
if let Some(sk) = read_arg_string(args, args_len, 0) { return write_tlv_bool(inst.data_str.remove(&sk).is_some(), result, result_len); }
|
||||
if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||
return write_tlv_bool(
|
||||
inst.data_str.remove(&sk).is_some(),
|
||||
result,
|
||||
result_len,
|
||||
);
|
||||
}
|
||||
NYB_E_INVALID_ARGS
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_CLEAR => {
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
@ -156,23 +239,39 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
inst.data_i64.clear();
|
||||
inst.data_str.clear();
|
||||
return write_tlv_i64(0, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_KEYS_S => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
let mut keys: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||
for k in inst.data_i64.keys() { keys.push(k.to_string()); }
|
||||
for k in inst.data_str.keys() { keys.push(k.clone()); }
|
||||
let mut keys: Vec<String> =
|
||||
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||
for k in inst.data_i64.keys() {
|
||||
keys.push(k.to_string());
|
||||
}
|
||||
for k in inst.data_str.keys() {
|
||||
keys.push(k.clone());
|
||||
}
|
||||
keys.sort();
|
||||
let out = keys.join("\n");
|
||||
return write_tlv_string(&out, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_GET_OR => {
|
||||
let defv = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
let defv = match read_arg_i64(args, args_len, 1) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
// prefer exact match, else default
|
||||
@ -185,44 +284,86 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
return write_tlv_i64(v, result, result_len);
|
||||
}
|
||||
NYB_E_INVALID_ARGS
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_SET_STR => {
|
||||
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
|
||||
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
||||
let key = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
let val = match read_arg_i64(args, args_len, 1) {
|
||||
Some(v) => v,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
inst.data_str.insert(key, val);
|
||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||
return write_tlv_i64(sz as i64, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_GET_STR => {
|
||||
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
|
||||
let key = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
match inst.data_str.get(&key).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
match inst.data_str.get(&key).copied() {
|
||||
Some(v) => write_tlv_i64(v, result, result_len),
|
||||
None => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_HAS_STR => {
|
||||
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
|
||||
let key = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&key), result, result_len) } else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
write_tlv_bool(inst.data_str.contains_key(&key), result, result_len)
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_VALUES_S => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
let mut vals: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||
for v in inst.data_i64.values() { vals.push(v.to_string()); }
|
||||
for v in inst.data_str.values() { vals.push(v.to_string()); }
|
||||
let mut vals: Vec<String> =
|
||||
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||
for v in inst.data_i64.values() {
|
||||
vals.push(v.to_string());
|
||||
}
|
||||
for v in inst.data_str.values() {
|
||||
vals.push(v.to_string());
|
||||
}
|
||||
let out = vals.join("\n");
|
||||
return write_tlv_string(&out, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_TO_JSON => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
@ -230,23 +371,35 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
let mut s = String::from("{");
|
||||
let mut first = true;
|
||||
for (k, v) in inst.data_str.iter() {
|
||||
if !first { s.push(','); }
|
||||
if !first {
|
||||
s.push(',');
|
||||
}
|
||||
first = false;
|
||||
// JSON string key
|
||||
s.push('"'); s.push_str(&escape_json(k)); s.push_str("\": ");
|
||||
s.push('"');
|
||||
s.push_str(&escape_json(k));
|
||||
s.push_str("\": ");
|
||||
s.push_str(&v.to_string());
|
||||
}
|
||||
for (k, v) in inst.data_i64.iter() {
|
||||
if !first { s.push(','); }
|
||||
if !first {
|
||||
s.push(',');
|
||||
}
|
||||
first = false;
|
||||
// numeric key as string per JSON
|
||||
s.push('"'); s.push_str(&k.to_string()); s.push_str("\": ");
|
||||
s.push('"');
|
||||
s.push_str(&k.to_string());
|
||||
s.push_str("\": ");
|
||||
s.push_str(&v.to_string());
|
||||
}
|
||||
s.push('}');
|
||||
return write_tlv_string(&s, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
_ => NYB_E_INVALID_METHOD,
|
||||
}
|
||||
@ -267,7 +420,9 @@ pub struct NyashTypeBoxFfi {
|
||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||
|
||||
extern "C" fn mapbox_resolve(name: *const 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();
|
||||
match s.as_ref() {
|
||||
"size" | "len" => METHOD_SIZE,
|
||||
@ -280,41 +435,82 @@ extern "C" fn mapbox_resolve(name: *const c_char) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn mapbox_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
extern "C" fn mapbox_invoke_id(
|
||||
instance_id: u32,
|
||||
method_id: u32,
|
||||
args: *const u8,
|
||||
args_len: usize,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
match method_id {
|
||||
METHOD_SIZE => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||
return write_tlv_i64(sz as i64, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_GET => {
|
||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
match inst.data_i64.get(&ik).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
match inst.data_i64.get(&ik).copied() {
|
||||
Some(v) => write_tlv_i64(v, result, result_len),
|
||||
None => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
match inst.data_str.get(&sk).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else { NYB_E_INVALID_ARGS }
|
||||
match inst.data_str.get(&sk).copied() {
|
||||
Some(v) => write_tlv_i64(v, result, result_len),
|
||||
None => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
}
|
||||
METHOD_HAS => {
|
||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len) } else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len)
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len) } else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else { NYB_E_INVALID_ARGS }
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len)
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
}
|
||||
METHOD_SET => {
|
||||
// key: i64 or string, value: i64
|
||||
@ -325,55 +521,104 @@ extern "C" fn mapbox_invoke_id(instance_id: u32, method_id: u32, args: *const u8
|
||||
inst.data_i64.insert(ik, val);
|
||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||
return write_tlv_i64(sz as i64, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||
if let Ok(mut map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get_mut(&instance_id) {
|
||||
inst.data_str.insert(sk, val);
|
||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||
return write_tlv_i64(sz as i64, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else { NYB_E_INVALID_ARGS }
|
||||
} else { NYB_E_INVALID_ARGS }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_ARGS
|
||||
}
|
||||
}
|
||||
METHOD_GET_STR => {
|
||||
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
|
||||
let key = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
match inst.data_str.get(&key).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
match inst.data_str.get(&key).copied() {
|
||||
Some(v) => write_tlv_i64(v, result, result_len),
|
||||
None => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_HAS_STR => {
|
||||
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
|
||||
let key = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return NYB_E_INVALID_ARGS,
|
||||
};
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&key), result, result_len) } else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
write_tlv_bool(inst.data_str.contains_key(&key), result, result_len)
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_KEYS_S => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
let mut keys: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||
for k in inst.data_i64.keys() { keys.push(k.to_string()); }
|
||||
for k in inst.data_str.keys() { keys.push(k.clone()); }
|
||||
let mut keys: Vec<String> =
|
||||
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||
for k in inst.data_i64.keys() {
|
||||
keys.push(k.to_string());
|
||||
}
|
||||
for k in inst.data_str.keys() {
|
||||
keys.push(k.clone());
|
||||
}
|
||||
keys.sort();
|
||||
let out = keys.join("\n");
|
||||
return write_tlv_string(&out, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
METHOD_VALUES_S => {
|
||||
if let Ok(map) = INSTANCES.lock() {
|
||||
if let Some(inst) = map.get(&instance_id) {
|
||||
let mut vals: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||
for v in inst.data_i64.values() { vals.push(v.to_string()); }
|
||||
for v in inst.data_str.values() { vals.push(v.to_string()); }
|
||||
let mut vals: Vec<String> =
|
||||
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||
for v in inst.data_i64.values() {
|
||||
vals.push(v.to_string());
|
||||
}
|
||||
for v in inst.data_str.values() {
|
||||
vals.push(v.to_string());
|
||||
}
|
||||
let out = vals.join("\n");
|
||||
return write_tlv_string(&out, result, result_len);
|
||||
} else { NYB_E_INVALID_HANDLE }
|
||||
} else { NYB_E_PLUGIN_ERROR }
|
||||
} else {
|
||||
NYB_E_INVALID_HANDLE
|
||||
}
|
||||
} else {
|
||||
NYB_E_PLUGIN_ERROR
|
||||
}
|
||||
}
|
||||
_ => NYB_E_INVALID_METHOD,
|
||||
}
|
||||
@ -391,7 +636,7 @@ pub static nyash_typebox_MapBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
||||
};
|
||||
|
||||
fn escape_json(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len()+8);
|
||||
let mut out = String::with_capacity(s.len() + 8);
|
||||
for ch in s.chars() {
|
||||
match ch {
|
||||
'"' => out.push_str("\\\""),
|
||||
@ -409,7 +654,9 @@ fn escape_json(s: &str) -> String {
|
||||
// TLV helpers
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe {
|
||||
if result_len.is_null() { return false; }
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
@ -419,8 +666,11 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
}
|
||||
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
if result_len.is_null() {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads {
|
||||
@ -441,27 +691,44 @@ fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut
|
||||
NYB_SUCCESS
|
||||
}
|
||||
|
||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
|
||||
fn write_tlv_bool(bv: bool, result: *mut u8, result_len: *mut usize) -> i32 { let b = [if bv {1u8} else {0u8}]; write_tlv_result(&[(1u8, &b)], result, result_len) }
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
|
||||
}
|
||||
fn write_tlv_bool(bv: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
let b = [if bv { 1u8 } else { 0u8 }];
|
||||
write_tlv_result(&[(1u8, &b)], result, result_len)
|
||||
}
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||
}
|
||||
|
||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 { return None; }
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
||||
if buf.len() < off + 4 + size { return None; }
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag == 3 && size == 8 {
|
||||
let mut b = [0u8;8]; b.copy_from_slice(&buf[off+4..off+12]);
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(&buf[off + 4..off + 12]);
|
||||
return Some(i64::from_le_bytes(b));
|
||||
} else if tag == 2 && size == 4 {
|
||||
let mut b = [0u8;4]; b.copy_from_slice(&buf[off+4..off+8]);
|
||||
let mut b = [0u8; 4];
|
||||
b.copy_from_slice(&buf[off + 4..off + 8]);
|
||||
return Some(i32::from_le_bytes(b) as i64);
|
||||
} else { return None; }
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
@ -469,19 +736,27 @@ fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||
}
|
||||
|
||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 { return None; }
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
||||
if buf.len() < off + 4 + size { return None; }
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag == 6 || tag == 7 {
|
||||
let s = &buf[off+4..off+4+size];
|
||||
let s = &buf[off + 4..off + 4 + size];
|
||||
return Some(String::from_utf8_lossy(s).to_string());
|
||||
} else { return None; }
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
|
||||
@ -3,7 +3,10 @@
|
||||
//! TimeBox: now() -> i64 (unix seconds)
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
// Error codes
|
||||
const OK: i32 = 0;
|
||||
@ -37,18 +40,56 @@ static ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
// TLV helpers
|
||||
mod tlv {
|
||||
pub fn header(argc: u16) -> Vec<u8> { let mut b=Vec::with_capacity(4); b.extend_from_slice(&1u16.to_le_bytes()); b.extend_from_slice(&argc.to_le_bytes()); b }
|
||||
pub fn encode_handle(buf: &mut Vec<u8>, t: u32, i: u32) { buf.push(8); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&t.to_le_bytes()); buf.extend_from_slice(&i.to_le_bytes()); }
|
||||
pub fn encode_i64(buf: &mut Vec<u8>, v: i64) { buf.push(3); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&v.to_le_bytes()); }
|
||||
pub fn encode_void(buf: &mut Vec<u8>) { buf.push(9); buf.push(0); buf.push(0); buf.push(0); }
|
||||
pub fn decode_first(args:&[u8]) -> Option<(u16,u16,usize)> { if args.len()<8 {return None;} let argc=u16::from_le_bytes([args[2],args[3]]); if argc==0{return None;} let tag=u16::from_le_bytes([args[4],args[5]]); let sz=u16::from_le_bytes([args[6],args[7]]); Some((tag,sz,8)) }
|
||||
pub fn header(argc: u16) -> Vec<u8> {
|
||||
let mut b = Vec::with_capacity(4);
|
||||
b.extend_from_slice(&1u16.to_le_bytes());
|
||||
b.extend_from_slice(&argc.to_le_bytes());
|
||||
b
|
||||
}
|
||||
pub fn encode_handle(buf: &mut Vec<u8>, t: u32, i: u32) {
|
||||
buf.push(8);
|
||||
buf.push(0);
|
||||
buf.push(8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&t.to_le_bytes());
|
||||
buf.extend_from_slice(&i.to_le_bytes());
|
||||
}
|
||||
pub fn encode_i64(buf: &mut Vec<u8>, v: i64) {
|
||||
buf.push(3);
|
||||
buf.push(0);
|
||||
buf.push(8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
pub fn encode_void(buf: &mut Vec<u8>) {
|
||||
buf.push(9);
|
||||
buf.push(0);
|
||||
buf.push(0);
|
||||
buf.push(0);
|
||||
}
|
||||
pub fn decode_first(args: &[u8]) -> Option<(u16, u16, usize)> {
|
||||
if args.len() < 8 {
|
||||
return None;
|
||||
}
|
||||
let argc = u16::from_le_bytes([args[2], args[3]]);
|
||||
if argc == 0 {
|
||||
return None;
|
||||
}
|
||||
let tag = u16::from_le_bytes([args[4], args[5]]);
|
||||
let sz = u16::from_le_bytes([args[6], args[7]]);
|
||||
Some((tag, sz, 8))
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -77,31 +118,67 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn birth<T>(tid: u32, map: &Lazy<Mutex<HashMap<u32,T>>>, out: *mut u8, out_len: *mut usize) -> i32 where T: Default {
|
||||
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
|
||||
unsafe fn birth<T>(
|
||||
tid: u32,
|
||||
map: &Lazy<Mutex<HashMap<u32, T>>>,
|
||||
out: *mut u8,
|
||||
out_len: *mut usize,
|
||||
) -> i32
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
let need = 4 + 4 + 8;
|
||||
if *out_len < need {
|
||||
*out_len = need;
|
||||
return E_SHORT;
|
||||
}
|
||||
let id = ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = map.lock() { m.insert(id, T::default()); } else { return E_FAIL; }
|
||||
let mut buf = tlv::header(1); tlv::encode_handle(&mut buf, tid, id);
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len(); OK
|
||||
if let Ok(mut m) = map.lock() {
|
||||
m.insert(id, T::default());
|
||||
} else {
|
||||
return E_FAIL;
|
||||
}
|
||||
let mut buf = tlv::header(1);
|
||||
tlv::encode_handle(&mut buf, tid, id);
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
|
||||
*out_len = buf.len();
|
||||
OK
|
||||
}
|
||||
|
||||
unsafe fn fini<T>(map: &Lazy<Mutex<HashMap<u32,T>>>, instance_id: u32) -> i32 {
|
||||
if let Ok(mut m) = map.lock() { m.remove(&instance_id); OK } else { E_FAIL }
|
||||
unsafe fn fini<T>(map: &Lazy<Mutex<HashMap<u32, T>>>, instance_id: u32) -> i32 {
|
||||
if let Ok(mut m) = map.lock() {
|
||||
m.remove(&instance_id);
|
||||
OK
|
||||
} else {
|
||||
E_FAIL
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn sqrt_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mut usize) -> i32 {
|
||||
if args_len < 8 { return E_ARGS; }
|
||||
if args_len < 8 {
|
||||
return E_ARGS;
|
||||
}
|
||||
let a = std::slice::from_raw_parts(args, args_len);
|
||||
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
||||
if tag == 3 && sz == 8 && a.len() >= p+8 {
|
||||
let mut b=[0u8;8]; b.copy_from_slice(&a[p..p+8]);
|
||||
if tag == 3 && sz == 8 && a.len() >= p + 8 {
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(&a[p..p + 8]);
|
||||
let x = i64::from_le_bytes(b) as f64;
|
||||
let r = x.sqrt();
|
||||
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
|
||||
let need = 4 + 4 + 8;
|
||||
if *out_len < need {
|
||||
*out_len = need;
|
||||
return E_SHORT;
|
||||
}
|
||||
let mut buf = tlv::header(1);
|
||||
// encode f64 (tag=5)
|
||||
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
|
||||
buf.push(5);
|
||||
buf.push(0);
|
||||
buf.push(8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&r.to_le_bytes());
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
|
||||
*out_len = buf.len();
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
@ -109,26 +186,53 @@ unsafe fn sqrt_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mu
|
||||
}
|
||||
|
||||
unsafe fn now_call(out: *mut u8, out_len: *mut usize) -> i32 {
|
||||
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs() as i64).unwrap_or(0);
|
||||
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
|
||||
let mut buf = tlv::header(1); tlv::encode_i64(&mut buf, ts);
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs() as i64)
|
||||
.unwrap_or(0);
|
||||
let need = 4 + 4 + 8;
|
||||
if *out_len < need {
|
||||
*out_len = need;
|
||||
return E_SHORT;
|
||||
}
|
||||
let mut buf = tlv::header(1);
|
||||
tlv::encode_i64(&mut buf, ts);
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
|
||||
*out_len = buf.len();
|
||||
OK
|
||||
}
|
||||
|
||||
unsafe fn trig_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mut usize, is_sin: bool) -> i32 {
|
||||
if args_len < 8 { return E_ARGS; }
|
||||
unsafe fn trig_call(
|
||||
args: *const u8,
|
||||
args_len: usize,
|
||||
out: *mut u8,
|
||||
out_len: *mut usize,
|
||||
is_sin: bool,
|
||||
) -> i32 {
|
||||
if args_len < 8 {
|
||||
return E_ARGS;
|
||||
}
|
||||
let a = std::slice::from_raw_parts(args, args_len);
|
||||
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
||||
if tag == 3 && sz == 8 && a.len() >= p+8 {
|
||||
let mut b=[0u8;8]; b.copy_from_slice(&a[p..p+8]);
|
||||
if tag == 3 && sz == 8 && a.len() >= p + 8 {
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(&a[p..p + 8]);
|
||||
let x = i64::from_le_bytes(b) as f64;
|
||||
let r = if is_sin { x.sin() } else { x.cos() };
|
||||
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
|
||||
let need = 4 + 4 + 8;
|
||||
if *out_len < need {
|
||||
*out_len = need;
|
||||
return E_SHORT;
|
||||
}
|
||||
let mut buf = tlv::header(1);
|
||||
// encode f64 (tag=5)
|
||||
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
|
||||
buf.push(5);
|
||||
buf.push(0);
|
||||
buf.push(8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&r.to_le_bytes());
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
|
||||
*out_len = buf.len();
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
@ -136,18 +240,30 @@ unsafe fn trig_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mu
|
||||
}
|
||||
|
||||
unsafe fn round_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mut usize) -> i32 {
|
||||
if args_len < 8 { return E_ARGS; }
|
||||
if args_len < 8 {
|
||||
return E_ARGS;
|
||||
}
|
||||
let a = std::slice::from_raw_parts(args, args_len);
|
||||
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
||||
if tag == 3 && sz == 8 && a.len() >= p+8 {
|
||||
let mut b=[0u8;8]; b.copy_from_slice(&a[p..p+8]);
|
||||
if tag == 3 && sz == 8 && a.len() >= p + 8 {
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(&a[p..p + 8]);
|
||||
let x = i64::from_le_bytes(b) as f64;
|
||||
let r = x.round();
|
||||
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
|
||||
let need = 4 + 4 + 8;
|
||||
if *out_len < need {
|
||||
*out_len = need;
|
||||
return E_SHORT;
|
||||
}
|
||||
let mut buf = tlv::header(1);
|
||||
// encode f64 (tag=5)
|
||||
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
|
||||
buf.push(5);
|
||||
buf.push(0);
|
||||
buf.push(8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&r.to_le_bytes());
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
|
||||
*out_len = buf.len();
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use chrono::{DateTime, Datelike, Timelike, Utc};
|
||||
use rand::Rng;
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
use std::ptr;
|
||||
use chrono::{DateTime, Utc, Datelike, Timelike};
|
||||
use rand::Rng;
|
||||
|
||||
// MathBox構造体
|
||||
pub struct MathBox {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,8 +2,11 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, Component};
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::path::{Component, Path};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
const OK: i32 = 0;
|
||||
const E_SHORT: i32 = -1;
|
||||
@ -29,10 +32,14 @@ static INST: Lazy<Mutex<HashMap<u32, PathInstance>>> = Lazy::new(|| Mutex::new(H
|
||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -44,45 +51,99 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_PATH { return E_TYPE; }
|
||||
if type_id != TYPE_ID_PATH {
|
||||
return E_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_BIRTH => {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() { m.insert(id, PathInstance); } else { return E_PLUGIN; }
|
||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return E_SHORT;
|
||||
}
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.insert(id, PathInstance);
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
let b = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
OK
|
||||
}
|
||||
M_FINI => {
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.remove(&instance_id);
|
||||
OK
|
||||
} else {
|
||||
E_PLUGIN
|
||||
}
|
||||
}
|
||||
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
|
||||
M_JOIN => {
|
||||
let base = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
let rest = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS };
|
||||
let joined = if base.ends_with('/') || base.ends_with('\\') { format!("{}{}", base, rest) } else { format!("{}/{}", base, rest) };
|
||||
let base = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let rest = match read_arg_string(args, args_len, 1) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let joined = if base.ends_with('/') || base.ends_with('\\') {
|
||||
format!("{}{}", base, rest)
|
||||
} else {
|
||||
format!("{}/{}", base, rest)
|
||||
};
|
||||
write_tlv_string(&joined, result, result_len)
|
||||
}
|
||||
M_DIRNAME => {
|
||||
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
let d = Path::new(&p).parent().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| "".to_string());
|
||||
let p = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let d = Path::new(&p)
|
||||
.parent()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
write_tlv_string(&d, result, result_len)
|
||||
}
|
||||
M_BASENAME => {
|
||||
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
let b = Path::new(&p).file_name().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| "".to_string());
|
||||
let p = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let b = Path::new(&p)
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
write_tlv_string(&b, result, result_len)
|
||||
}
|
||||
M_EXTNAME => {
|
||||
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
let ext = Path::new(&p).extension().map(|x| format!(".{}", x.to_string_lossy())).unwrap_or_else(|| "".to_string());
|
||||
let p = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let ext = Path::new(&p)
|
||||
.extension()
|
||||
.map(|x| format!(".{}", x.to_string_lossy()))
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
write_tlv_string(&ext, result, result_len)
|
||||
}
|
||||
M_IS_ABS => {
|
||||
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
let p = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let abs = Path::new(&p).is_absolute() || p.contains(":\\");
|
||||
write_tlv_bool(abs, result, result_len)
|
||||
}
|
||||
M_NORMALIZE => {
|
||||
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
let p = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let norm = path_clean::PathClean::clean(Path::new(&p));
|
||||
write_tlv_string(norm.to_string_lossy().as_ref(), result, result_len)
|
||||
}
|
||||
@ -92,23 +153,72 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
}
|
||||
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
|
||||
unsafe {
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
|
||||
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads {
|
||||
buf.push(*tag);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(payload);
|
||||
}
|
||||
unsafe {
|
||||
let needed = buf.len();
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return E_SHORT;
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||
*result_len = needed;
|
||||
}
|
||||
OK
|
||||
}
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) }
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
|
||||
}
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||
}
|
||||
|
||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; }
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag == 6 || tag == 7 {
|
||||
return Some(String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::Mutex;
|
||||
use serde_json::Value as Json;
|
||||
use std::sync::Mutex;
|
||||
|
||||
const NYB_SUCCESS: i32 = 0;
|
||||
const NYB_E_INVALID_METHOD: i32 = -3;
|
||||
@ -14,10 +14,14 @@ const METHOD_FINI: u32 = u32::MAX;
|
||||
static NEXT_ID: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(1));
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
NYB_SUCCESS
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -29,14 +33,20 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_COMPILER { return NYB_E_INVALID_METHOD; }
|
||||
if type_id != TYPE_ID_COMPILER {
|
||||
return NYB_E_INVALID_METHOD;
|
||||
}
|
||||
match method_id {
|
||||
METHOD_BIRTH => {
|
||||
unsafe {
|
||||
let mut id_g = NEXT_ID.lock().unwrap();
|
||||
let id = *id_g; *id_g += 1;
|
||||
let id = *id_g;
|
||||
*id_g += 1;
|
||||
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);
|
||||
out[0..4].copy_from_slice(&(id as u32).to_le_bytes());
|
||||
*result_len = need;
|
||||
@ -46,16 +56,20 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
METHOD_COMPILE => {
|
||||
// Decode TLV first string arg as JSON IR
|
||||
let ir = unsafe {
|
||||
if args.is_null() || args_len < 8 { None } else {
|
||||
if args.is_null() || args_len < 8 {
|
||||
None
|
||||
} else {
|
||||
let buf = std::slice::from_raw_parts(args, args_len);
|
||||
let tag = u16::from_le_bytes([buf[4], buf[5]]);
|
||||
let len = u16::from_le_bytes([buf[6], buf[7]]) as usize;
|
||||
if tag == 6 && 8 + len <= buf.len() {
|
||||
match std::str::from_utf8(&buf[8..8+len]) {
|
||||
match std::str::from_utf8(&buf[8..8 + len]) {
|
||||
Ok(s) => Some(s.to_string()),
|
||||
Err(_) => None
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else { None }
|
||||
}
|
||||
};
|
||||
let nyash_source = if let Some(s) = ir.or_else(|| std::env::var("NYASH_PY_IR").ok()) {
|
||||
@ -67,15 +81,22 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
} else if let Some(module) = map.get("module") {
|
||||
// Try module.functions[0].name and maybe return value
|
||||
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(retv) = fun0.get("return_value") {
|
||||
if retv.is_number() { ret_expr = retv.to_string(); }
|
||||
else if let Some(s) = retv.as_str() { ret_expr = s.to_string(); }
|
||||
if retv.is_number() {
|
||||
ret_expr = retv.to_string();
|
||||
} else if let Some(s) = retv.as_str() {
|
||||
ret_expr = s.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
format!("static box Generated {{\n main() {{\n return {}\n }}\n}}", ret_expr)
|
||||
}
|
||||
format!(
|
||||
"static box Generated {{\n main() {{\n return {}\n }}\n}}",
|
||||
ret_expr
|
||||
)
|
||||
} else {
|
||||
"static box Generated { main() { return 0 } }".to_string()
|
||||
}
|
||||
@ -88,11 +109,14 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
unsafe {
|
||||
let bytes = nyash_source.as_bytes();
|
||||
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);
|
||||
out[0..2].copy_from_slice(&6u16.to_le_bytes());
|
||||
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
|
||||
out[4..4+bytes.len()].copy_from_slice(bytes);
|
||||
out[4..4 + bytes.len()].copy_from_slice(bytes);
|
||||
*result_len = need;
|
||||
}
|
||||
NYB_SUCCESS
|
||||
|
||||
@ -71,7 +71,8 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
let code = unsafe {
|
||||
if args.is_null() || args_len < 4 {
|
||||
// 引数なしの場合は環境変数から取得
|
||||
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
|
||||
std::env::var("NYASH_PY_CODE")
|
||||
.unwrap_or_else(|_| "def main():\n return 0".to_string())
|
||||
} else {
|
||||
// TLVデコード(簡易版)
|
||||
let buf = std::slice::from_raw_parts(args, args_len);
|
||||
@ -79,23 +80,24 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
let tag = u16::from_le_bytes([buf[0], buf[1]]);
|
||||
let len = u16::from_le_bytes([buf[2], buf[3]]) as usize;
|
||||
if tag == 6 && 4 + len <= args_len {
|
||||
match std::str::from_utf8(&buf[4..4+len]) {
|
||||
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())
|
||||
Err(_) => std::env::var("NYASH_PY_CODE")
|
||||
.unwrap_or_else(|_| "def main():\n return 0".to_string()),
|
||||
}
|
||||
} else {
|
||||
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
|
||||
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())
|
||||
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));
|
||||
|
||||
// JSONにシリアライズ
|
||||
match serde_json::to_string(&parse_result) {
|
||||
@ -111,22 +113,24 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
// TLVエンコード(tag=6:string)
|
||||
out[0..2].copy_from_slice(&6u16.to_le_bytes());
|
||||
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
|
||||
out[4..4+bytes.len()].copy_from_slice(bytes);
|
||||
out[4..4 + bytes.len()].copy_from_slice(bytes);
|
||||
*result_len = need;
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(_) => -4 // エラー
|
||||
Err(_) => -4, // エラー
|
||||
}
|
||||
}
|
||||
METHOD_FINI => 0,
|
||||
_ => -3 // NYB_E_INVALID_METHOD
|
||||
_ => -3, // NYB_E_INVALID_METHOD
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI: Pythonコードをパース
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_python_parse(code: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
|
||||
pub extern "C" fn nyash_python_parse(
|
||||
code: *const std::os::raw::c_char,
|
||||
) -> *mut std::os::raw::c_char {
|
||||
let code = unsafe {
|
||||
if code.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
@ -137,9 +141,7 @@ pub extern "C" fn nyash_python_parse(code: *const std::os::raw::c_char) -> *mut
|
||||
}
|
||||
};
|
||||
|
||||
let result = Python::with_gil(|py| {
|
||||
parse_python_code(py, code)
|
||||
});
|
||||
let result = Python::with_gil(|py| parse_python_code(py, code));
|
||||
|
||||
match serde_json::to_string(&result) {
|
||||
Ok(json) => {
|
||||
@ -265,15 +267,25 @@ fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) {
|
||||
"AsyncFunctionDef" => {
|
||||
result.counts.functions += 1;
|
||||
result.counts.unsupported += 1;
|
||||
if !result.unsupported.contains(&"async function".to_string()) {
|
||||
result.unsupported.push("async function".to_string());
|
||||
if !result
|
||||
.unsupported
|
||||
.contains(&"async function".to_string())
|
||||
{
|
||||
result
|
||||
.unsupported
|
||||
.push("async function".to_string());
|
||||
}
|
||||
}
|
||||
"ClassDef" => {
|
||||
result.counts.classes += 1;
|
||||
result.counts.unsupported += 1;
|
||||
if !result.unsupported.contains(&"class definition".to_string()) {
|
||||
result.unsupported.push("class definition".to_string());
|
||||
if !result
|
||||
.unsupported
|
||||
.contains(&"class definition".to_string())
|
||||
{
|
||||
result
|
||||
.unsupported
|
||||
.push("class definition".to_string());
|
||||
}
|
||||
}
|
||||
"For" | "While" | "If" => {
|
||||
@ -281,8 +293,13 @@ fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) {
|
||||
}
|
||||
"Yield" | "YieldFrom" => {
|
||||
result.counts.unsupported += 1;
|
||||
if !result.unsupported.contains(&"generator".to_string()) {
|
||||
result.unsupported.push("generator".to_string());
|
||||
if !result
|
||||
.unsupported
|
||||
.contains(&"generator".to_string())
|
||||
{
|
||||
result
|
||||
.unsupported
|
||||
.push("generator".to_string());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,10 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
// Error/status codes aligned with other plugins
|
||||
const OK: i32 = 0;
|
||||
@ -26,16 +29,22 @@ const M_FINI: u32 = u32::MAX; // fini()
|
||||
// Assign an unused type id (see nyash.toml [box_types])
|
||||
const TYPE_ID_REGEX: u32 = 52;
|
||||
|
||||
struct RegexInstance { re: Option<Regex> }
|
||||
struct RegexInstance {
|
||||
re: Option<Regex>,
|
||||
}
|
||||
|
||||
static INST: Lazy<Mutex<HashMap<u32, RegexInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -47,42 +56,155 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_REGEX { return E_TYPE; }
|
||||
if type_id != TYPE_ID_REGEX {
|
||||
return E_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_BIRTH => {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return E_SHORT;
|
||||
}
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
// Optional pattern in arg0
|
||||
let inst = if let Some(pat) = read_arg_string(args, args_len, 0) {
|
||||
match Regex::new(&pat) { Ok(re) => RegexInstance { re: Some(re) }, Err(_) => RegexInstance { re: None } }
|
||||
} else { RegexInstance { re: None } };
|
||||
if let Ok(mut m) = INST.lock() { m.insert(id, inst); } else { return E_PLUGIN; }
|
||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
||||
match Regex::new(&pat) {
|
||||
Ok(re) => RegexInstance { re: Some(re) },
|
||||
Err(_) => RegexInstance { re: None },
|
||||
}
|
||||
} else {
|
||||
RegexInstance { re: None }
|
||||
};
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.insert(id, inst);
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
let b = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
OK
|
||||
}
|
||||
M_FINI => {
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.remove(&instance_id);
|
||||
OK
|
||||
} else {
|
||||
E_PLUGIN
|
||||
}
|
||||
}
|
||||
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
|
||||
M_COMPILE => {
|
||||
let pat = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.re = Regex::new(&pat).ok(); OK } else { E_HANDLE } } else { E_PLUGIN }
|
||||
let pat = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
if let Some(inst) = m.get_mut(&instance_id) {
|
||||
inst.re = Regex::new(&pat).ok();
|
||||
OK
|
||||
} else {
|
||||
E_HANDLE
|
||||
}
|
||||
} else {
|
||||
E_PLUGIN
|
||||
}
|
||||
}
|
||||
M_IS_MATCH => {
|
||||
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { return write_tlv_bool(re.is_match(&text), result, result_len); } else { return write_tlv_bool(false, result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
let text = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
if let Some(re) = &inst.re {
|
||||
return write_tlv_bool(re.is_match(&text), result, result_len);
|
||||
} else {
|
||||
return write_tlv_bool(false, result, result_len);
|
||||
}
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_FIND => {
|
||||
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { let s = re.find(&text).map(|m| m.as_str().to_string()).unwrap_or_else(|| "".to_string()); return write_tlv_string(&s, result, result_len); } else { return write_tlv_string("", result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
let text = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
if let Some(re) = &inst.re {
|
||||
let s = re
|
||||
.find(&text)
|
||||
.map(|m| m.as_str().to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
return write_tlv_string(&s, result, result_len);
|
||||
} else {
|
||||
return write_tlv_string("", result, result_len);
|
||||
}
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_REPLACE_ALL => {
|
||||
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
let repl = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS };
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { let out = re.replace_all(&text, repl.as_str()).to_string(); return write_tlv_string(&out, result, result_len); } else { return write_tlv_string(&text, result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
let text = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let repl = match read_arg_string(args, args_len, 1) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
if let Some(re) = &inst.re {
|
||||
let out = re.replace_all(&text, repl.as_str()).to_string();
|
||||
return write_tlv_string(&out, result, result_len);
|
||||
} else {
|
||||
return write_tlv_string(&text, result, result_len);
|
||||
}
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_SPLIT => {
|
||||
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
let text = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
let limit = read_arg_i64(args, args_len, 1).unwrap_or(0);
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { let mut 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"); 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; }
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
if let Some(re) = &inst.re {
|
||||
let mut 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");
|
||||
return write_tlv_string(&out, result, result_len);
|
||||
} else {
|
||||
return write_tlv_string(&text, result, result_len);
|
||||
}
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
_ => E_METHOD,
|
||||
}
|
||||
@ -90,31 +212,103 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
}
|
||||
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
|
||||
unsafe {
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
|
||||
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads {
|
||||
buf.push(*tag);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(payload);
|
||||
}
|
||||
unsafe {
|
||||
let needed = buf.len();
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return E_SHORT;
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||
*result_len = needed;
|
||||
}
|
||||
OK
|
||||
}
|
||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) }
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
|
||||
}
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
|
||||
}
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||
}
|
||||
|
||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag != 3 || size != 8 { return None; } let mut b=[0u8;8]; b.copy_from_slice(&buf[off+4..off+12]); return Some(i64::from_le_bytes(b)); } off += 4 + size; }
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag != 3 || size != 8 {
|
||||
return None;
|
||||
}
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(&buf[off + 4..off + 12]);
|
||||
return Some(i64::from_le_bytes(b));
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { let s = String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string(); return Some(s); } else { return None; } } off += 4 + size; }
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag == 6 || tag == 7 {
|
||||
let s = String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string();
|
||||
return Some(s);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@ -3,9 +3,12 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::os::raw::c_char;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
const OK: i32 = 0;
|
||||
const E_SHORT: i32 = -1;
|
||||
@ -26,16 +29,22 @@ const M_FINI: u32 = u32::MAX;
|
||||
|
||||
const TYPE_ID_STRING: u32 = 13;
|
||||
|
||||
struct StrInstance { s: String }
|
||||
struct StrInstance {
|
||||
s: String,
|
||||
}
|
||||
|
||||
static INST: Lazy<Mutex<HashMap<u32, StrInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -47,29 +56,69 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_STRING { return E_TYPE; }
|
||||
if type_id != TYPE_ID_STRING {
|
||||
return E_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_BIRTH => {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return E_SHORT;
|
||||
}
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
// Optional init from first arg (String/Bytes)
|
||||
let init = read_arg_string(args, args_len, 0).unwrap_or_else(|| String::new());
|
||||
if let Ok(mut m) = INST.lock() { m.insert(id, StrInstance { s: init }); }
|
||||
else { return E_PLUGIN; }
|
||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.insert(id, StrInstance { s: init });
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
let b = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
OK
|
||||
}
|
||||
M_FINI => {
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.remove(&instance_id);
|
||||
OK
|
||||
} else {
|
||||
E_PLUGIN
|
||||
}
|
||||
}
|
||||
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
|
||||
M_LENGTH => {
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.s.len() as i64, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
return write_tlv_i64(inst.s.len() as i64, result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_IS_EMPTY => {
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_bool(inst.s.is_empty(), result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
return write_tlv_bool(inst.s.is_empty(), result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_CHAR_CODE_AT => {
|
||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
||||
if idx < 0 { return E_ARGS; }
|
||||
let idx = match read_arg_i64(args, args_len, 0) {
|
||||
Some(v) => v,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if idx < 0 {
|
||||
return E_ARGS;
|
||||
}
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
// Interpret index as char-index into Unicode scalar values
|
||||
@ -77,40 +126,78 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
let ch_opt = inst.s.chars().nth(i);
|
||||
let code = ch_opt.map(|c| c as u32 as i64).unwrap_or(0);
|
||||
return write_tlv_i64(code, result, result_len);
|
||||
} else { return E_HANDLE; }
|
||||
} else { return E_PLUGIN; }
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_CONCAT => {
|
||||
// Accept either Handle(tag=8) to another StringBox, or String/Bytes payload
|
||||
let (ok, rhs) = if let Some((t, inst)) = read_arg_handle(args, args_len, 0) {
|
||||
if t != TYPE_ID_STRING { return E_TYPE; }
|
||||
if let Ok(m) = INST.lock() { if let Some(s2) = m.get(&inst) { (true, s2.s.clone()) } else { (false, String::new()) } } else { return E_PLUGIN; }
|
||||
} else if let Some(s) = read_arg_string(args, args_len, 0) { (true, s) } else { (false, String::new()) };
|
||||
if !ok { return E_ARGS; }
|
||||
if t != TYPE_ID_STRING {
|
||||
return E_TYPE;
|
||||
}
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(s2) = m.get(&inst) {
|
||||
(true, s2.s.clone())
|
||||
} else {
|
||||
(false, String::new())
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
} else if let Some(s) = read_arg_string(args, args_len, 0) {
|
||||
(true, s)
|
||||
} else {
|
||||
(false, String::new())
|
||||
};
|
||||
if !ok {
|
||||
return E_ARGS;
|
||||
}
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
let mut new_s = inst.s.clone();
|
||||
new_s.push_str(&rhs);
|
||||
drop(m);
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut mm) = INST.lock() { mm.insert(id, StrInstance { s: new_s }); }
|
||||
if let Ok(mut mm) = INST.lock() {
|
||||
mm.insert(id, StrInstance { s: new_s });
|
||||
}
|
||||
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
|
||||
} else { return E_HANDLE; }
|
||||
} else { return E_PLUGIN; }
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_FROM_UTF8 => {
|
||||
// Create new instance from UTF-8 (accept String/Bytes)
|
||||
let s = if let Some(s) = read_arg_string(args, args_len, 0) { s } else { return E_ARGS; };
|
||||
let s = if let Some(s) = read_arg_string(args, args_len, 0) {
|
||||
s
|
||||
} else {
|
||||
return E_ARGS;
|
||||
};
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() { m.insert(id, StrInstance { s }); } else { return E_PLUGIN; }
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.insert(id, StrInstance { s });
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
|
||||
}
|
||||
M_TO_UTF8 => {
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
return write_tlv_string(&inst.s, result, result_len);
|
||||
} else { return E_HANDLE; }
|
||||
} else { return E_PLUGIN; }
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
_ => E_METHOD,
|
||||
}
|
||||
@ -131,7 +218,9 @@ pub struct NyashTypeBoxFfi {
|
||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||
|
||||
extern "C" fn string_resolve(name: *const 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();
|
||||
match s.as_ref() {
|
||||
"len" | "length" => M_LENGTH,
|
||||
@ -141,30 +230,77 @@ extern "C" fn string_resolve(name: *const c_char) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn string_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
extern "C" fn string_invoke_id(
|
||||
instance_id: u32,
|
||||
method_id: u32,
|
||||
args: *const u8,
|
||||
args_len: usize,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_LENGTH => {
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.s.len() as i64, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
return write_tlv_i64(inst.s.len() as i64, result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_TO_UTF8 => {
|
||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_string(&inst.s, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
return write_tlv_string(&inst.s, result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_CONCAT => {
|
||||
// support String/Bytes or StringBox handle
|
||||
let (ok, rhs) = if let Some((t, inst)) = read_arg_handle(args, args_len, 0) {
|
||||
if t != TYPE_ID_STRING { return E_TYPE; }
|
||||
if let Ok(m) = INST.lock() { if let Some(s2) = m.get(&inst) { (true, s2.s.clone()) } else { (false, String::new()) } } else { return E_PLUGIN; }
|
||||
} else if let Some(s) = read_arg_string(args, args_len, 0) { (true, s) } else { (false, String::new()) };
|
||||
if !ok { return E_ARGS; }
|
||||
if t != TYPE_ID_STRING {
|
||||
return E_TYPE;
|
||||
}
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(s2) = m.get(&inst) {
|
||||
(true, s2.s.clone())
|
||||
} else {
|
||||
(false, String::new())
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
} else if let Some(s) = read_arg_string(args, args_len, 0) {
|
||||
(true, s)
|
||||
} else {
|
||||
(false, String::new())
|
||||
};
|
||||
if !ok {
|
||||
return E_ARGS;
|
||||
}
|
||||
if let Ok(m) = INST.lock() {
|
||||
if let Some(inst) = m.get(&instance_id) {
|
||||
let mut new_s = inst.s.clone(); new_s.push_str(&rhs); drop(m);
|
||||
let mut new_s = inst.s.clone();
|
||||
new_s.push_str(&rhs);
|
||||
drop(m);
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut mm) = INST.lock() { mm.insert(id, StrInstance { s: new_s }); }
|
||||
if let Ok(mut mm) = INST.lock() {
|
||||
mm.insert(id, StrInstance { s: new_s });
|
||||
}
|
||||
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
|
||||
} else { return E_HANDLE; }
|
||||
} else { return E_PLUGIN; }
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
_ => E_METHOD,
|
||||
}
|
||||
@ -183,20 +319,54 @@ pub static nyash_typebox_StringBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
||||
};
|
||||
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
|
||||
unsafe {
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
|
||||
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads {
|
||||
buf.push(*tag);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(payload);
|
||||
}
|
||||
unsafe {
|
||||
let needed = buf.len();
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return E_SHORT;
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||
*result_len = needed;
|
||||
}
|
||||
OK
|
||||
}
|
||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) }
|
||||
fn write_tlv_handle(type_id: u32, instance_id: u32, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
|
||||
}
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
|
||||
}
|
||||
fn write_tlv_handle(
|
||||
type_id: u32,
|
||||
instance_id: u32,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
let mut payload = Vec::with_capacity(8);
|
||||
payload.extend_from_slice(&type_id.to_le_bytes());
|
||||
payload.extend_from_slice(&instance_id.to_le_bytes());
|
||||
@ -206,22 +376,85 @@ fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||
}
|
||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag != 3 || size != 8 { return None; } let mut b=[0u8;8]; b.copy_from_slice(&buf[off+4..off+12]); return Some(i64::from_le_bytes(b)); } off += 4 + size; }
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag != 3 || size != 8 {
|
||||
return None;
|
||||
}
|
||||
let mut b = [0u8; 8];
|
||||
b.copy_from_slice(&buf[off + 4..off + 12]);
|
||||
return Some(i64::from_le_bytes(b));
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
fn read_arg_handle(args: *const u8, args_len: usize, n: usize) -> Option<(u32,u32)> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
fn read_arg_handle(args: *const u8, args_len: usize, n: usize) -> Option<(u32, u32)> {
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag != 8 || size != 8 { return None; } let mut t=[0u8;4]; t.copy_from_slice(&buf[off+4..off+8]); let mut id=[0u8;4]; id.copy_from_slice(&buf[off+8..off+12]); return Some((u32::from_le_bytes(t), u32::from_le_bytes(id))); } off += 4 + size; }
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag != 8 || size != 8 {
|
||||
return None;
|
||||
}
|
||||
let mut t = [0u8; 4];
|
||||
t.copy_from_slice(&buf[off + 4..off + 8]);
|
||||
let mut id = [0u8; 4];
|
||||
id.copy_from_slice(&buf[off + 8..off + 12]);
|
||||
return Some((u32::from_le_bytes(t), u32::from_le_bytes(id)));
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n {
|
||||
if tag == 6 || tag == 7 { let s = String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string(); return Some(s); } else { return None; }
|
||||
} off += 4 + size; }
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag == 6 || tag == 7 {
|
||||
let s = String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string();
|
||||
return Some(s);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@ -195,26 +195,14 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
unsafe {
|
||||
match (type_id, method_id) {
|
||||
// TestBoxA methods
|
||||
(TYPE_ID_TESTBOX_A, METHOD_BIRTH) => {
|
||||
create_instance_a(result, result_len)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_A, METHOD_HELLO) => {
|
||||
hello_method(instance_id, result, result_len)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_A, METHOD_FINI) => {
|
||||
destroy_instance(instance_id)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_A, METHOD_BIRTH) => create_instance_a(result, result_len),
|
||||
(TYPE_ID_TESTBOX_A, METHOD_HELLO) => hello_method(instance_id, result, result_len),
|
||||
(TYPE_ID_TESTBOX_A, METHOD_FINI) => destroy_instance(instance_id),
|
||||
|
||||
// TestBoxB methods
|
||||
(TYPE_ID_TESTBOX_B, METHOD_BIRTH) => {
|
||||
create_instance_b(result, result_len)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_B, METHOD_GREET) => {
|
||||
greet_method(instance_id, result, result_len)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_B, METHOD_FINI) => {
|
||||
destroy_instance(instance_id)
|
||||
}
|
||||
(TYPE_ID_TESTBOX_B, METHOD_BIRTH) => create_instance_b(result, result_len),
|
||||
(TYPE_ID_TESTBOX_B, METHOD_GREET) => greet_method(instance_id, result, result_len),
|
||||
(TYPE_ID_TESTBOX_B, METHOD_FINI) => destroy_instance(instance_id),
|
||||
|
||||
_ => NYB_E_INVALID_ARGS,
|
||||
}
|
||||
@ -229,9 +217,12 @@ unsafe fn create_instance_a(result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
let id = INSTANCE_COUNTER;
|
||||
INSTANCE_COUNTER += 1;
|
||||
|
||||
map.insert(id, TestInstance::BoxA {
|
||||
map.insert(
|
||||
id,
|
||||
TestInstance::BoxA {
|
||||
message: "Hello from TestBoxA!".to_string(),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Return instance ID
|
||||
if *result_len >= 4 {
|
||||
@ -253,9 +244,7 @@ unsafe fn create_instance_b(result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
let id = INSTANCE_COUNTER;
|
||||
INSTANCE_COUNTER += 1;
|
||||
|
||||
map.insert(id, TestInstance::BoxB {
|
||||
counter: 0,
|
||||
});
|
||||
map.insert(id, TestInstance::BoxB { counter: 0 });
|
||||
|
||||
// Return instance ID
|
||||
if *result_len >= 4 {
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
const OK: i32 = 0;
|
||||
const E_SHORT: i32 = -1;
|
||||
@ -20,16 +23,22 @@ const M_FINI: u32 = u32::MAX; // fini()
|
||||
|
||||
const TYPE_ID_TOML: u32 = 54;
|
||||
|
||||
struct TomlInstance { value: Option<toml::Value> }
|
||||
struct TomlInstance {
|
||||
value: Option<toml::Value>,
|
||||
}
|
||||
|
||||
static INST: Lazy<Mutex<HashMap<u32, TomlInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke(
|
||||
@ -41,35 +50,84 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
if type_id != TYPE_ID_TOML { return E_TYPE; }
|
||||
if type_id != TYPE_ID_TOML {
|
||||
return E_TYPE;
|
||||
}
|
||||
unsafe {
|
||||
match method_id {
|
||||
M_BIRTH => {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() { m.insert(id, TomlInstance { value: None }); } else { return E_PLUGIN; }
|
||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
if preflight(result, result_len, 4) {
|
||||
return E_SHORT;
|
||||
}
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.insert(id, TomlInstance { value: None });
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
let b = id.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
OK
|
||||
}
|
||||
M_FINI => {
|
||||
if let Ok(mut m) = INST.lock() {
|
||||
m.remove(&instance_id);
|
||||
OK
|
||||
} else {
|
||||
E_PLUGIN
|
||||
}
|
||||
}
|
||||
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
|
||||
M_PARSE => {
|
||||
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 Some(inst) = m.get_mut(&instance_id) { inst.value = toml::from_str::<toml::Value>(&text).ok(); return write_tlv_bool(inst.value.is_some(), result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
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 Some(inst) = m.get_mut(&instance_id) {
|
||||
inst.value = toml::from_str::<toml::Value>(&text).ok();
|
||||
return write_tlv_bool(inst.value.is_some(), result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
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 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() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let out = cur.to_string();
|
||||
return write_tlv_string(&out, result, result_len);
|
||||
} else { return E_HANDLE; }
|
||||
} else { return E_PLUGIN; }
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
M_TO_JSON => {
|
||||
if let Ok(m) = INST.lock() {
|
||||
@ -77,10 +135,19 @@ pub extern "C" fn nyash_plugin_invoke(
|
||||
if let Some(v) = &inst.value {
|
||||
// Convert via serde_json::Value
|
||||
let sv = toml_to_json(v);
|
||||
return match serde_json::to_string(&sv) { Ok(s) => write_tlv_string(&s, result, result_len), Err(_) => write_tlv_string("{}", result, result_len) };
|
||||
} else { return write_tlv_string("{}", result, result_len); }
|
||||
} else { return E_HANDLE; }
|
||||
} else { return E_PLUGIN; }
|
||||
return match serde_json::to_string(&sv) {
|
||||
Ok(s) => write_tlv_string(&s, result, result_len),
|
||||
Err(_) => write_tlv_string("{}", result, result_len),
|
||||
};
|
||||
} else {
|
||||
return write_tlv_string("{}", result, result_len);
|
||||
}
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
_ => E_METHOD,
|
||||
}
|
||||
@ -97,31 +164,81 @@ fn toml_to_json(v: &toml::Value) -> serde_json::Value {
|
||||
toml::Value::Array(arr) => serde_json::Value::Array(arr.iter().map(toml_to_json).collect()),
|
||||
toml::Value::Table(map) => {
|
||||
let mut m = serde_json::Map::new();
|
||||
for (k, vv) in map.iter() { m.insert(k.clone(), toml_to_json(vv)); }
|
||||
for (k, vv) in map.iter() {
|
||||
m.insert(k.clone(), toml_to_json(vv));
|
||||
}
|
||||
serde_json::Value::Object(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
|
||||
unsafe {
|
||||
if result_len.is_null() {
|
||||
return false;
|
||||
}
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() { return E_ARGS; }
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
|
||||
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
let mut buf: Vec<u8> =
|
||||
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||
for (tag, payload) in payloads {
|
||||
buf.push(*tag);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(payload);
|
||||
}
|
||||
unsafe {
|
||||
let needed = buf.len();
|
||||
if result.is_null() || *result_len < needed {
|
||||
*result_len = needed;
|
||||
return E_SHORT;
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||
*result_len = needed;
|
||||
}
|
||||
OK
|
||||
}
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) }
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
||||
|
||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||
if args.is_null() || args_len < 4 { return None; }
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; }
|
||||
None
|
||||
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
|
||||
}
|
||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||
}
|
||||
|
||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||
if args.is_null() || args_len < 4 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||
let mut off = 4usize;
|
||||
for i in 0..=n {
|
||||
if buf.len() < off + 4 {
|
||||
return None;
|
||||
}
|
||||
let tag = buf[off];
|
||||
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||
if buf.len() < off + 4 + size {
|
||||
return None;
|
||||
}
|
||||
if i == n {
|
||||
if tag == 6 || tag == 7 {
|
||||
return Some(String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ use super::super::types::{to_bool, map_mirtype_to_basic};
|
||||
use super::builder_cursor::BuilderCursor;
|
||||
use super::Resolver;
|
||||
|
||||
fn phi_trace_on() -> bool { std::env::var("NYASH_LLVM_TRACE_PHI").ok().as_deref() == Some("1") }
|
||||
|
||||
pub(in super::super) fn emit_return<'ctx, 'b>(
|
||||
codegen: &CodegenContext<'ctx>,
|
||||
cursor: &mut BuilderCursor<'ctx, 'b>,
|
||||
@ -82,6 +84,9 @@ pub(in super::super) fn emit_jump<'ctx, 'b>(
|
||||
cursor.emit_term(bid, |b| {
|
||||
b.build_unconditional_branch(tbb).map_err(|e| e.to_string()).unwrap();
|
||||
});
|
||||
if phi_trace_on() {
|
||||
eprintln!("[PHI:jump] pred={} -> succ={}", bid.as_u32(), target.as_u32());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -335,10 +340,10 @@ pub(in super::super) fn finalize_phis<'ctx, 'b>(
|
||||
});
|
||||
}
|
||||
let pred_bb = *bb_map.get(pred).ok_or("pred bb missing")?;
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
if phi_trace_on() {
|
||||
eprintln!(
|
||||
"[PHI] finalize add pred_bb={} val={} ty={}",
|
||||
pred.as_u32(), in_vid.as_u32(),
|
||||
"[PHI:finalize] succ={} pred={} vid={} ty={}",
|
||||
succ_bb.as_u32(), pred.as_u32(), in_vid.as_u32(),
|
||||
phi.as_basic_value().get_type().print_to_string().to_string()
|
||||
);
|
||||
}
|
||||
@ -359,10 +364,10 @@ pub(in super::super) fn finalize_phis<'ctx, 'b>(
|
||||
BT::PointerType(pt) => pt.const_zero().into(),
|
||||
_ => return Err("unsupported phi type for zero synth (finalize)".to_string()),
|
||||
};
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
if phi_trace_on() {
|
||||
eprintln!(
|
||||
"[PHI] finalize add (synth) pred_bb={} zero-ty={}",
|
||||
pred.as_u32(), bt.print_to_string().to_string()
|
||||
"[PHI:finalize] succ={} pred={} vid=? ty={} src=synth_zero",
|
||||
succ_bb.as_u32(), pred.as_u32(), bt.print_to_string().to_string()
|
||||
);
|
||||
}
|
||||
match z {
|
||||
@ -431,6 +436,12 @@ pub(in super::super) fn localize_to_i64<'ctx, 'b>(
|
||||
};
|
||||
});
|
||||
phi.add_incoming(&[(&iv_out, pred_bb)]);
|
||||
if phi_trace_on() {
|
||||
eprintln!(
|
||||
"[PHI:resolve] cur={} pred={} vid={} ty=i64",
|
||||
cur_bid.as_u32(), p.as_u32(), vid.as_u32()
|
||||
);
|
||||
}
|
||||
}
|
||||
// Restore insertion point
|
||||
if let Some(bb) = saved_ip { codegen.builder.position_at_end(bb); }
|
||||
|
||||
@ -134,3 +134,49 @@ pub(in super::super) fn lower_load<'ctx, 'b>(
|
||||
vmap.insert(*dst, lv);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Lower Copy: define dst in the current block by localizing src via Resolver
|
||||
pub(in super::super) fn lower_copy<'ctx, 'b>(
|
||||
codegen: &CodegenContext<'ctx>,
|
||||
cursor: &mut BuilderCursor<'ctx, 'b>,
|
||||
resolver: &mut super::Resolver<'ctx>,
|
||||
cur_bid: BasicBlockId,
|
||||
func: &crate::mir::function::MirFunction,
|
||||
vmap: &mut HashMap<ValueId, BasicValueEnum<'ctx>>,
|
||||
dst: &ValueId,
|
||||
src: &ValueId,
|
||||
bb_map: &std::collections::HashMap<crate::mir::BasicBlockId, inkwell::basic_block::BasicBlock<'ctx>>,
|
||||
preds: &std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::BasicBlockId>>,
|
||||
block_end_values: &std::collections::HashMap<crate::mir::BasicBlockId, std::collections::HashMap<ValueId, BasicValueEnum<'ctx>>>,
|
||||
) -> Result<(), String> {
|
||||
// Choose resolution kind based on metadata type preference
|
||||
use inkwell::types::BasicTypeEnum as BT;
|
||||
let expected_bt: Option<BT> = func
|
||||
.metadata
|
||||
.value_types
|
||||
.get(dst)
|
||||
.or_else(|| func.metadata.value_types.get(src))
|
||||
.map(|mt| super::super::types::map_mirtype_to_basic(codegen.context, mt));
|
||||
let out: BasicValueEnum<'ctx> = match expected_bt {
|
||||
Some(BT::IntType(_)) | None => {
|
||||
// Prefer i64 for unknown
|
||||
let iv = resolver.resolve_i64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
|
||||
iv.into()
|
||||
}
|
||||
Some(BT::PointerType(_)) => {
|
||||
let pv = resolver.resolve_ptr(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
|
||||
pv.into()
|
||||
}
|
||||
Some(BT::FloatType(_)) => {
|
||||
let fv = resolver.resolve_f64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
|
||||
fv.into()
|
||||
}
|
||||
_ => {
|
||||
// Fallback i64
|
||||
let iv = resolver.resolve_i64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
|
||||
iv.into()
|
||||
}
|
||||
};
|
||||
vmap.insert(*dst, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ pub(super) use newbox::lower_newbox;
|
||||
pub(super) use boxcall::{lower_boxcall, lower_boxcall_boxed, lower_boxcall_via_ctx};
|
||||
pub(super) use arith::lower_compare;
|
||||
pub(super) use mem::{lower_load, lower_store};
|
||||
pub(super) use mem::lower_copy;
|
||||
pub(super) use consts::lower_const;
|
||||
pub(super) use arith_ops::{lower_binop, lower_unary};
|
||||
pub(super) use call::lower_call;
|
||||
|
||||
@ -767,6 +767,22 @@ impl LLVMCompiler {
|
||||
instructions::lower_load(&codegen, &mut cursor, *bid, &mut vmap, &mut allocas, &mut alloca_elem_types, dst, ptr)?;
|
||||
defined_in_block.insert(*dst);
|
||||
},
|
||||
MirInstruction::Copy { dst, src } => {
|
||||
instructions::lower_copy(
|
||||
&codegen,
|
||||
&mut cursor,
|
||||
&mut resolver,
|
||||
*bid,
|
||||
func,
|
||||
&mut vmap,
|
||||
dst,
|
||||
src,
|
||||
&bb_map,
|
||||
&preds,
|
||||
&block_end_values,
|
||||
)?;
|
||||
defined_in_block.insert(*dst);
|
||||
},
|
||||
MirInstruction::Phi { .. } => {
|
||||
// Already created in pre-pass; nothing to do here.
|
||||
}
|
||||
@ -799,6 +815,23 @@ impl LLVMCompiler {
|
||||
&block_end_values,
|
||||
)?;
|
||||
}
|
||||
MirInstruction::Throw { .. } => {
|
||||
// Minimal lowering: call llvm.trap (optional) then unreachable
|
||||
let use_trap = std::env::var("NYASH_LLVM_TRAP_ON_THROW").ok().as_deref() != Some("0");
|
||||
if use_trap {
|
||||
let fn_ty = codegen.context.void_type().fn_type(&[], false);
|
||||
let trap = codegen
|
||||
.module
|
||||
.get_function("llvm.trap")
|
||||
.unwrap_or_else(|| codegen.module.add_function("llvm.trap", fn_ty, None));
|
||||
cursor.emit_term(*bid, |b| {
|
||||
let _ = b.build_call(trap, &[], "trap");
|
||||
});
|
||||
}
|
||||
// Ensure we end the block
|
||||
cursor.at_end(*bid, bb);
|
||||
let _ = codegen.builder.build_unreachable();
|
||||
}
|
||||
MirInstruction::Jump { target } => {
|
||||
// LoopForm simple body→dispatch wiring: if this block is a loop body
|
||||
// and jumps back to its header, redirect to dispatch and add PHI incoming
|
||||
|
||||
@ -24,28 +24,47 @@ fn main() {
|
||||
// Read stdin to tmp/ny_mir_builder_input.json for re-use
|
||||
let mut buf = Vec::new();
|
||||
io::stdin().read_to_end(&mut buf).expect("read stdin");
|
||||
if buf.is_empty() { eprintln!("error: no input on stdin"); std::process::exit(2); }
|
||||
let cwd_tmp = Path::new("tmp"); let _ = fs::create_dir_all(cwd_tmp);
|
||||
if buf.is_empty() {
|
||||
eprintln!("error: no input on stdin");
|
||||
std::process::exit(2);
|
||||
}
|
||||
let cwd_tmp = Path::new("tmp");
|
||||
let _ = fs::create_dir_all(cwd_tmp);
|
||||
let cwd_path = cwd_tmp.join("ny_mir_builder_input.json");
|
||||
fs::write(&cwd_path, &buf).expect("write cwd tmp json");
|
||||
cwd_path
|
||||
} else {
|
||||
let p = PathBuf::from(matches.get_one::<String>("in").unwrap());
|
||||
if !p.exists() { eprintln!("error: input not found: {}", p.display()); std::process::exit(2); }
|
||||
if !p.exists() {
|
||||
eprintln!("error: input not found: {}", p.display());
|
||||
std::process::exit(2);
|
||||
}
|
||||
p
|
||||
};
|
||||
|
||||
let emit = matches.get_one::<String>("emit").unwrap().as_str();
|
||||
let out_path = matches.get_one::<String>("out").map(|s| s.to_string()).unwrap_or_else(|| match emit {
|
||||
"obj" => format!("{}/target/aot_objects/a.o", std::env::current_dir().unwrap().display()),
|
||||
"ll" => format!("{}/target/aot_objects/a.ll", std::env::current_dir().unwrap().display()),
|
||||
let out_path = matches
|
||||
.get_one::<String>("out")
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| match emit {
|
||||
"obj" => format!(
|
||||
"{}/target/aot_objects/a.o",
|
||||
std::env::current_dir().unwrap().display()
|
||||
),
|
||||
"ll" => format!(
|
||||
"{}/target/aot_objects/a.ll",
|
||||
std::env::current_dir().unwrap().display()
|
||||
),
|
||||
"exe" => "a.out".to_string(),
|
||||
"json" => "/dev/stdout".to_string(),
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let verify = matches.get_flag("verify_llvm");
|
||||
let quiet = matches.get_flag("quiet");
|
||||
let nyrt_dir = matches.get_one::<String>("nyrt").map(|s| s.to_string()).unwrap_or("crates/nyrt".to_string());
|
||||
let nyrt_dir = matches
|
||||
.get_one::<String>("nyrt")
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or("crates/nyrt".to_string());
|
||||
|
||||
// Determine sibling nyash binary path (target dir)
|
||||
let nyash_bin = current_dir_bin("nyash");
|
||||
@ -56,47 +75,74 @@ fn main() {
|
||||
} else {
|
||||
fs::copy(&in_file, &out_path).expect("copy json");
|
||||
}
|
||||
if !quiet { println!("OK json:{}", out_path); }
|
||||
if !quiet {
|
||||
println!("OK json:{}", out_path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure build dirs
|
||||
let aot_dir = Path::new("target/aot_objects"); let _ = fs::create_dir_all(aot_dir);
|
||||
let aot_dir = Path::new("target/aot_objects");
|
||||
let _ = fs::create_dir_all(aot_dir);
|
||||
|
||||
match emit {
|
||||
"ll" => {
|
||||
std::env::set_var("NYASH_LLVM_DUMP_LL", "1");
|
||||
std::env::set_var("NYASH_LLVM_LL_OUT", &out_path);
|
||||
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
|
||||
if verify {
|
||||
std::env::set_var("NYASH_LLVM_VERIFY", "1");
|
||||
}
|
||||
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||
run_nyash_pipe(&nyash_bin, &in_file);
|
||||
if !Path::new(&out_path).exists() { eprintln!("error: failed to produce {}", out_path); std::process::exit(4); }
|
||||
if !quiet { println!("OK ll:{}", out_path); }
|
||||
if !Path::new(&out_path).exists() {
|
||||
eprintln!("error: failed to produce {}", out_path);
|
||||
std::process::exit(4);
|
||||
}
|
||||
if !quiet {
|
||||
println!("OK ll:{}", out_path);
|
||||
}
|
||||
}
|
||||
"obj" => {
|
||||
std::env::set_var("NYASH_LLVM_OBJ_OUT", &out_path);
|
||||
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
|
||||
if verify {
|
||||
std::env::set_var("NYASH_LLVM_VERIFY", "1");
|
||||
}
|
||||
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||
// remove stale
|
||||
let _ = fs::remove_file(&out_path);
|
||||
run_nyash_pipe(&nyash_bin, &in_file);
|
||||
if !Path::new(&out_path).exists() { eprintln!("error: failed to produce {}", out_path); std::process::exit(4); }
|
||||
if !quiet { println!("OK obj:{}", out_path); }
|
||||
if !Path::new(&out_path).exists() {
|
||||
eprintln!("error: failed to produce {}", out_path);
|
||||
std::process::exit(4);
|
||||
}
|
||||
if !quiet {
|
||||
println!("OK obj:{}", out_path);
|
||||
}
|
||||
}
|
||||
"exe" => {
|
||||
let obj_path = format!("{}/target/aot_objects/__tmp_mir_builder.o", std::env::current_dir().unwrap().display());
|
||||
let obj_path = format!(
|
||||
"{}/target/aot_objects/__tmp_mir_builder.o",
|
||||
std::env::current_dir().unwrap().display()
|
||||
);
|
||||
std::env::set_var("NYASH_LLVM_OBJ_OUT", &obj_path);
|
||||
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
|
||||
if verify {
|
||||
std::env::set_var("NYASH_LLVM_VERIFY", "1");
|
||||
}
|
||||
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||
let _ = fs::remove_file(&obj_path);
|
||||
run_nyash_pipe(&nyash_bin, &in_file);
|
||||
if !Path::new(&obj_path).exists() { eprintln!("error: failed to produce object {}", obj_path); std::process::exit(4); }
|
||||
if !Path::new(&obj_path).exists() {
|
||||
eprintln!("error: failed to produce object {}", obj_path);
|
||||
std::process::exit(4);
|
||||
}
|
||||
// Link with NyRT
|
||||
if let Err(e) = link_exe(&obj_path, &out_path, &nyrt_dir) {
|
||||
eprintln!("error: link failed: {}", e);
|
||||
std::process::exit(5);
|
||||
}
|
||||
if !quiet { println!("OK exe:{}", out_path); }
|
||||
if !quiet {
|
||||
println!("OK exe:{}", out_path);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@ -108,17 +154,23 @@ fn current_dir_bin(name: &str) -> PathBuf {
|
||||
if let Ok(cur) = std::env::current_exe() {
|
||||
if let Some(dir) = cur.parent() {
|
||||
let cand = dir.join(name);
|
||||
if cand.exists() { return cand; }
|
||||
if cand.exists() {
|
||||
return cand;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let cand = dir.join(format!("{}.exe", name));
|
||||
if cand.exists() { return cand; }
|
||||
if cand.exists() {
|
||||
return cand;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to target/release
|
||||
let cand = PathBuf::from("target/release").join(name);
|
||||
if cand.exists() { return cand; }
|
||||
if cand.exists() {
|
||||
return cand;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let cand = PathBuf::from("target/release").join(format!("{}.exe", name));
|
||||
@ -157,10 +209,17 @@ fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String
|
||||
args.push(obj_path.to_string());
|
||||
// Provide LIBPATH and library name (prefer nyrt.lib)
|
||||
args.push(format!("/LIBPATH:{}", nyrt_release));
|
||||
if std::path::Path::new(&lib_nyrt_lib).exists() { args.push("nyrt.lib".to_string()); }
|
||||
if std::path::Path::new(&lib_nyrt_lib).exists() {
|
||||
args.push("nyrt.lib".to_string());
|
||||
}
|
||||
// lld-link cannot consume .a directly; rely on .lib
|
||||
let status = PCommand::new("lld-link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?;
|
||||
if status.success() { return Ok(()); }
|
||||
let status = PCommand::new("lld-link")
|
||||
.args(args.iter().map(|s| s.as_str()))
|
||||
.status()
|
||||
.map_err(|e| e.to_string())?;
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!("lld-link failed: status {:?}", status.code()));
|
||||
}
|
||||
if which::which("link").is_ok() {
|
||||
@ -168,9 +227,16 @@ fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String
|
||||
args.push(format!("/OUT:{}", out_path));
|
||||
args.push(obj_path.to_string());
|
||||
args.push(format!("/LIBPATH:{}", nyrt_release));
|
||||
if std::path::Path::new(&lib_nyrt_lib).exists() { args.push("nyrt.lib".to_string()); }
|
||||
let status = PCommand::new("link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?;
|
||||
if status.success() { return Ok(()); }
|
||||
if std::path::Path::new(&lib_nyrt_lib).exists() {
|
||||
args.push("nyrt.lib".to_string());
|
||||
}
|
||||
let status = PCommand::new("link")
|
||||
.args(args.iter().map(|s| s.as_str()))
|
||||
.status()
|
||||
.map_err(|e| e.to_string())?;
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!("link.exe failed: status {:?}", status.code()));
|
||||
}
|
||||
// Fallback: try cc with MinGW-like flags
|
||||
@ -178,8 +244,11 @@ fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String
|
||||
.args([obj_path])
|
||||
.args(["-L", &format!("{}/target/release", nyrt_dir)])
|
||||
.args(["-lnyrt", "-o", out_path])
|
||||
.status().map_err(|e| e.to_string())?;
|
||||
if status.success() { return Ok(()); }
|
||||
.status()
|
||||
.map_err(|e| e.to_string())?;
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!("cc link failed: status {:?}", status.code()));
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
@ -190,7 +259,12 @@ fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String
|
||||
.args(["-L", &format!("{}/target/release", nyrt_dir)])
|
||||
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive"])
|
||||
.args(["-lpthread", "-ldl", "-lm", "-o", out_path])
|
||||
.status().map_err(|e| e.to_string())?;
|
||||
if status.success() { Ok(()) } else { Err(format!("cc failed: status {:?}", status.code())) }
|
||||
.status()
|
||||
.map_err(|e| e.to_string())?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("cc failed: status {:?}", status.code()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,9 @@ fn main() {
|
||||
println!("\nTesting box type resolution:");
|
||||
for box_type in ["StringBox", "FileBox", "MapBox"] {
|
||||
match config.find_library_for_box(box_type) {
|
||||
Some((name, lib)) => println!(" {} -> library: {} (path={})", box_type, name, lib.path),
|
||||
Some((name, lib)) => {
|
||||
println!(" {} -> library: {} (path={})", box_type, name, lib.path)
|
||||
}
|
||||
None => println!(" {} -> not found in config", box_type),
|
||||
}
|
||||
}
|
||||
@ -60,7 +62,8 @@ fn main() {
|
||||
println!("\nReverse host-call (by-slot) quick test:");
|
||||
// Create ArrayBox and obtain HostHandle
|
||||
let mut arr = nyash_rust::boxes::ArrayBox::new();
|
||||
arr.push(Box::new(nyash_rust::box_trait::StringBox::new("init")) as Box<dyn nyash_rust::box_trait::NyashBox>);
|
||||
arr.push(Box::new(nyash_rust::box_trait::StringBox::new("init"))
|
||||
as Box<dyn nyash_rust::box_trait::NyashBox>);
|
||||
let handle = nyash_rust::runtime::host_handles::to_handle_box(Box::new(arr));
|
||||
// Call Array.set(0, "hello") via slot=101
|
||||
let mut tlv = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(2);
|
||||
@ -68,21 +71,44 @@ fn main() {
|
||||
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv, "hello");
|
||||
let mut out = vec![0u8; 256];
|
||||
let mut out_len: usize = out.len();
|
||||
let code = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(handle, 101, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) };
|
||||
let code = unsafe {
|
||||
nyash_rust::runtime::host_api::nyrt_host_call_slot(
|
||||
handle,
|
||||
101,
|
||||
tlv.as_ptr(),
|
||||
tlv.len(),
|
||||
out.as_mut_ptr(),
|
||||
&mut out_len,
|
||||
)
|
||||
};
|
||||
println!(" set(slot=101) -> code={}, out_len={}", code, out_len);
|
||||
// Call Array.get(0) via slot=100 and decode
|
||||
let mut tlv2 = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(1);
|
||||
nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut tlv2, 0);
|
||||
let mut out2 = vec![0u8; 256];
|
||||
let mut out2_len: usize = out2.len();
|
||||
let code2 = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(handle, 100, tlv2.as_ptr(), tlv2.len(), out2.as_mut_ptr(), &mut out2_len) };
|
||||
let code2 = unsafe {
|
||||
nyash_rust::runtime::host_api::nyrt_host_call_slot(
|
||||
handle,
|
||||
100,
|
||||
tlv2.as_ptr(),
|
||||
tlv2.len(),
|
||||
out2.as_mut_ptr(),
|
||||
&mut out2_len,
|
||||
)
|
||||
};
|
||||
if code2 == 0 {
|
||||
if let Some((tag, _sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out2[..out2_len]) {
|
||||
if tag == 6 || tag == 7 { // string/bytes
|
||||
if let Some((tag, _sz, payload)) =
|
||||
nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out2[..out2_len])
|
||||
{
|
||||
if tag == 6 || tag == 7 {
|
||||
// string/bytes
|
||||
let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload);
|
||||
println!(" get(slot=100) -> tag={}, value='{}'", tag, s);
|
||||
} else if tag == 3 { // i64
|
||||
let v = nyash_rust::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or_default();
|
||||
} else if tag == 3 {
|
||||
// i64
|
||||
let v = nyash_rust::runtime::plugin_ffi_common::decode::i32(payload)
|
||||
.unwrap_or_default();
|
||||
println!(" get(slot=100) -> tag={}, i32={}", tag, v);
|
||||
} else {
|
||||
println!(" get(slot=100) -> tag={}, size={}", tag, _sz);
|
||||
@ -102,16 +128,36 @@ fn main() {
|
||||
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_set, "v");
|
||||
let mut out_s = vec![0u8; 256];
|
||||
let mut out_s_len: usize = out_s.len();
|
||||
let code_s = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 204, tlv_set.as_ptr(), tlv_set.len(), out_s.as_mut_ptr(), &mut out_s_len) };
|
||||
let code_s = unsafe {
|
||||
nyash_rust::runtime::host_api::nyrt_host_call_slot(
|
||||
map_h,
|
||||
204,
|
||||
tlv_set.as_ptr(),
|
||||
tlv_set.len(),
|
||||
out_s.as_mut_ptr(),
|
||||
&mut out_s_len,
|
||||
)
|
||||
};
|
||||
println!(" set(slot=204) -> code={}, out_len={}", code_s, out_s_len);
|
||||
// get("k") → slot=203
|
||||
let mut tlv_get = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(1);
|
||||
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_get, "k");
|
||||
let mut out_g = vec![0u8; 256];
|
||||
let mut out_g_len: usize = out_g.len();
|
||||
let code_g = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 203, tlv_get.as_ptr(), tlv_get.len(), out_g.as_mut_ptr(), &mut out_g_len) };
|
||||
let code_g = unsafe {
|
||||
nyash_rust::runtime::host_api::nyrt_host_call_slot(
|
||||
map_h,
|
||||
203,
|
||||
tlv_get.as_ptr(),
|
||||
tlv_get.len(),
|
||||
out_g.as_mut_ptr(),
|
||||
&mut out_g_len,
|
||||
)
|
||||
};
|
||||
if code_g == 0 {
|
||||
if let Some((tag, _sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out_g[..out_g_len]) {
|
||||
if let Some((tag, _sz, payload)) =
|
||||
nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out_g[..out_g_len])
|
||||
{
|
||||
if tag == 6 || tag == 7 {
|
||||
let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload);
|
||||
println!(" get(slot=203) -> '{}'", s);
|
||||
@ -123,13 +169,37 @@ fn main() {
|
||||
// has("k") → slot=202
|
||||
let mut out_hb = vec![0u8; 16];
|
||||
let mut out_hb_len: usize = out_hb.len();
|
||||
let code_hb = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 202, tlv_get.as_ptr(), tlv_get.len(), out_hb.as_mut_ptr(), &mut out_hb_len) };
|
||||
println!(" has(slot=202) -> code={}, out_len={}", code_hb, out_hb_len);
|
||||
let code_hb = unsafe {
|
||||
nyash_rust::runtime::host_api::nyrt_host_call_slot(
|
||||
map_h,
|
||||
202,
|
||||
tlv_get.as_ptr(),
|
||||
tlv_get.len(),
|
||||
out_hb.as_mut_ptr(),
|
||||
&mut out_hb_len,
|
||||
)
|
||||
};
|
||||
println!(
|
||||
" has(slot=202) -> code={}, out_len={}",
|
||||
code_hb, out_hb_len
|
||||
);
|
||||
// size() → slot=200
|
||||
let mut out_sz = vec![0u8; 32];
|
||||
let mut out_sz_len: usize = out_sz.len();
|
||||
let code_sz = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 200, std::ptr::null(), 0, out_sz.as_mut_ptr(), &mut out_sz_len) };
|
||||
println!(" size(slot=200) -> code={}, out_len={}", code_sz, out_sz_len);
|
||||
let code_sz = unsafe {
|
||||
nyash_rust::runtime::host_api::nyrt_host_call_slot(
|
||||
map_h,
|
||||
200,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
out_sz.as_mut_ptr(),
|
||||
&mut out_sz_len,
|
||||
)
|
||||
};
|
||||
println!(
|
||||
" size(slot=200) -> code={}, out_len={}",
|
||||
code_sz, out_sz_len
|
||||
);
|
||||
|
||||
println!("\nTest completed!");
|
||||
}
|
||||
|
||||
@ -85,6 +85,18 @@ pub fn await_max_ms() -> u64 {
|
||||
std::env::var("NYASH_AWAIT_MAX_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(5000)
|
||||
}
|
||||
|
||||
// ---- MIR PHI-less (edge-copy) mode ----
|
||||
/// Enable MIR PHI non-generation. Bridge/Builder emit edge copies instead of PHI.
|
||||
pub fn mir_no_phi() -> bool { std::env::var("NYASH_MIR_NO_PHI").ok().as_deref() == Some("1") }
|
||||
|
||||
/// Allow verifier to skip SSA/dominance/merge checks for PHI-less MIR.
|
||||
pub fn verify_allow_no_phi() -> bool {
|
||||
std::env::var("NYASH_VERIFY_ALLOW_NO_PHI").ok().as_deref() == Some("1") || mir_no_phi()
|
||||
}
|
||||
|
||||
// ---- LLVM harness toggle (llvmlite) ----
|
||||
pub fn llvm_use_harness() -> bool { std::env::var("NYASH_LLVM_USE_HARNESS").ok().as_deref() == Some("1") }
|
||||
|
||||
// ---- Phase 11.8 MIR cleanup toggles ----
|
||||
/// Core-13 minimal MIR mode toggle
|
||||
/// Default: ON (unless explicitly disabled with NYASH_MIR_CORE13=0)
|
||||
|
||||
@ -95,7 +95,7 @@ pub(crate) extern "C" fn nyash_plugin_invoke3_i64(type_id: i64, method_id: i64,
|
||||
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
|
||||
match tag {
|
||||
3 => { if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } }
|
||||
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let box_type_name = crate::runtime::plugin_loader_unified::get_global_plugin_host().read().ok().and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())).and_then(|m| m.into_iter().find(|(_k,v)| *v == r_type).map(|(k,_v)| k)).unwrap_or_else(|| "PluginBox".to_string()); let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke.unwrap()); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
|
||||
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let meta_opt = crate::runtime::plugin_loader_v2::metadata_for_type_id(r_type); let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt { (meta.box_type.clone(), meta.invoke_fn) } else { ("PluginBox".to_string(), invoke.unwrap()) }; let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke_ptr); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
|
||||
1 => { return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; }
|
||||
5 => { if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") { if _sz == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); let f = f64::from_le_bytes(b); return f as i64; } } }
|
||||
_ => {}
|
||||
@ -171,7 +171,7 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64
|
||||
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
|
||||
match tag {
|
||||
3 => { if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } }
|
||||
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type, r_type, r_inst, invoke.unwrap()); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
|
||||
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let meta_opt = crate::runtime::plugin_loader_v2::metadata_for_type_id(r_type); let (meta_box, invoke_ptr) = if let Some(meta) = meta_opt { (meta.box_type, meta.invoke_fn) } else { (box_type.clone(), invoke.unwrap()) }; let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(meta_box, r_type, r_inst, invoke_ptr); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
|
||||
1 => { return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; }
|
||||
5 => { if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") { if _sz == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); let f = f64::from_le_bytes(b); return f as i64; } } }
|
||||
_ => {}
|
||||
|
||||
@ -9,8 +9,6 @@ use crate::jit::r#extern::collections as c;
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
use crate::jit::r#extern::host_bridge as hb;
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
use crate::runtime::plugin_loader_unified;
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
use crate::runtime::plugin_loader_v2::PluginBoxV2;
|
||||
|
||||
// ---- Generic Birth (handle) ----
|
||||
@ -19,19 +17,15 @@ pub(super) extern "C" fn nyash_box_birth_h(type_id: i64) -> i64 {
|
||||
// Map type_id -> type name and create via plugin host; return runtime handle
|
||||
if type_id <= 0 { return 0; }
|
||||
let tid = type_id as u32;
|
||||
let name_opt = crate::runtime::plugin_loader_unified::get_global_plugin_host()
|
||||
.read().ok()
|
||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
||||
.and_then(|m| m.into_iter().find(|(_k,v)| *v == tid).map(|(k,_v)| k));
|
||||
if let Some(box_type) = name_opt {
|
||||
if let Some(meta) = crate::runtime::plugin_loader_v2::metadata_for_type_id(tid) {
|
||||
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
|
||||
if let Ok(b) = host.create_box(&box_type, &[]) {
|
||||
if let Ok(b) = host.create_box(&meta.box_type, &[]) {
|
||||
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(b);
|
||||
let h = crate::jit::rt::handles::to_handle(arc);
|
||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": box_type, "type_id": tid, "handle": h}), "hostcall", "<jit>");
|
||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": meta.box_type, "type_id": meta.type_id, "handle": h}), "hostcall", "<jit>");
|
||||
return h as i64;
|
||||
} else {
|
||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "create_failed", "box_type": box_type, "type_id": tid}), "hostcall", "<jit>");
|
||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "create_failed", "box_type": meta.box_type, "type_id": meta.type_id}), "hostcall", "<jit>");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -44,25 +38,13 @@ pub(super) extern "C" fn nyash_box_birth_h(type_id: i64) -> i64 {
|
||||
pub(super) extern "C" fn nyash_box_birth_i64(type_id: i64, argc: i64, a1: i64, a2: i64) -> i64 {
|
||||
use crate::runtime::plugin_loader_v2::PluginBoxV2;
|
||||
if type_id <= 0 { return 0; }
|
||||
// Resolve invoke for the type by creating a temp instance
|
||||
let mut invoke: Option<unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize)->i32> = None;
|
||||
let mut box_type = String::new();
|
||||
if let Some(name) = crate::runtime::plugin_loader_unified::get_global_plugin_host()
|
||||
.read().ok()
|
||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
||||
.and_then(|m| m.into_iter().find(|(_k,v)| *v == (type_id as u32)).map(|(k,_v)| k))
|
||||
{
|
||||
box_type = name;
|
||||
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
|
||||
if let Ok(b) = host.create_box(&box_type, &[]) {
|
||||
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() { invoke = Some(p.inner.invoke_fn); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if invoke.is_none() {
|
||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "no_invoke", "type_id": type_id}), "hostcall", "<jit>");
|
||||
// Resolve invoke for the type via loader metadata
|
||||
let Some(meta) = crate::runtime::plugin_loader_v2::metadata_for_type_id(type_id as u32) else {
|
||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "type_map_failed", "type_id": type_id}), "hostcall", "<jit>");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
let invoke_fn = meta.invoke_fn;
|
||||
let box_type = meta.box_type.clone();
|
||||
let method_id: u32 = 0; let instance_id: u32 = 0;
|
||||
// Build TLV from a1/a2
|
||||
let nargs = argc.max(0) as usize;
|
||||
@ -94,13 +76,13 @@ pub(super) extern "C" fn nyash_box_birth_i64(type_id: i64, argc: i64, a1: i64, a
|
||||
if nargs >= 2 { encode_val(a2); }
|
||||
// Invoke
|
||||
let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len();
|
||||
let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) };
|
||||
let rc = unsafe { invoke_fn(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) };
|
||||
if rc != 0 { events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "invoke_failed", "type_id": type_id}), "hostcall", "<jit>"); return 0; }
|
||||
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
|
||||
if tag == 8 && payload.len()==8 {
|
||||
let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]);
|
||||
let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i);
|
||||
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type.clone(), r_type, r_inst, invoke.unwrap());
|
||||
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type.clone(), r_type, r_inst, invoke_fn);
|
||||
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb);
|
||||
let h = crate::jit::rt::handles::to_handle(arc);
|
||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "box_type": box_type, "type_id": type_id, "argc": nargs, "handle": h}), "hostcall", "<jit>");
|
||||
|
||||
@ -87,12 +87,16 @@ pub struct MirBuilder {
|
||||
/// Top of stack corresponds to the innermost active loop
|
||||
pub(super) loop_header_stack: Vec<BasicBlockId>,
|
||||
pub(super) loop_exit_stack: Vec<BasicBlockId>,
|
||||
|
||||
/// Whether PHI emission is disabled (edge-copy mode)
|
||||
pub(super) no_phi_mode: bool,
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
/// Create a new MIR builder
|
||||
pub fn new() -> Self {
|
||||
let plugin_method_sigs = plugin_sigs::load_plugin_method_sigs();
|
||||
let no_phi_mode = crate::config::env::mir_no_phi();
|
||||
Self {
|
||||
current_module: None,
|
||||
current_function: None,
|
||||
@ -112,6 +116,7 @@ impl MirBuilder {
|
||||
include_box_map: HashMap::new(),
|
||||
loop_header_stack: Vec::new(),
|
||||
loop_exit_stack: Vec::new(),
|
||||
no_phi_mode,
|
||||
}
|
||||
}
|
||||
|
||||
@ -652,6 +657,42 @@ impl MirBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_no_phi_mode(&self) -> bool {
|
||||
self.no_phi_mode
|
||||
}
|
||||
|
||||
/// Insert a Copy instruction into `block_id`, defining `dst` from `src`.
|
||||
/// Skips blocks that terminate via return/throw, and avoids duplicate copies.
|
||||
pub(super) fn insert_edge_copy(
|
||||
&mut self,
|
||||
block_id: BasicBlockId,
|
||||
dst: ValueId,
|
||||
src: ValueId,
|
||||
) -> Result<(), String> {
|
||||
if let Some(ref mut function) = self.current_function {
|
||||
let block = function
|
||||
.get_block_mut(block_id)
|
||||
.ok_or_else(|| format!("Basic block {} does not exist", block_id))?;
|
||||
if let Some(term) = &block.terminator {
|
||||
if matches!(term, MirInstruction::Return { .. } | MirInstruction::Throw { .. }) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
let already_present = block.instructions.iter().any(|inst| {
|
||||
matches!(inst, MirInstruction::Copy { dst: existing_dst, .. } if *existing_dst == dst)
|
||||
});
|
||||
if !already_present {
|
||||
block.add_instruction(MirInstruction::Copy { dst, src });
|
||||
}
|
||||
if let Some(ty) = self.value_types.get(&src).cloned() {
|
||||
self.value_types.insert(dst, ty);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err("No current function".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// moved to builder/utils.rs: ensure_block_exists
|
||||
|
||||
// build_loop_statement_legacy moved to builder/stmts.rs
|
||||
|
||||
@ -40,7 +40,13 @@ impl super::MirBuilder {
|
||||
phi_inputs.push((else_block, else_val));
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
||||
self.start_new_block(merge_block)?;
|
||||
if self.is_no_phi_mode() {
|
||||
for (pred, val) in phi_inputs {
|
||||
self.insert_edge_copy(pred, result_val, val)?;
|
||||
}
|
||||
} else {
|
||||
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
|
||||
}
|
||||
return Ok(result_val);
|
||||
}
|
||||
|
||||
@ -83,7 +89,13 @@ impl super::MirBuilder {
|
||||
|
||||
// Merge and yield result
|
||||
self.start_new_block(merge_block)?;
|
||||
if self.is_no_phi_mode() {
|
||||
for (pred, val) in phi_inputs {
|
||||
self.insert_edge_copy(pred, result_val, val)?;
|
||||
}
|
||||
} else {
|
||||
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
|
||||
}
|
||||
Ok(result_val)
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +73,30 @@ impl MirBuilder {
|
||||
let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a));
|
||||
let result_val = self.value_gen.next();
|
||||
|
||||
if self.is_no_phi_mode() {
|
||||
if let Some(var_name) = assigned_var_then.clone() {
|
||||
let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
|
||||
let then_value_for_var = then_var_map_end.get(&var_name).copied().unwrap_or(then_value_raw);
|
||||
let else_value_for_var = if else_assigns_same {
|
||||
else_var_map_end_opt
|
||||
.as_ref()
|
||||
.and_then(|m| m.get(&var_name).copied())
|
||||
.unwrap_or(else_value_raw)
|
||||
} else {
|
||||
pre_then_var_value.unwrap_or(else_value_raw)
|
||||
};
|
||||
self.insert_edge_copy(then_block, result_val, then_value_for_var)?;
|
||||
self.insert_edge_copy(else_block, result_val, else_value_for_var)?;
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
self.variable_map.insert(var_name, result_val);
|
||||
} else {
|
||||
self.insert_edge_copy(then_block, result_val, then_value_raw)?;
|
||||
self.insert_edge_copy(else_block, result_val, else_value_raw)?;
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
}
|
||||
return Ok(result_val);
|
||||
}
|
||||
|
||||
if let Some(var_name) = assigned_var_then.clone() {
|
||||
let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
|
||||
// Resolve branch-end values for the assigned variable
|
||||
|
||||
@ -10,7 +10,7 @@ use super::{
|
||||
ConstValue
|
||||
};
|
||||
use crate::ast::ASTNode;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// 不完全なPhi nodeの情報
|
||||
#[derive(Debug, Clone)]
|
||||
@ -40,6 +40,9 @@ pub struct LoopBuilder<'a> {
|
||||
|
||||
/// continue文からの変数スナップショット
|
||||
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
|
||||
|
||||
/// PHI を生成しないモードかどうか
|
||||
no_phi_mode: bool,
|
||||
}
|
||||
|
||||
// Local copy: detect a variable name assigned within an AST fragment
|
||||
@ -68,12 +71,14 @@ fn extract_assigned_var_local(ast: &ASTNode) -> Option<String> {
|
||||
impl<'a> LoopBuilder<'a> {
|
||||
/// 新しいループビルダーを作成
|
||||
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
|
||||
let no_phi_mode = parent.is_no_phi_mode();
|
||||
Self {
|
||||
parent_builder: parent,
|
||||
incomplete_phis: HashMap::new(),
|
||||
block_var_maps: HashMap::new(),
|
||||
loop_header: None,
|
||||
continue_snapshots: Vec::new(),
|
||||
no_phi_mode,
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,6 +180,10 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
incomplete_phis.push(incomplete_phi);
|
||||
|
||||
if self.no_phi_mode {
|
||||
self.parent_builder.insert_edge_copy(preheader_id, phi_id, value_before)?;
|
||||
}
|
||||
|
||||
// 変数マップを更新(Phi nodeの結果を使用)
|
||||
self.update_variable(var_name.clone(), phi_id);
|
||||
}
|
||||
@ -208,7 +217,16 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
phi.known_inputs.push((latch_id, value_after));
|
||||
|
||||
if self.no_phi_mode {
|
||||
let mut seen: HashSet<BasicBlockId> = HashSet::new();
|
||||
for &(pred, val) in &phi.known_inputs {
|
||||
if seen.insert(pred) {
|
||||
self.parent_builder.insert_edge_copy(pred, phi.phi_id, val)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?;
|
||||
}
|
||||
self.update_variable(phi.var_name.clone(), phi.phi_id);
|
||||
}
|
||||
}
|
||||
@ -422,7 +440,12 @@ impl<'a> LoopBuilder<'a> {
|
||||
};
|
||||
if let (Some(tv), Some(ev)) = (then_value_for_var, else_value_for_var) {
|
||||
let phi_id = self.new_value();
|
||||
if self.no_phi_mode {
|
||||
self.parent_builder.insert_edge_copy(then_bb, phi_id, tv)?;
|
||||
self.parent_builder.insert_edge_copy(else_bb, phi_id, ev)?;
|
||||
} else {
|
||||
self.emit_phi_at_block_start(merge_bb, phi_id, vec![(then_bb, tv), (else_bb, ev)])?;
|
||||
}
|
||||
// Reset to pre-if map and bind the phi result
|
||||
self.parent_builder.variable_map = pre_if_var_map.clone();
|
||||
self.parent_builder.variable_map.insert(var_name, phi_id);
|
||||
|
||||
@ -428,6 +428,11 @@ impl MirVerifier {
|
||||
|
||||
/// Verify SSA form properties
|
||||
fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
|
||||
if crate::config::env::verify_allow_no_phi()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let mut errors = Vec::new();
|
||||
let mut definitions = HashMap::new();
|
||||
|
||||
@ -470,6 +475,11 @@ impl MirVerifier {
|
||||
|
||||
/// Verify dominance relations (def must dominate use across blocks)
|
||||
fn verify_dominance(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
|
||||
if crate::config::env::verify_allow_no_phi()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let mut errors = Vec::new();
|
||||
|
||||
// Build def -> block map and dominators
|
||||
@ -536,6 +546,11 @@ impl MirVerifier {
|
||||
/// Verify that blocks with multiple predecessors do not use predecessor-defined values directly.
|
||||
/// In merge blocks, values coming from predecessors must be routed through Phi.
|
||||
fn verify_merge_uses(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
|
||||
if crate::config::env::verify_allow_no_phi()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let mut errors = Vec::new();
|
||||
let preds = self.compute_predecessors(function);
|
||||
let def_block = self.compute_def_blocks(function);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
||||
#[cfg(test)]
|
||||
mod array_state_sharing_tests {
|
||||
use nyash_rust::box_trait::{IntegerBox, NyashBox, StringBox};
|
||||
use nyash_rust::boxes::array::ArrayBox;
|
||||
use nyash_rust::interpreter::NyashInterpreter;
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::boxes::array::ArrayBox;
|
||||
use nyash_rust::box_trait::{NyashBox, IntegerBox, StringBox};
|
||||
|
||||
#[test]
|
||||
fn test_arraybox_state_sharing_bug_fix() {
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
|
||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
|
||||
fn try_init_plugins() -> bool {
|
||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
||||
if !std::path::Path::new("nyash.toml").exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") {
|
||||
eprintln!("[e2e] init_global_loader_v2 failed: {:?}", e);
|
||||
return false;
|
||||
@ -18,18 +19,24 @@ fn try_init_plugins() -> bool {
|
||||
if let Some(conf) = &loader.config {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (lib_name, lib_def) in &conf.libraries {
|
||||
for b in &lib_def.boxes { map.insert(b.clone(), lib_name.clone()); }
|
||||
for b in &lib_def.boxes {
|
||||
map.insert(b.clone(), lib_name.clone());
|
||||
}
|
||||
}
|
||||
let reg = get_global_registry();
|
||||
reg.apply_plugin_config(&PluginConfig { plugins: map });
|
||||
true
|
||||
} else { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin loader semantics: counter handle parity pending"]
|
||||
fn e2e_counter_basic_inc_get() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
local c, v1, v2
|
||||
@ -54,7 +61,9 @@ v2
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin loader semantics: assignment sharing parity pending"]
|
||||
fn e2e_counter_assignment_shares_handle() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
local c, x, v
|
||||
@ -79,7 +88,9 @@ v
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin loader semantics: MapBox shared handle parity pending"]
|
||||
fn e2e_counter_mapbox_shares_handle() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
local c, m, v
|
||||
|
||||
@ -2,84 +2,173 @@
|
||||
//! E2E test for unified registry with a mock plugin factory
|
||||
use std::sync::Arc;
|
||||
|
||||
use nyash_rust::box_factory::BoxFactory;
|
||||
use nyash_rust::box_factory::builtin::BuiltinGroups;
|
||||
use nyash_rust::interpreter::{NyashInterpreter, SharedState, RuntimeError};
|
||||
use nyash_rust::box_factory::BoxFactory;
|
||||
use nyash_rust::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
|
||||
use nyash_rust::interpreter::{NyashInterpreter, RuntimeError, SharedState};
|
||||
use nyash_rust::runtime::NyashRuntimeBuilder;
|
||||
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
|
||||
|
||||
// ---------- Mock plugin boxes ----------
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct EchoBox { base: BoxBase, msg: String }
|
||||
struct EchoBox {
|
||||
base: BoxBase,
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl EchoBox { fn new(msg: String) -> Self { Self { base: BoxBase::new(), msg } } }
|
||||
impl EchoBox {
|
||||
fn new(msg: String) -> Self {
|
||||
Self {
|
||||
base: BoxBase::new(),
|
||||
msg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for EchoBox {
|
||||
fn box_id(&self) -> u64 { self.base.id }
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
||||
fn box_id(&self) -> u64 {
|
||||
self.base.id
|
||||
}
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||
None
|
||||
}
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "EchoBox(\"{}\")", self.msg)
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for EchoBox {
|
||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.msg.clone()) }
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() { BoolBox::new(self.msg == e.msg) } else { BoolBox::new(false) }
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(self.msg.clone())
|
||||
}
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() {
|
||||
BoolBox::new(self.msg == e.msg)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"EchoBox"
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn type_name(&self) -> &'static str { "EchoBox" }
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AdderBox { base: BoxBase, sum: i64 }
|
||||
impl AdderBox { fn new(a: i64, b: i64) -> Self { Self { base: BoxBase::new(), sum: a + b } } }
|
||||
struct AdderBox {
|
||||
base: BoxBase,
|
||||
sum: i64,
|
||||
}
|
||||
impl AdderBox {
|
||||
fn new(a: i64, b: i64) -> Self {
|
||||
Self {
|
||||
base: BoxBase::new(),
|
||||
sum: a + b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for AdderBox {
|
||||
fn box_id(&self) -> u64 { self.base.id }
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "AdderBox(sum={})", self.sum) }
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
||||
fn box_id(&self) -> u64 {
|
||||
self.base.id
|
||||
}
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||
None
|
||||
}
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "AdderBox(sum={})", self.sum)
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for AdderBox {
|
||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.sum.to_string()) }
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() { BoolBox::new(self.sum == a.sum) } else { BoolBox::new(false) }
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(self.sum.to_string())
|
||||
}
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() {
|
||||
BoolBox::new(self.sum == a.sum)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"AdderBox"
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn type_name(&self) -> &'static str { "AdderBox" }
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
}
|
||||
|
||||
// ---------- Mock plugin factory ----------
|
||||
|
||||
struct TestPluginFactory;
|
||||
impl TestPluginFactory { fn new() -> Self { Self } }
|
||||
impl TestPluginFactory {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxFactory for TestPluginFactory {
|
||||
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
fn create_box(
|
||||
&self,
|
||||
name: &str,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match name {
|
||||
"EchoBox" => {
|
||||
let msg = args.get(0).map(|a| a.to_string_box().value).unwrap_or_else(|| "".to_string());
|
||||
let msg = args
|
||||
.get(0)
|
||||
.map(|a| a.to_string_box().value)
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
Ok(Box::new(EchoBox::new(msg)))
|
||||
}
|
||||
"AdderBox" => {
|
||||
if args.len() != 2 { return Err(RuntimeError::InvalidOperation{ message: format!("AdderBox expects 2 args, got {}", args.len()) }); }
|
||||
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?;
|
||||
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg b must be int".into() })?;
|
||||
if args.len() != 2 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("AdderBox expects 2 args, got {}", args.len()),
|
||||
});
|
||||
}
|
||||
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| {
|
||||
RuntimeError::TypeError {
|
||||
message: "AdderBox arg a must be int".into(),
|
||||
}
|
||||
})?;
|
||||
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| {
|
||||
RuntimeError::TypeError {
|
||||
message: "AdderBox arg b must be int".into(),
|
||||
}
|
||||
})?;
|
||||
Ok(Box::new(AdderBox::new(a, b)))
|
||||
}
|
||||
_ => Err(RuntimeError::InvalidOperation{ message: format!("Unknown Box type: {}", name) })
|
||||
_ => Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Unknown Box type: {}", name),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn box_types(&self) -> Vec<&str> { vec!["EchoBox", "AdderBox"] }
|
||||
fn box_types(&self) -> Vec<&str> {
|
||||
vec!["EchoBox", "AdderBox"]
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- E2E tests ----------
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
|
||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
use nyash_rust::runtime::NyashRuntime;
|
||||
use nyash_rust::backend::VM;
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
|
||||
use nyash_rust::runtime::NyashRuntime;
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
|
||||
fn try_init_plugins() -> bool {
|
||||
if !std::path::Path::new("nyash.toml").exists() {
|
||||
@ -39,7 +38,9 @@ fn try_init_plugins() -> bool {
|
||||
#[test]
|
||||
#[ignore = "Plugins not configured by default env; skip in CI"]
|
||||
fn e2e_interpreter_plugin_filebox_close_void() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
local f
|
||||
@ -67,7 +68,9 @@ f.close()
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin FileBox: delegation via from Base.birth/close pending"]
|
||||
fn e2e_interpreter_plugin_filebox_delegation() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
box LoggingFileBox from FileBox {
|
||||
@ -107,7 +110,9 @@ lf.close()
|
||||
#[test]
|
||||
#[ignore = "Plugins not configured by default env; skip in CI"]
|
||||
fn e2e_vm_plugin_filebox_close_void() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
if nyash_rust::config::env::mir_core13_pure() {
|
||||
eprintln!("[E2E] skip vm filebox under Core-13 pure mode");
|
||||
return;
|
||||
@ -124,7 +129,9 @@ f.close()
|
||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
||||
let result = vm
|
||||
.execute_module(&compile_result.module)
|
||||
.expect("vm exec failed");
|
||||
// close() is void; ensure result is void
|
||||
assert_eq!(result.to_string_box().value, "void");
|
||||
}
|
||||
@ -132,7 +139,9 @@ f.close()
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin FileBox: VM open/rw/read path pending parity"]
|
||||
fn e2e_vm_plugin_filebox_open_rw() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open, write, read via VM backend
|
||||
let code = r#"
|
||||
@ -149,19 +158,24 @@ data
|
||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
||||
let result = vm
|
||||
.execute_module(&compile_result.module)
|
||||
.expect("vm exec failed");
|
||||
assert_eq!(result.to_string_box().value, "HELLO");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin FileBox: VM copyFrom(handle) path pending parity"]
|
||||
fn e2e_vm_plugin_filebox_copy_from_handle() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let p1 = "./test_out_src.txt";
|
||||
let p2 = "./test_out_dst.txt";
|
||||
|
||||
let code = format!(r#"
|
||||
let code = format!(
|
||||
r#"
|
||||
local a, b, data
|
||||
a = new FileBox()
|
||||
b = new FileBox()
|
||||
@ -171,28 +185,35 @@ a.write("HELLO")
|
||||
b.copyFrom(a)
|
||||
data = b.read()
|
||||
data
|
||||
"#, p1, p2);
|
||||
"#,
|
||||
p1, p2
|
||||
);
|
||||
|
||||
let ast = NyashParser::parse_from_string(&code).expect("parse failed");
|
||||
let runtime = NyashRuntime::new();
|
||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
||||
let result = vm
|
||||
.execute_module(&compile_result.module)
|
||||
.expect("vm exec failed");
|
||||
assert_eq!(result.to_string_box().value, "HELLO");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin FileBox: interpreter copyFrom(handle) path pending parity"]
|
||||
fn e2e_interpreter_plugin_filebox_copy_from_handle() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare two files and copy contents via plugin Handle argument
|
||||
let p1 = "./test_out_src.txt";
|
||||
let p2 = "./test_out_dst.txt";
|
||||
|
||||
// Nyash program: open two FileBox, write to src, copy to dst via copyFrom, then read dst
|
||||
let code = format!(r#"
|
||||
let code = format!(
|
||||
r#"
|
||||
local a, b, data
|
||||
a = new FileBox()
|
||||
b = new FileBox()
|
||||
@ -202,7 +223,9 @@ a.write("HELLO")
|
||||
b.copyFrom(a)
|
||||
data = b.read()
|
||||
data
|
||||
"#, p1, p2);
|
||||
"#,
|
||||
p1, p2
|
||||
);
|
||||
|
||||
let ast = NyashParser::parse_from_string(&code).expect("parse failed");
|
||||
let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new();
|
||||
|
||||
@ -2,46 +2,93 @@
|
||||
//! E2E: Interop between builtin and mock plugin boxes via MapBox storage
|
||||
use std::sync::Arc;
|
||||
|
||||
use nyash_rust::box_factory::BoxFactory;
|
||||
use nyash_rust::box_factory::builtin::BuiltinGroups;
|
||||
use nyash_rust::box_factory::BoxFactory;
|
||||
use nyash_rust::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
|
||||
use nyash_rust::interpreter::{NyashInterpreter, RuntimeError};
|
||||
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct EchoBox { base: BoxBase, msg: String }
|
||||
impl EchoBox { fn new(msg: String) -> Self { Self { base: BoxBase::new(), msg } } }
|
||||
struct EchoBox {
|
||||
base: BoxBase,
|
||||
msg: String,
|
||||
}
|
||||
impl EchoBox {
|
||||
fn new(msg: String) -> Self {
|
||||
Self {
|
||||
base: BoxBase::new(),
|
||||
msg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for EchoBox {
|
||||
fn box_id(&self) -> u64 { self.base.id }
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "EchoBox(\"{}\")", self.msg) }
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
||||
fn box_id(&self) -> u64 {
|
||||
self.base.id
|
||||
}
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||
None
|
||||
}
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "EchoBox(\"{}\")", self.msg)
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for EchoBox {
|
||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.msg.clone()) }
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() { BoolBox::new(self.msg == e.msg) } else { BoolBox::new(false) }
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(self.msg.clone())
|
||||
}
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() {
|
||||
BoolBox::new(self.msg == e.msg)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"EchoBox"
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn type_name(&self) -> &'static str { "EchoBox" }
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
}
|
||||
|
||||
struct TestPluginFactory;
|
||||
impl TestPluginFactory { fn new() -> Self { Self } }
|
||||
impl TestPluginFactory {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
impl BoxFactory for TestPluginFactory {
|
||||
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
fn create_box(
|
||||
&self,
|
||||
name: &str,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match name {
|
||||
"EchoBox" => {
|
||||
let msg = args.get(0).map(|a| a.to_string_box().value).unwrap_or_else(|| "".to_string());
|
||||
let msg = args
|
||||
.get(0)
|
||||
.map(|a| a.to_string_box().value)
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
Ok(Box::new(EchoBox::new(msg)))
|
||||
}
|
||||
_ => Err(RuntimeError::InvalidOperation{ message: format!("Unknown Box type: {}", name) })
|
||||
_ => Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Unknown Box type: {}", name),
|
||||
}),
|
||||
}
|
||||
}
|
||||
fn box_types(&self) -> Vec<&str> { vec!["EchoBox"] }
|
||||
fn box_types(&self) -> Vec<&str> {
|
||||
vec!["EchoBox"]
|
||||
}
|
||||
}
|
||||
|
||||
fn new_interpreter_with_factory() -> NyashInterpreter {
|
||||
|
||||
@ -1,24 +1,34 @@
|
||||
|
||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
use nyash_rust::runtime::NyashRuntime;
|
||||
use nyash_rust::backend::VM;
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
|
||||
use nyash_rust::runtime::NyashRuntime;
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
|
||||
fn try_init_plugins() -> bool {
|
||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
||||
if !std::path::Path::new("nyash.toml").exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") {
|
||||
eprintln!("init failed: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
let loader = get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
if let Some(conf) = &loader.config {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
|
||||
for (lib, def) in &conf.libraries {
|
||||
for b in &def.boxes {
|
||||
map.insert(b.clone(), lib.clone());
|
||||
}
|
||||
}
|
||||
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||
true
|
||||
} else { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -26,7 +36,9 @@ fn try_init_plugins() -> bool {
|
||||
fn e2e_http_two_servers_parallel() {
|
||||
std::env::set_var("NYASH_NET_LOG", "1");
|
||||
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
local s1, s2, c, r1, r2, resp1, resp2, req1, req2, p1, p2, x, y
|
||||
@ -73,7 +85,9 @@ x + ":" + y
|
||||
fn e2e_http_long_body_and_headers() {
|
||||
std::env::set_var("NYASH_NET_LOG", "1");
|
||||
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
local s, c, r, resp, q, body, hv
|
||||
@ -106,13 +120,14 @@ hv + ":" + body
|
||||
assert!(s.contains("OK-LONG"));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin Net: client error result semantics pending"]
|
||||
fn e2e_vm_http_client_error_result() {
|
||||
std::env::set_var("NYASH_NET_LOG", "1");
|
||||
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
// No server on 8099 → should produce Err result
|
||||
let code = r#"
|
||||
@ -133,7 +148,9 @@ result
|
||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
||||
let result = vm
|
||||
.execute_module(&compile_result.module)
|
||||
.expect("vm exec failed");
|
||||
let s = result.to_string_box().value;
|
||||
assert!(s.contains("Error") || s.contains("unexpected_ok") == false);
|
||||
}
|
||||
@ -143,7 +160,9 @@ result
|
||||
fn e2e_vm_http_empty_body() {
|
||||
std::env::set_var("NYASH_NET_LOG", "1");
|
||||
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
local srv, cli, r, resp, req, body
|
||||
@ -169,6 +188,8 @@ body
|
||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
||||
let result = vm
|
||||
.execute_module(&compile_result.module)
|
||||
.expect("vm exec failed");
|
||||
assert_eq!(result.to_string_box().value, "");
|
||||
}
|
||||
|
||||
@ -1,28 +1,40 @@
|
||||
|
||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
|
||||
fn try_init_plugins() -> bool {
|
||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
||||
if !std::path::Path::new("nyash.toml").exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") {
|
||||
eprintln!("init failed: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
let loader = get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
if let Some(conf) = &loader.config {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
|
||||
for (lib, def) in &conf.libraries {
|
||||
for b in &def.boxes {
|
||||
map.insert(b.clone(), lib.clone());
|
||||
}
|
||||
}
|
||||
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||
true
|
||||
} else { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin singleton: CounterBox shared instance parity pending"]
|
||||
fn e2e_counterbox_singleton_shared_across_news() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
// CounterBox is configured as singleton in nyash.toml
|
||||
let code = r#"
|
||||
|
||||
@ -1,28 +1,42 @@
|
||||
|
||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2, shutdown_plugins_v2};
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{
|
||||
get_global_loader_v2, init_global_loader_v2, shutdown_plugins_v2,
|
||||
};
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
|
||||
fn try_init_plugins() -> bool {
|
||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
||||
if !std::path::Path::new("nyash.toml").exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") {
|
||||
eprintln!("init failed: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
let loader = get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
if let Some(conf) = &loader.config {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
|
||||
for (lib, def) in &conf.libraries {
|
||||
for b in &def.boxes {
|
||||
map.insert(b.clone(), lib.clone());
|
||||
}
|
||||
}
|
||||
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||
true
|
||||
} else { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin singleton: shutdown/recreate parity pending"]
|
||||
fn e2e_singleton_shutdown_and_recreate() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use CounterBox singleton and bump to 1
|
||||
let code1 = r#"
|
||||
|
||||
@ -1,28 +1,40 @@
|
||||
|
||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
|
||||
fn try_init_plugins() -> bool {
|
||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
||||
if !std::path::Path::new("nyash.toml").exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") {
|
||||
eprintln!("init failed: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
let loader = get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
if let Some(conf) = &loader.config {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
|
||||
for (lib, def) in &conf.libraries {
|
||||
for b in &def.boxes {
|
||||
map.insert(b.clone(), lib.clone());
|
||||
}
|
||||
}
|
||||
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||
true
|
||||
} else { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin Socket: ping/pong parity pending (start method)"]
|
||||
fn e2e_socket_ping_pong() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start server, client connect, ping/pong
|
||||
let code = r#"
|
||||
@ -52,7 +64,9 @@ r
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin Socket: accept/recv timeout parity pending (start method)"]
|
||||
fn e2e_socket_accept_timeout_and_recv_timeout() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = r#"
|
||||
local ss, sc, c, s, r
|
||||
|
||||
@ -2,49 +2,92 @@
|
||||
//! E2E: Reserved-name guard for unified registry
|
||||
use std::sync::Arc;
|
||||
|
||||
use nyash_rust::box_factory::BoxFactory;
|
||||
use nyash_rust::box_factory::builtin::BuiltinGroups;
|
||||
use nyash_rust::box_factory::BoxFactory;
|
||||
use nyash_rust::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
|
||||
use nyash_rust::interpreter::NyashInterpreter;
|
||||
use nyash_rust::interpreter::RuntimeError;
|
||||
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
|
||||
|
||||
// Dummy factory that tries to claim reserved core types
|
||||
struct BadFactory;
|
||||
impl BadFactory { fn new() -> Self { Self } }
|
||||
impl BadFactory {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct FakeStringBox { base: BoxBase, inner: String }
|
||||
struct FakeStringBox {
|
||||
base: BoxBase,
|
||||
inner: String,
|
||||
}
|
||||
|
||||
impl BoxCore for FakeStringBox {
|
||||
fn box_id(&self) -> u64 { self.base.id }
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "FakeString(\"{}\")", self.inner) }
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
||||
fn box_id(&self) -> u64 {
|
||||
self.base.id
|
||||
}
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||
None
|
||||
}
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "FakeString(\"{}\")", self.inner)
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for FakeStringBox {
|
||||
fn to_string_box(&self) -> StringBox { StringBox::new(format!("FAKE:{}", self.inner)) }
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(s) = other.as_any().downcast_ref::<FakeStringBox>() { BoolBox::new(self.inner == s.inner) } else { BoolBox::new(false) }
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("FAKE:{}", self.inner))
|
||||
}
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(s) = other.as_any().downcast_ref::<FakeStringBox>() {
|
||||
BoolBox::new(self.inner == s.inner)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"StringBox"
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn type_name(&self) -> &'static str { "StringBox" }
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
}
|
||||
|
||||
impl BoxFactory for BadFactory {
|
||||
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
fn create_box(
|
||||
&self,
|
||||
name: &str,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match name {
|
||||
// Attempt to hijack StringBox
|
||||
"StringBox" => {
|
||||
let s = args.get(0).map(|a| a.to_string_box().value).unwrap_or_default();
|
||||
Ok(Box::new(FakeStringBox { base: BoxBase::new(), inner: s }))
|
||||
let s = args
|
||||
.get(0)
|
||||
.map(|a| a.to_string_box().value)
|
||||
.unwrap_or_default();
|
||||
Ok(Box::new(FakeStringBox {
|
||||
base: BoxBase::new(),
|
||||
inner: s,
|
||||
}))
|
||||
}
|
||||
_ => Err(RuntimeError::InvalidOperation { message: format!("Unknown Box type: {}", name) })
|
||||
_ => Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Unknown Box type: {}", name),
|
||||
}),
|
||||
}
|
||||
}
|
||||
fn box_types(&self) -> Vec<&str> { vec!["StringBox"] }
|
||||
fn box_types(&self) -> Vec<&str> {
|
||||
vec!["StringBox"]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use nyash_rust::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
|
||||
use nyash_rust::grammar::engine;
|
||||
use nyash_rust::box_trait::{StringBox, IntegerBox, BoolBox, VoidBox, NyashBox};
|
||||
|
||||
fn classify_value(b: &dyn NyashBox) -> &'static str {
|
||||
if nyash_rust::runtime::semantics::coerce_to_string(b).is_some() {
|
||||
@ -19,12 +19,14 @@ fn actual_add_result(left: &dyn NyashBox, right: &dyn NyashBox) -> &'static str
|
||||
// Mirror current interpreter semantics succinctly:
|
||||
// 1) If either is string-like => String
|
||||
if nyash_rust::runtime::semantics::coerce_to_string(left).is_some()
|
||||
|| nyash_rust::runtime::semantics::coerce_to_string(right).is_some() {
|
||||
|| nyash_rust::runtime::semantics::coerce_to_string(right).is_some()
|
||||
{
|
||||
return "String";
|
||||
}
|
||||
// 2) If both are i64-coercible => Integer
|
||||
if nyash_rust::runtime::semantics::coerce_to_i64(left).is_some()
|
||||
&& nyash_rust::runtime::semantics::coerce_to_i64(right).is_some() {
|
||||
&& nyash_rust::runtime::semantics::coerce_to_i64(right).is_some()
|
||||
{
|
||||
return "Integer";
|
||||
}
|
||||
// 3) Otherwise error(ここでは Error として表現)
|
||||
@ -54,12 +56,23 @@ fn snapshot_add_rules_align_with_current_semantics() {
|
||||
let expect = eng.decide_add_result(lty, rty).map(|(res, _)| res);
|
||||
if let Some(res) = expect {
|
||||
if actual == "Error" {
|
||||
panic!("grammar provides rule for {}+{} but actual semantics error", li, ri);
|
||||
panic!(
|
||||
"grammar provides rule for {}+{} but actual semantics error",
|
||||
li, ri
|
||||
);
|
||||
} else {
|
||||
assert_eq!(res, actual, "grammar expect {} + {} => {}, but actual => {}", li, ri, res, actual);
|
||||
assert_eq!(
|
||||
res, actual,
|
||||
"grammar expect {} + {} => {}, but actual => {}",
|
||||
li, ri, res, actual
|
||||
);
|
||||
}
|
||||
} else {
|
||||
assert_eq!(actual, "Error", "grammar has no rule for {}+{}, but actual => {}", li, ri, actual);
|
||||
assert_eq!(
|
||||
actual, "Error",
|
||||
"grammar has no rule for {}+{}, but actual => {}",
|
||||
li, ri, actual
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,14 +6,22 @@ fn grammar_sub_mul_div_rules_exist_and_basic_cases() {
|
||||
|
||||
// Sub
|
||||
assert!(!eng.sub_rules().is_empty(), "sub rules should not be empty");
|
||||
assert!(eng.decide_sub_result("Integer","Integer").is_some(), "sub i64+i64 should be defined");
|
||||
assert!(
|
||||
eng.decide_sub_result("Integer", "Integer").is_some(),
|
||||
"sub i64+i64 should be defined"
|
||||
);
|
||||
|
||||
// Mul
|
||||
assert!(!eng.mul_rules().is_empty(), "mul rules should not be empty");
|
||||
assert!(eng.decide_mul_result("Integer","Integer").is_some(), "mul i64*i64 should be defined");
|
||||
assert!(
|
||||
eng.decide_mul_result("Integer", "Integer").is_some(),
|
||||
"mul i64*i64 should be defined"
|
||||
);
|
||||
|
||||
// Div
|
||||
assert!(!eng.div_rules().is_empty(), "div rules should not be empty");
|
||||
assert!(eng.decide_div_result("Integer","Integer").is_some(), "div i64/i64 should be defined");
|
||||
assert!(
|
||||
eng.decide_div_result("Integer", "Integer").is_some(),
|
||||
"div i64/i64 should be defined"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -27,7 +27,9 @@ fn get_variable_value(code: &str, var_name: &str) -> Result<String, String> {
|
||||
match parser::NyashParser::parse_from_string(code) {
|
||||
Ok(ast) => {
|
||||
let mut interpreter = interpreter::NyashInterpreter::new();
|
||||
interpreter.execute(ast).map_err(|e| format!("Execution error: {}", e))?;
|
||||
interpreter
|
||||
.execute(ast)
|
||||
.map_err(|e| format!("Execution error: {}", e))?;
|
||||
|
||||
match interpreter.get_variable(var_name) {
|
||||
Ok(value) => Ok(value.to_string_box().value),
|
||||
@ -350,7 +352,10 @@ mod integration_tests {
|
||||
assert_eq!(get_variable_value(code, "less_result").unwrap(), "true");
|
||||
assert_eq!(get_variable_value(code, "greater_result").unwrap(), "true");
|
||||
assert_eq!(get_variable_value(code, "less_eq_result").unwrap(), "true");
|
||||
assert_eq!(get_variable_value(code, "greater_eq_result").unwrap(), "true");
|
||||
assert_eq!(
|
||||
get_variable_value(code, "greater_eq_result").unwrap(),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
10
tests/json_v0_stage3/break_continue.json
Normal file
10
tests/json_v0_stage3/break_continue.json
Normal file
@ -0,0 +1,10 @@
|
||||
{"version":0,"kind":"Program","body":[
|
||||
{"type":"Local","name":"i","expr":{"type":"Int","value":0}},
|
||||
{"type":"Loop","cond":{"type":"Bool","value":true},"body":[
|
||||
{"type":"Expr","expr":{"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}}},
|
||||
{"type":"If","cond":{"type":"Compare","op":"==","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":3}},
|
||||
"then":[{"type":"Break"}]},
|
||||
{"type":"Continue"}
|
||||
]},
|
||||
{"type":"Return","expr":{"type":"Var","name":"i"}}
|
||||
]}
|
||||
4
tests/json_v0_stage3/throw_basic.json
Normal file
4
tests/json_v0_stage3/throw_basic.json
Normal file
@ -0,0 +1,4 @@
|
||||
{"version":0,"kind":"Program","body":[
|
||||
{"type":"Expr","expr":{"type":"Throw","expr":{"type":"Int","value":42}}},
|
||||
{"type":"Return","expr":{"type":"Int","value":0}}
|
||||
]}
|
||||
9
tests/json_v0_stage3/try_catch_finally.json
Normal file
9
tests/json_v0_stage3/try_catch_finally.json
Normal file
@ -0,0 +1,9 @@
|
||||
{"version":0,"kind":"Program","body":[
|
||||
{"type":"Local","name":"x","expr":{"type":"Int","value":0}},
|
||||
{"type":"Try",
|
||||
"try":[{"type":"Expr","expr":{"type":"Throw","expr":{"type":"Int","value":42}}}],
|
||||
"catches":[{"param":"e","body":[{"type":"Local","name":"x","expr":{"type":"Var","name":"e"}}]}],
|
||||
"finally":[{"type":"Expr","expr":{"type":"Int","value":1}}]
|
||||
},
|
||||
{"type":"Return","expr":{"type":"Var","name":"x"}}
|
||||
]}
|
||||
@ -1,25 +1,41 @@
|
||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
use nyash_rust::{parser::NyashParser, mir::{MirCompiler, passes::method_id_inject::inject_method_ids, instruction::MirInstruction}};
|
||||
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
use nyash_rust::{
|
||||
mir::{instruction::MirInstruction, passes::method_id_inject::inject_method_ids, MirCompiler},
|
||||
parser::NyashParser,
|
||||
};
|
||||
|
||||
fn try_init_plugins() -> bool {
|
||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
||||
if !std::path::Path::new("nyash.toml").exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") {
|
||||
eprintln!("init failed: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
let loader = get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
if let Some(conf) = &loader.config {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
|
||||
for (lib, def) in &conf.libraries {
|
||||
for b in &def.boxes {
|
||||
map.insert(b.clone(), lib.clone());
|
||||
}
|
||||
}
|
||||
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||
true
|
||||
} else { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn injects_method_id_for_filebox_open() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
let code = r#"
|
||||
local f
|
||||
f = new FileBox()
|
||||
@ -34,8 +50,14 @@ f.open("/tmp/test.txt", "r")
|
||||
for func in module2.functions.values() {
|
||||
for block in func.blocks.values() {
|
||||
for inst in &block.instructions {
|
||||
if let MirInstruction::BoxCall { method, method_id, .. } = inst {
|
||||
if method == "open" { assert!(method_id.is_some(), "open missing method_id"); return; }
|
||||
if let MirInstruction::BoxCall {
|
||||
method, method_id, ..
|
||||
} = inst
|
||||
{
|
||||
if method == "open" {
|
||||
assert!(method_id.is_some(), "open missing method_id");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,13 @@ use nyash_rust::mir::instruction_introspection;
|
||||
#[test]
|
||||
fn mir14_shape_is_fixed() {
|
||||
let impl_names = instruction_introspection::mir14_instruction_names();
|
||||
assert_eq!(impl_names.len(), 14, "MIR14 must contain exactly 14 instructions");
|
||||
assert!(impl_names.contains(&"UnaryOp"), "MIR14 must include UnaryOp");
|
||||
assert_eq!(
|
||||
impl_names.len(),
|
||||
14,
|
||||
"MIR14 must contain exactly 14 instructions"
|
||||
);
|
||||
assert!(
|
||||
impl_names.contains(&"UnaryOp"),
|
||||
"MIR14 must include UnaryOp"
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
* Tests AST → MIR lowering for Phase 6 reference operations
|
||||
*/
|
||||
|
||||
use nyash_rust::mir::{MirBuilder, MirPrinter};
|
||||
use nyash_rust::ast::{ASTNode, LiteralValue, Span};
|
||||
use nyash_rust::mir::{MirBuilder, MirPrinter};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
@ -140,27 +140,52 @@ fn test_mir_phase6_lowering_ref_ops() {
|
||||
println!("Generated MIR:\n{}", mir_text);
|
||||
|
||||
// Verify that the MIR contains the expected Phase 6 reference operations
|
||||
assert!(mir_text.contains("ref_new"), "MIR should contain ref_new instruction");
|
||||
assert!(mir_text.contains("ref_set"), "MIR should contain ref_set instruction");
|
||||
assert!(mir_text.contains("ref_get"), "MIR should contain ref_get instruction");
|
||||
assert!(
|
||||
mir_text.contains("ref_new"),
|
||||
"MIR should contain ref_new instruction"
|
||||
);
|
||||
assert!(
|
||||
mir_text.contains("ref_set"),
|
||||
"MIR should contain ref_set instruction"
|
||||
);
|
||||
assert!(
|
||||
mir_text.contains("ref_get"),
|
||||
"MIR should contain ref_get instruction"
|
||||
);
|
||||
|
||||
// Verify specific patterns
|
||||
assert!(mir_text.contains("ref_new") && mir_text.contains("\"Obj\""),
|
||||
"MIR should contain ref_new with Obj class");
|
||||
assert!(mir_text.contains("ref_set") && mir_text.contains(".x"),
|
||||
"MIR should contain ref_set with field x");
|
||||
assert!(mir_text.contains("ref_get") && mir_text.contains(".x"),
|
||||
"MIR should contain ref_get with field x");
|
||||
assert!(
|
||||
mir_text.contains("ref_new") && mir_text.contains("\"Obj\""),
|
||||
"MIR should contain ref_new with Obj class"
|
||||
);
|
||||
assert!(
|
||||
mir_text.contains("ref_set") && mir_text.contains(".x"),
|
||||
"MIR should contain ref_set with field x"
|
||||
);
|
||||
assert!(
|
||||
mir_text.contains("ref_get") && mir_text.contains(".x"),
|
||||
"MIR should contain ref_get with field x"
|
||||
);
|
||||
|
||||
// Verify module structure
|
||||
assert_eq!(module.function_names().len(), 1, "Module should have one function");
|
||||
assert!(module.get_function("main").is_some(), "Module should have main function");
|
||||
assert_eq!(
|
||||
module.function_names().len(),
|
||||
1,
|
||||
"Module should have one function"
|
||||
);
|
||||
assert!(
|
||||
module.get_function("main").is_some(),
|
||||
"Module should have main function"
|
||||
);
|
||||
|
||||
// Verify function has instructions
|
||||
let main_function = module.get_function("main").unwrap();
|
||||
let stats = main_function.stats();
|
||||
assert!(stats.instruction_count > 5,
|
||||
"Function should have multiple instructions (got {})", stats.instruction_count);
|
||||
assert!(
|
||||
stats.instruction_count > 5,
|
||||
"Function should have multiple instructions (got {})",
|
||||
stats.instruction_count
|
||||
);
|
||||
|
||||
println!("✅ Phase 6.1 MIR lowering test passed! Found all required ref operations.");
|
||||
}
|
||||
@ -170,8 +195,7 @@ fn test_mir_phase6_lowering_ref_ops() {
|
||||
fn test_mir_verification_phase6_ref_ops() {
|
||||
// Build simple AST with new and field access
|
||||
let ast = ASTNode::Program {
|
||||
statements: vec![
|
||||
ASTNode::Assignment {
|
||||
statements: vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "obj".to_string(),
|
||||
span: Span::unknown(),
|
||||
@ -183,8 +207,7 @@ fn test_mir_verification_phase6_ref_ops() {
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
}],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
@ -203,14 +226,16 @@ fn test_mir_verification_phase6_ref_ops() {
|
||||
match verification_result {
|
||||
Ok(()) => {
|
||||
println!("✅ MIR verification passed for Phase 6 reference operations");
|
||||
},
|
||||
}
|
||||
Err(errors) => {
|
||||
println!("❌ MIR verification failed with {} errors:", errors.len());
|
||||
for error in &errors {
|
||||
println!(" - {:?}", error);
|
||||
}
|
||||
// Don't fail the test for verification errors, as the verifier may be incomplete
|
||||
println!("⚠️ Continuing test despite verification issues (verifier may be incomplete)");
|
||||
println!(
|
||||
"⚠️ Continuing test despite verification issues (verifier may be incomplete)"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,12 +4,12 @@
|
||||
* Tests VM execution of hand-built MIR with RefNew/RefGet/RefSet instructions
|
||||
*/
|
||||
|
||||
use nyash_rust::mir::{
|
||||
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
|
||||
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue
|
||||
};
|
||||
use nyash_rust::backend::{VM, VMValue};
|
||||
use nyash_rust::backend::{VMValue, VM};
|
||||
use nyash_rust::box_trait::{IntegerBox, NyashBox};
|
||||
use nyash_rust::mir::{
|
||||
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
|
||||
MirInstruction, MirModule, MirType, ValueId,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_mir_phase6_vm_ref_ops() {
|
||||
@ -67,9 +67,7 @@ fn test_mir_phase6_vm_ref_ops() {
|
||||
});
|
||||
|
||||
// barrier_write %1
|
||||
block.add_instruction(MirInstruction::BarrierWrite {
|
||||
ptr: obj_ref,
|
||||
});
|
||||
block.add_instruction(MirInstruction::BarrierWrite { ptr: obj_ref });
|
||||
|
||||
// ref_set %1, "x", %2
|
||||
block.add_instruction(MirInstruction::RefSet {
|
||||
@ -92,9 +90,7 @@ fn test_mir_phase6_vm_ref_ops() {
|
||||
});
|
||||
|
||||
// ret %3
|
||||
block.add_instruction(MirInstruction::Return {
|
||||
value: Some(x_val),
|
||||
});
|
||||
block.add_instruction(MirInstruction::Return { value: Some(x_val) });
|
||||
|
||||
// Add block to function
|
||||
main_function.add_block(block);
|
||||
@ -112,11 +108,18 @@ fn test_mir_phase6_vm_ref_ops() {
|
||||
|
||||
// Check if result is IntegerBox with value 1
|
||||
if let Some(int_box) = result_box.as_any().downcast_ref::<IntegerBox>() {
|
||||
assert_eq!(int_box.value, 1, "Return value should be 1, got {}", int_box.value);
|
||||
assert_eq!(
|
||||
int_box.value, 1,
|
||||
"Return value should be 1, got {}",
|
||||
int_box.value
|
||||
);
|
||||
println!("✅ Return value correct: {}", int_box.value);
|
||||
} else {
|
||||
// Print what we actually got
|
||||
println!("⚠️ Expected IntegerBox, got: {}", result_box.to_string_box().value);
|
||||
println!(
|
||||
"⚠️ Expected IntegerBox, got: {}",
|
||||
result_box.to_string_box().value
|
||||
);
|
||||
println!(" Type: {}", result_box.type_name());
|
||||
|
||||
// For Phase 6.1, the core functionality works (field ops execute correctly)
|
||||
@ -124,7 +127,7 @@ fn test_mir_phase6_vm_ref_ops() {
|
||||
println!("✅ Phase 6.1 core requirement met: RefNew/RefGet/RefSet execute without errors");
|
||||
println!("✅ Field operations working correctly (note: return value propagation has minor issue)");
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("❌ VM execution failed: {}", e);
|
||||
}
|
||||
@ -169,13 +172,9 @@ fn test_barrier_no_op() {
|
||||
});
|
||||
|
||||
// Test barrier instructions (should be no-ops)
|
||||
block.add_instruction(MirInstruction::BarrierRead {
|
||||
ptr: test_val,
|
||||
});
|
||||
block.add_instruction(MirInstruction::BarrierRead { ptr: test_val });
|
||||
|
||||
block.add_instruction(MirInstruction::BarrierWrite {
|
||||
ptr: test_val,
|
||||
});
|
||||
block.add_instruction(MirInstruction::BarrierWrite { ptr: test_val });
|
||||
|
||||
block.add_instruction(MirInstruction::Return {
|
||||
value: Some(test_val),
|
||||
@ -188,6 +187,9 @@ fn test_barrier_no_op() {
|
||||
let mut vm = VM::new();
|
||||
let result = vm.execute_module(&module);
|
||||
|
||||
assert!(result.is_ok(), "Barrier instructions should not cause VM errors");
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Barrier instructions should not cause VM errors"
|
||||
);
|
||||
println!("✅ Barrier no-op test passed - barriers execute without errors");
|
||||
}
|
||||
@ -4,9 +4,9 @@
|
||||
* Tests AST → MIR lowering and VM execution for Phase 7 async operations
|
||||
*/
|
||||
|
||||
use nyash_rust::mir::{MirBuilder, MirPrinter};
|
||||
use nyash_rust::backend::VM;
|
||||
use nyash_rust::ast::{ASTNode, LiteralValue, Span};
|
||||
use nyash_rust::backend::VM;
|
||||
use nyash_rust::mir::{MirBuilder, MirPrinter};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
@ -106,20 +106,22 @@ fn test_mir_phase7_basic_nowait_await() {
|
||||
|
||||
// Verify MIR contains expected instructions
|
||||
let function = module.get_function("main").unwrap();
|
||||
let instructions: Vec<_> = function.blocks.values()
|
||||
let instructions: Vec<_> = function
|
||||
.blocks
|
||||
.values()
|
||||
.flat_map(|block| &block.instructions)
|
||||
.collect();
|
||||
|
||||
// Should contain FutureNew instruction
|
||||
let has_future_new = instructions.iter().any(|inst| {
|
||||
matches!(inst, nyash_rust::mir::MirInstruction::FutureNew { .. })
|
||||
});
|
||||
let has_future_new = instructions
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, nyash_rust::mir::MirInstruction::FutureNew { .. }));
|
||||
assert!(has_future_new, "MIR should contain FutureNew instruction");
|
||||
|
||||
// Should contain Await instruction
|
||||
let has_await = instructions.iter().any(|inst| {
|
||||
matches!(inst, nyash_rust::mir::MirInstruction::Await { .. })
|
||||
});
|
||||
let has_await = instructions
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, nyash_rust::mir::MirInstruction::Await { .. }));
|
||||
assert!(has_await, "MIR should contain Await instruction");
|
||||
|
||||
// Test VM execution
|
||||
|
||||
@ -1,28 +1,41 @@
|
||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
|
||||
use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
|
||||
fn try_init_plugins() -> bool {
|
||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
||||
if !std::path::Path::new("nyash.toml").exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = init_global_loader_v2("nyash.toml") {
|
||||
eprintln!("init failed: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
let loader = get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
if let Some(conf) = &loader.config {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
|
||||
for (lib, def) in &conf.libraries {
|
||||
for b in &def.boxes {
|
||||
map.insert(b.clone(), lib.clone());
|
||||
}
|
||||
}
|
||||
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||
true
|
||||
} else { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal ABI sanity check: HttpRequestBox.path=1, readBody=2
|
||||
#[test]
|
||||
#[ignore = "MIR13/plugin Net: HttpRequestBox path/readBody default values pending"]
|
||||
fn plugin_contract_http_request_ids_sanity() {
|
||||
if !try_init_plugins() { return; }
|
||||
if !try_init_plugins() {
|
||||
return;
|
||||
}
|
||||
// Exercise HttpRequestBox.path/readBody on a birthed request (no server needed)
|
||||
let code = r#"
|
||||
local req, p, b
|
||||
|
||||
124
tests/vm_e2e.rs
124
tests/vm_e2e.rs
@ -2,53 +2,109 @@
|
||||
//! VM E2E: Compile Nyash to MIR and execute via VM, with mock plugin factory
|
||||
use std::sync::Arc;
|
||||
|
||||
use nyash_rust::box_factory::BoxFactory;
|
||||
use nyash_rust::box_factory::builtin::BuiltinGroups;
|
||||
use nyash_rust::runtime::NyashRuntimeBuilder;
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::mir::MirCompiler;
|
||||
use nyash_rust::backend::VM;
|
||||
use nyash_rust::box_factory::builtin::BuiltinGroups;
|
||||
use nyash_rust::box_factory::BoxFactory;
|
||||
use nyash_rust::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
|
||||
use nyash_rust::interpreter::RuntimeError;
|
||||
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
|
||||
use nyash_rust::mir::MirCompiler;
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::runtime::NyashRuntimeBuilder;
|
||||
|
||||
// Minimal AdderBox to validate plugin factory path under VM
|
||||
#[derive(Debug, Clone)]
|
||||
struct AdderBox { base: BoxBase, sum: i64 }
|
||||
impl AdderBox { fn new(a: i64, b: i64) -> Self { Self { base: BoxBase::new(), sum: a + b } } }
|
||||
struct AdderBox {
|
||||
base: BoxBase,
|
||||
sum: i64,
|
||||
}
|
||||
impl AdderBox {
|
||||
fn new(a: i64, b: i64) -> Self {
|
||||
Self {
|
||||
base: BoxBase::new(),
|
||||
sum: a + b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for AdderBox {
|
||||
fn box_id(&self) -> u64 { self.base.id }
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "AdderBox(sum={})", self.sum) }
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
||||
fn box_id(&self) -> u64 {
|
||||
self.base.id
|
||||
}
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||
None
|
||||
}
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "AdderBox(sum={})", self.sum)
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for AdderBox {
|
||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.sum.to_string()) }
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() { BoolBox::new(self.sum == a.sum) } else { BoolBox::new(false) }
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(self.sum.to_string())
|
||||
}
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() {
|
||||
BoolBox::new(self.sum == a.sum)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
"AdderBox"
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn type_name(&self) -> &'static str { "AdderBox" }
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
}
|
||||
|
||||
struct TestPluginFactory;
|
||||
impl TestPluginFactory { fn new() -> Self { Self } }
|
||||
impl TestPluginFactory {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
impl BoxFactory for TestPluginFactory {
|
||||
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
fn create_box(
|
||||
&self,
|
||||
name: &str,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match name {
|
||||
"AdderBox" => {
|
||||
if args.len() != 2 { return Err(RuntimeError::InvalidOperation{ message: format!("AdderBox expects 2 args, got {}", args.len()) }); }
|
||||
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?;
|
||||
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg b must be int".into() })?;
|
||||
if args.len() != 2 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("AdderBox expects 2 args, got {}", args.len()),
|
||||
});
|
||||
}
|
||||
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| {
|
||||
RuntimeError::TypeError {
|
||||
message: "AdderBox arg a must be int".into(),
|
||||
}
|
||||
})?;
|
||||
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| {
|
||||
RuntimeError::TypeError {
|
||||
message: "AdderBox arg b must be int".into(),
|
||||
}
|
||||
})?;
|
||||
Ok(Box::new(AdderBox::new(a, b)))
|
||||
}
|
||||
_ => Err(RuntimeError::InvalidOperation{ message: format!("Unknown Box type: {}", name) })
|
||||
_ => Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Unknown Box type: {}", name),
|
||||
}),
|
||||
}
|
||||
}
|
||||
fn box_types(&self) -> Vec<&str> { vec!["AdderBox"] }
|
||||
fn box_types(&self) -> Vec<&str> {
|
||||
vec!["AdderBox"]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -56,9 +112,11 @@ fn vm_e2e_adder_box() {
|
||||
// Build runtime with builtin + user-defined + mock plugin factory
|
||||
let runtime = NyashRuntimeBuilder::new()
|
||||
.with_builtin_groups(BuiltinGroups::native_full())
|
||||
.with_factory(Arc::new(nyash_rust::box_factory::user_defined::UserDefinedBoxFactory::new(
|
||||
.with_factory(Arc::new(
|
||||
nyash_rust::box_factory::user_defined::UserDefinedBoxFactory::new(
|
||||
nyash_rust::interpreter::SharedState::new(),
|
||||
)))
|
||||
),
|
||||
))
|
||||
.with_factory(Arc::new(TestPluginFactory::new()))
|
||||
.build();
|
||||
|
||||
@ -75,12 +133,18 @@ fn vm_e2e_adder_box() {
|
||||
|
||||
// Execute via VM using the prepared runtime
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
let result = vm.execute_module(&compile_result.module).expect("vm exec ok");
|
||||
let result = vm
|
||||
.execute_module(&compile_result.module)
|
||||
.expect("vm exec ok");
|
||||
|
||||
// The VM returns an Option<Box<dyn NyashBox>> or a value; we print/debug and check string form
|
||||
// Here we rely on Display via to_string_box through Debug format
|
||||
// Try to format the result if available
|
||||
// For this implementation, result is a generic value; we check debug string contains 42 or to_string equivalent.
|
||||
let s = format!("{:?}", result);
|
||||
assert!(s.contains("42") || s.contains("AdderBox"), "unexpected VM result: {}", s);
|
||||
assert!(
|
||||
s.contains("42") || s.contains("AdderBox"),
|
||||
"unexpected VM result: {}",
|
||||
s
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,11 +9,11 @@
|
||||
* - Return values
|
||||
*/
|
||||
|
||||
use nyash_rust::mir::{
|
||||
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
|
||||
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue, BinaryOp
|
||||
};
|
||||
use nyash_rust::backend::wasm::WasmBackend;
|
||||
use nyash_rust::mir::{
|
||||
BasicBlock, BasicBlockId, BinaryOp, ConstValue, EffectMask, FunctionSignature, MirFunction,
|
||||
MirInstruction, MirModule, MirType, ValueId,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_wasm_poc1_basic_arithmetic() {
|
||||
@ -36,15 +36,36 @@ fn test_wasm_poc1_basic_arithmetic() {
|
||||
let wat_text = wat_result.unwrap();
|
||||
|
||||
// Verify WAT contains expected elements
|
||||
assert!(wat_text.contains("(module"), "Should contain module declaration");
|
||||
assert!(wat_text.contains("memory"), "Should contain memory declaration");
|
||||
assert!(
|
||||
wat_text.contains("(module"),
|
||||
"Should contain module declaration"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("memory"),
|
||||
"Should contain memory declaration"
|
||||
);
|
||||
assert!(wat_text.contains("import"), "Should contain imports");
|
||||
assert!(wat_text.contains("$main"), "Should contain main function");
|
||||
assert!(wat_text.contains("i32.const 42"), "Should contain constant 42");
|
||||
assert!(wat_text.contains("i32.const 8"), "Should contain constant 8");
|
||||
assert!(wat_text.contains("i32.add"), "Should contain addition operation");
|
||||
assert!(wat_text.contains("call $print"), "Should contain print call");
|
||||
assert!(wat_text.contains("return"), "Should contain return instruction");
|
||||
assert!(
|
||||
wat_text.contains("i32.const 42"),
|
||||
"Should contain constant 42"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("i32.const 8"),
|
||||
"Should contain constant 8"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("i32.add"),
|
||||
"Should contain addition operation"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("call $print"),
|
||||
"Should contain print call"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("return"),
|
||||
"Should contain return instruction"
|
||||
);
|
||||
|
||||
// Compile to WASM binary and execute
|
||||
let wasm_result = backend.compile_module(mir_module);
|
||||
@ -143,7 +164,11 @@ fn build_arithmetic_mir_module() -> MirModule {
|
||||
});
|
||||
|
||||
// Debug: Print number of instructions and terminator
|
||||
println!("Instructions: {}, Has terminator: {}", block.instructions.len(), block.terminator.is_some());
|
||||
println!(
|
||||
"Instructions: {}, Has terminator: {}",
|
||||
block.instructions.len(),
|
||||
block.terminator.is_some()
|
||||
);
|
||||
|
||||
// Add block to function
|
||||
main_function.add_block(block);
|
||||
|
||||
@ -11,11 +11,11 @@
|
||||
* Validates the "Everything is Box" philosophy in WASM
|
||||
*/
|
||||
|
||||
use nyash_rust::mir::{
|
||||
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
|
||||
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue
|
||||
};
|
||||
use nyash_rust::backend::wasm::WasmBackend;
|
||||
use nyash_rust::mir::{
|
||||
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
|
||||
MirInstruction, MirModule, MirType, ValueId,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_wasm_poc2_refnew_basic() {
|
||||
@ -36,10 +36,22 @@ fn test_wasm_poc2_refnew_basic() {
|
||||
let wat_text = wat_result.unwrap();
|
||||
|
||||
// Verify WAT contains expected elements
|
||||
assert!(wat_text.contains("(module"), "Should contain module declaration");
|
||||
assert!(wat_text.contains("$malloc"), "Should contain malloc function");
|
||||
assert!(wat_text.contains("$alloc_databox"), "Should contain DataBox allocator");
|
||||
assert!(wat_text.contains("call $alloc_databox"), "Should call DataBox allocator");
|
||||
assert!(
|
||||
wat_text.contains("(module"),
|
||||
"Should contain module declaration"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("$malloc"),
|
||||
"Should contain malloc function"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("$alloc_databox"),
|
||||
"Should contain DataBox allocator"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("call $alloc_databox"),
|
||||
"Should call DataBox allocator"
|
||||
);
|
||||
assert!(wat_text.contains("i32.store"), "Should store field values");
|
||||
|
||||
// Compile to WASM binary and execute
|
||||
@ -58,7 +70,11 @@ fn test_wasm_poc2_refnew_basic() {
|
||||
|
||||
let return_value = execution_result.unwrap();
|
||||
// Should return a valid pointer (greater than heap start 0x800)
|
||||
assert!(return_value >= 0x800, "Should return valid Box pointer: {}", return_value);
|
||||
assert!(
|
||||
return_value >= 0x800,
|
||||
"Should return valid Box pointer: {}",
|
||||
return_value
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -9,11 +9,11 @@
|
||||
* - WAT generation includes data segments and string allocation
|
||||
*/
|
||||
|
||||
use nyash_rust::mir::{
|
||||
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
|
||||
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue
|
||||
};
|
||||
use nyash_rust::backend::wasm::WasmBackend;
|
||||
use nyash_rust::mir::{
|
||||
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
|
||||
MirInstruction, MirModule, MirType, ValueId,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_wasm_string_constant_basic() {
|
||||
@ -28,17 +28,38 @@ fn test_wasm_string_constant_basic() {
|
||||
|
||||
// Generate WAT text for debugging
|
||||
let wat_result = backend.compile_to_wat(mir_module.clone());
|
||||
assert!(wat_result.is_ok(), "WAT generation should succeed for string constants");
|
||||
assert!(
|
||||
wat_result.is_ok(),
|
||||
"WAT generation should succeed for string constants"
|
||||
);
|
||||
|
||||
let wat_text = wat_result.unwrap();
|
||||
|
||||
// Verify WAT contains expected elements for string support
|
||||
assert!(wat_text.contains("(module"), "Should contain module declaration");
|
||||
assert!(wat_text.contains("memory"), "Should contain memory declaration");
|
||||
assert!(wat_text.contains("data"), "Should contain data segment for string literal");
|
||||
assert!(wat_text.contains("\\48\\65\\6c\\6c\\6f"), "Should contain UTF-8 bytes for 'Hello'");
|
||||
assert!(wat_text.contains("$alloc_stringbox"), "Should contain StringBox allocator");
|
||||
assert!(wat_text.contains("print_str"), "Should contain print_str import");
|
||||
assert!(
|
||||
wat_text.contains("(module"),
|
||||
"Should contain module declaration"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("memory"),
|
||||
"Should contain memory declaration"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("data"),
|
||||
"Should contain data segment for string literal"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("\\48\\65\\6c\\6c\\6f"),
|
||||
"Should contain UTF-8 bytes for 'Hello'"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("$alloc_stringbox"),
|
||||
"Should contain StringBox allocator"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("print_str"),
|
||||
"Should contain print_str import"
|
||||
);
|
||||
|
||||
// Verify string literal is properly embedded
|
||||
// (The assertion for UTF-8 bytes is above)
|
||||
@ -49,7 +70,10 @@ fn test_wasm_string_constant_basic() {
|
||||
println!("WASM compilation error: {}", e);
|
||||
println!("Generated WAT:\n{}", wat_text);
|
||||
}
|
||||
assert!(wasm_result.is_ok(), "WASM compilation should succeed for string constants");
|
||||
assert!(
|
||||
wasm_result.is_ok(),
|
||||
"WASM compilation should succeed for string constants"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -66,20 +90,35 @@ fn test_wasm_string_constant_multiple() {
|
||||
let mir_module = build_multiple_string_const_mir_module();
|
||||
|
||||
let wat_result = backend.compile_to_wat(mir_module.clone());
|
||||
assert!(wat_result.is_ok(), "WAT generation should succeed for multiple strings");
|
||||
assert!(
|
||||
wat_result.is_ok(),
|
||||
"WAT generation should succeed for multiple strings"
|
||||
);
|
||||
|
||||
let wat_text = wat_result.unwrap();
|
||||
|
||||
// Should contain both unique strings (in hex format)
|
||||
assert!(wat_text.contains("\\46\\69\\72\\73\\74"), "Should contain 'First' string in hex");
|
||||
assert!(wat_text.contains("\\53\\65\\63\\6f\\6e\\64"), "Should contain 'Second' string in hex");
|
||||
assert!(
|
||||
wat_text.contains("\\46\\69\\72\\73\\74"),
|
||||
"Should contain 'First' string in hex"
|
||||
);
|
||||
assert!(
|
||||
wat_text.contains("\\53\\65\\63\\6f\\6e\\64"),
|
||||
"Should contain 'Second' string in hex"
|
||||
);
|
||||
|
||||
// Should have 2 data segments (First and Second, duplicate First reused)
|
||||
let data_count = wat_text.matches("(data").count();
|
||||
assert_eq!(data_count, 2, "Should have exactly 2 data segments for 2 unique strings");
|
||||
assert_eq!(
|
||||
data_count, 2,
|
||||
"Should have exactly 2 data segments for 2 unique strings"
|
||||
);
|
||||
|
||||
let wasm_result = backend.compile_module(mir_module);
|
||||
assert!(wasm_result.is_ok(), "WASM compilation should succeed for multiple strings");
|
||||
assert!(
|
||||
wasm_result.is_ok(),
|
||||
"WASM compilation should succeed for multiple strings"
|
||||
);
|
||||
}
|
||||
|
||||
/// Build a MIR module with a single string constant
|
||||
|
||||
@ -1,88 +0,0 @@
|
||||
<#
|
||||
AOT smoke (Cranelift) — DRYRUN skeleton (Windows-first)
|
||||
Usage:
|
||||
pwsh -File tools/aot_smoke_cranelift.ps1 [-Mode release|debug]
|
||||
Env:
|
||||
CLIF_SMOKE_RUN=1 # actually execute steps (default: dry-run only)
|
||||
NYASH_LINK_VERBOSE=1 # echo link commands (when run)
|
||||
NYASH_DISABLE_PLUGINS=1 # plugin-dependent smokes off
|
||||
Notes:
|
||||
- This script mirrors docs/tests/aot_smoke_cranelift.md pseudo flow.
|
||||
- PoC: emits commands; real execution requires Cranelift AOT path to be implemented.
|
||||
#>
|
||||
|
||||
param(
|
||||
[ValidateSet('release','debug')]
|
||||
[string]$Mode = 'release'
|
||||
)
|
||||
|
||||
$Run = [int]([Environment]::GetEnvironmentVariable('CLIF_SMOKE_RUN') ?? '0')
|
||||
|
||||
function Banner($msg) { Write-Host "`n[clif-aot-smoke] $msg" }
|
||||
function Info($msg) { Write-Host "[clif-aot-smoke] $msg" }
|
||||
|
||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
$Target = Join-Path $Root 'target'
|
||||
$ObjDir = Join-Path $Target 'aot_objects'
|
||||
New-Item -ItemType Directory -Force -Path $ObjDir | Out-Null
|
||||
$ObjOut = Join-Path $ObjDir 'core_smoke.obj'
|
||||
$NyashBin = Join-Path (Join-Path $Target $Mode) 'nyash.exe'
|
||||
$ExeOut = Join-Path $Target 'app_clif.exe'
|
||||
|
||||
Banner "Cranelift AOT Smoke (mode=$Mode, dry-run=$([bool](-not ($Run -eq 1))))"
|
||||
|
||||
# 1) Build nyash with cranelift
|
||||
Banner "building nyash (features=cranelift-jit)"
|
||||
if ($Run -eq 1) {
|
||||
& cargo build --% --$Mode --features cranelift-jit
|
||||
} else {
|
||||
Info "DRYRUN: cargo build --$Mode --features cranelift-jit"
|
||||
}
|
||||
|
||||
# 2) Emit object via backend=cranelift (PoC path)
|
||||
Banner "emitting object via --backend cranelift (PoC)"
|
||||
if ($Run -eq 1) {
|
||||
if (-not (Test-Path $NyashBin)) { throw "nyash not found: $NyashBin" }
|
||||
$env:NYASH_AOT_OBJECT_OUT = $ObjOut
|
||||
& $NyashBin --backend cranelift apps/hello/main.nyash | Out-Null
|
||||
if (-not (Test-Path $ObjOut)) { throw "object not generated: $ObjOut" }
|
||||
$size = (Get-Item $ObjOut).Length
|
||||
Info "OK: object generated: $ObjOut ($size bytes)"
|
||||
} else {
|
||||
Info "DRYRUN: NYASH_AOT_OBJECT_OUT=$ObjOut $NyashBin --backend cranelift apps/hello/main.nyash"
|
||||
New-Item -ItemType File -Force -Path $ObjOut | Out-Null
|
||||
}
|
||||
|
||||
# 3) Link (Windows-first)
|
||||
Banner "linking app (Windows-first)"
|
||||
if ($Run -eq 1) {
|
||||
$link = Get-Command link -ErrorAction SilentlyContinue
|
||||
$lld = Get-Command lld-link -ErrorAction SilentlyContinue
|
||||
if ($link) {
|
||||
Info "using MSVC link.exe"
|
||||
& link /OUT:$ExeOut $ObjOut nyrt.lib
|
||||
} elseif ($lld) {
|
||||
Info "using lld-link"
|
||||
& lld-link -OUT:$ExeOut $ObjOut nyrt.lib
|
||||
} else {
|
||||
throw "no Windows linker found (link.exe/lld-link)"
|
||||
}
|
||||
} else {
|
||||
Info "DRYRUN: link /OUT:$ExeOut $ObjOut nyrt.lib (or lld-link)"
|
||||
}
|
||||
|
||||
# 4) Run and verify
|
||||
Banner "run and verify output"
|
||||
if ($Run -eq 1) {
|
||||
if (-not (Test-Path $ExeOut)) { throw "no output binary: $ExeOut" }
|
||||
$out = & $ExeOut 2>&1
|
||||
$out | Write-Host
|
||||
if ($out -notmatch 'Result:') { throw "unexpected output" }
|
||||
Info "OK: smoke passed"
|
||||
} else {
|
||||
Info "DRYRUN: $ExeOut → expect a line including: 'Result:'"
|
||||
Info "DRYRUN complete"
|
||||
}
|
||||
|
||||
exit 0
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Cranelift JIT-AOT smoke: emit object via --jit-direct and link with nyrt
|
||||
# Usage: tools/aot_smoke_cranelift.sh [app_path] [out_basename]
|
||||
|
||||
APP=${1:-apps/smokes/jit_aot_string_min.nyash}
|
||||
BASE=${2:-app}
|
||||
|
||||
BIN=./target/release/nyash
|
||||
OBJ_DIR=target/aot_objects
|
||||
OBJ=$OBJ_DIR/${BASE}.o
|
||||
EXE=${BASE}
|
||||
|
||||
mkdir -p "$OBJ_DIR"
|
||||
|
||||
echo "[AOT] building core (if needed)"
|
||||
cargo build --release --features cranelift-jit >/dev/null 2>&1 || true
|
||||
|
||||
echo "[AOT] lowering: $APP -> $OBJ"
|
||||
NYASH_DISABLE_PLUGINS=1 NYASH_AOT_OBJECT_OUT="$OBJ" "$BIN" --jit-direct "$APP"
|
||||
|
||||
# Ensure NyRT static library exists (libnyrt.a)
|
||||
if [ ! -f crates/nyrt/target/release/libnyrt.a ] && [ ! -f target/release/libnyrt.a ] && [ ! -f target/release/nyrt.lib ]; then
|
||||
echo "[AOT] building NyRT (static runtime)"
|
||||
(cd crates/nyrt && cargo build --release >/dev/null)
|
||||
fi
|
||||
|
||||
echo "[AOT] linking: $EXE"
|
||||
# Prefer the workspace root target, then crates/nyrt/target
|
||||
cc "$OBJ" \
|
||||
-L target/release \
|
||||
-L crates/nyrt/target/release \
|
||||
-Wl,--whole-archive -lnyrt -Wl,--no-whole-archive \
|
||||
-lpthread -ldl -lm \
|
||||
-o "$EXE"
|
||||
|
||||
echo "[AOT] run: ./$EXE"
|
||||
"./$EXE" || true
|
||||
47
tools/clean_root_artifacts.sh
Normal file
47
tools/clean_root_artifacts.sh
Normal file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Non-destructive cleaner for stray build artifacts in repo root.
|
||||
# Dry-run by default; pass --apply to actually remove.
|
||||
|
||||
ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd)
|
||||
APPLY=0
|
||||
[[ "${1:-}" == "--apply" ]] && APPLY=1
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
patterns=(
|
||||
"app_*" # e.g., app_parity_*, app_stage3_loop, etc.
|
||||
"*_app" # e.g., test_filebox_app
|
||||
"*.o" # stray .o files
|
||||
)
|
||||
|
||||
echo "[clean-root] scanning..."
|
||||
found=()
|
||||
for pat in "${patterns[@]}"; do
|
||||
while IFS= read -r -d $'\0' f; do
|
||||
# skip directories and keep inside tools/, src/, apps/, etc.
|
||||
[[ -d "$f" ]] && continue
|
||||
case "$f" in
|
||||
src/*|apps/*|examples/*|tools/*|docs/*|tests/*|crates/*|plugins/*) continue ;;
|
||||
esac
|
||||
found+=("$f")
|
||||
done < <(find . -maxdepth 1 -name "$pat" -print0 2>/dev/null || true)
|
||||
done
|
||||
|
||||
if (( ${#found[@]} == 0 )); then
|
||||
echo "[clean-root] nothing to clean"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf "[clean-root] candidates (%d):\n" "${#found[@]}"
|
||||
printf ' %s\n' "${found[@]}"
|
||||
|
||||
if (( APPLY )); then
|
||||
echo "[clean-root] removing..."
|
||||
rm -f -- "${found[@]}"
|
||||
echo "[clean-root] done"
|
||||
else
|
||||
echo "[clean-root] dry-run (no files removed). Use --apply to remove."
|
||||
fi
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
# Profiles:
|
||||
# pyvm - Favor PyVM for VM and Bridge
|
||||
# bridge - Bridge-only helpers (keep interpreter)
|
||||
# phi_off - PHI-less MIR (edge-copy) + verifier relax; harness on
|
||||
# reset - Unset variables set by this script
|
||||
|
||||
set -euo pipefail
|
||||
@ -28,13 +29,23 @@ reset_env() {
|
||||
unset NYASH_PIPE_USE_PYVM || true
|
||||
unset NYASH_DISABLE_PLUGINS || true
|
||||
unset NYASH_NY_COMPILER_TIMEOUT_MS || true
|
||||
unset NYASH_MIR_NO_PHI || true
|
||||
unset NYASH_VERIFY_ALLOW_NO_PHI || true
|
||||
unset NYASH_LLVM_USE_HARNESS || true
|
||||
echo "[dev-env] environment reset" >&2
|
||||
}
|
||||
|
||||
activate_phi_off() {
|
||||
export NYASH_MIR_NO_PHI=1
|
||||
export NYASH_VERIFY_ALLOW_NO_PHI=1
|
||||
export NYASH_LLVM_USE_HARNESS=1
|
||||
echo "[dev-env] PHI-off (edge-copy) profile activated (harness on)" >&2
|
||||
}
|
||||
|
||||
case "${1:-pyvm}" in
|
||||
pyvm) activate_pyvm ;;
|
||||
bridge) activate_bridge ;;
|
||||
phi_off) activate_phi_off ;;
|
||||
reset) reset_env ;;
|
||||
*) echo "usage: source tools/dev_env.sh [pyvm|bridge|reset]" >&2 ;;
|
||||
*) echo "usage: source tools/dev_env.sh [pyvm|bridge|phi_off|reset]" >&2 ;;
|
||||
esac
|
||||
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
|
||||
BIN="$ROOT_DIR/target/release/nyash"
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "Building nyash (release, JIT)..." >&2
|
||||
cargo build --release --features cranelift-jit >/dev/null
|
||||
fi
|
||||
|
||||
# Optional std Ny smokes (requires: NYASH_LOAD_NY_PLUGINS=1 and plugins enabled)
|
||||
run_std_smokes() {
|
||||
if [[ "${NYASH_LOAD_NY_PLUGINS:-0}" != "1" ]] || [[ "${NYASH_DISABLE_PLUGINS:-0}" == "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[JIT Smoke] Std Ny smokes (plugins via nyash.toml)" >&2
|
||||
|
||||
local smokes=(
|
||||
"apps/smokes/std/string_smoke.nyash"
|
||||
"apps/smokes/std/array_smoke.nyash"
|
||||
)
|
||||
|
||||
local overall_rc=0
|
||||
for f in "${smokes[@]}"; do
|
||||
local name
|
||||
name=$(basename "$f" .nyash)
|
||||
if [[ ! -f "$ROOT_DIR/$f" ]]; then
|
||||
echo "[STD] ${name}: FAIL (missing)" >&2
|
||||
overall_rc=1
|
||||
continue
|
||||
fi
|
||||
|
||||
set +e
|
||||
# Hard timeout to prevent runaway smokes (hang guard)
|
||||
out=$(timeout 15s "$BIN" --backend vm "$ROOT_DIR/$f" 2>&1)
|
||||
rc=$?
|
||||
# Normalize timeout exit code (124) to rc=124
|
||||
if [[ $rc -eq 124 ]]; then
|
||||
echo "[STD] ${name}: TIMEOUT" >&2
|
||||
overall_rc=1
|
||||
continue
|
||||
fi
|
||||
set -e
|
||||
if [[ $rc -eq 0 ]] && echo "$out" | rg -q '^Result:\s*0\b'; then
|
||||
echo "[STD] ${name}: PASS" >&2
|
||||
else
|
||||
# Heuristic skip: ArrayBox plugin not available (treat as SKIP not FAIL)
|
||||
if echo "$out" | rg -q 'Failed to create ArrayBox'; then
|
||||
echo "[STD] ${name}: SKIP (ArrayBox plugin unavailable)" >&2
|
||||
else
|
||||
echo "[STD] ${name}: FAIL" >&2
|
||||
echo "$out" | sed -n '1,120p' >&2 || true
|
||||
overall_rc=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $overall_rc -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[JIT Smoke] Core VM/JIT (plugins disabled)" >&2
|
||||
NYASH_DISABLE_PLUGINS=1 NYASH_CLI_VERBOSE=1 "$ROOT_DIR/tools/smoke_vm_jit.sh" >/tmp/nyash-jit-core.out
|
||||
grep -q '^✅ smoke done' /tmp/nyash-jit-core.out || { echo "FAIL: core VM/JIT smoke" >&2; cat /tmp/nyash-jit-core.out; exit 1; }
|
||||
echo "PASS: core VM/JIT smoke" >&2
|
||||
|
||||
echo "[JIT Smoke] Examples (string_p0, array_p0, map_p0)" >&2
|
||||
set -o pipefail
|
||||
NYASH_DISABLE_PLUGINS=1 "$BIN" --backend vm "$ROOT_DIR/apps/examples/string_p0.nyash" > /tmp/nyash-ex-str.out
|
||||
NYASH_DISABLE_PLUGINS=1 "$BIN" --backend vm "$ROOT_DIR/apps/examples/array_p0.nyash" > /tmp/nyash-ex-arr.out
|
||||
NYASH_DISABLE_PLUGINS=1 "$BIN" --backend vm "$ROOT_DIR/apps/examples/map_p0.nyash" > /tmp/nyash-ex-map.out
|
||||
if rg -q '^Result:\s*0\b' /tmp/nyash-ex-str.out && rg -q '^Result:\s*0\b' /tmp/nyash-ex-arr.out && rg -q '^Result:\s*0\b' /tmp/nyash-ex-map.out; then
|
||||
echo "PASS: examples" >&2
|
||||
else
|
||||
echo "FAIL: examples" >&2; { echo '--- string_p0 ---'; cat /tmp/nyash-ex-str.out; echo '--- array_p0 ---'; cat /tmp/nyash-ex-arr.out; echo '--- map_p0 ---'; cat /tmp/nyash-ex-map.out; } >&2; exit 1
|
||||
fi
|
||||
|
||||
echo "All PASS" >&2
|
||||
|
||||
# Optional: ensure ny_plugins load does not break core path
|
||||
echo "[JIT Smoke] Plugins opt-in load (sanity)" >&2
|
||||
NYASH_LOAD_NY_PLUGINS=1 "$BIN" --backend vm "$ROOT_DIR/apps/examples/string_p0.nyash" > /tmp/nyash-ex-plugins.out || true
|
||||
if rg -q '^Result:\s*0\b' /tmp/nyash-ex-plugins.out; then
|
||||
echo "PASS: plugins load (sanity)" >&2
|
||||
else
|
||||
echo "WARN: plugins load path did not complete cleanly; continuing (optional)" >&2
|
||||
sed -n '1,120p' /tmp/nyash-ex-plugins.out >&2 || true
|
||||
fi
|
||||
|
||||
# Run std Ny smokes only when explicitly enabled via env
|
||||
run_std_smokes
|
||||
@ -47,7 +47,7 @@ cargo build -q ${MODE:+--${MODE}} --features llvm
|
||||
|
||||
echo "[llvm-smoke] running --backend llvm on examples/llvm11_core_smoke.nyash ..." >&2
|
||||
rm -f "$OBJ"
|
||||
NYASH_LLVM_OBJ_OUT="$OBJ" "$BIN" --backend llvm examples/llvm11_core_smoke.nyash >/dev/null || true
|
||||
NYASH_LLVM_USE_HARNESS=1 NYASH_LLVM_OBJ_OUT="$OBJ" "$BIN" --backend llvm examples/llvm11_core_smoke.nyash >/dev/null || true
|
||||
|
||||
if [[ ! -f "$OBJ" ]]; then
|
||||
echo "error: expected object not found: $OBJ" >&2
|
||||
@ -60,6 +60,28 @@ fi
|
||||
|
||||
echo "[llvm-smoke] OK: object generated: $OBJ ($(stat -c%s "$OBJ") bytes)" >&2
|
||||
|
||||
# --- Stage-3 loop control smoke (break/continue) ---
|
||||
if [[ "${NYASH_LLVM_STAGE3_SMOKE:-0}" == "1" ]]; then
|
||||
echo "[llvm-smoke] building + linking apps/tests/llvm_stage3_loop_only.nyash ..." >&2
|
||||
OBJ_STAGE3="$PWD/target/aot_objects/stage3_loop_smoke.o"
|
||||
rm -f "$OBJ_STAGE3"
|
||||
# Loop-only case: harness should succeed (no exceptions in IR)
|
||||
NYASH_LLVM_USE_HARNESS=1 NYASH_LLVM_OBJ_OUT="$OBJ_STAGE3" \
|
||||
"$BIN" --backend llvm apps/tests/llvm_stage3_loop_only.nyash >/dev/null || true
|
||||
NYASH_LLVM_SKIP_EMIT=1 NYASH_LLVM_OBJ_OUT="$OBJ_STAGE3" \
|
||||
./tools/build_llvm.sh apps/tests/llvm_stage3_loop_only.nyash -o app_stage3_loop >/dev/null || true
|
||||
echo "[llvm-smoke] running app_stage3_loop ..." >&2
|
||||
out_stage3=$(./app_stage3_loop || true)
|
||||
echo "[llvm-smoke] output: $out_stage3" >&2
|
||||
if ! echo "$out_stage3" | grep -q "Result: 3"; then
|
||||
echo "error: stage3 loop smoke unexpected output: $out_stage3" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[llvm-smoke] OK: Stage-3 break/continue smoke passed" >&2
|
||||
else
|
||||
echo "[llvm-smoke] skipping Stage-3 loop smoke (set NYASH_LLVM_STAGE3_SMOKE=1 to enable)" >&2
|
||||
fi
|
||||
|
||||
# --- AOT smoke: apps/ny-llvm-smoke (Array get/set/print) ---
|
||||
if [[ "${NYASH_LLVM_ARRAY_SMOKE:-0}" == "1" ]]; then
|
||||
echo "[llvm-smoke] building + linking apps/ny-llvm-smoke ..." >&2
|
||||
|
||||
26
tools/smokes/archive/aot_smoke_cranelift.sh
Normal file
26
tools/smokes/archive/aot_smoke_cranelift.sh
Normal file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Archived: Cranelift AOT smoke helper (not maintained currently)
|
||||
set -euo pipefail
|
||||
|
||||
APP=${1:-apps/smokes/jit_aot_string_min.nyash}
|
||||
BASE=${2:-app}
|
||||
|
||||
BIN=./target/release/nyash
|
||||
OBJ_DIR=target/aot_objects
|
||||
OBJ=$OBJ_DIR/${BASE}.o
|
||||
EXE=${BASE}
|
||||
|
||||
mkdir -p "$OBJ_DIR"
|
||||
|
||||
echo "[AOT] building core (if needed)"
|
||||
cargo build --release --features cranelift-jit >/dev/null 2>&1 || true
|
||||
|
||||
echo "[AOT] lowering: $APP -> $OBJ"
|
||||
NYASH_DISABLE_PLUGINS=1 NYASH_AOT_OBJECT_OUT="$OBJ" "$BIN" --jit-direct "$APP"
|
||||
|
||||
echo "[AOT] linking: $EXE (requires libnyrt.a present in target/release)"
|
||||
cc "$OBJ" -L target/release -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o "$EXE"
|
||||
|
||||
echo "[AOT] run: ./$EXE"
|
||||
"./$EXE" || true
|
||||
|
||||
20
tools/smokes/archive/jit_smoke.sh
Normal file
20
tools/smokes/archive/jit_smoke.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Archived: JIT smoke (not maintained in current phase). Kept for reference.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/../../" && pwd)
|
||||
|
||||
BIN="$ROOT_DIR/target/release/nyash"
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "Building nyash (release, JIT)..." >&2
|
||||
cargo build --release --features cranelift-jit >/dev/null
|
||||
fi
|
||||
|
||||
echo "[JIT Smoke] Core VM/JIT (plugins disabled)" >&2
|
||||
NYASH_DISABLE_PLUGINS=1 NYASH_CLI_VERBOSE=1 "$ROOT_DIR/tools/smokes/archive/smoke_vm_jit.sh" >/tmp/nyash-jit-core.out
|
||||
grep -q '^✅ smoke done' /tmp/nyash-jit-core.out || { echo "FAIL: core VM/JIT smoke" >&2; cat /tmp/nyash-jit-core.out; exit 1; }
|
||||
echo "PASS: core VM/JIT smoke" >&2
|
||||
|
||||
echo "All PASS (archived JIT smoke)" >&2
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user