diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index ec232c0b..01147b2d 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -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 diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 33e2b415..6ce693f2 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -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に直結: @@ -42,11 +59,12 @@ Quick Next (today) - `--stage3` CLI フラグから ParserBox へ渡す導線を追加。 - `docs/reference/architecture/parser_mvp_stage3.md` に Stage‑3 設計を記録。 5) 自己ホスト経路で Ny 実装切替のゲート準備(現状は Python MVP 優先を維持)。 - 6) テスト: + 6) テスト: - `source tools/dev_env.sh pyvm` - `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)` を左から右に順に発行 → 最後に配列値を返す。 diff --git a/app_stage3_loop b/app_stage3_loop new file mode 100644 index 00000000..77c11448 Binary files /dev/null and b/app_stage3_loop differ diff --git a/apps/tests/llvm_stage3_break_continue.nyash b/apps/tests/llvm_stage3_break_continue.nyash new file mode 100644 index 00000000..ba1db889 --- /dev/null +++ b/apps/tests/llvm_stage3_break_continue.nyash @@ -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 + } +} diff --git a/apps/tests/llvm_stage3_loop_only.nyash b/apps/tests/llvm_stage3_loop_only.nyash new file mode 100644 index 00000000..f53afe5f --- /dev/null +++ b/apps/tests/llvm_stage3_loop_only.nyash @@ -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 + } +} + diff --git a/build.rs b/build.rs index b7aa4fa8..e421268c 100644 --- a/build.rs +++ b/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 = 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"); diff --git a/crates/nyash-next/src/lib.rs b/crates/nyash-next/src/lib.rs index 23f482d1..d6d31fef 100644 --- a/crates/nyash-next/src/lib.rs +++ b/crates/nyash-next/src/lib.rs @@ -6,4 +6,3 @@ pub fn version() -> &'static str { "0.1.0-dev" } - diff --git a/crates/nyash-next/src/main.rs b/crates/nyash-next/src/main.rs index 51f217e4..1cb110b8 100644 --- a/crates/nyash-next/src/main.rs +++ b/crates/nyash-next/src/main.rs @@ -2,4 +2,3 @@ fn main() { env_logger::init(); println!("nyash-next: workspace skeleton is ready."); } - diff --git a/crates/nyrt/src/lib.rs b/crates/nyrt/src/lib.rs index 8db56cab..2cd05e59 100644 --- a/crates/nyrt/src/lib.rs +++ b/crates/nyrt/src/lib.rs @@ -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::() { sb.value.clone() } else { String::new() } - } else { String::new() }; + if let Some(sb) = obj.as_any().downcast_ref::() { + 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 = 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::() { sb.value.clone() } else { String::new() } - } else { String::new() } - } else { String::new() }; + if let Some(sb) = o.as_any().downcast_ref::() { + 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::() { 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::() { + 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 = 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 { diff --git a/crates/nyrt/src/plugin/birth.rs b/crates/nyrt/src/plugin/birth.rs index 35cdf600..119dd205 100644 --- a/crates/nyrt/src/plugin/birth.rs +++ b/crates/nyrt/src/plugin/birth.rs @@ -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 = 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::() { - 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 = std::sync::Arc::new(pb); let h = nyash_rust::jit::rt::handles::to_handle(arc); diff --git a/crates/nyrt/src/plugin/future.rs b/crates/nyrt/src/plugin/future.rs index b7548141..6ac8ed6e 100644 --- a/crates/nyrt/src/plugin/future.rs +++ b/crates/nyrt/src/plugin/future.rs @@ -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 = None; diff --git a/crates/nyrt/src/plugin/invoke.rs b/crates/nyrt/src/plugin/invoke.rs index 9847d6c0..1422968a 100644 --- a/crates/nyrt/src/plugin/invoke.rs +++ b/crates/nyrt/src/plugin/invoke.rs @@ -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 = 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 = std::sync::Arc::new(pb); diff --git a/crates/nyrt/src/plugin/map.rs b/crates/nyrt/src/plugin/map.rs index 14dc1c21..923baa03 100644 --- a/crates/nyrt/src/plugin/map.rs +++ b/crates/nyrt/src/plugin/map.rs @@ -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::() { + if let Some(map) = obj + .as_any() + .downcast_ref::() + { let key_box: Box = 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 = 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::() { + if let Some(map) = obj + .as_any() + .downcast_ref::() + { let kbox: Box = 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 = 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::() { + if let Some(map) = obj + .as_any() + .downcast_ref::() + { let kbox: Box = 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::() { return if b.value { 1 } else { 0 }; } + if let Some(b) = v.as_any().downcast_ref::() { + 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::() { return if b.value { 1 } else { 0 }; } + if let Some(b) = v.as_any().downcast_ref::() { + return if b.value { 1 } else { 0 }; + } } } 0 diff --git a/crates/nyrt/src/plugin/string.rs b/crates/nyrt/src/plugin/string.rs index 1f5bf204..4a88f5e5 100644 --- a/crates/nyrt/src/plugin/string.rs +++ b/crates/nyrt/src/plugin/string.rs @@ -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* diff --git a/docs/LLVM_LAYER_OVERVIEW.md b/docs/LLVM_LAYER_OVERVIEW.md index e9ad52fa..b1a6dcdc 100644 --- a/docs/LLVM_LAYER_OVERVIEW.md +++ b/docs/LLVM_LAYER_OVERVIEW.md @@ -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 - diff --git a/docs/RESOLVER_API.md b/docs/RESOLVER_API.md index bcbc8f1f..e70013be 100644 --- a/docs/RESOLVER_API.md +++ b/docs/RESOLVER_API.md @@ -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. diff --git a/docs/private/papers/paper-e-loop-signal-ir/README.md b/docs/private/papers/paper-e-loop-signal-ir/README.md index b2df7b1b..47ae6284 100644 --- a/docs/private/papers/paper-e-loop-signal-ir/README.md +++ b/docs/private/papers/paper-e-loop-signal-ir/README.md @@ -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図、評価計画の雛形を本文内に記載 diff --git a/docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md b/docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md new file mode 100644 index 00000000..c2957e7c --- /dev/null +++ b/docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md @@ -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: 実装計画 \ No newline at end of file diff --git a/docs/private/papers/paper-k-explosive-incidents/complete-incident-collection.md b/docs/private/papers/paper-k-explosive-incidents/complete-incident-collection.md index 600da627..cd45a96b 100644 --- a/docs/private/papers/paper-k-explosive-incidents/complete-incident-collection.md +++ b/docs/private/papers/paper-k-explosive-incidents/complete-incident-collection.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が相談、人間が救う) diff --git a/docs/reference/architecture/parser_mvp_stage3.md b/docs/reference/architecture/parser_mvp_stage3.md index b37ab2d2..650e7ce9 100644 --- a/docs/reference/architecture/parser_mvp_stage3.md +++ b/docs/reference/architecture/parser_mvp_stage3.md @@ -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`) diff --git a/docs/reference/ir/json_v0.md b/docs/reference/ir/json_v0.md index 0c986cc7..8335fa42 100644 --- a/docs/reference/ir/json_v0.md +++ b/docs/reference/ir/json_v0.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. diff --git a/docs/smokes.md b/docs/smokes.md new file mode 100644 index 00000000..e2d47d93 --- /dev/null +++ b/docs/smokes.md @@ -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. diff --git a/examples/debug_notepad.rs b/examples/debug_notepad.rs index de5c65f8..5fa5511a 100644 --- a/examples/debug_notepad.rs +++ b/examples/debug_notepad.rs @@ -3,14 +3,14 @@ use eframe::egui; fn main() -> eframe::Result { env_logger::init(); // Enable logging - + let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([640.0, 480.0]) .with_title("Debug Notepad"), ..Default::default() }; - + eframe::run_native( "Debug Notepad", options, @@ -29,32 +29,34 @@ impl eframe::App for DebugApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Debug Text Input Test"); - + // Single line input ui.horizontal(|ui| { 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)); } }); - + ui.separator(); - + // Multi line input ui.label("Multi Line:"); 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(); - + // Show input events ui.label("Event Log:"); egui::ScrollArea::vertical() @@ -64,18 +66,18 @@ impl eframe::App for DebugApp { ui.label(event); } }); - + // Debug info ui.separator(); ui.label(format!("Text length: {}", self.text.len())); ui.label(format!("Single line length: {}", self.single_line.len())); - + // Test buttons if ui.button("Add Test Text").clicked() { self.text.push_str("Test "); self.event_log.push("Button: Added test text".to_string()); } - + if ui.button("Clear All").clicked() { self.text.clear(); self.single_line.clear(); @@ -83,4 +85,4 @@ impl eframe::App for DebugApp { } }); } -} \ No newline at end of file +} diff --git a/examples/nyash_explorer.rs b/examples/nyash_explorer.rs index bfec1fd5..8a840c39 100644 --- a/examples/nyash_explorer.rs +++ b/examples/nyash_explorer.rs @@ -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::*, }, }; @@ -23,7 +20,7 @@ fn main() -> eframe::Result { .with_title("Nyash Explorer - ドライブ情報ビューアー"), ..Default::default() }; - + eframe::run_native( "Nyash Explorer", options, @@ -37,24 +34,25 @@ fn main() -> eframe::Result { // フォント設定 fn setup_custom_fonts(ctx: &egui::Context) { let mut fonts = egui::FontDefinitions::default(); - + 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 .families .entry(FontFamily::Proportional) .or_default() .insert(0, "noto_sans_jp".to_owned()); - + fonts .families .entry(FontFamily::Monospace) .or_default() .push("noto_sans_jp".to_owned()); - + ctx.set_fonts(fonts); } @@ -84,22 +82,22 @@ impl NyashExplorer { explorer.refresh_drives(); explorer } - + fn refresh_drives(&mut self) { self.drives.clear(); self.status = "ドライブ情報を取得中...".to_string(); - + #[cfg(windows)] { unsafe { // 論理ドライブのビットマスクを取得 let drives_mask = GetLogicalDrives(); - + for i in 0..26 { if drives_mask & (1 << i) != 0 { let drive_letter = format!("{}:", (b'A' + i) as char); let drive_path = format!("{}\\", drive_letter); - + // ドライブ情報を取得 let mut drive_info = DriveInfo { letter: drive_letter.clone(), @@ -109,12 +107,15 @@ impl NyashExplorer { free_bytes: 0, icon_data: None, }; - + // ドライブタイプを取得 let drive_type_code = GetDriveTypeW(PCWSTR::from_raw( - format!("{}\0", drive_path).encode_utf16().collect::>().as_ptr() + format!("{}\0", drive_path) + .encode_utf16() + .collect::>() + .as_ptr(), )); - + drive_info.drive_type = match drive_type_code { DRIVE_REMOVABLE => "リムーバブル".to_string(), DRIVE_FIXED => "ハードディスク".to_string(), @@ -123,22 +124,29 @@ impl NyashExplorer { DRIVE_RAMDISK => "RAMディスク".to_string(), _ => "不明".to_string(), }; - + // ボリューム情報を取得 let mut volume_name = vec![0u16; 256]; let mut file_system = vec![0u16; 256]; let mut serial_number = 0u32; let mut max_component_len = 0u32; let mut file_system_flags = 0u32; - + if GetVolumeInformationW( - PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::>().as_ptr()), + PCWSTR::from_raw( + format!("{}\0", drive_path) + .encode_utf16() + .collect::>() + .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(); @@ -150,28 +158,35 @@ impl NyashExplorer { } else { drive_info.name = format!("ドライブ ({})", drive_letter); } - + // 空き容量を取得 let mut free_bytes_available = 0u64; let mut total_bytes = 0u64; let mut total_free_bytes = 0u64; - + if GetDiskFreeSpaceExW( - PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::>().as_ptr()), + PCWSTR::from_raw( + format!("{}\0", drive_path) + .encode_utf16() + .collect::>() + .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; } - + self.drives.push(drive_info); } } } } - + #[cfg(not(windows))] { // Windows以外の環境ではダミーデータ @@ -184,20 +199,20 @@ impl NyashExplorer { icon_data: None, }); } - + self.status = format!("{}個のドライブを検出しました", self.drives.len()); } - + fn format_bytes(bytes: u64) -> String { const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"]; let mut size = bytes as f64; let mut unit_index = 0; - + while size >= 1024.0 && unit_index < UNITS.len() - 1 { size /= 1024.0; unit_index += 1; } - + format!("{:.2} {}", size, UNITS[unit_index]) } } @@ -216,7 +231,7 @@ impl eframe::App for NyashExplorer { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } }); - + ui.menu_button("表示", |ui| { if ui.button("大きいアイコン").clicked() { self.status = "表示モード: 大きいアイコン".to_string(); @@ -225,7 +240,7 @@ impl eframe::App for NyashExplorer { self.status = "表示モード: 詳細".to_string(); } }); - + ui.menu_button("ヘルプ", |ui| { if ui.button("Nyash Explorerについて").clicked() { self.status = "Nyash Explorer - Everything is Box! 🐱".to_string(); @@ -233,7 +248,7 @@ impl eframe::App for NyashExplorer { }); }); }); - + // ツールバー egui::TopBottomPanel::top("toolbar").show(ctx, |ui| { ui.horizontal(|ui| { @@ -244,7 +259,7 @@ impl eframe::App for NyashExplorer { ui.label("Nyash Explorer - ドライブ情報ビューアー"); }); }); - + // ステータスバー egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { ui.horizontal(|ui| { @@ -254,27 +269,27 @@ impl eframe::App for NyashExplorer { }); }); }); - + // メインパネル - ドライブ一覧 egui::CentralPanel::default().show(ctx, |ui| { ui.heading("💾 ドライブ一覧"); ui.separator(); - + egui::ScrollArea::vertical().show(ui, |ui| { for (index, drive) in self.drives.iter().enumerate() { let is_selected = self.selected_drive == Some(index); - + ui.group(|ui| { let response = ui.allocate_response( egui::vec2(ui.available_width(), 100.0), egui::Sense::click(), ); - + if response.clicked() { self.selected_drive = Some(index); self.status = format!("{} を選択しました", drive.name); } - + // 背景色 if is_selected { ui.painter().rect_filled( @@ -283,7 +298,7 @@ impl eframe::App for NyashExplorer { egui::Color32::from_rgb(100, 149, 237).gamma_multiply(0.2), ); } - + ui.allocate_ui_at_rect(response.rect, |ui| { ui.horizontal(|ui| { // ドライブアイコン(仮) @@ -298,19 +313,20 @@ impl eframe::App for NyashExplorer { }; ui.label(egui::RichText::new(icon_text).size(40.0)); }); - + ui.add_space(20.0); - + // ドライブ情報 ui.vertical(|ui| { ui.add_space(10.0); ui.label(egui::RichText::new(&drive.name).size(16.0).strong()); ui.label(format!("種類: {}", drive.drive_type)); - + 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!( "使用領域: {} / {} ({:.1}%)", @@ -319,7 +335,7 @@ impl eframe::App for NyashExplorer { usage_percent )); }); - + // 使用率バー let bar_width = 200.0; let bar_height = 10.0; @@ -327,14 +343,14 @@ impl eframe::App for NyashExplorer { egui::vec2(bar_width, bar_height), egui::Sense::hover(), ); - + // 背景 ui.painter().rect_filled( rect, 2.0, egui::Color32::from_gray(60), ); - + // 使用領域 let used_width = bar_width * (usage_percent / 100.0); let used_rect = egui::Rect::from_min_size( @@ -354,18 +370,19 @@ impl eframe::App for NyashExplorer { }); }); }); - + ui.add_space(5.0); } }); - + // クイックアクション 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() { let total: u64 = self.drives.iter().map(|d| d.total_bytes).sum(); let free: u64 = self.drives.iter().map(|d| d.free_bytes).sum(); @@ -378,4 +395,4 @@ impl eframe::App for NyashExplorer { }); }); } -} \ No newline at end of file +} diff --git a/examples/nyash_explorer_with_icons.rs b/examples/nyash_explorer_with_icons.rs index f0791224..ead77a0e 100644 --- a/examples/nyash_explorer_with_icons.rs +++ b/examples/nyash_explorer_with_icons.rs @@ -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 { @@ -24,14 +19,14 @@ fn main() -> eframe::Result { unsafe { let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED); } - + let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([1024.0, 768.0]) .with_title("Nyash Explorer with Icons - アイコン付きドライブビューアー"), ..Default::default() }; - + eframe::run_native( "Nyash Explorer Icons", options, @@ -45,24 +40,25 @@ fn main() -> eframe::Result { // フォント設定 fn setup_custom_fonts(ctx: &egui::Context) { let mut fonts = egui::FontDefinitions::default(); - + 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 .families .entry(FontFamily::Proportional) .or_default() .insert(0, "noto_sans_jp".to_owned()); - + fonts .families .entry(FontFamily::Monospace) .or_default() .push("noto_sans_jp".to_owned()); - + ctx.set_fonts(fonts); } @@ -93,13 +89,16 @@ impl NyashExplorer { explorer.refresh_drives(); explorer } - + #[cfg(windows)] fn get_drive_icon(&self, drive_path: &str) -> Option { unsafe { let mut shfi = SHFILEINFOW::default(); - let drive_path_wide: Vec = drive_path.encode_utf16().chain(std::iter::once(0)).collect(); - + let drive_path_wide: Vec = drive_path + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + // アイコンを取得 let result = SHGetFileInfoW( PCWSTR::from_raw(drive_path_wide.as_ptr()), @@ -108,25 +107,25 @@ impl NyashExplorer { std::mem::size_of::() as u32, SHGFI_ICON | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES, ); - + if result == 0 || shfi.hIcon.is_invalid() { return None; } - + // アイコンからビットマップを取得 let icon_info = ICONINFO::default(); if GetIconInfo(shfi.hIcon, &icon_info as *const _ as *mut _).is_ok() { // ビットマップからピクセルデータを取得する処理 // アイコンを破棄 let _ = DestroyIcon(shfi.hIcon); - + // C:ドライブの場合は保存済みBMPファイルを読み込む if drive_path.contains("C:") { if let Some(icon) = Self::load_bmp_icon("c_drive_icon.bmp") { return Some(icon); } } - + // それ以外はダミーアイコンを返す Some(Self::create_dummy_icon(&drive_path)) } else { @@ -135,44 +134,46 @@ impl NyashExplorer { } } } - + #[cfg(not(windows))] fn get_drive_icon(&self, drive_path: &str) -> Option { Some(Self::create_dummy_icon(drive_path)) } - + // BMPファイルを読み込んでColorImageに変換 fn load_bmp_icon(file_path: &str) -> Option { let mut file = File::open(file_path).ok()?; let mut buffer = Vec::new(); file.read_to_end(&mut buffer).ok()?; - + // BMPヘッダーをパース(簡易版) if buffer.len() < 54 { return None; } - + // BMPマジックナンバーをチェック if &buffer[0..2] != b"BM" { return None; } - + // ヘッダーから情報を読み取る - 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のみサポート if bits_per_pixel != 32 { println!("Unsupported BMP format: {} bits per pixel", bits_per_pixel); return None; } - + // ピクセルデータを読み取る let mut pixels = Vec::with_capacity(width * height); let pixel_data = &buffer[data_offset..]; - + // BMPは下から上に格納されているので、反転しながら読み取る for y in (0..height).rev() { for x in 0..width { @@ -188,18 +189,18 @@ impl NyashExplorer { } } } - + Some(ColorImage { size: [width, height], pixels, }) } - + // ダミーアイコンを生成(実際のアイコン取得が複雑なため) fn create_dummy_icon(drive_path: &str) -> ColorImage { let size = 48; let mut pixels = vec![egui::Color32::TRANSPARENT; size * size]; - + // ドライブタイプに応じた色を設定 let color = if drive_path.contains("C:") { egui::Color32::from_rgb(100, 149, 237) // コーンフラワーブルー @@ -208,17 +209,17 @@ impl NyashExplorer { } else { egui::Color32::from_rgb(255, 182, 193) // ライトピンク }; - + // シンプルな円形アイコンを描画 let center = size as f32 / 2.0; let radius = (size as f32 / 2.0) - 4.0; - + for y in 0..size { for x in 0..size { let dx = x as f32 - center; let dy = y as f32 - center; let distance = (dx * dx + dy * dy).sqrt(); - + if distance <= radius { pixels[y * size + x] = color; } else if distance <= radius + 2.0 { @@ -227,13 +228,13 @@ impl NyashExplorer { } } } - + // ドライブ文字を中央に配置(簡易版) if let Some(_letter) = drive_path.chars().next() { // 文字の位置(中央) let text_x = size / 2 - 8; let text_y = size / 2 - 8; - + // 白い文字で描画 for dy in 0..16 { for dx in 0..16 { @@ -246,28 +247,28 @@ impl NyashExplorer { } } } - + ColorImage { size: [size, size], pixels, } } - + fn refresh_drives(&mut self) { self.drives.clear(); self.status = "ドライブ情報を取得中...".to_string(); - + #[cfg(windows)] { unsafe { // 論理ドライブのビットマスクを取得 let drives_mask = GetLogicalDrives(); - + for i in 0..26 { if drives_mask & (1 << i) != 0 { let drive_letter = format!("{}:", (b'A' + i) as char); let drive_path = format!("{}\\", drive_letter); - + // ドライブ情報を取得 let mut drive_info = DriveInfo { letter: drive_letter.clone(), @@ -277,12 +278,15 @@ impl NyashExplorer { free_bytes: 0, icon_texture: None, }; - + // ドライブタイプを取得 let drive_type_code = GetDriveTypeW(PCWSTR::from_raw( - format!("{}\0", drive_path).encode_utf16().collect::>().as_ptr() + format!("{}\0", drive_path) + .encode_utf16() + .collect::>() + .as_ptr(), )); - + drive_info.drive_type = match drive_type_code { DRIVE_REMOVABLE => "リムーバブル".to_string(), DRIVE_FIXED => "ハードディスク".to_string(), @@ -291,22 +295,29 @@ impl NyashExplorer { DRIVE_RAMDISK => "RAMディスク".to_string(), _ => "不明".to_string(), }; - + // ボリューム情報を取得 let mut volume_name = vec![0u16; 256]; let mut file_system = vec![0u16; 256]; let mut serial_number = 0u32; let mut max_component_len = 0u32; let mut file_system_flags = 0u32; - + if GetVolumeInformationW( - PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::>().as_ptr()), + PCWSTR::from_raw( + format!("{}\0", drive_path) + .encode_utf16() + .collect::>() + .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(); @@ -318,38 +329,45 @@ impl NyashExplorer { } else { drive_info.name = format!("ドライブ ({})", drive_letter); } - + // 空き容量を取得 let mut free_bytes_available = 0u64; let mut total_bytes = 0u64; let mut total_free_bytes = 0u64; - + if GetDiskFreeSpaceExW( - PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::>().as_ptr()), + PCWSTR::from_raw( + format!("{}\0", drive_path) + .encode_utf16() + .collect::>() + .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; } - + // アイコンを取得してテクスチャに変換 if let Some(icon_image) = self.get_drive_icon(&drive_path) { let texture = self.ctx.load_texture( format!("drive_icon_{}", drive_letter), icon_image, - Default::default() + Default::default(), ); drive_info.icon_texture = Some(texture); } - + self.drives.push(drive_info); } } } } - + #[cfg(not(windows))] { // Windows以外の環境ではダミーデータ @@ -361,32 +379,33 @@ impl NyashExplorer { free_bytes: 250_000_000_000, icon_texture: None, }; - + 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 { const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"]; let mut size = bytes as f64; let mut unit_index = 0; - + while size >= 1024.0 && unit_index < UNITS.len() - 1 { size /= 1024.0; unit_index += 1; } - + format!("{:.2} {}", size, UNITS[unit_index]) } } @@ -422,7 +441,7 @@ impl eframe::App for NyashExplorer { }); }); }); - + // ツールバー egui::TopBottomPanel::top("toolbar").show(ctx, |ui| { ui.horizontal(|ui| { @@ -433,7 +452,7 @@ impl eframe::App for NyashExplorer { ui.label("Nyash Explorer - アイコン付きドライブビューアー"); }); }); - + // ステータスバー egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { ui.horizontal(|ui| { @@ -443,27 +462,27 @@ impl eframe::App for NyashExplorer { }); }); }); - + // メインパネル - ドライブ一覧 egui::CentralPanel::default().show(ctx, |ui| { ui.heading("💾 ドライブ一覧(アイコン付き)"); ui.separator(); - + egui::ScrollArea::vertical().show(ui, |ui| { for (index, drive) in self.drives.iter().enumerate() { let is_selected = self.selected_drive == Some(index); - + ui.group(|ui| { let response = ui.allocate_response( egui::vec2(ui.available_width(), 100.0), egui::Sense::click(), ); - + if response.clicked() { self.selected_drive = Some(index); self.status = format!("{} を選択しました", drive.name); } - + // 背景色 if is_selected { ui.painter().rect_filled( @@ -472,13 +491,13 @@ impl eframe::App for NyashExplorer { egui::Color32::from_rgb(100, 149, 237).gamma_multiply(0.2), ); } - + ui.allocate_new_ui(egui::UiBuilder::new().max_rect(response.rect), |ui| { ui.horizontal(|ui| { // ドライブアイコン ui.vertical(|ui| { ui.add_space(10.0); - + if let Some(texture) = &drive.icon_texture { ui.image((texture.id(), egui::vec2(48.0, 48.0))); } else { @@ -493,19 +512,20 @@ impl eframe::App for NyashExplorer { ui.label(egui::RichText::new(icon_text).size(40.0)); } }); - + ui.add_space(20.0); - + // ドライブ情報 ui.vertical(|ui| { ui.add_space(10.0); ui.label(egui::RichText::new(&drive.name).size(16.0).strong()); ui.label(format!("種類: {}", drive.drive_type)); - + 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!( "使用領域: {} / {} ({:.1}%)", @@ -514,7 +534,7 @@ impl eframe::App for NyashExplorer { usage_percent )); }); - + // 使用率バー let bar_width = 200.0; let bar_height = 10.0; @@ -522,14 +542,14 @@ impl eframe::App for NyashExplorer { egui::vec2(bar_width, bar_height), egui::Sense::hover(), ); - + // 背景 ui.painter().rect_filled( rect, 2.0, egui::Color32::from_gray(60), ); - + // 使用領域 let used_width = bar_width * (usage_percent / 100.0); let used_rect = egui::Rect::from_min_size( @@ -549,18 +569,20 @@ impl eframe::App for NyashExplorer { }); }); }); - + ui.add_space(5.0); } }); - + // クイックアクション 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() { let total: u64 = self.drives.iter().map(|d| d.total_bytes).sum(); let free: u64 = self.drives.iter().map(|d| d.free_bytes).sum(); @@ -573,4 +595,4 @@ impl eframe::App for NyashExplorer { }); }); } -} \ No newline at end of file +} diff --git a/examples/nyash_notepad_jp.rs b/examples/nyash_notepad_jp.rs index 4402013c..67f48c52 100644 --- a/examples/nyash_notepad_jp.rs +++ b/examples/nyash_notepad_jp.rs @@ -10,7 +10,7 @@ fn main() -> eframe::Result { .with_title("Nyash Notepad - にゃっしゅメモ帳"), ..Default::default() }; - + eframe::run_native( "Nyash Notepad JP", options, @@ -26,27 +26,28 @@ fn main() -> eframe::Result { fn setup_custom_fonts(ctx: &egui::Context) { // フォント設定を取得 let mut fonts = egui::FontDefinitions::default(); - + // 日本語フォント(可変ウェイト)を追加 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 .families .entry(FontFamily::Proportional) .or_default() .insert(0, "noto_sans_jp".to_owned()); // 一番優先度高く追加 - + // モノスペースフォントにも日本語フォントを追加 fonts .families .entry(FontFamily::Monospace) .or_default() .push("noto_sans_jp".to_owned()); - + // フォント設定を適用 ctx.set_fonts(fonts); } @@ -76,7 +77,7 @@ impl eframe::App for NyashNotepad { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } }); - + ui.menu_button("編集", |ui| { if ui.button("すべて選択").clicked() { self.status = "すべて選択(未実装)".to_string(); @@ -85,31 +86,33 @@ impl eframe::App for NyashNotepad { self.status = "検索機能(未実装)".to_string(); } }); - + ui.menu_button("ヘルプ", |ui| { if ui.button("Nyashについて").clicked() { self.status = "Nyash - Everything is Box! 🐱".to_string(); } if ui.button("使い方").clicked() { - self.status = "テキストを入力して、にゃっしゅプログラムを書こう!".to_string(); + self.status = + "テキストを入力して、にゃっしゅプログラムを書こう!".to_string(); } }); }); }); - + // ステータスバー egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { 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() )); }); }); }); - + // メインのテキストエディタ egui::CentralPanel::default().show(ctx, |ui| { // ツールバー @@ -118,39 +121,39 @@ impl eframe::App for NyashNotepad { self.text.clear(); self.status = "テキストをクリアしました".to_string(); } - + ui.separator(); - + if ui.button("📋 コピー").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.status = "テキストをコピーしました".to_string(); } - + if ui.button("✂️ カット").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.text.clear(); self.status = "テキストをカットしました".to_string(); } - + if ui.button("📄 ペースト").clicked() { self.status = "ペースト機能(簡易版)".to_string(); } - + ui.separator(); - + if ui.button("🔤 フォント大").clicked() { ctx.set_zoom_factor(ctx.zoom_factor() * 1.1); self.status = "フォントサイズを拡大しました".to_string(); } - + if ui.button("🔡 フォント小").clicked() { ctx.set_zoom_factor(ctx.zoom_factor() * 0.9); self.status = "フォントサイズを縮小しました".to_string(); } }); - + ui.separator(); - + // テキストエディタ本体 egui::ScrollArea::vertical().show(ui, |ui| { ui.add( @@ -158,22 +161,26 @@ impl eframe::App for NyashNotepad { .font(egui::TextStyle::Monospace) .desired_width(f32::INFINITY) .desired_rows(20) - .hint_text("ここにテキストを入力してください... にゃ!🐱") + .hint_text("ここにテキストを入力してください... にゃ!🐱"), ); }); - + // サンプルボタン ui.separator(); ui.horizontal(|ui| { 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"); @@ -186,13 +193,16 @@ impl eframe::App for NyashNotepad { self.text.push_str("hello.挨拶()\n"); self.status = "Nyashサンプルコードを挿入しました".to_string(); } - + 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(); } - + if ui.button("🐱 ASCIIにゃんこ").clicked() { self.text.push_str("\n/*\n"); self.text.push_str(" /\\_/\\ \n"); @@ -205,4 +215,4 @@ impl eframe::App for NyashNotepad { }); }); } -} \ No newline at end of file +} diff --git a/examples/simple_notepad.rs b/examples/simple_notepad.rs index 9b310443..14dc2228 100644 --- a/examples/simple_notepad.rs +++ b/examples/simple_notepad.rs @@ -11,7 +11,7 @@ fn main() -> eframe::Result { .with_title("Nyash Notepad"), ..Default::default() }; - + eframe::run_native( "Nyash Notepad", options, @@ -44,14 +44,14 @@ impl eframe::App for NyashNotepad { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } }); - + ui.menu_button("編集", |ui| { if ui.button("すべて選択").clicked() { // TODO: テキストエリア全選択 self.status = "すべて選択(未実装)".to_string(); } }); - + ui.menu_button("ヘルプ", |ui| { if ui.button("Nyashについて").clicked() { self.status = "Nyash - Everything is Box! 🐱".to_string(); @@ -59,7 +59,7 @@ impl eframe::App for NyashNotepad { }); }); }); - + // ステータスバー egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { ui.horizontal(|ui| { @@ -69,7 +69,7 @@ impl eframe::App for NyashNotepad { }); }); }); - + // メインのテキストエディタ egui::CentralPanel::default().show(ctx, |ui| { // ツールバー @@ -78,28 +78,28 @@ impl eframe::App for NyashNotepad { self.text.clear(); self.status = "テキストをクリアしました".to_string(); } - + ui.separator(); - + if ui.button("📋 コピー").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.status = "テキストをコピーしました".to_string(); } - + if ui.button("✂️ カット").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.text.clear(); self.status = "テキストをカットしました".to_string(); } - + if ui.button("📄 ペースト").clicked() { // egui 0.29ではクリップボードAPIが変更されている self.status = "ペースト機能(簡易版)".to_string(); } }); - + ui.separator(); - + // テキストエディタ本体 egui::ScrollArea::vertical().show(ui, |ui| { ui.add( @@ -107,10 +107,10 @@ impl eframe::App for NyashNotepad { .font(egui::TextStyle::Monospace) .desired_width(f32::INFINITY) .desired_rows(20) - .hint_text("ここにテキストを入力してください... にゃ!") + .hint_text("ここにテキストを入力してください... にゃ!"), ); }); - + // サンプルボタン ui.separator(); ui.horizontal(|ui| { @@ -120,18 +120,20 @@ 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(); } - + 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(); } }); }); } -} \ No newline at end of file +} diff --git a/examples/simple_notepad_ascii.rs b/examples/simple_notepad_ascii.rs index 87a8de82..a3241b84 100644 --- a/examples/simple_notepad_ascii.rs +++ b/examples/simple_notepad_ascii.rs @@ -10,7 +10,7 @@ fn main() -> eframe::Result { .with_title("Nyash Notepad - ASCII Version"), ..Default::default() }; - + eframe::run_native( "Nyash Notepad", options, @@ -43,13 +43,13 @@ impl eframe::App for NyashNotepad { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } }); - + ui.menu_button("Edit", |ui| { if ui.button("Select All").clicked() { self.status = "Select All (not implemented)".to_string(); } }); - + ui.menu_button("Help", |ui| { if ui.button("About Nyash").clicked() { self.status = "Nyash - Everything is Box! (^-^)".to_string(); @@ -57,7 +57,7 @@ impl eframe::App for NyashNotepad { }); }); }); - + // Status bar egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { ui.horizontal(|ui| { @@ -67,45 +67,45 @@ impl eframe::App for NyashNotepad { }); }); }); - + // Main text editor egui::CentralPanel::default().show(ctx, |ui| { // Title ui.heading("=== Nyash Text Editor ==="); - + // Toolbar without emojis ui.horizontal(|ui| { if ui.button("[X] Clear").clicked() { self.text.clear(); self.status = "Text cleared".to_string(); } - + ui.separator(); - + if ui.button("[C] Copy").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.status = "Text copied to clipboard".to_string(); } - + if ui.button("[X] Cut").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.text.clear(); self.status = "Text cut to clipboard".to_string(); } - + if ui.button("[V] Paste").clicked() { self.status = "Paste (simplified version)".to_string(); } - + ui.separator(); - + if ui.button("[?] Help").clicked() { self.status = "Nyash Notepad v1.0 - Everything is Box!".to_string(); } }); - + ui.separator(); - + // Text editor body egui::ScrollArea::vertical().show(ui, |ui| { ui.add( @@ -113,22 +113,23 @@ 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!"), ); }); - + // Sample buttons ui.separator(); ui.horizontal(|ui| { ui.label("Quick Insert: "); - + if ui.button("Nyash Sample Code").clicked() { self.text.push_str("\n// Nyash - Everything is Box!\n"); self.text.push_str("box HelloWorld {\n"); 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"); @@ -141,13 +142,14 @@ impl eframe::App for NyashNotepad { self.text.push_str("hello.greet()\n"); self.status = "Nyash sample code inserted".to_string(); } - + 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(); } - + if ui.button("ASCII Art Cat").clicked() { self.text.push_str("\n"); self.text.push_str(" /\\_/\\ \n"); @@ -160,4 +162,4 @@ impl eframe::App for NyashNotepad { }); }); } -} \ No newline at end of file +} diff --git a/examples/simple_notepad_v2.rs b/examples/simple_notepad_v2.rs index 814a8b4d..01b9b609 100644 --- a/examples/simple_notepad_v2.rs +++ b/examples/simple_notepad_v2.rs @@ -10,7 +10,7 @@ fn main() -> eframe::Result { .with_title("Nyash Notepad"), ..Default::default() }; - + eframe::run_native( "Nyash Notepad", options, @@ -43,13 +43,13 @@ impl eframe::App for NyashNotepad { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } }); - + ui.menu_button("Edit", |ui| { if ui.button("Select All").clicked() { self.status = "Select All (not implemented)".to_string(); } }); - + ui.menu_button("Help", |ui| { if ui.button("About Nyash").clicked() { self.status = "Nyash - Everything is Box!".to_string(); @@ -57,7 +57,7 @@ impl eframe::App for NyashNotepad { }); }); }); - + // Status bar egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { ui.horizontal(|ui| { @@ -67,7 +67,7 @@ impl eframe::App for NyashNotepad { }); }); }); - + // Main text editor egui::CentralPanel::default().show(ctx, |ui| { // Toolbar @@ -76,27 +76,27 @@ impl eframe::App for NyashNotepad { self.text.clear(); self.status = "Text cleared".to_string(); } - + ui.separator(); - + if ui.button("Copy").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.status = "Text copied to clipboard".to_string(); } - + if ui.button("Cut").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.text.clear(); self.status = "Text cut to clipboard".to_string(); } - + if ui.button("Paste").clicked() { self.status = "Paste (simplified version)".to_string(); } }); - + ui.separator(); - + // Text editor body egui::ScrollArea::vertical().show(ui, |ui| { ui.add( @@ -104,10 +104,10 @@ 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!"), ); }); - + // Sample buttons ui.separator(); ui.horizontal(|ui| { @@ -117,18 +117,20 @@ 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(); } - + 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(); } }); }); } -} \ No newline at end of file +} diff --git a/examples/simple_notepad_win.rs b/examples/simple_notepad_win.rs index 6b3b45e4..51d3674b 100644 --- a/examples/simple_notepad_win.rs +++ b/examples/simple_notepad_win.rs @@ -12,7 +12,7 @@ fn main() -> eframe::Result { .with_title("Nyash Notepad"), ..Default::default() }; - + eframe::run_native( "Nyash Notepad", options, @@ -28,10 +28,10 @@ fn main() -> eframe::Result { fn setup_custom_fonts(ctx: &egui::Context) { // Start with the default fonts let mut fonts = egui::FontDefinitions::default(); - + // Use built-in fonts for cross-platform compatibility // Note: On Windows, egui will automatically use system fonts - + // Tell egui to use these fonts ctx.set_fonts(fonts); } @@ -71,13 +71,13 @@ impl eframe::App for NyashNotepad { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } }); - + ui.menu_button("Edit", |ui| { if ui.button("Select All").clicked() { self.status = "Select All (not implemented)".to_string(); } }); - + ui.menu_button("Help", |ui| { if ui.button("About Nyash").clicked() { self.status = "Nyash - Everything is Box!".to_string(); @@ -85,7 +85,7 @@ impl eframe::App for NyashNotepad { }); }); }); - + // Status bar egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { ui.horizontal(|ui| { @@ -95,7 +95,7 @@ impl eframe::App for NyashNotepad { }); }); }); - + // Main text editor egui::CentralPanel::default().show(ctx, |ui| { // Toolbar with ASCII-only labels @@ -104,27 +104,27 @@ impl eframe::App for NyashNotepad { self.text.clear(); self.status = "Text cleared".to_string(); } - + ui.separator(); - + if ui.button("[Copy]").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.status = "Text copied to clipboard".to_string(); } - + if ui.button("[Cut]").clicked() { ui.output_mut(|o| o.copied_text = self.text.clone()); self.text.clear(); self.status = "Text cut to clipboard".to_string(); } - + if ui.button("[Paste]").clicked() { self.status = "Paste (simplified version)".to_string(); } }); - + ui.separator(); - + // Text editor body egui::ScrollArea::vertical().show(ui, |ui| { ui.add( @@ -132,10 +132,10 @@ 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!"), ); }); - + // Sample buttons ui.separator(); ui.horizontal(|ui| { @@ -145,15 +145,17 @@ 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(); } - + 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(); } }); diff --git a/examples/test_icon_extraction.rs b/examples/test_icon_extraction.rs index d7fdfc08..e3fa48d9 100644 --- a/examples/test_icon_extraction.rs +++ b/examples/test_icon_extraction.rs @@ -4,25 +4,23 @@ #[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() { #[cfg(windows)] unsafe { println!("Windows Icon Extraction Test"); - + // C:ドライブのアイコンを取得 let drive_path = "C:\\"; - let drive_path_wide: Vec = drive_path.encode_utf16().chain(std::iter::once(0)).collect(); - + let drive_path_wide: Vec = drive_path + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + let mut shfi = SHFILEINFOW::default(); - + let result = SHGetFileInfoW( PCWSTR::from_raw(drive_path_wide.as_ptr()), FILE_ATTRIBUTE_NORMAL, @@ -30,74 +28,80 @@ fn main() { std::mem::size_of::() as u32, SHGFI_ICON | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES, ); - + println!("SHGetFileInfoW result: {}", result); - + if result != 0 && !shfi.hIcon.is_invalid() { println!("Icon handle obtained!"); - + // アイコン情報を取得 let mut icon_info = ICONINFO::default(); if GetIconInfo(shfi.hIcon, &mut icon_info).is_ok() { println!("GetIconInfo success!"); println!("fIcon: {}", icon_info.fIcon.as_bool()); - + // ビットマップ情報を取得 if !icon_info.hbmColor.is_invalid() { println!("Color bitmap handle obtained!"); - + // ビットマップ情報を取得 let mut bitmap = BITMAP::default(); let size = GetObjectW( - icon_info.hbmColor.into(), + icon_info.hbmColor.into(), std::mem::size_of::() as i32, - Some(&mut bitmap as *mut _ as *mut _) + Some(&mut bitmap as *mut _ as *mut _), ); - + if size > 0 { println!("Bitmap info:"); println!(" Width: {}", bitmap.bmWidth); println!(" Height: {}", bitmap.bmHeight); println!(" Bits per pixel: {}", bitmap.bmBitsPixel); println!(" Planes: {}", bitmap.bmPlanes); - + // ピクセルデータを取得 let pixel_count = (bitmap.bmWidth * bitmap.bmHeight) as usize; let bytes_per_pixel = (bitmap.bmBitsPixel / 8) as usize; let mut pixels = vec![0u8; pixel_count * bytes_per_pixel]; - + 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"); } } - + // ビットマップを削除 let _ = DeleteObject(icon_info.hbmColor.into()); } - + if !icon_info.hbmMask.is_invalid() { println!("Mask bitmap handle obtained!"); let _ = DeleteObject(icon_info.hbmMask.into()); } } - + // アイコンを破棄 let _ = DestroyIcon(shfi.hIcon); } else { println!("Failed to get icon"); } } - + #[cfg(not(windows))] println!("This test only works on Windows"); } @@ -106,17 +110,17 @@ fn main() { fn save_as_bmp(filename: &str, pixels: &[u8], width: i32, height: i32, bits_per_pixel: u16) { use std::fs::File; use std::io::Write; - + // 簡易BMPヘッダー(実際の実装はもっと複雑) let file_size = 54 + pixels.len() as u32; let mut file = File::create(filename).unwrap(); - + // BMPファイルヘッダー file.write_all(b"BM").unwrap(); // マジックナンバー file.write_all(&file_size.to_le_bytes()).unwrap(); file.write_all(&0u32.to_le_bytes()).unwrap(); // 予約 file.write_all(&54u32.to_le_bytes()).unwrap(); // データオフセット - + // BMPインフォヘッダー file.write_all(&40u32.to_le_bytes()).unwrap(); // ヘッダーサイズ file.write_all(&width.to_le_bytes()).unwrap(); @@ -124,14 +128,15 @@ 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(); // カラーテーブル数 file.write_all(&0u32.to_le_bytes()).unwrap(); // 重要な色数 - + // ピクセルデータ file.write_all(pixels).unwrap(); - + println!("BMP file saved: {}", filename); -} \ No newline at end of file +} diff --git a/plugins/nyash-array-plugin/src/lib.rs b/plugins/nyash-array-plugin/src/lib.rs index 12e76f15..4b093f3a 100644 --- a/plugins/nyash-array-plugin/src/lib.rs +++ b/plugins/nyash-array-plugin/src/lib.rs @@ -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; @@ -17,27 +20,34 @@ const NYB_E_PLUGIN_ERROR: i32 = -5; const NYB_E_INVALID_HANDLE: i32 = -8; // ===== Method IDs ===== -const METHOD_BIRTH: u32 = 0; // constructor -> returns instance_id (u32 LE, no TLV) -const METHOD_LENGTH: u32 = 1; // returns TLV i64 -const METHOD_GET: u32 = 2; // args: i64 index -> returns TLV i64 -const METHOD_PUSH: u32 = 3; // args: i64 value -> returns TLV i64 (new length) -const METHOD_SET: u32 = 4; // args: i64 index, i64 value -> returns TLV i64 (new length) -const METHOD_FINI: u32 = u32::MAX; // destructor +const METHOD_BIRTH: u32 = 0; // constructor -> returns instance_id (u32 LE, no TLV) +const METHOD_LENGTH: u32 = 1; // returns TLV i64 +const METHOD_GET: u32 = 2; // args: i64 index -> returns TLV i64 +const METHOD_PUSH: u32 = 3; // args: i64 value -> returns TLV i64 (new length) +const METHOD_SET: u32 = 4; // args: i64 index, i64 value -> returns TLV i64 (new length) +const METHOD_FINI: u32 = u32::MAX; // destructor // Assign a unique type_id for ArrayBox (as declared in nyash.toml) const TYPE_ID_ARRAY: u32 = 10; // ===== Instance state (PoC: store i64 values only) ===== -struct ArrayInstance { data: Vec } +struct ArrayInstance { + data: Vec, +} -static INSTANCES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static INSTANCES: Lazy>> = + 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + let mut buf: Vec = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); 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 { - 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; diff --git a/plugins/nyash-console-plugin/src/lib.rs b/plugins/nyash-console-plugin/src/lib.rs index e3068260..c821e8ad 100644 --- a/plugins/nyash-console-plugin/src/lib.rs +++ b/plugins/nyash-console-plugin/src/lib.rs @@ -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; @@ -16,7 +19,7 @@ const NYB_E_PLUGIN_ERROR: i32 = -5; // ===== Method IDs ===== const METHOD_BIRTH: u32 = 0; -const METHOD_LOG: u32 = 1; // log(text) +const METHOD_LOG: u32 = 1; // log(text) const METHOD_PRINTLN: u32 = 2; // println(text) const METHOD_FINI: u32 = u32::MAX; @@ -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>> = Lazy::new(|| { - Mutex::new(HashMap::new()) -}); +static INSTANCES: Lazy>> = + 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 { - 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 { - 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!("", 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!("", 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!( + "", + 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, diff --git a/plugins/nyash-counter-plugin/src/lib.rs b/plugins/nyash-counter-plugin/src/lib.rs index 7d6d082c..84b56d5a 100644 --- a/plugins/nyash-counter-plugin/src/lib.rs +++ b/plugins/nyash-counter-plugin/src/lib.rs @@ -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; @@ -15,25 +18,32 @@ const NYB_E_PLUGIN_ERROR: i32 = -5; const NYB_E_INVALID_HANDLE: i32 = -8; // ===== Method IDs ===== -const METHOD_BIRTH: u32 = 0; // constructor -const METHOD_INC: u32 = 1; // increments and returns new count -const METHOD_GET: u32 = 2; // returns current count -const METHOD_FINI: u32 = u32::MAX; // destructor +const METHOD_BIRTH: u32 = 0; // constructor +const METHOD_INC: u32 = 1; // increments and returns new count +const METHOD_GET: u32 = 2; // returns current count +const METHOD_FINI: u32 = u32::MAX; // destructor // Assign a unique type_id for CounterBox (distinct from FileBox=6) const TYPE_ID_COUNTER: u32 = 7; // ===== Instance state ===== -struct CounterInstance { count: i32 } +struct CounterInstance { + count: i32, +} -static INSTANCES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static INSTANCES: Lazy>> = + 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + let mut buf: Vec = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); 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 } - diff --git a/plugins/nyash-egui-plugin/src/lib.rs b/plugins/nyash-egui-plugin/src/lib.rs index eaba1997..d48d2774 100644 --- a/plugins/nyash-egui-plugin/src/lib.rs +++ b/plugins/nyash-egui-plugin/src/lib.rs @@ -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; @@ -18,12 +24,12 @@ const TID_EGUI: u32 = 70; // match nyash.toml [box_types] // methods const M_BIRTH: u32 = 0; -const M_OPEN: u32 = 1; // open(width:int, height:int, title:str) -const M_UI_LABEL: u32 = 2; // uiLabel(text:str) -const M_UI_BUTTON: u32 = 3; // uiButton(text:str) -> future: events +const M_OPEN: u32 = 1; // open(width:int, height:int, title:str) +const M_UI_LABEL: u32 = 2; // uiLabel(text:str) +const M_UI_BUTTON: u32 = 3; // uiButton(text:str) -> future: events const M_POLL_EVENT: u32 = 4; // pollEvent() -> Result.Ok(text) / Result.Err("none") -const M_RUN: u32 = 5; // run() -> enters loop or no-op -const M_CLOSE: u32 = 6; // close() +const M_RUN: u32 = 5; // run() -> enters loop or no-op +const M_CLOSE: u32 = 6; // close() const M_FINI: u32 = u32::MAX; #[derive(Default)] @@ -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; } + 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 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + if result_len.is_null() { + return E_ARGS; + } + let mut buf: Vec = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); 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 { - 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 { - 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) { 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::().ok()); + let scale_override = std::env::var("NYASH_EGUI_SCALE") + .ok() + .and_then(|s| s.parse::().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, diag: bool, printed: bool, init_w: i32, init_h: i32, scale: Option } + struct App { + labels: Vec, + diag: bool, + printed: bool, + init_w: i32, + init_h: i32, + scale: Option, + } 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, + }) } }), ); diff --git a/plugins/nyash-encoding-plugin/src/lib.rs b/plugins/nyash-encoding-plugin/src/lib.rs index 032f1376..a14ea3cd 100644 --- a/plugins/nyash-encoding-plugin/src/lib.rs +++ b/plugins/nyash-encoding-plugin/src/lib.rs @@ -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; @@ -12,13 +15,13 @@ const E_ARGS: i32 = -4; const E_PLUGIN: i32 = -5; const E_HANDLE: i32 = -8; -const M_BIRTH: u32 = 0; // constructor (stateless) -const M_TO_UTF8_BYTES: u32 = 1; // toUtf8Bytes(s) -> bytes +const M_BIRTH: u32 = 0; // constructor (stateless) +const M_TO_UTF8_BYTES: u32 = 1; // toUtf8Bytes(s) -> bytes const M_FROM_UTF8_BYTES: u32 = 2; // fromUtf8Bytes(bytes) -> string -const M_BASE64_ENC: u32 = 3; // base64Encode(s|bytes) -> string -const M_BASE64_DEC: u32 = 4; // base64Decode(str) -> bytes -const M_HEX_ENC: u32 = 5; // hexEncode(s|bytes) -> string -const M_HEX_DEC: u32 = 6; // hexDecode(str) -> bytes +const M_BASE64_ENC: u32 = 3; // base64Encode(s|bytes) -> string +const M_BASE64_DEC: u32 = 4; // base64Decode(str) -> bytes +const M_HEX_ENC: u32 = 5; // hexEncode(s|bytes) -> string +const M_HEX_DEC: u32 = 6; // hexDecode(str) -> bytes const M_FINI: u32 = u32::MAX; // Assign an unused type id @@ -30,10 +33,14 @@ static INST: Lazy>> = 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; } + 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 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); - 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 = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); + 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 { - 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> { - 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 } diff --git a/plugins/nyash-file/src/lib.rs b/plugins/nyash-file/src/lib.rs index cae5664a..1c517e04 100644 --- a/plugins/nyash-file/src/lib.rs +++ b/plugins/nyash-file/src/lib.rs @@ -1,10 +1,10 @@ //! FileBox Dynamic Plugin -//! +//! //! C ABIを使用した動的ライブラリとしてFileBox機能を提供 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') @@ -36,7 +36,7 @@ pub extern "C" fn nyash_plugin_init() -> *const FileBoxPlugin { } /// FileBoxを開く -/// +/// /// # Safety /// - pathは有効なUTF-8のC文字列である必要がある /// - 返されたポインタはnyash_file_freeで解放する必要がある @@ -45,12 +45,12 @@ pub unsafe extern "C" fn nyash_file_open(path: *const c_char) -> *mut c_void { if path.is_null() { return std::ptr::null_mut(); } - + let path_str = match CStr::from_ptr(path).to_str() { Ok(s) => s, Err(_) => return std::ptr::null_mut(), }; - + match OpenOptions::new() .read(true) .write(true) @@ -69,7 +69,7 @@ pub unsafe extern "C" fn nyash_file_open(path: *const c_char) -> *mut c_void { } /// ファイルの内容を読み取る -/// +/// /// # Safety /// - handleはnyash_file_openから返された有効なポインタである必要がある /// - 返された文字列はnyash_string_freeで解放する必要がある @@ -78,46 +78,41 @@ pub unsafe extern "C" fn nyash_file_read(handle: *mut c_void) -> *mut c_char { if handle.is_null() { return std::ptr::null_mut(); } - + let file_box = &mut *(handle as *mut FileBoxHandle); let mut content = String::new(); - + // ファイルポインタを最初に戻す if let Err(_) = file_box.file.seek(std::io::SeekFrom::Start(0)) { return std::ptr::null_mut(); } - + match file_box.file.read_to_string(&mut content) { - Ok(_) => { - match CString::new(content) { - Ok(c_str) => c_str.into_raw(), - Err(_) => std::ptr::null_mut(), - } - } + Ok(_) => match CString::new(content) { + Ok(c_str) => c_str.into_raw(), + Err(_) => std::ptr::null_mut(), + }, Err(_) => std::ptr::null_mut(), } } /// ファイルに内容を書き込む -/// +/// /// # Safety /// - 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; } - + let file_box = &mut *(handle as *mut FileBoxHandle); let content_str = match CStr::from_ptr(content).to_str() { Ok(s) => s, Err(_) => return 0, }; - + // ファイルをクリアして最初から書き込む if let Err(_) = file_box.file.set_len(0) { return 0; @@ -125,15 +120,15 @@ pub unsafe extern "C" fn nyash_file_write( if let Err(_) = file_box.file.seek(std::io::SeekFrom::Start(0)) { return 0; } - + match file_box.file.write_all(content_str.as_bytes()) { Ok(_) => 1, // 成功 - Err(_) => 0, // 失敗 + Err(_) => 0, // 失敗 } } /// ファイルが存在するかチェック -/// +/// /// # Safety /// - pathは有効なUTF-8のC文字列である必要がある #[no_mangle] @@ -141,12 +136,12 @@ pub unsafe extern "C" fn nyash_file_exists(path: *const c_char) -> c_int { if path.is_null() { return 0; } - + let path_str = match CStr::from_ptr(path).to_str() { Ok(s) => s, Err(_) => return 0, }; - + if std::path::Path::new(path_str).exists() { 1 } else { @@ -155,7 +150,7 @@ pub unsafe extern "C" fn nyash_file_exists(path: *const c_char) -> c_int { } /// FileBoxハンドルを解放 -/// +/// /// # Safety /// - handleはnyash_file_openから返された有効なポインタである必要がある /// - 解放後はhandleを使用してはいけない @@ -167,7 +162,7 @@ pub unsafe extern "C" fn nyash_file_free(handle: *mut c_void) { } /// 文字列を解放(nyash_file_readの戻り値用) -/// +/// /// # Safety /// - strはnyash_file_readから返された有効なポインタである必要がある #[no_mangle] @@ -181,7 +176,7 @@ pub unsafe extern "C" fn nyash_string_free(str: *mut c_char) { mod tests { use super::*; use std::ffi::CString; - + #[test] fn test_plugin_init() { unsafe { @@ -193,4 +188,4 @@ mod tests { assert_eq!(plugin_info.api_version, 1); } } -} \ No newline at end of file +} diff --git a/plugins/nyash-filebox-plugin/src/lib.rs b/plugins/nyash-filebox-plugin/src/lib.rs index f0380c5d..e0cdfa4c 100644 --- a/plugins/nyash-filebox-plugin/src/lib.rs +++ b/plugins/nyash-filebox-plugin/src/lib.rs @@ -1,12 +1,15 @@ //! Nyash FileBox Plugin - BID-FFI v1 Implementation -//! +//! //! Provides file I/O operations as a Nyash plugin 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 ============ @@ -37,7 +40,7 @@ const NYB_E_PLUGIN_ERROR: i32 = -5; const NYB_E_INVALID_HANDLE: i32 = -8; // ============ Method IDs ============ -const METHOD_BIRTH: u32 = 0; // Constructor +const METHOD_BIRTH: u32 = 0; // Constructor const METHOD_OPEN: u32 = 1; const METHOD_READ: u32 = 2; const METHOD_WRITE: u32 = 3; @@ -45,20 +48,19 @@ const METHOD_CLOSE: u32 = 4; const METHOD_EXISTS: u32 = 5; const METHOD_COPY_FROM: u32 = 7; // New: copyFrom(other: Handle) const METHOD_CLONE_SELF: u32 = 8; // New: cloneSelf() -> Handle -const METHOD_FINI: u32 = u32::MAX; // Destructor +const METHOD_FINI: u32 = u32::MAX; // Destructor // ============ FileBox Instance ============ struct FileBoxInstance { file: Option, path: String, - buffer: Option>, // プラグインが管理するバッファ + buffer: Option>, // プラグインが管理するバッファ } // グローバルインスタンス管理(実際の実装ではより安全な方法を使用) use once_cell::sync::Lazy; -static INSTANCES: Lazy>> = Lazy::new(|| { - Mutex::new(HashMap::new()) -}); +static INSTANCES: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); // ホスト関数テーブルは使用しない(Host VTable廃止) @@ -70,7 +72,7 @@ static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1); /// ABI version #[no_mangle] pub extern "C" fn nyash_plugin_abi() -> u32 { - 1 // BID-1 support + 1 // BID-1 support } /// Plugin initialization (optional - global setup) @@ -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 { - file: None, - path: String::new(), - buffer: None, - }); + 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(mut file) => { - let mut buf = Vec::new(); - if let Err(_) = file.read_to_end(&mut buf) { return NYB_E_PLUGIN_ERROR; } - let need = 8usize.saturating_add(buf.len()); - if preflight(_result, _result_len, need) { return NYB_E_SHORT_BUFFER; } - return write_tlv_bytes(&buf, _result, _result_len); + 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; } - Err(_) => return NYB_E_PLUGIN_ERROR, + let need = 8usize.saturating_add(buf.len()); + if preflight(_result, _result_len, need) { + return NYB_E_SHORT_BUFFER; + } + return write_tlv_bytes(&buf, _result, _result_len); } - } + Err(_) => return NYB_E_PLUGIN_ERROR, + }, Err(_) => return NYB_E_INVALID_ARGS, } } else { @@ -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,50 +232,68 @@ 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) { inst.file = None; return write_tlv_void(_result, _result_len); - } else { - return NYB_E_PLUGIN_ERROR; + } else { + return NYB_E_PLUGIN_ERROR; } - } else { - return NYB_E_PLUGIN_ERROR; + } else { + return NYB_E_PLUGIN_ERROR; } } METHOD_COPY_FROM => { @@ -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 = 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 { 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + let mut buf: Vec = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); 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 { - 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 { 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, ()> { 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 { 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, 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, 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))) } diff --git a/plugins/nyash-integer-plugin/src/lib.rs b/plugins/nyash-integer-plugin/src/lib.rs index c310e30a..6fc939e2 100644 --- a/plugins/nyash-integer-plugin/src/lib.rs +++ b/plugins/nyash-integer-plugin/src/lib.rs @@ -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; @@ -18,23 +21,29 @@ const E_HANDLE: i32 = -8; // Methods const M_BIRTH: u32 = 0; -const M_GET: u32 = 1; -const M_SET: u32 = 2; -const M_FINI: u32 = u32::MAX; +const M_GET: u32 = 1; +const M_SET: u32 = 2; +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>> = 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + if result_len.is_null() { + return E_ARGS; + } + let mut buf: Vec = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); 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 { - 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, } } diff --git a/plugins/nyash-map-plugin/src/lib.rs b/plugins/nyash-map-plugin/src/lib.rs index 532c87d9..4446e5e2 100644 --- a/plugins/nyash-map-plugin/src/lib.rs +++ b/plugins/nyash-map-plugin/src/lib.rs @@ -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; @@ -19,37 +22,41 @@ const NYB_E_INVALID_HANDLE: i32 = -8; // Methods const METHOD_BIRTH: u32 = 0; -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_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_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_FINI: u32 = u32::MAX; +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) const METHOD_GET_STR: u32 = 11; // getS(name: string) -> i64 const METHOD_HAS_STR: u32 = 12; // hasS(name: string) -> bool const METHOD_VALUES_S: u32 = 13; // valuesStr() -> string (newline-joined) -const METHOD_TO_JSON: u32 = 14; // toJson() -> string +const METHOD_TO_JSON: u32 = 14; // toJson() -> string // Type id (nyash.toml に合わせる) const TYPE_ID_MAP: u32 = 11; struct MapInstance { - data_i64: HashMap, - data_str: HashMap, + data_i64: HashMap, + data_str: HashMap, } static INSTANCES: Lazy>> = 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 = 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 = + 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 = 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 = + 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 = 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 = + 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 = 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 = + 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + let mut buf: Vec = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); 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 { - 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 { } fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option { - 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; } diff --git a/plugins/nyash-math-plugin/src/lib.rs b/plugins/nyash-math-plugin/src/lib.rs index 2038996a..7b89167f 100644 --- a/plugins/nyash-math-plugin/src/lib.rs +++ b/plugins/nyash-math-plugin/src/lib.rs @@ -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 { 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, 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, 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) { 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 { + 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, 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, 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) { + 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(tid: u32, map: &Lazy>>, 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( + tid: u32, + map: &Lazy>>, + 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(map: &Lazy>>, instance_id: u32) -> i32 { - if let Ok(mut m) = map.lock() { m.remove(&instance_id); OK } else { E_FAIL } +unsafe fn fini(map: &Lazy>>, 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; } } diff --git a/plugins/nyash-math/src/lib.rs b/plugins/nyash-math/src/lib.rs index b1c7a350..31103d0f 100644 --- a/plugins/nyash-math/src/lib.rs +++ b/plugins/nyash-math/src/lib.rs @@ -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 { @@ -185,7 +185,7 @@ pub extern "C" fn nyash_time_parse(time_str: *const c_char) -> *mut c_void { if time_str.is_null() { return ptr::null_mut(); } - + unsafe { let c_str = CStr::from_ptr(time_str); if let Ok(rust_str) = c_str.to_str() { @@ -196,7 +196,7 @@ pub extern "C" fn nyash_time_parse(time_str: *const c_char) -> *mut c_void { } } } - + ptr::null_mut() } @@ -217,11 +217,11 @@ pub extern "C" fn nyash_datetime_to_string(ptr: *mut c_void) -> *mut c_char { if ptr.is_null() { return ptr::null_mut(); } - + unsafe { let datetime_box = &*(ptr as *mut DateTimeBox); let datetime_str = datetime_box.datetime.to_rfc3339(); - + if let Ok(c_string) = CString::new(datetime_str) { c_string.into_raw() } else { @@ -325,4 +325,4 @@ pub extern "C" fn nyash_string_free(ptr: *mut c_char) { unsafe { let _ = CString::from_raw(ptr); } -} \ No newline at end of file +} diff --git a/plugins/nyash-net-plugin/src/lib.rs b/plugins/nyash-net-plugin/src/lib.rs index e11c985a..fa2603ee 100644 --- a/plugins/nyash-net-plugin/src/lib.rs +++ b/plugins/nyash-net-plugin/src/lib.rs @@ -4,23 +4,34 @@ use once_cell::sync::Lazy; use std::collections::{HashMap, VecDeque}; -use std::sync::{Mutex, Arc, atomic::{AtomicBool, AtomicU32, Ordering}}; -use std::net::{TcpListener, TcpStream}; -use std::io::{Read, Write}; -use std::time::Duration; use std::io::Write as IoWrite; +use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; +use std::sync::{ + atomic::{AtomicBool, AtomicU32, Ordering}, + Arc, Mutex, +}; +use std::time::Duration; // ===== Simple logger (enabled when NYASH_NET_LOG=1) ===== static LOG_ON: Lazy = Lazy::new(|| std::env::var("NYASH_NET_LOG").unwrap_or_default() == "1"); -static LOG_PATH: Lazy = Lazy::new(|| std::env::var("NYASH_NET_LOG_FILE").unwrap_or_else(|_| "net_plugin.log".to_string())); +static LOG_PATH: Lazy = Lazy::new(|| { + std::env::var("NYASH_NET_LOG_FILE").unwrap_or_else(|_| "net_plugin.log".to_string()) +}); static LOG_MTX: Lazy> = Lazy::new(|| Mutex::new(())); fn net_log(msg: &str) { - if !*LOG_ON { return; } + if !*LOG_ON { + return; + } // Always mirror to stderr for visibility eprintln!("[net] {}", msg); let _g = LOG_MTX.lock().unwrap(); - if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&*LOG_PATH) { + if let Ok(mut f) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&*LOG_PATH) + { let _ = writeln!(f, "[{:?}] {}", std::time::SystemTime::now(), msg); } } @@ -57,21 +68,21 @@ const M_SERVER_STOP: u32 = 2; const M_SERVER_ACCEPT: u32 = 3; // -> Handle(Request) // Request -const M_REQ_PATH: u32 = 1; // -> String +const M_REQ_PATH: u32 = 1; // -> String const M_REQ_READ_BODY: u32 = 2; // -> Bytes (optional) -const M_REQ_RESPOND: u32 = 3; // arg: Handle(Response) +const M_REQ_RESPOND: u32 = 3; // arg: Handle(Response) // Response const M_RESP_SET_STATUS: u32 = 1; // arg: i32 const M_RESP_SET_HEADER: u32 = 2; // args: name, value (string) -const M_RESP_WRITE: u32 = 3; // arg: bytes/string -const M_RESP_READ_BODY: u32 = 4; // -> Bytes +const M_RESP_WRITE: u32 = 3; // arg: bytes/string +const M_RESP_READ_BODY: u32 = 4; // -> Bytes const M_RESP_GET_STATUS: u32 = 5; // -> i32 const M_RESP_GET_HEADER: u32 = 6; // arg: name -> string (or empty) // Client -const M_CLIENT_GET: u32 = 1; // arg: url -> Handle(Response) -const M_CLIENT_POST: u32 = 2; // args: url, body(bytes/string) -> Handle(Response) +const M_CLIENT_GET: u32 = 1; // arg: url -> Handle(Response) +const M_CLIENT_POST: u32 = 2; // args: url, body(bytes/string) -> Handle(Response) // Socket Server const M_SRV_BIRTH: u32 = 0; @@ -92,12 +103,14 @@ const M_CONN_CLOSE: u32 = 3; // -> void const M_CONN_RECV_TIMEOUT: u32 = 4; // ms -> bytes (empty if timeout) // Global State -static SERVER_INSTANCES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static SERVER_INSTANCES: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); static SERVER_START_SEQ: AtomicU32 = AtomicU32::new(1); static ACTIVE_SERVER_ID: Lazy>> = Lazy::new(|| Mutex::new(None)); static LAST_ACCEPTED_REQ: Lazy>> = Lazy::new(|| Mutex::new(None)); static REQUESTS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); -static RESPONSES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static RESPONSES: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); static CLIENTS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); static SERVER_ID: AtomicU32 = AtomicU32::new(1); @@ -150,16 +163,22 @@ struct SockConnState { struct SockClientState; #[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 { +pub extern "C" fn nyash_plugin_init() -> i32 { // Force initialize logging let _ = *LOG_ON; let _ = &*LOG_PATH; - netlog!("Net plugin initialized, LOG_ON={}, LOG_PATH={}", *LOG_ON, *LOG_PATH); + netlog!( + "Net plugin initialized, LOG_ON={}, LOG_PATH={}", + *LOG_ON, + *LOG_PATH + ); eprintln!("Net plugin: LOG_ON={}, LOG_PATH={}", *LOG_ON, *LOG_PATH); - OK + OK } #[no_mangle] @@ -176,71 +195,117 @@ pub extern "C" fn nyash_plugin_invoke( match type_id { T_SERVER => server_invoke(method_id, instance_id, args, args_len, result, result_len), T_REQUEST => request_invoke(method_id, instance_id, args, args_len, result, result_len), - T_RESPONSE => response_invoke(method_id, instance_id, args, args_len, result, result_len), + T_RESPONSE => { + response_invoke(method_id, instance_id, args, args_len, result, result_len) + } T_CLIENT => client_invoke(method_id, instance_id, args, args_len, result, result_len), - T_SOCK_SERVER => sock_server_invoke(method_id, instance_id, args, args_len, result, result_len), - T_SOCK_CLIENT => sock_client_invoke(method_id, instance_id, args, args_len, result, result_len), - T_SOCK_CONN => sock_conn_invoke(method_id, instance_id, args, args_len, result, result_len), + T_SOCK_SERVER => { + sock_server_invoke(method_id, instance_id, args, args_len, result, result_len) + } + T_SOCK_CLIENT => { + sock_client_invoke(method_id, instance_id, args, args_len, result, result_len) + } + T_SOCK_CONN => { + sock_conn_invoke(method_id, instance_id, args, args_len, result, result_len) + } _ => E_INV_TYPE, } } } -unsafe fn server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: *mut u8, res_len: *mut usize) -> i32 { +unsafe fn server_invoke( + m: u32, + id: u32, + args: *const u8, + args_len: usize, + res: *mut u8, + res_len: *mut usize, +) -> i32 { match m { M_BIRTH => { let id = SERVER_ID.fetch_add(1, Ordering::Relaxed); - SERVER_INSTANCES.lock().unwrap().insert(id, ServerState { - running: Arc::new(AtomicBool::new(false)), - port: 0, - pending: Arc::new(Mutex::new(VecDeque::new())), - handle: Mutex::new(None), - start_seq: 0, - }); + SERVER_INSTANCES.lock().unwrap().insert( + id, + ServerState { + running: Arc::new(AtomicBool::new(false)), + port: 0, + pending: Arc::new(Mutex::new(VecDeque::new())), + handle: Mutex::new(None), + start_seq: 0, + }, + ); write_u32(id, res, res_len) } M_SERVER_START => { // args: TLV string/int (port) let port = tlv_parse_i32(slice(args, args_len)).unwrap_or(0); if let Some(s) = SERVER_INSTANCES.lock().unwrap().get_mut(&id) { - s.port = port; s.start_seq = SERVER_START_SEQ.fetch_add(1, Ordering::Relaxed); + s.port = port; + s.start_seq = SERVER_START_SEQ.fetch_add(1, Ordering::Relaxed); let running = s.running.clone(); let pending = s.pending.clone(); running.store(true, Ordering::SeqCst); // Bind listener synchronously to avoid race with client connect let addr = format!("127.0.0.1:{}", port); let listener = match TcpListener::bind(&addr) { - Ok(l) => { netlog!("http:listener bound {}", addr); l }, - Err(e) => { netlog!("http:bind error {} err={:?}", addr, e); running.store(false, Ordering::SeqCst); return write_tlv_void(res, res_len); } + Ok(l) => { + netlog!("http:listener bound {}", addr); + l + } + Err(e) => { + netlog!("http:bind error {} err={:?}", addr, e); + running.store(false, Ordering::SeqCst); + return write_tlv_void(res, res_len); + } }; // Spawn HTTP listener thread (real TCP) let server_id_copy = id; let handle = std::thread::spawn(move || { let _ = listener.set_nonblocking(true); loop { - if !running.load(Ordering::SeqCst) { break; } + if !running.load(Ordering::SeqCst) { + break; + } match listener.accept() { - Ok((mut stream, _)) => { - // Parse minimal HTTP request (GET/POST) - let _ = stream.set_read_timeout(Some(Duration::from_millis(2000))); - if let Some((path, body, resp_hint)) = read_http_request(&mut stream) { - // Store stream for later respond() - let conn_id = SOCK_CONN_ID.fetch_add(1, Ordering::Relaxed); - SOCK_CONNS.lock().unwrap().insert(conn_id, SockConnState { stream: Mutex::new(stream) }); + Ok((mut stream, _)) => { + // Parse minimal HTTP request (GET/POST) + let _ = stream.set_read_timeout(Some(Duration::from_millis(2000))); + if let Some((path, body, resp_hint)) = + read_http_request(&mut stream) + { + // Store stream for later respond() + let conn_id = SOCK_CONN_ID.fetch_add(1, Ordering::Relaxed); + SOCK_CONNS.lock().unwrap().insert( + conn_id, + SockConnState { + stream: Mutex::new(stream), + }, + ); - let req_id = REQUEST_ID.fetch_add(1, Ordering::Relaxed); - REQUESTS.lock().unwrap().insert(req_id, RequestState { path, body, response_id: resp_hint, server_conn_id: Some(conn_id), responded: false }); - if let Some(h) = resp_hint { netlog!("http:accept linked resp_id hint={} for req_id={} conn_id={}", h, req_id, conn_id); } - pending.lock().unwrap().push_back(req_id); - } else { - // Malformed; drop connection + let req_id = REQUEST_ID.fetch_add(1, Ordering::Relaxed); + REQUESTS.lock().unwrap().insert( + req_id, + RequestState { + path, + body, + response_id: resp_hint, + server_conn_id: Some(conn_id), + responded: false, + }, + ); + if let Some(h) = resp_hint { + netlog!("http:accept linked resp_id hint={} for req_id={} conn_id={}", h, req_id, conn_id); } - } - Err(_) => { - std::thread::sleep(Duration::from_millis(10)); + pending.lock().unwrap().push_back(req_id); + } else { + // Malformed; drop connection } } + Err(_) => { + std::thread::sleep(Duration::from_millis(10)); + } } + } }); *s.handle.lock().unwrap() = Some(handle); } @@ -251,11 +316,15 @@ unsafe fn server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: M_SERVER_STOP => { if let Some(s) = SERVER_INSTANCES.lock().unwrap().get_mut(&id) { s.running.store(false, Ordering::SeqCst); - if let Some(h) = s.handle.lock().unwrap().take() { let _ = h.join(); } + if let Some(h) = s.handle.lock().unwrap().take() { + let _ = h.join(); + } } // clear active if this server was active let mut active = ACTIVE_SERVER_ID.lock().unwrap(); - if active.map(|v| v == id).unwrap_or(false) { *active = None; } + if active.map(|v| v == id).unwrap_or(false) { + *active = None; + } write_tlv_void(res, res_len) } M_SERVER_ACCEPT => { @@ -271,12 +340,21 @@ unsafe fn server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: for i in 0..q.len() { if let Some(rid) = q.get(i).copied() { if let Some(rq) = REQUESTS.lock().unwrap().get(&rid) { - if rq.server_conn_id.is_some() { chosen = Some(i); break; } + if rq.server_conn_id.is_some() { + chosen = Some(i); + break; + } } } } - if let Some(idx) = chosen { q.remove(idx) } else { q.pop_front() } - } else { None } + if let Some(idx) = chosen { + q.remove(idx) + } else { + q.pop_front() + } + } else { + None + } } { netlog!("server.accept: return req_id={} srv_id={}", req_id, id); *LAST_ACCEPTED_REQ.lock().unwrap() = Some(req_id); @@ -290,27 +368,52 @@ unsafe fn server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: } } -unsafe fn request_invoke(m: u32, id: u32, _args: *const u8, _args_len: usize, res: *mut u8, res_len: *mut usize) -> i32 { +unsafe fn request_invoke( + m: u32, + id: u32, + _args: *const u8, + _args_len: usize, + res: *mut u8, + res_len: *mut usize, +) -> i32 { match m { M_BIRTH => { let id = REQUEST_ID.fetch_add(1, Ordering::Relaxed); - REQUESTS.lock().unwrap().insert(id, RequestState { path: String::new(), body: vec![], response_id: None, server_conn_id: None, responded: false }); + REQUESTS.lock().unwrap().insert( + id, + RequestState { + path: String::new(), + body: vec![], + response_id: None, + server_conn_id: None, + responded: false, + }, + ); write_u32(id, res, res_len) } M_REQ_PATH => { if let Some(rq) = REQUESTS.lock().unwrap().get(&id) { write_tlv_string(&rq.path, res, res_len) - } else { E_INV_HANDLE } + } else { + E_INV_HANDLE + } } M_REQ_READ_BODY => { if let Some(rq) = REQUESTS.lock().unwrap().get(&id) { write_tlv_bytes(&rq.body, res, res_len) - } else { E_INV_HANDLE } + } else { + E_INV_HANDLE + } } M_REQ_RESPOND => { // args: TLV Handle(Response) - let (t, provided_resp_id) = tlv_parse_handle(slice(_args, _args_len)).map_err(|_| ()).or(Err(())).unwrap_or((0,0)); - if t != T_RESPONSE { return E_INV_ARGS; } + let (t, provided_resp_id) = tlv_parse_handle(slice(_args, _args_len)) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or((0, 0)); + if t != T_RESPONSE { + return E_INV_ARGS; + } // Acquire request let mut rq_map = REQUESTS.lock().unwrap(); if let Some(rq) = rq_map.get_mut(&id) { @@ -325,33 +428,61 @@ unsafe fn request_invoke(m: u32, id: u32, _args: *const u8, _args_len: usize, re let (status, headers, body) = { let resp_map = RESPONSES.lock().unwrap(); if let Some(src) = resp_map.get(&provided_resp_id) { - netlog!("Request.respond: Reading response id={}, status={}, body_len={}", provided_resp_id, src.status, src.body.len()); + netlog!( + "Request.respond: Reading response id={}, status={}, body_len={}", + provided_resp_id, + src.status, + src.body.len() + ); (src.status, src.headers.clone(), src.body.clone()) - } else { - netlog!("Request.respond: Response id={} not found!", provided_resp_id); - return E_INV_HANDLE + } else { + netlog!( + "Request.respond: Response id={} not found!", + provided_resp_id + ); + return E_INV_HANDLE; } }; // Build minimal HTTP/1.1 response - let reason = match status { 200 => "OK", 201 => "Created", 204 => "No Content", 400 => "Bad Request", 404 => "Not Found", 500 => "Internal Server Error", _ => "OK" }; + let reason = match status { + 200 => "OK", + 201 => "Created", + 204 => "No Content", + 400 => "Bad Request", + 404 => "Not Found", + 500 => "Internal Server Error", + _ => "OK", + }; let mut buf = Vec::new(); buf.extend_from_slice(format!("HTTP/1.1 {} {}\r\n", status, reason).as_bytes()); let mut has_len = false; - for (k,v) in &headers { - if k.eq_ignore_ascii_case("Content-Length") { has_len = true; } + for (k, v) in &headers { + if k.eq_ignore_ascii_case("Content-Length") { + has_len = true; + } buf.extend_from_slice(format!("{}: {}\r\n", k, v).as_bytes()); } - if !has_len { buf.extend_from_slice(format!("Content-Length: {}\r\n", body.len()).as_bytes()); } + if !has_len { + buf.extend_from_slice( + format!("Content-Length: {}\r\n", body.len()).as_bytes(), + ); + } buf.extend_from_slice(b"Connection: close\r\n"); buf.extend_from_slice(b"\r\n"); buf.extend_from_slice(&body); // Write and close - netlog!("Request.respond: Sending HTTP response, buf_len={}", buf.len()); + netlog!( + "Request.respond: Sending HTTP response, buf_len={}", + buf.len() + ); if let Some(conn) = SOCK_CONNS.lock().unwrap().remove(&conn_id) { - if let Ok(mut s) = conn.stream.lock() { - let _ = s.write_all(&buf); - let _ = s.flush(); - netlog!("Request.respond: HTTP response sent to socket conn_id={}", conn_id); + if let Ok(mut s) = conn.stream.lock() { + let _ = s.write_all(&buf); + let _ = s.flush(); + netlog!( + "Request.respond: HTTP response sent to socket conn_id={}", + conn_id + ); } } else { netlog!("Request.respond: Socket conn_id={} not found!", conn_id); @@ -362,7 +493,13 @@ unsafe fn request_invoke(m: u32, id: u32, _args: *const u8, _args_len: usize, re rq_map2.get(&id).and_then(|rq2| rq2.response_id) } { let mut resp_map = RESPONSES.lock().unwrap(); - let dst = resp_map.entry(target_id).or_insert(ResponseState { status: 200, headers: HashMap::new(), body: vec![], client_conn_id: None, parsed: true }); + let dst = resp_map.entry(target_id).or_insert(ResponseState { + status: 200, + headers: HashMap::new(), + body: vec![], + client_conn_id: None, + parsed: true, + }); dst.status = status; dst.headers = headers.clone(); dst.body = body.clone(); @@ -371,7 +508,9 @@ unsafe fn request_invoke(m: u32, id: u32, _args: *const u8, _args_len: usize, re // mark responded { let mut rq_map3 = REQUESTS.lock().unwrap(); - if let Some(rq3) = rq_map3.get_mut(&id) { rq3.responded = true; } + if let Some(rq3) = rq_map3.get_mut(&id) { + rq3.responded = true; + } } return write_tlv_void(res, res_len); } @@ -381,12 +520,30 @@ unsafe fn request_invoke(m: u32, id: u32, _args: *const u8, _args_len: usize, re let candidate_req = { if let Some(last_id) = *LAST_ACCEPTED_REQ.lock().unwrap() { if let Some(r) = REQUESTS.lock().unwrap().get(&last_id) { - if r.server_conn_id.is_some() && !r.responded { Some(last_id) } else { None } - } else { None } - } else { None } - }.or_else(|| { - REQUESTS.lock().unwrap().iter() - .filter_map(|(rid, rqs)| if rqs.server_conn_id.is_some() && !rqs.responded { Some(*rid) } else { None }) + if r.server_conn_id.is_some() && !r.responded { + Some(last_id) + } else { + None + } + } else { + None + } + } else { + None + } + } + .or_else(|| { + REQUESTS + .lock() + .unwrap() + .iter() + .filter_map(|(rid, rqs)| { + if rqs.server_conn_id.is_some() && !rqs.responded { + Some(*rid) + } else { + None + } + }) .max() }); if let Some(target_req_id) = candidate_req { @@ -397,26 +554,65 @@ unsafe fn request_invoke(m: u32, id: u32, _args: *const u8, _args_len: usize, re }; let (status, headers, body) = { let resp_map = RESPONSES.lock().unwrap(); - if let Some(src) = resp_map.get(&provided_resp_id) { (src.status, src.headers.clone(), src.body.clone()) } else { return E_INV_HANDLE } + if let Some(src) = resp_map.get(&provided_resp_id) { + (src.status, src.headers.clone(), src.body.clone()) + } else { + return E_INV_HANDLE; + } + }; + let reason = match status { + 200 => "OK", + 201 => "Created", + 204 => "No Content", + 400 => "Bad Request", + 404 => "Not Found", + 500 => "Internal Server Error", + _ => "OK", }; - let reason = match status { 200 => "OK", 201 => "Created", 204 => "No Content", 400 => "Bad Request", 404 => "Not Found", 500 => "Internal Server Error", _ => "OK" }; let mut buf = Vec::new(); buf.extend_from_slice(format!("HTTP/1.1 {} {}\r\n", status, reason).as_bytes()); let mut has_len = false; - for (k,v) in &headers { if k.eq_ignore_ascii_case("Content-Length") { has_len = true; } buf.extend_from_slice(format!("{}: {}\r\n", k, v).as_bytes()); } - if !has_len { buf.extend_from_slice(format!("Content-Length: {}\r\n", body.len()).as_bytes()); } - buf.extend_from_slice(b"Connection: close\r\n\r\n"); buf.extend_from_slice(&body); - netlog!("Request.respond: reroute TCP send via req_id={} conn_id={}", target_req_id, conn_id_alt); + for (k, v) in &headers { + if k.eq_ignore_ascii_case("Content-Length") { + has_len = true; + } + buf.extend_from_slice(format!("{}: {}\r\n", k, v).as_bytes()); + } + if !has_len { + buf.extend_from_slice( + format!("Content-Length: {}\r\n", body.len()).as_bytes(), + ); + } + buf.extend_from_slice(b"Connection: close\r\n\r\n"); + buf.extend_from_slice(&body); + netlog!( + "Request.respond: reroute TCP send via req_id={} conn_id={}", + target_req_id, + conn_id_alt + ); if let Some(conn) = SOCK_CONNS.lock().unwrap().remove(&conn_id_alt) { - if let Ok(mut s) = conn.stream.lock() { let _ = s.write_all(&buf); let _ = s.flush(); } + if let Ok(mut s) = conn.stream.lock() { + let _ = s.write_all(&buf); + let _ = s.flush(); + } } if let Some(target_id) = resp_hint_alt { let mut resp_map = RESPONSES.lock().unwrap(); - let dst = resp_map.entry(target_id).or_insert(ResponseState { status: 200, headers: HashMap::new(), body: vec![], client_conn_id: None, parsed: true }); - dst.status = status; dst.headers = headers.clone(); dst.body = body.clone(); + let dst = resp_map.entry(target_id).or_insert(ResponseState { + status: 200, + headers: HashMap::new(), + body: vec![], + client_conn_id: None, + parsed: true, + }); + dst.status = status; + dst.headers = headers.clone(); + dst.body = body.clone(); netlog!("Request.respond: mirrored client handle id={} body_len={} headers={} status={}", target_id, dst.body.len(), dst.headers.len(), dst.status); } - if let Some(rq4) = REQUESTS.lock().unwrap().get_mut(&target_req_id) { rq4.responded = true; } + if let Some(rq4) = REQUESTS.lock().unwrap().get_mut(&target_req_id) { + rq4.responded = true; + } return write_tlv_void(res, res_len); } netlog!("Request.respond: no suitable TCP-backed request found for reroute; invalid handle"); @@ -428,22 +624,42 @@ unsafe fn request_invoke(m: u32, id: u32, _args: *const u8, _args_len: usize, re } } -unsafe fn response_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: *mut u8, res_len: *mut usize) -> i32 { +unsafe fn response_invoke( + m: u32, + id: u32, + args: *const u8, + args_len: usize, + res: *mut u8, + res_len: *mut usize, +) -> i32 { match m { M_BIRTH => { let id = RESPONSE_ID.fetch_add(1, Ordering::Relaxed); - RESPONSES.lock().unwrap().insert(id, ResponseState { status: 200, headers: HashMap::new(), body: vec![], client_conn_id: None, parsed: false }); + RESPONSES.lock().unwrap().insert( + id, + ResponseState { + status: 200, + headers: HashMap::new(), + body: vec![], + client_conn_id: None, + parsed: false, + }, + ); netlog!("Response.birth: new id={}", id); write_u32(id, res, res_len) } M_RESP_SET_STATUS => { let code = tlv_parse_i32(slice(args, args_len)).unwrap_or(200); - if let Some(rp) = RESPONSES.lock().unwrap().get_mut(&id) { rp.status = code; } + if let Some(rp) = RESPONSES.lock().unwrap().get_mut(&id) { + rp.status = code; + } write_tlv_void(res, res_len) } M_RESP_SET_HEADER => { if let Ok((name, value)) = tlv_parse_two_strings(slice(args, args_len)) { - if let Some(rp) = RESPONSES.lock().unwrap().get_mut(&id) { rp.headers.insert(name, value); } + if let Some(rp) = RESPONSES.lock().unwrap().get_mut(&id) { + rp.headers.insert(name, value); + } return write_tlv_void(res, res_len); } E_INV_ARGS @@ -452,8 +668,8 @@ unsafe fn response_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res // Accept String or Bytes let bytes = tlv_parse_bytes(slice(args, args_len)).unwrap_or_default(); netlog!("HttpResponse.write: id={} bytes_len={}", id, bytes.len()); - if let Some(rp) = RESPONSES.lock().unwrap().get_mut(&id) { - rp.body.extend_from_slice(&bytes); + if let Some(rp) = RESPONSES.lock().unwrap().get_mut(&id) { + rp.body.extend_from_slice(&bytes); netlog!("HttpResponse.write: body now has {} bytes", rp.body.len()); } write_tlv_void(res, res_len) @@ -465,31 +681,49 @@ unsafe fn response_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res let need_parse = { if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { rp.client_conn_id - } else { return E_INV_HANDLE; } + } else { + return E_INV_HANDLE; + } }; if let Some(conn_id) = need_parse { parse_client_response_into(id, conn_id); std::thread::sleep(Duration::from_millis(5)); - } else { break; } + } else { + break; + } + } + if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { + netlog!( + "HttpResponse.readBody: id={} body_len={}", + id, + rp.body.len() + ); + write_tlv_bytes(&rp.body, res, res_len) + } else { + E_INV_HANDLE } - if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { - netlog!("HttpResponse.readBody: id={} body_len={}", id, rp.body.len()); - write_tlv_bytes(&rp.body, res, res_len) - } else { E_INV_HANDLE } } M_RESP_GET_STATUS => { for _ in 0..50 { let need_parse = { if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { rp.client_conn_id - } else { return E_INV_HANDLE; } + } else { + return E_INV_HANDLE; + } }; if let Some(conn_id) = need_parse { parse_client_response_into(id, conn_id); std::thread::sleep(Duration::from_millis(5)); - } else { break; } + } else { + break; + } + } + if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { + write_tlv_i32(rp.status, res, res_len) + } else { + E_INV_HANDLE } - if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { write_tlv_i32(rp.status, res, res_len) } else { E_INV_HANDLE } } M_RESP_GET_HEADER => { if let Ok(name) = tlv_parse_string(slice(args, args_len)) { @@ -497,17 +731,23 @@ unsafe fn response_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res let need_parse = { if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { rp.client_conn_id - } else { return E_INV_HANDLE; } + } else { + return E_INV_HANDLE; + } }; if let Some(conn_id) = need_parse { parse_client_response_into(id, conn_id); std::thread::sleep(Duration::from_millis(5)); - } else { break; } + } else { + break; + } } if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { let v = rp.headers.get(&name).cloned().unwrap_or_default(); return write_tlv_string(&v, res, res_len); - } else { return E_INV_HANDLE; } + } else { + return E_INV_HANDLE; + } } E_INV_ARGS } @@ -515,7 +755,14 @@ unsafe fn response_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res } } -unsafe fn client_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: *mut u8, res_len: *mut usize) -> i32 { +unsafe fn client_invoke( + m: u32, + id: u32, + args: *const u8, + args_len: usize, + res: *mut u8, + res_len: *mut usize, +) -> i32 { match m { M_BIRTH => { let id = CLIENT_ID.fetch_add(1, Ordering::Relaxed); @@ -537,21 +784,55 @@ unsafe fn client_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: let _ = stream.write_all(&req_bytes); let _ = stream.flush(); let conn_id = SOCK_CONN_ID.fetch_add(1, Ordering::Relaxed); - SOCK_CONNS.lock().unwrap().insert(conn_id, SockConnState { stream: Mutex::new(stream) }); + SOCK_CONNS.lock().unwrap().insert( + conn_id, + SockConnState { + stream: Mutex::new(stream), + }, + ); // Map to server_id by port if available let server_id_for_port = { let servers = SERVER_INSTANCES.lock().unwrap(); - servers.iter().find(|(_, s)| s.port == port).map(|(sid, _)| *sid) + servers + .iter() + .find(|(_, s)| s.port == port) + .map(|(sid, _)| *sid) }; - RESPONSES.lock().unwrap().insert(resp_id, ResponseState { status: 0, headers: HashMap::new(), body: vec![], client_conn_id: Some(conn_id), parsed: false }); + RESPONSES.lock().unwrap().insert( + resp_id, + ResponseState { + status: 0, + headers: HashMap::new(), + body: vec![], + client_conn_id: Some(conn_id), + parsed: false, + }, + ); tcp_ok = true; - netlog!("client.get: url={} resp_id={} tcp_ok=true conn_id={}", url, resp_id, conn_id); + netlog!( + "client.get: url={} resp_id={} tcp_ok=true conn_id={}", + url, + resp_id, + conn_id + ); } else { let server_id_for_port = { let servers = SERVER_INSTANCES.lock().unwrap(); - servers.iter().find(|(_, s)| s.port == port).map(|(sid, _)| *sid) + servers + .iter() + .find(|(_, s)| s.port == port) + .map(|(sid, _)| *sid) }; - RESPONSES.lock().unwrap().insert(resp_id, ResponseState { status: 0, headers: HashMap::new(), body: vec![], client_conn_id: None, parsed: false }); + RESPONSES.lock().unwrap().insert( + resp_id, + ResponseState { + status: 0, + headers: HashMap::new(), + body: vec![], + client_conn_id: None, + parsed: false, + }, + ); netlog!("client.get: url={} resp_id={} tcp_ok=false", url, resp_id); } // No stub enqueue in TCP-only design @@ -559,22 +840,46 @@ unsafe fn client_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: write_tlv_handle(T_RESPONSE, resp_id, res, res_len) } else { // Encode error string; loader interprets returns_result=true methods' string payload as Err - let msg = format!("connect failed for {}:{}{}", host, port, if path.is_empty() { "" } else { &path }); + let msg = format!( + "connect failed for {}:{}{}", + host, + port, + if path.is_empty() { "" } else { &path } + ); write_tlv_string(&msg, res, res_len) } } M_CLIENT_POST => { // args: TLV String(url), Bytes body let data = slice(args, args_len); - let (_, argc, mut pos) = tlv_parse_header(data).map_err(|_| ()).or(Err(())).unwrap_or((1,0,4)); - if argc < 2 { return E_INV_ARGS; } - let (_t1, s1, p1) = tlv_parse_entry_hdr(data, pos).map_err(|_| ()).or(Err(())).unwrap_or((0,0,0)); - if data[pos] != 6 { return E_INV_ARGS; } - let url = std::str::from_utf8(&data[p1..p1+s1]).map_err(|_| ()).or(Err(())) .unwrap_or("").to_string(); + let (_, argc, mut pos) = tlv_parse_header(data) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or((1, 0, 4)); + if argc < 2 { + return E_INV_ARGS; + } + let (_t1, s1, p1) = tlv_parse_entry_hdr(data, pos) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or((0, 0, 0)); + if data[pos] != 6 { + return E_INV_ARGS; + } + let url = std::str::from_utf8(&data[p1..p1 + s1]) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or("") + .to_string(); pos = p1 + s1; - let (t2, s2, p2) = tlv_parse_entry_hdr(data, pos).map_err(|_| ()).or(Err(())).unwrap_or((0,0,0)); - if t2 != 6 && t2 != 7 { return E_INV_ARGS; } - let body = data[p2..p2+s2].to_vec(); + let (t2, s2, p2) = tlv_parse_entry_hdr(data, pos) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or((0, 0, 0)); + if t2 != 6 && t2 != 7 { + return E_INV_ARGS; + } + let body = data[p2..p2 + s2].to_vec(); let port = parse_port(&url).unwrap_or(80); let host = parse_host(&url).unwrap_or_else(|| "127.0.0.1".to_string()); let path = parse_path(&url); @@ -587,27 +892,73 @@ unsafe fn client_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: let _ = stream.write_all(&req_bytes); let _ = stream.flush(); let conn_id = SOCK_CONN_ID.fetch_add(1, Ordering::Relaxed); - SOCK_CONNS.lock().unwrap().insert(conn_id, SockConnState { stream: Mutex::new(stream) }); + SOCK_CONNS.lock().unwrap().insert( + conn_id, + SockConnState { + stream: Mutex::new(stream), + }, + ); let server_id_for_port = { let servers = SERVER_INSTANCES.lock().unwrap(); - servers.iter().find(|(_, s)| s.port == port).map(|(sid, _)| *sid) + servers + .iter() + .find(|(_, s)| s.port == port) + .map(|(sid, _)| *sid) }; - RESPONSES.lock().unwrap().insert(resp_id, ResponseState { status: 0, headers: HashMap::new(), body: vec![], client_conn_id: Some(conn_id), parsed: false }); + RESPONSES.lock().unwrap().insert( + resp_id, + ResponseState { + status: 0, + headers: HashMap::new(), + body: vec![], + client_conn_id: Some(conn_id), + parsed: false, + }, + ); tcp_ok = true; - netlog!("client.post: url={} resp_id={} tcp_ok=true conn_id={} body_len={}", url, resp_id, conn_id, body.len()); + netlog!( + "client.post: url={} resp_id={} tcp_ok=true conn_id={} body_len={}", + url, + resp_id, + conn_id, + body.len() + ); } else { let server_id_for_port = { let servers = SERVER_INSTANCES.lock().unwrap(); - servers.iter().find(|(_, s)| s.port == port).map(|(sid, _)| *sid) + servers + .iter() + .find(|(_, s)| s.port == port) + .map(|(sid, _)| *sid) }; - RESPONSES.lock().unwrap().insert(resp_id, ResponseState { status: 0, headers: HashMap::new(), body: vec![], client_conn_id: None, parsed: false }); - netlog!("client.post: url={} resp_id={} tcp_ok=false body_len={}", url, resp_id, body.len()); + RESPONSES.lock().unwrap().insert( + resp_id, + ResponseState { + status: 0, + headers: HashMap::new(), + body: vec![], + client_conn_id: None, + parsed: false, + }, + ); + netlog!( + "client.post: url={} resp_id={} tcp_ok=false body_len={}", + url, + resp_id, + body.len() + ); } // No stub enqueue in TCP-only design if tcp_ok { write_tlv_handle(T_RESPONSE, resp_id, res, res_len) } else { - let msg = format!("connect failed for {}:{}{} (body_len={})", host, port, if path.is_empty() { "" } else { &path }, body_len); + let msg = format!( + "connect failed for {}:{}{} (body_len={})", + host, + port, + if path.is_empty() { "" } else { &path }, + body_len + ); write_tlv_string(&msg, res, res_len) } } @@ -621,9 +972,11 @@ fn parse_path(url: &str) -> String { // - https://host/path -> "/path" // - /relative -> as-is // - otherwise -> "/" - if url.starts_with('/') { return url.to_string(); } + if url.starts_with('/') { + return url.to_string(); + } if let Some(scheme_pos) = url.find("//") { - let after_scheme = &url[scheme_pos+2..]; + let after_scheme = &url[scheme_pos + 2..]; if let Some(slash) = after_scheme.find('/') { return after_scheme[slash..].to_string(); } else { @@ -638,7 +991,7 @@ fn parse_port(url: &str) -> Option { if let Some(pat) = url.split("//").nth(1) { if let Some(after_host) = pat.split('/').next() { if let Some(colon) = after_host.rfind(':') { - return after_host[colon+1..].parse::().ok(); + return after_host[colon + 1..].parse::().ok(); } } } @@ -646,12 +999,19 @@ fn parse_port(url: &str) -> Option { } // ===== Helpers ===== -unsafe fn slice<'a>(p: *const u8, len: usize) -> &'a [u8] { std::slice::from_raw_parts(p, len) } +unsafe fn slice<'a>(p: *const u8, len: usize) -> &'a [u8] { + std::slice::from_raw_parts(p, len) +} fn write_u32(v: u32, res: *mut u8, res_len: *mut usize) -> i32 { unsafe { - if res_len.is_null() { return E_INV_ARGS; } - if res.is_null() || *res_len < 4 { *res_len = 4; return E_SHORT; } + if res_len.is_null() { + return E_INV_ARGS; + } + if res.is_null() || *res_len < 4 { + *res_len = 4; + return E_SHORT; + } let b = v.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), res, 4); *res_len = 4; @@ -660,77 +1020,154 @@ fn write_u32(v: u32, res: *mut u8, res_len: *mut usize) -> i32 { } fn write_tlv_result(payloads: &[(u8, &[u8])], res: *mut u8, res_len: *mut usize) -> i32 { - if res_len.is_null() { return E_INV_ARGS; } - let mut buf = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + if res_len.is_null() { + return E_INV_ARGS; + } + let mut buf = Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); for (tag, p) in payloads { - buf.push(*tag); buf.push(0); buf.extend_from_slice(&(p.len() as u16).to_le_bytes()); buf.extend_from_slice(p); + buf.push(*tag); + buf.push(0); + buf.extend_from_slice(&(p.len() as u16).to_le_bytes()); + buf.extend_from_slice(p); } unsafe { let need = buf.len(); - if res.is_null() || *res_len < need { *res_len = need; return E_SHORT; } + if res.is_null() || *res_len < need { + *res_len = need; + return E_SHORT; + } std::ptr::copy_nonoverlapping(buf.as_ptr(), res, need); *res_len = need; } OK } -fn write_tlv_void(res: *mut u8, res_len: *mut usize) -> i32 { write_tlv_result(&[(9u8, &[])], res, res_len) } -fn write_tlv_string(s: &str, res: *mut u8, res_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], res, res_len) } -fn write_tlv_bytes(b: &[u8], res: *mut u8, res_len: *mut usize) -> i32 { write_tlv_result(&[(7u8, b)], res, res_len) } -fn write_tlv_i32(v: i32, res: *mut u8, res_len: *mut usize) -> i32 { write_tlv_result(&[(2u8, &v.to_le_bytes())], res, res_len) } +fn write_tlv_void(res: *mut u8, res_len: *mut usize) -> i32 { + write_tlv_result(&[(9u8, &[])], res, res_len) +} +fn write_tlv_string(s: &str, res: *mut u8, res_len: *mut usize) -> i32 { + write_tlv_result(&[(6u8, s.as_bytes())], res, res_len) +} +fn write_tlv_bytes(b: &[u8], res: *mut u8, res_len: *mut usize) -> i32 { + write_tlv_result(&[(7u8, b)], res, res_len) +} +fn write_tlv_i32(v: i32, res: *mut u8, res_len: *mut usize) -> i32 { + write_tlv_result(&[(2u8, &v.to_le_bytes())], res, res_len) +} fn write_tlv_handle(t: u32, id: u32, res: *mut u8, res_len: *mut usize) -> i32 { - let mut payload = [0u8;8]; payload[0..4].copy_from_slice(&t.to_le_bytes()); payload[4..8].copy_from_slice(&id.to_le_bytes()); + let mut payload = [0u8; 8]; + payload[0..4].copy_from_slice(&t.to_le_bytes()); + payload[4..8].copy_from_slice(&id.to_le_bytes()); write_tlv_result(&[(8u8, &payload)], res, res_len) } -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(()); } +fn tlv_parse_header(data: &[u8]) -> Result<(u16, u16, usize), ()> { + if data.len() < 4 { + return Err(()); + } + let ver = u16::from_le_bytes([data[0], data[1]]); + let argc = u16::from_le_bytes([data[2], data[3]]); + if ver != 1 { + return Err(()); + } Ok((ver, argc, 4)) } fn tlv_parse_string(data: &[u8]) -> Result { - let (_, argc, mut pos) = tlv_parse_header(data)?; if argc < 1 { return Err(()); } - let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?; if tag != 6 { return Err(()); } - Ok(std::str::from_utf8(&data[p..p+size]).map_err(|_| ())?.to_string()) + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 1 { + return Err(()); + } + let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?; + if tag != 6 { + return Err(()); + } + Ok(std::str::from_utf8(&data[p..p + size]) + .map_err(|_| ())? + .to_string()) } -fn tlv_parse_two_strings(data: &[u8]) -> Result<(String,String), ()> { - let (_, argc, mut pos) = tlv_parse_header(data)?; if argc < 2 { return Err(()); } - let (tag1, size1, p1) = tlv_parse_entry_hdr(data, pos)?; if tag1 != 6 { return Err(()); } - let s1 = std::str::from_utf8(&data[p1..p1+size1]).map_err(|_| ())?.to_string(); pos = p1+size1; - let (tag2, size2, p2) = tlv_parse_entry_hdr(data, pos)?; if tag2 != 6 { return Err(()); } - let s2 = std::str::from_utf8(&data[p2..p2+size2]).map_err(|_| ())?.to_string(); - Ok((s1,s2)) +fn tlv_parse_two_strings(data: &[u8]) -> Result<(String, String), ()> { + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 2 { + return Err(()); + } + let (tag1, size1, p1) = tlv_parse_entry_hdr(data, pos)?; + if tag1 != 6 { + return Err(()); + } + let s1 = std::str::from_utf8(&data[p1..p1 + size1]) + .map_err(|_| ())? + .to_string(); + pos = p1 + size1; + let (tag2, size2, p2) = tlv_parse_entry_hdr(data, pos)?; + if tag2 != 6 { + return Err(()); + } + let s2 = std::str::from_utf8(&data[p2..p2 + size2]) + .map_err(|_| ())? + .to_string(); + Ok((s1, s2)) } fn tlv_parse_bytes(data: &[u8]) -> Result, ()> { - let (_, argc, mut pos) = tlv_parse_header(data)?; if argc < 1 { return Err(()); } - let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?; if tag != 6 && tag != 7 { return Err(()); } - Ok(data[p..p+size].to_vec()) + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 1 { + return Err(()); + } + let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?; + if tag != 6 && tag != 7 { + return Err(()); + } + Ok(data[p..p + size].to_vec()) } fn tlv_parse_i32(data: &[u8]) -> Result { - let (_, argc, mut pos) = tlv_parse_header(data)?; if argc < 1 { return Err(()); } + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 1 { + return Err(()); + } let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?; match (tag, size) { - (2, 4) => { let mut b=[0u8;4]; b.copy_from_slice(&data[p..p+4]); Ok(i32::from_le_bytes(b)) } - (5, 8) => { // accept i64 - let mut b=[0u8;8]; b.copy_from_slice(&data[p..p+8]); Ok(i64::from_le_bytes(b) as i32) + (2, 4) => { + let mut b = [0u8; 4]; + b.copy_from_slice(&data[p..p + 4]); + Ok(i32::from_le_bytes(b)) } - _ => Err(()) + (5, 8) => { + // accept i64 + let mut b = [0u8; 8]; + b.copy_from_slice(&data[p..p + 8]); + Ok(i64::from_le_bytes(b) as i32) + } + _ => Err(()), } } -fn tlv_parse_handle(data: &[u8]) -> Result<(u32,u32), ()> { - let (_, argc, mut pos) = tlv_parse_header(data)?; if argc < 1 { return Err(()); } - let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?; if tag != 8 || size != 8 { return Err(()); } - let mut t = [0u8;4]; let mut i = [0u8;4]; t.copy_from_slice(&data[p..p+4]); i.copy_from_slice(&data[p+4..p+8]); +fn tlv_parse_handle(data: &[u8]) -> Result<(u32, u32), ()> { + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 1 { + return Err(()); + } + let (tag, size, p) = tlv_parse_entry_hdr(data, pos)?; + if tag != 8 || size != 8 { + return Err(()); + } + let mut t = [0u8; 4]; + let mut i = [0u8; 4]; + t.copy_from_slice(&data[p..p + 4]); + i.copy_from_slice(&data[p + 4..p + 8]); Ok((u32::from_le_bytes(t), u32::from_le_bytes(i))) } -fn tlv_parse_entry_hdr(data: &[u8], pos: usize) -> Result<(u8,usize,usize), ()> { - if pos+4 > data.len() { return Err(()); } - let tag = data[pos]; let _rsv = data[pos+1]; let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; let p = pos+4; - if p+size > data.len() { return Err(()); } - Ok((tag,size,p)) +fn tlv_parse_entry_hdr(data: &[u8], pos: usize) -> Result<(u8, usize, usize), ()> { + if pos + 4 > data.len() { + return Err(()); + } + let tag = data[pos]; + let _rsv = data[pos + 1]; + let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize; + let p = pos + 4; + if p + size > data.len() { + return Err(()); + } + Ok((tag, size, p)) } // ===== HTTP helpers ===== @@ -739,12 +1176,19 @@ fn parse_host(url: &str) -> Option { if let Some(rest) = url.split("//").nth(1) { let host_port = rest.split('/').next().unwrap_or(""); let host = host_port.split(':').next().unwrap_or(""); - if !host.is_empty() { return Some(host.to_string()); } + if !host.is_empty() { + return Some(host.to_string()); + } } None } -fn build_http_request(method: &str, url: &str, body: Option<&[u8]>, resp_id: u32) -> (String, String, Vec) { +fn build_http_request( + method: &str, + url: &str, + body: Option<&[u8]>, + resp_id: u32, +) -> (String, String, Vec) { let host = parse_host(url).unwrap_or_else(|| "127.0.0.1".to_string()); let path = parse_path(url); let mut buf = Vec::new(); @@ -777,15 +1221,20 @@ fn read_http_request(stream: &mut TcpStream) -> Option<(String, Vec, Option< Ok(0) => return None, // EOF without finding header end Ok(n) => { buf.extend_from_slice(&tmp[..n]); - if let Some(pos) = find_header_end(&buf) { header_end = pos; break; } - if buf.len() > 64 * 1024 { return None; } + if let Some(pos) = find_header_end(&buf) { + header_end = pos; + break; + } + if buf.len() > 64 * 1024 { + return None; + } } Err(_) => return None, } } // Parse request line and headers let header = &buf[..header_end]; - let after = &buf[header_end+4..]; + let after = &buf[header_end + 4..]; let header_str = String::from_utf8_lossy(header); let mut lines = header_str.split("\r\n"); let request_line = lines.next().unwrap_or(""); @@ -795,8 +1244,10 @@ fn read_http_request(stream: &mut TcpStream) -> Option<(String, Vec, Option< let mut content_length: usize = 0; let mut resp_handle_id: Option = None; for line in lines { - if let Some((k,v)) = line.split_once(':') { - if k.eq_ignore_ascii_case("Content-Length") { content_length = v.trim().parse().unwrap_or(0); } + if let Some((k, v)) = line.split_once(':') { + if k.eq_ignore_ascii_case("Content-Length") { + content_length = v.trim().parse().unwrap_or(0); + } if k.eq_ignore_ascii_case("X-Nyash-Resp-Id") { resp_handle_id = v.trim().parse::().ok(); } @@ -804,14 +1255,28 @@ fn read_http_request(stream: &mut TcpStream) -> Option<(String, Vec, Option< } let mut body = after.to_vec(); while body.len() < content_length { - match stream.read(&mut tmp) { Ok(0) => break, Ok(n) => body.extend_from_slice(&tmp[..n]), Err(_) => break } + match stream.read(&mut tmp) { + Ok(0) => break, + Ok(n) => body.extend_from_slice(&tmp[..n]), + Err(_) => break, + } + } + if method == "GET" || method == "POST" { + Some((path, body, resp_handle_id)) + } else { + None } - if method == "GET" || method == "POST" { Some((path, body, resp_handle_id)) } else { None } } fn find_header_end(buf: &[u8]) -> Option { - if buf.len() < 4 { return None; } - for i in 0..=buf.len()-4 { if &buf[i..i+4] == b"\r\n\r\n" { return Some(i); } } + if buf.len() < 4 { + return None; + } + for i in 0..=buf.len() - 4 { + if &buf[i..i + 4] == b"\r\n\r\n" { + return Some(i); + } + } None } @@ -836,28 +1301,45 @@ fn parse_client_response_into(resp_id: u32, conn_id: u32) { } Ok(n) => { buf.extend_from_slice(&tmp[..n]); - if find_header_end(&buf).is_some() { break; } - if buf.len() > 256 * 1024 { break; } + if find_header_end(&buf).is_some() { + break; + } + if buf.len() > 256 * 1024 { + break; + } } Err(_) => return, } } if let Some(pos) = find_header_end(&buf) { let header = &buf[..pos]; - let after = &buf[pos+4..]; + let after = &buf[pos + 4..]; // Parse status line and headers let header_str = String::from_utf8_lossy(header); let mut lines = header_str.split("\r\n"); if let Some(status_line) = lines.next() { let mut sp = status_line.split_whitespace(); let _ver = sp.next(); - if let Some(code_str) = sp.next() { status = code_str.parse::().unwrap_or(200); } + if let Some(code_str) = sp.next() { + status = code_str.parse::().unwrap_or(200); + } + } + for line in lines { + if let Some((k, v)) = line.split_once(':') { + headers.insert(k.trim().to_string(), v.trim().to_string()); + } } - for line in lines { if let Some((k,v)) = line.split_once(':') { headers.insert(k.trim().to_string(), v.trim().to_string()); } } body.extend_from_slice(after); - let need = headers.get("Content-Length").and_then(|v| v.parse::().ok()).unwrap_or(0); + let need = headers + .get("Content-Length") + .and_then(|v| v.parse::().ok()) + .unwrap_or(0); while body.len() < need { - match s.read(&mut tmp) { Ok(0) => break, Ok(n) => body.extend_from_slice(&tmp[..n]), Err(_) => break } + match s.read(&mut tmp) { + Ok(0) => break, + Ok(n) => body.extend_from_slice(&tmp[..n]), + Err(_) => break, + } } // Parsing succeeded; mark for removal should_remove = true; @@ -869,21 +1351,42 @@ fn parse_client_response_into(resp_id: u32, conn_id: u32) { } } if let Some(rp) = RESPONSES.lock().unwrap().get_mut(&resp_id) { - rp.status = status; rp.headers = headers; rp.body = body; rp.parsed = true; rp.client_conn_id = None; + rp.status = status; + rp.headers = headers; + rp.body = body; + rp.parsed = true; + rp.client_conn_id = None; } } // ===== Socket implementation ===== -static SOCK_SERVERS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); -static SOCK_CONNS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); -static SOCK_CLIENTS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static SOCK_SERVERS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); +static SOCK_CONNS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); +static SOCK_CLIENTS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); -unsafe fn sock_server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: *mut u8, res_len: *mut usize) -> i32 { +unsafe fn sock_server_invoke( + m: u32, + id: u32, + args: *const u8, + args_len: usize, + res: *mut u8, + res_len: *mut usize, +) -> i32 { match m { M_SRV_BIRTH => { netlog!("sock:birth server"); let id = SOCK_SERVER_ID.fetch_add(1, Ordering::Relaxed); - SOCK_SERVERS.lock().unwrap().insert(id, SockServerState { running: Arc::new(AtomicBool::new(false)), pending: Arc::new(Mutex::new(VecDeque::new())), handle: Mutex::new(None) }); + SOCK_SERVERS.lock().unwrap().insert( + id, + SockServerState { + running: Arc::new(AtomicBool::new(false)), + pending: Arc::new(Mutex::new(VecDeque::new())), + handle: Mutex::new(None), + }, + ); write_u32(id, res, res_len) } M_SRV_START => { @@ -903,7 +1406,12 @@ unsafe fn sock_server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, Ok((stream, _)) => { stream.set_nonblocking(false).ok(); let conn_id = SOCK_CONN_ID.fetch_add(1, Ordering::Relaxed); - SOCK_CONNS.lock().unwrap().insert(conn_id, SockConnState { stream: Mutex::new(stream) }); + SOCK_CONNS.lock().unwrap().insert( + conn_id, + SockConnState { + stream: Mutex::new(stream), + }, + ); netlog!("sock:accept conn_id={}", conn_id); pending.lock().unwrap().push_back(conn_id); } @@ -923,7 +1431,9 @@ unsafe fn sock_server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, netlog!("sock:stop server id={}", id); if let Some(ss) = SOCK_SERVERS.lock().unwrap().get(&id) { ss.running.store(false, Ordering::SeqCst); - if let Some(h) = ss.handle.lock().unwrap().take() { let _ = h.join(); } + if let Some(h) = ss.handle.lock().unwrap().take() { + let _ = h.join(); + } } write_tlv_void(res, res_len) } @@ -950,7 +1460,9 @@ unsafe fn sock_server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, netlog!("sock:acceptTimeout returned conn_id={}", cid); return write_tlv_handle(T_SOCK_CONN, cid, res, res_len); } - if std::time::Instant::now() >= deadline { break; } + if std::time::Instant::now() >= deadline { + break; + } std::thread::sleep(Duration::from_millis(5)); } } @@ -962,7 +1474,14 @@ unsafe fn sock_server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, } } -unsafe fn sock_client_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: *mut u8, res_len: *mut usize) -> i32 { +unsafe fn sock_client_invoke( + m: u32, + id: u32, + args: *const u8, + args_len: usize, + res: *mut u8, + res_len: *mut usize, +) -> i32 { match m { M_SC_BIRTH => { let id = SOCK_CLIENT_ID.fetch_add(1, Ordering::Relaxed); @@ -972,33 +1491,70 @@ unsafe fn sock_client_invoke(m: u32, id: u32, args: *const u8, args_len: usize, M_SC_CONNECT => { // args: host(string), port(i32) let data = slice(args, args_len); - let (_, argc, mut pos) = tlv_parse_header(data).map_err(|_| ()).or(Err(())).unwrap_or((1,0,4)); - if argc < 2 { return E_INV_ARGS; } - let (_t1, s1, p1) = tlv_parse_entry_hdr(data, pos).map_err(|_| ()).or(Err(())).unwrap_or((0,0,0)); - if data[pos] != 6 { return E_INV_ARGS; } - let host = std::str::from_utf8(&data[p1..p1+s1]).map_err(|_| ()).or(Err(())) .unwrap_or("").to_string(); + let (_, argc, mut pos) = tlv_parse_header(data) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or((1, 0, 4)); + if argc < 2 { + return E_INV_ARGS; + } + let (_t1, s1, p1) = tlv_parse_entry_hdr(data, pos) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or((0, 0, 0)); + if data[pos] != 6 { + return E_INV_ARGS; + } + let host = std::str::from_utf8(&data[p1..p1 + s1]) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or("") + .to_string(); pos = p1 + s1; - let (_t2, _s2, p2) = tlv_parse_entry_hdr(data, pos).map_err(|_| ()).or(Err(())).unwrap_or((0,0,0)); - let port = if data[pos] == 2 { // i32 - let mut b=[0u8;4]; b.copy_from_slice(&data[p2..p2+4]); i32::from_le_bytes(b) - } else { return E_INV_ARGS }; + let (_t2, _s2, p2) = tlv_parse_entry_hdr(data, pos) + .map_err(|_| ()) + .or(Err(())) + .unwrap_or((0, 0, 0)); + let port = if data[pos] == 2 { + // i32 + let mut b = [0u8; 4]; + b.copy_from_slice(&data[p2..p2 + 4]); + i32::from_le_bytes(b) + } else { + return E_INV_ARGS; + }; let addr = format!("{}:{}", host, port); match TcpStream::connect(addr) { Ok(mut stream) => { stream.set_nonblocking(false).ok(); let conn_id = SOCK_CONN_ID.fetch_add(1, Ordering::Relaxed); - SOCK_CONNS.lock().unwrap().insert(conn_id, SockConnState { stream: Mutex::new(stream) }); + SOCK_CONNS.lock().unwrap().insert( + conn_id, + SockConnState { + stream: Mutex::new(stream), + }, + ); netlog!("sock:connect ok conn_id={}", conn_id); write_tlv_handle(T_SOCK_CONN, conn_id, res, res_len) } - Err(e) => { netlog!("sock:connect error: {:?}", e); E_ERR } + Err(e) => { + netlog!("sock:connect error: {:?}", e); + E_ERR + } } } _ => E_INV_METHOD, } } -unsafe fn sock_conn_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: *mut u8, res_len: *mut usize) -> i32 { +unsafe fn sock_conn_invoke( + m: u32, + id: u32, + args: *const u8, + args_len: usize, + res: *mut u8, + res_len: *mut usize, +) -> i32 { match m { M_CONN_BIRTH => { // not used directly @@ -1007,7 +1563,9 @@ unsafe fn sock_conn_invoke(m: u32, id: u32, args: *const u8, args_len: usize, re M_CONN_SEND => { let bytes = tlv_parse_bytes(slice(args, args_len)).unwrap_or_default(); if let Some(conn) = SOCK_CONNS.lock().unwrap().get(&id) { - if let Ok(mut s) = conn.stream.lock() { let _ = s.write_all(&bytes); } + if let Ok(mut s) = conn.stream.lock() { + let _ = s.write_all(&bytes); + } netlog!("sock:send id={} n={}", id, bytes.len()); return write_tlv_void(res, res_len); } @@ -1018,7 +1576,11 @@ unsafe fn sock_conn_invoke(m: u32, id: u32, args: *const u8, args_len: usize, re if let Ok(mut s) = conn.stream.lock() { let mut buf = vec![0u8; 4096]; match s.read(&mut buf) { - Ok(n) => { buf.truncate(n); netlog!("sock:recv id={} n={}", id, n); return write_tlv_bytes(&buf, res, res_len); } + Ok(n) => { + buf.truncate(n); + netlog!("sock:recv id={} n={}", id, n); + return write_tlv_bytes(&buf, res, res_len); + } Err(_) => return write_tlv_bytes(&[], res, res_len), } } @@ -1034,8 +1596,20 @@ unsafe fn sock_conn_invoke(m: u32, id: u32, args: *const u8, args_len: usize, re let resv = s.read(&mut buf); let _ = s.set_read_timeout(None); match resv { - Ok(n) => { buf.truncate(n); netlog!("sock:recvTimeout id={} n={} ms={}", id, n, timeout_ms); return write_tlv_bytes(&buf, res, res_len); } - Err(e) => { netlog!("sock:recvTimeout error id={} ms={} err={:?}", id, timeout_ms, e); return E_ERR; }, + Ok(n) => { + buf.truncate(n); + netlog!("sock:recvTimeout id={} n={} ms={}", id, n, timeout_ms); + return write_tlv_bytes(&buf, res, res_len); + } + Err(e) => { + netlog!( + "sock:recvTimeout error id={} ms={} err={:?}", + id, + timeout_ms, + e + ); + return E_ERR; + } } } } diff --git a/plugins/nyash-path-plugin/src/lib.rs b/plugins/nyash-path-plugin/src/lib.rs index 4c1a7e24..4d9407cb 100644 --- a/plugins/nyash-path-plugin/src/lib.rs +++ b/plugins/nyash-path-plugin/src/lib.rs @@ -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; @@ -12,14 +15,14 @@ const E_METHOD: i32 = -3; const E_ARGS: i32 = -4; const E_PLUGIN: i32 = -5; -const M_BIRTH: u32 = 0; // constructor -> instance -const M_JOIN: u32 = 1; // join(base, rest) -> string -const M_DIRNAME: u32 = 2; // dirname(path) -> string -const M_BASENAME: u32 = 3; // basename(path) -> string -const M_EXTNAME: u32 = 4; // extname(path) -> string -const M_IS_ABS: u32 = 5; // isAbs(path) -> bool -const M_NORMALIZE: u32 = 6; // normalize(path) -> string -const M_FINI: u32 = u32::MAX; // fini +const M_BIRTH: u32 = 0; // constructor -> instance +const M_JOIN: u32 = 1; // join(base, rest) -> string +const M_DIRNAME: u32 = 2; // dirname(path) -> string +const M_BASENAME: u32 = 3; // basename(path) -> string +const M_EXTNAME: u32 = 4; // extname(path) -> string +const M_IS_ABS: u32 = 5; // isAbs(path) -> bool +const M_NORMALIZE: u32 = 6; // normalize(path) -> string +const M_FINI: u32 = u32::MAX; // fini const TYPE_ID_PATH: u32 = 55; @@ -29,10 +32,14 @@ static INST: Lazy>> = 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; } + 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 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); - 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 = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); + 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 { - 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 } diff --git a/plugins/nyash-python-compiler-plugin/src/lib.rs b/plugins/nyash-python-compiler-plugin/src/lib.rs index 04e8c0f8..295e8be6 100644 --- a/plugins/nyash-python-compiler-plugin/src/lib.rs +++ b/plugins/nyash-python-compiler-plugin/src/lib.rs @@ -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> = 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 diff --git a/plugins/nyash-python-parser-plugin/src/lib.rs b/plugins/nyash-python-parser-plugin/src/lib.rs index b3bc40e8..f68b6c20 100644 --- a/plugins/nyash-python-parser-plugin/src/lib.rs +++ b/plugins/nyash-python-parser-plugin/src/lib.rs @@ -46,11 +46,11 @@ pub extern "C" fn nyash_plugin_invoke( const METHOD_BIRTH: u32 = 0; const METHOD_PARSE: u32 = 1; const METHOD_FINI: u32 = u32::MAX; - + if type_id != TYPE_ID_PARSER { return -3; // NYB_E_INVALID_METHOD } - + match method_id { METHOD_BIRTH => { // インスタンスIDを返す @@ -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,24 +80,25 @@ 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) { Ok(json) => { @@ -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) => { @@ -253,7 +255,7 @@ fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) { // 再帰的に解析(ただし walk は全ノードを返すので、 // 実際には再帰なしでフラットに処理される) result.counts.total_nodes += 1; - + if let Ok(class_obj) = child.getattr("__class__") { if let Ok(name_obj) = class_obj.getattr("__name__") { if let Ok(type_name) = name_obj.extract::() { @@ -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()); } } _ => {} @@ -305,14 +322,14 @@ mod tests { #[test] fn test_simple_parse() { pyo3::prepare_freethreaded_python(); - + Python::with_gil(|py| { let code = "def main():\n return 0"; let result = parse_python_code(py, code); - + assert!(result.success); assert_eq!(result.counts.functions, 1); assert_eq!(result.counts.supported, 1); }); } -} \ No newline at end of file +} diff --git a/plugins/nyash-python-plugin/src/lib.rs b/plugins/nyash-python-plugin/src/lib.rs index a886fb1a..a25cf09d 100644 --- a/plugins/nyash-python-plugin/src/lib.rs +++ b/plugins/nyash-python-plugin/src/lib.rs @@ -7,12 +7,15 @@ //! ABI alignment and loader wiring. Future subphases (10.5b–d) will implement //! actual CPython embedding and conversion. +use libloading::Library; use once_cell::sync::Lazy; use std::collections::HashMap; use std::ffi::{CStr, CString}; -use std::os::raw::{c_char, c_int, c_void, c_long}; -use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; -use libloading::Library; +use std::os::raw::{c_char, c_int, c_long, c_void}; +use std::sync::{ + atomic::{AtomicU32, Ordering}, + Mutex, +}; // ===== Error Codes (aligned with other plugins) ===== const NYB_SUCCESS: i32 = 0; @@ -25,33 +28,35 @@ const NYB_E_INVALID_HANDLE: i32 = -8; // ===== Type IDs (must match nyash.toml) ===== const TYPE_ID_PY_RUNTIME: u32 = 40; -const TYPE_ID_PY_OBJECT: u32 = 41; +const TYPE_ID_PY_OBJECT: u32 = 41; // ===== Method IDs (initial draft) ===== // PyRuntimeBox -const PY_METHOD_BIRTH: u32 = 0; // returns instance_id (u32 LE, no TLV) -const PY_METHOD_EVAL: u32 = 1; // args: string code -> returns Handle(PyObject) -const PY_METHOD_IMPORT:u32 = 2; // args: string name -> returns Handle(PyObject) -const PY_METHOD_FINI: u32 = u32::MAX; // destructor -// Result-returning variants (R) -const PY_METHOD_EVAL_R: u32 = 11; -const PY_METHOD_IMPORT_R:u32 = 12; +const PY_METHOD_BIRTH: u32 = 0; // returns instance_id (u32 LE, no TLV) +const PY_METHOD_EVAL: u32 = 1; // args: string code -> returns Handle(PyObject) +const PY_METHOD_IMPORT: u32 = 2; // args: string name -> returns Handle(PyObject) +const PY_METHOD_FINI: u32 = u32::MAX; // destructor + // Result-returning variants (R) +const PY_METHOD_EVAL_R: u32 = 11; +const PY_METHOD_IMPORT_R: u32 = 12; // PyObjectBox -const PYO_METHOD_BIRTH: u32 = 0; // reserved (should not be used directly) -const PYO_METHOD_GETATTR: u32 = 1; // args: string name -> returns Handle(PyObject) -const PYO_METHOD_CALL: u32 = 2; // args: variadic TLV -> returns Handle(PyObject) -const PYO_METHOD_STR: u32 = 3; // returns String -const PYO_METHOD_CALL_KW: u32 = 5; // args: key:string, val:TLV, ... -> returns Handle(PyObject) -const PYO_METHOD_FINI: u32 = u32::MAX; // destructor -// Result-returning variants (R) -const PYO_METHOD_GETATTR_R:u32 = 11; -const PYO_METHOD_CALL_R: u32 = 12; -const PYO_METHOD_CALL_KW_R:u32 = 15; +const PYO_METHOD_BIRTH: u32 = 0; // reserved (should not be used directly) +const PYO_METHOD_GETATTR: u32 = 1; // args: string name -> returns Handle(PyObject) +const PYO_METHOD_CALL: u32 = 2; // args: variadic TLV -> returns Handle(PyObject) +const PYO_METHOD_STR: u32 = 3; // returns String +const PYO_METHOD_CALL_KW: u32 = 5; // args: key:string, val:TLV, ... -> returns Handle(PyObject) +const PYO_METHOD_FINI: u32 = u32::MAX; // destructor + // Result-returning variants (R) +const PYO_METHOD_GETATTR_R: u32 = 11; +const PYO_METHOD_CALL_R: u32 = 12; +const PYO_METHOD_CALL_KW_R: u32 = 15; // ===== Minimal in-memory state for stubs ===== #[derive(Default)] -struct PyRuntimeInstance { globals: Option<*mut PyObject> } +struct PyRuntimeInstance { + globals: Option<*mut PyObject>, +} // Safety: Access to CPython state is guarded by the GIL in all call sites // and we only store raw pointers captured under the GIL. We never mutate // from multiple threads without reacquiring the GIL. Therefore, mark as @@ -62,10 +67,12 @@ unsafe impl Sync for PyRuntimeInstance {} #[derive(Default)] struct PyObjectInstance {} -static RUNTIMES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); -static PYOBJS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static RUNTIMES: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); +static PYOBJS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); -static RT_COUNTER: AtomicU32 = AtomicU32::new(1); +static RT_COUNTER: AtomicU32 = AtomicU32::new(1); static OBJ_COUNTER: AtomicU32 = AtomicU32::new(1); // ====== Minimal dynamic CPython loader ====== @@ -79,7 +86,13 @@ struct CPython { Py_IsInitialized: unsafe extern "C" fn() -> c_int, PyGILState_Ensure: unsafe extern "C" fn() -> PyGILState_STATE, PyGILState_Release: unsafe extern "C" fn(PyGILState_STATE), - PyRun_StringFlags: unsafe extern "C" fn(*const c_char, c_int, *mut PyObject, *mut PyObject, *mut c_void) -> *mut PyObject, + PyRun_StringFlags: unsafe extern "C" fn( + *const c_char, + c_int, + *mut PyObject, + *mut PyObject, + *mut c_void, + ) -> *mut PyObject, PyImport_AddModule: unsafe extern "C" fn(*const c_char) -> *mut PyObject, PyModule_GetDict: unsafe extern "C" fn(*mut PyObject) -> *mut PyObject, PyImport_ImportModule: unsafe extern "C" fn(*const c_char) -> *mut PyObject, @@ -90,7 +103,8 @@ struct CPython { // Added for getattr/call PyObject_GetAttrString: unsafe extern "C" fn(*mut PyObject, *const c_char) -> *mut PyObject, PyObject_CallObject: unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject, - PyObject_Call: unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject, + PyObject_Call: + unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject, PyTuple_New: unsafe extern "C" fn(isize) -> *mut PyObject, PyTuple_SetItem: unsafe extern "C" fn(*mut PyObject, isize, *mut PyObject) -> c_int, PyLong_FromLongLong: unsafe extern "C" fn(i64) -> *mut PyObject, @@ -101,9 +115,11 @@ struct CPython { PyFloat_AsDouble: unsafe extern "C" fn(*mut PyObject) -> f64, PyLong_AsLongLong: unsafe extern "C" fn(*mut PyObject) -> i64, PyBytes_FromStringAndSize: unsafe extern "C" fn(*const c_char, isize) -> *mut PyObject, - PyBytes_AsStringAndSize: unsafe extern "C" fn(*mut PyObject, *mut *mut c_char, *mut isize) -> c_int, + PyBytes_AsStringAndSize: + unsafe extern "C" fn(*mut PyObject, *mut *mut c_char, *mut isize) -> c_int, PyDict_New: unsafe extern "C" fn() -> *mut PyObject, - PyDict_SetItemString: unsafe extern "C" fn(*mut PyObject, *const c_char, *mut PyObject) -> c_int, + PyDict_SetItemString: + unsafe extern "C" fn(*mut PyObject, *const c_char, *mut PyObject) -> c_int, PyErr_Occurred: unsafe extern "C" fn() -> *mut PyObject, PyErr_Fetch: unsafe extern "C" fn(*mut *mut PyObject, *mut *mut PyObject, *mut *mut PyObject), PyErr_Clear: unsafe extern "C" fn(), @@ -130,49 +146,150 @@ fn try_load_cpython() -> Result<(), ()> { ]; // Windows DLLs (search via PATH / System32) if cfg!(target_os = "windows") { - let dlls = ["python312.dll","python311.dll","python310.dll","python39.dll"]; - for d in dlls.iter() { candidates.push((*d).into()); } + let dlls = [ + "python312.dll", + "python311.dll", + "python310.dll", + "python39.dll", + ]; + for d in dlls.iter() { + candidates.push((*d).into()); + } if let Ok(pyhome) = std::env::var("PYTHONHOME") { for d in dlls.iter() { let p = std::path::Path::new(&pyhome).join(d); - if p.exists() { candidates.push(p.to_string_lossy().to_string()); } + if p.exists() { + candidates.push(p.to_string_lossy().to_string()); + } } } } for name in candidates.into_iter() { if let Ok(lib) = unsafe { Library::new(&name) } { unsafe { - let Py_Initialize = *lib.get::(b"Py_Initialize\0").map_err(|_| ())?; - let Py_Finalize = *lib.get::(b"Py_Finalize\0").map_err(|_| ())?; - let Py_IsInitialized = *lib.get:: c_int>(b"Py_IsInitialized\0").map_err(|_| ())?; - let PyGILState_Ensure = *lib.get:: PyGILState_STATE>(b"PyGILState_Ensure\0").map_err(|_| ())?; - let PyGILState_Release = *lib.get::(b"PyGILState_Release\0").map_err(|_| ())?; - let PyRun_StringFlags = *lib.get:: *mut PyObject>(b"PyRun_StringFlags\0").map_err(|_| ())?; - let PyImport_AddModule = *lib.get:: *mut PyObject>(b"PyImport_AddModule\0").map_err(|_| ())?; - let PyModule_GetDict = *lib.get:: *mut PyObject>(b"PyModule_GetDict\0").map_err(|_| ())?; - let PyImport_ImportModule = *lib.get:: *mut PyObject>(b"PyImport_ImportModule\0").map_err(|_| ())?; - let PyObject_Str = *lib.get:: *mut PyObject>(b"PyObject_Str\0").map_err(|_| ())?; - let PyUnicode_AsUTF8 = *lib.get:: *const c_char>(b"PyUnicode_AsUTF8\0").map_err(|_| ())?; - let Py_DecRef = *lib.get::(b"Py_DecRef\0").map_err(|_| ())?; - let Py_IncRef = *lib.get::(b"Py_IncRef\0").map_err(|_| ())?; - let PyObject_GetAttrString = *lib.get:: *mut PyObject>(b"PyObject_GetAttrString\0").map_err(|_| ())?; - let PyObject_CallObject = *lib.get:: *mut PyObject>(b"PyObject_CallObject\0").map_err(|_| ())?; - let PyObject_Call = *lib.get:: *mut PyObject>(b"PyObject_Call\0").map_err(|_| ())?; - let PyTuple_New = *lib.get:: *mut PyObject>(b"PyTuple_New\0").map_err(|_| ())?; - let PyTuple_SetItem = *lib.get:: c_int>(b"PyTuple_SetItem\0").map_err(|_| ())?; - let PyLong_FromLongLong = *lib.get:: *mut PyObject>(b"PyLong_FromLongLong\0").map_err(|_| ())?; - let PyUnicode_FromString = *lib.get:: *mut PyObject>(b"PyUnicode_FromString\0").map_err(|_| ())?; - let PyBool_FromLong = *lib.get:: *mut PyObject>(b"PyBool_FromLong\0").map_err(|_| ())?; - let PyFloat_FromDouble = *lib.get:: *mut PyObject>(b"PyFloat_FromDouble\0").map_err(|_| ())?; - let PyFloat_AsDouble = *lib.get:: f64>(b"PyFloat_AsDouble\0").map_err(|_| ())?; - let PyLong_AsLongLong = *lib.get:: i64>(b"PyLong_AsLongLong\0").map_err(|_| ())?; - let PyBytes_FromStringAndSize = *lib.get:: *mut PyObject>(b"PyBytes_FromStringAndSize\0").map_err(|_| ())?; + let Py_Initialize = *lib + .get::(b"Py_Initialize\0") + .map_err(|_| ())?; + let Py_Finalize = *lib + .get::(b"Py_Finalize\0") + .map_err(|_| ())?; + let Py_IsInitialized = *lib + .get:: c_int>(b"Py_IsInitialized\0") + .map_err(|_| ())?; + let PyGILState_Ensure = *lib + .get:: PyGILState_STATE>(b"PyGILState_Ensure\0") + .map_err(|_| ())?; + let PyGILState_Release = *lib + .get::(b"PyGILState_Release\0") + .map_err(|_| ())?; + let PyRun_StringFlags = *lib + .get:: *mut PyObject>(b"PyRun_StringFlags\0") + .map_err(|_| ())?; + let PyImport_AddModule = *lib + .get:: *mut PyObject>( + b"PyImport_AddModule\0", + ) + .map_err(|_| ())?; + let PyModule_GetDict = *lib + .get:: *mut PyObject>( + b"PyModule_GetDict\0", + ) + .map_err(|_| ())?; + let PyImport_ImportModule = *lib + .get:: *mut PyObject>( + b"PyImport_ImportModule\0", + ) + .map_err(|_| ())?; + let PyObject_Str = *lib + .get:: *mut PyObject>(b"PyObject_Str\0") + .map_err(|_| ())?; + let PyUnicode_AsUTF8 = *lib + .get:: *const c_char>( + b"PyUnicode_AsUTF8\0", + ) + .map_err(|_| ())?; + let Py_DecRef = *lib + .get::(b"Py_DecRef\0") + .map_err(|_| ())?; + let Py_IncRef = *lib + .get::(b"Py_IncRef\0") + .map_err(|_| ())?; + let PyObject_GetAttrString = *lib + .get:: *mut PyObject>( + b"PyObject_GetAttrString\0", + ) + .map_err(|_| ())?; + let PyObject_CallObject = *lib + .get:: *mut PyObject>( + b"PyObject_CallObject\0", + ) + .map_err(|_| ())?; + let PyObject_Call = *lib + .get:: *mut PyObject>(b"PyObject_Call\0") + .map_err(|_| ())?; + let PyTuple_New = *lib + .get:: *mut PyObject>(b"PyTuple_New\0") + .map_err(|_| ())?; + let PyTuple_SetItem = *lib + .get:: c_int>( + b"PyTuple_SetItem\0", + ) + .map_err(|_| ())?; + let PyLong_FromLongLong = *lib + .get:: *mut PyObject>(b"PyLong_FromLongLong\0") + .map_err(|_| ())?; + let PyUnicode_FromString = *lib + .get:: *mut PyObject>( + b"PyUnicode_FromString\0", + ) + .map_err(|_| ())?; + let PyBool_FromLong = *lib + .get:: *mut PyObject>( + b"PyBool_FromLong\0", + ) + .map_err(|_| ())?; + let PyFloat_FromDouble = *lib + .get:: *mut PyObject>(b"PyFloat_FromDouble\0") + .map_err(|_| ())?; + let PyFloat_AsDouble = *lib + .get:: f64>(b"PyFloat_AsDouble\0") + .map_err(|_| ())?; + let PyLong_AsLongLong = *lib + .get:: i64>(b"PyLong_AsLongLong\0") + .map_err(|_| ())?; + let PyBytes_FromStringAndSize = *lib + .get:: *mut PyObject>( + b"PyBytes_FromStringAndSize\0", + ) + .map_err(|_| ())?; let PyBytes_AsStringAndSize = *lib.get:: c_int>(b"PyBytes_AsStringAndSize\0").map_err(|_| ())?; - let PyErr_Occurred = *lib.get:: *mut PyObject>(b"PyErr_Occurred\0").map_err(|_| ())?; - let PyDict_New = *lib.get:: *mut PyObject>(b"PyDict_New\0").map_err(|_| ())?; + let PyErr_Occurred = *lib + .get:: *mut PyObject>(b"PyErr_Occurred\0") + .map_err(|_| ())?; + let PyDict_New = *lib + .get:: *mut PyObject>(b"PyDict_New\0") + .map_err(|_| ())?; let PyDict_SetItemString = *lib.get:: c_int>(b"PyDict_SetItemString\0").map_err(|_| ())?; - let PyErr_Fetch = *lib.get::(b"PyErr_Fetch\0").map_err(|_| ())?; - let PyErr_Clear = *lib.get::(b"PyErr_Clear\0").map_err(|_| ())?; + let PyErr_Fetch = + *lib.get::(b"PyErr_Fetch\0") + .map_err(|_| ())?; + let PyErr_Clear = *lib + .get::(b"PyErr_Clear\0") + .map_err(|_| ())?; let cpy = CPython { _lib: lib, @@ -222,7 +339,9 @@ fn ensure_cpython() -> Result<(), ()> { // Initialize on first load unsafe { if let Some(cpy) = &*CPY.lock().unwrap() { - if (cpy.Py_IsInitialized)() == 0 { (cpy.Py_Initialize)(); } + if (cpy.Py_IsInitialized)() == 0 { + (cpy.Py_Initialize)(); + } } } } @@ -230,10 +349,14 @@ fn ensure_cpython() -> Result<(), ()> { } #[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( @@ -246,19 +369,36 @@ pub extern "C" fn nyash_plugin_invoke( result_len: *mut usize, ) -> i32 { match type_id { - TYPE_ID_PY_RUNTIME => handle_py_runtime(method_id, instance_id, args, args_len, result, result_len), - TYPE_ID_PY_OBJECT => handle_py_object(method_id, instance_id, args, args_len, result, result_len), + TYPE_ID_PY_RUNTIME => { + handle_py_runtime(method_id, instance_id, args, args_len, result, result_len) + } + TYPE_ID_PY_OBJECT => { + handle_py_object(method_id, instance_id, args, args_len, result, result_len) + } _ => NYB_E_INVALID_TYPE, } } -fn handle_py_runtime(method_id: u32, _instance_id: u32, _args: *const u8, _args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { +fn handle_py_runtime( + method_id: u32, + _instance_id: u32, + _args: *const u8, + _args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { unsafe { match method_id { PY_METHOD_BIRTH => { - if result_len.is_null() { return NYB_E_INVALID_ARGS; } - if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; } - if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + if preflight(result, result_len, 4) { + return NYB_E_SHORT_BUFFER; + } + if ensure_cpython().is_err() { + return NYB_E_PLUGIN_ERROR; + } let id = RT_COUNTER.fetch_add(1, Ordering::Relaxed); let mut inst = PyRuntimeInstance::default(); if let Some(cpy) = &*CPY.lock().unwrap() { @@ -267,11 +407,17 @@ fn handle_py_runtime(method_id: u32, _instance_id: u32, _args: *const u8, _args_ let module = (cpy.PyImport_AddModule)(c_main.as_ptr()); if !module.is_null() { let dict = (cpy.PyModule_GetDict)(module); - if !dict.is_null() { inst.globals = Some(dict); } + if !dict.is_null() { + inst.globals = Some(dict); + } } (cpy.PyGILState_Release)(state); } - if let Ok(mut map) = RUNTIMES.lock() { map.insert(id, inst); } else { return NYB_E_PLUGIN_ERROR; } + if let Ok(mut map) = RUNTIMES.lock() { + map.insert(id, inst); + } else { + return NYB_E_PLUGIN_ERROR; + } let bytes = id.to_le_bytes(); std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4); *result_len = 4; @@ -279,40 +425,73 @@ fn handle_py_runtime(method_id: u32, _instance_id: u32, _args: *const u8, _args_ } PY_METHOD_FINI => { // Only drop runtime slot; avoid calling Py_Finalize to prevent shutdown crashes. - if let Ok(mut map) = RUNTIMES.lock() { map.remove(&_instance_id); } + if let Ok(mut map) = RUNTIMES.lock() { + map.remove(&_instance_id); + } NYB_SUCCESS } PY_METHOD_EVAL | PY_METHOD_EVAL_R => { - if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + if ensure_cpython().is_err() { + return NYB_E_PLUGIN_ERROR; + } // Allow zero-arg eval by reading from env var (NYASH_PY_EVAL_CODE) for bootstrap demos let argc = tlv_count_args(_args, _args_len); let code = if argc == 0 { std::env::var("NYASH_PY_EVAL_CODE").unwrap_or_else(|_| "".to_string()) } else { - if let Some(s) = read_arg_string(_args, _args_len, 0) { s } else { return NYB_E_INVALID_ARGS } + if let Some(s) = read_arg_string(_args, _args_len, 0) { + s + } else { + return NYB_E_INVALID_ARGS; + } + }; + let c_code = match CString::new(code) { + Ok(s) => s, + Err(_) => return NYB_E_INVALID_ARGS, }; - let c_code = match CString::new(code) { Ok(s) => s, Err(_) => return NYB_E_INVALID_ARGS }; if let Some(cpy) = &*CPY.lock().unwrap() { let state = (cpy.PyGILState_Ensure)(); // use per-runtime globals if available let mut dict: *mut PyObject = std::ptr::null_mut(); - if let Ok(map) = RUNTIMES.lock() { if let Some(rt) = map.get(&_instance_id) { if let Some(g) = rt.globals { dict = g; } } } + if let Ok(map) = RUNTIMES.lock() { + if let Some(rt) = map.get(&_instance_id) { + if let Some(g) = rt.globals { + dict = g; + } + } + } if dict.is_null() { let c_main = CString::new("__main__").unwrap(); let module = (cpy.PyImport_AddModule)(c_main.as_ptr()); - if module.is_null() { (cpy.PyGILState_Release)(state); return NYB_E_PLUGIN_ERROR; } + if module.is_null() { + (cpy.PyGILState_Release)(state); + return NYB_E_PLUGIN_ERROR; + } dict = (cpy.PyModule_GetDict)(module); } // 258 == Py_eval_input - let obj = (cpy.PyRun_StringFlags)(c_code.as_ptr(), 258, dict, dict, std::ptr::null_mut()); + let obj = (cpy.PyRun_StringFlags)( + c_code.as_ptr(), + 258, + dict, + dict, + std::ptr::null_mut(), + ); if obj.is_null() { let msg = take_py_error_string(cpy); (cpy.PyGILState_Release)(state); - if method_id == PY_METHOD_EVAL_R { return NYB_E_PLUGIN_ERROR; } - if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + if method_id == PY_METHOD_EVAL_R { + return NYB_E_PLUGIN_ERROR; + } + if let Some(m) = msg { + return write_tlv_string(&m, result, result_len); + } return NYB_E_PLUGIN_ERROR; } - if (method_id == PY_METHOD_EVAL || method_id == PY_METHOD_EVAL_R) && should_autodecode() && try_write_autodecode(cpy, obj, result, result_len) { + if (method_id == PY_METHOD_EVAL || method_id == PY_METHOD_EVAL_R) + && should_autodecode() + && try_write_autodecode(cpy, obj, result, result_len) + { (cpy.Py_DecRef)(obj); (cpy.PyGILState_Release)(state); return NYB_SUCCESS; @@ -335,23 +514,46 @@ fn handle_py_runtime(method_id: u32, _instance_id: u32, _args: *const u8, _args_ NYB_E_PLUGIN_ERROR } PY_METHOD_IMPORT | PY_METHOD_IMPORT_R => { - if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } - let Some(name) = read_arg_string(_args, _args_len, 0) else { return NYB_E_INVALID_ARGS; }; - let c_name = match CString::new(name) { Ok(s) => s, Err(_) => return NYB_E_INVALID_ARGS }; + if ensure_cpython().is_err() { + return NYB_E_PLUGIN_ERROR; + } + let Some(name) = read_arg_string(_args, _args_len, 0) else { + return NYB_E_INVALID_ARGS; + }; + let c_name = match CString::new(name) { + Ok(s) => s, + Err(_) => return NYB_E_INVALID_ARGS, + }; if let Some(cpy) = &*CPY.lock().unwrap() { let state = (cpy.PyGILState_Ensure)(); let obj = (cpy.PyImport_ImportModule)(c_name.as_ptr()); if obj.is_null() { let msg = take_py_error_string(cpy); (cpy.PyGILState_Release)(state); - if method_id == PY_METHOD_IMPORT_R { return NYB_E_PLUGIN_ERROR; } - if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + if method_id == PY_METHOD_IMPORT_R { + return NYB_E_PLUGIN_ERROR; + } + if let Some(m) = msg { + return write_tlv_string(&m, result, result_len); + } return NYB_E_PLUGIN_ERROR; } // expose module into runtime globals - if let Ok(map) = RUNTIMES.lock() { if let Some(rt) = map.get(&_instance_id) { if let Some(gl) = rt.globals { (cpy.PyDict_SetItemString)(gl, c_name.as_ptr(), obj); } } } + if let Ok(map) = RUNTIMES.lock() { + if let Some(rt) = map.get(&_instance_id) { + if let Some(gl) = rt.globals { + (cpy.PyDict_SetItemString)(gl, c_name.as_ptr(), obj); + } + } + } let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); - if let Ok(mut map) = PYOBJS.lock() { map.insert(id, PyObjectInstance::default()); } else { (cpy.Py_DecRef)(obj); (cpy.PyGILState_Release)(state); return NYB_E_PLUGIN_ERROR; } + if let Ok(mut map) = PYOBJS.lock() { + map.insert(id, PyObjectInstance::default()); + } else { + (cpy.Py_DecRef)(obj); + (cpy.PyGILState_Release)(state); + return NYB_E_PLUGIN_ERROR; + } OBJ_PTRS.lock().unwrap().insert(id, PyPtr(obj)); (cpy.PyGILState_Release)(state); return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); @@ -363,122 +565,237 @@ fn handle_py_runtime(method_id: u32, _instance_id: u32, _args: *const u8, _args_ } } -fn handle_py_object(method_id: u32, instance_id: u32, _args: *const u8, _args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { +fn handle_py_object( + method_id: u32, + instance_id: u32, + _args: *const u8, + _args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { match method_id { PYO_METHOD_BIRTH => NYB_E_INVALID_METHOD, // should be created via runtime PYO_METHOD_FINI => { if let Some(cpy) = &*CPY.lock().unwrap() { if let Some(ptr) = OBJ_PTRS.lock().unwrap().remove(&instance_id) { - unsafe { (cpy.Py_DecRef)(ptr.0); } + unsafe { + (cpy.Py_DecRef)(ptr.0); + } } } - if let Ok(mut map) = PYOBJS.lock() { map.remove(&instance_id); NYB_SUCCESS } else { NYB_E_PLUGIN_ERROR } + if let Ok(mut map) = PYOBJS.lock() { + map.remove(&instance_id); + NYB_SUCCESS + } else { + NYB_E_PLUGIN_ERROR + } } PYO_METHOD_GETATTR | PYO_METHOD_GETATTR_R => { - if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } - let Some(name) = read_arg_string(_args, _args_len, 0) else { return NYB_E_INVALID_ARGS; }; + if ensure_cpython().is_err() { + return NYB_E_PLUGIN_ERROR; + } + let Some(name) = read_arg_string(_args, _args_len, 0) else { + return NYB_E_INVALID_ARGS; + }; if let Some(cpy) = &*CPY.lock().unwrap() { - let Some(obj) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { return NYB_E_INVALID_HANDLE; }; - let c_name = match CString::new(name) { Ok(s) => s, Err(_) => return NYB_E_INVALID_ARGS }; + let Some(obj) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { + return NYB_E_INVALID_HANDLE; + }; + let c_name = match CString::new(name) { + Ok(s) => s, + Err(_) => return NYB_E_INVALID_ARGS, + }; let state = unsafe { (cpy.PyGILState_Ensure)() }; let attr = unsafe { (cpy.PyObject_GetAttrString)(obj.0, c_name.as_ptr()) }; if attr.is_null() { let msg = take_py_error_string(cpy); - unsafe { (cpy.PyGILState_Release)(state); } - if method_id == PYO_METHOD_GETATTR_R { return NYB_E_PLUGIN_ERROR; } - if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + unsafe { + (cpy.PyGILState_Release)(state); + } + if method_id == PYO_METHOD_GETATTR_R { + return NYB_E_PLUGIN_ERROR; + } + if let Some(m) = msg { + return write_tlv_string(&m, result, result_len); + } return NYB_E_PLUGIN_ERROR; } if should_autodecode() && try_write_autodecode(cpy, attr, result, result_len) { - unsafe { (cpy.Py_DecRef)(attr); (cpy.PyGILState_Release)(state); } + unsafe { + (cpy.Py_DecRef)(attr); + (cpy.PyGILState_Release)(state); + } return NYB_SUCCESS; } let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); OBJ_PTRS.lock().unwrap().insert(id, PyPtr(attr)); - unsafe { (cpy.PyGILState_Release)(state); } + unsafe { + (cpy.PyGILState_Release)(state); + } return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); } NYB_E_PLUGIN_ERROR } PYO_METHOD_CALL | PYO_METHOD_CALL_R => { - if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + if ensure_cpython().is_err() { + return NYB_E_PLUGIN_ERROR; + } if let Some(cpy) = &*CPY.lock().unwrap() { - let Some(func) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { return NYB_E_INVALID_HANDLE; }; + let Some(func) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { + return NYB_E_INVALID_HANDLE; + }; let state = unsafe { (cpy.PyGILState_Ensure)() }; // Build tuple from TLV args let argc = tlv_count_args(_args, _args_len); let tuple = unsafe { (cpy.PyTuple_New)(argc as isize) }; - if tuple.is_null() { unsafe { (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + if tuple.is_null() { + unsafe { + (cpy.PyGILState_Release)(state); + } + return NYB_E_PLUGIN_ERROR; + } if !fill_tuple_from_tlv(cpy, tuple, _args, _args_len) { - unsafe { (cpy.Py_DecRef)(tuple); (cpy.PyGILState_Release)(state); } + unsafe { + (cpy.Py_DecRef)(tuple); + (cpy.PyGILState_Release)(state); + } return NYB_E_INVALID_ARGS; } let ret = unsafe { (cpy.PyObject_CallObject)(func.0, tuple) }; - unsafe { (cpy.Py_DecRef)(tuple); } + unsafe { + (cpy.Py_DecRef)(tuple); + } if ret.is_null() { let msg = take_py_error_string(cpy); - unsafe { (cpy.PyGILState_Release)(state); } - if method_id == PYO_METHOD_CALL_R { return NYB_E_PLUGIN_ERROR; } - if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + unsafe { + (cpy.PyGILState_Release)(state); + } + if method_id == PYO_METHOD_CALL_R { + return NYB_E_PLUGIN_ERROR; + } + if let Some(m) = msg { + return write_tlv_string(&m, result, result_len); + } return NYB_E_PLUGIN_ERROR; } if should_autodecode() && try_write_autodecode(cpy, ret, result, result_len) { - unsafe { (cpy.Py_DecRef)(ret); (cpy.PyGILState_Release)(state); } + unsafe { + (cpy.Py_DecRef)(ret); + (cpy.PyGILState_Release)(state); + } return NYB_SUCCESS; } let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); OBJ_PTRS.lock().unwrap().insert(id, PyPtr(ret)); - unsafe { (cpy.PyGILState_Release)(state); } + unsafe { + (cpy.PyGILState_Release)(state); + } return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); } NYB_E_PLUGIN_ERROR } PYO_METHOD_CALL_KW | PYO_METHOD_CALL_KW_R => { - if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + if ensure_cpython().is_err() { + return NYB_E_PLUGIN_ERROR; + } if let Some(cpy) = &*CPY.lock().unwrap() { - let Some(func) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { return NYB_E_INVALID_HANDLE; }; + let Some(func) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { + return NYB_E_INVALID_HANDLE; + }; let state = unsafe { (cpy.PyGILState_Ensure)() }; // Empty args tuple for kwargs-only call let args_tup = unsafe { (cpy.PyTuple_New)(0) }; - if args_tup.is_null() { unsafe { (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + if args_tup.is_null() { + unsafe { + (cpy.PyGILState_Release)(state); + } + return NYB_E_PLUGIN_ERROR; + } // Build kwargs dict from TLV pairs let kwargs = unsafe { (cpy.PyDict_New)() }; - if kwargs.is_null() { unsafe { (cpy.Py_DecRef)(args_tup); (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + if kwargs.is_null() { + unsafe { + (cpy.Py_DecRef)(args_tup); + (cpy.PyGILState_Release)(state); + } + return NYB_E_PLUGIN_ERROR; + } if !fill_kwargs_from_tlv(cpy, kwargs, _args, _args_len) { - unsafe { (cpy.Py_DecRef)(kwargs); (cpy.Py_DecRef)(args_tup); (cpy.PyGILState_Release)(state); } + unsafe { + (cpy.Py_DecRef)(kwargs); + (cpy.Py_DecRef)(args_tup); + (cpy.PyGILState_Release)(state); + } return NYB_E_INVALID_ARGS; } let ret = unsafe { (cpy.PyObject_Call)(func.0, args_tup, kwargs) }; - unsafe { (cpy.Py_DecRef)(kwargs); (cpy.Py_DecRef)(args_tup); } + unsafe { + (cpy.Py_DecRef)(kwargs); + (cpy.Py_DecRef)(args_tup); + } if ret.is_null() { let msg = take_py_error_string(cpy); - unsafe { (cpy.PyGILState_Release)(state); } - if method_id == PYO_METHOD_CALL_KW_R { return NYB_E_PLUGIN_ERROR; } - if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + unsafe { + (cpy.PyGILState_Release)(state); + } + if method_id == PYO_METHOD_CALL_KW_R { + return NYB_E_PLUGIN_ERROR; + } + if let Some(m) = msg { + return write_tlv_string(&m, result, result_len); + } return NYB_E_PLUGIN_ERROR; } - if (method_id == PYO_METHOD_CALL_KW || method_id == PYO_METHOD_CALL_KW_R) && should_autodecode() && try_write_autodecode(cpy, ret, result, result_len) { - unsafe { (cpy.Py_DecRef)(ret); (cpy.PyGILState_Release)(state); } + if (method_id == PYO_METHOD_CALL_KW || method_id == PYO_METHOD_CALL_KW_R) + && should_autodecode() + && try_write_autodecode(cpy, ret, result, result_len) + { + unsafe { + (cpy.Py_DecRef)(ret); + (cpy.PyGILState_Release)(state); + } return NYB_SUCCESS; } let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); OBJ_PTRS.lock().unwrap().insert(id, PyPtr(ret)); - unsafe { (cpy.PyGILState_Release)(state); } + unsafe { + (cpy.PyGILState_Release)(state); + } return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); } NYB_E_PLUGIN_ERROR } PYO_METHOD_STR => { - if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + if ensure_cpython().is_err() { + return NYB_E_PLUGIN_ERROR; + } if let Some(cpy) = &*CPY.lock().unwrap() { - let Some(obj) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { return NYB_E_INVALID_HANDLE; }; + let Some(obj) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { + return NYB_E_INVALID_HANDLE; + }; let state = unsafe { (cpy.PyGILState_Ensure)() }; let s_obj = unsafe { (cpy.PyObject_Str)(obj.0) }; - if s_obj.is_null() { unsafe { (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + if s_obj.is_null() { + unsafe { + (cpy.PyGILState_Release)(state); + } + return NYB_E_PLUGIN_ERROR; + } let cstr = unsafe { (cpy.PyUnicode_AsUTF8)(s_obj) }; - if cstr.is_null() { unsafe { (cpy.Py_DecRef)(s_obj); (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } - let rust_str = unsafe { CStr::from_ptr(cstr) }.to_string_lossy().to_string(); - unsafe { (cpy.Py_DecRef)(s_obj); (cpy.PyGILState_Release)(state); } + if cstr.is_null() { + unsafe { + (cpy.Py_DecRef)(s_obj); + (cpy.PyGILState_Release)(state); + } + return NYB_E_PLUGIN_ERROR; + } + let rust_str = unsafe { CStr::from_ptr(cstr) } + .to_string_lossy() + .to_string(); + unsafe { + (cpy.Py_DecRef)(s_obj); + (cpy.PyGILState_Release)(state); + } return write_tlv_string(&rust_str, result, result_len); } NYB_E_PLUGIN_ERROR @@ -491,7 +808,9 @@ fn handle_py_object(method_id: u32, instance_id: u32, _args: *const u8, _args_le // ===== Minimal TLV helpers (copy from other plugins for consistency) ===== 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; @@ -505,7 +824,12 @@ fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, payload)], result, result_len) } -fn write_tlv_handle(type_id: u32, instance_id: u32, result: *mut u8, result_len: *mut usize) -> i32 { +fn write_tlv_handle( + type_id: u32, + instance_id: u32, + result: *mut u8, + result_len: *mut usize, +) -> i32 { let mut payload = [0u8; 8]; payload[..4].copy_from_slice(&type_id.to_le_bytes()); payload[4..].copy_from_slice(&instance_id.to_le_bytes()); @@ -514,18 +838,26 @@ fn write_tlv_handle(type_id: u32, instance_id: u32, result: *mut u8, result_len: /// Read nth TLV argument as String (tag 6) fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option { - 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 != 6 { return None; } - let slice = &buf[off+4 .. off+4+size]; + if tag != 6 { + return None; + } + let slice = &buf[off + 4..off + 4 + size]; return std::str::from_utf8(slice).ok().map(|s| s.to_string()); } off += 4 + size; @@ -542,8 +874,11 @@ static OBJ_PTRS: Lazy>> = Lazy::new(|| Mutex::new(Hash // Base TLV writer used by 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + let mut buf: Vec = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); 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 { @@ -565,54 +900,85 @@ fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut } fn tlv_count_args(args: *const u8, args_len: usize) -> usize { - if args.is_null() || args_len < 4 { return 0; } + if args.is_null() || args_len < 4 { + return 0; + } let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; - if buf.len() < 4 { return 0; } + if buf.len() < 4 { + return 0; + } let argc = u16::from_le_bytes([buf[2], buf[3]]) as usize; argc } -fn fill_tuple_from_tlv(cpy: &CPython, tuple: *mut PyObject, args: *const u8, args_len: usize) -> bool { - if args.is_null() || args_len < 4 { return true; } +fn fill_tuple_from_tlv( + cpy: &CPython, + tuple: *mut PyObject, + args: *const u8, + args_len: usize, +) -> bool { + if args.is_null() || args_len < 4 { + return true; + } let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; let mut off = 4usize; let mut idx: isize = 0; while off + 4 <= buf.len() { let tag = buf[off]; - let _rsv = buf[off+1]; - let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; - if off + 4 + size > buf.len() { return false; } - let payload = &buf[off+4 .. off+4+size]; + let _rsv = buf[off + 1]; + let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize; + if off + 4 + size > buf.len() { + return false; + } + let payload = &buf[off + 4..off + 4 + size]; let mut obj: *mut PyObject = std::ptr::null_mut(); unsafe { match tag { - 1 => { // Bool (1 byte) + 1 => { + // Bool (1 byte) let v = if size >= 1 && payload[0] != 0 { 1 } else { 0 }; obj = (cpy.PyBool_FromLong)(v); } - 2 => { // I32 - if size != 4 { return false; } - let mut b = [0u8;4]; b.copy_from_slice(payload); + 2 => { + // I32 + if size != 4 { + return false; + } + let mut b = [0u8; 4]; + b.copy_from_slice(payload); let v = i32::from_le_bytes(b) as i64; obj = (cpy.PyLong_FromLongLong)(v); } - 3 => { // I64 - if size != 8 { return false; } - let mut b = [0u8;8]; b.copy_from_slice(payload); + 3 => { + // I64 + if size != 8 { + return false; + } + let mut b = [0u8; 8]; + b.copy_from_slice(payload); let v = i64::from_le_bytes(b); obj = (cpy.PyLong_FromLongLong)(v); } - 5 => { // F64 - if size != 8 { return false; } - let mut b = [0u8;8]; b.copy_from_slice(payload); + 5 => { + // F64 + if size != 8 { + return false; + } + let mut b = [0u8; 8]; + b.copy_from_slice(payload); let v = f64::from_le_bytes(b); obj = (cpy.PyFloat_FromDouble)(v); } - 6 => { // String - let c = match CString::new(payload) { Ok(c) => c, Err(_) => return false }; + 6 => { + // String + let c = match CString::new(payload) { + Ok(c) => c, + Err(_) => return false, + }; obj = (cpy.PyUnicode_FromString)(c.as_ptr()); } - 7 => { // Bytes + 7 => { + // Bytes if size > 0 { let ptr = payload.as_ptr() as *const c_char; obj = (cpy.PyBytes_FromStringAndSize)(ptr, size as isize); @@ -620,23 +986,36 @@ fn fill_tuple_from_tlv(cpy: &CPython, tuple: *mut PyObject, args: *const u8, arg obj = (cpy.PyBytes_FromStringAndSize)(std::ptr::null(), 0); } } - 8 => { // Handle - if size != 8 { return false; } - let mut t = [0u8;4]; t.copy_from_slice(&payload[0..4]); - let mut i = [0u8;4]; i.copy_from_slice(&payload[4..8]); + 8 => { + // Handle + if size != 8 { + return false; + } + 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 _type_id = u32::from_le_bytes(t); let inst_id = u32::from_le_bytes(i); if let Some(ptr) = OBJ_PTRS.lock().unwrap().get(&inst_id).copied() { // PyTuple_SetItem steals a reference; INCREF to keep original alive (cpy.Py_IncRef)(ptr.0); obj = ptr.0; - } else { return false; } + } else { + return false; + } + } + _ => { + return false; } - _ => { return false; } } - if obj.is_null() { return false; } + if obj.is_null() { + return false; + } let rc = (cpy.PyTuple_SetItem)(tuple, idx, obj); - if rc != 0 { return false; } + if rc != 0 { + return false; + } idx += 1; } off += 4 + size; @@ -646,16 +1025,25 @@ fn fill_tuple_from_tlv(cpy: &CPython, tuple: *mut PyObject, args: *const u8, arg fn take_py_error_string(cpy: &CPython) -> Option { unsafe { - if (cpy.PyErr_Occurred)().is_null() { return None; } + if (cpy.PyErr_Occurred)().is_null() { + return None; + } let mut ptype: *mut PyObject = std::ptr::null_mut(); let mut pvalue: *mut PyObject = std::ptr::null_mut(); let mut ptrace: *mut PyObject = std::ptr::null_mut(); (cpy.PyErr_Fetch)(&mut ptype, &mut pvalue, &mut ptrace); let s = if !pvalue.is_null() { let sobj = (cpy.PyObject_Str)(pvalue); - if sobj.is_null() { (cpy.PyErr_Clear)(); return Some("Python error".to_string()); } + if sobj.is_null() { + (cpy.PyErr_Clear)(); + return Some("Python error".to_string()); + } let cstr = (cpy.PyUnicode_AsUTF8)(sobj); - let msg = if cstr.is_null() { "Python error".to_string() } else { CStr::from_ptr(cstr).to_string_lossy().to_string() }; + let msg = if cstr.is_null() { + "Python error".to_string() + } else { + CStr::from_ptr(cstr).to_string_lossy().to_string() + }; (cpy.Py_DecRef)(sobj); msg } else { @@ -666,75 +1054,156 @@ fn take_py_error_string(cpy: &CPython) -> Option { } } -fn fill_kwargs_from_tlv(cpy: &CPython, dict: *mut PyObject, args: *const u8, args_len: usize) -> bool { - if args.is_null() || args_len < 4 { return true; } +fn fill_kwargs_from_tlv( + cpy: &CPython, + dict: *mut PyObject, + args: *const u8, + args_len: usize, +) -> bool { + if args.is_null() || args_len < 4 { + return true; + } let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; let mut off = 4usize; loop { - if off + 4 > buf.len() { break; } + if off + 4 > buf.len() { + break; + } let tag_k = buf[off]; - let _rsv = buf[off+1]; - let size_k = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; - if tag_k != 6 || off + 4 + size_k > buf.len() { return false; } - let key_bytes = &buf[off+4 .. off+4+size_k]; + let _rsv = buf[off + 1]; + let size_k = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize; + if tag_k != 6 || off + 4 + size_k > buf.len() { + return false; + } + let key_bytes = &buf[off + 4..off + 4 + size_k]; off += 4 + size_k; - if off + 4 > buf.len() { return false; } + if off + 4 > buf.len() { + return false; + } let tag_v = buf[off]; - let _rsv2 = buf[off+1]; - let size_v = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; - if off + 4 + size_v > buf.len() { return false; } - let val_payload = &buf[off+4 .. off+4+size_v]; + let _rsv2 = buf[off + 1]; + let size_v = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize; + if off + 4 + size_v > buf.len() { + return false; + } + let val_payload = &buf[off + 4..off + 4 + size_v]; off += 4 + size_v; unsafe { - let key_c = match CString::new(key_bytes) { Ok(c) => c, Err(_) => return false }; + let key_c = match CString::new(key_bytes) { + Ok(c) => c, + Err(_) => return false, + }; let mut obj: *mut PyObject = std::ptr::null_mut(); match tag_v { - 1 => { let v = if size_v >= 1 && val_payload[0] != 0 { 1 } else { 0 }; obj = (cpy.PyBool_FromLong)(v); } - 2 => { if size_v != 4 { return false; } let mut b = [0u8;4]; b.copy_from_slice(val_payload); obj = (cpy.PyLong_FromLongLong)(i32::from_le_bytes(b) as i64); } - 3 => { if size_v != 8 { return false; } let mut b=[0u8;8]; b.copy_from_slice(val_payload); obj = (cpy.PyLong_FromLongLong)(i64::from_le_bytes(b)); } - 5 => { if size_v != 8 { return false; } let mut b=[0u8;8]; b.copy_from_slice(val_payload); obj = (cpy.PyFloat_FromDouble)(f64::from_le_bytes(b)); } - 6 => { let c = match CString::new(val_payload) { Ok(c) => c, Err(_) => return false }; obj = (cpy.PyUnicode_FromString)(c.as_ptr()); } - 7 => { let ptr = if size_v>0 { val_payload.as_ptr() as *const c_char } else { std::ptr::null() }; obj = (cpy.PyBytes_FromStringAndSize)(ptr, size_v as isize); } + 1 => { + let v = if size_v >= 1 && val_payload[0] != 0 { + 1 + } else { + 0 + }; + obj = (cpy.PyBool_FromLong)(v); + } + 2 => { + if size_v != 4 { + return false; + } + let mut b = [0u8; 4]; + b.copy_from_slice(val_payload); + obj = (cpy.PyLong_FromLongLong)(i32::from_le_bytes(b) as i64); + } + 3 => { + if size_v != 8 { + return false; + } + let mut b = [0u8; 8]; + b.copy_from_slice(val_payload); + obj = (cpy.PyLong_FromLongLong)(i64::from_le_bytes(b)); + } + 5 => { + if size_v != 8 { + return false; + } + let mut b = [0u8; 8]; + b.copy_from_slice(val_payload); + obj = (cpy.PyFloat_FromDouble)(f64::from_le_bytes(b)); + } + 6 => { + let c = match CString::new(val_payload) { + Ok(c) => c, + Err(_) => return false, + }; + obj = (cpy.PyUnicode_FromString)(c.as_ptr()); + } + 7 => { + let ptr = if size_v > 0 { + val_payload.as_ptr() as *const c_char + } else { + std::ptr::null() + }; + obj = (cpy.PyBytes_FromStringAndSize)(ptr, size_v as isize); + } 8 => { - if size_v != 8 { return false; } - let mut i = [0u8;4]; i.copy_from_slice(&val_payload[4..8]); + if size_v != 8 { + return false; + } + let mut i = [0u8; 4]; + i.copy_from_slice(&val_payload[4..8]); let inst_id = u32::from_le_bytes(i); - if let Some(p) = OBJ_PTRS.lock().unwrap().get(&inst_id).copied() { (cpy.Py_IncRef)(p.0); obj = p.0; } else { return false; } + if let Some(p) = OBJ_PTRS.lock().unwrap().get(&inst_id).copied() { + (cpy.Py_IncRef)(p.0); + obj = p.0; + } else { + return false; + } } _ => return false, } - if obj.is_null() { return false; } + if obj.is_null() { + return false; + } let rc = (cpy.PyDict_SetItemString)(dict, key_c.as_ptr(), obj); (cpy.Py_DecRef)(obj); - if rc != 0 { return false; } + if rc != 0 { + return false; + } } } true } fn should_autodecode() -> bool { - std::env::var("NYASH_PY_AUTODECODE").map(|v| v != "0").unwrap_or(false) + std::env::var("NYASH_PY_AUTODECODE") + .map(|v| v != "0") + .unwrap_or(false) } -fn try_write_autodecode(cpy: &CPython, obj: *mut PyObject, result: *mut u8, result_len: *mut usize) -> bool { +fn try_write_autodecode( + cpy: &CPython, + obj: *mut PyObject, + result: *mut u8, + result_len: *mut usize, +) -> bool { unsafe { // Float -> tag=5 let mut had_err = false; let f = (cpy.PyFloat_AsDouble)(obj); if !(cpy.PyErr_Occurred)().is_null() { - had_err = true; (cpy.PyErr_Clear)(); + had_err = true; + (cpy.PyErr_Clear)(); } if !had_err { eprintln!("[PyPlugin] autodecode: Float {}", f); - let mut payload = [0u8;8]; + let mut payload = [0u8; 8]; payload.copy_from_slice(&f.to_le_bytes()); let rc = write_tlv_result(&[(5u8, &payload)], result, result_len); return rc == NYB_SUCCESS || rc == NYB_E_SHORT_BUFFER; } // Integer (PyLong) -> tag=3 (i64) let i = (cpy.PyLong_AsLongLong)(obj); - if !(cpy.PyErr_Occurred)().is_null() { (cpy.PyErr_Clear)(); } else { + if !(cpy.PyErr_Occurred)().is_null() { + (cpy.PyErr_Clear)(); + } else { eprintln!("[PyPlugin] autodecode: I64 {}", i); - let mut payload = [0u8;8]; + let mut payload = [0u8; 8]; payload.copy_from_slice(&i.to_le_bytes()); let rc = write_tlv_result(&[(3u8, &payload)], result, result_len); return rc == NYB_SUCCESS || rc == NYB_E_SHORT_BUFFER; @@ -757,7 +1226,9 @@ fn try_write_autodecode(cpy: &CPython, obj: *mut PyObject, result: *mut u8, resu eprintln!("[PyPlugin] autodecode: Bytes {} bytes", sz); let rc = write_tlv_result(&[(7u8, slice)], result, result_len); return rc == NYB_SUCCESS || rc == NYB_E_SHORT_BUFFER; - } else if !(cpy.PyErr_Occurred)().is_null() { (cpy.PyErr_Clear)(); } + } else if !(cpy.PyErr_Occurred)().is_null() { + (cpy.PyErr_Clear)(); + } false } } diff --git a/plugins/nyash-regex-plugin/src/lib.rs b/plugins/nyash-regex-plugin/src/lib.rs index e2aacd82..039edce8 100644 --- a/plugins/nyash-regex-plugin/src/lib.rs +++ b/plugins/nyash-regex-plugin/src/lib.rs @@ -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; @@ -15,27 +18,33 @@ const E_PLUGIN: i32 = -5; const E_HANDLE: i32 = -8; // Methods -const M_BIRTH: u32 = 0; // birth(pattern?) -> instance -const M_COMPILE: u32 = 1; // compile(pattern) -> self (new compiled) -const M_IS_MATCH: u32 = 2; // isMatch(text) -> bool -const M_FIND: u32 = 3; // find(text) -> String (first match or empty) +const M_BIRTH: u32 = 0; // birth(pattern?) -> instance +const M_COMPILE: u32 = 1; // compile(pattern) -> self (new compiled) +const M_IS_MATCH: u32 = 2; // isMatch(text) -> bool +const M_FIND: u32 = 3; // find(text) -> String (first match or empty) const M_REPLACE_ALL: u32 = 4; // replaceAll(text, repl) -> String -const M_SPLIT: u32 = 5; // split(text, limit) -> String (joined by '\n') minimal +const M_SPLIT: u32 = 5; // split(text, limit) -> String (joined by '\n') minimal 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 } +struct RegexInstance { + re: Option, +} static INST: Lazy>> = 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 = 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 = 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); - 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 = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); + 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 { - 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 { - 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 } - diff --git a/plugins/nyash-string-plugin/src/lib.rs b/plugins/nyash-string-plugin/src/lib.rs index bc0a9757..a0045f0f 100644 --- a/plugins/nyash-string-plugin/src/lib.rs +++ b/plugins/nyash-string-plugin/src/lib.rs @@ -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; @@ -19,23 +22,29 @@ const M_BIRTH: u32 = 0; const M_LENGTH: u32 = 1; const M_IS_EMPTY: u32 = 2; const M_CHAR_CODE_AT: u32 = 3; -const M_CONCAT: u32 = 4; // concat(other: String|Handle) -> Handle(new) -const M_FROM_UTF8: u32 = 5; // fromUtf8(data: String|Bytes) -> Handle(new) -const M_TO_UTF8: u32 = 6; // toUtf8() -> String +const M_CONCAT: u32 = 4; // concat(other: String|Handle) -> Handle(new) +const M_FROM_UTF8: u32 = 5; // fromUtf8(data: String|Bytes) -> Handle(new) +const M_TO_UTF8: u32 = 6; // toUtf8() -> String const M_FINI: u32 = u32::MAX; const TYPE_ID_STRING: u32 = 13; -struct StrInstance { s: String } +struct StrInstance { + s: String, +} static INST: Lazy>> = 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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); - 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 = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); + 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 { - 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 { - 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 } diff --git a/plugins/nyash-test-multibox/src/lib.rs b/plugins/nyash-test-multibox/src/lib.rs index 986ef7dc..77db05b9 100644 --- a/plugins/nyash-test-multibox/src/lib.rs +++ b/plugins/nyash-test-multibox/src/lib.rs @@ -1,5 +1,5 @@ //! Test Multi-Box Plugin for Nyash -//! +//! //! Provides TestBoxA and TestBoxB to demonstrate multi-box plugin support use std::collections::HashMap; @@ -125,26 +125,26 @@ static TESTBOX_B_INFO: NyashPluginInfo = NyashPluginInfo { #[no_mangle] pub extern "C" fn nyash_plugin_abi() -> u32 { - 1 // BID-1 ABI version + 1 // BID-1 ABI version } #[no_mangle] pub extern "C" fn nyash_plugin_init( host: *const NyashHostVtable, - _info: *mut std::ffi::c_void, // For v2, we use get_box_info instead + _info: *mut std::ffi::c_void, // For v2, we use get_box_info instead ) -> i32 { if host.is_null() { return NYB_E_INVALID_ARGS; } - + unsafe { HOST_VTABLE = Some(&*host); INSTANCES = Some(Mutex::new(HashMap::new())); - + // Log initialization log_info("Multi-box test plugin initialized"); } - + NYB_SUCCESS } @@ -152,7 +152,7 @@ pub extern "C" fn nyash_plugin_init( #[no_mangle] pub extern "C" fn nyash_plugin_get_box_count() -> u32 { - 2 // TestBoxA and TestBoxB + 2 // TestBoxA and TestBoxB } #[no_mangle] @@ -169,7 +169,7 @@ pub extern "C" fn nyash_plugin_get_type_id(box_name: *const c_char) -> u32 { if box_name.is_null() { return 0; } - + unsafe { let name = std::ffi::CStr::from_ptr(box_name).to_string_lossy(); match name.as_ref() { @@ -195,27 +195,15 @@ 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, } } @@ -228,17 +216,20 @@ unsafe fn create_instance_a(result: *mut u8, result_len: *mut usize) -> i32 { if let Ok(mut map) = mutex.lock() { let id = INSTANCE_COUNTER; INSTANCE_COUNTER += 1; - - map.insert(id, TestInstance::BoxA { - message: "Hello from TestBoxA!".to_string(), - }); - + + map.insert( + id, + TestInstance::BoxA { + message: "Hello from TestBoxA!".to_string(), + }, + ); + // Return instance ID if *result_len >= 4 { let bytes = id.to_le_bytes(); ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4); *result_len = 4; - + log_info(&format!("Created TestBoxA instance {}", id)); return NYB_SUCCESS; } @@ -252,17 +243,15 @@ unsafe fn create_instance_b(result: *mut u8, result_len: *mut usize) -> i32 { if let Ok(mut map) = mutex.lock() { 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 { let bytes = id.to_le_bytes(); ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4); *result_len = 4; - + log_info(&format!("Created TestBoxB instance {}", id)); return NYB_SUCCESS; } @@ -294,7 +283,7 @@ unsafe fn greet_method(instance_id: u32, result: *mut u8, result_len: *mut usize if let Some(TestInstance::BoxB { counter }) = map.get_mut(&instance_id) { *counter += 1; let message = format!("Greeting #{} from TestBoxB!", counter); - + // Return message as TLV string write_tlv_string(&message, result, result_len) } else { @@ -329,28 +318,28 @@ unsafe fn destroy_instance(instance_id: u32) -> i32 { unsafe fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { let bytes = s.as_bytes(); - let needed = 8 + bytes.len(); // header(4) + entry(4) + string - + let needed = 8 + bytes.len(); // header(4) + entry(4) + string + if *result_len < needed { return NYB_E_INVALID_ARGS; } - + // TLV header - *result = 1; // version low - *result.offset(1) = 0; // version high - *result.offset(2) = 1; // argc low - *result.offset(3) = 0; // argc high - + *result = 1; // version low + *result.offset(1) = 0; // version high + *result.offset(2) = 1; // argc low + *result.offset(3) = 0; // argc high + // String entry - *result.offset(4) = 6; // Tag::String - *result.offset(5) = 0; // padding + *result.offset(4) = 6; // Tag::String + *result.offset(5) = 0; // padding let len_bytes = (bytes.len() as u16).to_le_bytes(); *result.offset(6) = len_bytes[0]; *result.offset(7) = len_bytes[1]; - + // String data ptr::copy_nonoverlapping(bytes.as_ptr(), result.offset(8), bytes.len()); - + *result_len = needed; NYB_SUCCESS } @@ -370,4 +359,4 @@ pub extern "C" fn nyash_plugin_shutdown() { INSTANCES = None; HOST_VTABLE = None; } -} \ No newline at end of file +} diff --git a/plugins/nyash-toml-plugin/src/lib.rs b/plugins/nyash-toml-plugin/src/lib.rs index 87edd7f5..5f16ad27 100644 --- a/plugins/nyash-toml-plugin/src/lib.rs +++ b/plugins/nyash-toml-plugin/src/lib.rs @@ -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; @@ -12,24 +15,30 @@ const E_ARGS: i32 = -4; const E_PLUGIN: i32 = -5; const E_HANDLE: i32 = -8; -const M_BIRTH: u32 = 0; // constructor -> instance -const M_PARSE: u32 = 1; // parse(text) -> bool -const M_GET: u32 = 2; // get(path.dot.segments) -> string (toml-display) or empty -const M_TO_JSON: u32 = 3; // toJson() -> string (JSON) +const M_BIRTH: u32 = 0; // constructor -> instance +const M_PARSE: u32 = 1; // parse(text) -> bool +const M_GET: u32 = 2; // get(path.dot.segments) -> string (toml-display) or empty +const M_TO_JSON: u32 = 3; // toJson() -> string (JSON) const M_FINI: u32 = u32::MAX; // fini() const TYPE_ID_TOML: u32 = 54; -struct TomlInstance { value: Option } +struct TomlInstance { + value: Option, +} static INST: Lazy>> = 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; } + 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 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::(&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::(&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 = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); - 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 = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); + 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 { - 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 { + 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 +} diff --git a/src/backend/llvm/compiler/codegen/instructions/flow.rs b/src/backend/llvm/compiler/codegen/instructions/flow.rs index ff6760eb..087c4549 100644 --- a/src/backend/llvm/compiler/codegen/instructions/flow.rs +++ b/src/backend/llvm/compiler/codegen/instructions/flow.rs @@ -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); } diff --git a/src/backend/llvm/compiler/codegen/instructions/mem.rs b/src/backend/llvm/compiler/codegen/instructions/mem.rs index cfa47949..27dbe02b 100644 --- a/src/backend/llvm/compiler/codegen/instructions/mem.rs +++ b/src/backend/llvm/compiler/codegen/instructions/mem.rs @@ -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>, + dst: &ValueId, + src: &ValueId, + bb_map: &std::collections::HashMap>, + preds: &std::collections::HashMap>, + block_end_values: &std::collections::HashMap>>, +) -> Result<(), String> { + // Choose resolution kind based on metadata type preference + use inkwell::types::BasicTypeEnum as BT; + let expected_bt: Option = 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(()) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/mod.rs b/src/backend/llvm/compiler/codegen/instructions/mod.rs index 73fa16c5..01bb3d25 100644 --- a/src/backend/llvm/compiler/codegen/instructions/mod.rs +++ b/src/backend/llvm/compiler/codegen/instructions/mod.rs @@ -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; diff --git a/src/backend/llvm/compiler/codegen/mod.rs b/src/backend/llvm/compiler/codegen/mod.rs index 112ad12f..3694ceb0 100644 --- a/src/backend/llvm/compiler/codegen/mod.rs +++ b/src/backend/llvm/compiler/codegen/mod.rs @@ -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 diff --git a/src/bin/ny_mir_builder.rs b/src/bin/ny_mir_builder.rs index 8701792c..fc299a2b 100644 --- a/src/bin/ny_mir_builder.rs +++ b/src/bin/ny_mir_builder.rs @@ -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::("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::("emit").unwrap().as_str(); - let out_path = matches.get_one::("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 out_path = matches + .get_one::("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::("nyrt").map(|s| s.to_string()).unwrap_or("crates/nyrt".to_string()); + let nyrt_dir = matches + .get_one::("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())) + } } } diff --git a/src/bin/test_plugin_loader_v2.rs b/src/bin/test_plugin_loader_v2.rs index 78f65b6b..5d36e400 100644 --- a/src/bin/test_plugin_loader_v2.rs +++ b/src/bin/test_plugin_loader_v2.rs @@ -5,13 +5,13 @@ use nyash_rust::runtime::{get_global_loader_v2, init_global_loader_v2}; fn main() { env_logger::init(); - + println!("=== v2 Plugin Loader Test (Phase 12 prep) ===\n"); - + // Load configuration let config_path = "test_nyash_v2.toml"; println!("Loading configuration from: {}", config_path); - + let config = match NyashConfigV2::from_file(config_path) { Ok(cfg) => cfg, Err(e) => { @@ -19,10 +19,10 @@ fn main() { return; } }; - + println!("Configuration loaded successfully!"); println!("Is v2 format: {}", config.is_v2_format()); - + if let Some(libs) = &config.plugins.libraries { println!("\nLibraries found:"); for (name, lib) in libs { @@ -30,7 +30,7 @@ fn main() { println!(" Provides: {:?}", lib.provides); } } - + // Initialize and load plugins println!("\nLoading plugins..."); if let Err(e) = init_global_loader_v2(config_path) { @@ -39,12 +39,14 @@ fn main() { } let loader = get_global_loader_v2(); let loader = loader.read().unwrap(); - + // Test box type resolution 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), } } @@ -55,12 +57,13 @@ fn main() { } else { println!("create_box(StringBox) not available or failed (ok for stub)"); } - + // Simple reverse host-call exercise (simulate plugin calling host via C ABI by-slot) 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); + arr.push(Box::new(nyash_rust::box_trait::StringBox::new("init")) + as Box); 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!"); } diff --git a/src/config/env.rs b/src/config/env.rs index 66dad854..4773eb01 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -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) diff --git a/src/jit/lower/builder/rt_shims.rs b/src/jit/lower/builder/rt_shims.rs index fe5606f0..a087a715 100644 --- a/src/jit/lower/builder/rt_shims.rs +++ b/src/jit/lower/builder/rt_shims.rs @@ -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 = 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 = 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 = 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 = 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; } } } _ => {} diff --git a/src/jit/lower/extern_thunks.rs b/src/jit/lower/extern_thunks.rs index e2988e83..53e9b2f3 100644 --- a/src/jit/lower/extern_thunks.rs +++ b/src/jit/lower/extern_thunks.rs @@ -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 = 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", ""); + events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": meta.box_type, "type_id": meta.type_id, "handle": h}), "hostcall", ""); 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", ""); + 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", ""); } } } 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: Optioni32> = 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::() { 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", ""); + // 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", ""); 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", ""); 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 = 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", ""); diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 7da10858..25e309fc 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -87,12 +87,16 @@ pub struct MirBuilder { /// Top of stack corresponds to the innermost active loop pub(super) loop_header_stack: Vec, pub(super) loop_exit_stack: Vec, + + /// 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, } } @@ -651,6 +656,42 @@ impl MirBuilder { Err("No current function".to_string()) } } + + 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 diff --git a/src/mir/builder/exprs_peek.rs b/src/mir/builder/exprs_peek.rs index eea75d52..a0bc96f5 100644 --- a/src/mir/builder/exprs_peek.rs +++ b/src/mir/builder/exprs_peek.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)?; - self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?; + 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)?; - self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?; + 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) } } diff --git a/src/mir/builder/phi.rs b/src/mir/builder/phi.rs index d39dfdae..fc379c43 100644 --- a/src/mir/builder/phi.rs +++ b/src/mir/builder/phi.rs @@ -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 diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 3db9d68c..d956510e 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -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)>, + + /// 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 { 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,10 +180,14 @@ 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); } - + // 不完全なPhi nodeを記録 self.incomplete_phis.insert(header_id, incomplete_phis); @@ -208,7 +217,16 @@ impl<'a> LoopBuilder<'a> { phi.known_inputs.push((latch_id, value_after)); - self.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?; + if self.no_phi_mode { + let mut seen: HashSet = 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(); - self.emit_phi_at_block_start(merge_bb, phi_id, vec![(then_bb, tv), (else_bb, ev)])?; + 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); diff --git a/src/mir/verification.rs b/src/mir/verification.rs index 23ef02d9..e1c6a1b4 100644 --- a/src/mir/verification.rs +++ b/src/mir/verification.rs @@ -428,6 +428,11 @@ impl MirVerifier { /// Verify SSA form properties fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec> { + // 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> { + // 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> { + // 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); diff --git a/src/runner/json_v0_bridge.rs b/src/runner/json_v0_bridge.rs index 18646753..4cb7185a 100644 --- a/src/runner/json_v0_bridge.rs +++ b/src/runner/json_v0_bridge.rs @@ -1,8 +1,8 @@ -use serde::{Deserialize, Serialize}; use crate::mir::{ - MirModule, MirFunction, FunctionSignature, BasicBlockId, MirInstruction, - ConstValue, BinaryOp, MirType, EffectMask, MirPrinter, ValueId, + BasicBlockId, BinaryOp, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction, + MirModule, MirPrinter, MirType, ValueId, }; +use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] struct ProgramV0 { @@ -11,29 +11,55 @@ struct ProgramV0 { body: Vec, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(tag = "type")] enum StmtV0 { - Return { expr: ExprV0 }, - Extern { iface: String, method: String, args: Vec }, + Return { + expr: ExprV0, + }, + Extern { + iface: String, + method: String, + args: Vec, + }, // Optional: expression statement (side effects only) - Expr { expr: ExprV0 }, + Expr { + expr: ExprV0, + }, // Optional: local binding (Stage-2) - Local { name: String, expr: ExprV0 }, + Local { + name: String, + expr: ExprV0, + }, // Optional: if/else (Stage-2) - If { cond: ExprV0, then: Vec, #[serde(rename="else", default)] r#else: Option> }, + If { + cond: ExprV0, + then: Vec, + #[serde(rename = "else", default)] + r#else: Option>, + }, // Optional: loop (Stage-2) - Loop { cond: ExprV0, body: Vec }, + Loop { + cond: ExprV0, + body: Vec, + }, Break, Continue, - Try { #[serde(rename="try")] try_body: Vec, #[serde(default)] catches: Vec, #[serde(default)] finally: Vec }, + Try { + #[serde(rename = "try")] + try_body: Vec, + #[serde(default)] + catches: Vec, + #[serde(default)] + finally: Vec, + }, } #[derive(Debug, Deserialize, Serialize, Clone, Default)] struct CatchV0 { - #[serde(rename="param", default)] + #[serde(rename = "param", default)] param: Option, - #[serde(rename="typeHint", default)] + #[serde(rename = "typeHint", default)] type_hint: Option, #[serde(default)] body: Vec, @@ -42,47 +68,130 @@ struct CatchV0 { #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(tag = "type")] enum ExprV0 { - Int { value: serde_json::Value }, - Str { value: String }, - Bool { value: bool }, - Binary { op: String, lhs: Box, rhs: Box }, - Extern { iface: String, method: String, args: Vec }, - Compare { op: String, lhs: Box, rhs: Box }, - Logical { op: String, lhs: Box, rhs: Box }, // short-circuit: &&, || (or: "and"/"or") + Int { + value: serde_json::Value, + }, + Str { + value: String, + }, + Bool { + value: bool, + }, + Binary { + op: String, + lhs: Box, + rhs: Box, + }, + Extern { + iface: String, + method: String, + args: Vec, + }, + Compare { + op: String, + lhs: Box, + rhs: Box, + }, + Logical { + op: String, + lhs: Box, + rhs: Box, + }, // short-circuit: &&, || (or: "and"/"or") // Stage-2 additions (optional): - Call { name: String, args: Vec }, - Method { recv: Box, method: String, args: Vec }, - New { class: String, args: Vec }, - Var { name: String }, - Throw { expr: Box }, + Call { + name: String, + args: Vec, + }, + Method { + recv: Box, + method: String, + args: Vec, + }, + New { + class: String, + args: Vec, + }, + Var { + name: String, + }, + Throw { + expr: Box, + }, +} + +#[derive(Clone, Copy)] +struct LoopContext { + cond_bb: BasicBlockId, + exit_bb: BasicBlockId, +} + +fn lower_throw( + f: &mut MirFunction, + cur_bb: BasicBlockId, + exception_value: ValueId, +) -> (ValueId, BasicBlockId) { + if std::env::var("NYASH_BRIDGE_THROW_ENABLE").ok().as_deref() == Some("1") { + if let Some(bb) = f.get_block_mut(cur_bb) { + bb.set_terminator(MirInstruction::Throw { + exception: exception_value, + effects: EffectMask::PANIC, + }); + } + (exception_value, cur_bb) + } else { + let dst = f.next_value_id(); + if let Some(bb) = f.get_block_mut(cur_bb) { + bb.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::Integer(0), + }); + } + (dst, cur_bb) + } } pub fn parse_json_v0_to_module(json: &str) -> Result { - let prog: ProgramV0 = serde_json::from_str(json).map_err(|e| format!("invalid JSON v0: {}", e))?; + let prog: ProgramV0 = + serde_json::from_str(json).map_err(|e| format!("invalid JSON v0: {}", e))?; if prog.version != 0 || prog.kind != "Program" { return Err("unsupported IR: expected {version:0, kind:\"Program\"}".into()); } // Create module and main function let mut module = MirModule::new("ny_json_v0".into()); - let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE }; + let sig = FunctionSignature { + name: "main".into(), + params: vec![], + return_type: MirType::Integer, + effects: EffectMask::PURE, + }; let entry = BasicBlockId::new(0); let mut f = MirFunction::new(sig, entry); - - if prog.body.is_empty() { return Err("empty body".into()); } + + if prog.body.is_empty() { + return Err("empty body".into()); + } // Variable map for simple locals (Stage-2; currently minimal) - let mut var_map: std::collections::HashMap = std::collections::HashMap::new(); + let mut var_map: std::collections::HashMap = + std::collections::HashMap::new(); + let mut loop_stack: Vec = Vec::new(); let start_bb = f.entry_block; - let end_bb = lower_stmt_list_with_vars(&mut f, start_bb, &prog.body, &mut var_map)?; + let end_bb = + lower_stmt_list_with_vars(&mut f, start_bb, &prog.body, &mut var_map, &mut loop_stack)?; // Ensure function terminates: add `ret 0` to last un-terminated block (prefer end_bb else entry) - let need_default_ret = f.blocks.iter().any(|(_k,b)| !b.is_terminated()); + let need_default_ret = f.blocks.iter().any(|(_k, b)| !b.is_terminated()); if need_default_ret { let target_bb = end_bb; let dst_id = f.next_value_id(); if let Some(bb) = f.get_block_mut(target_bb) { if !bb.is_terminated() { - bb.add_instruction(MirInstruction::Const { dst: dst_id, value: ConstValue::Integer(0) }); - bb.set_terminator(MirInstruction::Return { value: Some(dst_id) }); + bb.add_instruction(MirInstruction::Const { + dst: dst_id, + value: ConstValue::Integer(0), + }); + bb.set_terminator(MirInstruction::Return { + value: Some(dst_id), + }); } } } @@ -94,54 +203,94 @@ pub fn parse_json_v0_to_module(json: &str) -> Result { fn next_block_id(f: &MirFunction) -> BasicBlockId { let mut mx = 0u32; - for k in f.blocks.keys() { if k.0 >= mx { mx = k.0 + 1; } } + for k in f.blocks.keys() { + if k.0 >= mx { + mx = k.0 + 1; + } + } BasicBlockId::new(mx) } -fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(crate::mir::ValueId, BasicBlockId), String> { +fn lower_expr( + f: &mut MirFunction, + cur_bb: BasicBlockId, + e: &ExprV0, +) -> Result<(crate::mir::ValueId, BasicBlockId), String> { match e { ExprV0::Int { value } => { // Accept number or stringified digits let ival: i64 = if let Some(n) = value.as_i64() { n - } else if let Some(s) = value.as_str() { s.parse().map_err(|_| "invalid int literal")? } else { + } else if let Some(s) = value.as_str() { + s.parse().map_err(|_| "invalid int literal")? + } else { return Err("invalid int literal".into()); }; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(ival) }); + bb.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::Integer(ival), + }); } Ok((dst, cur_bb)) } ExprV0::Str { value } => { let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::String(value.clone()) }); + bb.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::String(value.clone()), + }); } Ok((dst, cur_bb)) } ExprV0::Bool { value } => { let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Bool(*value) }); + bb.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::Bool(*value), + }); } Ok((dst, cur_bb)) } ExprV0::Binary { op, lhs, rhs } => { let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?; let (r, cur_after_r) = lower_expr(f, cur_after_l, rhs)?; - let bop = match op.as_str() { "+" => BinaryOp::Add, "-" => BinaryOp::Sub, "*" => BinaryOp::Mul, "/" => BinaryOp::Div, _ => return Err("unsupported op".into()) }; + let bop = match op.as_str() { + "+" => BinaryOp::Add, + "-" => BinaryOp::Sub, + "*" => BinaryOp::Mul, + "/" => BinaryOp::Div, + _ => return Err("unsupported op".into()), + }; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_after_r) { - bb.add_instruction(MirInstruction::BinOp { dst, op: bop, lhs: l, rhs: r }); + bb.add_instruction(MirInstruction::BinOp { + dst, + op: bop, + lhs: l, + rhs: r, + }); } Ok((dst, cur_after_r)) } - ExprV0::Extern { iface, method, args } => { + ExprV0::Extern { + iface, + method, + args, + } => { let (arg_ids, cur2) = lower_args(f, cur_bb, args)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur2) { - bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO }); + bb.add_instruction(MirInstruction::ExternCall { + dst: Some(dst), + iface_name: iface.clone(), + method_name: method.clone(), + args: arg_ids, + effects: EffectMask::IO, + }); } Ok((dst, cur2)) } @@ -151,20 +300,25 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( let cop = match op.as_str() { "==" => crate::mir::CompareOp::Eq, "!=" => crate::mir::CompareOp::Ne, - "<" => crate::mir::CompareOp::Lt, + "<" => crate::mir::CompareOp::Lt, "<=" => crate::mir::CompareOp::Le, - ">" => crate::mir::CompareOp::Gt, + ">" => crate::mir::CompareOp::Gt, ">=" => crate::mir::CompareOp::Ge, _ => return Err("unsupported compare op".into()), }; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_after_r) { - bb.add_instruction(MirInstruction::Compare { dst, op: cop, lhs: l, rhs: r }); + bb.add_instruction(MirInstruction::Compare { + dst, + op: cop, + lhs: l, + rhs: r, + }); } Ok((dst, cur_after_r)) } ExprV0::Logical { op, lhs, rhs } => { - // Short-circuit boolean logic with branches + phi + // Short-circuit boolean logic with branches (+phi or edge-copy) let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?; let rhs_bb = next_block_id(f); let fall_bb = BasicBlockId::new(rhs_bb.0 + 1); @@ -176,10 +330,18 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( let is_and = matches!(op.as_str(), "&&" | "and"); if let Some(bb) = f.get_block_mut(cur_after_l) { if is_and { - bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: rhs_bb, else_bb: fall_bb }); + bb.set_terminator(MirInstruction::Branch { + condition: l, + then_bb: rhs_bb, + else_bb: fall_bb, + }); } else { // OR: if lhs true, go to fall_bb (true path), else evaluate rhs - bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: fall_bb, else_bb: rhs_bb }); + bb.set_terminator(MirInstruction::Branch { + condition: l, + then_bb: fall_bb, + else_bb: rhs_bb, + }); } } // Telemetry: note short-circuit lowering @@ -192,25 +354,56 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( "merge_bb": merge_bb.0 }), "shortcircuit", - "" + "", ); - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[bridge/logical] op={} rhs_bb={} fall_bb={} merge_bb={}", if is_and {"and"} else {"or"}, rhs_bb.0, fall_bb.0, merge_bb.0); } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!( + "[bridge/logical] op={} rhs_bb={} fall_bb={} merge_bb={}", + if is_and { "and" } else { "or" }, + rhs_bb.0, + fall_bb.0, + merge_bb.0 + ); + } // false/true constant in fall_bb depending on op let cdst = f.next_value_id(); if let Some(bb) = f.get_block_mut(fall_bb) { - let cval = if is_and { ConstValue::Bool(false) } else { ConstValue::Bool(true) }; - bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval }); + let cval = if is_and { + ConstValue::Bool(false) + } else { + ConstValue::Bool(true) + }; + bb.add_instruction(MirInstruction::Const { + dst: cdst, + value: cval, + }); bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } // evaluate rhs starting at rhs_bb and ensure the terminal block jumps to merge let (rval, rhs_end) = lower_expr(f, rhs_bb, rhs)?; if let Some(bb) = f.get_block_mut(rhs_end) { - if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } + if !bb.is_terminated() { + bb.set_terminator(MirInstruction::Jump { target: merge_bb }); + } } - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[bridge/logical] rhs_end={} jump->merge_bb={}", rhs_end.0, merge_bb.0); } - // merge with phi (use actual predecessors rhs_end and fall_bb) + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!( + "[bridge/logical] rhs_end={} jump->merge_bb={}", + rhs_end.0, merge_bb.0 + ); + } + // Merge: PHI または edge-copy で合流値を定義 + let no_phi = crate::config::env::mir_no_phi(); let out = f.next_value_id(); - if let Some(bb) = f.get_block_mut(merge_bb) { + if no_phi { + // Edge copies in predecessors + if let Some(bb) = f.get_block_mut(fall_bb) { + bb.add_instruction(MirInstruction::Copy { dst: out, src: cdst }); + } + if let Some(bb) = f.get_block_mut(rhs_end) { + bb.add_instruction(MirInstruction::Copy { dst: out, src: rval }); + } + } else if let Some(bb) = f.get_block_mut(merge_bb) { let mut inputs: Vec<(BasicBlockId, ValueId)> = vec![(fall_bb, cdst)]; if rhs_end != fall_bb { inputs.push((rhs_end, rval)); @@ -230,15 +423,27 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( // Create array first let arr = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] }); + bb.add_instruction(MirInstruction::NewBox { + dst: arr, + box_type: "ArrayBox".into(), + args: vec![], + }); } // For each element: eval then push let mut cur = cur_bb; for e in args { - let (v, c) = lower_expr(f, cur, e)?; cur = c; + let (v, c) = lower_expr(f, cur, e)?; + cur = c; let tmp = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: arr, method: "push".into(), method_id: None, args: vec![v], effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::BoxCall { + dst: Some(tmp), + box_val: arr, + method: "push".into(), + method_id: None, + args: vec![v], + effects: EffectMask::READ, + }); } } return Ok((arr, cur)); @@ -247,19 +452,34 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( if name == "map.of" { let mapv = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] }); + bb.add_instruction(MirInstruction::NewBox { + dst: mapv, + box_type: "MapBox".into(), + args: vec![], + }); } let mut cur = cur_bb; let mut it = args.iter(); while let Some(k) = it.next() { if let Some(v) = it.next() { - let (kv, cur2) = lower_expr(f, cur, k)?; cur = cur2; - let (vv, cur3) = lower_expr(f, cur, v)?; cur = cur3; + let (kv, cur2) = lower_expr(f, cur, k)?; + cur = cur2; + let (vv, cur3) = lower_expr(f, cur, v)?; + cur = cur3; let tmp = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: mapv, method: "set".into(), method_id: None, args: vec![kv, vv], effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::BoxCall { + dst: Some(tmp), + box_val: mapv, + method: "set".into(), + method_id: None, + args: vec![kv, vv], + effects: EffectMask::READ, + }); } - } else { break; } + } else { + break; + } } return Ok((mapv, cur)); } @@ -267,22 +487,38 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( let (arg_ids, cur) = lower_args(f, cur_bb, args)?; let fun_val = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::Const { dst: fun_val, value: ConstValue::String(name.clone()) }); + bb.add_instruction(MirInstruction::Const { + dst: fun_val, + value: ConstValue::String(name.clone()), + }); } let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::Call { dst: Some(dst), func: fun_val, args: arg_ids, effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::Call { + dst: Some(dst), + func: fun_val, + args: arg_ids, + effects: EffectMask::READ, + }); } Ok((dst, cur)) } ExprV0::Method { recv, method, args } => { // Heuristic: new ConsoleBox().println(x) → externcall env.console.log(x) - let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox"); - if recv_is_console_new && (method == "println" || method == "print" || method == "log") { + let recv_is_console_new = + matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox"); + if recv_is_console_new && (method == "println" || method == "print" || method == "log") + { let (arg_ids, cur2) = lower_args(f, cur_bb, args)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur2) { - bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.console".into(), method_name: "log".into(), args: arg_ids, effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::ExternCall { + dst: Some(dst), + iface_name: "env.console".into(), + method_name: "log".into(), + args: arg_ids, + effects: EffectMask::READ, + }); } return Ok((dst, cur2)); } @@ -290,7 +526,14 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( let (arg_ids, cur2) = lower_args(f, cur, args)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur2) { - bb.add_instruction(MirInstruction::BoxCall { dst: Some(dst), box_val: recv_v, method: method.clone(), method_id: None, args: arg_ids, effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::BoxCall { + dst: Some(dst), + box_val: recv_v, + method: method.clone(), + method_id: None, + args: arg_ids, + effects: EffectMask::READ, + }); } Ok((dst, cur2)) } @@ -298,17 +541,18 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<( let (arg_ids, cur) = lower_args(f, cur_bb, args)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::NewBox { dst, box_type: class.clone(), args: arg_ids }); + bb.add_instruction(MirInstruction::NewBox { + dst, + box_type: class.clone(), + args: arg_ids, + }); } Ok((dst, cur)) } ExprV0::Var { name } => Err(format!("undefined variable in this context: {}", name)), ExprV0::Throw { expr } => { - let (_ignored, cur) = lower_expr(f, cur_bb, expr)?; - let dst = f.next_value_id(); - if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(0) }); - } + let (exc, cur) = lower_expr(f, cur_bb, expr)?; + let (dst, cur) = lower_throw(f, cur, exc); Ok((dst, cur)) } } @@ -328,10 +572,15 @@ fn lower_expr_with_vars( if name == "me" { // Optional gate: allow a dummy 'me' instance for Stage-2 JSON smoke if std::env::var("NYASH_BRIDGE_ME_DUMMY").ok().as_deref() == Some("1") { - let class = std::env::var("NYASH_BRIDGE_ME_CLASS").unwrap_or_else(|_| "Main".to_string()); + let class = std::env::var("NYASH_BRIDGE_ME_CLASS") + .unwrap_or_else(|_| "Main".to_string()); let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::NewBox { dst, box_type: class, args: vec![] }); + bb.add_instruction(MirInstruction::NewBox { + dst, + box_type: class, + args: vec![], + }); } vars.insert("me".to_string(), dst); return Ok((dst, cur_bb)); @@ -342,11 +591,8 @@ fn lower_expr_with_vars( Err(format!("undefined variable: {}", name)) } ExprV0::Throw { expr } => { - let (_ignored, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; - let dst = f.next_value_id(); - if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(0) }); - } + let (exc, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; + let (dst, cur) = lower_throw(f, cur, exc); Ok((dst, cur)) } ExprV0::Call { name, args } => { @@ -354,14 +600,26 @@ fn lower_expr_with_vars( if name == "array.of" { let arr = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] }); + bb.add_instruction(MirInstruction::NewBox { + dst: arr, + box_type: "ArrayBox".into(), + args: vec![], + }); } let mut cur = cur_bb; for e in args { - let (v, c) = lower_expr_with_vars(f, cur, e, vars)?; cur = c; + let (v, c) = lower_expr_with_vars(f, cur, e, vars)?; + cur = c; let tmp = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: arr, method: "push".into(), method_id: None, args: vec![v], effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::BoxCall { + dst: Some(tmp), + box_val: arr, + method: "push".into(), + method_id: None, + args: vec![v], + effects: EffectMask::READ, + }); } } return Ok((arr, cur)); @@ -370,19 +628,34 @@ fn lower_expr_with_vars( if name == "map.of" { let mapv = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] }); + bb.add_instruction(MirInstruction::NewBox { + dst: mapv, + box_type: "MapBox".into(), + args: vec![], + }); } let mut cur = cur_bb; let mut it = args.iter(); while let Some(k) = it.next() { if let Some(v) = it.next() { - let (kv, cur2) = lower_expr_with_vars(f, cur, k, vars)?; cur = cur2; - let (vv, cur3) = lower_expr_with_vars(f, cur, v, vars)?; cur = cur3; + let (kv, cur2) = lower_expr_with_vars(f, cur, k, vars)?; + cur = cur2; + let (vv, cur3) = lower_expr_with_vars(f, cur, v, vars)?; + cur = cur3; let tmp = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: mapv, method: "set".into(), method_id: None, args: vec![kv, vv], effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::BoxCall { + dst: Some(tmp), + box_val: mapv, + method: "set".into(), + method_id: None, + args: vec![kv, vv], + effects: EffectMask::READ, + }); } - } else { break; } + } else { + break; + } } return Ok((mapv, cur)); } @@ -391,21 +664,37 @@ fn lower_expr_with_vars( // Encode as: const fun_name; call let fun_val = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::Const { dst: fun_val, value: ConstValue::String(name.clone()) }); + bb.add_instruction(MirInstruction::Const { + dst: fun_val, + value: ConstValue::String(name.clone()), + }); } let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::Call { dst: Some(dst), func: fun_val, args: arg_ids, effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::Call { + dst: Some(dst), + func: fun_val, + args: arg_ids, + effects: EffectMask::READ, + }); } Ok((dst, cur)) } ExprV0::Method { recv, method, args } => { - let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox"); - if recv_is_console_new && (method == "println" || method == "print" || method == "log") { + let recv_is_console_new = + matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox"); + if recv_is_console_new && (method == "println" || method == "print" || method == "log") + { let (arg_ids, cur2) = lower_args_with_vars(f, cur_bb, args, vars)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur2) { - bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.console".into(), method_name: "log".into(), args: arg_ids, effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::ExternCall { + dst: Some(dst), + iface_name: "env.console".into(), + method_name: "log".into(), + args: arg_ids, + effects: EffectMask::READ, + }); } return Ok((dst, cur2)); } @@ -413,7 +702,14 @@ fn lower_expr_with_vars( let (arg_ids, cur2) = lower_args_with_vars(f, cur, args, vars)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur2) { - bb.add_instruction(MirInstruction::BoxCall { dst: Some(dst), box_val: recv_v, method: method.clone(), method_id: None, args: arg_ids, effects: EffectMask::READ }); + bb.add_instruction(MirInstruction::BoxCall { + dst: Some(dst), + box_val: recv_v, + method: method.clone(), + method_id: None, + args: arg_ids, + effects: EffectMask::READ, + }); } Ok((dst, cur2)) } @@ -421,17 +717,32 @@ fn lower_expr_with_vars( let (arg_ids, cur) = lower_args_with_vars(f, cur_bb, args, vars)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::NewBox { dst, box_type: class.clone(), args: arg_ids }); + bb.add_instruction(MirInstruction::NewBox { + dst, + box_type: class.clone(), + args: arg_ids, + }); } Ok((dst, cur)) } ExprV0::Binary { op, lhs, rhs } => { let (l, cur_after_l) = lower_expr_with_vars(f, cur_bb, lhs, vars)?; let (r, cur_after_r) = lower_expr_with_vars(f, cur_after_l, rhs, vars)?; - let bop = match op.as_str() { "+" => BinaryOp::Add, "-" => BinaryOp::Sub, "*" => BinaryOp::Mul, "/" => BinaryOp::Div, _ => return Err("unsupported op".into()) }; + let bop = match op.as_str() { + "+" => BinaryOp::Add, + "-" => BinaryOp::Sub, + "*" => BinaryOp::Mul, + "/" => BinaryOp::Div, + _ => return Err("unsupported op".into()), + }; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_after_r) { - bb.add_instruction(MirInstruction::BinOp { dst, op: bop, lhs: l, rhs: r }); + bb.add_instruction(MirInstruction::BinOp { + dst, + op: bop, + lhs: l, + rhs: r, + }); } Ok((dst, cur_after_r)) } @@ -441,15 +752,20 @@ fn lower_expr_with_vars( let cop = match op.as_str() { "==" => crate::mir::CompareOp::Eq, "!=" => crate::mir::CompareOp::Ne, - "<" => crate::mir::CompareOp::Lt, + "<" => crate::mir::CompareOp::Lt, "<=" => crate::mir::CompareOp::Le, - ">" => crate::mir::CompareOp::Gt, + ">" => crate::mir::CompareOp::Gt, ">=" => crate::mir::CompareOp::Ge, _ => return Err("unsupported compare op".into()), }; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_after_r) { - bb.add_instruction(MirInstruction::Compare { dst, op: cop, lhs: l, rhs: r }); + bb.add_instruction(MirInstruction::Compare { + dst, + op: cop, + lhs: l, + rhs: r, + }); } Ok((dst, cur_after_r)) } @@ -464,21 +780,45 @@ fn lower_expr_with_vars( let is_and = matches!(op.as_str(), "&&" | "and"); if let Some(bb) = f.get_block_mut(cur_after_l) { if is_and { - bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: rhs_bb, else_bb: fall_bb }); + bb.set_terminator(MirInstruction::Branch { + condition: l, + then_bb: rhs_bb, + else_bb: fall_bb, + }); } else { - bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: fall_bb, else_bb: rhs_bb }); + bb.set_terminator(MirInstruction::Branch { + condition: l, + then_bb: fall_bb, + else_bb: rhs_bb, + }); } } let cdst = f.next_value_id(); if let Some(bb) = f.get_block_mut(fall_bb) { - let cval = if is_and { ConstValue::Bool(false) } else { ConstValue::Bool(true) }; - bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval }); + let cval = if is_and { + ConstValue::Bool(false) + } else { + ConstValue::Bool(true) + }; + bb.add_instruction(MirInstruction::Const { + dst: cdst, + value: cval, + }); bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } let (rval, rhs_end) = lower_expr_with_vars(f, rhs_bb, rhs, vars)?; - if let Some(bb) = f.get_block_mut(rhs_end) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } } + if let Some(bb) = f.get_block_mut(rhs_end) { + if !bb.is_terminated() { + bb.set_terminator(MirInstruction::Jump { target: merge_bb }); + } + } let out = f.next_value_id(); - if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_end, rval), (fall_bb, cdst)] }); } + if let Some(bb) = f.get_block_mut(merge_bb) { + bb.insert_instruction_after_phis(MirInstruction::Phi { + dst: out, + inputs: vec![(rhs_end, rval), (fall_bb, cdst)], + }); + } Ok((out, merge_bb)) } _ => lower_expr(f, cur_bb, e), @@ -490,38 +830,271 @@ fn lower_stmt_with_vars( cur_bb: BasicBlockId, s: &StmtV0, vars: &mut std::collections::HashMap, + loop_stack: &mut Vec, ) -> Result { match s { StmtV0::Return { expr } => { let (v, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; - if let Some(bb) = f.get_block_mut(cur) { bb.set_terminator(MirInstruction::Return { value: Some(v) }); } + if let Some(bb) = f.get_block_mut(cur) { + bb.set_terminator(MirInstruction::Return { value: Some(v) }); + } Ok(cur) } - StmtV0::Extern { iface, method, args } => { + StmtV0::Extern { + iface, + method, + args, + } => { let (arg_ids, cur) = lower_args_with_vars(f, cur_bb, args, vars)?; - if let Some(bb) = f.get_block_mut(cur) { bb.add_instruction(MirInstruction::ExternCall { dst: None, iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO }); } + if let Some(bb) = f.get_block_mut(cur) { + bb.add_instruction(MirInstruction::ExternCall { + dst: None, + iface_name: iface.clone(), + method_name: method.clone(), + args: arg_ids, + effects: EffectMask::IO, + }); + } Ok(cur) } StmtV0::Expr { expr } => { - let (_v, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; Ok(cur) + let (_v, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; + Ok(cur) } StmtV0::Local { name, expr } => { - let (v, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; vars.insert(name.clone(), v); Ok(cur) + let (v, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; + vars.insert(name.clone(), v); + Ok(cur) } StmtV0::Break => { - // Stage-3 placeholder: no-op until loop lowering supports break + if let Some(ctx) = loop_stack.last().copied() { + if let Some(bb) = f.get_block_mut(cur_bb) { + bb.set_terminator(MirInstruction::Jump { + target: ctx.exit_bb, + }); + } + crate::jit::events::emit_lower( + serde_json::json!({ + "id": "loop_break", + "exit_bb": ctx.exit_bb.0, + "decision": "lower", + }), + "loop", + "", + ); + } else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[bridge/break] ignoring break outside loop context"); + } Ok(cur_bb) } StmtV0::Continue => { - // Stage-3 placeholder: no-op until loop lowering supports continue + if let Some(ctx) = loop_stack.last().copied() { + if let Some(bb) = f.get_block_mut(cur_bb) { + bb.set_terminator(MirInstruction::Jump { + target: ctx.cond_bb, + }); + } + crate::jit::events::emit_lower( + serde_json::json!({ + "id": "loop_continue", + "cond_bb": ctx.cond_bb.0, + "decision": "lower", + }), + "loop", + "", + ); + } else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[bridge/continue] ignoring continue outside loop context"); + } Ok(cur_bb) } - StmtV0::Try { try_body, .. } => { - // Stage-3 placeholder: lower try body sequentially, ignore catches/finally for now - let mut tmp_vars = vars.clone(); - let next_bb = lower_stmt_list_with_vars(f, cur_bb, try_body, &mut tmp_vars)?; - *vars = tmp_vars; - Ok(next_bb) + StmtV0::Try { + try_body, + catches, + finally, + } => { + let try_enabled = std::env::var("NYASH_BRIDGE_TRY_ENABLE").ok().as_deref() == Some("1"); + + if !try_enabled || catches.is_empty() || catches.len() > 1 { + let mut tmp_vars = vars.clone(); + let mut next_bb = + lower_stmt_list_with_vars(f, cur_bb, try_body, &mut tmp_vars, loop_stack)?; + if !finally.is_empty() { + next_bb = + lower_stmt_list_with_vars(f, next_bb, finally, &mut tmp_vars, loop_stack)?; + } + *vars = tmp_vars; + return Ok(next_bb); + } + + let base_vars = vars.clone(); + let try_bb = next_block_id(f); + f.add_block(crate::mir::BasicBlock::new(try_bb)); + + let catch_clause = &catches[0]; + let catch_bb = next_block_id(f); + f.add_block(crate::mir::BasicBlock::new(catch_bb)); + + let finally_bb = if !finally.is_empty() { + let id = next_block_id(f); + f.add_block(crate::mir::BasicBlock::new(id)); + Some(id) + } else { + None + }; + + let exit_bb = next_block_id(f); + f.add_block(crate::mir::BasicBlock::new(exit_bb)); + + let handler_target = finally_bb.unwrap_or(exit_bb); + + let exception_value = f.next_value_id(); + if let Some(bb) = f.get_block_mut(cur_bb) { + bb.add_instruction(MirInstruction::Catch { + exception_type: catch_clause.type_hint.clone(), + exception_value, + handler_bb: catch_bb, + }); + bb.set_terminator(MirInstruction::Jump { target: try_bb }); + } + + let mut try_vars = vars.clone(); + let try_end = + lower_stmt_list_with_vars(f, try_bb, try_body, &mut try_vars, loop_stack)?; + if let Some(bb) = f.get_block_mut(try_end) { + if !bb.is_terminated() { + bb.set_terminator(MirInstruction::Jump { + target: handler_target, + }); + } + } + let try_branch_vars = try_vars.clone(); + + let mut catch_vars = base_vars.clone(); + if let Some(param) = &catch_clause.param { + catch_vars.insert(param.clone(), exception_value); + } + let catch_end = lower_stmt_list_with_vars( + f, + catch_bb, + &catch_clause.body, + &mut catch_vars, + loop_stack, + )?; + if let Some(param) = &catch_clause.param { + catch_vars.remove(param); + } + if let Some(bb) = f.get_block_mut(catch_end) { + if !bb.is_terminated() { + bb.set_terminator(MirInstruction::Jump { + target: handler_target, + }); + } + } + let catch_branch_vars = catch_vars.clone(); + + use std::collections::HashSet; + let mut branch_vars = vec![(try_end, try_branch_vars), (catch_end, catch_branch_vars)]; + let merge_target = handler_target; + + if let Some(finally_block) = finally_bb { + // ensure finally block exists before inserting phi + let names: HashSet = { + let mut set: HashSet = base_vars.keys().cloned().collect(); + for (_, map) in &branch_vars { + set.extend(map.keys().cloned()); + } + set + }; + + let mut merged_vars = base_vars.clone(); + let mut phi_entries: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)> = Vec::new(); + for name in names { + let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); + for (bbid, map) in &branch_vars { + if let Some(&val) = map.get(&name) { + inputs.push((*bbid, val)); + } + } + if inputs.is_empty() { + if let Some(&base_val) = base_vars.get(&name) { + merged_vars.insert(name.clone(), base_val); + } + continue; + } + let unique: HashSet = inputs.iter().map(|(_, v)| *v).collect(); + if unique.len() == 1 { + merged_vars.insert(name.clone(), inputs[0].1); + continue; + } + let dst = f.next_value_id(); + inputs.sort_by_key(|(bbid, _)| bbid.0); + phi_entries.push((dst, inputs)); + merged_vars.insert(name.clone(), dst); + } + if let Some(bb) = f.get_block_mut(finally_block) { + for (dst, inputs) in phi_entries { + bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs }); + } + } + + let mut finally_vars = merged_vars.clone(); + let final_end = lower_stmt_list_with_vars( + f, + finally_block, + finally, + &mut finally_vars, + loop_stack, + )?; + if let Some(bb) = f.get_block_mut(final_end) { + if !bb.is_terminated() { + bb.set_terminator(MirInstruction::Jump { target: exit_bb }); + } + } + *vars = finally_vars; + Ok(exit_bb) + } else { + let names: HashSet = { + let mut set: HashSet = base_vars.keys().cloned().collect(); + for (_, map) in &branch_vars { + set.extend(map.keys().cloned()); + } + set + }; + + let mut merged_vars = base_vars.clone(); + let mut phi_entries: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)> = Vec::new(); + for name in names { + let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); + for (bbid, map) in &branch_vars { + if let Some(&val) = map.get(&name) { + inputs.push((*bbid, val)); + } + } + if inputs.is_empty() { + if let Some(&base_val) = base_vars.get(&name) { + merged_vars.insert(name.clone(), base_val); + } + continue; + } + let unique: HashSet = inputs.iter().map(|(_, v)| *v).collect(); + if unique.len() == 1 { + merged_vars.insert(name.clone(), inputs[0].1); + continue; + } + let dst = f.next_value_id(); + inputs.sort_by_key(|(bbid, _)| bbid.0); + phi_entries.push((dst, inputs)); + merged_vars.insert(name.clone(), dst); + } + if let Some(bb) = f.get_block_mut(exit_bb) { + for (dst, inputs) in phi_entries { + bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs }); + } + } + *vars = merged_vars; + Ok(exit_bb) + } } StmtV0::If { cond, then, r#else } => { // Lower condition first @@ -535,22 +1108,30 @@ fn lower_stmt_with_vars( f.add_block(crate::mir::BasicBlock::new(merge_bb)); // Branch to then/else if let Some(bb) = f.get_block_mut(cur) { - bb.set_terminator(MirInstruction::Branch { condition: cval, then_bb, else_bb }); + bb.set_terminator(MirInstruction::Branch { + condition: cval, + then_bb, + else_bb, + }); } // Clone current vars as branch-local maps let base_vars = vars.clone(); let mut then_vars = base_vars.clone(); - let tend = lower_stmt_list_with_vars(f, then_bb, then, &mut then_vars)?; + let tend = lower_stmt_list_with_vars(f, then_bb, then, &mut then_vars, loop_stack)?; if let Some(bb) = f.get_block_mut(tend) { - if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } + if !bb.is_terminated() { + bb.set_terminator(MirInstruction::Jump { target: merge_bb }); + } } let (else_end_pred, else_vars) = if let Some(elses) = r#else { let mut ev = base_vars.clone(); - let eend = lower_stmt_list_with_vars(f, else_bb, elses, &mut ev)?; + let eend = lower_stmt_list_with_vars(f, else_bb, elses, &mut ev, loop_stack)?; if let Some(bb) = f.get_block_mut(eend) { - if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } + if !bb.is_terminated() { + bb.set_terminator(MirInstruction::Jump { target: merge_bb }); + } } (eend, ev) } else { @@ -561,25 +1142,25 @@ fn lower_stmt_with_vars( (else_bb, base_vars.clone()) }; - // PHI merge at merge_bb + // Merge at then/else predecessors(PHI or edge-copy) use std::collections::HashSet; + let no_phi = crate::config::env::mir_no_phi(); let mut names: HashSet = base_vars.keys().cloned().collect(); - // Also merge variables newly defined on both sides for k in then_vars.keys() { names.insert(k.clone()); } for k in else_vars.keys() { names.insert(k.clone()); } for name in names { let tv = then_vars.get(&name).copied(); let ev = else_vars.get(&name).copied(); - // Only propagate if variable exists on both paths or existed before let exists_base = base_vars.contains_key(&name); match (tv, ev, exists_base) { - (Some(tval), Some( eval), _) => { - let merged = if tval == eval { - tval - } else { + (Some(tval), Some(eval), _) => { + let merged = if tval == eval { tval } else { let dst = f.next_value_id(); - if let Some(bb) = f.get_block_mut(merge_bb) { + if no_phi { + if let Some(bb) = f.get_block_mut(tend) { bb.add_instruction(MirInstruction::Copy { dst, src: tval }); } + if let Some(bb) = f.get_block_mut(else_end_pred) { bb.add_instruction(MirInstruction::Copy { dst, src: eval }); } + } else if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, tval), (else_end_pred, eval)] }); } dst @@ -587,11 +1168,13 @@ fn lower_stmt_with_vars( vars.insert(name, merged); } (Some(tval), None, true) => { - // Else path inherits base; merge then vs base if let Some(&bval) = base_vars.get(&name) { let merged = if tval == bval { tval } else { let dst = f.next_value_id(); - if let Some(bb) = f.get_block_mut(merge_bb) { + if no_phi { + if let Some(bb) = f.get_block_mut(tend) { bb.add_instruction(MirInstruction::Copy { dst, src: tval }); } + if let Some(bb) = f.get_block_mut(else_end_pred) { bb.add_instruction(MirInstruction::Copy { dst, src: bval }); } + } else if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, tval), (else_end_pred, bval)] }); } dst @@ -600,11 +1183,13 @@ fn lower_stmt_with_vars( } } (None, Some(eval), true) => { - // Then path inherits base; merge else vs base if let Some(&bval) = base_vars.get(&name) { let merged = if eval == bval { eval } else { let dst = f.next_value_id(); - if let Some(bb) = f.get_block_mut(merge_bb) { + if no_phi { + if let Some(bb) = f.get_block_mut(tend) { bb.add_instruction(MirInstruction::Copy { dst, src: bval }); } + if let Some(bb) = f.get_block_mut(else_end_pred) { bb.add_instruction(MirInstruction::Copy { dst, src: eval }); } + } else if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, bval), (else_end_pred, eval)] }); } dst @@ -612,7 +1197,6 @@ fn lower_stmt_with_vars( vars.insert(name, merged); } } - // If neither side has it, or only one side has it without base, skip (out-of-scope new var) _ => {} } } @@ -630,57 +1214,99 @@ fn lower_stmt_with_vars( // Preheader jump into cond if let Some(bb) = f.get_block_mut(cur_bb) { - if !bb.is_terminated() { bb.add_instruction(MirInstruction::Jump { target: cond_bb }); } + if !bb.is_terminated() { + bb.add_instruction(MirInstruction::Jump { target: cond_bb }); + } } - // Snapshot base vars and set up PHI placeholders at cond for loop-carried vars + // Snapshot base vars and set up merged ids for loop-carried vars + let no_phi = crate::config::env::mir_no_phi(); let base_vars = vars.clone(); let orig_names: Vec = base_vars.keys().cloned().collect(); - let mut phi_map: std::collections::HashMap = std::collections::HashMap::new(); + let mut phi_map: std::collections::HashMap = + std::collections::HashMap::new(); for name in &orig_names { if let Some(&bval) = base_vars.get(name) { let dst = f.next_value_id(); - if let Some(bb) = f.get_block_mut(cond_bb) { - // Initial incoming from preheader + if no_phi { + // Preheader edge-copy(cur_bb -> cond) + if let Some(bb) = f.get_block_mut(cur_bb) { + bb.add_instruction(MirInstruction::Copy { dst, src: bval }); + } + } else if let Some(bb) = f.get_block_mut(cond_bb) { + // Initial incoming from preheader via PHI bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(cur_bb, bval)] }); } phi_map.insert(name.clone(), dst); } } // Redirect current vars to PHIs for use in cond/body - for (name, &phi) in &phi_map { vars.insert(name.clone(), phi); } + for (name, &phi) in &phi_map { + vars.insert(name.clone(), phi); + } // Lower condition using phi-backed vars let (cval, _cend) = lower_expr_with_vars(f, cond_bb, cond, vars)?; if let Some(bb) = f.get_block_mut(cond_bb) { - bb.set_terminator(MirInstruction::Branch { condition: cval, then_bb: body_bb, else_bb: exit_bb }); + bb.set_terminator(MirInstruction::Branch { + condition: cval, + then_bb: body_bb, + else_bb: exit_bb, + }); } // Lower body; record end block and body-out vars let mut body_vars = vars.clone(); - let bend = lower_stmt_list_with_vars(f, body_bb, body, &mut body_vars)?; + loop_stack.push(LoopContext { cond_bb, exit_bb }); + let bend_res = lower_stmt_list_with_vars(f, body_bb, body, &mut body_vars, loop_stack); + loop_stack.pop(); + let bend = bend_res?; if let Some(bb) = f.get_block_mut(bend) { - if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: cond_bb }); } + if !bb.is_terminated() { + bb.set_terminator(MirInstruction::Jump { target: cond_bb }); + } } - // Wire PHI second incoming from latch (body end) - if let Some(bb) = f.get_block_mut(cond_bb) { - for (name, &phi_dst) in &phi_map { - if let Some(&latch_val) = body_vars.get(name) { - for inst in &mut bb.instructions { - if let MirInstruction::Phi { dst, inputs } = inst { - if *dst == phi_dst { - inputs.push((bend, latch_val)); - break; + // Wire second incoming from latch (body end) + let backedge_to_cond = matches!( + f.blocks.get(&bend).and_then(|bb| bb.terminator.as_ref()), + Some(MirInstruction::Jump { target, .. }) if *target == cond_bb + ); + if backedge_to_cond { + if no_phi { + // Latch edge-copy(bend -> cond) + for (name, &phi_dst) in &phi_map { + if let Some(&latch_val) = body_vars.get(name) { + if let Some(bb) = f.get_block_mut(bend) { + bb.add_instruction(MirInstruction::Copy { dst: phi_dst, src: latch_val }); + } + } + } + } else if let Some(bb) = f.get_block_mut(cond_bb) { + for (name, &phi_dst) in &phi_map { + if let Some(&latch_val) = body_vars.get(name) { + for inst in &mut bb.instructions { + if let MirInstruction::Phi { dst, inputs } = inst { + if *dst == phi_dst { + inputs.push((bend, latch_val)); + break; + } } } } } } + } else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!( + "[bridge/loop] skipped latch bb{} -> cond bb{}", + bend.0, cond_bb.0 + ); } // After the loop, keep vars mapped to the PHI values (current loop state) - for (name, &phi) in &phi_map { vars.insert(name.clone(), phi); } + for (name, &phi) in &phi_map { + vars.insert(name.clone(), phi); + } Ok(exit_bb) } @@ -692,11 +1318,16 @@ fn lower_stmt_list_with_vars( start_bb: BasicBlockId, stmts: &[StmtV0], vars: &mut std::collections::HashMap, + loop_stack: &mut Vec, ) -> Result { let mut cur = start_bb; for s in stmts { - cur = lower_stmt_with_vars(f, cur, s, vars)?; - if let Some(bb) = f.blocks.get(&cur) { if bb.is_terminated() { break; } } + cur = lower_stmt_with_vars(f, cur, s, vars, loop_stack)?; + if let Some(bb) = f.blocks.get(&cur) { + if bb.is_terminated() { + break; + } + } } Ok(cur) } @@ -709,16 +1340,24 @@ fn lower_args_with_vars( let mut out = Vec::with_capacity(args.len()); let mut cur = cur_bb; for a in args { - let (v, c) = lower_expr_with_vars(f, cur, a, vars)?; out.push(v); cur = c; + let (v, c) = lower_expr_with_vars(f, cur, a, vars)?; + out.push(v); + cur = c; } Ok((out, cur)) } -fn lower_args(f: &mut MirFunction, cur_bb: BasicBlockId, args: &[ExprV0]) -> Result<(Vec, BasicBlockId), String> { +fn lower_args( + f: &mut MirFunction, + cur_bb: BasicBlockId, + args: &[ExprV0], +) -> Result<(Vec, BasicBlockId), String> { let mut out = Vec::with_capacity(args.len()); let mut cur = cur_bb; for a in args { - let (v, c) = lower_expr(f, cur, a)?; out.push(v); cur = c; + let (v, c) = lower_expr(f, cur, a)?; + out.push(v); + cur = c; } Ok((out, cur)) } @@ -730,6 +1369,43 @@ pub fn maybe_dump_mir(module: &MirModule) { } } +#[cfg(test)] +mod tests { + use super::parse_json_v0_to_module; + use crate::mir::{BasicBlockId, MirInstruction, MirModule}; + + fn block_terminator(module: &MirModule, block: BasicBlockId) -> MirInstruction { + module + .get_function("main") + .unwrap() + .get_block(block) + .and_then(|bb| bb.terminator.clone()) + .expect("terminator") + } + + #[test] + fn stage3_break_jumps_to_exit() { + let json = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Loop\",\"cond\":{\"type\":\"Bool\",\"value\":true},\"body\":[{\"type\":\"Break\"}]},{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}"; + let module = parse_json_v0_to_module(json).unwrap(); + match block_terminator(&module, BasicBlockId::new(2)) { + MirInstruction::Jump { target, .. } => assert_eq!(target, BasicBlockId::new(3)), + other => panic!("expected jump, got {:?}", other), + } + module.verify().unwrap(); + } + + #[test] + fn stage3_continue_jumps_to_head() { + let json = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Loop\",\"cond\":{\"type\":\"Bool\",\"value\":true},\"body\":[{\"type\":\"Continue\"}]},{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}"; + let module = parse_json_v0_to_module(json).unwrap(); + match block_terminator(&module, BasicBlockId::new(2)) { + MirInstruction::Jump { target, .. } => assert_eq!(target, BasicBlockId::new(1)), + other => panic!("expected jump, got {:?}", other), + } + module.verify().unwrap(); + } +} + // ========== Direct bridge (source → JSON v0 → MIR) ========== #[derive(Clone, Debug)] @@ -753,23 +1429,57 @@ fn lex(input: &str) -> Result, String> { while i < n { let c = bytes[i] as char; // Treat semicolon as whitespace (Stage-1 minimal ASI: optional ';') - if c.is_whitespace() || c == ';' { i += 1; continue; } + if c.is_whitespace() || c == ';' { + i += 1; + continue; + } match c { - '+' => { toks.push(Tok::Plus); i+=1; } - '-' => { toks.push(Tok::Minus); i+=1; } - '*' => { toks.push(Tok::Star); i+=1; } - '/' => { toks.push(Tok::Slash); i+=1; } - '(' => { toks.push(Tok::LParen); i+=1; } - ')' => { toks.push(Tok::RParen); i+=1; } + '+' => { + toks.push(Tok::Plus); + i += 1; + } + '-' => { + toks.push(Tok::Minus); + i += 1; + } + '*' => { + toks.push(Tok::Star); + i += 1; + } + '/' => { + toks.push(Tok::Slash); + i += 1; + } + '(' => { + toks.push(Tok::LParen); + i += 1; + } + ')' => { + toks.push(Tok::RParen); + i += 1; + } '0'..='9' => { - let start = i; while i { // return - if i+6<=n && &input[i..i+6]=="return" { toks.push(Tok::Return); i+=6; } else { return Err("unexpected 'r'".into()); } + if i + 6 <= n && &input[i..i + 6] == "return" { + toks.push(Tok::Return); + i += 6; + } else { + return Err("unexpected 'r'".into()); + } } _ => return Err(format!("unexpected char '{}'", c)), } @@ -778,43 +1488,119 @@ fn lex(input: &str) -> Result, String> { Ok(toks) } -struct P { toks: Vec, pos: usize } +struct P { + toks: Vec, + pos: usize, +} impl P { - fn new(toks: Vec) -> Self { Self{ toks, pos:0 } } - fn peek(&self) -> &Tok { self.toks.get(self.pos).unwrap() } - fn next(&mut self) -> Tok { let t = self.toks.get(self.pos).unwrap().clone(); self.pos+=1; t } - fn expect_return(&mut self) -> Result<(), String> { match self.next() { Tok::Return => Ok(()), _ => Err("expected 'return'".into()) } } - fn parse_program(&mut self) -> Result { self.expect_return()?; self.parse_expr() } - fn parse_expr(&mut self) -> Result { - let mut left = self.parse_term()?; - loop { match self.peek() { Tok::Plus => { self.next(); let r=self.parse_term()?; left = ExprV0::Binary{op:"+".into(), lhs:Box::new(left), rhs:Box::new(r)}; }, Tok::Minus => { self.next(); let r=self.parse_term()?; left = ExprV0::Binary{op:"-".into(), lhs:Box::new(left), rhs:Box::new(r)}; }, _ => break } - } - Ok(left) + fn new(toks: Vec) -> Self { + Self { toks, pos: 0 } } - fn parse_term(&mut self) -> Result { - let mut left = self.parse_factor()?; - loop { match self.peek() { Tok::Star => { self.next(); let r=self.parse_factor()?; left = ExprV0::Binary{op:"*".into(), lhs:Box::new(left), rhs:Box::new(r)}; }, Tok::Slash => { self.next(); let r=self.parse_factor()?; left = ExprV0::Binary{op:"/".into(), lhs:Box::new(left), rhs:Box::new(r)}; }, _ => break } - } - Ok(left) + fn peek(&self) -> &Tok { + self.toks.get(self.pos).unwrap() } - fn parse_factor(&mut self) -> Result { + fn next(&mut self) -> Tok { + let t = self.toks.get(self.pos).unwrap().clone(); + self.pos += 1; + t + } + fn expect_return(&mut self) -> Result<(), String> { match self.next() { - Tok::Int(v) => Ok(ExprV0::Int{ value: serde_json::Value::from(v) }), - Tok::LParen => { let e = self.parse_expr()?; match self.next() { Tok::RParen => Ok(e), _ => Err(") expected".into()) } } + Tok::Return => Ok(()), + _ => Err("expected 'return'".into()), + } + } + fn parse_program(&mut self) -> Result { + self.expect_return()?; + self.parse_expr() + } + fn parse_expr(&mut self) -> Result { + let mut left = self.parse_term()?; + loop { + match self.peek() { + Tok::Plus => { + self.next(); + let r = self.parse_term()?; + left = ExprV0::Binary { + op: "+".into(), + lhs: Box::new(left), + rhs: Box::new(r), + }; + } + Tok::Minus => { + self.next(); + let r = self.parse_term()?; + left = ExprV0::Binary { + op: "-".into(), + lhs: Box::new(left), + rhs: Box::new(r), + }; + } + _ => break, + } + } + Ok(left) + } + fn parse_term(&mut self) -> Result { + let mut left = self.parse_factor()?; + loop { + match self.peek() { + Tok::Star => { + self.next(); + let r = self.parse_factor()?; + left = ExprV0::Binary { + op: "*".into(), + lhs: Box::new(left), + rhs: Box::new(r), + }; + } + Tok::Slash => { + self.next(); + let r = self.parse_factor()?; + left = ExprV0::Binary { + op: "/".into(), + lhs: Box::new(left), + rhs: Box::new(r), + }; + } + _ => break, + } + } + Ok(left) + } + fn parse_factor(&mut self) -> Result { + match self.next() { + Tok::Int(v) => Ok(ExprV0::Int { + value: serde_json::Value::from(v), + }), + Tok::LParen => { + let e = self.parse_expr()?; + match self.next() { + Tok::RParen => Ok(e), + _ => Err(") expected".into()), + } + } _ => Err("factor expected".into()), } } } pub fn parse_source_v0_to_json(input: &str) -> Result { - let toks = lex(input)?; let mut p = P::new(toks); + let toks = lex(input)?; + let mut p = P::new(toks); let expr = p.parse_program()?; - let prog = ProgramV0 { version:0, kind: "Program".into(), body: vec![StmtV0::Return{ expr }] }; + let prog = ProgramV0 { + version: 0, + kind: "Program".into(), + body: vec![StmtV0::Return { expr }], + }; serde_json::to_string(&prog).map_err(|e| e.to_string()) } pub fn parse_source_v0_to_module(input: &str) -> Result { let json = parse_source_v0_to_json(input)?; - if std::env::var("NYASH_DUMP_JSON_IR").ok().as_deref() == Some("1") { println!("{}", json); } + if std::env::var("NYASH_DUMP_JSON_IR").ok().as_deref() == Some("1") { + println!("{}", json); + } parse_json_v0_to_module(&json) } diff --git a/tests/array_state_sharing_test.rs b/tests/array_state_sharing_test.rs index 9d614927..5fbaafa6 100644 --- a/tests/array_state_sharing_test.rs +++ b/tests/array_state_sharing_test.rs @@ -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() { @@ -21,30 +21,30 @@ mod array_state_sharing_tests { } } "#; - + let ast = NyashParser::parse_from_string(program).unwrap(); let result = interpreter.execute(ast).unwrap(); let int_result = result.as_any().downcast_ref::().unwrap(); - assert_eq!(int_result.value, 1); // 🎯 0ではなく1を返すべき + assert_eq!(int_result.value, 1); // 🎯 0ではなく1を返すべき } #[test] fn test_share_box_vs_clone_box_semantics() { let arr1 = ArrayBox::new(); arr1.push(Box::new(StringBox::new("hello"))); - + // share_box: 状態共有 let arr2 = arr1.share_box(); let arr2_array = arr2.as_any().downcast_ref::().unwrap(); - assert_eq!(arr2_array.len(), 1); // 共有されている - + assert_eq!(arr2_array.len(), 1); // 共有されている + // clone_box: 独立 let arr3 = arr1.clone_box(); let arr3_array = arr3.as_any().downcast_ref::().unwrap(); arr1.push(Box::new(StringBox::new("world"))); - assert_eq!(arr3_array.len(), 1); // 影響を受けない - assert_eq!(arr1.len(), 2); // 元は2要素 - assert_eq!(arr2_array.len(), 2); // 共有されているので2要素 + assert_eq!(arr3_array.len(), 1); // 影響を受けない + assert_eq!(arr1.len(), 2); // 元は2要素 + assert_eq!(arr2_array.len(), 2); // 共有されているので2要素 } #[test] @@ -64,10 +64,10 @@ mod array_state_sharing_tests { } } "#; - + let ast = NyashParser::parse_from_string(program).unwrap(); let result = interpreter.execute(ast).unwrap(); let int_result = result.as_any().downcast_ref::().unwrap(); - assert_eq!(int_result.value, 3); // 3要素が正しく保持されるべき + assert_eq!(int_result.value, 3); // 3要素が正しく保持されるべき } } diff --git a/tests/e2e_plugin_counterbox.rs b/tests/e2e_plugin_counterbox.rs index 07628086..81d3165a 100644 --- a/tests/e2e_plugin_counterbox.rs +++ b/tests/e2e_plugin_counterbox.rs @@ -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 diff --git a/tests/e2e_plugin_echo.rs b/tests/e2e_plugin_echo.rs index 86cbac5e..0917df61 100644 --- a/tests/e2e_plugin_echo.rs +++ b/tests/e2e_plugin_echo.rs @@ -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 { None } + fn box_id(&self) -> u64 { + self.base.id + } + fn parent_type_id(&self) -> Option { + 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::() { 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::() { + BoolBox::new(self.msg == e.msg) + } else { + BoolBox::new(false) + } + } + fn type_name(&self) -> &'static str { + "EchoBox" + } + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + fn share_box(&self) -> Box { + Box::new(self.clone()) } - fn type_name(&self) -> &'static str { "EchoBox" } - fn clone_box(&self) -> Box { Box::new(self.clone()) } - fn share_box(&self) -> Box { 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 { 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 { + 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::() { 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::() { + BoolBox::new(self.sum == a.sum) + } else { + BoolBox::new(false) + } + } + fn type_name(&self) -> &'static str { + "AdderBox" + } + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + fn share_box(&self) -> Box { + Box::new(self.clone()) } - fn type_name(&self) -> &'static str { "AdderBox" } - fn clone_box(&self) -> Box { Box::new(self.clone()) } - fn share_box(&self) -> Box { 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]) -> Result, RuntimeError> { + fn create_box( + &self, + name: &str, + args: &[Box], + ) -> Result, 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::().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?; - let b = args[1].to_string_box().value.parse::().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::().map_err(|_| { + RuntimeError::TypeError { + message: "AdderBox arg a must be int".into(), + } + })?; + let b = args[1].to_string_box().value.parse::().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 ---------- diff --git a/tests/e2e_plugin_filebox.rs b/tests/e2e_plugin_filebox.rs index 2981e6a2..bf062675 100644 --- a/tests/e2e_plugin_filebox.rs +++ b/tests/e2e_plugin_filebox.rs @@ -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,18 +38,20 @@ 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 f = new FileBox() f.close() "#; - - // Test through interpreter path first + + // Test through interpreter path first let ast = NyashParser::parse_from_string(code).expect("parse failed"); let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new(); - + match interpreter.execute(ast) { Ok(result) => { // close() returns void (BID-1 tag=9) @@ -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 { @@ -89,11 +92,11 @@ local lf lf = new LoggingFileBox() lf.close() "#; - - // Test delegation through interpreter + + // Test delegation through interpreter let ast = NyashParser::parse_from_string(code).expect("parse failed"); let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new(); - + match interpreter.execute(ast) { Ok(_) => { println!("✅ E2E Plugin FileBox delegation test passed!"); @@ -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(); diff --git a/tests/e2e_plugin_interop.rs b/tests/e2e_plugin_interop.rs index 2ac6e18e..3915810f 100644 --- a/tests/e2e_plugin_interop.rs +++ b/tests/e2e_plugin_interop.rs @@ -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 { 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 { + 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::() { 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::() { + BoolBox::new(self.msg == e.msg) + } else { + BoolBox::new(false) + } + } + fn type_name(&self) -> &'static str { + "EchoBox" + } + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + fn share_box(&self) -> Box { + Box::new(self.clone()) } - fn type_name(&self) -> &'static str { "EchoBox" } - fn clone_box(&self) -> Box { Box::new(self.clone()) } - fn share_box(&self) -> Box { 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]) -> Result, RuntimeError> { + fn create_box( + &self, + name: &str, + args: &[Box], + ) -> Result, 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 { diff --git a/tests/e2e_plugin_net_additional.rs b/tests/e2e_plugin_net_additional.rs index 172bf086..2bfd2e6f 100644 --- a/tests/e2e_plugin_net_additional.rs +++ b/tests/e2e_plugin_net_additional.rs @@ -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, ""); } diff --git a/tests/e2e_plugin_singleton.rs b/tests/e2e_plugin_singleton.rs index f1487228..31002041 100644 --- a/tests/e2e_plugin_singleton.rs +++ b/tests/e2e_plugin_singleton.rs @@ -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#" diff --git a/tests/e2e_plugin_singleton_shutdown.rs b/tests/e2e_plugin_singleton_shutdown.rs index b95e8ca1..1bbb1f97 100644 --- a/tests/e2e_plugin_singleton_shutdown.rs +++ b/tests/e2e_plugin_singleton_shutdown.rs @@ -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#" diff --git a/tests/e2e_plugin_socket.rs b/tests/e2e_plugin_socket.rs index 4260193f..cb1a9896 100644 --- a/tests/e2e_plugin_socket.rs +++ b/tests/e2e_plugin_socket.rs @@ -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 diff --git a/tests/e2e_reserved_name_guard.rs b/tests/e2e_reserved_name_guard.rs index b51f3f2c..13902bd8 100644 --- a/tests/e2e_reserved_name_guard.rs +++ b/tests/e2e_reserved_name_guard.rs @@ -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 { 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 { + 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::() { 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::() { + BoolBox::new(self.inner == s.inner) + } else { + BoolBox::new(false) + } + } + fn type_name(&self) -> &'static str { + "StringBox" + } + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + fn share_box(&self) -> Box { + Box::new(self.clone()) } - fn type_name(&self) -> &'static str { "StringBox" } - fn clone_box(&self) -> Box { Box::new(self.clone()) } - fn share_box(&self) -> Box { Box::new(self.clone()) } } impl BoxFactory for BadFactory { - fn create_box(&self, name: &str, args: &[Box]) -> Result, RuntimeError> { + fn create_box( + &self, + name: &str, + args: &[Box], + ) -> Result, 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] diff --git a/tests/grammar_add_rules.rs b/tests/grammar_add_rules.rs index bf9df103..bed4bce5 100644 --- a/tests/grammar_add_rules.rs +++ b/tests/grammar_add_rules.rs @@ -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 + ); } } } diff --git a/tests/grammar_other_ops.rs b/tests/grammar_other_ops.rs index 2828a8cf..be94c6c9 100644 --- a/tests/grammar_other_ops.rs +++ b/tests/grammar_other_ops.rs @@ -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" + ); } - diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 52aff9f8..d2c9a6c5 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,6 +1,6 @@ -/*! +/*! * Nyash Rust Implementation - Integration Tests - * + * * 完全なNyash言語機能テストスイート * Everything is Box哲学の包括的検証 */ @@ -27,8 +27,10 @@ fn get_variable_value(code: &str, var_name: &str) -> Result { 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), Err(_) => Err(format!("Variable '{}' not found", var_name)), @@ -49,7 +51,7 @@ mod integration_tests { b = 32 result = a + b "#; - + let result = get_variable_value(code, "result").unwrap(); assert_eq!(result, "42"); } @@ -61,7 +63,7 @@ mod integration_tests { name = "Nyash!" message = greeting + name "#; - + let result = get_variable_value(code, "message").unwrap(); assert_eq!(result, "Hello, Nyash!"); } @@ -74,10 +76,10 @@ mod integration_tests { and_result = a && b or_result = a || b "#; - + let and_result = get_variable_value(code, "and_result").unwrap(); let or_result = get_variable_value(code, "or_result").unwrap(); - + assert_eq!(and_result, "false"); assert_eq!(or_result, "true"); } @@ -92,7 +94,7 @@ mod integration_tests { equal = a == a not_equal = a != b "#; - + assert_eq!(get_variable_value(code, "less").unwrap(), "true"); assert_eq!(get_variable_value(code, "greater").unwrap(), "false"); assert_eq!(get_variable_value(code, "equal").unwrap(), "true"); @@ -109,7 +111,7 @@ mod integration_tests { result = "failure" } "#; - + let result = get_variable_value(code, "result").unwrap(); assert_eq!(result, "success"); } @@ -125,7 +127,7 @@ mod integration_tests { } } "#; - + let result = get_variable_value(code, "counter").unwrap(); assert_eq!(result, "5"); } @@ -140,7 +142,7 @@ mod integration_tests { result = add(10, 32) "#; - + let result = get_variable_value(code, "result").unwrap(); assert_eq!(result, "42"); } @@ -160,7 +162,7 @@ mod integration_tests { p.x = 100 result = p.getX() "#; - + let result = get_variable_value(code, "result").unwrap(); assert_eq!(result, "100"); } @@ -190,7 +192,7 @@ mod integration_tests { calc.add(32) result = calc.getValue() "#; - + let result = get_variable_value(code, "result").unwrap(); assert_eq!(result, "42"); } @@ -221,7 +223,7 @@ mod integration_tests { c.increment() result = c.getCount() "#; - + let result = get_variable_value(code, "result").unwrap(); assert_eq!(result, "3"); } @@ -250,10 +252,10 @@ mod integration_tests { result1 = d1.getValue() result2 = d2.getValue() "#; - + let result1 = get_variable_value(code, "result1").unwrap(); let result2 = get_variable_value(code, "result2").unwrap(); - + assert_eq!(result1, "first"); assert_eq!(result2, "second"); } @@ -271,7 +273,7 @@ mod integration_tests { result = getConfig() "#; - + let result = get_variable_value(code, "result").unwrap(); assert_eq!(result, "production v42"); } @@ -284,7 +286,7 @@ mod integration_tests { c = 15 result = (a + b) * c - a "#; - + let result = get_variable_value(code, "result").unwrap(); // (5 + 10) * 15 - 5 = 15 * 15 - 5 = 225 - 5 = 220 assert_eq!(result, "220"); @@ -323,7 +325,7 @@ mod integration_tests { w.setInner("nested value") result = container.getWrapper().getInner() "#; - + let result = get_variable_value(code, "result").unwrap(); assert_eq!(result, "nested value"); } @@ -343,14 +345,17 @@ mod integration_tests { less_eq_result = b <= a greater_eq_result = a >= b "#; - + assert_eq!(get_variable_value(code, "add_result").unwrap(), "25"); assert_eq!(get_variable_value(code, "sub_result").unwrap(), "15"); assert_eq!(get_variable_value(code, "mul_result").unwrap(), "100"); 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] @@ -370,13 +375,13 @@ mod integration_tests { direct_access = obj.value method_result = obj.getValue() "#; - + let direct = get_variable_value(code, "direct_access").unwrap(); let method = get_variable_value(code, "method_result").unwrap(); - + assert_eq!(direct, "test123"); assert_eq!(method, "test123"); - + // thisが正しく動作している証明 assert_eq!(direct, method); } diff --git a/tests/json_v0_stage3/break_continue.json b/tests/json_v0_stage3/break_continue.json new file mode 100644 index 00000000..c24fc15e --- /dev/null +++ b/tests/json_v0_stage3/break_continue.json @@ -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"}} +]} diff --git a/tests/json_v0_stage3/throw_basic.json b/tests/json_v0_stage3/throw_basic.json new file mode 100644 index 00000000..be4ae1bf --- /dev/null +++ b/tests/json_v0_stage3/throw_basic.json @@ -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}} +]} diff --git a/tests/json_v0_stage3/try_catch_finally.json b/tests/json_v0_stage3/try_catch_finally.json new file mode 100644 index 00000000..53079fc4 --- /dev/null +++ b/tests/json_v0_stage3/try_catch_finally.json @@ -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"}} +]} diff --git a/tests/method_id_inject_filebox.rs b/tests/method_id_inject_filebox.rs index 0b4a9772..441eeca1 100644 --- a/tests/method_id_inject_filebox.rs +++ b/tests/method_id_inject_filebox.rs @@ -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,11 +50,17 @@ 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; + } } } } } panic!("FileBox.open not found"); -} \ No newline at end of file +} diff --git a/tests/mir_instruction_set_sync.rs b/tests/mir_instruction_set_sync.rs index db5e3b15..fbfbc653 100644 --- a/tests/mir_instruction_set_sync.rs +++ b/tests/mir_instruction_set_sync.rs @@ -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" + ); } diff --git a/tests/mir_phase6_lowering_ref_ops.rs b/tests/mir_phase6_lowering_ref_ops.rs index dea7494c..85d9a361 100644 --- a/tests/mir_phase6_lowering_ref_ops.rs +++ b/tests/mir_phase6_lowering_ref_ops.rs @@ -1,30 +1,30 @@ /*! * Phase 6.1 MIR Builder Lowering Test - RefNew/RefGet/RefSet - * + * * 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] #[ignore = "MIR13: ref_new/ref_get/ref_set removed; test targets legacy ops"] fn test_mir_phase6_lowering_ref_ops() { // Build AST equivalent to: - // static box Main { - // main() { - // local o; - // o = new Obj(); - // o.x = 1; - // local y; - // y = o.x; - // return y + // static box Main { + // main() { + // local o; + // o = new Obj(); + // o.x = 1; + // local y; + // y = o.x; + // return y // } // } - + let mut main_methods = HashMap::new(); - + // Create main method body let main_body = vec![ // local o @@ -94,7 +94,7 @@ fn test_mir_phase6_lowering_ref_ops() { span: Span::unknown(), }, ]; - + // Create main function declaration let main_function = ASTNode::FunctionDeclaration { name: "main".to_string(), @@ -104,9 +104,9 @@ fn test_mir_phase6_lowering_ref_ops() { is_override: false, span: Span::unknown(), }; - + main_methods.insert("main".to_string(), main_function); - + // Create static box Main let ast = ASTNode::BoxDeclaration { name: "Main".to_string(), @@ -125,43 +125,68 @@ fn test_mir_phase6_lowering_ref_ops() { static_init: None, span: Span::unknown(), }; - + // Build MIR from AST let mut builder = MirBuilder::new(); let result = builder.build_module(ast); assert!(result.is_ok(), "MIR building should succeed"); - + let module = result.unwrap(); - + // Print MIR to string for verification let printer = MirPrinter::new(); let mir_text = printer.print_module(&module); - + 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,47 +195,47 @@ 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 { - target: Box::new(ASTNode::Variable { - name: "obj".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::New { - class: "TestObj".to_string(), - arguments: vec![], - type_arguments: vec![], - span: Span::unknown(), - }), + statements: vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "obj".to_string(), span: Span::unknown(), - }, - ], + }), + value: Box::new(ASTNode::New { + class: "TestObj".to_string(), + arguments: vec![], + type_arguments: vec![], + span: Span::unknown(), + }), + span: Span::unknown(), + }], span: Span::unknown(), }; - + // Build and verify module let mut builder = MirBuilder::new(); let result = builder.build_module(ast); assert!(result.is_ok(), "MIR building should succeed"); - + let module = result.unwrap(); - + // Verify module passes verification use nyash_rust::mir::MirVerifier; let mut verifier = MirVerifier::new(); let verification_result = verifier.verify_module(&module); - + 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)" + ); } } } diff --git a/tests/mir_phase6_vm_ref_ops.rs b/tests/mir_phase6_vm_ref_ops.rs index fd1c09fc..1fd4b2a9 100644 --- a/tests/mir_phase6_vm_ref_ops.rs +++ b/tests/mir_phase6_vm_ref_ops.rs @@ -1,15 +1,15 @@ /*! * Phase 6.1 VM Reference Operations Test - * + * * 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() { @@ -21,10 +21,10 @@ fn test_mir_phase6_vm_ref_ops() { // %x = ref_get %o, "x" // print %x // ret %x - + // Create module let mut module = MirModule::new("test".to_string()); - + // Create main function signature let main_signature = FunctionSignature { name: "main".to_string(), @@ -32,104 +32,107 @@ fn test_mir_phase6_vm_ref_ops() { return_type: MirType::Integer, effects: EffectMask::PURE, }; - + // Create entry block let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); - + // Create basic block let mut block = BasicBlock::new(entry_block); - + // Generate value IDs let obj_type_val = ValueId::new(0); let obj_ref = ValueId::new(1); let one_val = ValueId::new(2); let x_val = ValueId::new(3); - + // Add instructions - + // %0 = const "Obj" block.add_instruction(MirInstruction::Const { dst: obj_type_val, value: ConstValue::String("Obj".to_string()), }); - + // %1 = ref_new %0 block.add_instruction(MirInstruction::RefNew { dst: obj_ref, box_val: obj_type_val, }); - + // %2 = const 1 block.add_instruction(MirInstruction::Const { dst: one_val, value: ConstValue::Integer(1), }); - + // 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 { reference: obj_ref, field: "x".to_string(), value: one_val, }); - + // %3 = ref_get %1, "x" block.add_instruction(MirInstruction::RefGet { dst: x_val, reference: obj_ref, field: "x".to_string(), }); - + // print %3 block.add_instruction(MirInstruction::Print { value: x_val, effects: EffectMask::IO, }); - + // 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); - + // Add function to module module.add_function(main_function); - + // Execute with VM let mut vm = VM::new(); let result = vm.execute_module(&module); - + match result { Ok(result_box) => { println!("✅ VM execution successful!"); - + // Check if result is IntegerBox with value 1 if let Some(int_box) = result_box.as_any().downcast_ref::() { - 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) // Even if return value handling isn't perfect, the main goal is achieved 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); } } - + println!("✅ Phase 6.1 VM reference operations test passed!"); } @@ -137,7 +140,7 @@ fn test_mir_phase6_vm_ref_ops() { fn test_vm_ref_ops_basic_field_storage() { // Test basic field storage without complex MIR let mut vm = VM::new(); - + // This is a white-box test to verify field storage mechanism // In practice, the VM field storage is tested via the full MIR execution above println!("✅ Basic VM field storage mechanism available (tested via full MIR execution)"); @@ -147,7 +150,7 @@ fn test_vm_ref_ops_basic_field_storage() { fn test_barrier_no_op() { // Test that barrier instructions are no-ops but don't cause errors let mut module = MirModule::new("barrier_test".to_string()); - + // Create function with barriers let main_signature = FunctionSignature { name: "main".to_string(), @@ -155,39 +158,38 @@ fn test_barrier_no_op() { return_type: MirType::Void, effects: EffectMask::PURE, }; - + let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); let mut block = BasicBlock::new(entry_block); - + let test_val = ValueId::new(0); - + // Add test instructions block.add_instruction(MirInstruction::Const { dst: test_val, value: ConstValue::Integer(42), }); - + // Test barrier instructions (should be no-ops) - block.add_instruction(MirInstruction::BarrierRead { - ptr: test_val, - }); - - block.add_instruction(MirInstruction::BarrierWrite { - ptr: test_val, - }); - + block.add_instruction(MirInstruction::BarrierRead { ptr: test_val }); + + block.add_instruction(MirInstruction::BarrierWrite { ptr: test_val }); + block.add_instruction(MirInstruction::Return { value: Some(test_val), }); - + main_function.add_block(block); module.add_function(main_function); - + // Execute - barriers should not cause any issues 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"); -} \ No newline at end of file +} diff --git a/tests/mir_phase7_async_ops.rs b/tests/mir_phase7_async_ops.rs index 01a41c37..1d2afb97 100644 --- a/tests/mir_phase7_async_ops.rs +++ b/tests/mir_phase7_async_ops.rs @@ -1,28 +1,28 @@ /*! * Phase 7 MIR Builder & VM Test - Async Operations (nowait/await) - * + * * 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] #[ignore = "MIR13 async: await result wrapping semantics under revision"] fn test_mir_phase7_basic_nowait_await() { // Build AST equivalent to: - // static box Main { - // main() { - // nowait f1 = 42; - // local result = await f1; - // return result + // static box Main { + // main() { + // nowait f1 = 42; + // local result = await f1; + // return result // } // } - + let mut main_methods = HashMap::new(); - + // Create main method body let main_body = vec![ // nowait f1 = 42 @@ -55,7 +55,7 @@ fn test_mir_phase7_basic_nowait_await() { span: Span::unknown(), }, ]; - + // Create main method let main_method = ASTNode::FunctionDeclaration { name: "main".to_string(), @@ -65,9 +65,9 @@ fn test_mir_phase7_basic_nowait_await() { is_override: false, span: Span::unknown(), }; - + main_methods.insert("main".to_string(), main_method); - + // Create static box Main let ast = ASTNode::BoxDeclaration { name: "Main".to_string(), @@ -86,54 +86,56 @@ fn test_mir_phase7_basic_nowait_await() { static_init: None, span: Span::unknown(), }; - + // Build MIR let mut builder = MirBuilder::new(); let result = builder.build_module(ast); - + if let Err(e) = &result { println!("MIR build error: {}", e); } assert!(result.is_ok(), "MIR build should succeed"); - + let module = result.unwrap(); - + // Print MIR for debugging let printer = MirPrinter::new(); let mir_output = printer.print_module(&module); println!("Generated MIR:"); println!("{}", mir_output); - + // 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 let mut vm = VM::new(); let execution_result = vm.execute_module(&module); - + if let Err(e) = &execution_result { println!("VM execution error: {}", e); } assert!(execution_result.is_ok(), "VM execution should succeed"); - + let final_value = execution_result.unwrap(); println!("VM execution result: {}", final_value.to_string_box().value); - + // Should return 42 assert_eq!(final_value.to_string_box().value, "42"); } @@ -142,18 +144,18 @@ fn test_mir_phase7_basic_nowait_await() { #[ignore = "MIR13 async: multiple await aggregation semantics under revision"] fn test_mir_phase7_multiple_nowait_await() { // Build AST equivalent to: - // static box Main { - // main() { - // nowait f1 = 10; - // nowait f2 = 20; - // local result1 = await f1; - // local result2 = await f2; - // return result1 + result2 + // static box Main { + // main() { + // nowait f1 = 10; + // nowait f2 = 20; + // local result1 = await f1; + // local result2 = await f2; + // return result1 + result2 // } // } - + let mut main_methods = HashMap::new(); - + // Create main method body let main_body = vec![ // nowait f1 = 10 @@ -215,7 +217,7 @@ fn test_mir_phase7_multiple_nowait_await() { span: Span::unknown(), }, ]; - + // Create main method let main_method = ASTNode::FunctionDeclaration { name: "main".to_string(), @@ -225,9 +227,9 @@ fn test_mir_phase7_multiple_nowait_await() { is_override: false, span: Span::unknown(), }; - + main_methods.insert("main".to_string(), main_method); - + // Create static box Main let ast = ASTNode::BoxDeclaration { name: "Main".to_string(), @@ -246,28 +248,28 @@ fn test_mir_phase7_multiple_nowait_await() { static_init: None, span: Span::unknown(), }; - + // Build MIR let mut builder = MirBuilder::new(); let result = builder.build_module(ast); - + assert!(result.is_ok(), "MIR build should succeed"); let module = result.unwrap(); - + // Print MIR for debugging let printer = MirPrinter::new(); let mir_output = printer.print_module(&module); println!("Generated MIR for multiple nowait/await:"); println!("{}", mir_output); - + // Test VM execution let mut vm = VM::new(); let execution_result = vm.execute_module(&module); - + assert!(execution_result.is_ok(), "VM execution should succeed"); let final_value = execution_result.unwrap(); println!("VM execution result: {}", final_value.to_string_box().value); - + // Should return 30 (10 + 20) assert_eq!(final_value.to_string_box().value, "30"); } @@ -276,18 +278,18 @@ fn test_mir_phase7_multiple_nowait_await() { #[ignore = "MIR13 async: nested await semantics under revision"] fn test_mir_phase7_nested_await() { // Build AST equivalent to: - // static box Main { - // main() { + // static box Main { + // main() { // nowait outer = { // nowait inner = 5; // await inner * 2 // }; - // return await outer + // return await outer // } // } - + let mut main_methods = HashMap::new(); - + // Create inner computation: nowait inner = 5; await inner * 2 let inner_computation = ASTNode::Program { statements: vec![ @@ -317,7 +319,7 @@ fn test_mir_phase7_nested_await() { ], span: Span::unknown(), }; - + // Create main method body let main_body = vec![ // nowait outer = { ... } @@ -338,7 +340,7 @@ fn test_mir_phase7_nested_await() { span: Span::unknown(), }, ]; - + // Create main method let main_method = ASTNode::FunctionDeclaration { name: "main".to_string(), @@ -348,9 +350,9 @@ fn test_mir_phase7_nested_await() { is_override: false, span: Span::unknown(), }; - + main_methods.insert("main".to_string(), main_method); - + // Create static box Main let ast = ASTNode::BoxDeclaration { name: "Main".to_string(), @@ -369,28 +371,28 @@ fn test_mir_phase7_nested_await() { static_init: None, span: Span::unknown(), }; - + // Build MIR let mut builder = MirBuilder::new(); let result = builder.build_module(ast); - + assert!(result.is_ok(), "MIR build should succeed"); let module = result.unwrap(); - + // Print MIR for debugging let printer = MirPrinter::new(); let mir_output = printer.print_module(&module); println!("Generated MIR for nested await:"); println!("{}", mir_output); - + // Test VM execution let mut vm = VM::new(); let execution_result = vm.execute_module(&module); - + assert!(execution_result.is_ok(), "VM execution should succeed"); let final_value = execution_result.unwrap(); println!("VM execution result: {}", final_value.to_string_box().value); - + // Should return 10 (5 * 2) assert_eq!(final_value.to_string_box().value, "10"); } diff --git a/tests/plugin_contract_net_ids.rs b/tests/plugin_contract_net_ids.rs index 1d59428d..024f76b6 100644 --- a/tests/plugin_contract_net_ids.rs +++ b/tests/plugin_contract_net_ids.rs @@ -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 diff --git a/tests/vm_e2e.rs b/tests/vm_e2e.rs index 8b0d7cae..b48f0485 100644 --- a/tests/vm_e2e.rs +++ b/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 { 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 { + 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::() { 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::() { + BoolBox::new(self.sum == a.sum) + } else { + BoolBox::new(false) + } + } + fn type_name(&self) -> &'static str { + "AdderBox" + } + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + fn share_box(&self) -> Box { + Box::new(self.clone()) } - fn type_name(&self) -> &'static str { "AdderBox" } - fn clone_box(&self) -> Box { Box::new(self.clone()) } - fn share_box(&self) -> Box { 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]) -> Result, RuntimeError> { + fn create_box( + &self, + name: &str, + args: &[Box], + ) -> Result, 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::().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?; - let b = args[1].to_string_box().value.parse::().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::().map_err(|_| { + RuntimeError::TypeError { + message: "AdderBox arg a must be int".into(), + } + })?; + let b = args[1].to_string_box().value.parse::().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( - nyash_rust::interpreter::SharedState::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> 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 + ); } diff --git a/tests/wasm_poc1_basic_operations.rs b/tests/wasm_poc1_basic_operations.rs index 4b2a56b2..83cf1bfa 100644 --- a/tests/wasm_poc1_basic_operations.rs +++ b/tests/wasm_poc1_basic_operations.rs @@ -1,78 +1,99 @@ #![cfg(feature = "wasm-backend")] /*! * Phase 8.2 PoC1 Integration Test - Basic WASM Arithmetic Operations - * + * * Tests end-to-end MIR→WASM compilation and execution for: * - Constant loading - * - Binary arithmetic (addition, subtraction, multiplication) + * - Binary arithmetic (addition, subtraction, multiplication) * - Print output * - 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() { - // Build MIR equivalent to: + // Build MIR equivalent to: // function main() { // %a = const 42 - // %b = const 8 + // %b = const 8 // %result = %a + %b // print %result // return %result // } - + let mut backend = WasmBackend::new(); let mir_module = build_arithmetic_mir_module(); - + // Generate WAT text for debugging let wat_result = backend.compile_to_wat(mir_module.clone()); assert!(wat_result.is_ok(), "WAT generation should succeed"); - + 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"); - - // Compile to WASM binary and execute + 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); if let Err(e) = &wasm_result { println!("WASM compilation error: {}", e); } assert!(wasm_result.is_ok(), "WASM compilation should succeed"); - + let wasm_bytes = wasm_result.unwrap(); assert!(!wasm_bytes.is_empty(), "WASM bytes should not be empty"); - + // Execute with wasmtime let execution_result = backend.execute_wasm(&wasm_bytes); assert!(execution_result.is_ok(), "WASM execution should succeed"); - + let return_value = execution_result.unwrap(); assert_eq!(return_value, 50, "Should return 42 + 8 = 50"); } -#[test] +#[test] fn test_wasm_poc1_multiplication() { // Test: 6 * 7 = 42 let mut backend = WasmBackend::new(); let mir_module = build_multiplication_mir_module(); - + let wasm_result = backend.compile_module(mir_module); assert!(wasm_result.is_ok(), "WASM compilation should succeed"); - + let return_value = backend.execute_wasm(&wasm_result.unwrap()).unwrap(); assert_eq!(return_value, 42, "Should return 6 * 7 = 42"); } @@ -82,10 +103,10 @@ fn test_wasm_poc1_subtraction() { // Test: 50 - 8 = 42 let mut backend = WasmBackend::new(); let mir_module = build_subtraction_mir_module(); - + let wasm_result = backend.compile_module(mir_module); assert!(wasm_result.is_ok(), "WASM compilation should succeed"); - + let return_value = backend.execute_wasm(&wasm_result.unwrap()).unwrap(); assert_eq!(return_value, 42, "Should return 50 - 8 = 42"); } @@ -93,7 +114,7 @@ fn test_wasm_poc1_subtraction() { /// Build MIR module for: 42 + 8 fn build_arithmetic_mir_module() -> MirModule { let mut module = MirModule::new("test_arithmetic".to_string()); - + // Create main function signature let main_signature = FunctionSignature { name: "main".to_string(), @@ -101,147 +122,151 @@ fn build_arithmetic_mir_module() -> MirModule { return_type: MirType::Integer, effects: EffectMask::PURE, }; - + // Create entry block let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); - + // Create basic block let mut block = BasicBlock::new(entry_block); - + // Generate value IDs - let val_a = ValueId::new(0); // 42 - let val_b = ValueId::new(1); // 8 - let result = ValueId::new(2); // 42 + 8 - + let val_a = ValueId::new(0); // 42 + let val_b = ValueId::new(1); // 8 + let result = ValueId::new(2); // 42 + 8 + // Add instructions block.add_instruction(MirInstruction::Const { dst: val_a, value: ConstValue::Integer(42), }); - + block.add_instruction(MirInstruction::Const { dst: val_b, value: ConstValue::Integer(8), }); - + block.add_instruction(MirInstruction::BinOp { dst: result, op: BinaryOp::Add, lhs: val_a, rhs: val_b, }); - + block.add_instruction(MirInstruction::Print { value: result, effects: EffectMask::IO, }); - + // Set terminator instruction (Return must be a terminator, not regular instruction) block.set_terminator(MirInstruction::Return { value: Some(result), }); - + // 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); - + // Add function to module module.add_function(main_function); - + module } /// Build MIR module for: 6 * 7 fn build_multiplication_mir_module() -> MirModule { let mut module = MirModule::new("test_multiplication".to_string()); - + let main_signature = FunctionSignature { name: "main".to_string(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE, }; - + let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); let mut block = BasicBlock::new(entry_block); - - let val_a = ValueId::new(0); // 6 - let val_b = ValueId::new(1); // 7 - let result = ValueId::new(2); // 6 * 7 - + + let val_a = ValueId::new(0); // 6 + let val_b = ValueId::new(1); // 7 + let result = ValueId::new(2); // 6 * 7 + block.add_instruction(MirInstruction::Const { dst: val_a, value: ConstValue::Integer(6), }); - + block.add_instruction(MirInstruction::Const { dst: val_b, value: ConstValue::Integer(7), }); - + block.add_instruction(MirInstruction::BinOp { dst: result, op: BinaryOp::Mul, lhs: val_a, rhs: val_b, }); - + block.set_terminator(MirInstruction::Return { value: Some(result), }); - + main_function.add_block(block); module.add_function(main_function); - + module } -/// Build MIR module for: 50 - 8 +/// Build MIR module for: 50 - 8 fn build_subtraction_mir_module() -> MirModule { let mut module = MirModule::new("test_subtraction".to_string()); - + let main_signature = FunctionSignature { name: "main".to_string(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE, }; - + let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); let mut block = BasicBlock::new(entry_block); - - let val_a = ValueId::new(0); // 50 - let val_b = ValueId::new(1); // 8 - let result = ValueId::new(2); // 50 - 8 - + + let val_a = ValueId::new(0); // 50 + let val_b = ValueId::new(1); // 8 + let result = ValueId::new(2); // 50 - 8 + block.add_instruction(MirInstruction::Const { dst: val_a, value: ConstValue::Integer(50), }); - + block.add_instruction(MirInstruction::Const { dst: val_b, value: ConstValue::Integer(8), }); - + block.add_instruction(MirInstruction::BinOp { dst: result, op: BinaryOp::Sub, lhs: val_a, rhs: val_b, }); - + block.set_terminator(MirInstruction::Return { value: Some(result), }); - + main_function.add_block(block); module.add_function(main_function); - + module } diff --git a/tests/wasm_poc2_box_operations.rs b/tests/wasm_poc2_box_operations.rs index 6ac827ec..2671fdd7 100644 --- a/tests/wasm_poc2_box_operations.rs +++ b/tests/wasm_poc2_box_operations.rs @@ -1,64 +1,80 @@ #![cfg(feature = "wasm-backend")] /*! * Phase 8.3 PoC2 Integration Test - Box Operations in WASM - * + * * Tests end-to-end MIR→WASM compilation and execution for: * - RefNew: Box creation and reference assignment * - RefGet: Field reading from Box objects * - RefSet: Field writing to Box objects * - NewBox: Direct Box allocation with type information - * + * * 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() { // Build MIR equivalent to: // function main() { // %box = new_box "DataBox"(42) - // %ref = ref_new %box + // %ref = ref_new %box // return %ref // Should return box pointer // } - + let mut backend = WasmBackend::new(); let mir_module = build_refnew_mir_module(); - + // Generate WAT text for debugging let wat_result = backend.compile_to_wat(mir_module.clone()); assert!(wat_result.is_ok(), "WAT generation should succeed"); - + 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 let wasm_result = backend.compile_module(mir_module); if let Err(e) = &wasm_result { println!("WASM compilation error: {}", e); } assert!(wasm_result.is_ok(), "WASM compilation should succeed"); - + let wasm_bytes = wasm_result.unwrap(); assert!(!wasm_bytes.is_empty(), "WASM bytes should not be empty"); - + // Execute with wasmtime let execution_result = backend.execute_wasm(&wasm_bytes); assert!(execution_result.is_ok(), "WASM execution should succeed"); - + 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] @@ -71,13 +87,13 @@ fn test_wasm_poc2_refget_refset() { // %result = ref_get %ref.value // return %result // Should return 42 // } - + let mut backend = WasmBackend::new(); let mir_module = build_refget_refset_mir_module(); - + let wasm_result = backend.compile_module(mir_module); assert!(wasm_result.is_ok(), "WASM compilation should succeed"); - + let return_value = backend.execute_wasm(&wasm_result.unwrap()).unwrap(); assert_eq!(return_value, 42, "Should return updated field value"); } @@ -97,13 +113,13 @@ fn test_wasm_poc2_complete_box_workflow() { // %result = ref_get %ref1.value // return %result // Should return 300 // } - + let mut backend = WasmBackend::new(); let mir_module = build_complete_workflow_mir_module(); - + let wasm_result = backend.compile_module(mir_module); assert!(wasm_result.is_ok(), "WASM compilation should succeed"); - + let return_value = backend.execute_wasm(&wasm_result.unwrap()).unwrap(); assert_eq!(return_value, 300, "Should return sum of Box values"); } @@ -111,196 +127,196 @@ fn test_wasm_poc2_complete_box_workflow() { /// Build MIR module for basic RefNew test fn build_refnew_mir_module() -> MirModule { let mut module = MirModule::new("test_refnew".to_string()); - + let main_signature = FunctionSignature { name: "main".to_string(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE, }; - + let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); let mut block = BasicBlock::new(entry_block); - - let init_val = ValueId::new(0); // 42 - let box_ptr = ValueId::new(1); // DataBox pointer - let ref_ptr = ValueId::new(2); // Reference to DataBox - + + let init_val = ValueId::new(0); // 42 + let box_ptr = ValueId::new(1); // DataBox pointer + let ref_ptr = ValueId::new(2); // Reference to DataBox + // Create constant for initialization block.add_instruction(MirInstruction::Const { dst: init_val, value: ConstValue::Integer(42), }); - + // Create DataBox with initial value block.add_instruction(MirInstruction::NewBox { dst: box_ptr, box_type: "DataBox".to_string(), args: vec![init_val], }); - + // Create reference to the Box block.add_instruction(MirInstruction::RefNew { dst: ref_ptr, box_val: box_ptr, }); - + // Return the reference block.set_terminator(MirInstruction::Return { value: Some(ref_ptr), }); - + main_function.add_block(block); module.add_function(main_function); - + module } /// Build MIR module for RefGet/RefSet test fn build_refget_refset_mir_module() -> MirModule { let mut module = MirModule::new("test_refget_refset".to_string()); - + let main_signature = FunctionSignature { name: "main".to_string(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE, }; - + let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); let mut block = BasicBlock::new(entry_block); - - let init_val = ValueId::new(0); // 10 - let new_val = ValueId::new(1); // 42 - let box_ptr = ValueId::new(2); // DataBox pointer - let ref_ptr = ValueId::new(3); // Reference to DataBox - let result = ValueId::new(4); // Read back value - + + let init_val = ValueId::new(0); // 10 + let new_val = ValueId::new(1); // 42 + let box_ptr = ValueId::new(2); // DataBox pointer + let ref_ptr = ValueId::new(3); // Reference to DataBox + let result = ValueId::new(4); // Read back value + // Create constants block.add_instruction(MirInstruction::Const { dst: init_val, value: ConstValue::Integer(10), }); - + block.add_instruction(MirInstruction::Const { dst: new_val, value: ConstValue::Integer(42), }); - + // Create DataBox with initial value block.add_instruction(MirInstruction::NewBox { dst: box_ptr, box_type: "DataBox".to_string(), args: vec![init_val], }); - + // Create reference to the Box block.add_instruction(MirInstruction::RefNew { dst: ref_ptr, box_val: box_ptr, }); - + // Set field value block.add_instruction(MirInstruction::RefSet { reference: ref_ptr, field: "value".to_string(), value: new_val, }); - + // Get field value block.add_instruction(MirInstruction::RefGet { dst: result, reference: ref_ptr, field: "value".to_string(), }); - + // Return the result block.set_terminator(MirInstruction::Return { value: Some(result), }); - + main_function.add_block(block); module.add_function(main_function); - + module } /// Build MIR module for complete Box workflow test fn build_complete_workflow_mir_module() -> MirModule { let mut module = MirModule::new("test_complete_workflow".to_string()); - + let main_signature = FunctionSignature { name: "main".to_string(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE, }; - + let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); let mut block = BasicBlock::new(entry_block); - - let val1_init = ValueId::new(0); // 100 - let val2_init = ValueId::new(1); // 200 - let box1_ptr = ValueId::new(2); // DataBox 1 pointer - let box2_ptr = ValueId::new(3); // DataBox 2 pointer - let ref1_ptr = ValueId::new(4); // Reference to DataBox 1 - let ref2_ptr = ValueId::new(5); // Reference to DataBox 2 - let val1 = ValueId::new(6); // Value from box1 - let val2 = ValueId::new(7); // Value from box2 - let sum = ValueId::new(8); // Sum of values - let result = ValueId::new(9); // Final result - + + let val1_init = ValueId::new(0); // 100 + let val2_init = ValueId::new(1); // 200 + let box1_ptr = ValueId::new(2); // DataBox 1 pointer + let box2_ptr = ValueId::new(3); // DataBox 2 pointer + let ref1_ptr = ValueId::new(4); // Reference to DataBox 1 + let ref2_ptr = ValueId::new(5); // Reference to DataBox 2 + let val1 = ValueId::new(6); // Value from box1 + let val2 = ValueId::new(7); // Value from box2 + let sum = ValueId::new(8); // Sum of values + let result = ValueId::new(9); // Final result + // Create constants block.add_instruction(MirInstruction::Const { dst: val1_init, value: ConstValue::Integer(100), }); - + block.add_instruction(MirInstruction::Const { dst: val2_init, value: ConstValue::Integer(200), }); - + // Create DataBoxes block.add_instruction(MirInstruction::NewBox { dst: box1_ptr, box_type: "DataBox".to_string(), args: vec![val1_init], }); - + block.add_instruction(MirInstruction::NewBox { dst: box2_ptr, box_type: "DataBox".to_string(), args: vec![val2_init], }); - + // Create references block.add_instruction(MirInstruction::RefNew { dst: ref1_ptr, box_val: box1_ptr, }); - + block.add_instruction(MirInstruction::RefNew { dst: ref2_ptr, box_val: box2_ptr, }); - + // Get values from both boxes block.add_instruction(MirInstruction::RefGet { dst: val1, reference: ref1_ptr, field: "value".to_string(), }); - + block.add_instruction(MirInstruction::RefGet { dst: val2, reference: ref2_ptr, field: "value".to_string(), }); - + // Add values block.add_instruction(MirInstruction::BinOp { dst: sum, @@ -308,28 +324,28 @@ fn build_complete_workflow_mir_module() -> MirModule { lhs: val1, rhs: val2, }); - + // Store sum back to first box block.add_instruction(MirInstruction::RefSet { reference: ref1_ptr, field: "value".to_string(), value: sum, }); - + // Read back the result block.add_instruction(MirInstruction::RefGet { dst: result, reference: ref1_ptr, field: "value".to_string(), }); - + // Return the result block.set_terminator(MirInstruction::Return { value: Some(result), }); - + main_function.add_block(block); module.add_function(main_function); - + module } diff --git a/tests/wasm_string_constants.rs b/tests/wasm_string_constants.rs index 232cbd01..55964cfe 100644 --- a/tests/wasm_string_constants.rs +++ b/tests/wasm_string_constants.rs @@ -1,7 +1,7 @@ #![cfg(feature = "wasm-backend")] /*! * WASM String Constants Test - Validates Issue #65 implementation - * + * * Tests string constant support in WASM backend: * - ConstValue::String handling in generate_const * - Data segment generation for string literals @@ -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() { @@ -22,34 +22,58 @@ fn test_wasm_string_constant_basic() { // %str = const "Hello, WASM!" // return %str // Should return StringBox pointer // } - + let mut backend = WasmBackend::new(); let mir_module = build_string_const_mir_module(); - + // 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) - - // Compile to WASM binary + + // Compile to WASM binary let wasm_result = backend.compile_module(mir_module); if let Err(e) = &wasm_result { 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] @@ -57,35 +81,50 @@ fn test_wasm_string_constant_multiple() { // Test multiple string constants to verify data segment management // function main() { // %str1 = const "First" - // %str2 = const "Second" + // %str2 = const "Second" // %str3 = const "First" // Duplicate should reuse data segment // return %str1 // } - + let mut backend = WasmBackend::new(); 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 fn build_string_const_mir_module() -> MirModule { let mut module = MirModule::new("test_string_const".to_string()); - + // Create main function signature let main_signature = FunctionSignature { name: "main".to_string(), @@ -93,26 +132,26 @@ fn build_string_const_mir_module() -> MirModule { return_type: MirType::Integer, // StringBox pointer as i32 effects: EffectMask::PURE, }; - + // Create basic block let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); let mut block = BasicBlock::new(entry_block); - + // %str = const "Hello, WASM!" let str_value = ValueId::new(0); block.instructions.push(MirInstruction::Const { dst: str_value, value: ConstValue::String("Hello, WASM!".to_string()), }); - + // return %str block.terminator = Some(MirInstruction::Return { value: Some(str_value), }); - + main_function.blocks.insert(entry_block, block); - + module.functions.insert("main".to_string(), main_function); module } @@ -120,46 +159,46 @@ fn build_string_const_mir_module() -> MirModule { /// Build a MIR module with multiple string constants fn build_multiple_string_const_mir_module() -> MirModule { let mut module = MirModule::new("test_multiple_strings".to_string()); - + let main_signature = FunctionSignature { name: "main".to_string(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE, }; - + let entry_block = BasicBlockId::new(0); let mut main_function = MirFunction::new(main_signature, entry_block); let mut block = BasicBlock::new(entry_block); - + // %str1 = const "First" let str1_value = ValueId::new(0); block.instructions.push(MirInstruction::Const { dst: str1_value, value: ConstValue::String("First".to_string()), }); - + // %str2 = const "Second" let str2_value = ValueId::new(1); block.instructions.push(MirInstruction::Const { dst: str2_value, value: ConstValue::String("Second".to_string()), }); - + // %str3 = const "First" (duplicate) let str3_value = ValueId::new(2); block.instructions.push(MirInstruction::Const { dst: str3_value, value: ConstValue::String("First".to_string()), }); - + // return %str1 block.terminator = Some(MirInstruction::Return { value: Some(str1_value), }); - + main_function.blocks.insert(entry_block, block); - + module.functions.insert("main".to_string(), main_function); module } diff --git a/tools/aot_smoke_cranelift.ps1 b/tools/aot_smoke_cranelift.ps1 deleted file mode 100644 index f437263c..00000000 --- a/tools/aot_smoke_cranelift.ps1 +++ /dev/null @@ -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 - diff --git a/tools/aot_smoke_cranelift.sh b/tools/aot_smoke_cranelift.sh deleted file mode 100644 index 1708e677..00000000 --- a/tools/aot_smoke_cranelift.sh +++ /dev/null @@ -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 diff --git a/tools/clean_root_artifacts.sh b/tools/clean_root_artifacts.sh new file mode 100644 index 00000000..ff044572 --- /dev/null +++ b/tools/clean_root_artifacts.sh @@ -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 + diff --git a/tools/dev_env.sh b/tools/dev_env.sh index 83f75049..3e61ed97 100644 --- a/tools/dev_env.sh +++ b/tools/dev_env.sh @@ -2,9 +2,10 @@ # Nyash dev environment convenience script # Usage: source tools/dev_env.sh [profile] # Profiles: -# pyvm - Favor PyVM for VM and Bridge -# bridge - Bridge-only helpers (keep interpreter) -# reset - Unset variables set by this script +# 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 - diff --git a/tools/jit_smoke.sh b/tools/jit_smoke.sh deleted file mode 100644 index c81d2a95..00000000 --- a/tools/jit_smoke.sh +++ /dev/null @@ -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 diff --git a/tools/llvm_smoke.sh b/tools/llvm_smoke.sh index 9ccbead4..bf26d738 100644 --- a/tools/llvm_smoke.sh +++ b/tools/llvm_smoke.sh @@ -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 diff --git a/tools/smokes/archive/aot_smoke_cranelift.sh b/tools/smokes/archive/aot_smoke_cranelift.sh new file mode 100644 index 00000000..1b286473 --- /dev/null +++ b/tools/smokes/archive/aot_smoke_cranelift.sh @@ -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 + diff --git a/tools/smokes/archive/jit_smoke.sh b/tools/smokes/archive/jit_smoke.sh new file mode 100644 index 00000000..113d1e2a --- /dev/null +++ b/tools/smokes/archive/jit_smoke.sh @@ -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 + diff --git a/tools/smoke_async_spawn.sh b/tools/smokes/archive/smoke_async_spawn.sh similarity index 85% rename from tools/smoke_async_spawn.sh rename to tools/smokes/archive/smoke_async_spawn.sh index 7b5acd79..48c39e41 100644 --- a/tools/smoke_async_spawn.sh +++ b/tools/smokes/archive/smoke_async_spawn.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash +# Archived: async spawn VM/JIT runs (LLVM is preferred for async) set -euo pipefail -ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) +ROOT_DIR=$(cd "$(dirname "$0")/../../" && pwd) BIN="$ROOT_DIR/target/release/nyash" APP="$ROOT_DIR/apps/tests/async-spawn-instance/main.nyash" @@ -16,3 +17,4 @@ timeout 10s env NYASH_PLUGIN_ONLY=1 NYASH_AWAIT_MAX_MS=5000 "$BIN" --backend cra echo "[smoke] LLVM AOT skipped for this test (no 'env' binding in source)" echo "[smoke] Done" + diff --git a/tools/smoke_phase_10_10.sh b/tools/smokes/archive/smoke_phase_10_10.sh similarity index 91% rename from tools/smoke_phase_10_10.sh rename to tools/smokes/archive/smoke_phase_10_10.sh index d4761b60..ba5f898a 100644 --- a/tools/smoke_phase_10_10.sh +++ b/tools/smokes/archive/smoke_phase_10_10.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Phase 10.10 Smoke Test - Minimal verification for key features -# Usage: ./tools/smoke_phase_10_10.sh +# Phase 10.10 Smoke Test - Minimal verification for key features (archived) +# Note: JIT/Cranelift paths are not maintained in current phase. set -e @@ -33,4 +33,5 @@ echo "4) Testing CountingGC..." echo "✓ CountingGC verified" echo -echo "=== All smoke tests passed! ===" \ No newline at end of file +echo "=== All smoke tests passed! ===" + diff --git a/tools/smoke_vm_jit.sh b/tools/smokes/archive/smoke_vm_jit.sh similarity index 86% rename from tools/smoke_vm_jit.sh rename to tools/smokes/archive/smoke_vm_jit.sh index 2f1d17d5..c8b6e3db 100644 --- a/tools/smoke_vm_jit.sh +++ b/tools/smokes/archive/smoke_vm_jit.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash +# Archived: VM/JIT dual-run helper (JIT is not maintained currently) set -euo pipefail HERE=$(cd "$(dirname "$0")" && pwd) -ROOT=$(cd "$HERE/.." && pwd) +ROOT=$(cd "$HERE/../../" && pwd) SMOKE_FILE="${1:-$ROOT/tmp/smoke_print.nyash}" diff --git a/tools/smokes/curated_llvm.sh b/tools/smokes/curated_llvm.sh new file mode 100644 index 00000000..3e027470 --- /dev/null +++ b/tools/smokes/curated_llvm.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Curated LLVM smoke runner (llvmlite harness) +# Usage: tools/smokes/curated_llvm.sh [--phi-off] + +ROOT_DIR=$(cd "$(dirname "$0")/../.." && pwd) +BIN="$ROOT_DIR/target/release/nyash" + +# Ensure release binary with LLVM feature +if ! [ -x "$BIN" ]; then + echo "[curated-llvm] building nyash (release, features=llvm)" >&2 + cargo build --release --features llvm >/dev/null +fi + +export NYASH_LLVM_USE_HARNESS=1 + +# Optional: PHI-off mode +if [[ "${1:-}" == "--phi-off" ]]; then + export NYASH_MIR_NO_PHI=1 + export NYASH_VERIFY_ALLOW_NO_PHI=1 + echo "[curated-llvm] PHI-off (edge-copy) enabled" >&2 +fi + +run() { + local path="$1" + echo "[curated-llvm] RUN --backend llvm: ${path}" + timeout 10s "$BIN" --backend llvm "$path" >/dev/null +} + +# Core minimal (existing harness sample) +run "$ROOT_DIR/examples/llvm11_core_smoke.nyash" + +# Async/await (LLVM only) +run "$ROOT_DIR/apps/tests/async-await-min/main.nyash" +run "$ROOT_DIR/apps/tests/async-spawn-instance/main.nyash" +NYASH_AWAIT_MAX_MS=100 run "$ROOT_DIR/apps/tests/async-await-timeout-fixed/main.nyash" + +# Control-flow: nested loop break/continue and loop+if phi pattern +run "$ROOT_DIR/apps/tests/nested_loop_inner_break_isolated.nyash" +run "$ROOT_DIR/apps/tests/nested_loop_inner_continue_isolated.nyash" +run "$ROOT_DIR/apps/tests/loop_if_phi.nyash" + +# Peek expression +run "$ROOT_DIR/apps/tests/peek_expr_block.nyash" + +echo "[curated-llvm] OK"