smokes: add curated LLVM runner; archive legacy smokes; PHI-off unified across Bridge/Builder; LLVM resolver tracing; minimal Throw lowering; config env getters; dev profile and root cleaner; docs updated; CI workflow runs curated LLVM (PHI-on/off)
This commit is contained in:
14
.github/workflows/smoke.yml
vendored
14
.github/workflows/smoke.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: Smoke (Phase 10.10)
|
name: LLVM Curated Smoke
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -20,12 +20,13 @@ on:
|
|||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
smoke:
|
llvm-curated:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
# Disable external plugins to keep CI deterministic
|
# Disable external plugins to keep CI deterministic
|
||||||
NYASH_DISABLE_PLUGINS: '1'
|
NYASH_DISABLE_PLUGINS: '1'
|
||||||
|
NYASH_LLVM_USE_HARNESS: '1'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -44,10 +45,14 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-cargo-
|
${{ runner.os }}-cargo-
|
||||||
|
|
||||||
- name: Run smoke script
|
- name: Run curated LLVM smokes (PHI-on)
|
||||||
run: bash tools/smoke_phase_10_10.sh
|
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:
|
jit-direct-smoke:
|
||||||
|
if: false # archived: JIT path not maintained in current phase
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -101,6 +106,7 @@ jobs:
|
|||||||
timeout 15s "$BIN" --jit-direct apps/tests/mir-branch-multi/main.nyash
|
timeout 15s "$BIN" --jit-direct apps/tests/mir-branch-multi/main.nyash
|
||||||
|
|
||||||
smoke-compile-events:
|
smoke-compile-events:
|
||||||
|
if: false # archived: JIT compile-events not maintained in current phase
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
|||||||
@ -13,6 +13,23 @@ What Changed (today)
|
|||||||
- CLI `--using` を追加(`--using "ns as Alias"` / `--using '"apps/foo.nyash" as Foo'`)。
|
- CLI `--using` を追加(`--using "ns as Alias"` / `--using '"apps/foo.nyash" as Foo'`)。
|
||||||
- フィールドは box 先頭のみルールのリンタを Runner に追加(`NYASH_FIELDS_TOP_STRICT=1` でエラー)。
|
- フィールドは box 先頭のみルールのリンタを Runner に追加(`NYASH_FIELDS_TOP_STRICT=1` でエラー)。
|
||||||
- Syntax Torture スイートの実行正規化(末行比較)。一部テスト本文を Nyash 仕様に合わせて修正。
|
- 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(本戦)強化 — コアコレクション配線とエントリ統一
|
- llvmlite/AOT(本戦)強化 — コアコレクション配線とエントリ統一
|
||||||
- Array/Map の BoxCall を NyRT ハンドルAPIに直結:
|
- Array/Map の BoxCall を NyRT ハンドルAPIに直結:
|
||||||
@ -42,11 +59,12 @@ Quick Next (today)
|
|||||||
- `--stage3` CLI フラグから ParserBox へ渡す導線を追加。
|
- `--stage3` CLI フラグから ParserBox へ渡す導線を追加。
|
||||||
- `docs/reference/architecture/parser_mvp_stage3.md` に Stage‑3 設計を記録。
|
- `docs/reference/architecture/parser_mvp_stage3.md` に Stage‑3 設計を記録。
|
||||||
5) 自己ホスト経路で Ny 実装切替のゲート準備(現状は Python MVP 優先を維持)。
|
5) 自己ホスト経路で Ny 実装切替のゲート準備(現状は Python MVP 優先を維持)。
|
||||||
6) テスト:
|
6) テスト:
|
||||||
- `source tools/dev_env.sh pyvm`
|
- `source tools/dev_env.sh pyvm`
|
||||||
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_smoke.sh`
|
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_smoke.sh`
|
||||||
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_bridge_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)`
|
- 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 実行系
|
- Runner/Bridge 実行系
|
||||||
- `--ny-parser-pipe` は `NYASH_PIPE_USE_PYVM=1` で PyVM に委譲(exit code 判定に統一)。
|
- `--ny-parser-pipe` は `NYASH_PIPE_USE_PYVM=1` で PyVM に委譲(exit code 判定に統一)。
|
||||||
- 自己ホスト JSON 生成は Python MVP を優先、LLVM EXE/インラインVMを段階フォールバック。
|
- 自己ホスト JSON 生成は Python MVP を優先、LLVM EXE/インラインVMを段階フォールバック。
|
||||||
@ -148,6 +166,17 @@ Recommended Next (short list)
|
|||||||
- `mod.rs` の残置ヘルパ(usingの候補提示・環境注入ログ)を `pipeline/dispatch` へ集約し、`mod.rs` を最小のオーケストレーションに。
|
- `mod.rs` の残置ヘルパ(usingの候補提示・環境注入ログ)を `pipeline/dispatch` へ集約し、`mod.rs` を最小のオーケストレーションに。
|
||||||
- Namespaces Phase‑1(実装着手): BoxIndex 構築・3段階解決・toml aliases・曖昧エラー改善・トレース
|
- 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)
|
Array/Map Literals Plan(Syntax Sugar)
|
||||||
- Stage‑1: Array literal `[e1, e2, ...]` を実装(ゲート: `NYASH_SYNTAX_SUGAR_LEVEL=basic|full` または `NYASH_ENABLE_ARRAY_LITERAL=1`)。
|
- Stage‑1: Array literal `[e1, e2, ...]` を実装(ゲート: `NYASH_SYNTAX_SUGAR_LEVEL=basic|full` または `NYASH_ENABLE_ARRAY_LITERAL=1`)。
|
||||||
- Lowering: `new ArrayBox()` → 各要素を評価 → `.push(elem)` を左から右に順に発行 → 最後に配列値を返す。
|
- Lowering: `new ArrayBox()` → 各要素を評価 → `.push(elem)` を左から右に順に発行 → 最後に配列値を返す。
|
||||||
|
|||||||
BIN
app_stage3_loop
Normal file
BIN
app_stage3_loop
Normal file
Binary file not shown.
26
apps/tests/llvm_stage3_break_continue.nyash
Normal file
26
apps/tests/llvm_stage3_break_continue.nyash
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Stage-3 regression smoke: break/continue executes once; with bridge throw/try flags enabled the catch sets 42 and finally runs.
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local break_counter = 0
|
||||||
|
loop (true) {
|
||||||
|
break_counter = break_counter + 1
|
||||||
|
if break_counter == 3 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
local caught = 0
|
||||||
|
local finally_flag = 0
|
||||||
|
try {
|
||||||
|
throw 42
|
||||||
|
} catch (Error e) {
|
||||||
|
caught = 42
|
||||||
|
} finally {
|
||||||
|
finally_flag = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Result: " + (break_counter + caught + finally_flag))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
14
apps/tests/llvm_stage3_loop_only.nyash
Normal file
14
apps/tests/llvm_stage3_loop_only.nyash
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Stage-3 smoke (LLVM): loop control only (break/continue) — expected Result: 3
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local counter = 0
|
||||||
|
loop (true) {
|
||||||
|
counter = counter + 1
|
||||||
|
if counter == 3 { break }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
print("Result: " + counter)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
316
build.rs
316
build.rs
@ -40,7 +40,10 @@ type_rules = [
|
|||||||
]
|
]
|
||||||
"#;
|
"#;
|
||||||
fs::write(&grammar_file, minimal).expect("write minimal unified-grammar.toml");
|
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
|
// Read and very light parse: collect
|
||||||
@ -69,7 +72,10 @@ type_rules = [
|
|||||||
for line in content.lines() {
|
for line in content.lines() {
|
||||||
let s = line.trim();
|
let s = line.trim();
|
||||||
if s.starts_with("[keywords.") && s.ends_with("]") {
|
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);
|
current_key = Some(name);
|
||||||
in_operators_add = false;
|
in_operators_add = false;
|
||||||
in_operators_sub = false;
|
in_operators_sub = false;
|
||||||
@ -77,25 +83,66 @@ type_rules = [
|
|||||||
in_operators_div = false;
|
in_operators_div = false;
|
||||||
continue;
|
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.add]" {
|
||||||
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; }
|
current_key = None;
|
||||||
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; }
|
in_operators_add = true;
|
||||||
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; }
|
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(ref key) = current_key {
|
||||||
if let Some(rest) = s.strip_prefix("token") {
|
if let Some(rest) = s.strip_prefix("token") {
|
||||||
if let Some(eq) = rest.find('=') {
|
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));
|
entries.push((key.clone(), val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if in_operators_add || in_operators_sub || in_operators_mul || in_operators_div {
|
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 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" },
|
// Expect lines like: { left = "String", right = "String", result = "String", action = "concat" },
|
||||||
if s.starts_with('{') && s.ends_with("},") || s.ends_with('}') {
|
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 left = String::new();
|
||||||
let mut right = String::new();
|
let mut right = String::new();
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
@ -104,7 +151,7 @@ type_rules = [
|
|||||||
let kv = part.trim();
|
let kv = part.trim();
|
||||||
if let Some(eq) = kv.find('=') {
|
if let Some(eq) = kv.find('=') {
|
||||||
let key = kv[..eq].trim();
|
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 {
|
match key {
|
||||||
"left" => left = val,
|
"left" => left = val,
|
||||||
"right" => right = val,
|
"right" => right = val,
|
||||||
@ -114,21 +161,35 @@ type_rules = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !left.is_empty() && !right.is_empty() && !result.is_empty() && !action.is_empty() {
|
if !left.is_empty()
|
||||||
if in_operators_add { add_rules.push((left, right, result, action)); }
|
&& !right.is_empty()
|
||||||
else if in_operators_sub { sub_rules.push((left, right, result, action)); }
|
&& !result.is_empty()
|
||||||
else if in_operators_mul { mul_rules.push((left, right, result, action)); }
|
&& !action.is_empty()
|
||||||
else if in_operators_div { div_rules.push((left, right, result, action)); }
|
{
|
||||||
|
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(rest) = s.strip_prefix("coercion_strategy") {
|
||||||
if let Some(eq) = rest.find('=') {
|
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();
|
||||||
if in_operators_add { add_coercion = Some(val.clone()); }
|
if in_operators_add {
|
||||||
else if in_operators_sub { sub_coercion = Some(val.clone()); }
|
add_coercion = Some(val.clone());
|
||||||
else if in_operators_mul { mul_coercion = Some(val.clone()); }
|
} else if in_operators_sub {
|
||||||
else if in_operators_div { div_coercion = Some(val.clone()); }
|
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)
|
// Default rules if none present in TOML (keep codegen deterministic)
|
||||||
if add_rules.is_empty() {
|
if add_rules.is_empty() {
|
||||||
add_rules.push(("String".into(), "String".into(), "String".into(), "concat".into()));
|
add_rules.push((
|
||||||
add_rules.push(("String".into(), "Integer".into(), "String".into(), "concat".into()));
|
"String".into(),
|
||||||
add_rules.push(("Integer".into(), "String".into(), "String".into(), "concat".into()));
|
"String".into(),
|
||||||
add_rules.push(("String".into(), "Bool".into(), "String".into(), "concat".into()));
|
"String".into(),
|
||||||
add_rules.push(("Bool".into(), "String".into(), "String".into(), "concat".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((
|
||||||
add_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "add_i64".into()));
|
"String".into(),
|
||||||
add_rules.push(("Float".into(), "Float".into(), "Float".into(), "add_f64".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() {
|
if sub_rules.is_empty() {
|
||||||
sub_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "sub_i64".into()));
|
sub_rules.push((
|
||||||
sub_rules.push(("Float".into(), "Float".into(), "Float".into(), "sub_f64".into()));
|
"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() {
|
if mul_rules.is_empty() {
|
||||||
mul_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "mul_i64".into()));
|
mul_rules.push((
|
||||||
mul_rules.push(("Float".into(), "Float".into(), "Float".into(), "mul_f64".into()));
|
"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() {
|
if div_rules.is_empty() {
|
||||||
div_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "div_i64".into()));
|
div_rules.push((
|
||||||
div_rules.push(("Float".into(), "Float".into(), "Float".into(), "div_f64".into()));
|
"Integer".into(),
|
||||||
|
"Integer".into(),
|
||||||
|
"Integer".into(),
|
||||||
|
"div_i64".into(),
|
||||||
|
));
|
||||||
|
div_rules.push((
|
||||||
|
"Float".into(),
|
||||||
|
"Float".into(),
|
||||||
|
"Float".into(),
|
||||||
|
"div_f64".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Rust code
|
// Generate Rust code
|
||||||
@ -171,32 +307,56 @@ type_rules = [
|
|||||||
let sub_coercion_val = sub_coercion.unwrap_or_else(|| "numeric_only".to_string());
|
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 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());
|
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!(
|
||||||
code.push_str(&format!("pub static OPERATORS_SUB_COERCION: &str = \"{}\";\n", sub_coercion_val));
|
"\npub static OPERATORS_ADD_COERCION: &str = \"{}\";\n",
|
||||||
code.push_str(&format!("pub static OPERATORS_MUL_COERCION: &str = \"{}\";\n", mul_coercion_val));
|
add_coercion_val
|
||||||
code.push_str(&format!("pub static OPERATORS_DIV_COERCION: &str = \"{}\";\n", div_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
|
// Emit add rules
|
||||||
code.push_str("pub static OPERATORS_ADD_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
code.push_str("pub static OPERATORS_ADD_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
||||||
for (l, r, res, act) in &add_rules {
|
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("];");
|
code.push_str("];");
|
||||||
// Emit sub rules
|
// Emit sub rules
|
||||||
code.push_str("\npub static OPERATORS_SUB_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
code.push_str("\npub static OPERATORS_SUB_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
||||||
for (l, r, res, act) in &sub_rules {
|
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("];");
|
code.push_str("];");
|
||||||
// Emit mul rules
|
// Emit mul rules
|
||||||
code.push_str("\npub static OPERATORS_MUL_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
code.push_str("\npub static OPERATORS_MUL_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
||||||
for (l, r, res, act) in &mul_rules {
|
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("];");
|
code.push_str("];");
|
||||||
// Emit div rules
|
// Emit div rules
|
||||||
code.push_str("\npub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
code.push_str("\npub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[\n");
|
||||||
for (l, r, res, act) in &div_rules {
|
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("];");
|
||||||
code.push_str(
|
code.push_str(
|
||||||
@ -207,7 +367,8 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
"#);
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
// --- Naive parse for syntax rules (statements/expressions) ---
|
// --- Naive parse for syntax rules (statements/expressions) ---
|
||||||
let mut syntax_statements: Vec<String> = Vec::new();
|
let mut syntax_statements: Vec<String> = Vec::new();
|
||||||
@ -216,24 +377,43 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
|||||||
let mut in_syntax_expressions = false;
|
let mut in_syntax_expressions = false;
|
||||||
for line in content.lines() {
|
for line in content.lines() {
|
||||||
let s = line.trim();
|
let s = line.trim();
|
||||||
if s == "[syntax.statements]" { in_syntax_statements = true; in_syntax_expressions = false; continue; }
|
if s == "[syntax.statements]" {
|
||||||
if s == "[syntax.expressions]" { in_syntax_statements = false; in_syntax_expressions = true; continue; }
|
in_syntax_statements = true;
|
||||||
if s.starts_with('[') { in_syntax_statements = false; in_syntax_expressions = false; }
|
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 in_syntax_statements {
|
||||||
if let Some(rest) = s.strip_prefix("allow") {
|
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
|
// Expect [ "if", "loop", ... ] possibly spanning multiple lines; simple split for this snapshot
|
||||||
for part in arr.trim_matches(&['[',']'][..]).split(',') {
|
for part in arr.trim_matches(&['[', ']'][..]).split(',') {
|
||||||
let v = part.trim().trim_matches('"'); if !v.is_empty() { syntax_statements.push(v.to_string()); }
|
let v = part.trim().trim_matches('"');
|
||||||
|
if !v.is_empty() {
|
||||||
|
syntax_statements.push(v.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if in_syntax_expressions {
|
if in_syntax_expressions {
|
||||||
if let Some(rest) = s.strip_prefix("allow_binops") {
|
if let Some(rest) = s.strip_prefix("allow_binops") {
|
||||||
if let Some(eq) = rest.find('=') { let arr = rest[eq+1..].trim();
|
if let Some(eq) = rest.find('=') {
|
||||||
for part in arr.trim_matches(&['[',']'][..]).split(',') {
|
let arr = rest[eq + 1..].trim();
|
||||||
let v = part.trim().trim_matches('"'); if !v.is_empty() { syntax_binops.push(v.to_string()); }
|
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() {
|
if syntax_statements.is_empty() {
|
||||||
syntax_statements = vec![
|
syntax_statements = vec![
|
||||||
"box".into(), "global".into(), "function".into(), "static".into(),
|
"box".into(),
|
||||||
"if".into(), "loop".into(), "break".into(), "return".into(), "print".into(),
|
"global".into(),
|
||||||
"nowait".into(), "include".into(), "local".into(), "outbox".into(), "try".into(), "throw".into(), "using".into(), "from".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() {
|
if syntax_binops.is_empty() {
|
||||||
@ -251,10 +445,14 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
|||||||
}
|
}
|
||||||
// Emit syntax arrays
|
// Emit syntax arrays
|
||||||
code.push_str("\npub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[\n");
|
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("];");
|
||||||
code.push_str("\npub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[\n");
|
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("];");
|
code.push_str("];");
|
||||||
|
|
||||||
fs::write(&out_file, code).expect("write generated.rs");
|
fs::write(&out_file, code).expect("write generated.rs");
|
||||||
|
|||||||
@ -6,4 +6,3 @@
|
|||||||
pub fn version() -> &'static str {
|
pub fn version() -> &'static str {
|
||||||
"0.1.0-dev"
|
"0.1.0-dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,4 +2,3 @@ fn main() {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
println!("nyash-next: workspace skeleton is ready.");
|
println!("nyash-next: workspace skeleton is ready.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -111,14 +111,26 @@ pub extern "C" fn nyash_string_substring_hii_export(h: i64, start: i64, end: i64
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let s = if let Some(obj) = handles::get(h as u64) {
|
let s = if let Some(obj) = handles::get(h as u64) {
|
||||||
if let Some(sb) = obj.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
|
if let Some(sb) = obj.as_any().downcast_ref::<StringBox>() {
|
||||||
} else { String::new() };
|
sb.value.clone()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
let n = s.len() as i64;
|
let n = s.len() as i64;
|
||||||
let mut st = if start < 0 { 0 } else { start };
|
let mut st = if start < 0 { 0 } else { start };
|
||||||
let mut en = if end < 0 { 0 } else { end };
|
let mut en = if end < 0 { 0 } else { end };
|
||||||
if st > n { st = n; }
|
if st > n {
|
||||||
if en > n { en = n; }
|
st = n;
|
||||||
if en < st { std::mem::swap(&mut st, &mut en); }
|
}
|
||||||
|
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 (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 sub = s.get(st_u.min(s.len())..en_u.min(s.len())).unwrap_or("");
|
||||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(StringBox::new(sub.to_string()));
|
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(StringBox::new(sub.to_string()));
|
||||||
@ -133,16 +145,38 @@ pub extern "C" fn nyash_string_lastindexof_hh_export(h: i64, n: i64) -> i64 {
|
|||||||
use nyash_rust::{box_trait::StringBox, jit::rt::handles};
|
use nyash_rust::{box_trait::StringBox, jit::rt::handles};
|
||||||
let hay = if h > 0 {
|
let hay = if h > 0 {
|
||||||
if let Some(o) = handles::get(h as u64) {
|
if let Some(o) = handles::get(h as u64) {
|
||||||
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
|
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() {
|
||||||
} else { String::new() }
|
sb.value.clone()
|
||||||
} else { String::new() };
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
let nee = if n > 0 {
|
let nee = if n > 0 {
|
||||||
if let Some(o) = handles::get(n as u64) {
|
if let Some(o) = handles::get(n as u64) {
|
||||||
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
|
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() {
|
||||||
} else { String::new() }
|
sb.value.clone()
|
||||||
} else { String::new() };
|
} else {
|
||||||
if nee.is_empty() { return hay.len() as i64; }
|
String::new()
|
||||||
if let Some(pos) = hay.rfind(&nee) { pos as i64 } else { -1 }
|
}
|
||||||
|
} 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
|
// 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
|
// Helper: build an IntegerBox and return a handle
|
||||||
#[export_name = "nyash.box.from_i64"]
|
#[export_name = "nyash.box.from_i64"]
|
||||||
pub extern "C" fn nyash_box_from_i64(val: i64) -> i64 {
|
pub extern "C" fn nyash_box_from_i64(val: i64) -> i64 {
|
||||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
|
use nyash_rust::{
|
||||||
|
box_trait::{IntegerBox, NyashBox},
|
||||||
|
jit::rt::handles,
|
||||||
|
};
|
||||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(IntegerBox::new(val));
|
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(IntegerBox::new(val));
|
||||||
handles::to_handle(arc) as i64
|
handles::to_handle(arc) as i64
|
||||||
}
|
}
|
||||||
@ -545,8 +582,6 @@ pub extern "C" fn nyash_console_birth_h_export() -> i64 {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ArrayBox birth shim for AOT/JIT handle-based creation
|
// ArrayBox birth shim for AOT/JIT handle-based creation
|
||||||
#[export_name = "nyash.array.birth_h"]
|
#[export_name = "nyash.array.birth_h"]
|
||||||
pub extern "C" fn nyash_array_birth_h_export() -> i64 {
|
pub extern "C" fn nyash_array_birth_h_export() -> i64 {
|
||||||
|
|||||||
@ -9,28 +9,23 @@ pub extern "C" fn nyash_box_birth_h_export(type_id: i64) -> i64 {
|
|||||||
}
|
}
|
||||||
let tid = type_id as u32;
|
let tid = type_id as u32;
|
||||||
// Map type_id back to type name
|
// Map type_id back to type name
|
||||||
let name_opt = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
|
if let Some(meta) = nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(tid) {
|
||||||
.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 Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() {
|
if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() {
|
||||||
if let Ok(b) = host_g.create_box(&box_type, &[]) {
|
if let Ok(b) = host_g.create_box(&meta.box_type, &[]) {
|
||||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
||||||
std::sync::Arc::from(b);
|
std::sync::Arc::from(b);
|
||||||
let h = nyash_rust::jit::rt::handles::to_handle(arc);
|
let h = nyash_rust::jit::rt::handles::to_handle(arc);
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
println!(
|
println!(
|
||||||
"nyrt: birth_h {} (type_id={}) -> handle={}",
|
"nyrt: birth_h {} (type_id={}) -> handle={}",
|
||||||
box_type, tid, h
|
meta.box_type, meta.type_id, h
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return h as i64;
|
return h as i64;
|
||||||
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"nyrt: birth_h {} (type_id={}) FAILED: create_box",
|
"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 {
|
if type_id <= 0 {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let mut invoke: Option<
|
// Resolve invoke_fn via loader metadata
|
||||||
unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32,
|
let meta = if let Some(meta) =
|
||||||
> = None;
|
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(type_id as u32)
|
||||||
// Resolve invoke_fn via temporary instance
|
{
|
||||||
let box_type_name = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
|
meta
|
||||||
.read()
|
} else {
|
||||||
.ok()
|
|
||||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
|
||||||
.and_then(|m| {
|
|
||||||
m.into_iter()
|
|
||||||
.find(|(_k, v)| *v == (type_id as u32))
|
|
||||||
.map(|(k, _v)| k)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| "PluginBox".to_string());
|
|
||||||
if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() {
|
|
||||||
if let Ok(b) = host_g.create_box(&box_type_name, &[]) {
|
|
||||||
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
|
|
||||||
invoke = Some(p.inner.invoke_fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if invoke.is_none() {
|
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
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;
|
return 0;
|
||||||
}
|
};
|
||||||
|
let box_type_name = meta.box_type.clone();
|
||||||
|
let invoke_fn = meta.invoke_fn;
|
||||||
let method_id: u32 = 0; // birth
|
let method_id: u32 = 0; // birth
|
||||||
let instance_id: u32 = 0; // static
|
let instance_id: u32 = 0; // static
|
||||||
// Build TLV args
|
// 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 = vec![0u8; 1024];
|
||||||
let mut out_len: usize = out.len();
|
let mut out_len: usize = out.len();
|
||||||
let rc = unsafe {
|
let rc = unsafe {
|
||||||
invoke.unwrap()(
|
invoke_fn(
|
||||||
type_id as u32,
|
type_id as u32,
|
||||||
method_id,
|
method_id,
|
||||||
instance_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(),
|
box_type_name.clone(),
|
||||||
r_type,
|
r_type,
|
||||||
r_inst,
|
r_inst,
|
||||||
invoke.unwrap(),
|
invoke_fn,
|
||||||
);
|
);
|
||||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> = std::sync::Arc::new(pb);
|
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> = std::sync::Arc::new(pb);
|
||||||
let h = nyash_rust::jit::rt::handles::to_handle(arc);
|
let h = nyash_rust::jit::rt::handles::to_handle(arc);
|
||||||
|
|||||||
@ -197,22 +197,20 @@ pub extern "C" fn nyash_future_spawn_method_h(
|
|||||||
let r_type = u32::from_le_bytes(t);
|
let r_type = u32::from_le_bytes(t);
|
||||||
let r_inst = u32::from_le_bytes(i);
|
let r_inst = u32::from_le_bytes(i);
|
||||||
// Map type_id -> box type name (best-effort)
|
// Map type_id -> box type name (best-effort)
|
||||||
let box_type_name =
|
let meta_opt =
|
||||||
nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host(
|
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
|
||||||
.read()
|
{
|
||||||
.ok()
|
(meta.box_type.clone(), meta.invoke_fn, meta.fini_method_id)
|
||||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
} else {
|
||||||
.and_then(|m| {
|
("PluginBox".to_string(), inv, None)
|
||||||
m.into_iter().find(|(_k, v)| *v == r_type).map(|(k, _v)| k)
|
};
|
||||||
})
|
|
||||||
.unwrap_or_else(|| "PluginBox".to_string());
|
|
||||||
let pb = nyash_rust::runtime::plugin_loader_v2::construct_plugin_box(
|
let pb = nyash_rust::runtime::plugin_loader_v2::construct_plugin_box(
|
||||||
box_type_name,
|
box_type_name,
|
||||||
r_type,
|
r_type,
|
||||||
inv,
|
invoke_ptr,
|
||||||
r_inst,
|
r_inst,
|
||||||
None,
|
fini_id,
|
||||||
);
|
);
|
||||||
fut_box.set_result(Box::new(pb));
|
fut_box.set_result(Box::new(pb));
|
||||||
return;
|
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();
|
let invoke = invoke.unwrap();
|
||||||
// Determine box type name from type_id
|
// Determine box type name from type_id
|
||||||
let box_type_name = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
|
let box_type_name = nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(real_type_id)
|
||||||
.read()
|
.map(|meta| meta.box_type)
|
||||||
.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)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| "PluginBox".to_string());
|
.unwrap_or_else(|| "PluginBox".to_string());
|
||||||
// Determine method name string (from a1 handle→StringBox, or a1 as C string pointer, or legacy VM args)
|
// Determine method name string (from a1 handle→StringBox, or a1 as C string pointer, or legacy VM args)
|
||||||
let mut method_name: Option<String> = None;
|
let mut method_name: Option<String> = None;
|
||||||
|
|||||||
@ -295,20 +295,18 @@ pub extern "C" fn nyash_plugin_invoke3_i64(
|
|||||||
let r_type = u32::from_le_bytes(t);
|
let r_type = u32::from_le_bytes(t);
|
||||||
let r_inst = u32::from_le_bytes(i);
|
let r_inst = u32::from_le_bytes(i);
|
||||||
// Build PluginBoxV2 and register into handle-registry
|
// Build PluginBoxV2 and register into handle-registry
|
||||||
let box_type_name =
|
let meta_opt =
|
||||||
nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
|
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
|
||||||
.read()
|
let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt {
|
||||||
.ok()
|
(meta.box_type.clone(), meta.invoke_fn)
|
||||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
} else {
|
||||||
.and_then(|m| {
|
("PluginBox".to_string(), invoke.unwrap())
|
||||||
m.into_iter().find(|(_k, v)| *v == r_type).map(|(k, _v)| k)
|
};
|
||||||
})
|
|
||||||
.unwrap_or_else(|| "PluginBox".to_string());
|
|
||||||
let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(
|
let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(
|
||||||
box_type_name.clone(),
|
box_type_name.clone(),
|
||||||
r_type,
|
r_type,
|
||||||
r_inst,
|
r_inst,
|
||||||
invoke.unwrap(),
|
invoke_ptr,
|
||||||
);
|
);
|
||||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
||||||
std::sync::Arc::new(pb);
|
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]);
|
i.copy_from_slice(&payload[4..8]);
|
||||||
let r_type = u32::from_le_bytes(t);
|
let r_type = u32::from_le_bytes(t);
|
||||||
let r_inst = u32::from_le_bytes(i);
|
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(
|
let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(
|
||||||
box_type.clone(),
|
box_type_name,
|
||||||
r_type,
|
r_type,
|
||||||
r_inst,
|
r_inst,
|
||||||
invoke.unwrap(),
|
invoke_ptr,
|
||||||
);
|
);
|
||||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
||||||
std::sync::Arc::new(pb);
|
std::sync::Arc::new(pb);
|
||||||
|
|||||||
@ -64,13 +64,27 @@ pub extern "C" fn nyash_map_get_h(handle: i64, key: i64) -> i64 {
|
|||||||
// get_hh: (map_handle, key_handle) -> value_handle
|
// get_hh: (map_handle, key_handle) -> value_handle
|
||||||
#[export_name = "nyash.map.get_hh"]
|
#[export_name = "nyash.map.get_hh"]
|
||||||
pub extern "C" fn nyash_map_get_hh(handle: i64, key_any: i64) -> i64 {
|
pub extern "C" fn nyash_map_get_hh(handle: i64, key_any: i64) -> i64 {
|
||||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
|
use nyash_rust::{
|
||||||
if handle <= 0 { return 0; }
|
box_trait::{IntegerBox, NyashBox},
|
||||||
|
jit::rt::handles,
|
||||||
|
};
|
||||||
|
if handle <= 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if let Some(obj) = handles::get(handle as u64) {
|
if let Some(obj) = handles::get(handle as u64) {
|
||||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
if let Some(map) = obj
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
|
||||||
|
{
|
||||||
let key_box: Box<dyn NyashBox> = if key_any > 0 {
|
let key_box: Box<dyn NyashBox> = if key_any > 0 {
|
||||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
if let Some(k) = handles::get(key_any as u64) {
|
||||||
} else { Box::new(IntegerBox::new(key_any)) };
|
k.clone_box()
|
||||||
|
} else {
|
||||||
|
Box::new(IntegerBox::new(key_any))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box::new(IntegerBox::new(key_any))
|
||||||
|
};
|
||||||
let v = map.get(key_box);
|
let v = map.get(key_box);
|
||||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::from(v);
|
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::from(v);
|
||||||
let h = handles::to_handle(arc);
|
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
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// set_h: (map_handle, key_i64, val) -> i64 (ignored/0)
|
// set_h: (map_handle, key_i64, val) -> i64 (ignored/0)
|
||||||
#[export_name = "nyash.map.set_h"]
|
#[export_name = "nyash.map.set_h"]
|
||||||
pub extern "C" fn nyash_map_set_h(handle: i64, key: i64, val: i64) -> i64 {
|
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
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// set_hh: (map_handle, key_any: handle or i64, val_any: handle or i64) -> i64
|
// set_hh: (map_handle, key_any: handle or i64, val_any: handle or i64) -> i64
|
||||||
#[export_name = "nyash.map.set_hh"]
|
#[export_name = "nyash.map.set_hh"]
|
||||||
pub extern "C" fn nyash_map_set_hh(handle: i64, key_any: i64, val_any: i64) -> i64 {
|
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};
|
use nyash_rust::{
|
||||||
if handle <= 0 { return 0; }
|
box_trait::{IntegerBox, NyashBox},
|
||||||
|
jit::rt::handles,
|
||||||
|
};
|
||||||
|
if handle <= 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if let Some(obj) = handles::get(handle as u64) {
|
if let Some(obj) = handles::get(handle as u64) {
|
||||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
if let Some(map) = obj
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
|
||||||
|
{
|
||||||
let kbox: Box<dyn NyashBox> = if key_any > 0 {
|
let kbox: Box<dyn NyashBox> = if key_any > 0 {
|
||||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
if let Some(k) = handles::get(key_any as u64) {
|
||||||
} else { Box::new(IntegerBox::new(key_any)) };
|
k.clone_box()
|
||||||
|
} else {
|
||||||
|
Box::new(IntegerBox::new(key_any))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box::new(IntegerBox::new(key_any))
|
||||||
|
};
|
||||||
let vbox: Box<dyn NyashBox> = if val_any > 0 {
|
let vbox: Box<dyn NyashBox> = if val_any > 0 {
|
||||||
if let Some(v) = handles::get(val_any as u64) { v.clone_box() } else { Box::new(IntegerBox::new(val_any)) }
|
if let Some(v) = handles::get(val_any as u64) {
|
||||||
} else { Box::new(IntegerBox::new(val_any)) };
|
v.clone_box()
|
||||||
|
} else {
|
||||||
|
Box::new(IntegerBox::new(val_any))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box::new(IntegerBox::new(val_any))
|
||||||
|
};
|
||||||
let _ = map.set(kbox, vbox);
|
let _ = map.set(kbox, vbox);
|
||||||
return 0;
|
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)
|
// has_hh: (map_handle, key_any: handle or i64) -> i64 (0/1)
|
||||||
#[export_name = "nyash.map.has_hh"]
|
#[export_name = "nyash.map.has_hh"]
|
||||||
pub extern "C" fn nyash_map_has_hh(handle: i64, key_any: i64) -> i64 {
|
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};
|
use nyash_rust::{
|
||||||
if handle <= 0 { return 0; }
|
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(obj) = handles::get(handle as u64) {
|
||||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
if let Some(map) = obj
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
|
||||||
|
{
|
||||||
let kbox: Box<dyn NyashBox> = if key_any > 0 {
|
let kbox: Box<dyn NyashBox> = if key_any > 0 {
|
||||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
if let Some(k) = handles::get(key_any as u64) {
|
||||||
} else { Box::new(IntegerBox::new(key_any)) };
|
k.clone_box()
|
||||||
|
} else {
|
||||||
|
Box::new(IntegerBox::new(key_any))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box::new(IntegerBox::new(key_any))
|
||||||
|
};
|
||||||
let v = map.has(kbox);
|
let v = map.has(kbox);
|
||||||
if let Some(b) = v.as_any().downcast_ref::<BoolBox>() { return if b.value { 1 } else { 0 }; }
|
if let Some(b) = v.as_any().downcast_ref::<BoolBox>() {
|
||||||
|
return if b.value { 1 } else { 0 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0
|
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 kbox = Box::new(IntegerBox::new(key));
|
||||||
let v = map.has(kbox);
|
let v = map.has(kbox);
|
||||||
if let Some(b) = v.as_any().downcast_ref::<nyash_rust::box_trait::BoolBox>() { return if b.value { 1 } else { 0 }; }
|
if let Some(b) = v.as_any().downcast_ref::<nyash_rust::box_trait::BoolBox>() {
|
||||||
|
return if b.value { 1 } else { 0 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0
|
0
|
||||||
|
|||||||
@ -83,7 +83,7 @@ pub extern "C" fn nyash_string_concat_is(a: i64, b: *const i8) -> *mut i8 {
|
|||||||
// Exported as: nyash.string.substring_sii(i8* s, i64 start, i64 end) -> i8*
|
// Exported as: nyash.string.substring_sii(i8* s, i64 start, i64 end) -> i8*
|
||||||
#[export_name = "nyash.string.substring_sii"]
|
#[export_name = "nyash.string.substring_sii"]
|
||||||
pub extern "C" fn nyash_string_substring_sii(s: *const i8, start: i64, end: i64) -> *mut i8 {
|
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() {
|
if s.is_null() {
|
||||||
return std::ptr::null_mut();
|
return std::ptr::null_mut();
|
||||||
}
|
}
|
||||||
@ -95,9 +95,15 @@ use std::ffi::CStr;
|
|||||||
let n = src.len() as i64;
|
let n = src.len() as i64;
|
||||||
let mut st = if start < 0 { 0 } else { start };
|
let mut st = if start < 0 { 0 } else { start };
|
||||||
let mut en = if end < 0 { 0 } else { end };
|
let mut en = if end < 0 { 0 } else { end };
|
||||||
if st > n { st = n; }
|
if st > n {
|
||||||
if en > n { en = n; }
|
st = n;
|
||||||
if en < st { std::mem::swap(&mut st, &mut en); }
|
}
|
||||||
|
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 (st_u, en_u) = (st as usize, en as usize);
|
||||||
let sub = &src[st_u.min(src.len())..en_u.min(src.len())];
|
let sub = &src[st_u.min(src.len())..en_u.min(src.len())];
|
||||||
let mut bytes = sub.as_bytes().to_vec();
|
let mut bytes = sub.as_bytes().to_vec();
|
||||||
@ -111,15 +117,27 @@ use std::ffi::CStr;
|
|||||||
#[export_name = "nyash.string.lastIndexOf_ss"]
|
#[export_name = "nyash.string.lastIndexOf_ss"]
|
||||||
pub extern "C" fn nyash_string_lastindexof_ss(s: *const i8, needle: *const i8) -> i64 {
|
pub extern "C" fn nyash_string_lastindexof_ss(s: *const i8, needle: *const i8) -> i64 {
|
||||||
use std::ffi::CStr;
|
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 hs = unsafe { CStr::from_ptr(s) };
|
||||||
let ns = unsafe { CStr::from_ptr(needle) };
|
let ns = unsafe { CStr::from_ptr(needle) };
|
||||||
let h = match hs.to_str() { Ok(v) => v, Err(_) => return -1 };
|
let h = match hs.to_str() {
|
||||||
let n = match ns.to_str() { Ok(v) => v, Err(_) => return -1 };
|
Ok(v) => v,
|
||||||
if n.is_empty() { return h.len() as i64; }
|
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) {
|
if let Some(pos) = h.rfind(n) {
|
||||||
pos as i64
|
pos as i64
|
||||||
} else { -1 }
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exported as: nyash.string.to_i8p_h(i64 handle) -> i8*
|
// Exported as: nyash.string.to_i8p_h(i64 handle) -> i8*
|
||||||
|
|||||||
@ -11,16 +11,16 @@ Module Layout
|
|||||||
- `builder_cursor.rs`: central insertion/terminator guard.
|
- `builder_cursor.rs`: central insertion/terminator guard.
|
||||||
|
|
||||||
Core Invariants
|
Core Invariants
|
||||||
- Resolver-only reads: lowerers fetch MIR values through `Resolver` (no direct `vmap` access for cross-BB values).
|
- Phase‑15 終盤(MIR13運用): MIR 生成層では PHI を生成しない。PHI 合成は LLVM 層(llvmlite/Resolver)が担う。
|
||||||
- Localize at block start: PHIs created at the beginning of the current BB (before non-PHI) to guarantee dominance.
|
- Resolver-only reads: lowerers fetch MIR values through `Resolver`(クロスBBの vmap 直接参照は禁止)。
|
||||||
- Cast placement: perform ptr↔int and width casts outside PHIs, at BB start or just-before-pred-terminator via `with_block`.
|
- Localize at block start: PHIs are created at the beginning of the current BB(non‑PHI より手前)で優位性を保証。
|
||||||
- Sealed SSA: successor PHIs wired by predecessor snapshots and `seal_block`; branch/jump do not push incoming directly.
|
- Cast placement: ptr↔int/幅変換は PHI の外側(BB先頭 or pred終端直前)に配置。
|
||||||
- Cursor discipline: only insert via `BuilderCursor`; post-terminator insertions are forbidden.
|
- Sealed SSA: 後続ブロックの PHI は pred スナップショットと `seal_block` で配線し、branch/jump 自体は incoming を直接積まない。
|
||||||
|
- Cursor discipline: 生成は `BuilderCursor` 経由のみ。terminator 後の挿入は禁止。
|
||||||
|
|
||||||
LoopForm (gated)
|
LoopForm(次フェーズ予定/MIR18)
|
||||||
- Shape: `preheader → header → body → dispatch(phi) → {latch|exit} → header` with PHIs centralized in `dispatch`.
|
- Phase‑15 では LoopForm を MIR に導入しない。既存 CFG(preheader→header→{body|exit}; body→header)から llvmlite がループ搬送 PHI を合成。
|
||||||
- State: model loop-carried values via a `LoopState` aggregate (tag + payloads).
|
- 次フェーズで LoopForm(`LoopHeader/Enter/Latch` などの占位)を MIR に追加し、Resolver/PHI 合成は維持する。
|
||||||
- Goals: move all PHIs to dispatch, ensure header uses are dominated by preheader/dispatch values.
|
|
||||||
|
|
||||||
Types and Bridges
|
Types and Bridges
|
||||||
- Box handle is `i64` across NyRT boundary; strings prefer `i8*` fast paths.
|
- 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
|
- LOWERING_LLVM.md — lowering rules and runtime calls
|
||||||
- RESOLVER_API.md — Resolver design and usage
|
- RESOLVER_API.md — Resolver design and usage
|
||||||
- LLVM_HARNESS.md — llvmlite harness interface and usage
|
- LLVM_HARNESS.md — llvmlite harness interface and usage
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# Resolver API (Minimal i64 Prototype)
|
# Resolver API (Minimal i64 Prototype)
|
||||||
|
|
||||||
Goals
|
Goals
|
||||||
|
- Phase‑15(MIR13運用)における方針: MIR 生成層は PHI を出さず、LLVM 層で PHI を合成する。
|
||||||
- Centralize "ValueId → current-block value" resolution.
|
- Centralize "ValueId → current-block value" resolution.
|
||||||
- Guarantee dominance by localizing values at the start of the block (before non-PHI).
|
- Guarantee dominance by localizing values at the start of the block (before non-PHI).
|
||||||
- De-duplicate per (block, value) to avoid redundant PHIs/casts.
|
- De-duplicate per (block, value) to avoid redundant PHIs/casts.
|
||||||
@ -28,6 +29,7 @@ Ban: Direct `vmap.get(..)` for cross-BB reads
|
|||||||
Next
|
Next
|
||||||
- Migrate remaining `localize_to_i64` call sites to the resolver.
|
- Migrate remaining `localize_to_i64` call sites to the resolver.
|
||||||
- Enforce vmap direct access ban in lowerers (Resolver-only for reads).
|
- Enforce vmap direct access ban in lowerers (Resolver-only for reads).
|
||||||
|
- ループ(while 形 CFG)の検出と、ヘッダ BB での搬送 PHI 合成(preheader/backedge の 2 incoming)を実装。
|
||||||
|
|
||||||
Tracing
|
Tracing
|
||||||
- `NYASH_LLVM_TRACE_PHI=1`: log PHI creation/wiring in the Rust/inkwell path.
|
- `NYASH_LLVM_TRACE_PHI=1`: log PHI creation/wiring in the Rust/inkwell path.
|
||||||
|
|||||||
@ -15,7 +15,13 @@ LifeBox Model(LBM)は「Box=Loop1」という見方でライフサイクル
|
|||||||
- 論文A(MIR13/IR設計): 本稿は将来の拡張。まずAを優先して仕上げ、その後に独立短論文としてまとめる。
|
- 論文A(MIR13/IR設計): 本稿は将来の拡張。まずAを優先して仕上げ、その後に独立短論文としてまとめる。
|
||||||
- 論文B(Nyash言語): birth/fini・async/generator の設計と橋渡し要素。
|
- 論文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`
|
- 下書き本文: `main-paper-jp.md`
|
||||||
|
- MIR進化計画: `mir-evolution-plan.md`(新規追加)
|
||||||
- 補助: 擬似MIRとLowering図、評価計画の雛形を本文内に記載
|
- 補助: 擬似MIRとLowering図、評価計画の雛形を本文内に記載
|
||||||
|
|||||||
119
docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md
Normal file
119
docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# MIR進化計画: MIR14 → MIR13 → MIR17
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
|
||||||
|
LoopForm導入に向けた段階的なMIR進化戦略。PHI命令の責務をLLVM層に移管し、より高レベルな制御構造表現を導入する。
|
||||||
|
|
||||||
|
## 命令数の変遷
|
||||||
|
|
||||||
|
```
|
||||||
|
MIR14(現在): 14命令(PHIあり)
|
||||||
|
↓ Phase 15終盤
|
||||||
|
MIR13: 13命令(PHI除去)
|
||||||
|
↓ Phase 16
|
||||||
|
MIR17: 17命令(LoopForm追加)
|
||||||
|
```
|
||||||
|
|
||||||
|
## MIR13(Phase 15終盤)
|
||||||
|
|
||||||
|
### 削除される命令
|
||||||
|
- **Phi**: SSA PHIノードの生成をLLVM層に移管
|
||||||
|
|
||||||
|
### 残る13命令
|
||||||
|
1. Const
|
||||||
|
2. Load / Store
|
||||||
|
3. UnaryOp / BinOp / Compare / TypeOp
|
||||||
|
4. Branch / Jump / Return
|
||||||
|
5. NewBox / BoxCall
|
||||||
|
6. ExternCall / Call
|
||||||
|
7. Throw / Catch(既存維持)
|
||||||
|
|
||||||
|
### 実装方針
|
||||||
|
- Bridge/BuilderはPHIを生成しない
|
||||||
|
- LLVM層(llvmlite/Resolver)がCFGパターンからPHIを合成
|
||||||
|
- VM/Interpreterは直線的実行を継続(PHI無視)
|
||||||
|
|
||||||
|
## MIR17(Phase 16)
|
||||||
|
|
||||||
|
### 追加される4命令(LoopForm)
|
||||||
|
|
||||||
|
1. **LoopHeader** `{ id, params[] }`
|
||||||
|
- ループヘッダBBの宣言
|
||||||
|
- params[]はループ搬送値のプレースホルダ
|
||||||
|
|
||||||
|
2. **LoopEnter** `{ header: bb, args[] }` (terminator)
|
||||||
|
- Preheaderからヘッダへの遷移
|
||||||
|
- args[]は搬送値の初期値
|
||||||
|
|
||||||
|
3. **LoopLatch** `{ header: bb, args[] }` (terminator)
|
||||||
|
- 本体末尾からヘッダへのバックエッジ
|
||||||
|
- args[]は更新後の搬送値
|
||||||
|
|
||||||
|
4. **LoopExit** `{ exit: bb }` (terminator)
|
||||||
|
- ループからの脱出(break相当)
|
||||||
|
|
||||||
|
### なぜ+4命令か
|
||||||
|
- ループの構造を明示的に表現
|
||||||
|
- Preheader/Header/Body/Exitの役割を宣言的に
|
||||||
|
- LLVM層でのPHI生成が機械的に可能
|
||||||
|
|
||||||
|
## 合流点の扱い(将来検討)
|
||||||
|
|
||||||
|
### MergeForm(追加候補)
|
||||||
|
```rust
|
||||||
|
// If/Else等の合流用(将来のMIR18以降)
|
||||||
|
MergeHeader { id, params[] }
|
||||||
|
MergeEnter { header: bb, args[] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 移行のメリット
|
||||||
|
|
||||||
|
1. **責務分離の明確化**
|
||||||
|
- MIR: 制御構造の宣言
|
||||||
|
- LLVM: SSA表現の生成
|
||||||
|
|
||||||
|
2. **段階的移行**
|
||||||
|
- Phase 15: PHI生成を止める(互換性維持)
|
||||||
|
- Phase 16: LoopForm導入(新機能)
|
||||||
|
|
||||||
|
3. **将来拡張への道筋**
|
||||||
|
- Generator/Async/Effectへの対応準備
|
||||||
|
- LifeBox Model(LBM)への布石
|
||||||
|
|
||||||
|
## AI協働の記録(2025-09-16)
|
||||||
|
|
||||||
|
### Codex提案
|
||||||
|
- PHI合成をllvmlite側で実施
|
||||||
|
- LoopForm/MergeFormの構造化表現
|
||||||
|
- TryRegionによる例外構造化(将来)
|
||||||
|
|
||||||
|
### 人間の洞察
|
||||||
|
- 「MIR13に減らしてから+4でMIR17」
|
||||||
|
- 段階的移行の重要性
|
||||||
|
- 一気に変えないことの価値
|
||||||
|
|
||||||
|
### 合意形成
|
||||||
|
- Phase 15でまずPHI非生成化
|
||||||
|
- 安定後にLoopForm導入
|
||||||
|
- 責務分離を徹底
|
||||||
|
|
||||||
|
## 実装チェックリスト
|
||||||
|
|
||||||
|
### Phase 15終盤(MIR13化)
|
||||||
|
- [ ] Bridge: PHI生成を無効化するフラグ
|
||||||
|
- [ ] llvmlite: Resolver実装(PHI合成)
|
||||||
|
- [ ] Smoke: ループ搬送値のテスト
|
||||||
|
- [ ] VM: PHI無視の動作確認
|
||||||
|
|
||||||
|
### Phase 16(MIR17化)
|
||||||
|
- [ ] MIR: LoopForm命令の追加
|
||||||
|
- [ ] Bridge: Loop構造の認識とLoopForm生成
|
||||||
|
- [ ] llvmlite: LoopFormからPHIへの変換
|
||||||
|
- [ ] ドキュメント: MIR17仕様書
|
||||||
|
|
||||||
|
## 関連文献
|
||||||
|
|
||||||
|
- 論文A(MIR13/14): 基本設計
|
||||||
|
- 論文D(SSA構築): PHI生成の理論
|
||||||
|
- 論文E(本稿): LoopForm理論
|
||||||
|
- CURRENT_TASK.md: 実装計画
|
||||||
@ -1,10 +1,10 @@
|
|||||||
# 🎉 Nyash開発 完全事件コレクション - 世界記録級46事例の記録
|
# 🎉 Nyash開発 完全事件コレクション - 世界記録級47事例の記録
|
||||||
|
|
||||||
## 📝 概要
|
## 📝 概要
|
||||||
|
|
||||||
2025年8月9日から9月15日までのNyash爆速開発で発生した46個の「面白事件」の完全記録。
|
2025年8月9日から9月16日までのNyash爆速開発で発生した47個の「面白事件」の完全記録。
|
||||||
AI協働開発の歴史に残る世界記録級の事件から、開発現場の生々しいドラマまでを網羅。
|
AI協働開発の歴史に残る世界記録級の事件から、開発現場の生々しいドラマまでを網羅。
|
||||||
(2025年9月15日更新:5件追加)
|
(2025年9月16日更新:6件追加)
|
||||||
|
|
||||||
## 🌟 世界記録級TOP10
|
## 🌟 世界記録級TOP10
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ AI協働開発の歴史に残る世界記録級の事件から、開発現場の
|
|||||||
- **意味**: Everything is Fold哲学へ
|
- **意味**: Everything is Fold哲学へ
|
||||||
- **評価**: 「革命的アイデア」認定
|
- **評価**: 「革命的アイデア」認定
|
||||||
|
|
||||||
## 📊 17パターン別分類(全46事例)
|
## 📊 17パターン別分類(全47事例)
|
||||||
|
|
||||||
### 1. 箱化による解決(8事例)
|
### 1. 箱化による解決(8事例)
|
||||||
- 事例001: DebugBoxによる出力制御統一
|
- 事例001: DebugBoxによる出力制御統一
|
||||||
@ -140,10 +140,11 @@ AI協働開発の歴史に残る世界記録級の事件から、開発現場の
|
|||||||
### 16. 予防的設計(1事例)
|
### 16. 予防的設計(1事例)
|
||||||
- 事例039: ID衝突との戦い
|
- 事例039: ID衝突との戦い
|
||||||
|
|
||||||
### 17. 段階的洗練型(1事例)
|
### 17. 段階的洗練型(2事例)
|
||||||
- 事例046: initブロック vs 先頭のみ事件
|
- 事例046: initブロック vs 先頭のみ事件
|
||||||
|
- 事例047: LoopForm先取り抑制事件
|
||||||
|
|
||||||
### その他(10事例)
|
### その他(9事例)
|
||||||
- 事例020: 26日間の奇跡
|
- 事例020: 26日間の奇跡
|
||||||
- 事例021: 2段階パーサー理論
|
- 事例021: 2段階パーサー理論
|
||||||
- 事例022: NyashFlowプロジェクト
|
- 事例022: NyashFlowプロジェクト
|
||||||
@ -189,7 +190,7 @@ ChatGPT: 「!!!」(瞬時に理解)
|
|||||||
- **世界記録**: 20日でネイティブEXE
|
- **世界記録**: 20日でネイティブEXE
|
||||||
|
|
||||||
### 成果
|
### 成果
|
||||||
- **事件数**: 46個(9/15更新)
|
- **事件数**: 47個(9/16更新)
|
||||||
- **パターン**: 17種類
|
- **パターン**: 17種類
|
||||||
- **致命的破綻**: 0回
|
- **致命的破綻**: 0回
|
||||||
- **大規模リファクタ**: 0回
|
- **大規模リファクタ**: 0回
|
||||||
@ -206,7 +207,7 @@ ChatGPT: 「!!!」(瞬時に理解)
|
|||||||
- [技術的ブレークスルー](../paper-l-technical-breakthroughs/README.md)
|
- [技術的ブレークスルー](../paper-l-technical-breakthroughs/README.md)
|
||||||
- [AI協働開発ログ](../paper-g-ai-collaboration/development-log.md)
|
- [AI協働開発ログ](../paper-g-ai-collaboration/development-log.md)
|
||||||
|
|
||||||
## 🚀 2025年9月追加事例(5件)
|
## 🚀 2025年9月追加事例(6件)
|
||||||
|
|
||||||
### 事例042: PyVM迂回路の混乱
|
### 事例042: PyVM迂回路の混乱
|
||||||
- **日付**: 2025年9月15日
|
- **日付**: 2025年9月15日
|
||||||
@ -251,9 +252,18 @@ ChatGPT: 「!!!」(瞬時に理解)
|
|||||||
- **パターン**: 段階的洗練型の典型例(複雑→単純)
|
- **パターン**: 段階的洗練型の典型例(複雑→単純)
|
||||||
- **教訓**: AIは複雑に考えがち、人間の直感が本質を突く
|
- **教訓**: 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)
|
1. **世界記録級の開発速度**(JIT1日、20日でEXE)
|
||||||
2. **AI-人間の新しい関係**(AIが相談、人間が救う)
|
2. **AI-人間の新しい関係**(AIが相談、人間が救う)
|
||||||
|
|||||||
@ -13,6 +13,37 @@ Guiding Principles
|
|||||||
- Short-circuit semantics are already mirrored via logical nodes; Stage-3 should reuse the same block-building infrastructure (Bridge/VM/JIT) to avoid special cases.
|
- 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.
|
- 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
|
JSON v0 Additions
|
||||||
| Construct | JSON v0 Node | Notes |
|
| Construct | JSON v0 Node | Notes |
|
||||||
|------------|-------------------------------------------------|-------|
|
|------------|-------------------------------------------------|-------|
|
||||||
@ -29,7 +60,11 @@ Lowering Strategy (Bridge)
|
|||||||
|
|
||||||
2. **Throw/Try**
|
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.
|
- 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**
|
3. **Metadata Events**
|
||||||
- Augment `crate::jit::observe` with `lower_shortcircuit`/`lower_try` stubs so instrumentation remains coherent when full support is wired.
|
- 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
|
Testing Plan
|
||||||
- Extend selfhost Stage-2 smoke file with guard cases (`return break` etc.) once lowering is live.
|
- 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.
|
- 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
|
Migration Checklist
|
||||||
1. ParserBox emits Stage-3 nodes under `NYASH_PARSER_STAGE3=1` gate to allow gradual rollout.
|
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).
|
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.
|
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).
|
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).
|
5. Documentation kept in sync (`CURRENT_TASK.md`, release notes). ✅ (break/continue) / 🔄 (throw/try runtime notes).
|
||||||
|
|
||||||
References
|
References
|
||||||
- Stage-2 design (`parser_mvp_stage2.md`)
|
- Stage-2 design (`parser_mvp_stage2.md`)
|
||||||
|
|||||||
@ -14,6 +14,9 @@ Statements (`StmtV0`)
|
|||||||
- `Local { name, expr }` (Stage‑2)
|
- `Local { name, expr }` (Stage‑2)
|
||||||
- `If { cond, then: Stmt[], else?: Stmt[] }` (Stage‑2)
|
- `If { cond, then: Stmt[], else?: Stmt[] }` (Stage‑2)
|
||||||
- `Loop { cond, body: Stmt[] }` (Stage‑2; while(cond) body)
|
- `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`)
|
Expressions (`ExprV0`)
|
||||||
- `Int { value }` where `value` is JSON number or digit string
|
- `Int { value }` where `value` is JSON number or digit string
|
||||||
@ -26,6 +29,7 @@ Expressions (`ExprV0`)
|
|||||||
- `Method { recv: Expr, method: string, args[] }` (box method)
|
- `Method { recv: Expr, method: string, args[] }` (box method)
|
||||||
- `New { class: string, args[] }` (construct Box)
|
- `New { class: string, args[] }` (construct Box)
|
||||||
- `Var { name: string }`
|
- `Var { name: string }`
|
||||||
|
- `Throw { expr }` (Stage‑3; currently degrades to expression statement when runtime semantics are disabled)
|
||||||
|
|
||||||
CFG conventions (lowered by the bridge)
|
CFG conventions (lowered by the bridge)
|
||||||
- If: create `then_bb`, `else_bb`, `merge_bb`. Both branches jump to merge if unterminated.
|
- 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.
|
- Short‑circuit Logical: create `rhs_bb`, `fall_bb`, `merge_bb` with constants on fall path.
|
||||||
- All blocks end with a terminator (branch/jump/return).
|
- All blocks end with a terminator (branch/jump/return).
|
||||||
|
|
||||||
PHI merging (current behavior)
|
PHI merging(Phase‑15 終盤の方針)
|
||||||
- If: locals updated in `then`/`else` merge at `merge_bb` via `phi`.
|
- MIR 生成層は PHI を生成しない(MIR13 運用)。If/Loop の合流は LLVM 層(llvmlite/Resolver)が PHI を合成。
|
||||||
- Else欠落時は else 側に分岐前(base)を採用。
|
- ループは既存 CFG(preheader→cond→{body|exit}; body→cond)の検出により、ヘッダ BB で搬送値の PHI を構築。
|
||||||
- 片側にしか存在しない新規変数はスコープ外として外へ未伝播。
|
- 将来(LoopForm= MIR18)では LoopForm 占位命令から逆 Lowering で PHI を自動化予定。
|
||||||
- Loop: `cond_bb` にヘッダ PHI を先置き(preheader/base と latch/body end を合流)。
|
|
||||||
- 目的: Stage‑2 を早期に安定化させるための橋渡し。将来(LoopForm= MIR18)では LoopForm からの逆Loweringで PHI を自動化予定。
|
|
||||||
|
|
||||||
Type meta (emitter/LLVM harness cooperation)
|
Type meta (emitter/LLVM harness cooperation)
|
||||||
- `+` with any string operand → string concat path(handle固定)。
|
- `+` with any string operand → string concat path(handle固定)。
|
||||||
@ -78,3 +80,5 @@ If with local + PHI merge
|
|||||||
{"type":"Return","expr":{"type":"Var","name":"x"}}
|
{"type":"Return","expr":{"type":"Var","name":"x"}}
|
||||||
]}
|
]}
|
||||||
```
|
```
|
||||||
|
- `Break` / `Continue` are emitted when Stage‑3 gate is enabled. When the bridge is compiled without Stage‑3 lowering, frontends may degrade them into `Expr(Int(0))` as a safety fallback.
|
||||||
|
- `Try` nodes include optional `catches` entries of the form `{ param?: string, typeHint?: string, body: Stmt[] }`. Until runtime exception semantics land, downstream lowers only the `try` body and ignores handlers/finally.
|
||||||
|
|||||||
28
docs/smokes.md
Normal file
28
docs/smokes.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Smoke Matrix (Backends)
|
||||||
|
|
||||||
|
Policy
|
||||||
|
- PyVM: partial coverage only (no async/nowait/await/GC/sync). Used for semantics sanity.
|
||||||
|
- LLVM: full coverage via llvmlite harness. Preferred path for async/nowait and Stage-3 control-flow.
|
||||||
|
- JIT: not maintained for these smokes (skip).
|
||||||
|
|
||||||
|
Curated Smokes
|
||||||
|
- Core (LLVM): `examples/llvm11_core_smoke.nyash`
|
||||||
|
- Async (LLVM only):
|
||||||
|
- `apps/tests/async-await-min/main.nyash`
|
||||||
|
- `apps/tests/async-spawn-instance/main.nyash`
|
||||||
|
- `apps/tests/async-await-timeout-fixed/main.nyash` (set `NYASH_AWAIT_MAX_MS=100`)
|
||||||
|
|
||||||
|
Runner Scripts
|
||||||
|
- `tools/smokes/curated_llvm.sh [--phi-off]`
|
||||||
|
- `--phi-off`: enables `NYASH_MIR_NO_PHI=1` + verifier relaxor.
|
||||||
|
- Archived (not maintained): see `tools/smokes/archive/`
|
||||||
|
- `smoke_phase_10_10.sh`, `smoke_vm_jit.sh`, `smoke_async_spawn.sh`, `jit_smoke.sh`, `aot_smoke_cranelift.sh`
|
||||||
|
- These target JIT/Cranelift-era flows. Use the curated LLVM runner instead.
|
||||||
|
|
||||||
|
Flags
|
||||||
|
- `NYASH_LLVM_USE_HARNESS=1`: use llvmlite harness for AOT.
|
||||||
|
- `NYASH_MIR_NO_PHI=1` and `NYASH_VERIFY_ALLOW_NO_PHI=1`: PHI-less MIR (edge-copy) mode.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Marked async smokes should not be run under PyVM/JIT.
|
||||||
|
- Legacy/archived smokes may exist; curated runner avoids them.
|
||||||
@ -35,7 +35,8 @@ impl eframe::App for DebugApp {
|
|||||||
ui.label("Single Line:");
|
ui.label("Single Line:");
|
||||||
let response = ui.text_edit_singleline(&mut self.single_line);
|
let response = ui.text_edit_singleline(&mut self.single_line);
|
||||||
if response.changed() {
|
if response.changed() {
|
||||||
self.event_log.push(format!("Single line changed: '{}'", self.single_line));
|
self.event_log
|
||||||
|
.push(format!("Single line changed: '{}'", self.single_line));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,11 +47,12 @@ impl eframe::App for DebugApp {
|
|||||||
let response = ui.add(
|
let response = ui.add(
|
||||||
egui::TextEdit::multiline(&mut self.text)
|
egui::TextEdit::multiline(&mut self.text)
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
.desired_rows(10)
|
.desired_rows(10),
|
||||||
);
|
);
|
||||||
|
|
||||||
if response.changed() {
|
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();
|
ui.separator();
|
||||||
|
|||||||
@ -8,11 +8,8 @@ use std::path::PathBuf;
|
|||||||
use windows::{
|
use windows::{
|
||||||
core::*,
|
core::*,
|
||||||
Win32::{
|
Win32::{
|
||||||
Foundation::*,
|
Foundation::*, Storage::FileSystem::*, System::Com::*, UI::Shell::*,
|
||||||
Storage::FileSystem::*,
|
|
||||||
UI::Shell::*,
|
|
||||||
UI::WindowsAndMessaging::*,
|
UI::WindowsAndMessaging::*,
|
||||||
System::Com::*,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,7 +37,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
|
|||||||
|
|
||||||
fonts.font_data.insert(
|
fonts.font_data.insert(
|
||||||
"noto_sans_jp".to_owned(),
|
"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
|
fonts
|
||||||
@ -112,7 +110,10 @@ impl NyashExplorer {
|
|||||||
|
|
||||||
// ドライブタイプを取得
|
// ドライブタイプを取得
|
||||||
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
|
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
|
||||||
format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()
|
format!("{}\0", drive_path)
|
||||||
|
.encode_utf16()
|
||||||
|
.collect::<Vec<u16>>()
|
||||||
|
.as_ptr(),
|
||||||
));
|
));
|
||||||
|
|
||||||
drive_info.drive_type = match drive_type_code {
|
drive_info.drive_type = match drive_type_code {
|
||||||
@ -132,13 +133,20 @@ impl NyashExplorer {
|
|||||||
let mut file_system_flags = 0u32;
|
let mut file_system_flags = 0u32;
|
||||||
|
|
||||||
if GetVolumeInformationW(
|
if GetVolumeInformationW(
|
||||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
PCWSTR::from_raw(
|
||||||
|
format!("{}\0", drive_path)
|
||||||
|
.encode_utf16()
|
||||||
|
.collect::<Vec<u16>>()
|
||||||
|
.as_ptr(),
|
||||||
|
),
|
||||||
Some(&mut volume_name),
|
Some(&mut volume_name),
|
||||||
Some(&mut serial_number),
|
Some(&mut serial_number),
|
||||||
Some(&mut max_component_len),
|
Some(&mut max_component_len),
|
||||||
Some(&mut file_system_flags),
|
Some(&mut file_system_flags),
|
||||||
Some(&mut file_system),
|
Some(&mut file_system),
|
||||||
).is_ok() {
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
let volume_name_str = String::from_utf16_lossy(&volume_name)
|
let volume_name_str = String::from_utf16_lossy(&volume_name)
|
||||||
.trim_end_matches('\0')
|
.trim_end_matches('\0')
|
||||||
.to_string();
|
.to_string();
|
||||||
@ -157,11 +165,18 @@ impl NyashExplorer {
|
|||||||
let mut total_free_bytes = 0u64;
|
let mut total_free_bytes = 0u64;
|
||||||
|
|
||||||
if GetDiskFreeSpaceExW(
|
if GetDiskFreeSpaceExW(
|
||||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
PCWSTR::from_raw(
|
||||||
|
format!("{}\0", drive_path)
|
||||||
|
.encode_utf16()
|
||||||
|
.collect::<Vec<u16>>()
|
||||||
|
.as_ptr(),
|
||||||
|
),
|
||||||
Some(&mut free_bytes_available),
|
Some(&mut free_bytes_available),
|
||||||
Some(&mut total_bytes),
|
Some(&mut total_bytes),
|
||||||
Some(&mut total_free_bytes),
|
Some(&mut total_free_bytes),
|
||||||
).is_ok() {
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
drive_info.total_bytes = total_bytes;
|
drive_info.total_bytes = total_bytes;
|
||||||
drive_info.free_bytes = total_free_bytes;
|
drive_info.free_bytes = total_free_bytes;
|
||||||
}
|
}
|
||||||
@ -309,7 +324,8 @@ impl eframe::App for NyashExplorer {
|
|||||||
|
|
||||||
if drive.total_bytes > 0 {
|
if drive.total_bytes > 0 {
|
||||||
let used_bytes = drive.total_bytes - drive.free_bytes;
|
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.horizontal(|ui| {
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
@ -363,7 +379,8 @@ impl eframe::App for NyashExplorer {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("🐱 Nyashについて").clicked() {
|
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() {
|
if ui.button("📊 システム情報").clicked() {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Nyash Explorer with Icons - Windows API Drive Icon Viewer
|
// 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::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
// use std::collections::HashMap;
|
// use std::collections::HashMap;
|
||||||
@ -10,12 +10,7 @@ use std::io::Read;
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use windows::{
|
use windows::{
|
||||||
core::*,
|
core::*,
|
||||||
Win32::{
|
Win32::{Storage::FileSystem::*, System::Com::*, UI::Shell::*, UI::WindowsAndMessaging::*},
|
||||||
Storage::FileSystem::*,
|
|
||||||
UI::Shell::*,
|
|
||||||
UI::WindowsAndMessaging::*,
|
|
||||||
System::Com::*,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
@ -48,7 +43,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
|
|||||||
|
|
||||||
fonts.font_data.insert(
|
fonts.font_data.insert(
|
||||||
"noto_sans_jp".to_owned(),
|
"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
|
fonts
|
||||||
@ -98,7 +94,10 @@ impl NyashExplorer {
|
|||||||
fn get_drive_icon(&self, drive_path: &str) -> Option<ColorImage> {
|
fn get_drive_icon(&self, drive_path: &str) -> Option<ColorImage> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut shfi = SHFILEINFOW::default();
|
let mut shfi = SHFILEINFOW::default();
|
||||||
let drive_path_wide: Vec<u16> = drive_path.encode_utf16().chain(std::iter::once(0)).collect();
|
let drive_path_wide: Vec<u16> = drive_path
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
// アイコンを取得
|
// アイコンを取得
|
||||||
let result = SHGetFileInfoW(
|
let result = SHGetFileInfoW(
|
||||||
@ -158,9 +157,11 @@ impl NyashExplorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ヘッダーから情報を読み取る
|
// ヘッダーから情報を読み取る
|
||||||
let data_offset = u32::from_le_bytes([buffer[10], buffer[11], buffer[12], buffer[13]]) as usize;
|
let data_offset =
|
||||||
|
u32::from_le_bytes([buffer[10], buffer[11], buffer[12], buffer[13]]) as usize;
|
||||||
let width = i32::from_le_bytes([buffer[18], buffer[19], buffer[20], buffer[21]]) as usize;
|
let 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]]);
|
let bits_per_pixel = u16::from_le_bytes([buffer[28], buffer[29]]);
|
||||||
|
|
||||||
// 32ビットBMPのみサポート
|
// 32ビットBMPのみサポート
|
||||||
@ -280,7 +281,10 @@ impl NyashExplorer {
|
|||||||
|
|
||||||
// ドライブタイプを取得
|
// ドライブタイプを取得
|
||||||
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
|
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
|
||||||
format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()
|
format!("{}\0", drive_path)
|
||||||
|
.encode_utf16()
|
||||||
|
.collect::<Vec<u16>>()
|
||||||
|
.as_ptr(),
|
||||||
));
|
));
|
||||||
|
|
||||||
drive_info.drive_type = match drive_type_code {
|
drive_info.drive_type = match drive_type_code {
|
||||||
@ -300,13 +304,20 @@ impl NyashExplorer {
|
|||||||
let mut file_system_flags = 0u32;
|
let mut file_system_flags = 0u32;
|
||||||
|
|
||||||
if GetVolumeInformationW(
|
if GetVolumeInformationW(
|
||||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
PCWSTR::from_raw(
|
||||||
|
format!("{}\0", drive_path)
|
||||||
|
.encode_utf16()
|
||||||
|
.collect::<Vec<u16>>()
|
||||||
|
.as_ptr(),
|
||||||
|
),
|
||||||
Some(&mut volume_name),
|
Some(&mut volume_name),
|
||||||
Some(&mut serial_number),
|
Some(&mut serial_number),
|
||||||
Some(&mut max_component_len),
|
Some(&mut max_component_len),
|
||||||
Some(&mut file_system_flags),
|
Some(&mut file_system_flags),
|
||||||
Some(&mut file_system),
|
Some(&mut file_system),
|
||||||
).is_ok() {
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
let volume_name_str = String::from_utf16_lossy(&volume_name)
|
let volume_name_str = String::from_utf16_lossy(&volume_name)
|
||||||
.trim_end_matches('\0')
|
.trim_end_matches('\0')
|
||||||
.to_string();
|
.to_string();
|
||||||
@ -325,11 +336,18 @@ impl NyashExplorer {
|
|||||||
let mut total_free_bytes = 0u64;
|
let mut total_free_bytes = 0u64;
|
||||||
|
|
||||||
if GetDiskFreeSpaceExW(
|
if GetDiskFreeSpaceExW(
|
||||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
PCWSTR::from_raw(
|
||||||
|
format!("{}\0", drive_path)
|
||||||
|
.encode_utf16()
|
||||||
|
.collect::<Vec<u16>>()
|
||||||
|
.as_ptr(),
|
||||||
|
),
|
||||||
Some(&mut free_bytes_available),
|
Some(&mut free_bytes_available),
|
||||||
Some(&mut total_bytes),
|
Some(&mut total_bytes),
|
||||||
Some(&mut total_free_bytes),
|
Some(&mut total_free_bytes),
|
||||||
).is_ok() {
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
drive_info.total_bytes = total_bytes;
|
drive_info.total_bytes = total_bytes;
|
||||||
drive_info.free_bytes = total_free_bytes;
|
drive_info.free_bytes = total_free_bytes;
|
||||||
}
|
}
|
||||||
@ -339,7 +357,7 @@ impl NyashExplorer {
|
|||||||
let texture = self.ctx.load_texture(
|
let texture = self.ctx.load_texture(
|
||||||
format!("drive_icon_{}", drive_letter),
|
format!("drive_icon_{}", drive_letter),
|
||||||
icon_image,
|
icon_image,
|
||||||
Default::default()
|
Default::default(),
|
||||||
);
|
);
|
||||||
drive_info.icon_texture = Some(texture);
|
drive_info.icon_texture = Some(texture);
|
||||||
}
|
}
|
||||||
@ -363,18 +381,19 @@ impl NyashExplorer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(icon_image) = self.get_drive_icon("C:") {
|
if let Some(icon_image) = self.get_drive_icon("C:") {
|
||||||
let texture = self.ctx.load_texture(
|
let texture =
|
||||||
"drive_icon_C:",
|
self.ctx
|
||||||
icon_image,
|
.load_texture("drive_icon_C:", icon_image, Default::default());
|
||||||
Default::default()
|
|
||||||
);
|
|
||||||
drive_info.icon_texture = Some(texture);
|
drive_info.icon_texture = Some(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.drives.push(drive_info);
|
self.drives.push(drive_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.status = format!("{}個のドライブを検出しました(アイコン付き)", self.drives.len());
|
self.status = format!(
|
||||||
|
"{}個のドライブを検出しました(アイコン付き)",
|
||||||
|
self.drives.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_bytes(bytes: u64) -> String {
|
fn format_bytes(bytes: u64) -> String {
|
||||||
@ -504,7 +523,8 @@ impl eframe::App for NyashExplorer {
|
|||||||
|
|
||||||
if drive.total_bytes > 0 {
|
if drive.total_bytes > 0 {
|
||||||
let used_bytes = drive.total_bytes - drive.free_bytes;
|
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.horizontal(|ui| {
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
@ -558,7 +578,9 @@ impl eframe::App for NyashExplorer {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("🐱 Nyashについて").clicked() {
|
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() {
|
if ui.button("📊 システム情報").clicked() {
|
||||||
|
|||||||
@ -30,7 +30,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
|
|||||||
// 日本語フォント(可変ウェイト)を追加
|
// 日本語フォント(可変ウェイト)を追加
|
||||||
fonts.font_data.insert(
|
fonts.font_data.insert(
|
||||||
"noto_sans_jp".to_owned(),
|
"noto_sans_jp".to_owned(),
|
||||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
|
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf"))
|
||||||
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// フォントファミリーに追加
|
// フォントファミリーに追加
|
||||||
@ -91,7 +92,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
self.status = "Nyash - Everything is Box! 🐱".to_string();
|
self.status = "Nyash - Everything is Box! 🐱".to_string();
|
||||||
}
|
}
|
||||||
if ui.button("使い方").clicked() {
|
if ui.button("使い方").clicked() {
|
||||||
self.status = "テキストを入力して、にゃっしゅプログラムを書こう!".to_string();
|
self.status =
|
||||||
|
"テキストを入力して、にゃっしゅプログラムを書こう!".to_string();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -102,7 +104,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(&self.status);
|
ui.label(&self.status);
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
ui.label(format!("文字数: {} | 行数: {}",
|
ui.label(format!(
|
||||||
|
"文字数: {} | 行数: {}",
|
||||||
self.text.chars().count(),
|
self.text.chars().count(),
|
||||||
self.text.lines().count()
|
self.text.lines().count()
|
||||||
));
|
));
|
||||||
@ -158,7 +161,7 @@ impl eframe::App for NyashNotepad {
|
|||||||
.font(egui::TextStyle::Monospace)
|
.font(egui::TextStyle::Monospace)
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
.desired_rows(20)
|
.desired_rows(20)
|
||||||
.hint_text("ここにテキストを入力してください... にゃ!🐱")
|
.hint_text("ここにテキストを入力してください... にゃ!🐱"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -168,12 +171,16 @@ impl eframe::App for NyashNotepad {
|
|||||||
ui.label("クイック挿入: ");
|
ui.label("クイック挿入: ");
|
||||||
|
|
||||||
if ui.button("📝 Nyashサンプル").clicked() {
|
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("box こんにちは世界 {\n");
|
||||||
self.text.push_str(" init { メッセージ }\n");
|
self.text.push_str(" init { メッセージ }\n");
|
||||||
self.text.push_str(" \n");
|
self.text.push_str(" \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");
|
self.text.push_str(" \n");
|
||||||
self.text.push_str(" 挨拶() {\n");
|
self.text.push_str(" 挨拶() {\n");
|
||||||
@ -189,7 +196,10 @@ impl eframe::App for NyashNotepad {
|
|||||||
|
|
||||||
if ui.button("🕐 現在時刻").clicked() {
|
if ui.button("🕐 現在時刻").clicked() {
|
||||||
let now = chrono::Local::now();
|
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();
|
self.status = "現在時刻を挿入しました".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -107,7 +107,7 @@ impl eframe::App for NyashNotepad {
|
|||||||
.font(egui::TextStyle::Monospace)
|
.font(egui::TextStyle::Monospace)
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
.desired_rows(20)
|
.desired_rows(20)
|
||||||
.hint_text("ここにテキストを入力してください... にゃ!")
|
.hint_text("ここにテキストを入力してください... にゃ!"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -120,7 +120,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
self.text.push_str(" init { message }\n");
|
self.text.push_str(" init { message }\n");
|
||||||
self.text.push_str(" \n");
|
self.text.push_str(" \n");
|
||||||
self.text.push_str(" HelloWorld() {\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.text.push_str("}\n");
|
self.text.push_str("}\n");
|
||||||
self.status = "Nyashサンプルコードを挿入しました".to_string();
|
self.status = "Nyashサンプルコードを挿入しました".to_string();
|
||||||
@ -128,7 +129,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
|
|
||||||
if ui.button("時刻挿入").clicked() {
|
if ui.button("時刻挿入").clicked() {
|
||||||
let now = chrono::Local::now();
|
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();
|
self.status = "現在時刻を挿入しました".to_string();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -113,7 +113,7 @@ impl eframe::App for NyashNotepad {
|
|||||||
.font(egui::TextStyle::Monospace)
|
.font(egui::TextStyle::Monospace)
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
.desired_rows(20)
|
.desired_rows(20)
|
||||||
.hint_text("Type your text here... nya!")
|
.hint_text("Type your text here... nya!"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,7 +128,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
self.text.push_str(" init { message }\n");
|
self.text.push_str(" init { message }\n");
|
||||||
self.text.push_str(" \n");
|
self.text.push_str(" \n");
|
||||||
self.text.push_str(" HelloWorld() {\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(" \n");
|
self.text.push_str(" \n");
|
||||||
self.text.push_str(" greet() {\n");
|
self.text.push_str(" greet() {\n");
|
||||||
@ -144,7 +145,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
|
|
||||||
if ui.button("Current Time").clicked() {
|
if ui.button("Current Time").clicked() {
|
||||||
let now = chrono::Local::now();
|
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();
|
self.status = "Timestamp inserted".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -104,7 +104,7 @@ impl eframe::App for NyashNotepad {
|
|||||||
.font(egui::TextStyle::Monospace)
|
.font(egui::TextStyle::Monospace)
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
.desired_rows(20)
|
.desired_rows(20)
|
||||||
.hint_text("Type your text here... nya!")
|
.hint_text("Type your text here... nya!"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,7 +117,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
self.text.push_str(" init { message }\n");
|
self.text.push_str(" init { message }\n");
|
||||||
self.text.push_str(" \n");
|
self.text.push_str(" \n");
|
||||||
self.text.push_str(" HelloWorld() {\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("}\n");
|
self.text.push_str("}\n");
|
||||||
self.status = "Nyash sample code inserted".to_string();
|
self.status = "Nyash sample code inserted".to_string();
|
||||||
@ -125,7 +126,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
|
|
||||||
if ui.button("Insert Timestamp").clicked() {
|
if ui.button("Insert Timestamp").clicked() {
|
||||||
let now = chrono::Local::now();
|
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();
|
self.status = "Timestamp inserted".to_string();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -132,7 +132,7 @@ impl eframe::App for NyashNotepad {
|
|||||||
.font(egui::TextStyle::Monospace)
|
.font(egui::TextStyle::Monospace)
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
.desired_rows(20)
|
.desired_rows(20)
|
||||||
.hint_text("Type your text here... nya!")
|
.hint_text("Type your text here... nya!"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -145,7 +145,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
self.text.push_str(" init { message }\n");
|
self.text.push_str(" init { message }\n");
|
||||||
self.text.push_str(" \n");
|
self.text.push_str(" \n");
|
||||||
self.text.push_str(" HelloWorld() {\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("}\n");
|
self.text.push_str("}\n");
|
||||||
self.status = "Nyash sample code inserted".to_string();
|
self.status = "Nyash sample code inserted".to_string();
|
||||||
@ -153,7 +154,8 @@ impl eframe::App for NyashNotepad {
|
|||||||
|
|
||||||
if ui.button("Insert Timestamp").clicked() {
|
if ui.button("Insert Timestamp").clicked() {
|
||||||
let now = chrono::Local::now();
|
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();
|
self.status = "Timestamp inserted".to_string();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,12 +4,7 @@
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use windows::{
|
use windows::{
|
||||||
core::*,
|
core::*,
|
||||||
Win32::{
|
Win32::{Graphics::Gdi::*, Storage::FileSystem::*, UI::Shell::*, UI::WindowsAndMessaging::*},
|
||||||
Storage::FileSystem::*,
|
|
||||||
UI::Shell::*,
|
|
||||||
UI::WindowsAndMessaging::*,
|
|
||||||
Graphics::Gdi::*,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -19,7 +14,10 @@ fn main() {
|
|||||||
|
|
||||||
// C:ドライブのアイコンを取得
|
// C:ドライブのアイコンを取得
|
||||||
let drive_path = "C:\\";
|
let drive_path = "C:\\";
|
||||||
let drive_path_wide: Vec<u16> = drive_path.encode_utf16().chain(std::iter::once(0)).collect();
|
let drive_path_wide: Vec<u16> = drive_path
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut shfi = SHFILEINFOW::default();
|
let mut shfi = SHFILEINFOW::default();
|
||||||
|
|
||||||
@ -51,7 +49,7 @@ fn main() {
|
|||||||
let size = GetObjectW(
|
let size = GetObjectW(
|
||||||
icon_info.hbmColor.into(),
|
icon_info.hbmColor.into(),
|
||||||
std::mem::size_of::<BITMAP>() as i32,
|
std::mem::size_of::<BITMAP>() as i32,
|
||||||
Some(&mut bitmap as *mut _ as *mut _)
|
Some(&mut bitmap as *mut _ as *mut _),
|
||||||
);
|
);
|
||||||
|
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
@ -69,14 +67,20 @@ fn main() {
|
|||||||
let copied = GetBitmapBits(
|
let copied = GetBitmapBits(
|
||||||
icon_info.hbmColor,
|
icon_info.hbmColor,
|
||||||
pixels.len() as i32,
|
pixels.len() as i32,
|
||||||
pixels.as_mut_ptr() as *mut _
|
pixels.as_mut_ptr() as *mut _,
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("Copied {} bytes of pixel data", copied);
|
println!("Copied {} bytes of pixel data", copied);
|
||||||
|
|
||||||
// 簡易的にBMPファイルとして保存
|
// 簡易的にBMPファイルとして保存
|
||||||
if copied > 0 {
|
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");
|
println!("Saved as c_drive_icon.bmp");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +128,8 @@ fn save_as_bmp(filename: &str, pixels: &[u8], width: i32, height: i32, bits_per_
|
|||||||
file.write_all(&1u16.to_le_bytes()).unwrap(); // プレーン数
|
file.write_all(&1u16.to_le_bytes()).unwrap(); // プレーン数
|
||||||
file.write_all(&bits_per_pixel.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(&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(); // X解像度
|
||||||
file.write_all(&0i32.to_le_bytes()).unwrap(); // Y解像度
|
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(); // カラーテーブル数
|
||||||
|
|||||||
@ -3,9 +3,12 @@
|
|||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
// ===== Error Codes (aligned with existing plugins) =====
|
// ===== Error Codes (aligned with existing plugins) =====
|
||||||
const NYB_SUCCESS: i32 = 0;
|
const NYB_SUCCESS: i32 = 0;
|
||||||
@ -17,27 +20,34 @@ const NYB_E_PLUGIN_ERROR: i32 = -5;
|
|||||||
const NYB_E_INVALID_HANDLE: i32 = -8;
|
const NYB_E_INVALID_HANDLE: i32 = -8;
|
||||||
|
|
||||||
// ===== Method IDs =====
|
// ===== Method IDs =====
|
||||||
const METHOD_BIRTH: u32 = 0; // constructor -> returns instance_id (u32 LE, no TLV)
|
const METHOD_BIRTH: u32 = 0; // constructor -> returns instance_id (u32 LE, no TLV)
|
||||||
const METHOD_LENGTH: u32 = 1; // returns TLV i64
|
const METHOD_LENGTH: u32 = 1; // returns TLV i64
|
||||||
const METHOD_GET: u32 = 2; // args: i64 index -> 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_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_SET: u32 = 4; // args: i64 index, i64 value -> returns TLV i64 (new length)
|
||||||
const METHOD_FINI: u32 = u32::MAX; // destructor
|
const METHOD_FINI: u32 = u32::MAX; // destructor
|
||||||
|
|
||||||
// Assign a unique type_id for ArrayBox (as declared in nyash.toml)
|
// Assign a unique type_id for ArrayBox (as declared in nyash.toml)
|
||||||
const TYPE_ID_ARRAY: u32 = 10;
|
const TYPE_ID_ARRAY: u32 = 10;
|
||||||
|
|
||||||
// ===== Instance state (PoC: store i64 values only) =====
|
// ===== Instance state (PoC: store i64 values only) =====
|
||||||
struct ArrayInstance { data: Vec<i64> }
|
struct ArrayInstance {
|
||||||
|
data: Vec<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
static INSTANCES: Lazy<Mutex<HashMap<u32, ArrayInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static INSTANCES: Lazy<Mutex<HashMap<u32, ArrayInstance>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
NYB_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -49,17 +59,25 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_ARRAY { return NYB_E_INVALID_TYPE; }
|
if type_id != TYPE_ID_ARRAY {
|
||||||
|
return NYB_E_INVALID_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
METHOD_BIRTH => {
|
METHOD_BIRTH => {
|
||||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
|
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);
|
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
map.insert(id, ArrayInstance { data: Vec::new() });
|
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();
|
let bytes = id.to_le_bytes();
|
||||||
std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
|
std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
|
||||||
*result_len = 4;
|
*result_len = 4;
|
||||||
@ -69,39 +87,71 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
map.remove(&instance_id);
|
map.remove(&instance_id);
|
||||||
NYB_SUCCESS
|
NYB_SUCCESS
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_LENGTH => {
|
METHOD_LENGTH => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_GET => {
|
METHOD_GET => {
|
||||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
let idx = match read_arg_i64(args, args_len, 0) {
|
||||||
if idx < 0 { return NYB_E_INVALID_ARGS; }
|
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 Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
let i = idx as usize;
|
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);
|
return write_tlv_i64(inst.data[i], result, result_len);
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_PUSH => {
|
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 Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&instance_id) {
|
if let Some(inst) = map.get_mut(&instance_id) {
|
||||||
inst.data.push(val);
|
inst.data.push(val);
|
||||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_SET => {
|
METHOD_SET => {
|
||||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
let idx = match read_arg_i64(args, args_len, 0) {
|
||||||
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
Some(v) => v,
|
||||||
if idx < 0 { return NYB_E_INVALID_ARGS; }
|
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 Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&instance_id) {
|
if let Some(inst) = map.get_mut(&instance_id) {
|
||||||
let i = idx as usize;
|
let i = idx as usize;
|
||||||
@ -114,8 +164,12 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
return NYB_E_INVALID_ARGS;
|
return NYB_E_INVALID_ARGS;
|
||||||
}
|
}
|
||||||
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
return write_tlv_i64(inst.data.len() as i64, result, result_len);
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => NYB_E_INVALID_METHOD,
|
_ => NYB_E_INVALID_METHOD,
|
||||||
}
|
}
|
||||||
@ -136,7 +190,9 @@ pub struct NyashTypeBoxFfi {
|
|||||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||||
|
|
||||||
extern "C" fn array_resolve(name: *const c_char) -> u32 {
|
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();
|
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
|
||||||
match s.as_ref() {
|
match s.as_ref() {
|
||||||
"len" | "length" => METHOD_LENGTH,
|
"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 {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
METHOD_LENGTH => {
|
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 => {
|
METHOD_GET => {
|
||||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
let idx = match read_arg_i64(args, args_len, 0) {
|
||||||
if idx < 0 { return NYB_E_INVALID_ARGS; }
|
Some(v) => v,
|
||||||
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; }
|
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 => {
|
METHOD_SET => {
|
||||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
let idx = match read_arg_i64(args, args_len, 0) {
|
||||||
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
Some(v) => v,
|
||||||
if idx < 0 { return NYB_E_INVALID_ARGS; }
|
None => 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 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 => {
|
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) {
|
||||||
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; }
|
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,
|
_ => NYB_E_INVALID_METHOD,
|
||||||
}
|
}
|
||||||
@ -187,7 +312,9 @@ pub static nyash_typebox_ArrayBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
|||||||
// ===== Minimal TLV helpers (compatible with host expectations) =====
|
// ===== Minimal TLV helpers (compatible with host expectations) =====
|
||||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
if result_len.is_null() { return false; }
|
if result_len.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if result.is_null() || *result_len < needed {
|
if result.is_null() || *result_len < needed {
|
||||||
*result_len = needed;
|
*result_len = needed;
|
||||||
return true;
|
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 {
|
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; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return NYB_E_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
let mut buf: Vec<u8> =
|
||||||
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
||||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
||||||
for (tag, payload) in payloads {
|
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)
|
/// Read nth TLV argument as i64 (tag 3)
|
||||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||||
let mut off = 4usize; // skip header
|
let mut off = 4usize; // skip header
|
||||||
for i in 0..=n {
|
for i in 0..=n {
|
||||||
if buf.len() < off + 4 { return None; }
|
if buf.len() < off + 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let tag = buf[off];
|
let tag = buf[off];
|
||||||
let _rsv = buf[off+1];
|
let _rsv = buf[off + 1];
|
||||||
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
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 + size {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
if i == n {
|
if i == n {
|
||||||
if tag != 3 || size != 8 { return None; }
|
if tag != 3 || size != 8 {
|
||||||
let mut b = [0u8;8];
|
return None;
|
||||||
b.copy_from_slice(&buf[off+4 .. off+4+8]);
|
}
|
||||||
|
let mut b = [0u8; 8];
|
||||||
|
b.copy_from_slice(&buf[off + 4..off + 4 + 8]);
|
||||||
return Some(i64::from_le_bytes(b));
|
return Some(i64::from_le_bytes(b));
|
||||||
}
|
}
|
||||||
off += 4 + size;
|
off += 4 + size;
|
||||||
|
|||||||
@ -2,9 +2,12 @@
|
|||||||
//! Provides simple stdout printing via ConsoleBox
|
//! Provides simple stdout printing via ConsoleBox
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
// ===== Error Codes (BID-1) =====
|
// ===== Error Codes (BID-1) =====
|
||||||
const NYB_SUCCESS: i32 = 0;
|
const NYB_SUCCESS: i32 = 0;
|
||||||
@ -16,7 +19,7 @@ const NYB_E_PLUGIN_ERROR: i32 = -5;
|
|||||||
|
|
||||||
// ===== Method IDs =====
|
// ===== Method IDs =====
|
||||||
const METHOD_BIRTH: u32 = 0;
|
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_PRINTLN: u32 = 2; // println(text)
|
||||||
const METHOD_FINI: u32 = u32::MAX;
|
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]
|
const TYPE_ID_CONSOLE_BOX: u32 = 5; // keep in sync with nyash.toml [box_types]
|
||||||
|
|
||||||
// ===== Instance management =====
|
// ===== Instance management =====
|
||||||
struct ConsoleInstance { /* no state for now */ }
|
struct ConsoleInstance {/* no state for now */}
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
static INSTANCES: Lazy<Mutex<HashMap<u32, ConsoleInstance>>> = Lazy::new(|| {
|
static INSTANCES: Lazy<Mutex<HashMap<u32, ConsoleInstance>>> =
|
||||||
Mutex::new(HashMap::new())
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
});
|
|
||||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
// ===== TLV helpers (minimal) =====
|
// ===== TLV helpers (minimal) =====
|
||||||
// TLV layout: [u16 ver=1][u16 argc][entries...]
|
// TLV layout: [u16 ver=1][u16 argc][entries...]
|
||||||
// Entry: [u16 tag][u16 size][payload...]
|
// Entry: [u16 tag][u16 size][payload...]
|
||||||
fn parse_first_string(args: &[u8]) -> Result<String, ()> {
|
fn parse_first_string(args: &[u8]) -> Result<String, ()> {
|
||||||
if args.len() < 4 { return Err(()); }
|
if args.len() < 4 {
|
||||||
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(());
|
return Err(());
|
||||||
}
|
}
|
||||||
if args.len() < p + sz { return Err(()); }
|
let argc = u16::from_le_bytes([args[2], args[3]]) as usize;
|
||||||
let s = String::from_utf8_lossy(&args[p..p+sz]).to_string();
|
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)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_first_any(args: &[u8]) -> Option<String> {
|
fn format_first_any(args: &[u8]) -> Option<String> {
|
||||||
if args.len() < 4 { return None; }
|
if args.len() < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let mut p = 4usize;
|
let mut p = 4usize;
|
||||||
if args.len() < p + 4 { return None; }
|
if args.len() < p + 4 {
|
||||||
let tag = u16::from_le_bytes([args[p], args[p+1]]); p += 2;
|
return None;
|
||||||
let sz = u16::from_le_bytes([args[p], args[p+1]]) as usize; p += 2;
|
}
|
||||||
if args.len() < p + sz { return None; }
|
let tag = u16::from_le_bytes([args[p], args[p + 1]]);
|
||||||
let payload = &args[p..p+sz];
|
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 {
|
match tag {
|
||||||
1 => Some(if sz>0 && payload[0]!=0 { "true".into() } else { "false".into() }),
|
1 => Some(if sz > 0 && payload[0] != 0 {
|
||||||
2 => { if sz!=4 { None } else { let mut b=[0u8;4]; b.copy_from_slice(payload); Some((i32::from_le_bytes(b)).to_string()) } },
|
"true".into()
|
||||||
3 => { if sz!=8 { None } else { let mut b=[0u8;8]; b.copy_from_slice(payload); Some((i64::from_le_bytes(b)).to_string()) } },
|
} else {
|
||||||
5 => { if sz!=8 { None } else { let mut b=[0u8;8]; b.copy_from_slice(payload); Some(f64::from_le_bytes(b).to_string()) } },
|
"false".into()
|
||||||
6 => { std::str::from_utf8(payload).ok().map(|s| s.to_string()) },
|
}),
|
||||||
|
2 => {
|
||||||
|
if sz != 4 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut b = [0u8; 4];
|
||||||
|
b.copy_from_slice(payload);
|
||||||
|
Some((i32::from_le_bytes(b)).to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
if sz != 8 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut b = [0u8; 8];
|
||||||
|
b.copy_from_slice(payload);
|
||||||
|
Some((i64::from_le_bytes(b)).to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
if sz != 8 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut b = [0u8; 8];
|
||||||
|
b.copy_from_slice(payload);
|
||||||
|
Some(f64::from_le_bytes(b).to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
6 => std::str::from_utf8(payload).ok().map(|s| s.to_string()),
|
||||||
7 => Some(format!("<bytes:{}>", sz)),
|
7 => Some(format!("<bytes:{}>", sz)),
|
||||||
8 => { if sz==8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); Some(format!("<handle {}:{}>", u32::from_le_bytes(t), u32::from_le_bytes(i))) } else { None } },
|
8 => {
|
||||||
|
if sz == 8 {
|
||||||
|
let mut t = [0u8; 4];
|
||||||
|
t.copy_from_slice(&payload[0..4]);
|
||||||
|
let mut i = [0u8; 4];
|
||||||
|
i.copy_from_slice(&payload[4..8]);
|
||||||
|
Some(format!(
|
||||||
|
"<handle {}:{}>",
|
||||||
|
u32::from_le_bytes(t),
|
||||||
|
u32::from_le_bytes(i)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write TLV birth result: Handle(tag=8,size=8) with (type_id, instance_id)
|
// 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
|
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);
|
let mut buf = Vec::with_capacity(need);
|
||||||
// header
|
// header
|
||||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
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 {
|
unsafe fn write_tlv_void(out: *mut u8, out_len: *mut usize) -> i32 {
|
||||||
let need = 4 + 4; // header + entry
|
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);
|
let mut buf = Vec::with_capacity(need);
|
||||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
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 =====
|
// ===== Entry points =====
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
@ -123,18 +199,24 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> 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 {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
METHOD_BIRTH => {
|
METHOD_BIRTH => {
|
||||||
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut m) = INSTANCES.lock() {
|
if let Ok(mut m) = INSTANCES.lock() {
|
||||||
m.insert(id, ConsoleInstance{});
|
m.insert(id, ConsoleInstance {});
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
return write_tlv_birth(TYPE_ID_CONSOLE_BOX, id, result, result_len);
|
return write_tlv_birth(TYPE_ID_CONSOLE_BOX, id, result, result_len);
|
||||||
}
|
}
|
||||||
METHOD_FINI => {
|
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;
|
return NYB_SUCCESS;
|
||||||
}
|
}
|
||||||
METHOD_LOG | METHOD_PRINTLN => {
|
METHOD_LOG | METHOD_PRINTLN => {
|
||||||
@ -143,7 +225,11 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(_) => format_first_any(slice).unwrap_or_else(|| "".to_string()),
|
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);
|
return write_tlv_void(result, result_len);
|
||||||
}
|
}
|
||||||
_ => NYB_E_INVALID_METHOD,
|
_ => NYB_E_INVALID_METHOD,
|
||||||
@ -165,7 +251,9 @@ pub struct NyashTypeBoxFfi {
|
|||||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||||
|
|
||||||
extern "C" fn console_resolve(name: *const c_char) -> u32 {
|
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();
|
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
|
||||||
match s.as_ref() {
|
match s.as_ref() {
|
||||||
"log" => METHOD_LOG,
|
"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 {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
METHOD_LOG | METHOD_PRINTLN => {
|
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,
|
Ok(s) => s,
|
||||||
Err(_) => format_first_any(slice).unwrap_or_else(|| "".to_string()),
|
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);
|
return write_tlv_void(result, result_len);
|
||||||
}
|
}
|
||||||
_ => NYB_E_INVALID_METHOD,
|
_ => NYB_E_INVALID_METHOD,
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
//! Nyash CounterBox Plugin - BID-FFI v1 Implementation
|
//! Nyash CounterBox Plugin - BID-FFI v1 Implementation
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
@ -15,25 +18,32 @@ const NYB_E_PLUGIN_ERROR: i32 = -5;
|
|||||||
const NYB_E_INVALID_HANDLE: i32 = -8;
|
const NYB_E_INVALID_HANDLE: i32 = -8;
|
||||||
|
|
||||||
// ===== Method IDs =====
|
// ===== Method IDs =====
|
||||||
const METHOD_BIRTH: u32 = 0; // constructor
|
const METHOD_BIRTH: u32 = 0; // constructor
|
||||||
const METHOD_INC: u32 = 1; // increments and returns new count
|
const METHOD_INC: u32 = 1; // increments and returns new count
|
||||||
const METHOD_GET: u32 = 2; // returns current count
|
const METHOD_GET: u32 = 2; // returns current count
|
||||||
const METHOD_FINI: u32 = u32::MAX; // destructor
|
const METHOD_FINI: u32 = u32::MAX; // destructor
|
||||||
|
|
||||||
// Assign a unique type_id for CounterBox (distinct from FileBox=6)
|
// Assign a unique type_id for CounterBox (distinct from FileBox=6)
|
||||||
const TYPE_ID_COUNTER: u32 = 7;
|
const TYPE_ID_COUNTER: u32 = 7;
|
||||||
|
|
||||||
// ===== Instance state =====
|
// ===== Instance state =====
|
||||||
struct CounterInstance { count: i32 }
|
struct CounterInstance {
|
||||||
|
count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
static INSTANCES: Lazy<Mutex<HashMap<u32, CounterInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static INSTANCES: Lazy<Mutex<HashMap<u32, CounterInstance>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
NYB_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -45,18 +55,26 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_COUNTER { return NYB_E_INVALID_TYPE; }
|
if type_id != TYPE_ID_COUNTER {
|
||||||
|
return NYB_E_INVALID_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
METHOD_BIRTH => {
|
METHOD_BIRTH => {
|
||||||
// Return new instance handle (u32 id)
|
// Return new instance handle (u32 id)
|
||||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
|
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);
|
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
map.insert(id, CounterInstance { count: 0 });
|
map.insert(id, CounterInstance { count: 0 });
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
let bytes = id.to_le_bytes();
|
let bytes = id.to_le_bytes();
|
||||||
std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
|
std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
|
||||||
*result_len = 4;
|
*result_len = 4;
|
||||||
@ -66,7 +84,9 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
map.remove(&instance_id);
|
map.remove(&instance_id);
|
||||||
NYB_SUCCESS
|
NYB_SUCCESS
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_INC => {
|
METHOD_INC => {
|
||||||
// increments and returns new count as I32 TLV
|
// 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) {
|
if let Some(inst) = map.get_mut(&instance_id) {
|
||||||
inst.count += 1;
|
inst.count += 1;
|
||||||
let v = inst.count;
|
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);
|
return write_tlv_i32(v, result, result_len);
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_GET => {
|
METHOD_GET => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
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);
|
return write_tlv_i32(inst.count, result, result_len);
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => NYB_E_INVALID_METHOD,
|
_ => NYB_E_INVALID_METHOD,
|
||||||
}
|
}
|
||||||
@ -94,8 +126,11 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
|
|
||||||
// ===== TLV helpers =====
|
// ===== TLV helpers =====
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
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; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return NYB_E_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
let mut buf: Vec<u8> =
|
||||||
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
||||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
||||||
for (tag, payload) in payloads {
|
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 {
|
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
if result_len.is_null() { return false; }
|
if result_len.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if result.is_null() || *result_len < needed {
|
if result.is_null() || *result_len < needed {
|
||||||
*result_len = needed;
|
*result_len = needed;
|
||||||
return true;
|
return true;
|
||||||
@ -130,4 +167,3 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
|||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,13 @@
|
|||||||
//! - Windows GUI integration (egui/eframe) can be enabled later via `with-egui` feature
|
//! - Windows GUI integration (egui/eframe) can be enabled later via `with-egui` feature
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
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) =====
|
// ===== Error/Status codes (BID-FFI v1 aligned) =====
|
||||||
const OK: i32 = 0;
|
const OK: i32 = 0;
|
||||||
@ -18,12 +24,12 @@ const TID_EGUI: u32 = 70; // match nyash.toml [box_types]
|
|||||||
|
|
||||||
// methods
|
// methods
|
||||||
const M_BIRTH: u32 = 0;
|
const M_BIRTH: u32 = 0;
|
||||||
const M_OPEN: u32 = 1; // open(width:int, height:int, title:str)
|
const M_OPEN: u32 = 1; // open(width:int, height:int, title:str)
|
||||||
const M_UI_LABEL: u32 = 2; // uiLabel(text:str)
|
const M_UI_LABEL: u32 = 2; // uiLabel(text:str)
|
||||||
const M_UI_BUTTON: u32 = 3; // uiButton(text:str) -> future: events
|
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_POLL_EVENT: u32 = 4; // pollEvent() -> Result.Ok(text) / Result.Err("none")
|
||||||
const M_RUN: u32 = 5; // run() -> enters loop or no-op
|
const M_RUN: u32 = 5; // run() -> enters loop or no-op
|
||||||
const M_CLOSE: u32 = 6; // close()
|
const M_CLOSE: u32 = 6; // close()
|
||||||
const M_FINI: u32 = u32::MAX;
|
const M_FINI: u32 = u32::MAX;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[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 {
|
extern "C" fn tb_resolve(name: *const std::os::raw::c_char) -> u32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
if name.is_null() { return 0; }
|
if name.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
let s = std::ffi::CStr::from_ptr(name).to_string_lossy();
|
let s = std::ffi::CStr::from_ptr(name).to_string_lossy();
|
||||||
match s.as_ref() {
|
match s.as_ref() {
|
||||||
"birth" => M_BIRTH,
|
"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 {
|
extern "C" fn tb_invoke_id(
|
||||||
nyash_plugin_invoke(TID_EGUI, _method_id, instance_id, args, args_len, result, result_len)
|
_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";
|
static TYPE_NAME: &[u8] = b"EguiBox\0";
|
||||||
@ -90,10 +113,14 @@ pub static nyash_typebox_EguiBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
|||||||
|
|
||||||
// ===== Plugin entry points =====
|
// ===== Plugin entry points =====
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -105,34 +132,81 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TID_EGUI { return E_TYPE; }
|
if type_id != TID_EGUI {
|
||||||
|
return E_TYPE;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_BIRTH => {
|
M_BIRTH => {
|
||||||
let need = 4; // instance_id (u32 LE)
|
let need = 4; // instance_id (u32 LE)
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
if result.is_null() || *result_len < need { *result_len = need; return E_SHORT; }
|
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);
|
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; }
|
if let Ok(mut m) = INST.lock() {
|
||||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
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 => {
|
M_OPEN => {
|
||||||
eprintln!("[EGUI] M_OPEN invoked");
|
eprintln!("[EGUI] M_OPEN invoked");
|
||||||
let (w, h, title) = match tlv_read_open_args(args, args_len) { Some(v) => v, None => return E_ARGS };
|
let (w, h, title) = match tlv_read_open_args(args, args_len) {
|
||||||
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; }
|
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)
|
write_tlv_void(result, result_len)
|
||||||
}
|
}
|
||||||
M_UI_LABEL => {
|
M_UI_LABEL => {
|
||||||
eprintln!("[EGUI] M_UI_LABEL invoked");
|
eprintln!("[EGUI] M_UI_LABEL invoked");
|
||||||
let text = match tlv_read_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
let text = match tlv_read_string(args, args_len, 0) {
|
||||||
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; }
|
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)
|
write_tlv_void(result, result_len)
|
||||||
}
|
}
|
||||||
M_UI_BUTTON => {
|
M_UI_BUTTON => {
|
||||||
eprintln!("[EGUI] M_UI_BUTTON invoked");
|
eprintln!("[EGUI] M_UI_BUTTON invoked");
|
||||||
// For now: stub, accept and return Void
|
// 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)
|
write_tlv_void(result, result_len)
|
||||||
}
|
}
|
||||||
M_POLL_EVENT => {
|
M_POLL_EVENT => {
|
||||||
@ -147,7 +221,12 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
{
|
{
|
||||||
if let Ok(m) = INST.lock() {
|
if let Ok(m) = INST.lock() {
|
||||||
if let Some(inst) = m.get(&instance_id) {
|
if let Some(inst) = m.get(&instance_id) {
|
||||||
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) =====
|
// ===== TLV helpers (version=1) =====
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return E_ARGS;
|
||||||
|
}
|
||||||
|
let mut buf: Vec<u8> =
|
||||||
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||||
for (tag, payload) in payloads {
|
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 {
|
unsafe {
|
||||||
let need = buf.len();
|
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);
|
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, need);
|
||||||
*result_len = need;
|
*result_len = need;
|
||||||
}
|
}
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(9u8, &[])], result, result_len) }
|
fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
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)> {
|
unsafe fn tlv_parse_header(data: *const u8, len: usize) -> Option<(u16, u16, usize)> {
|
||||||
if data.is_null() || len < 4 { return None; }
|
if data.is_null() || len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let b = std::slice::from_raw_parts(data, len);
|
let b = std::slice::from_raw_parts(data, len);
|
||||||
let ver = u16::from_le_bytes([b[0], b[1]]);
|
let ver = u16::from_le_bytes([b[0], b[1]]);
|
||||||
let argc = u16::from_le_bytes([b[2], b[3]]);
|
let argc = u16::from_le_bytes([b[2], b[3]]);
|
||||||
if ver != 1 { return None; }
|
if ver != 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
Some((ver, argc, 4))
|
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);
|
let b = std::slice::from_raw_parts(data, len);
|
||||||
if pos + 4 > len { return None; }
|
if pos + 4 > len {
|
||||||
let tag = b[pos]; let _ = b[pos+1]; let size = u16::from_le_bytes([b[pos+2], b[pos+3]]) as usize; pos += 4;
|
return None;
|
||||||
if pos + size > 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))
|
Some((tag, size, pos))
|
||||||
}
|
}
|
||||||
unsafe fn tlv_read_i64(data: *const u8, len: usize, index: usize) -> Option<i64> {
|
unsafe fn tlv_read_i64(data: *const u8, len: usize, index: usize) -> Option<i64> {
|
||||||
let (_, argc, mut pos) = tlv_parse_header(data, len)?; if argc < (index as u16 + 1) { return None; }
|
let (_, argc, mut pos) = tlv_parse_header(data, len)?;
|
||||||
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; }
|
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
|
None
|
||||||
}
|
}
|
||||||
unsafe fn tlv_read_string(data: *const u8, len: usize, index: usize) -> Option<String> {
|
unsafe fn tlv_read_string(data: *const u8, len: usize, index: usize) -> Option<String> {
|
||||||
let (_, argc, mut pos) = tlv_parse_header(data, len)?; if argc < (index as u16 + 1) { return None; }
|
let (_, argc, mut pos) = tlv_parse_header(data, len)?;
|
||||||
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; }
|
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
|
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 w = tlv_read_i64(args, len, 0)? as i32;
|
||||||
let h = tlv_read_i64(args, len, 1)? as i32;
|
let h = tlv_read_i64(args, len, 1)? as i32;
|
||||||
let t = tlv_read_string(args, len, 2)?;
|
let t = tlv_read_string(args, len, 2)?;
|
||||||
Some((w,h,t))
|
Some((w, h, t))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== GUI 実行(with-egui, クロスプラットフォーム) =====
|
// ===== GUI 実行(with-egui, クロスプラットフォーム) =====
|
||||||
@ -224,7 +357,9 @@ mod guirun {
|
|||||||
pub fn run_window(w: i32, h: i32, title: &str, labels: Vec<String>) {
|
pub fn run_window(w: i32, h: i32, title: &str, labels: Vec<String>) {
|
||||||
eprintln!("[EGUI] run_window: w={} h={} title='{}'", w, h, title);
|
eprintln!("[EGUI] run_window: w={} h={} title='{}'", w, h, title);
|
||||||
let diag = std::env::var("NYASH_EGUI_DIAG").ok().as_deref() == Some("1");
|
let diag = std::env::var("NYASH_EGUI_DIAG").ok().as_deref() == Some("1");
|
||||||
let scale_override = std::env::var("NYASH_EGUI_SCALE").ok().and_then(|s| s.parse::<f32>().ok());
|
let scale_override = std::env::var("NYASH_EGUI_SCALE")
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.parse::<f32>().ok());
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default()
|
viewport: egui::ViewportBuilder::default()
|
||||||
.with_inner_size([w.max(100) as f32, h.max(100) as f32])
|
.with_inner_size([w.max(100) as f32, h.max(100) as f32])
|
||||||
@ -232,7 +367,14 @@ mod guirun {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
struct App { labels: Vec<String>, diag: bool, printed: bool, init_w: i32, init_h: i32, scale: Option<f32> }
|
struct App {
|
||||||
|
labels: Vec<String>,
|
||||||
|
diag: bool,
|
||||||
|
printed: bool,
|
||||||
|
init_w: i32,
|
||||||
|
init_h: i32,
|
||||||
|
scale: Option<f32>,
|
||||||
|
}
|
||||||
impl eframe::App for App {
|
impl eframe::App for App {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
if self.diag && !self.printed {
|
if self.diag && !self.printed {
|
||||||
@ -252,13 +394,21 @@ mod guirun {
|
|||||||
let rect = ctx.screen_rect();
|
let rect = ctx.screen_rect();
|
||||||
ui.small(format!(
|
ui.small(format!(
|
||||||
"DPI: ppp={:.3} logical={:.1}x{:.1} init={}x{} scale={}",
|
"DPI: ppp={:.3} logical={:.1}x{:.1} init={}x{} scale={}",
|
||||||
ppp, rect.width(), rect.height(), self.init_w, self.init_h,
|
ppp,
|
||||||
self.scale.map(|v| format!("{:.2}", v)).unwrap_or_else(|| "auto".into())
|
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| {
|
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);
|
cc.egui_ctx.set_pixels_per_point(ppp);
|
||||||
eprintln!("[EGUI][diag] override pixels_per_point to {:.3}", ppp);
|
eprintln!("[EGUI][diag] override pixels_per_point to {:.3}", ppp);
|
||||||
}
|
}
|
||||||
Box::new(App { labels, diag, printed: false, init_w, init_h, scale: scale_override })
|
Box::new(App {
|
||||||
|
labels,
|
||||||
|
diag,
|
||||||
|
printed: false,
|
||||||
|
init_w,
|
||||||
|
init_h,
|
||||||
|
scale: scale_override,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
const OK: i32 = 0;
|
const OK: i32 = 0;
|
||||||
const E_SHORT: i32 = -1;
|
const E_SHORT: i32 = -1;
|
||||||
@ -12,13 +15,13 @@ const E_ARGS: i32 = -4;
|
|||||||
const E_PLUGIN: i32 = -5;
|
const E_PLUGIN: i32 = -5;
|
||||||
const E_HANDLE: i32 = -8;
|
const E_HANDLE: i32 = -8;
|
||||||
|
|
||||||
const M_BIRTH: u32 = 0; // constructor (stateless)
|
const M_BIRTH: u32 = 0; // constructor (stateless)
|
||||||
const M_TO_UTF8_BYTES: u32 = 1; // toUtf8Bytes(s) -> bytes
|
const M_TO_UTF8_BYTES: u32 = 1; // toUtf8Bytes(s) -> bytes
|
||||||
const M_FROM_UTF8_BYTES: u32 = 2; // fromUtf8Bytes(bytes) -> string
|
const M_FROM_UTF8_BYTES: u32 = 2; // fromUtf8Bytes(bytes) -> string
|
||||||
const M_BASE64_ENC: u32 = 3; // base64Encode(s|bytes) -> string
|
const M_BASE64_ENC: u32 = 3; // base64Encode(s|bytes) -> string
|
||||||
const M_BASE64_DEC: u32 = 4; // base64Decode(str) -> bytes
|
const M_BASE64_DEC: u32 = 4; // base64Decode(str) -> bytes
|
||||||
const M_HEX_ENC: u32 = 5; // hexEncode(s|bytes) -> string
|
const M_HEX_ENC: u32 = 5; // hexEncode(s|bytes) -> string
|
||||||
const M_HEX_DEC: u32 = 6; // hexDecode(str) -> bytes
|
const M_HEX_DEC: u32 = 6; // hexDecode(str) -> bytes
|
||||||
const M_FINI: u32 = u32::MAX;
|
const M_FINI: u32 = u32::MAX;
|
||||||
|
|
||||||
// Assign an unused type id
|
// Assign an unused type id
|
||||||
@ -30,10 +33,14 @@ static INST: Lazy<Mutex<HashMap<u32, EncInstance>>> = Lazy::new(|| Mutex::new(Ha
|
|||||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -45,44 +52,97 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_ENCODING { return E_TYPE; }
|
if type_id != TYPE_ID_ENCODING {
|
||||||
|
return E_TYPE;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_BIRTH => {
|
M_BIRTH => {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
return E_ARGS;
|
||||||
|
}
|
||||||
|
if preflight(result, result_len, 4) {
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut m) = INST.lock() { m.insert(id, EncInstance); } else { return E_PLUGIN; }
|
if let Ok(mut m) = INST.lock() {
|
||||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
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 => {
|
M_TO_UTF8_BYTES => {
|
||||||
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let s = match read_arg_string(args, args_len, 0) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
write_tlv_bytes(s.as_bytes(), result, result_len)
|
write_tlv_bytes(s.as_bytes(), result, result_len)
|
||||||
}
|
}
|
||||||
M_FROM_UTF8_BYTES => {
|
M_FROM_UTF8_BYTES => {
|
||||||
let bytes = match read_arg_bytes(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let bytes = match read_arg_bytes(args, args_len, 0) {
|
||||||
match String::from_utf8(bytes) { Ok(s) => write_tlv_string(&s, result, result_len), Err(_) => write_tlv_string("", result, result_len) }
|
Some(v) => v,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
|
match String::from_utf8(bytes) {
|
||||||
|
Ok(s) => write_tlv_string(&s, result, result_len),
|
||||||
|
Err(_) => write_tlv_string("", result, result_len),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
M_BASE64_ENC => {
|
M_BASE64_ENC => {
|
||||||
if let Some(b) = read_arg_bytes(args, args_len, 0) { let s = base64::encode(b); return write_tlv_string(&s, result, result_len); }
|
if let Some(b) = read_arg_bytes(args, args_len, 0) {
|
||||||
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let s = base64::encode(b);
|
||||||
|
return write_tlv_string(&s, result, result_len);
|
||||||
|
}
|
||||||
|
let s = match read_arg_string(args, args_len, 0) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
let enc = base64::encode(s.as_bytes());
|
let enc = base64::encode(s.as_bytes());
|
||||||
write_tlv_string(&enc, result, result_len)
|
write_tlv_string(&enc, result, result_len)
|
||||||
}
|
}
|
||||||
M_BASE64_DEC => {
|
M_BASE64_DEC => {
|
||||||
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let s = match read_arg_string(args, args_len, 0) {
|
||||||
match base64::decode(s.as_bytes()) { Ok(b) => write_tlv_bytes(&b, result, result_len), Err(_) => write_tlv_bytes(&[], result, result_len) }
|
Some(v) => v,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
|
match base64::decode(s.as_bytes()) {
|
||||||
|
Ok(b) => write_tlv_bytes(&b, result, result_len),
|
||||||
|
Err(_) => write_tlv_bytes(&[], result, result_len),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
M_HEX_ENC => {
|
M_HEX_ENC => {
|
||||||
if let Some(b) = read_arg_bytes(args, args_len, 0) { let s = hex::encode(b); return write_tlv_string(&s, result, result_len); }
|
if let Some(b) = read_arg_bytes(args, args_len, 0) {
|
||||||
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let s = hex::encode(b);
|
||||||
|
return write_tlv_string(&s, result, result_len);
|
||||||
|
}
|
||||||
|
let s = match read_arg_string(args, args_len, 0) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
let enc = hex::encode(s.as_bytes());
|
let enc = hex::encode(s.as_bytes());
|
||||||
write_tlv_string(&enc, result, result_len)
|
write_tlv_string(&enc, result, result_len)
|
||||||
}
|
}
|
||||||
M_HEX_DEC => {
|
M_HEX_DEC => {
|
||||||
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let s = match read_arg_string(args, args_len, 0) {
|
||||||
match hex::decode(s.as_bytes()) { Ok(b) => write_tlv_bytes(&b, result, result_len), Err(_) => write_tlv_bytes(&[], result, result_len) }
|
Some(v) => v,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
|
match hex::decode(s.as_bytes()) {
|
||||||
|
Ok(b) => write_tlv_bytes(&b, result, result_len),
|
||||||
|
Err(_) => write_tlv_bytes(&[], result, result_len),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => E_METHOD,
|
_ => E_METHOD,
|
||||||
}
|
}
|
||||||
@ -90,29 +150,98 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
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
|
false
|
||||||
}
|
}
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return E_ARGS;
|
||||||
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); }
|
let mut buf: Vec<u8> =
|
||||||
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; }
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||||
|
for (tag, payload) in payloads {
|
||||||
|
buf.push(*tag);
|
||||||
|
buf.push(0);
|
||||||
|
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(payload);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let needed = buf.len();
|
||||||
|
if result.is_null() || *result_len < needed {
|
||||||
|
*result_len = needed;
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
|
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||||
|
*result_len = needed;
|
||||||
|
}
|
||||||
OK
|
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_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
fn write_tlv_bytes(b: &[u8], result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(7u8, b)], result, result_len) }
|
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||||
|
}
|
||||||
|
fn write_tlv_bytes(b: &[u8], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
|
write_tlv_result(&[(7u8, b)], result, result_len)
|
||||||
|
}
|
||||||
|
|
||||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let 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
|
None
|
||||||
}
|
}
|
||||||
fn read_arg_bytes(args: *const u8, args_len: usize, n: usize) -> Option<Vec<u8>> {
|
fn read_arg_bytes(args: *const u8, args_len: usize, n: usize) -> Option<Vec<u8>> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let 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
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use std::ffi::{c_char, c_void, CStr, CString};
|
use std::ffi::{c_char, c_void, CStr, CString};
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{Read, Write, Seek};
|
use std::io::{Read, Seek, Write};
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
/// プラグインのマジックナンバー('NYAS')
|
/// プラグインのマジックナンバー('NYAS')
|
||||||
@ -88,12 +88,10 @@ pub unsafe extern "C" fn nyash_file_read(handle: *mut c_void) -> *mut c_char {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match file_box.file.read_to_string(&mut content) {
|
match file_box.file.read_to_string(&mut content) {
|
||||||
Ok(_) => {
|
Ok(_) => match CString::new(content) {
|
||||||
match CString::new(content) {
|
Ok(c_str) => c_str.into_raw(),
|
||||||
Ok(c_str) => c_str.into_raw(),
|
Err(_) => std::ptr::null_mut(),
|
||||||
Err(_) => std::ptr::null_mut(),
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => std::ptr::null_mut(),
|
Err(_) => std::ptr::null_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,10 +102,7 @@ pub unsafe extern "C" fn nyash_file_read(handle: *mut c_void) -> *mut c_char {
|
|||||||
/// - handleはnyash_file_openから返された有効なポインタである必要がある
|
/// - handleはnyash_file_openから返された有効なポインタである必要がある
|
||||||
/// - contentは有効なUTF-8のC文字列である必要がある
|
/// - contentは有効なUTF-8のC文字列である必要がある
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn nyash_file_write(
|
pub unsafe extern "C" fn nyash_file_write(handle: *mut c_void, content: *const c_char) -> c_int {
|
||||||
handle: *mut c_void,
|
|
||||||
content: *const c_char
|
|
||||||
) -> c_int {
|
|
||||||
if handle.is_null() || content.is_null() {
|
if handle.is_null() || content.is_null() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -128,7 +123,7 @@ pub unsafe extern "C" fn nyash_file_write(
|
|||||||
|
|
||||||
match file_box.file.write_all(content_str.as_bytes()) {
|
match file_box.file.write_all(content_str.as_bytes()) {
|
||||||
Ok(_) => 1, // 成功
|
Ok(_) => 1, // 成功
|
||||||
Err(_) => 0, // 失敗
|
Err(_) => 0, // 失敗
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,11 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
// std::ptr削除(未使用)
|
// std::ptr削除(未使用)
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
use std::io::{Read, Write, Seek, SeekFrom};
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
// ============ FFI Types ============
|
// ============ FFI Types ============
|
||||||
|
|
||||||
@ -37,7 +40,7 @@ const NYB_E_PLUGIN_ERROR: i32 = -5;
|
|||||||
const NYB_E_INVALID_HANDLE: i32 = -8;
|
const NYB_E_INVALID_HANDLE: i32 = -8;
|
||||||
|
|
||||||
// ============ Method IDs ============
|
// ============ Method IDs ============
|
||||||
const METHOD_BIRTH: u32 = 0; // Constructor
|
const METHOD_BIRTH: u32 = 0; // Constructor
|
||||||
const METHOD_OPEN: u32 = 1;
|
const METHOD_OPEN: u32 = 1;
|
||||||
const METHOD_READ: u32 = 2;
|
const METHOD_READ: u32 = 2;
|
||||||
const METHOD_WRITE: u32 = 3;
|
const METHOD_WRITE: u32 = 3;
|
||||||
@ -45,20 +48,19 @@ const METHOD_CLOSE: u32 = 4;
|
|||||||
const METHOD_EXISTS: u32 = 5;
|
const METHOD_EXISTS: u32 = 5;
|
||||||
const METHOD_COPY_FROM: u32 = 7; // New: copyFrom(other: Handle)
|
const METHOD_COPY_FROM: u32 = 7; // New: copyFrom(other: Handle)
|
||||||
const METHOD_CLONE_SELF: u32 = 8; // New: cloneSelf() -> 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 ============
|
// ============ FileBox Instance ============
|
||||||
struct FileBoxInstance {
|
struct FileBoxInstance {
|
||||||
file: Option<std::fs::File>,
|
file: Option<std::fs::File>,
|
||||||
path: String,
|
path: String,
|
||||||
buffer: Option<Vec<u8>>, // プラグインが管理するバッファ
|
buffer: Option<Vec<u8>>, // プラグインが管理するバッファ
|
||||||
}
|
}
|
||||||
|
|
||||||
// グローバルインスタンス管理(実際の実装ではより安全な方法を使用)
|
// グローバルインスタンス管理(実際の実装ではより安全な方法を使用)
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
static INSTANCES: Lazy<Mutex<HashMap<u32, FileBoxInstance>>> = Lazy::new(|| {
|
static INSTANCES: Lazy<Mutex<HashMap<u32, FileBoxInstance>>> =
|
||||||
Mutex::new(HashMap::new())
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
});
|
|
||||||
|
|
||||||
// ホスト関数テーブルは使用しない(Host VTable廃止)
|
// ホスト関数テーブルは使用しない(Host VTable廃止)
|
||||||
|
|
||||||
@ -70,7 +72,7 @@ static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
|||||||
/// ABI version
|
/// ABI version
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
1 // BID-1 support
|
1 // BID-1 support
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plugin initialization (optional - global setup)
|
/// 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);
|
let instance_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
map.insert(instance_id, FileBoxInstance {
|
map.insert(
|
||||||
file: None,
|
instance_id,
|
||||||
path: String::new(),
|
FileBoxInstance {
|
||||||
buffer: None,
|
file: None,
|
||||||
});
|
path: String::new(),
|
||||||
|
buffer: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return NYB_E_PLUGIN_ERROR;
|
return NYB_E_PLUGIN_ERROR;
|
||||||
}
|
}
|
||||||
@ -149,7 +154,9 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
match tlv_parse_two_strings(args) {
|
match tlv_parse_two_strings(args) {
|
||||||
Ok((path, mode)) => {
|
Ok((path, mode)) => {
|
||||||
// Preflight for Void TLV: header(4) + entry(4)
|
// 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));
|
log_info(&format!("OPEN path='{}' mode='{}'", path, mode));
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&_instance_id) {
|
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,
|
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||||
}
|
}
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => NYB_E_INVALID_ARGS,
|
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);
|
let args = std::slice::from_raw_parts(_args, _args_len);
|
||||||
if _args_len > 0 {
|
if _args_len > 0 {
|
||||||
match tlv_parse_string(args) {
|
match tlv_parse_string(args) {
|
||||||
Ok(path) => {
|
Ok(path) => match open_file("r", &path) {
|
||||||
match open_file("r", &path) {
|
Ok(mut file) => {
|
||||||
Ok(mut file) => {
|
let mut buf = Vec::new();
|
||||||
let mut buf = Vec::new();
|
if let Err(_) = file.read_to_end(&mut buf) {
|
||||||
if let Err(_) = file.read_to_end(&mut buf) { return NYB_E_PLUGIN_ERROR; }
|
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,
|
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,
|
Err(_) => return NYB_E_INVALID_ARGS,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -196,14 +209,22 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
log_info(&format!("READ {} bytes (entire file)", n));
|
log_info(&format!("READ {} bytes (entire file)", n));
|
||||||
let need = 8usize.saturating_add(buf.len());
|
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);
|
return write_tlv_bytes(&buf, _result, _result_len);
|
||||||
}
|
}
|
||||||
Err(_) => return NYB_E_PLUGIN_ERROR,
|
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||||
}
|
}
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
METHOD_WRITE => {
|
METHOD_WRITE => {
|
||||||
@ -211,40 +232,58 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
let args = std::slice::from_raw_parts(_args, _args_len);
|
let args = std::slice::from_raw_parts(_args, _args_len);
|
||||||
match tlv_parse_optional_string_and_bytes(args) {
|
match tlv_parse_optional_string_and_bytes(args) {
|
||||||
Ok((Some(path), data)) => {
|
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) {
|
match open_file("w", &path) {
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
if let Err(_) = file.write_all(&data) { return NYB_E_PLUGIN_ERROR; }
|
if let Err(_) = file.write_all(&data) {
|
||||||
if let Err(_) = file.flush() { return NYB_E_PLUGIN_ERROR; }
|
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);
|
return write_tlv_i32(data.len() as i32, _result, _result_len);
|
||||||
}
|
}
|
||||||
Err(_) => return NYB_E_PLUGIN_ERROR,
|
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((None, data)) => {
|
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 Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&_instance_id) {
|
if let Some(inst) = map.get_mut(&_instance_id) {
|
||||||
if let Some(file) = inst.file.as_mut() {
|
if let Some(file) = inst.file.as_mut() {
|
||||||
match file.write(&data) {
|
match file.write(&data) {
|
||||||
Ok(n) => {
|
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));
|
log_info(&format!("WRITE {} bytes", n));
|
||||||
inst.buffer = Some(data.clone());
|
inst.buffer = Some(data.clone());
|
||||||
return write_tlv_i32(n as i32, _result, _result_len);
|
return write_tlv_i32(n as i32, _result, _result_len);
|
||||||
}
|
}
|
||||||
Err(_) => return NYB_E_PLUGIN_ERROR,
|
Err(_) => return NYB_E_PLUGIN_ERROR,
|
||||||
}
|
}
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => NYB_E_INVALID_ARGS,
|
Err(_) => NYB_E_INVALID_ARGS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
METHOD_CLOSE => {
|
METHOD_CLOSE => {
|
||||||
// Preflight for Void TLV
|
// 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");
|
log_info("CLOSE");
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&_instance_id) {
|
if let Some(inst) = map.get_mut(&_instance_id) {
|
||||||
@ -262,8 +301,12 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
let args = std::slice::from_raw_parts(_args, _args_len);
|
let args = std::slice::from_raw_parts(_args, _args_len);
|
||||||
match tlv_parse_handle(args) {
|
match tlv_parse_handle(args) {
|
||||||
Ok((type_id, other_id)) => {
|
Ok((type_id, other_id)) => {
|
||||||
if type_id != _type_id { return NYB_E_INVALID_TYPE; }
|
if type_id != _type_id {
|
||||||
if preflight(_result, _result_len, 8) { return NYB_E_SHORT_BUFFER; }
|
return NYB_E_INVALID_TYPE;
|
||||||
|
}
|
||||||
|
if preflight(_result, _result_len, 8) {
|
||||||
|
return NYB_E_SHORT_BUFFER;
|
||||||
|
}
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
// 1) まずsrcからデータを取り出す(不変参照のみ)
|
// 1) まずsrcからデータを取り出す(不変参照のみ)
|
||||||
let mut data: Vec<u8> = Vec::new();
|
let mut data: Vec<u8> = Vec::new();
|
||||||
@ -283,21 +326,31 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
read_ok = true;
|
read_ok = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !read_ok { return NYB_E_PLUGIN_ERROR; }
|
if !read_ok {
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
// 2) dstへ書き込み(可変参照)
|
// 2) dstへ書き込み(可変参照)
|
||||||
if let Some(dst) = map.get_mut(&_instance_id) {
|
if let Some(dst) = map.get_mut(&_instance_id) {
|
||||||
if let Some(fdst) = dst.file.as_mut() {
|
if let Some(fdst) = dst.file.as_mut() {
|
||||||
let _ = fdst.seek(SeekFrom::Start(0));
|
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.set_len(data.len() as u64);
|
||||||
let _ = fdst.flush();
|
let _ = fdst.flush();
|
||||||
}
|
}
|
||||||
dst.buffer = Some(data);
|
dst.buffer = Some(data);
|
||||||
return write_tlv_void(_result, _result_len);
|
return write_tlv_void(_result, _result_len);
|
||||||
} else { return NYB_E_INVALID_HANDLE; }
|
} else {
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
return NYB_E_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NYB_E_PLUGIN_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => NYB_E_INVALID_ARGS,
|
Err(_) => NYB_E_INVALID_ARGS,
|
||||||
}
|
}
|
||||||
@ -305,11 +358,22 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
METHOD_CLONE_SELF => {
|
METHOD_CLONE_SELF => {
|
||||||
// Return a new instance (handle) as TLV Handle
|
// Return a new instance (handle) as TLV Handle
|
||||||
// Preflight for Handle TLV: header(4) + entry(4) + payload(8)
|
// 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);
|
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
|
// 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[0..4].copy_from_slice(&_type_id.to_le_bytes());
|
||||||
payload[4..8].copy_from_slice(&new_id.to_le_bytes());
|
payload[4..8].copy_from_slice(&new_id.to_le_bytes());
|
||||||
return write_tlv_result(&[(8u8, &payload)], _result, _result_len);
|
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) {
|
match tlv_parse_string(args) {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
let exists = std::path::Path::new(&path).exists();
|
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);
|
return write_tlv_bool(exists, _result, _result_len);
|
||||||
}
|
}
|
||||||
Err(_) => NYB_E_INVALID_ARGS,
|
Err(_) => NYB_E_INVALID_ARGS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => NYB_SUCCESS
|
_ => NYB_SUCCESS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,16 +403,27 @@ fn open_file(mode: &str, path: &str) -> Result<std::fs::File, std::io::Error> {
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
match mode {
|
match mode {
|
||||||
"r" => OpenOptions::new().read(true).open(path),
|
"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),
|
"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),
|
_ => OpenOptions::new().read(true).open(path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
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; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return NYB_E_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
let mut buf: Vec<u8> =
|
||||||
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
buf.extend_from_slice(&1u16.to_le_bytes()); // version
|
||||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
|
||||||
for (tag, payload) in payloads {
|
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 {
|
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)
|
write_tlv_result(&[(1u8, &b)], result, result_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
if result_len.is_null() { return false; }
|
if result_len.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if result.is_null() || *result_len < needed {
|
if result.is_null() || *result_len < needed {
|
||||||
*result_len = needed;
|
*result_len = needed;
|
||||||
return true;
|
return true;
|
||||||
@ -395,58 +474,90 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tlv_parse_header(data: &[u8]) -> Result<(u16,u16,usize), ()> {
|
fn tlv_parse_header(data: &[u8]) -> Result<(u16, u16, usize), ()> {
|
||||||
if data.len() < 4 { return Err(()); }
|
if data.len() < 4 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
let ver = u16::from_le_bytes([data[0], data[1]]);
|
let ver = u16::from_le_bytes([data[0], data[1]]);
|
||||||
let argc = u16::from_le_bytes([data[2], data[3]]);
|
let argc = u16::from_le_bytes([data[2], data[3]]);
|
||||||
if ver != 1 { return Err(()); }
|
if ver != 1 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
Ok((ver, argc, 4))
|
Ok((ver, argc, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tlv_parse_two_strings(data: &[u8]) -> Result<(String, String), ()> {
|
fn tlv_parse_two_strings(data: &[u8]) -> Result<(String, String), ()> {
|
||||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
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 s1 = tlv_parse_string_at(data, &mut pos)?;
|
||||||
let s2 = tlv_parse_string_at(data, &mut pos)?;
|
let s2 = tlv_parse_string_at(data, &mut pos)?;
|
||||||
Ok((s1, s2))
|
Ok((s1, s2))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tlv_parse_string_at(data: &[u8], pos: &mut usize) -> Result<String, ()> {
|
fn tlv_parse_string_at(data: &[u8], pos: &mut usize) -> Result<String, ()> {
|
||||||
if *pos + 4 > data.len() { return Err(()); }
|
if *pos + 4 > data.len() {
|
||||||
let tag = data[*pos]; let _res = data[*pos+1];
|
return Err(());
|
||||||
let size = u16::from_le_bytes([data[*pos+2], data[*pos+3]]) as usize;
|
}
|
||||||
|
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;
|
*pos += 4;
|
||||||
if tag != 6 || *pos + size > data.len() { return Err(()); }
|
if tag != 6 || *pos + size > data.len() {
|
||||||
let slice = &data[*pos..*pos+size];
|
return Err(());
|
||||||
|
}
|
||||||
|
let slice = &data[*pos..*pos + size];
|
||||||
*pos += size;
|
*pos += size;
|
||||||
std::str::from_utf8(slice).map(|s| s.to_string()).map_err(|_| ())
|
std::str::from_utf8(slice)
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.map_err(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tlv_parse_i32(data: &[u8]) -> Result<i32, ()> {
|
fn tlv_parse_i32(data: &[u8]) -> Result<i32, ()> {
|
||||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||||
if argc < 1 { return Err(()); }
|
if argc < 1 {
|
||||||
if pos + 8 > data.len() { return Err(()); }
|
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 pos + 8 > data.len() {
|
||||||
if tag != 2 || size != 4 || pos + size > data.len() { return Err(()); }
|
return Err(());
|
||||||
let mut b = [0u8;4]; b.copy_from_slice(&data[pos..pos+4]);
|
}
|
||||||
|
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))
|
Ok(i32::from_le_bytes(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tlv_parse_bytes(data: &[u8]) -> Result<Vec<u8>, ()> {
|
fn tlv_parse_bytes(data: &[u8]) -> Result<Vec<u8>, ()> {
|
||||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||||
if argc < 1 { return Err(()); }
|
if argc < 1 {
|
||||||
if pos + 4 > data.len() { return Err(()); }
|
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 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タグも受け付ける(互換性のため)
|
// StringタグもBytesタグも受け付ける(互換性のため)
|
||||||
if (tag != 6 && tag != 7) || pos + size > data.len() { return Err(()); }
|
if (tag != 6 && tag != 7) || pos + size > data.len() {
|
||||||
Ok(data[pos..pos+size].to_vec())
|
return Err(());
|
||||||
|
}
|
||||||
|
Ok(data[pos..pos + size].to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tlv_parse_string(data: &[u8]) -> Result<String, ()> {
|
fn tlv_parse_string(data: &[u8]) -> Result<String, ()> {
|
||||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
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)
|
tlv_parse_string_at(data, &mut pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,18 +565,30 @@ fn tlv_parse_optional_string_and_bytes(data: &[u8]) -> Result<(Option<String>, V
|
|||||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||||
if argc == 1 {
|
if argc == 1 {
|
||||||
// only bytes
|
// only bytes
|
||||||
if pos + 4 > data.len() { return Err(()); }
|
if pos + 4 > data.len() {
|
||||||
let tag = data[pos]; let _res = data[pos+1];
|
return Err(());
|
||||||
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(()); }
|
let tag = data[pos];
|
||||||
return Ok((None, data[pos..pos+size].to_vec()));
|
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 {
|
} else if argc >= 2 {
|
||||||
let s = tlv_parse_string_at(data, &mut pos)?;
|
let s = tlv_parse_string_at(data, &mut pos)?;
|
||||||
if pos + 4 > data.len() { return Err(()); }
|
if pos + 4 > data.len() {
|
||||||
let tag = data[pos]; let _res = data[pos+1];
|
return Err(());
|
||||||
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(()); }
|
let tag = data[pos];
|
||||||
Ok((Some(s), data[pos..pos+size].to_vec()))
|
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 {
|
} else {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
@ -473,13 +596,23 @@ fn tlv_parse_optional_string_and_bytes(data: &[u8]) -> Result<(Option<String>, V
|
|||||||
|
|
||||||
fn tlv_parse_handle(data: &[u8]) -> Result<(u32, u32), ()> {
|
fn tlv_parse_handle(data: &[u8]) -> Result<(u32, u32), ()> {
|
||||||
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
let (_, argc, mut pos) = tlv_parse_header(data)?;
|
||||||
if argc < 1 { return Err(()); }
|
if argc < 1 {
|
||||||
if pos + 4 > data.len() { return Err(()); }
|
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 pos + 4 > data.len() {
|
||||||
if tag != 8 || size != 8 || pos + size > data.len() { return Err(()); }
|
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]);
|
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)))
|
Ok((u32::from_le_bytes(t), u32::from_le_bytes(i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,12 @@
|
|||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
// Error codes
|
// Error codes
|
||||||
const OK: i32 = 0;
|
const OK: i32 = 0;
|
||||||
@ -18,23 +21,29 @@ const E_HANDLE: i32 = -8;
|
|||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const M_BIRTH: u32 = 0;
|
const M_BIRTH: u32 = 0;
|
||||||
const M_GET: u32 = 1;
|
const M_GET: u32 = 1;
|
||||||
const M_SET: u32 = 2;
|
const M_SET: u32 = 2;
|
||||||
const M_FINI: u32 = u32::MAX;
|
const M_FINI: u32 = u32::MAX;
|
||||||
|
|
||||||
// Assigned type id (nyash.toml must match)
|
// Assigned type id (nyash.toml must match)
|
||||||
const TYPE_ID_INTEGER: u32 = 12;
|
const TYPE_ID_INTEGER: u32 = 12;
|
||||||
|
|
||||||
struct IntInstance { value: i64 }
|
struct IntInstance {
|
||||||
|
value: i64,
|
||||||
|
}
|
||||||
|
|
||||||
static INST: Lazy<Mutex<HashMap<u32, IntInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static INST: Lazy<Mutex<HashMap<u32, IntInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -46,29 +55,65 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_INTEGER { return E_TYPE; }
|
if type_id != TYPE_ID_INTEGER {
|
||||||
|
return E_TYPE;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_BIRTH => {
|
M_BIRTH => {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
return E_ARGS;
|
||||||
|
}
|
||||||
|
if preflight(result, result_len, 4) {
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
// Optional initial value from first arg (i64/i32)
|
// Optional initial value from first arg (i64/i32)
|
||||||
let init = read_arg_i64(args, args_len, 0).unwrap_or(0);
|
let init = read_arg_i64(args, args_len, 0).unwrap_or(0);
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut m) = INST.lock() { m.insert(id, IntInstance { value: init }); }
|
if let Ok(mut m) = INST.lock() {
|
||||||
else { return E_PLUGIN; }
|
m.insert(id, IntInstance { value: init });
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
let b = id.to_le_bytes();
|
let b = id.to_le_bytes();
|
||||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||||
|
*result_len = 4;
|
||||||
|
OK
|
||||||
}
|
}
|
||||||
M_FINI => {
|
M_FINI => {
|
||||||
if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN }
|
if let Ok(mut m) = INST.lock() {
|
||||||
|
m.remove(&instance_id);
|
||||||
|
OK
|
||||||
|
} else {
|
||||||
|
E_PLUGIN
|
||||||
|
}
|
||||||
}
|
}
|
||||||
M_GET => {
|
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 => {
|
M_SET => {
|
||||||
let v = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let v = match read_arg_i64(args, args_len, 0) {
|
||||||
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; }
|
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,
|
_ => E_METHOD,
|
||||||
}
|
}
|
||||||
@ -89,7 +134,9 @@ pub struct NyashTypeBoxFfi {
|
|||||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||||
|
|
||||||
extern "C" fn integer_resolve(name: *const c_char) -> u32 {
|
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();
|
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
|
||||||
match s.as_ref() {
|
match s.as_ref() {
|
||||||
"get" => M_GET,
|
"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 {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_GET => {
|
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 => {
|
M_SET => {
|
||||||
let v = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let v = match read_arg_i64(args, args_len, 0) {
|
||||||
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; }
|
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,
|
_ => 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 {
|
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
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return E_ARGS;
|
||||||
|
}
|
||||||
|
let mut buf: Vec<u8> =
|
||||||
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
buf.extend_from_slice(&(payloads.len() as u16).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); }
|
for (tag, payload) in payloads {
|
||||||
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; }
|
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
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
|
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
|
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
|
||||||
|
}
|
||||||
|
|
||||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||||
let mut off = 4usize; // header
|
let mut off = 4usize; // header
|
||||||
for i in 0..=n {
|
for i in 0..=n {
|
||||||
if buf.len() < off + 4 { return None; }
|
if buf.len() < off + 4 {
|
||||||
let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
return None;
|
||||||
if buf.len() < off + 4 + size { 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 i == n {
|
||||||
match (tag, size) {
|
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)); }
|
(3, 8) => {
|
||||||
(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); }
|
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,
|
_ => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
// Error codes
|
// Error codes
|
||||||
const NYB_SUCCESS: i32 = 0;
|
const NYB_SUCCESS: i32 = 0;
|
||||||
@ -19,37 +22,41 @@ const NYB_E_INVALID_HANDLE: i32 = -8;
|
|||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const METHOD_BIRTH: u32 = 0;
|
const METHOD_BIRTH: u32 = 0;
|
||||||
const METHOD_SIZE: u32 = 1;
|
const METHOD_SIZE: u32 = 1;
|
||||||
const METHOD_GET: u32 = 2; // args: i64 key -> TLV i64
|
const METHOD_GET: u32 = 2; // args: i64 key -> TLV i64
|
||||||
const METHOD_HAS: u32 = 3; // args: i64 key -> TLV bool
|
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_SET: u32 = 4; // args: key(int|string), value(i64) -> TLV i64 (size)
|
||||||
const METHOD_REMOVE:u32 = 6; // args: key(int|string) -> TLV bool (removed)
|
const METHOD_REMOVE: u32 = 6; // args: key(int|string) -> TLV bool (removed)
|
||||||
const METHOD_CLEAR: u32 = 7; // args: () -> TLV i64 (size after clear=0)
|
const METHOD_CLEAR: u32 = 7; // args: () -> TLV i64 (size after clear=0)
|
||||||
const METHOD_KEYS_S:u32 = 8; // args: () -> TLV string (newline-joined keys)
|
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_GET_OR: u32 = 9; // args: key(int|string), default(i64) -> TLV i64
|
||||||
const METHOD_FINI: u32 = u32::MAX;
|
const METHOD_FINI: u32 = u32::MAX;
|
||||||
// Extended string-key methods
|
// Extended string-key methods
|
||||||
const METHOD_SET_STR: u32 = 10; // setS(name: string, val: i64) -> i64(size)
|
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_GET_STR: u32 = 11; // getS(name: string) -> i64
|
||||||
const METHOD_HAS_STR: u32 = 12; // hasS(name: string) -> bool
|
const METHOD_HAS_STR: u32 = 12; // hasS(name: string) -> bool
|
||||||
const METHOD_VALUES_S: u32 = 13; // valuesStr() -> string (newline-joined)
|
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 に合わせる)
|
// Type id (nyash.toml に合わせる)
|
||||||
const TYPE_ID_MAP: u32 = 11;
|
const TYPE_ID_MAP: u32 = 11;
|
||||||
|
|
||||||
struct MapInstance {
|
struct MapInstance {
|
||||||
data_i64: HashMap<i64,i64>,
|
data_i64: HashMap<i64, i64>,
|
||||||
data_str: HashMap<String,i64>,
|
data_str: HashMap<String, i64>,
|
||||||
}
|
}
|
||||||
static INSTANCES: Lazy<Mutex<HashMap<u32, MapInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static INSTANCES: Lazy<Mutex<HashMap<u32, MapInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
NYB_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -61,44 +68,81 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_MAP { return NYB_E_INVALID_TYPE; }
|
if type_id != TYPE_ID_MAP {
|
||||||
|
return NYB_E_INVALID_TYPE;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
METHOD_BIRTH => {
|
METHOD_BIRTH => {
|
||||||
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
|
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);
|
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
map.insert(id, MapInstance { data_i64: HashMap::new(), data_str: HashMap::new() });
|
map.insert(
|
||||||
} else { return NYB_E_PLUGIN_ERROR; }
|
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);
|
std::ptr::copy_nonoverlapping(id.to_le_bytes().as_ptr(), result, 4);
|
||||||
*result_len = 4;
|
*result_len = 4;
|
||||||
NYB_SUCCESS
|
NYB_SUCCESS
|
||||||
}
|
}
|
||||||
METHOD_FINI => {
|
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 => {
|
METHOD_SIZE => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||||
write_tlv_i64(sz as i64, result, result_len)
|
write_tlv_i64(sz as i64, result, result_len)
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_GET => {
|
METHOD_GET => {
|
||||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
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 }
|
match inst.data_i64.get(&ik).copied() {
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
Some(v) => write_tlv_i64(v, result, result_len),
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
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) {
|
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
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 }
|
match inst.data_str.get(&sk).copied() {
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
Some(v) => write_tlv_i64(v, result, result_len),
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
None => NYB_E_INVALID_ARGS,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
NYB_E_INVALID_ARGS
|
NYB_E_INVALID_ARGS
|
||||||
}
|
}
|
||||||
@ -106,35 +150,58 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
METHOD_HAS => {
|
METHOD_HAS => {
|
||||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
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 }
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
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) {
|
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
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 }
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len)
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
NYB_E_INVALID_ARGS
|
NYB_E_INVALID_ARGS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
METHOD_SET => {
|
METHOD_SET => {
|
||||||
// value は i64 限定
|
// 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 Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&instance_id) {
|
if let Some(inst) = map.get_mut(&instance_id) {
|
||||||
inst.data_i64.insert(ik, val);
|
inst.data_i64.insert(ik, val);
|
||||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||||
return write_tlv_i64(sz as i64, result, result_len);
|
return write_tlv_i64(sz as i64, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&instance_id) {
|
if let Some(inst) = map.get_mut(&instance_id) {
|
||||||
inst.data_str.insert(sk, val);
|
inst.data_str.insert(sk, val);
|
||||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||||
return write_tlv_i64(sz as i64, result, result_len);
|
return write_tlv_i64(sz as i64, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
NYB_E_INVALID_ARGS
|
NYB_E_INVALID_ARGS
|
||||||
}
|
}
|
||||||
@ -143,12 +210,28 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&instance_id) {
|
if let Some(inst) = map.get_mut(&instance_id) {
|
||||||
// try int key
|
// 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
|
// 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
|
NYB_E_INVALID_ARGS
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_CLEAR => {
|
METHOD_CLEAR => {
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
@ -156,23 +239,39 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
inst.data_i64.clear();
|
inst.data_i64.clear();
|
||||||
inst.data_str.clear();
|
inst.data_str.clear();
|
||||||
return write_tlv_i64(0, result, result_len);
|
return write_tlv_i64(0, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_KEYS_S => {
|
METHOD_KEYS_S => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
let mut keys: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
let mut keys: Vec<String> =
|
||||||
for k in inst.data_i64.keys() { keys.push(k.to_string()); }
|
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||||
for k in inst.data_str.keys() { keys.push(k.clone()); }
|
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();
|
keys.sort();
|
||||||
let out = keys.join("\n");
|
let out = keys.join("\n");
|
||||||
return write_tlv_string(&out, result, result_len);
|
return write_tlv_string(&out, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_GET_OR => {
|
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 Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
// prefer exact match, else default
|
// prefer exact match, else default
|
||||||
@ -185,44 +284,86 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
return write_tlv_i64(v, result, result_len);
|
return write_tlv_i64(v, result, result_len);
|
||||||
}
|
}
|
||||||
NYB_E_INVALID_ARGS
|
NYB_E_INVALID_ARGS
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_SET_STR => {
|
METHOD_SET_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) {
|
||||||
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
|
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 Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&instance_id) {
|
if let Some(inst) = map.get_mut(&instance_id) {
|
||||||
inst.data_str.insert(key, val);
|
inst.data_str.insert(key, val);
|
||||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||||
return write_tlv_i64(sz as i64, result, result_len);
|
return write_tlv_i64(sz as i64, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_GET_STR => {
|
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 Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
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 }
|
match inst.data_str.get(&key).copied() {
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
Some(v) => write_tlv_i64(v, result, result_len),
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
None => NYB_E_INVALID_ARGS,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_HAS_STR => {
|
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 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 }
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
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 => {
|
METHOD_VALUES_S => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
let mut vals: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
let mut vals: Vec<String> =
|
||||||
for v in inst.data_i64.values() { vals.push(v.to_string()); }
|
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||||
for v in inst.data_str.values() { vals.push(v.to_string()); }
|
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");
|
let out = vals.join("\n");
|
||||||
return write_tlv_string(&out, result, result_len);
|
return write_tlv_string(&out, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_TO_JSON => {
|
METHOD_TO_JSON => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
@ -230,23 +371,35 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
let mut s = String::from("{");
|
let mut s = String::from("{");
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for (k, v) in inst.data_str.iter() {
|
for (k, v) in inst.data_str.iter() {
|
||||||
if !first { s.push(','); }
|
if !first {
|
||||||
|
s.push(',');
|
||||||
|
}
|
||||||
first = false;
|
first = false;
|
||||||
// JSON string key
|
// 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());
|
s.push_str(&v.to_string());
|
||||||
}
|
}
|
||||||
for (k, v) in inst.data_i64.iter() {
|
for (k, v) in inst.data_i64.iter() {
|
||||||
if !first { s.push(','); }
|
if !first {
|
||||||
|
s.push(',');
|
||||||
|
}
|
||||||
first = false;
|
first = false;
|
||||||
// numeric key as string per JSON
|
// 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_str(&v.to_string());
|
||||||
}
|
}
|
||||||
s.push('}');
|
s.push('}');
|
||||||
return write_tlv_string(&s, result, result_len);
|
return write_tlv_string(&s, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => NYB_E_INVALID_METHOD,
|
_ => NYB_E_INVALID_METHOD,
|
||||||
}
|
}
|
||||||
@ -267,7 +420,9 @@ pub struct NyashTypeBoxFfi {
|
|||||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||||
|
|
||||||
extern "C" fn mapbox_resolve(name: *const c_char) -> u32 {
|
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();
|
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
|
||||||
match s.as_ref() {
|
match s.as_ref() {
|
||||||
"size" | "len" => METHOD_SIZE,
|
"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 {
|
match method_id {
|
||||||
METHOD_SIZE => {
|
METHOD_SIZE => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||||
return write_tlv_i64(sz as i64, result, result_len);
|
return write_tlv_i64(sz as i64, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_GET => {
|
METHOD_GET => {
|
||||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
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 }
|
match inst.data_i64.get(&ik).copied() {
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
Some(v) => write_tlv_i64(v, result, result_len),
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
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) {
|
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
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 }
|
match inst.data_str.get(&sk).copied() {
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
Some(v) => write_tlv_i64(v, result, result_len),
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
None => NYB_E_INVALID_ARGS,
|
||||||
} else { NYB_E_INVALID_ARGS }
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_ARGS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_HAS => {
|
METHOD_HAS => {
|
||||||
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
if let Some(ik) = read_arg_i64(args, args_len, 0) {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
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 }
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
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) {
|
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
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 }
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len)
|
||||||
} else { NYB_E_INVALID_ARGS }
|
} else {
|
||||||
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_ARGS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_SET => {
|
METHOD_SET => {
|
||||||
// key: i64 or string, value: i64
|
// 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);
|
inst.data_i64.insert(ik, val);
|
||||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||||
return write_tlv_i64(sz as i64, result, result_len);
|
return write_tlv_i64(sz as i64, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(mut map) = INSTANCES.lock() {
|
if let Ok(mut map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get_mut(&instance_id) {
|
if let Some(inst) = map.get_mut(&instance_id) {
|
||||||
inst.data_str.insert(sk, val);
|
inst.data_str.insert(sk, val);
|
||||||
let sz = inst.data_i64.len() + inst.data_str.len();
|
let sz = inst.data_i64.len() + inst.data_str.len();
|
||||||
return write_tlv_i64(sz as i64, result, result_len);
|
return write_tlv_i64(sz as i64, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
} else { NYB_E_INVALID_ARGS }
|
}
|
||||||
} else { NYB_E_INVALID_ARGS }
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_ARGS
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_ARGS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_GET_STR => {
|
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 Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
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 }
|
match inst.data_str.get(&key).copied() {
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
Some(v) => write_tlv_i64(v, result, result_len),
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
None => NYB_E_INVALID_ARGS,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_HAS_STR => {
|
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 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 }
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
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 => {
|
METHOD_KEYS_S => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
let mut keys: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
let mut keys: Vec<String> =
|
||||||
for k in inst.data_i64.keys() { keys.push(k.to_string()); }
|
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||||
for k in inst.data_str.keys() { keys.push(k.clone()); }
|
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();
|
keys.sort();
|
||||||
let out = keys.join("\n");
|
let out = keys.join("\n");
|
||||||
return write_tlv_string(&out, result, result_len);
|
return write_tlv_string(&out, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
METHOD_VALUES_S => {
|
METHOD_VALUES_S => {
|
||||||
if let Ok(map) = INSTANCES.lock() {
|
if let Ok(map) = INSTANCES.lock() {
|
||||||
if let Some(inst) = map.get(&instance_id) {
|
if let Some(inst) = map.get(&instance_id) {
|
||||||
let mut vals: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
let mut vals: Vec<String> =
|
||||||
for v in inst.data_i64.values() { vals.push(v.to_string()); }
|
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
|
||||||
for v in inst.data_str.values() { vals.push(v.to_string()); }
|
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");
|
let out = vals.join("\n");
|
||||||
return write_tlv_string(&out, result, result_len);
|
return write_tlv_string(&out, result, result_len);
|
||||||
} else { NYB_E_INVALID_HANDLE }
|
} else {
|
||||||
} else { NYB_E_PLUGIN_ERROR }
|
NYB_E_INVALID_HANDLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NYB_E_PLUGIN_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => NYB_E_INVALID_METHOD,
|
_ => NYB_E_INVALID_METHOD,
|
||||||
}
|
}
|
||||||
@ -391,7 +636,7 @@ pub static nyash_typebox_MapBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn escape_json(s: &str) -> String {
|
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() {
|
for ch in s.chars() {
|
||||||
match ch {
|
match ch {
|
||||||
'"' => out.push_str("\\\""),
|
'"' => out.push_str("\\\""),
|
||||||
@ -409,7 +654,9 @@ fn escape_json(s: &str) -> String {
|
|||||||
// TLV helpers
|
// TLV helpers
|
||||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
if result_len.is_null() { return false; }
|
if result_len.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if result.is_null() || *result_len < needed {
|
if result.is_null() || *result_len < needed {
|
||||||
*result_len = needed;
|
*result_len = needed;
|
||||||
return true;
|
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 {
|
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; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return NYB_E_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
let mut buf: Vec<u8> =
|
||||||
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||||
for (tag, payload) in payloads {
|
for (tag, payload) in payloads {
|
||||||
@ -441,27 +691,44 @@ fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut
|
|||||||
NYB_SUCCESS
|
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_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
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) }
|
write_tlv_result(&[(3u8, &v.to_le_bytes())], 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(bv: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
|
let b = [if bv { 1u8 } else { 0u8 }];
|
||||||
|
write_tlv_result(&[(1u8, &b)], result, result_len)
|
||||||
|
}
|
||||||
|
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
|
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||||
|
}
|
||||||
|
|
||||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||||
let mut off = 4usize;
|
let mut off = 4usize;
|
||||||
for i in 0..=n {
|
for i in 0..=n {
|
||||||
if buf.len() < off + 4 { return None; }
|
if buf.len() < off + 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let tag = buf[off];
|
let tag = buf[off];
|
||||||
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
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 + size {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
if i == n {
|
if i == n {
|
||||||
if tag == 3 && size == 8 {
|
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));
|
return Some(i64::from_le_bytes(b));
|
||||||
} else if tag == 2 && size == 4 {
|
} 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);
|
return Some(i32::from_le_bytes(b) as i64);
|
||||||
} else { return None; }
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
off += 4 + size;
|
off += 4 + size;
|
||||||
}
|
}
|
||||||
@ -469,19 +736,27 @@ fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||||
let mut off = 4usize;
|
let mut off = 4usize;
|
||||||
for i in 0..=n {
|
for i in 0..=n {
|
||||||
if buf.len() < off + 4 { return None; }
|
if buf.len() < off + 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let tag = buf[off];
|
let tag = buf[off];
|
||||||
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
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 + size {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
if i == n {
|
if i == n {
|
||||||
if tag == 6 || tag == 7 {
|
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());
|
return Some(String::from_utf8_lossy(s).to_string());
|
||||||
} else { return None; }
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
off += 4 + size;
|
off += 4 + size;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,10 @@
|
|||||||
//! TimeBox: now() -> i64 (unix seconds)
|
//! TimeBox: now() -> i64 (unix seconds)
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
// Error codes
|
// Error codes
|
||||||
const OK: i32 = 0;
|
const OK: i32 = 0;
|
||||||
@ -37,18 +40,56 @@ static ID: AtomicU32 = AtomicU32::new(1);
|
|||||||
|
|
||||||
// TLV helpers
|
// TLV helpers
|
||||||
mod tlv {
|
mod tlv {
|
||||||
pub fn header(argc: u16) -> Vec<u8> { let mut b=Vec::with_capacity(4); b.extend_from_slice(&1u16.to_le_bytes()); b.extend_from_slice(&argc.to_le_bytes()); b }
|
pub fn header(argc: u16) -> Vec<u8> {
|
||||||
pub fn encode_handle(buf: &mut Vec<u8>, t: u32, i: u32) { buf.push(8); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&t.to_le_bytes()); buf.extend_from_slice(&i.to_le_bytes()); }
|
let mut b = Vec::with_capacity(4);
|
||||||
pub fn encode_i64(buf: &mut Vec<u8>, v: i64) { buf.push(3); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&v.to_le_bytes()); }
|
b.extend_from_slice(&1u16.to_le_bytes());
|
||||||
pub fn encode_void(buf: &mut Vec<u8>) { buf.push(9); buf.push(0); buf.push(0); buf.push(0); }
|
b.extend_from_slice(&argc.to_le_bytes());
|
||||||
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)) }
|
b
|
||||||
|
}
|
||||||
|
pub fn encode_handle(buf: &mut Vec<u8>, t: u32, i: u32) {
|
||||||
|
buf.push(8);
|
||||||
|
buf.push(0);
|
||||||
|
buf.push(8);
|
||||||
|
buf.push(0);
|
||||||
|
buf.extend_from_slice(&t.to_le_bytes());
|
||||||
|
buf.extend_from_slice(&i.to_le_bytes());
|
||||||
|
}
|
||||||
|
pub fn encode_i64(buf: &mut Vec<u8>, v: i64) {
|
||||||
|
buf.push(3);
|
||||||
|
buf.push(0);
|
||||||
|
buf.push(8);
|
||||||
|
buf.push(0);
|
||||||
|
buf.extend_from_slice(&v.to_le_bytes());
|
||||||
|
}
|
||||||
|
pub fn encode_void(buf: &mut Vec<u8>) {
|
||||||
|
buf.push(9);
|
||||||
|
buf.push(0);
|
||||||
|
buf.push(0);
|
||||||
|
buf.push(0);
|
||||||
|
}
|
||||||
|
pub fn decode_first(args: &[u8]) -> Option<(u16, u16, usize)> {
|
||||||
|
if args.len() < 8 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let argc = u16::from_le_bytes([args[2], args[3]]);
|
||||||
|
if argc == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let tag = u16::from_le_bytes([args[4], args[5]]);
|
||||||
|
let sz = u16::from_le_bytes([args[6], args[7]]);
|
||||||
|
Some((tag, sz, 8))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -77,31 +118,67 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn birth<T>(tid: u32, map: &Lazy<Mutex<HashMap<u32,T>>>, out: *mut u8, out_len: *mut usize) -> i32 where T: Default {
|
unsafe fn birth<T>(
|
||||||
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
|
tid: u32,
|
||||||
|
map: &Lazy<Mutex<HashMap<u32, T>>>,
|
||||||
|
out: *mut u8,
|
||||||
|
out_len: *mut usize,
|
||||||
|
) -> i32
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
let need = 4 + 4 + 8;
|
||||||
|
if *out_len < need {
|
||||||
|
*out_len = need;
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
let id = ID.fetch_add(1, Ordering::Relaxed);
|
let id = ID.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut m) = map.lock() { m.insert(id, T::default()); } else { return E_FAIL; }
|
if let Ok(mut m) = map.lock() {
|
||||||
let mut buf = tlv::header(1); tlv::encode_handle(&mut buf, tid, id);
|
m.insert(id, T::default());
|
||||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len(); OK
|
} else {
|
||||||
|
return E_FAIL;
|
||||||
|
}
|
||||||
|
let mut buf = tlv::header(1);
|
||||||
|
tlv::encode_handle(&mut buf, tid, id);
|
||||||
|
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
|
||||||
|
*out_len = buf.len();
|
||||||
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn fini<T>(map: &Lazy<Mutex<HashMap<u32,T>>>, instance_id: u32) -> i32 {
|
unsafe fn fini<T>(map: &Lazy<Mutex<HashMap<u32, T>>>, instance_id: u32) -> i32 {
|
||||||
if let Ok(mut m) = map.lock() { m.remove(&instance_id); OK } else { E_FAIL }
|
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 {
|
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);
|
let a = std::slice::from_raw_parts(args, args_len);
|
||||||
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
||||||
if tag == 3 && sz == 8 && a.len() >= 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 mut b = [0u8; 8];
|
||||||
|
b.copy_from_slice(&a[p..p + 8]);
|
||||||
let x = i64::from_le_bytes(b) as f64;
|
let x = i64::from_le_bytes(b) as f64;
|
||||||
let r = x.sqrt();
|
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);
|
let mut buf = tlv::header(1);
|
||||||
// encode f64 (tag=5)
|
// encode f64 (tag=5)
|
||||||
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
|
buf.push(5);
|
||||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
|
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;
|
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 {
|
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 ts = std::time::SystemTime::now()
|
||||||
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
let mut buf = tlv::header(1); tlv::encode_i64(&mut buf, ts);
|
.map(|d| d.as_secs() as i64)
|
||||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
|
.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
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn trig_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mut usize, is_sin: bool) -> i32 {
|
unsafe fn trig_call(
|
||||||
if args_len < 8 { return E_ARGS; }
|
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);
|
let a = std::slice::from_raw_parts(args, args_len);
|
||||||
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
||||||
if tag == 3 && sz == 8 && a.len() >= 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 mut b = [0u8; 8];
|
||||||
|
b.copy_from_slice(&a[p..p + 8]);
|
||||||
let x = i64::from_le_bytes(b) as f64;
|
let x = i64::from_le_bytes(b) as f64;
|
||||||
let r = if is_sin { x.sin() } else { x.cos() };
|
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);
|
let mut buf = tlv::header(1);
|
||||||
// encode f64 (tag=5)
|
// encode f64 (tag=5)
|
||||||
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
|
buf.push(5);
|
||||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
|
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;
|
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 {
|
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);
|
let a = std::slice::from_raw_parts(args, args_len);
|
||||||
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
if let Some((tag, sz, p)) = tlv::decode_first(a) {
|
||||||
if tag == 3 && sz == 8 && a.len() >= 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 mut b = [0u8; 8];
|
||||||
|
b.copy_from_slice(&a[p..p + 8]);
|
||||||
let x = i64::from_le_bytes(b) as f64;
|
let x = i64::from_le_bytes(b) as f64;
|
||||||
let r = x.round();
|
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);
|
let mut buf = tlv::header(1);
|
||||||
// encode f64 (tag=5)
|
// encode f64 (tag=5)
|
||||||
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
|
buf.push(5);
|
||||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
|
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;
|
return OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
use chrono::{DateTime, Datelike, Timelike, Utc};
|
||||||
|
use rand::Rng;
|
||||||
use std::ffi::{c_char, c_void, CStr, CString};
|
use std::ffi::{c_char, c_void, CStr, CString};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use chrono::{DateTime, Utc, Datelike, Timelike};
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
// MathBox構造体
|
// MathBox構造体
|
||||||
pub struct MathBox {
|
pub struct MathBox {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, Component};
|
use std::path::{Component, Path};
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
const OK: i32 = 0;
|
const OK: i32 = 0;
|
||||||
const E_SHORT: i32 = -1;
|
const E_SHORT: i32 = -1;
|
||||||
@ -12,14 +15,14 @@ const E_METHOD: i32 = -3;
|
|||||||
const E_ARGS: i32 = -4;
|
const E_ARGS: i32 = -4;
|
||||||
const E_PLUGIN: i32 = -5;
|
const E_PLUGIN: i32 = -5;
|
||||||
|
|
||||||
const M_BIRTH: u32 = 0; // constructor -> instance
|
const M_BIRTH: u32 = 0; // constructor -> instance
|
||||||
const M_JOIN: u32 = 1; // join(base, rest) -> string
|
const M_JOIN: u32 = 1; // join(base, rest) -> string
|
||||||
const M_DIRNAME: u32 = 2; // dirname(path) -> string
|
const M_DIRNAME: u32 = 2; // dirname(path) -> string
|
||||||
const M_BASENAME: u32 = 3; // basename(path) -> string
|
const M_BASENAME: u32 = 3; // basename(path) -> string
|
||||||
const M_EXTNAME: u32 = 4; // extname(path) -> string
|
const M_EXTNAME: u32 = 4; // extname(path) -> string
|
||||||
const M_IS_ABS: u32 = 5; // isAbs(path) -> bool
|
const M_IS_ABS: u32 = 5; // isAbs(path) -> bool
|
||||||
const M_NORMALIZE: u32 = 6; // normalize(path) -> string
|
const M_NORMALIZE: u32 = 6; // normalize(path) -> string
|
||||||
const M_FINI: u32 = u32::MAX; // fini
|
const M_FINI: u32 = u32::MAX; // fini
|
||||||
|
|
||||||
const TYPE_ID_PATH: u32 = 55;
|
const TYPE_ID_PATH: u32 = 55;
|
||||||
|
|
||||||
@ -29,10 +32,14 @@ static INST: Lazy<Mutex<HashMap<u32, PathInstance>>> = Lazy::new(|| Mutex::new(H
|
|||||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -44,45 +51,99 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_PATH { return E_TYPE; }
|
if type_id != TYPE_ID_PATH {
|
||||||
|
return E_TYPE;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_BIRTH => {
|
M_BIRTH => {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
return E_ARGS;
|
||||||
|
}
|
||||||
|
if preflight(result, result_len, 4) {
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut m) = INST.lock() { m.insert(id, PathInstance); } else { return E_PLUGIN; }
|
if let Ok(mut m) = INST.lock() {
|
||||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
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 => {
|
M_JOIN => {
|
||||||
let base = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
let base = match read_arg_string(args, args_len, 0) {
|
||||||
let rest = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS };
|
Some(s) => s,
|
||||||
let joined = if base.ends_with('/') || base.ends_with('\\') { format!("{}{}", base, rest) } else { format!("{}/{}", base, rest) };
|
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)
|
write_tlv_string(&joined, result, result_len)
|
||||||
}
|
}
|
||||||
M_DIRNAME => {
|
M_DIRNAME => {
|
||||||
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) {
|
||||||
let d = Path::new(&p).parent().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| "".to_string());
|
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)
|
write_tlv_string(&d, result, result_len)
|
||||||
}
|
}
|
||||||
M_BASENAME => {
|
M_BASENAME => {
|
||||||
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) {
|
||||||
let b = Path::new(&p).file_name().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| "".to_string());
|
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)
|
write_tlv_string(&b, result, result_len)
|
||||||
}
|
}
|
||||||
M_EXTNAME => {
|
M_EXTNAME => {
|
||||||
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) {
|
||||||
let ext = Path::new(&p).extension().map(|x| format!(".{}", x.to_string_lossy())).unwrap_or_else(|| "".to_string());
|
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)
|
write_tlv_string(&ext, result, result_len)
|
||||||
}
|
}
|
||||||
M_IS_ABS => {
|
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(":\\");
|
let abs = Path::new(&p).is_absolute() || p.contains(":\\");
|
||||||
write_tlv_bool(abs, result, result_len)
|
write_tlv_bool(abs, result, result_len)
|
||||||
}
|
}
|
||||||
M_NORMALIZE => {
|
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));
|
let norm = path_clean::PathClean::clean(Path::new(&p));
|
||||||
write_tlv_string(norm.to_string_lossy().as_ref(), result, result_len)
|
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 {
|
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
|
false
|
||||||
}
|
}
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return E_ARGS;
|
||||||
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); }
|
let mut buf: Vec<u8> =
|
||||||
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; }
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||||
|
for (tag, payload) in payloads {
|
||||||
|
buf.push(*tag);
|
||||||
|
buf.push(0);
|
||||||
|
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(payload);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let needed = buf.len();
|
||||||
|
if result.is_null() || *result_len < needed {
|
||||||
|
*result_len = needed;
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
|
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||||
|
*result_len = needed;
|
||||||
|
}
|
||||||
OK
|
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_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
|
||||||
|
}
|
||||||
|
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
|
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||||
|
}
|
||||||
|
|
||||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let 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
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::sync::Mutex;
|
|
||||||
use serde_json::Value as Json;
|
use serde_json::Value as Json;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
const NYB_SUCCESS: i32 = 0;
|
const NYB_SUCCESS: i32 = 0;
|
||||||
const NYB_E_INVALID_METHOD: i32 = -3;
|
const NYB_E_INVALID_METHOD: i32 = -3;
|
||||||
@ -14,10 +14,14 @@ const METHOD_FINI: u32 = u32::MAX;
|
|||||||
static NEXT_ID: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(1));
|
static NEXT_ID: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(1));
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
NYB_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -29,14 +33,20 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> 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 {
|
match method_id {
|
||||||
METHOD_BIRTH => {
|
METHOD_BIRTH => {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut id_g = NEXT_ID.lock().unwrap();
|
let mut id_g = NEXT_ID.lock().unwrap();
|
||||||
let id = *id_g; *id_g += 1;
|
let id = *id_g;
|
||||||
|
*id_g += 1;
|
||||||
let need = 4usize;
|
let need = 4usize;
|
||||||
if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; }
|
if *result_len < need {
|
||||||
|
*result_len = need;
|
||||||
|
return NYB_E_SHORT_BUFFER;
|
||||||
|
}
|
||||||
let out = std::slice::from_raw_parts_mut(result, *result_len);
|
let out = std::slice::from_raw_parts_mut(result, *result_len);
|
||||||
out[0..4].copy_from_slice(&(id as u32).to_le_bytes());
|
out[0..4].copy_from_slice(&(id as u32).to_le_bytes());
|
||||||
*result_len = need;
|
*result_len = need;
|
||||||
@ -46,16 +56,20 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
METHOD_COMPILE => {
|
METHOD_COMPILE => {
|
||||||
// Decode TLV first string arg as JSON IR
|
// Decode TLV first string arg as JSON IR
|
||||||
let ir = unsafe {
|
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 buf = std::slice::from_raw_parts(args, args_len);
|
||||||
let tag = u16::from_le_bytes([buf[4], buf[5]]);
|
let tag = u16::from_le_bytes([buf[4], buf[5]]);
|
||||||
let len = u16::from_le_bytes([buf[6], buf[7]]) as usize;
|
let len = u16::from_le_bytes([buf[6], buf[7]]) as usize;
|
||||||
if tag == 6 && 8 + len <= buf.len() {
|
if tag == 6 && 8 + len <= buf.len() {
|
||||||
match std::str::from_utf8(&buf[8..8+len]) {
|
match std::str::from_utf8(&buf[8..8 + len]) {
|
||||||
Ok(s) => Some(s.to_string()),
|
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()) {
|
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") {
|
} else if let Some(module) = map.get("module") {
|
||||||
// Try module.functions[0].name and maybe return value
|
// Try module.functions[0].name and maybe return value
|
||||||
let mut ret_expr = "0".to_string();
|
let mut ret_expr = "0".to_string();
|
||||||
if let Some(funcs) = module.get("functions").and_then(|v| v.as_array()) {
|
if let Some(funcs) = module.get("functions").and_then(|v| v.as_array())
|
||||||
|
{
|
||||||
if let Some(fun0) = funcs.get(0) {
|
if let Some(fun0) = funcs.get(0) {
|
||||||
if let Some(retv) = fun0.get("return_value") {
|
if let Some(retv) = fun0.get("return_value") {
|
||||||
if retv.is_number() { ret_expr = retv.to_string(); }
|
if retv.is_number() {
|
||||||
else if let Some(s) = retv.as_str() { ret_expr = s.to_string(); }
|
ret_expr = retv.to_string();
|
||||||
|
} else if let Some(s) = retv.as_str() {
|
||||||
|
ret_expr = s.to_string();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
format!("static box Generated {{\n main() {{\n return {}\n }}\n}}", ret_expr)
|
format!(
|
||||||
|
"static box Generated {{\n main() {{\n return {}\n }}\n}}",
|
||||||
|
ret_expr
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
"static box Generated { main() { return 0 } }".to_string()
|
"static box Generated { main() { return 0 } }".to_string()
|
||||||
}
|
}
|
||||||
@ -88,11 +109,14 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
unsafe {
|
unsafe {
|
||||||
let bytes = nyash_source.as_bytes();
|
let bytes = nyash_source.as_bytes();
|
||||||
let need = 4 + bytes.len();
|
let need = 4 + bytes.len();
|
||||||
if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; }
|
if *result_len < need {
|
||||||
|
*result_len = need;
|
||||||
|
return NYB_E_SHORT_BUFFER;
|
||||||
|
}
|
||||||
let out = std::slice::from_raw_parts_mut(result, *result_len);
|
let out = std::slice::from_raw_parts_mut(result, *result_len);
|
||||||
out[0..2].copy_from_slice(&6u16.to_le_bytes());
|
out[0..2].copy_from_slice(&6u16.to_le_bytes());
|
||||||
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
|
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
|
||||||
out[4..4+bytes.len()].copy_from_slice(bytes);
|
out[4..4 + bytes.len()].copy_from_slice(bytes);
|
||||||
*result_len = need;
|
*result_len = need;
|
||||||
}
|
}
|
||||||
NYB_SUCCESS
|
NYB_SUCCESS
|
||||||
|
|||||||
@ -71,7 +71,8 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
let code = unsafe {
|
let code = unsafe {
|
||||||
if args.is_null() || args_len < 4 {
|
if args.is_null() || args_len < 4 {
|
||||||
// 引数なしの場合は環境変数から取得
|
// 引数なしの場合は環境変数から取得
|
||||||
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
|
std::env::var("NYASH_PY_CODE")
|
||||||
|
.unwrap_or_else(|_| "def main():\n return 0".to_string())
|
||||||
} else {
|
} else {
|
||||||
// TLVデコード(簡易版)
|
// TLVデコード(簡易版)
|
||||||
let buf = std::slice::from_raw_parts(args, args_len);
|
let buf = std::slice::from_raw_parts(args, args_len);
|
||||||
@ -79,23 +80,24 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
let tag = u16::from_le_bytes([buf[0], buf[1]]);
|
let tag = u16::from_le_bytes([buf[0], buf[1]]);
|
||||||
let len = u16::from_le_bytes([buf[2], buf[3]]) as usize;
|
let len = u16::from_le_bytes([buf[2], buf[3]]) as usize;
|
||||||
if tag == 6 && 4 + len <= args_len {
|
if tag == 6 && 4 + len <= args_len {
|
||||||
match std::str::from_utf8(&buf[4..4+len]) {
|
match std::str::from_utf8(&buf[4..4 + len]) {
|
||||||
Ok(s) => s.to_string(),
|
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 {
|
} 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 {
|
} 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| {
|
let parse_result = Python::with_gil(|py| parse_python_code(py, &code));
|
||||||
parse_python_code(py, &code)
|
|
||||||
});
|
|
||||||
|
|
||||||
// JSONにシリアライズ
|
// JSONにシリアライズ
|
||||||
match serde_json::to_string(&parse_result) {
|
match serde_json::to_string(&parse_result) {
|
||||||
@ -111,22 +113,24 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
// TLVエンコード(tag=6:string)
|
// TLVエンコード(tag=6:string)
|
||||||
out[0..2].copy_from_slice(&6u16.to_le_bytes());
|
out[0..2].copy_from_slice(&6u16.to_le_bytes());
|
||||||
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
|
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
|
||||||
out[4..4+bytes.len()].copy_from_slice(bytes);
|
out[4..4 + bytes.len()].copy_from_slice(bytes);
|
||||||
*result_len = need;
|
*result_len = need;
|
||||||
}
|
}
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
Err(_) => -4 // エラー
|
Err(_) => -4, // エラー
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
METHOD_FINI => 0,
|
METHOD_FINI => 0,
|
||||||
_ => -3 // NYB_E_INVALID_METHOD
|
_ => -3, // NYB_E_INVALID_METHOD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FFI: Pythonコードをパース
|
/// FFI: Pythonコードをパース
|
||||||
#[no_mangle]
|
#[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 {
|
let code = unsafe {
|
||||||
if code.is_null() {
|
if code.is_null() {
|
||||||
return std::ptr::null_mut();
|
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| {
|
let result = Python::with_gil(|py| parse_python_code(py, code));
|
||||||
parse_python_code(py, code)
|
|
||||||
});
|
|
||||||
|
|
||||||
match serde_json::to_string(&result) {
|
match serde_json::to_string(&result) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
@ -265,15 +267,25 @@ fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) {
|
|||||||
"AsyncFunctionDef" => {
|
"AsyncFunctionDef" => {
|
||||||
result.counts.functions += 1;
|
result.counts.functions += 1;
|
||||||
result.counts.unsupported += 1;
|
result.counts.unsupported += 1;
|
||||||
if !result.unsupported.contains(&"async function".to_string()) {
|
if !result
|
||||||
result.unsupported.push("async function".to_string());
|
.unsupported
|
||||||
|
.contains(&"async function".to_string())
|
||||||
|
{
|
||||||
|
result
|
||||||
|
.unsupported
|
||||||
|
.push("async function".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"ClassDef" => {
|
"ClassDef" => {
|
||||||
result.counts.classes += 1;
|
result.counts.classes += 1;
|
||||||
result.counts.unsupported += 1;
|
result.counts.unsupported += 1;
|
||||||
if !result.unsupported.contains(&"class definition".to_string()) {
|
if !result
|
||||||
result.unsupported.push("class definition".to_string());
|
.unsupported
|
||||||
|
.contains(&"class definition".to_string())
|
||||||
|
{
|
||||||
|
result
|
||||||
|
.unsupported
|
||||||
|
.push("class definition".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"For" | "While" | "If" => {
|
"For" | "While" | "If" => {
|
||||||
@ -281,8 +293,13 @@ fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) {
|
|||||||
}
|
}
|
||||||
"Yield" | "YieldFrom" => {
|
"Yield" | "YieldFrom" => {
|
||||||
result.counts.unsupported += 1;
|
result.counts.unsupported += 1;
|
||||||
if !result.unsupported.contains(&"generator".to_string()) {
|
if !result
|
||||||
result.unsupported.push("generator".to_string());
|
.unsupported
|
||||||
|
.contains(&"generator".to_string())
|
||||||
|
{
|
||||||
|
result
|
||||||
|
.unsupported
|
||||||
|
.push("generator".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,10 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
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
|
// Error/status codes aligned with other plugins
|
||||||
const OK: i32 = 0;
|
const OK: i32 = 0;
|
||||||
@ -15,27 +18,33 @@ const E_PLUGIN: i32 = -5;
|
|||||||
const E_HANDLE: i32 = -8;
|
const E_HANDLE: i32 = -8;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const M_BIRTH: u32 = 0; // birth(pattern?) -> instance
|
const M_BIRTH: u32 = 0; // birth(pattern?) -> instance
|
||||||
const M_COMPILE: u32 = 1; // compile(pattern) -> self (new compiled)
|
const M_COMPILE: u32 = 1; // compile(pattern) -> self (new compiled)
|
||||||
const M_IS_MATCH: u32 = 2; // isMatch(text) -> bool
|
const M_IS_MATCH: u32 = 2; // isMatch(text) -> bool
|
||||||
const M_FIND: u32 = 3; // find(text) -> String (first match or empty)
|
const M_FIND: u32 = 3; // find(text) -> String (first match or empty)
|
||||||
const M_REPLACE_ALL: u32 = 4; // replaceAll(text, repl) -> String
|
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()
|
const M_FINI: u32 = u32::MAX; // fini()
|
||||||
|
|
||||||
// Assign an unused type id (see nyash.toml [box_types])
|
// Assign an unused type id (see nyash.toml [box_types])
|
||||||
const TYPE_ID_REGEX: u32 = 52;
|
const TYPE_ID_REGEX: u32 = 52;
|
||||||
|
|
||||||
struct RegexInstance { re: Option<Regex> }
|
struct RegexInstance {
|
||||||
|
re: Option<Regex>,
|
||||||
|
}
|
||||||
|
|
||||||
static INST: Lazy<Mutex<HashMap<u32, RegexInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static INST: Lazy<Mutex<HashMap<u32, RegexInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -47,42 +56,155 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_REGEX { return E_TYPE; }
|
if type_id != TYPE_ID_REGEX {
|
||||||
|
return E_TYPE;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_BIRTH => {
|
M_BIRTH => {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
return E_ARGS;
|
||||||
|
}
|
||||||
|
if preflight(result, result_len, 4) {
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
// Optional pattern in arg0
|
// Optional pattern in arg0
|
||||||
let inst = if let Some(pat) = read_arg_string(args, args_len, 0) {
|
let inst = if let Some(pat) = read_arg_string(args, args_len, 0) {
|
||||||
match Regex::new(&pat) { Ok(re) => RegexInstance { re: Some(re) }, Err(_) => RegexInstance { re: None } }
|
match Regex::new(&pat) {
|
||||||
} else { RegexInstance { re: None } };
|
Ok(re) => RegexInstance { re: Some(re) },
|
||||||
if let Ok(mut m) = INST.lock() { m.insert(id, inst); } else { return E_PLUGIN; }
|
Err(_) => RegexInstance { re: None },
|
||||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
}
|
||||||
|
} 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 => {
|
M_COMPILE => {
|
||||||
let pat = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
let pat = match read_arg_string(args, args_len, 0) {
|
||||||
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 }
|
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 => {
|
M_IS_MATCH => {
|
||||||
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
let text = match read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(m) = INST.lock() { 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; }
|
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 => {
|
M_FIND => {
|
||||||
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
let text = match read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(m) = INST.lock() { 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; }
|
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 => {
|
M_REPLACE_ALL => {
|
||||||
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) {
|
||||||
let repl = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS };
|
Some(s) => s,
|
||||||
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; }
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
|
let repl = match read_arg_string(args, args_len, 1) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
|
if let Ok(m) = INST.lock() {
|
||||||
|
if let Some(inst) = m.get(&instance_id) {
|
||||||
|
if let Some(re) = &inst.re {
|
||||||
|
let out = re.replace_all(&text, repl.as_str()).to_string();
|
||||||
|
return write_tlv_string(&out, result, result_len);
|
||||||
|
} else {
|
||||||
|
return write_tlv_string(&text, result, result_len);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
M_SPLIT => {
|
M_SPLIT => {
|
||||||
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
let text = match read_arg_string(args, args_len, 0) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
let limit = read_arg_i64(args, args_len, 1).unwrap_or(0);
|
let limit = read_arg_i64(args, args_len, 1).unwrap_or(0);
|
||||||
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { let mut parts: Vec<String> = if limit > 0 { re.splitn(&text, limit as usize).map(|s| s.to_string()).collect() } else { re.split(&text).map(|s| s.to_string()).collect() }; let out = parts.join("\n"); return write_tlv_string(&out, result, result_len); } else { return write_tlv_string(&text, result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
if let Ok(m) = INST.lock() {
|
||||||
|
if let Some(inst) = m.get(&instance_id) {
|
||||||
|
if let Some(re) = &inst.re {
|
||||||
|
let mut parts: Vec<String> = if limit > 0 {
|
||||||
|
re.splitn(&text, limit as usize)
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
re.split(&text).map(|s| s.to_string()).collect()
|
||||||
|
};
|
||||||
|
let out = parts.join("\n");
|
||||||
|
return write_tlv_string(&out, result, result_len);
|
||||||
|
} else {
|
||||||
|
return write_tlv_string(&text, result, result_len);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => E_METHOD,
|
_ => 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 {
|
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
|
false
|
||||||
}
|
}
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return E_ARGS;
|
||||||
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); }
|
let mut buf: Vec<u8> =
|
||||||
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; }
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||||
|
for (tag, payload) in payloads {
|
||||||
|
buf.push(*tag);
|
||||||
|
buf.push(0);
|
||||||
|
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(payload);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let needed = buf.len();
|
||||||
|
if result.is_null() || *result_len < needed {
|
||||||
|
*result_len = needed;
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
|
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||||
|
*result_len = needed;
|
||||||
|
}
|
||||||
OK
|
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 {
|
||||||
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) }
|
write_tlv_result(&[(3u8, &v.to_le_bytes())], 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_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let 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
|
None
|
||||||
}
|
}
|
||||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let 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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,12 @@
|
|||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
const OK: i32 = 0;
|
const OK: i32 = 0;
|
||||||
const E_SHORT: i32 = -1;
|
const E_SHORT: i32 = -1;
|
||||||
@ -19,23 +22,29 @@ const M_BIRTH: u32 = 0;
|
|||||||
const M_LENGTH: u32 = 1;
|
const M_LENGTH: u32 = 1;
|
||||||
const M_IS_EMPTY: u32 = 2;
|
const M_IS_EMPTY: u32 = 2;
|
||||||
const M_CHAR_CODE_AT: u32 = 3;
|
const M_CHAR_CODE_AT: u32 = 3;
|
||||||
const M_CONCAT: u32 = 4; // concat(other: String|Handle) -> Handle(new)
|
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_FROM_UTF8: u32 = 5; // fromUtf8(data: String|Bytes) -> Handle(new)
|
||||||
const M_TO_UTF8: u32 = 6; // toUtf8() -> String
|
const M_TO_UTF8: u32 = 6; // toUtf8() -> String
|
||||||
const M_FINI: u32 = u32::MAX;
|
const M_FINI: u32 = u32::MAX;
|
||||||
|
|
||||||
const TYPE_ID_STRING: u32 = 13;
|
const TYPE_ID_STRING: u32 = 13;
|
||||||
|
|
||||||
struct StrInstance { s: String }
|
struct StrInstance {
|
||||||
|
s: String,
|
||||||
|
}
|
||||||
|
|
||||||
static INST: Lazy<Mutex<HashMap<u32, StrInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static INST: Lazy<Mutex<HashMap<u32, StrInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -47,29 +56,69 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_STRING { return E_TYPE; }
|
if type_id != TYPE_ID_STRING {
|
||||||
|
return E_TYPE;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_BIRTH => {
|
M_BIRTH => {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
return E_ARGS;
|
||||||
|
}
|
||||||
|
if preflight(result, result_len, 4) {
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
// Optional init from first arg (String/Bytes)
|
// Optional init from first arg (String/Bytes)
|
||||||
let init = read_arg_string(args, args_len, 0).unwrap_or_else(|| String::new());
|
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 }); }
|
if let Ok(mut m) = INST.lock() {
|
||||||
else { return E_PLUGIN; }
|
m.insert(id, StrInstance { s: init });
|
||||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
} 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 => {
|
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 => {
|
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 => {
|
M_CHAR_CODE_AT => {
|
||||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
let idx = match read_arg_i64(args, args_len, 0) {
|
||||||
if idx < 0 { return E_ARGS; }
|
Some(v) => v,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
|
if idx < 0 {
|
||||||
|
return E_ARGS;
|
||||||
|
}
|
||||||
if let Ok(m) = INST.lock() {
|
if let Ok(m) = INST.lock() {
|
||||||
if let Some(inst) = m.get(&instance_id) {
|
if let Some(inst) = m.get(&instance_id) {
|
||||||
// Interpret index as char-index into Unicode scalar values
|
// 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 ch_opt = inst.s.chars().nth(i);
|
||||||
let code = ch_opt.map(|c| c as u32 as i64).unwrap_or(0);
|
let code = ch_opt.map(|c| c as u32 as i64).unwrap_or(0);
|
||||||
return write_tlv_i64(code, result, result_len);
|
return write_tlv_i64(code, result, result_len);
|
||||||
} else { return E_HANDLE; }
|
} else {
|
||||||
} else { return E_PLUGIN; }
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
M_CONCAT => {
|
M_CONCAT => {
|
||||||
// Accept either Handle(tag=8) to another StringBox, or String/Bytes payload
|
// 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) {
|
let (ok, rhs) = if let Some((t, inst)) = read_arg_handle(args, args_len, 0) {
|
||||||
if t != TYPE_ID_STRING { return E_TYPE; }
|
if t != TYPE_ID_STRING {
|
||||||
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; }
|
return E_TYPE;
|
||||||
} 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(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 Ok(m) = INST.lock() {
|
||||||
if let Some(inst) = m.get(&instance_id) {
|
if let Some(inst) = m.get(&instance_id) {
|
||||||
let mut new_s = inst.s.clone();
|
let mut new_s = inst.s.clone();
|
||||||
new_s.push_str(&rhs);
|
new_s.push_str(&rhs);
|
||||||
drop(m);
|
drop(m);
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
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);
|
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
|
||||||
} else { return E_HANDLE; }
|
} else {
|
||||||
} else { return E_PLUGIN; }
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
M_FROM_UTF8 => {
|
M_FROM_UTF8 => {
|
||||||
// Create new instance from UTF-8 (accept String/Bytes)
|
// 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);
|
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);
|
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
|
||||||
}
|
}
|
||||||
M_TO_UTF8 => {
|
M_TO_UTF8 => {
|
||||||
if let Ok(m) = INST.lock() {
|
if let Ok(m) = INST.lock() {
|
||||||
if let Some(inst) = m.get(&instance_id) {
|
if let Some(inst) = m.get(&instance_id) {
|
||||||
return write_tlv_string(&inst.s, result, result_len);
|
return write_tlv_string(&inst.s, result, result_len);
|
||||||
} else { return E_HANDLE; }
|
} else {
|
||||||
} else { return E_PLUGIN; }
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => E_METHOD,
|
_ => E_METHOD,
|
||||||
}
|
}
|
||||||
@ -131,7 +218,9 @@ pub struct NyashTypeBoxFfi {
|
|||||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||||
|
|
||||||
extern "C" fn string_resolve(name: *const c_char) -> u32 {
|
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();
|
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
|
||||||
match s.as_ref() {
|
match s.as_ref() {
|
||||||
"len" | "length" => M_LENGTH,
|
"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 {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_LENGTH => {
|
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 => {
|
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 => {
|
M_CONCAT => {
|
||||||
// support String/Bytes or StringBox handle
|
// support String/Bytes or StringBox handle
|
||||||
let (ok, rhs) = if let Some((t, inst)) = read_arg_handle(args, args_len, 0) {
|
let (ok, rhs) = if let Some((t, inst)) = read_arg_handle(args, args_len, 0) {
|
||||||
if t != TYPE_ID_STRING { return E_TYPE; }
|
if t != TYPE_ID_STRING {
|
||||||
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; }
|
return E_TYPE;
|
||||||
} 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(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 Ok(m) = INST.lock() {
|
||||||
if let Some(inst) = m.get(&instance_id) {
|
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);
|
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);
|
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
|
||||||
} else { return E_HANDLE; }
|
} else {
|
||||||
} else { return E_PLUGIN; }
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => E_METHOD,
|
_ => 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 {
|
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
|
false
|
||||||
}
|
}
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return E_ARGS;
|
||||||
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); }
|
let mut buf: Vec<u8> =
|
||||||
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; }
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||||
|
for (tag, payload) in payloads {
|
||||||
|
buf.push(*tag);
|
||||||
|
buf.push(0);
|
||||||
|
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(payload);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let needed = buf.len();
|
||||||
|
if result.is_null() || *result_len < needed {
|
||||||
|
*result_len = needed;
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
|
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||||
|
*result_len = needed;
|
||||||
|
}
|
||||||
OK
|
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 {
|
||||||
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) }
|
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
|
||||||
fn write_tlv_handle(type_id: u32, instance_id: u32, result: *mut u8, result_len: *mut usize) -> i32 {
|
}
|
||||||
|
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);
|
let mut payload = Vec::with_capacity(8);
|
||||||
payload.extend_from_slice(&type_id.to_le_bytes());
|
payload.extend_from_slice(&type_id.to_le_bytes());
|
||||||
payload.extend_from_slice(&instance_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)
|
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||||
}
|
}
|
||||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let 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
|
None
|
||||||
}
|
}
|
||||||
fn read_arg_handle(args: *const u8, args_len: usize, n: usize) -> Option<(u32,u32)> {
|
fn read_arg_handle(args: *const u8, args_len: usize, n: usize) -> Option<(u32, u32)> {
|
||||||
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 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
|
None
|
||||||
}
|
}
|
||||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
let 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 {
|
let mut off = 4usize;
|
||||||
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; }
|
for i in 0..=n {
|
||||||
} off += 4 + size; }
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,13 +125,13 @@ static TESTBOX_B_INFO: NyashPluginInfo = NyashPluginInfo {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
1 // BID-1 ABI version
|
1 // BID-1 ABI version
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init(
|
pub extern "C" fn nyash_plugin_init(
|
||||||
host: *const NyashHostVtable,
|
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 {
|
) -> i32 {
|
||||||
if host.is_null() {
|
if host.is_null() {
|
||||||
return NYB_E_INVALID_ARGS;
|
return NYB_E_INVALID_ARGS;
|
||||||
@ -152,7 +152,7 @@ pub extern "C" fn nyash_plugin_init(
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_get_box_count() -> u32 {
|
pub extern "C" fn nyash_plugin_get_box_count() -> u32 {
|
||||||
2 // TestBoxA and TestBoxB
|
2 // TestBoxA and TestBoxB
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@ -195,26 +195,14 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
unsafe {
|
unsafe {
|
||||||
match (type_id, method_id) {
|
match (type_id, method_id) {
|
||||||
// TestBoxA methods
|
// TestBoxA methods
|
||||||
(TYPE_ID_TESTBOX_A, METHOD_BIRTH) => {
|
(TYPE_ID_TESTBOX_A, METHOD_BIRTH) => create_instance_a(result, result_len),
|
||||||
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_HELLO) => {
|
|
||||||
hello_method(instance_id, result, result_len)
|
|
||||||
}
|
|
||||||
(TYPE_ID_TESTBOX_A, METHOD_FINI) => {
|
|
||||||
destroy_instance(instance_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestBoxB methods
|
// TestBoxB methods
|
||||||
(TYPE_ID_TESTBOX_B, METHOD_BIRTH) => {
|
(TYPE_ID_TESTBOX_B, METHOD_BIRTH) => create_instance_b(result, result_len),
|
||||||
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_GREET) => {
|
|
||||||
greet_method(instance_id, result, result_len)
|
|
||||||
}
|
|
||||||
(TYPE_ID_TESTBOX_B, METHOD_FINI) => {
|
|
||||||
destroy_instance(instance_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => NYB_E_INVALID_ARGS,
|
_ => NYB_E_INVALID_ARGS,
|
||||||
}
|
}
|
||||||
@ -229,9 +217,12 @@ unsafe fn create_instance_a(result: *mut u8, result_len: *mut usize) -> i32 {
|
|||||||
let id = INSTANCE_COUNTER;
|
let id = INSTANCE_COUNTER;
|
||||||
INSTANCE_COUNTER += 1;
|
INSTANCE_COUNTER += 1;
|
||||||
|
|
||||||
map.insert(id, TestInstance::BoxA {
|
map.insert(
|
||||||
message: "Hello from TestBoxA!".to_string(),
|
id,
|
||||||
});
|
TestInstance::BoxA {
|
||||||
|
message: "Hello from TestBoxA!".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Return instance ID
|
// Return instance ID
|
||||||
if *result_len >= 4 {
|
if *result_len >= 4 {
|
||||||
@ -253,9 +244,7 @@ unsafe fn create_instance_b(result: *mut u8, result_len: *mut usize) -> i32 {
|
|||||||
let id = INSTANCE_COUNTER;
|
let id = INSTANCE_COUNTER;
|
||||||
INSTANCE_COUNTER += 1;
|
INSTANCE_COUNTER += 1;
|
||||||
|
|
||||||
map.insert(id, TestInstance::BoxB {
|
map.insert(id, TestInstance::BoxB { counter: 0 });
|
||||||
counter: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return instance ID
|
// Return instance ID
|
||||||
if *result_len >= 4 {
|
if *result_len >= 4 {
|
||||||
@ -329,21 +318,21 @@ unsafe fn destroy_instance(instance_id: u32) -> i32 {
|
|||||||
|
|
||||||
unsafe fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
unsafe fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
let bytes = s.as_bytes();
|
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 {
|
if *result_len < needed {
|
||||||
return NYB_E_INVALID_ARGS;
|
return NYB_E_INVALID_ARGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLV header
|
// TLV header
|
||||||
*result = 1; // version low
|
*result = 1; // version low
|
||||||
*result.offset(1) = 0; // version high
|
*result.offset(1) = 0; // version high
|
||||||
*result.offset(2) = 1; // argc low
|
*result.offset(2) = 1; // argc low
|
||||||
*result.offset(3) = 0; // argc high
|
*result.offset(3) = 0; // argc high
|
||||||
|
|
||||||
// String entry
|
// String entry
|
||||||
*result.offset(4) = 6; // Tag::String
|
*result.offset(4) = 6; // Tag::String
|
||||||
*result.offset(5) = 0; // padding
|
*result.offset(5) = 0; // padding
|
||||||
let len_bytes = (bytes.len() as u16).to_le_bytes();
|
let len_bytes = (bytes.len() as u16).to_le_bytes();
|
||||||
*result.offset(6) = len_bytes[0];
|
*result.offset(6) = len_bytes[0];
|
||||||
*result.offset(7) = len_bytes[1];
|
*result.offset(7) = len_bytes[1];
|
||||||
|
|||||||
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
|
use std::sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
const OK: i32 = 0;
|
const OK: i32 = 0;
|
||||||
const E_SHORT: i32 = -1;
|
const E_SHORT: i32 = -1;
|
||||||
@ -12,24 +15,30 @@ const E_ARGS: i32 = -4;
|
|||||||
const E_PLUGIN: i32 = -5;
|
const E_PLUGIN: i32 = -5;
|
||||||
const E_HANDLE: i32 = -8;
|
const E_HANDLE: i32 = -8;
|
||||||
|
|
||||||
const M_BIRTH: u32 = 0; // constructor -> instance
|
const M_BIRTH: u32 = 0; // constructor -> instance
|
||||||
const M_PARSE: u32 = 1; // parse(text) -> bool
|
const M_PARSE: u32 = 1; // parse(text) -> bool
|
||||||
const M_GET: u32 = 2; // get(path.dot.segments) -> string (toml-display) or empty
|
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_TO_JSON: u32 = 3; // toJson() -> string (JSON)
|
||||||
const M_FINI: u32 = u32::MAX; // fini()
|
const M_FINI: u32 = u32::MAX; // fini()
|
||||||
|
|
||||||
const TYPE_ID_TOML: u32 = 54;
|
const TYPE_ID_TOML: u32 = 54;
|
||||||
|
|
||||||
struct TomlInstance { value: Option<toml::Value> }
|
struct TomlInstance {
|
||||||
|
value: Option<toml::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
static INST: Lazy<Mutex<HashMap<u32, TomlInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static INST: Lazy<Mutex<HashMap<u32, TomlInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
|
pub extern "C" fn nyash_plugin_abi() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nyash_plugin_invoke(
|
pub extern "C" fn nyash_plugin_invoke(
|
||||||
@ -41,35 +50,84 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
result: *mut u8,
|
result: *mut u8,
|
||||||
result_len: *mut usize,
|
result_len: *mut usize,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if type_id != TYPE_ID_TOML { return E_TYPE; }
|
if type_id != TYPE_ID_TOML {
|
||||||
|
return E_TYPE;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
match method_id {
|
match method_id {
|
||||||
M_BIRTH => {
|
M_BIRTH => {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
if preflight(result, result_len, 4) { return E_SHORT; }
|
return E_ARGS;
|
||||||
|
}
|
||||||
|
if preflight(result, result_len, 4) {
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Ok(mut m) = INST.lock() { m.insert(id, TomlInstance { value: None }); } else { return E_PLUGIN; }
|
if let Ok(mut m) = INST.lock() {
|
||||||
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
|
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 => {
|
M_PARSE => {
|
||||||
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
let text = match read_arg_string(args, args_len, 0) {
|
||||||
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.value = toml::from_str::<toml::Value>(&text).ok(); return write_tlv_bool(inst.value.is_some(), result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
Some(s) => s,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
|
if let Ok(mut m) = INST.lock() {
|
||||||
|
if let Some(inst) = m.get_mut(&instance_id) {
|
||||||
|
inst.value = toml::from_str::<toml::Value>(&text).ok();
|
||||||
|
return write_tlv_bool(inst.value.is_some(), result, result_len);
|
||||||
|
} else {
|
||||||
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
M_GET => {
|
M_GET => {
|
||||||
let path = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
let path = match read_arg_string(args, args_len, 0) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return E_ARGS,
|
||||||
|
};
|
||||||
if let Ok(m) = INST.lock() {
|
if let Ok(m) = INST.lock() {
|
||||||
if let Some(inst) = m.get(&instance_id) {
|
if let Some(inst) = m.get(&instance_id) {
|
||||||
let mut cur = match &inst.value { Some(v) => v, None => { return write_tlv_string("", result, result_len); } };
|
let mut cur = match &inst.value {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
return write_tlv_string("", result, result_len);
|
||||||
|
}
|
||||||
|
};
|
||||||
if !path.is_empty() {
|
if !path.is_empty() {
|
||||||
for seg in path.split('.') {
|
for seg in path.split('.') {
|
||||||
match cur.get(seg) { Some(v) => cur = v, None => { return write_tlv_string("", result, result_len); } }
|
match cur.get(seg) {
|
||||||
|
Some(v) => cur = v,
|
||||||
|
None => {
|
||||||
|
return write_tlv_string("", result, result_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let out = cur.to_string();
|
let out = cur.to_string();
|
||||||
return write_tlv_string(&out, result, result_len);
|
return write_tlv_string(&out, result, result_len);
|
||||||
} else { return E_HANDLE; }
|
} else {
|
||||||
} else { return E_PLUGIN; }
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
M_TO_JSON => {
|
M_TO_JSON => {
|
||||||
if let Ok(m) = INST.lock() {
|
if let Ok(m) = INST.lock() {
|
||||||
@ -77,10 +135,19 @@ pub extern "C" fn nyash_plugin_invoke(
|
|||||||
if let Some(v) = &inst.value {
|
if let Some(v) = &inst.value {
|
||||||
// Convert via serde_json::Value
|
// Convert via serde_json::Value
|
||||||
let sv = toml_to_json(v);
|
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) };
|
return match serde_json::to_string(&sv) {
|
||||||
} else { return write_tlv_string("{}", result, result_len); }
|
Ok(s) => write_tlv_string(&s, result, result_len),
|
||||||
} else { return E_HANDLE; }
|
Err(_) => write_tlv_string("{}", result, result_len),
|
||||||
} else { return E_PLUGIN; }
|
};
|
||||||
|
} else {
|
||||||
|
return write_tlv_string("{}", result, result_len);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_HANDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return E_PLUGIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => E_METHOD,
|
_ => 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::Array(arr) => serde_json::Value::Array(arr.iter().map(toml_to_json).collect()),
|
||||||
toml::Value::Table(map) => {
|
toml::Value::Table(map) => {
|
||||||
let mut m = serde_json::Map::new();
|
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)
|
serde_json::Value::Object(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
|
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
|
false
|
||||||
}
|
}
|
||||||
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
if result_len.is_null() { return E_ARGS; }
|
if result_len.is_null() {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
return E_ARGS;
|
||||||
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); }
|
let mut buf: Vec<u8> =
|
||||||
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; }
|
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
|
||||||
|
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||||
|
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
|
||||||
|
for (tag, payload) in payloads {
|
||||||
|
buf.push(*tag);
|
||||||
|
buf.push(0);
|
||||||
|
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(payload);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let needed = buf.len();
|
||||||
|
if result.is_null() || *result_len < needed {
|
||||||
|
*result_len = needed;
|
||||||
|
return E_SHORT;
|
||||||
|
}
|
||||||
|
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
|
||||||
|
*result_len = needed;
|
||||||
|
}
|
||||||
OK
|
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_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
|
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
|
||||||
|
}
|
||||||
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||||
if args.is_null() || args_len < 4 { return None; }
|
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
|
||||||
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 read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
|
||||||
|
if args.is_null() || args_len < 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
|
||||||
|
let mut off = 4usize;
|
||||||
|
for i in 0..=n {
|
||||||
|
if buf.len() < off + 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let tag = buf[off];
|
||||||
|
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
|
||||||
|
if buf.len() < off + 4 + size {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if i == n {
|
||||||
|
if tag == 6 || tag == 7 {
|
||||||
|
return Some(String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string());
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
off += 4 + size;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ use super::super::types::{to_bool, map_mirtype_to_basic};
|
|||||||
use super::builder_cursor::BuilderCursor;
|
use super::builder_cursor::BuilderCursor;
|
||||||
use super::Resolver;
|
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>(
|
pub(in super::super) fn emit_return<'ctx, 'b>(
|
||||||
codegen: &CodegenContext<'ctx>,
|
codegen: &CodegenContext<'ctx>,
|
||||||
cursor: &mut BuilderCursor<'ctx, 'b>,
|
cursor: &mut BuilderCursor<'ctx, 'b>,
|
||||||
@ -82,6 +84,9 @@ pub(in super::super) fn emit_jump<'ctx, 'b>(
|
|||||||
cursor.emit_term(bid, |b| {
|
cursor.emit_term(bid, |b| {
|
||||||
b.build_unconditional_branch(tbb).map_err(|e| e.to_string()).unwrap();
|
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(())
|
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")?;
|
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!(
|
eprintln!(
|
||||||
"[PHI] finalize add pred_bb={} val={} ty={}",
|
"[PHI:finalize] succ={} pred={} vid={} ty={}",
|
||||||
pred.as_u32(), in_vid.as_u32(),
|
succ_bb.as_u32(), pred.as_u32(), in_vid.as_u32(),
|
||||||
phi.as_basic_value().get_type().print_to_string().to_string()
|
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(),
|
BT::PointerType(pt) => pt.const_zero().into(),
|
||||||
_ => return Err("unsupported phi type for zero synth (finalize)".to_string()),
|
_ => 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!(
|
eprintln!(
|
||||||
"[PHI] finalize add (synth) pred_bb={} zero-ty={}",
|
"[PHI:finalize] succ={} pred={} vid=? ty={} src=synth_zero",
|
||||||
pred.as_u32(), bt.print_to_string().to_string()
|
succ_bb.as_u32(), pred.as_u32(), bt.print_to_string().to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
match z {
|
match z {
|
||||||
@ -431,6 +436,12 @@ pub(in super::super) fn localize_to_i64<'ctx, 'b>(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
phi.add_incoming(&[(&iv_out, pred_bb)]);
|
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
|
// Restore insertion point
|
||||||
if let Some(bb) = saved_ip { codegen.builder.position_at_end(bb); }
|
if let Some(bb) = saved_ip { codegen.builder.position_at_end(bb); }
|
||||||
|
|||||||
@ -134,3 +134,49 @@ pub(in super::super) fn lower_load<'ctx, 'b>(
|
|||||||
vmap.insert(*dst, lv);
|
vmap.insert(*dst, lv);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lower Copy: define dst in the current block by localizing src via Resolver
|
||||||
|
pub(in super::super) fn lower_copy<'ctx, 'b>(
|
||||||
|
codegen: &CodegenContext<'ctx>,
|
||||||
|
cursor: &mut BuilderCursor<'ctx, 'b>,
|
||||||
|
resolver: &mut super::Resolver<'ctx>,
|
||||||
|
cur_bid: BasicBlockId,
|
||||||
|
func: &crate::mir::function::MirFunction,
|
||||||
|
vmap: &mut HashMap<ValueId, BasicValueEnum<'ctx>>,
|
||||||
|
dst: &ValueId,
|
||||||
|
src: &ValueId,
|
||||||
|
bb_map: &std::collections::HashMap<crate::mir::BasicBlockId, inkwell::basic_block::BasicBlock<'ctx>>,
|
||||||
|
preds: &std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::BasicBlockId>>,
|
||||||
|
block_end_values: &std::collections::HashMap<crate::mir::BasicBlockId, std::collections::HashMap<ValueId, BasicValueEnum<'ctx>>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
// Choose resolution kind based on metadata type preference
|
||||||
|
use inkwell::types::BasicTypeEnum as BT;
|
||||||
|
let expected_bt: Option<BT> = func
|
||||||
|
.metadata
|
||||||
|
.value_types
|
||||||
|
.get(dst)
|
||||||
|
.or_else(|| func.metadata.value_types.get(src))
|
||||||
|
.map(|mt| super::super::types::map_mirtype_to_basic(codegen.context, mt));
|
||||||
|
let out: BasicValueEnum<'ctx> = match expected_bt {
|
||||||
|
Some(BT::IntType(_)) | None => {
|
||||||
|
// Prefer i64 for unknown
|
||||||
|
let iv = resolver.resolve_i64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
|
||||||
|
iv.into()
|
||||||
|
}
|
||||||
|
Some(BT::PointerType(_)) => {
|
||||||
|
let pv = resolver.resolve_ptr(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
|
||||||
|
pv.into()
|
||||||
|
}
|
||||||
|
Some(BT::FloatType(_)) => {
|
||||||
|
let fv = resolver.resolve_f64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
|
||||||
|
fv.into()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Fallback i64
|
||||||
|
let iv = resolver.resolve_i64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
|
||||||
|
iv.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
vmap.insert(*dst, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ pub(super) use newbox::lower_newbox;
|
|||||||
pub(super) use boxcall::{lower_boxcall, lower_boxcall_boxed, lower_boxcall_via_ctx};
|
pub(super) use boxcall::{lower_boxcall, lower_boxcall_boxed, lower_boxcall_via_ctx};
|
||||||
pub(super) use arith::lower_compare;
|
pub(super) use arith::lower_compare;
|
||||||
pub(super) use mem::{lower_load, lower_store};
|
pub(super) use mem::{lower_load, lower_store};
|
||||||
|
pub(super) use mem::lower_copy;
|
||||||
pub(super) use consts::lower_const;
|
pub(super) use consts::lower_const;
|
||||||
pub(super) use arith_ops::{lower_binop, lower_unary};
|
pub(super) use arith_ops::{lower_binop, lower_unary};
|
||||||
pub(super) use call::lower_call;
|
pub(super) use call::lower_call;
|
||||||
|
|||||||
@ -767,6 +767,22 @@ impl LLVMCompiler {
|
|||||||
instructions::lower_load(&codegen, &mut cursor, *bid, &mut vmap, &mut allocas, &mut alloca_elem_types, dst, ptr)?;
|
instructions::lower_load(&codegen, &mut cursor, *bid, &mut vmap, &mut allocas, &mut alloca_elem_types, dst, ptr)?;
|
||||||
defined_in_block.insert(*dst);
|
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 { .. } => {
|
MirInstruction::Phi { .. } => {
|
||||||
// Already created in pre-pass; nothing to do here.
|
// Already created in pre-pass; nothing to do here.
|
||||||
}
|
}
|
||||||
@ -799,6 +815,23 @@ impl LLVMCompiler {
|
|||||||
&block_end_values,
|
&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 } => {
|
MirInstruction::Jump { target } => {
|
||||||
// LoopForm simple body→dispatch wiring: if this block is a loop body
|
// 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
|
// and jumps back to its header, redirect to dispatch and add PHI incoming
|
||||||
|
|||||||
@ -24,28 +24,47 @@ fn main() {
|
|||||||
// Read stdin to tmp/ny_mir_builder_input.json for re-use
|
// Read stdin to tmp/ny_mir_builder_input.json for re-use
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
io::stdin().read_to_end(&mut buf).expect("read stdin");
|
io::stdin().read_to_end(&mut buf).expect("read stdin");
|
||||||
if buf.is_empty() { eprintln!("error: no input on stdin"); std::process::exit(2); }
|
if buf.is_empty() {
|
||||||
let cwd_tmp = Path::new("tmp"); let _ = fs::create_dir_all(cwd_tmp);
|
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");
|
let cwd_path = cwd_tmp.join("ny_mir_builder_input.json");
|
||||||
fs::write(&cwd_path, &buf).expect("write cwd tmp json");
|
fs::write(&cwd_path, &buf).expect("write cwd tmp json");
|
||||||
cwd_path
|
cwd_path
|
||||||
} else {
|
} else {
|
||||||
let p = PathBuf::from(matches.get_one::<String>("in").unwrap());
|
let p = PathBuf::from(matches.get_one::<String>("in").unwrap());
|
||||||
if !p.exists() { eprintln!("error: input not found: {}", p.display()); std::process::exit(2); }
|
if !p.exists() {
|
||||||
|
eprintln!("error: input not found: {}", p.display());
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
p
|
p
|
||||||
};
|
};
|
||||||
|
|
||||||
let emit = matches.get_one::<String>("emit").unwrap().as_str();
|
let emit = matches.get_one::<String>("emit").unwrap().as_str();
|
||||||
let out_path = matches.get_one::<String>("out").map(|s| s.to_string()).unwrap_or_else(|| match emit {
|
let out_path = matches
|
||||||
"obj" => format!("{}/target/aot_objects/a.o", std::env::current_dir().unwrap().display()),
|
.get_one::<String>("out")
|
||||||
"ll" => format!("{}/target/aot_objects/a.ll", std::env::current_dir().unwrap().display()),
|
.map(|s| s.to_string())
|
||||||
"exe" => "a.out".to_string(),
|
.unwrap_or_else(|| match emit {
|
||||||
"json" => "/dev/stdout".to_string(),
|
"obj" => format!(
|
||||||
_ => unreachable!(),
|
"{}/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 verify = matches.get_flag("verify_llvm");
|
||||||
let quiet = matches.get_flag("quiet");
|
let quiet = matches.get_flag("quiet");
|
||||||
let nyrt_dir = matches.get_one::<String>("nyrt").map(|s| s.to_string()).unwrap_or("crates/nyrt".to_string());
|
let nyrt_dir = matches
|
||||||
|
.get_one::<String>("nyrt")
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.unwrap_or("crates/nyrt".to_string());
|
||||||
|
|
||||||
// Determine sibling nyash binary path (target dir)
|
// Determine sibling nyash binary path (target dir)
|
||||||
let nyash_bin = current_dir_bin("nyash");
|
let nyash_bin = current_dir_bin("nyash");
|
||||||
@ -56,47 +75,74 @@ fn main() {
|
|||||||
} else {
|
} else {
|
||||||
fs::copy(&in_file, &out_path).expect("copy json");
|
fs::copy(&in_file, &out_path).expect("copy json");
|
||||||
}
|
}
|
||||||
if !quiet { println!("OK json:{}", out_path); }
|
if !quiet {
|
||||||
|
println!("OK json:{}", out_path);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure build dirs
|
// 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 {
|
match emit {
|
||||||
"ll" => {
|
"ll" => {
|
||||||
std::env::set_var("NYASH_LLVM_DUMP_LL", "1");
|
std::env::set_var("NYASH_LLVM_DUMP_LL", "1");
|
||||||
std::env::set_var("NYASH_LLVM_LL_OUT", &out_path);
|
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");
|
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||||
run_nyash_pipe(&nyash_bin, &in_file);
|
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 !Path::new(&out_path).exists() {
|
||||||
if !quiet { println!("OK ll:{}", out_path); }
|
eprintln!("error: failed to produce {}", out_path);
|
||||||
|
std::process::exit(4);
|
||||||
|
}
|
||||||
|
if !quiet {
|
||||||
|
println!("OK ll:{}", out_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"obj" => {
|
"obj" => {
|
||||||
std::env::set_var("NYASH_LLVM_OBJ_OUT", &out_path);
|
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");
|
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||||
// remove stale
|
// remove stale
|
||||||
let _ = fs::remove_file(&out_path);
|
let _ = fs::remove_file(&out_path);
|
||||||
run_nyash_pipe(&nyash_bin, &in_file);
|
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 !Path::new(&out_path).exists() {
|
||||||
if !quiet { println!("OK obj:{}", out_path); }
|
eprintln!("error: failed to produce {}", out_path);
|
||||||
|
std::process::exit(4);
|
||||||
|
}
|
||||||
|
if !quiet {
|
||||||
|
println!("OK obj:{}", out_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"exe" => {
|
"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);
|
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");
|
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||||
let _ = fs::remove_file(&obj_path);
|
let _ = fs::remove_file(&obj_path);
|
||||||
run_nyash_pipe(&nyash_bin, &in_file);
|
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
|
// Link with NyRT
|
||||||
if let Err(e) = link_exe(&obj_path, &out_path, &nyrt_dir) {
|
if let Err(e) = link_exe(&obj_path, &out_path, &nyrt_dir) {
|
||||||
eprintln!("error: link failed: {}", e);
|
eprintln!("error: link failed: {}", e);
|
||||||
std::process::exit(5);
|
std::process::exit(5);
|
||||||
}
|
}
|
||||||
if !quiet { println!("OK exe:{}", out_path); }
|
if !quiet {
|
||||||
|
println!("OK exe:{}", out_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
@ -108,17 +154,23 @@ fn current_dir_bin(name: &str) -> PathBuf {
|
|||||||
if let Ok(cur) = std::env::current_exe() {
|
if let Ok(cur) = std::env::current_exe() {
|
||||||
if let Some(dir) = cur.parent() {
|
if let Some(dir) = cur.parent() {
|
||||||
let cand = dir.join(name);
|
let cand = dir.join(name);
|
||||||
if cand.exists() { return cand; }
|
if cand.exists() {
|
||||||
|
return cand;
|
||||||
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let cand = dir.join(format!("{}.exe", name));
|
let cand = dir.join(format!("{}.exe", name));
|
||||||
if cand.exists() { return cand; }
|
if cand.exists() {
|
||||||
|
return cand;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback to target/release
|
// Fallback to target/release
|
||||||
let cand = PathBuf::from("target/release").join(name);
|
let cand = PathBuf::from("target/release").join(name);
|
||||||
if cand.exists() { return cand; }
|
if cand.exists() {
|
||||||
|
return cand;
|
||||||
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let cand = PathBuf::from("target/release").join(format!("{}.exe", name));
|
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());
|
args.push(obj_path.to_string());
|
||||||
// Provide LIBPATH and library name (prefer nyrt.lib)
|
// Provide LIBPATH and library name (prefer nyrt.lib)
|
||||||
args.push(format!("/LIBPATH:{}", nyrt_release));
|
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
|
// 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())?;
|
let status = PCommand::new("lld-link")
|
||||||
if status.success() { return Ok(()); }
|
.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()));
|
return Err(format!("lld-link failed: status {:?}", status.code()));
|
||||||
}
|
}
|
||||||
if which::which("link").is_ok() {
|
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(format!("/OUT:{}", out_path));
|
||||||
args.push(obj_path.to_string());
|
args.push(obj_path.to_string());
|
||||||
args.push(format!("/LIBPATH:{}", nyrt_release));
|
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() {
|
||||||
let status = PCommand::new("link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?;
|
args.push("nyrt.lib".to_string());
|
||||||
if status.success() { return Ok(()); }
|
}
|
||||||
|
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()));
|
return Err(format!("link.exe failed: status {:?}", status.code()));
|
||||||
}
|
}
|
||||||
// Fallback: try cc with MinGW-like flags
|
// 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([obj_path])
|
||||||
.args(["-L", &format!("{}/target/release", nyrt_dir)])
|
.args(["-L", &format!("{}/target/release", nyrt_dir)])
|
||||||
.args(["-lnyrt", "-o", out_path])
|
.args(["-lnyrt", "-o", out_path])
|
||||||
.status().map_err(|e| e.to_string())?;
|
.status()
|
||||||
if status.success() { return Ok(()); }
|
.map_err(|e| e.to_string())?;
|
||||||
|
if status.success() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
return Err(format!("cc link failed: status {:?}", status.code()));
|
return Err(format!("cc link failed: status {:?}", status.code()));
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[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(["-L", &format!("{}/target/release", nyrt_dir)])
|
||||||
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive"])
|
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive"])
|
||||||
.args(["-lpthread", "-ldl", "-lm", "-o", out_path])
|
.args(["-lpthread", "-ldl", "-lm", "-o", out_path])
|
||||||
.status().map_err(|e| e.to_string())?;
|
.status()
|
||||||
if status.success() { Ok(()) } else { Err(format!("cc failed: status {:?}", status.code())) }
|
.map_err(|e| e.to_string())?;
|
||||||
|
if status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("cc failed: status {:?}", status.code()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,9 @@ fn main() {
|
|||||||
println!("\nTesting box type resolution:");
|
println!("\nTesting box type resolution:");
|
||||||
for box_type in ["StringBox", "FileBox", "MapBox"] {
|
for box_type in ["StringBox", "FileBox", "MapBox"] {
|
||||||
match config.find_library_for_box(box_type) {
|
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),
|
None => println!(" {} -> not found in config", box_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +62,8 @@ fn main() {
|
|||||||
println!("\nReverse host-call (by-slot) quick test:");
|
println!("\nReverse host-call (by-slot) quick test:");
|
||||||
// Create ArrayBox and obtain HostHandle
|
// Create ArrayBox and obtain HostHandle
|
||||||
let mut arr = nyash_rust::boxes::ArrayBox::new();
|
let mut arr = nyash_rust::boxes::ArrayBox::new();
|
||||||
arr.push(Box::new(nyash_rust::box_trait::StringBox::new("init")) as Box<dyn nyash_rust::box_trait::NyashBox>);
|
arr.push(Box::new(nyash_rust::box_trait::StringBox::new("init"))
|
||||||
|
as Box<dyn nyash_rust::box_trait::NyashBox>);
|
||||||
let handle = nyash_rust::runtime::host_handles::to_handle_box(Box::new(arr));
|
let handle = nyash_rust::runtime::host_handles::to_handle_box(Box::new(arr));
|
||||||
// Call Array.set(0, "hello") via slot=101
|
// Call Array.set(0, "hello") via slot=101
|
||||||
let mut tlv = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(2);
|
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");
|
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv, "hello");
|
||||||
let mut out = vec![0u8; 256];
|
let mut out = vec![0u8; 256];
|
||||||
let mut out_len: usize = out.len();
|
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);
|
println!(" set(slot=101) -> code={}, out_len={}", code, out_len);
|
||||||
// Call Array.get(0) via slot=100 and decode
|
// Call Array.get(0) via slot=100 and decode
|
||||||
let mut tlv2 = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(1);
|
let mut tlv2 = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(1);
|
||||||
nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut tlv2, 0);
|
nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut tlv2, 0);
|
||||||
let mut out2 = vec![0u8; 256];
|
let mut out2 = vec![0u8; 256];
|
||||||
let mut out2_len: usize = out2.len();
|
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 code2 == 0 {
|
||||||
if let Some((tag, _sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out2[..out2_len]) {
|
if let Some((tag, _sz, payload)) =
|
||||||
if tag == 6 || tag == 7 { // string/bytes
|
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);
|
let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload);
|
||||||
println!(" get(slot=100) -> tag={}, value='{}'", tag, s);
|
println!(" get(slot=100) -> tag={}, value='{}'", tag, s);
|
||||||
} else if tag == 3 { // i64
|
} else if tag == 3 {
|
||||||
let v = nyash_rust::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or_default();
|
// i64
|
||||||
|
let v = nyash_rust::runtime::plugin_ffi_common::decode::i32(payload)
|
||||||
|
.unwrap_or_default();
|
||||||
println!(" get(slot=100) -> tag={}, i32={}", tag, v);
|
println!(" get(slot=100) -> tag={}, i32={}", tag, v);
|
||||||
} else {
|
} else {
|
||||||
println!(" get(slot=100) -> tag={}, size={}", tag, _sz);
|
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");
|
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_set, "v");
|
||||||
let mut out_s = vec![0u8; 256];
|
let mut out_s = vec![0u8; 256];
|
||||||
let mut out_s_len: usize = out_s.len();
|
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);
|
println!(" set(slot=204) -> code={}, out_len={}", code_s, out_s_len);
|
||||||
// get("k") → slot=203
|
// get("k") → slot=203
|
||||||
let mut tlv_get = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(1);
|
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");
|
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_get, "k");
|
||||||
let mut out_g = vec![0u8; 256];
|
let mut out_g = vec![0u8; 256];
|
||||||
let mut out_g_len: usize = out_g.len();
|
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 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 {
|
if tag == 6 || tag == 7 {
|
||||||
let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload);
|
let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload);
|
||||||
println!(" get(slot=203) -> '{}'", s);
|
println!(" get(slot=203) -> '{}'", s);
|
||||||
@ -123,13 +169,37 @@ fn main() {
|
|||||||
// has("k") → slot=202
|
// has("k") → slot=202
|
||||||
let mut out_hb = vec![0u8; 16];
|
let mut out_hb = vec![0u8; 16];
|
||||||
let mut out_hb_len: usize = out_hb.len();
|
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) };
|
let code_hb = unsafe {
|
||||||
println!(" has(slot=202) -> code={}, out_len={}", code_hb, out_hb_len);
|
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
|
// size() → slot=200
|
||||||
let mut out_sz = vec![0u8; 32];
|
let mut out_sz = vec![0u8; 32];
|
||||||
let mut out_sz_len: usize = out_sz.len();
|
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) };
|
let code_sz = unsafe {
|
||||||
println!(" size(slot=200) -> code={}, out_len={}", code_sz, out_sz_len);
|
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!");
|
println!("\nTest completed!");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,18 @@ pub fn await_max_ms() -> u64 {
|
|||||||
std::env::var("NYASH_AWAIT_MAX_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(5000)
|
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 ----
|
// ---- Phase 11.8 MIR cleanup toggles ----
|
||||||
/// Core-13 minimal MIR mode toggle
|
/// Core-13 minimal MIR mode toggle
|
||||||
/// Default: ON (unless explicitly disabled with NYASH_MIR_CORE13=0)
|
/// Default: ON (unless explicitly disabled with NYASH_MIR_CORE13=0)
|
||||||
|
|||||||
@ -95,7 +95,7 @@ pub(crate) extern "C" fn nyash_plugin_invoke3_i64(type_id: i64, method_id: i64,
|
|||||||
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
|
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
|
||||||
match tag {
|
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); } }
|
3 => { if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } }
|
||||||
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let box_type_name = crate::runtime::plugin_loader_unified::get_global_plugin_host().read().ok().and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())).and_then(|m| m.into_iter().find(|(_k,v)| *v == r_type).map(|(k,_v)| k)).unwrap_or_else(|| "PluginBox".to_string()); let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke.unwrap()); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
|
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let meta_opt = crate::runtime::plugin_loader_v2::metadata_for_type_id(r_type); let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt { (meta.box_type.clone(), meta.invoke_fn) } else { ("PluginBox".to_string(), invoke.unwrap()) }; let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke_ptr); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
|
||||||
1 => { return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; }
|
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; } } }
|
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) {
|
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
|
||||||
match tag {
|
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; } }
|
3 => { if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } }
|
||||||
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type, r_type, r_inst, invoke.unwrap()); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
|
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let meta_opt = crate::runtime::plugin_loader_v2::metadata_for_type_id(r_type); let (meta_box, invoke_ptr) = if let Some(meta) = meta_opt { (meta.box_type, meta.invoke_fn) } else { (box_type.clone(), invoke.unwrap()) }; let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(meta_box, r_type, r_inst, invoke_ptr); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
|
||||||
1 => { return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; }
|
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; } } }
|
5 => { if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") { if _sz == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); let f = f64::from_le_bytes(b); return f as i64; } } }
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@ -9,8 +9,6 @@ use crate::jit::r#extern::collections as c;
|
|||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
use crate::jit::r#extern::host_bridge as hb;
|
use crate::jit::r#extern::host_bridge as hb;
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
use crate::runtime::plugin_loader_unified;
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
|
||||||
use crate::runtime::plugin_loader_v2::PluginBoxV2;
|
use crate::runtime::plugin_loader_v2::PluginBoxV2;
|
||||||
|
|
||||||
// ---- Generic Birth (handle) ----
|
// ---- 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
|
// Map type_id -> type name and create via plugin host; return runtime handle
|
||||||
if type_id <= 0 { return 0; }
|
if type_id <= 0 { return 0; }
|
||||||
let tid = type_id as u32;
|
let tid = type_id as u32;
|
||||||
let name_opt = crate::runtime::plugin_loader_unified::get_global_plugin_host()
|
if let Some(meta) = crate::runtime::plugin_loader_v2::metadata_for_type_id(tid) {
|
||||||
.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 Ok(host) = crate::runtime::get_global_plugin_host().read() {
|
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
|
||||||
if let Ok(b) = host.create_box(&box_type, &[]) {
|
if let Ok(b) = host.create_box(&meta.box_type, &[]) {
|
||||||
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(b);
|
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(b);
|
||||||
let h = crate::jit::rt::handles::to_handle(arc);
|
let h = crate::jit::rt::handles::to_handle(arc);
|
||||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": box_type, "type_id": tid, "handle": h}), "hostcall", "<jit>");
|
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": meta.box_type, "type_id": meta.type_id, "handle": h}), "hostcall", "<jit>");
|
||||||
return h as i64;
|
return h as i64;
|
||||||
} else {
|
} else {
|
||||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "create_failed", "box_type": box_type, "type_id": tid}), "hostcall", "<jit>");
|
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "create_failed", "box_type": meta.box_type, "type_id": meta.type_id}), "hostcall", "<jit>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
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;
|
use crate::runtime::plugin_loader_v2::PluginBoxV2;
|
||||||
if type_id <= 0 { return 0; }
|
if type_id <= 0 { return 0; }
|
||||||
// Resolve invoke for the type by creating a temp instance
|
// Resolve invoke for the type via loader metadata
|
||||||
let mut invoke: Option<unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize)->i32> = None;
|
let Some(meta) = crate::runtime::plugin_loader_v2::metadata_for_type_id(type_id as u32) else {
|
||||||
let mut box_type = String::new();
|
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "type_map_failed", "type_id": type_id}), "hostcall", "<jit>");
|
||||||
if let Some(name) = crate::runtime::plugin_loader_unified::get_global_plugin_host()
|
|
||||||
.read().ok()
|
|
||||||
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
|
|
||||||
.and_then(|m| m.into_iter().find(|(_k,v)| *v == (type_id as u32)).map(|(k,_v)| k))
|
|
||||||
{
|
|
||||||
box_type = name;
|
|
||||||
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
|
|
||||||
if let Ok(b) = host.create_box(&box_type, &[]) {
|
|
||||||
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() { invoke = Some(p.inner.invoke_fn); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if invoke.is_none() {
|
|
||||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "no_invoke", "type_id": type_id}), "hostcall", "<jit>");
|
|
||||||
return 0;
|
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;
|
let method_id: u32 = 0; let instance_id: u32 = 0;
|
||||||
// Build TLV from a1/a2
|
// Build TLV from a1/a2
|
||||||
let nargs = argc.max(0) as usize;
|
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); }
|
if nargs >= 2 { encode_val(a2); }
|
||||||
// Invoke
|
// Invoke
|
||||||
let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len();
|
let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len();
|
||||||
let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) };
|
let rc = unsafe { invoke_fn(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) };
|
||||||
if rc != 0 { events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "invoke_failed", "type_id": type_id}), "hostcall", "<jit>"); return 0; }
|
if rc != 0 { events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "invoke_failed", "type_id": type_id}), "hostcall", "<jit>"); return 0; }
|
||||||
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
|
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
|
||||||
if tag == 8 && payload.len()==8 {
|
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 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 r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i);
|
||||||
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type.clone(), r_type, r_inst, invoke.unwrap());
|
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type.clone(), r_type, r_inst, invoke_fn);
|
||||||
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb);
|
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb);
|
||||||
let h = crate::jit::rt::handles::to_handle(arc);
|
let h = crate::jit::rt::handles::to_handle(arc);
|
||||||
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "box_type": box_type, "type_id": type_id, "argc": nargs, "handle": h}), "hostcall", "<jit>");
|
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "box_type": box_type, "type_id": type_id, "argc": nargs, "handle": h}), "hostcall", "<jit>");
|
||||||
|
|||||||
@ -87,12 +87,16 @@ pub struct MirBuilder {
|
|||||||
/// Top of stack corresponds to the innermost active loop
|
/// Top of stack corresponds to the innermost active loop
|
||||||
pub(super) loop_header_stack: Vec<BasicBlockId>,
|
pub(super) loop_header_stack: Vec<BasicBlockId>,
|
||||||
pub(super) loop_exit_stack: Vec<BasicBlockId>,
|
pub(super) loop_exit_stack: Vec<BasicBlockId>,
|
||||||
|
|
||||||
|
/// Whether PHI emission is disabled (edge-copy mode)
|
||||||
|
pub(super) no_phi_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MirBuilder {
|
impl MirBuilder {
|
||||||
/// Create a new MIR builder
|
/// Create a new MIR builder
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let plugin_method_sigs = plugin_sigs::load_plugin_method_sigs();
|
let plugin_method_sigs = plugin_sigs::load_plugin_method_sigs();
|
||||||
|
let no_phi_mode = crate::config::env::mir_no_phi();
|
||||||
Self {
|
Self {
|
||||||
current_module: None,
|
current_module: None,
|
||||||
current_function: None,
|
current_function: None,
|
||||||
@ -112,6 +116,7 @@ impl MirBuilder {
|
|||||||
include_box_map: HashMap::new(),
|
include_box_map: HashMap::new(),
|
||||||
loop_header_stack: Vec::new(),
|
loop_header_stack: Vec::new(),
|
||||||
loop_exit_stack: Vec::new(),
|
loop_exit_stack: Vec::new(),
|
||||||
|
no_phi_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -652,6 +657,42 @@ impl MirBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn is_no_phi_mode(&self) -> bool {
|
||||||
|
self.no_phi_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a Copy instruction into `block_id`, defining `dst` from `src`.
|
||||||
|
/// Skips blocks that terminate via return/throw, and avoids duplicate copies.
|
||||||
|
pub(super) fn insert_edge_copy(
|
||||||
|
&mut self,
|
||||||
|
block_id: BasicBlockId,
|
||||||
|
dst: ValueId,
|
||||||
|
src: ValueId,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
if let Some(ref mut function) = self.current_function {
|
||||||
|
let block = function
|
||||||
|
.get_block_mut(block_id)
|
||||||
|
.ok_or_else(|| format!("Basic block {} does not exist", block_id))?;
|
||||||
|
if let Some(term) = &block.terminator {
|
||||||
|
if matches!(term, MirInstruction::Return { .. } | MirInstruction::Throw { .. }) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let already_present = block.instructions.iter().any(|inst| {
|
||||||
|
matches!(inst, MirInstruction::Copy { dst: existing_dst, .. } if *existing_dst == dst)
|
||||||
|
});
|
||||||
|
if !already_present {
|
||||||
|
block.add_instruction(MirInstruction::Copy { dst, src });
|
||||||
|
}
|
||||||
|
if let Some(ty) = self.value_types.get(&src).cloned() {
|
||||||
|
self.value_types.insert(dst, ty);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("No current function".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// moved to builder/utils.rs: ensure_block_exists
|
// moved to builder/utils.rs: ensure_block_exists
|
||||||
|
|
||||||
// build_loop_statement_legacy moved to builder/stmts.rs
|
// build_loop_statement_legacy moved to builder/stmts.rs
|
||||||
|
|||||||
@ -40,7 +40,13 @@ impl super::MirBuilder {
|
|||||||
phi_inputs.push((else_block, else_val));
|
phi_inputs.push((else_block, else_val));
|
||||||
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
||||||
self.start_new_block(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);
|
return Ok(result_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +89,13 @@ impl super::MirBuilder {
|
|||||||
|
|
||||||
// Merge and yield result
|
// Merge and yield result
|
||||||
self.start_new_block(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 })?;
|
||||||
|
}
|
||||||
Ok(result_val)
|
Ok(result_val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,6 +73,30 @@ impl MirBuilder {
|
|||||||
let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a));
|
let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a));
|
||||||
let result_val = self.value_gen.next();
|
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() {
|
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 else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
|
||||||
// Resolve branch-end values for the assigned variable
|
// Resolve branch-end values for the assigned variable
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use super::{
|
|||||||
ConstValue
|
ConstValue
|
||||||
};
|
};
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
/// 不完全なPhi nodeの情報
|
/// 不完全なPhi nodeの情報
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -40,6 +40,9 @@ pub struct LoopBuilder<'a> {
|
|||||||
|
|
||||||
/// continue文からの変数スナップショット
|
/// continue文からの変数スナップショット
|
||||||
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
|
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
|
||||||
|
|
||||||
|
/// PHI を生成しないモードかどうか
|
||||||
|
no_phi_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local copy: detect a variable name assigned within an AST fragment
|
// Local copy: detect a variable name assigned within an AST fragment
|
||||||
@ -68,12 +71,14 @@ fn extract_assigned_var_local(ast: &ASTNode) -> Option<String> {
|
|||||||
impl<'a> LoopBuilder<'a> {
|
impl<'a> LoopBuilder<'a> {
|
||||||
/// 新しいループビルダーを作成
|
/// 新しいループビルダーを作成
|
||||||
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
|
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
|
||||||
|
let no_phi_mode = parent.is_no_phi_mode();
|
||||||
Self {
|
Self {
|
||||||
parent_builder: parent,
|
parent_builder: parent,
|
||||||
incomplete_phis: HashMap::new(),
|
incomplete_phis: HashMap::new(),
|
||||||
block_var_maps: HashMap::new(),
|
block_var_maps: HashMap::new(),
|
||||||
loop_header: None,
|
loop_header: None,
|
||||||
continue_snapshots: Vec::new(),
|
continue_snapshots: Vec::new(),
|
||||||
|
no_phi_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,6 +180,10 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
|
|
||||||
incomplete_phis.push(incomplete_phi);
|
incomplete_phis.push(incomplete_phi);
|
||||||
|
|
||||||
|
if self.no_phi_mode {
|
||||||
|
self.parent_builder.insert_edge_copy(preheader_id, phi_id, value_before)?;
|
||||||
|
}
|
||||||
|
|
||||||
// 変数マップを更新(Phi nodeの結果を使用)
|
// 変数マップを更新(Phi nodeの結果を使用)
|
||||||
self.update_variable(var_name.clone(), phi_id);
|
self.update_variable(var_name.clone(), phi_id);
|
||||||
}
|
}
|
||||||
@ -208,7 +217,16 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
|
|
||||||
phi.known_inputs.push((latch_id, value_after));
|
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<BasicBlockId> = HashSet::new();
|
||||||
|
for &(pred, val) in &phi.known_inputs {
|
||||||
|
if seen.insert(pred) {
|
||||||
|
self.parent_builder.insert_edge_copy(pred, phi.phi_id, val)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?;
|
||||||
|
}
|
||||||
self.update_variable(phi.var_name.clone(), phi.phi_id);
|
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) {
|
if let (Some(tv), Some(ev)) = (then_value_for_var, else_value_for_var) {
|
||||||
let phi_id = self.new_value();
|
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
|
// 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 = pre_if_var_map.clone();
|
||||||
self.parent_builder.variable_map.insert(var_name, phi_id);
|
self.parent_builder.variable_map.insert(var_name, phi_id);
|
||||||
|
|||||||
@ -428,6 +428,11 @@ impl MirVerifier {
|
|||||||
|
|
||||||
/// Verify SSA form properties
|
/// Verify SSA form properties
|
||||||
fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||||
|
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
|
||||||
|
if crate::config::env::verify_allow_no_phi()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let mut definitions = HashMap::new();
|
let mut definitions = HashMap::new();
|
||||||
|
|
||||||
@ -470,6 +475,11 @@ impl MirVerifier {
|
|||||||
|
|
||||||
/// Verify dominance relations (def must dominate use across blocks)
|
/// Verify dominance relations (def must dominate use across blocks)
|
||||||
fn verify_dominance(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
fn verify_dominance(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||||
|
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
|
||||||
|
if crate::config::env::verify_allow_no_phi()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
// Build def -> block map and dominators
|
// 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.
|
/// 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.
|
/// In merge blocks, values coming from predecessors must be routed through Phi.
|
||||||
fn verify_merge_uses(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
fn verify_merge_uses(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||||
|
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
|
||||||
|
if crate::config::env::verify_allow_no_phi()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let preds = self.compute_predecessors(function);
|
let preds = self.compute_predecessors(function);
|
||||||
let def_block = self.compute_def_blocks(function);
|
let def_block = self.compute_def_blocks(function);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod array_state_sharing_tests {
|
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::interpreter::NyashInterpreter;
|
||||||
use nyash_rust::parser::NyashParser;
|
use nyash_rust::parser::NyashParser;
|
||||||
use nyash_rust::boxes::array::ArrayBox;
|
|
||||||
use nyash_rust::box_trait::{NyashBox, IntegerBox, StringBox};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arraybox_state_sharing_bug_fix() {
|
fn test_arraybox_state_sharing_bug_fix() {
|
||||||
@ -25,7 +25,7 @@ mod array_state_sharing_tests {
|
|||||||
let ast = NyashParser::parse_from_string(program).unwrap();
|
let ast = NyashParser::parse_from_string(program).unwrap();
|
||||||
let result = interpreter.execute(ast).unwrap();
|
let result = interpreter.execute(ast).unwrap();
|
||||||
let int_result = result.as_any().downcast_ref::<IntegerBox>().unwrap();
|
let int_result = result.as_any().downcast_ref::<IntegerBox>().unwrap();
|
||||||
assert_eq!(int_result.value, 1); // 🎯 0ではなく1を返すべき
|
assert_eq!(int_result.value, 1); // 🎯 0ではなく1を返すべき
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -36,15 +36,15 @@ mod array_state_sharing_tests {
|
|||||||
// share_box: 状態共有
|
// share_box: 状態共有
|
||||||
let arr2 = arr1.share_box();
|
let arr2 = arr1.share_box();
|
||||||
let arr2_array = arr2.as_any().downcast_ref::<ArrayBox>().unwrap();
|
let arr2_array = arr2.as_any().downcast_ref::<ArrayBox>().unwrap();
|
||||||
assert_eq!(arr2_array.len(), 1); // 共有されている
|
assert_eq!(arr2_array.len(), 1); // 共有されている
|
||||||
|
|
||||||
// clone_box: 独立
|
// clone_box: 独立
|
||||||
let arr3 = arr1.clone_box();
|
let arr3 = arr1.clone_box();
|
||||||
let arr3_array = arr3.as_any().downcast_ref::<ArrayBox>().unwrap();
|
let arr3_array = arr3.as_any().downcast_ref::<ArrayBox>().unwrap();
|
||||||
arr1.push(Box::new(StringBox::new("world")));
|
arr1.push(Box::new(StringBox::new("world")));
|
||||||
assert_eq!(arr3_array.len(), 1); // 影響を受けない
|
assert_eq!(arr3_array.len(), 1); // 影響を受けない
|
||||||
assert_eq!(arr1.len(), 2); // 元は2要素
|
assert_eq!(arr1.len(), 2); // 元は2要素
|
||||||
assert_eq!(arr2_array.len(), 2); // 共有されているので2要素
|
assert_eq!(arr2_array.len(), 2); // 共有されているので2要素
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -68,6 +68,6 @@ mod array_state_sharing_tests {
|
|||||||
let ast = NyashParser::parse_from_string(program).unwrap();
|
let ast = NyashParser::parse_from_string(program).unwrap();
|
||||||
let result = interpreter.execute(ast).unwrap();
|
let result = interpreter.execute(ast).unwrap();
|
||||||
let int_result = result.as_any().downcast_ref::<IntegerBox>().unwrap();
|
let int_result = result.as_any().downcast_ref::<IntegerBox>().unwrap();
|
||||||
assert_eq!(int_result.value, 3); // 3要素が正しく保持されるべき
|
assert_eq!(int_result.value, 3); // 3要素が正しく保持されるべき
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
|
|
||||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||||
|
|
||||||
use nyash_rust::parser::NyashParser;
|
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::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::runtime::PluginConfig;
|
||||||
|
|
||||||
fn try_init_plugins() -> bool {
|
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") {
|
if let Err(e) = init_global_loader_v2("nyash.toml") {
|
||||||
eprintln!("[e2e] init_global_loader_v2 failed: {:?}", e);
|
eprintln!("[e2e] init_global_loader_v2 failed: {:?}", e);
|
||||||
return false;
|
return false;
|
||||||
@ -18,18 +19,24 @@ fn try_init_plugins() -> bool {
|
|||||||
if let Some(conf) = &loader.config {
|
if let Some(conf) = &loader.config {
|
||||||
let mut map = std::collections::HashMap::new();
|
let mut map = std::collections::HashMap::new();
|
||||||
for (lib_name, lib_def) in &conf.libraries {
|
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();
|
let reg = get_global_registry();
|
||||||
reg.apply_plugin_config(&PluginConfig { plugins: map });
|
reg.apply_plugin_config(&PluginConfig { plugins: map });
|
||||||
true
|
true
|
||||||
} else { false }
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin loader semantics: counter handle parity pending"]
|
#[ignore = "MIR13/plugin loader semantics: counter handle parity pending"]
|
||||||
fn e2e_counter_basic_inc_get() {
|
fn e2e_counter_basic_inc_get() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local c, v1, v2
|
local c, v1, v2
|
||||||
@ -54,7 +61,9 @@ v2
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin loader semantics: assignment sharing parity pending"]
|
#[ignore = "MIR13/plugin loader semantics: assignment sharing parity pending"]
|
||||||
fn e2e_counter_assignment_shares_handle() {
|
fn e2e_counter_assignment_shares_handle() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local c, x, v
|
local c, x, v
|
||||||
@ -79,7 +88,9 @@ v
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin loader semantics: MapBox shared handle parity pending"]
|
#[ignore = "MIR13/plugin loader semantics: MapBox shared handle parity pending"]
|
||||||
fn e2e_counter_mapbox_shares_handle() {
|
fn e2e_counter_mapbox_shares_handle() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local c, m, v
|
local c, m, v
|
||||||
|
|||||||
@ -2,84 +2,173 @@
|
|||||||
//! E2E test for unified registry with a mock plugin factory
|
//! E2E test for unified registry with a mock plugin factory
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use nyash_rust::box_factory::BoxFactory;
|
|
||||||
use nyash_rust::box_factory::builtin::BuiltinGroups;
|
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::runtime::NyashRuntimeBuilder;
|
||||||
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
|
|
||||||
|
|
||||||
// ---------- Mock plugin boxes ----------
|
// ---------- Mock plugin boxes ----------
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[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 {
|
impl BoxCore for EchoBox {
|
||||||
fn box_id(&self) -> u64 { self.base.id }
|
fn box_id(&self) -> u64 {
|
||||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
self.base.id
|
||||||
|
}
|
||||||
|
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||||
|
None
|
||||||
|
}
|
||||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "EchoBox(\"{}\")", self.msg)
|
write!(f, "EchoBox(\"{}\")", self.msg)
|
||||||
}
|
}
|
||||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
self
|
||||||
|
}
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NyashBox for EchoBox {
|
impl NyashBox for EchoBox {
|
||||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.msg.clone()) }
|
fn to_string_box(&self) -> StringBox {
|
||||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
StringBox::new(self.msg.clone())
|
||||||
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() { BoolBox::new(self.msg == e.msg) } else { BoolBox::new(false) }
|
}
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||||
|
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() {
|
||||||
|
BoolBox::new(self.msg == e.msg)
|
||||||
|
} else {
|
||||||
|
BoolBox::new(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
"EchoBox"
|
||||||
|
}
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
fn type_name(&self) -> &'static str { "EchoBox" }
|
|
||||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct AdderBox { base: BoxBase, sum: i64 }
|
struct AdderBox {
|
||||||
impl AdderBox { fn new(a: i64, b: i64) -> Self { Self { base: BoxBase::new(), sum: a + b } } }
|
base: BoxBase,
|
||||||
|
sum: i64,
|
||||||
|
}
|
||||||
|
impl AdderBox {
|
||||||
|
fn new(a: i64, b: i64) -> Self {
|
||||||
|
Self {
|
||||||
|
base: BoxBase::new(),
|
||||||
|
sum: a + b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BoxCore for AdderBox {
|
impl BoxCore for AdderBox {
|
||||||
fn box_id(&self) -> u64 { self.base.id }
|
fn box_id(&self) -> u64 {
|
||||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
self.base.id
|
||||||
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 parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
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 {
|
impl NyashBox for AdderBox {
|
||||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.sum.to_string()) }
|
fn to_string_box(&self) -> StringBox {
|
||||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
StringBox::new(self.sum.to_string())
|
||||||
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() { BoolBox::new(self.sum == a.sum) } else { BoolBox::new(false) }
|
}
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||||
|
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() {
|
||||||
|
BoolBox::new(self.sum == a.sum)
|
||||||
|
} else {
|
||||||
|
BoolBox::new(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
"AdderBox"
|
||||||
|
}
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
fn type_name(&self) -> &'static str { "AdderBox" }
|
|
||||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Mock plugin factory ----------
|
// ---------- Mock plugin factory ----------
|
||||||
|
|
||||||
struct TestPluginFactory;
|
struct TestPluginFactory;
|
||||||
impl TestPluginFactory { fn new() -> Self { Self } }
|
impl TestPluginFactory {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BoxFactory for TestPluginFactory {
|
impl BoxFactory for TestPluginFactory {
|
||||||
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
fn create_box(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
args: &[Box<dyn NyashBox>],
|
||||||
|
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
match name {
|
match name {
|
||||||
"EchoBox" => {
|
"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)))
|
Ok(Box::new(EchoBox::new(msg)))
|
||||||
}
|
}
|
||||||
"AdderBox" => {
|
"AdderBox" => {
|
||||||
if args.len() != 2 { return Err(RuntimeError::InvalidOperation{ message: format!("AdderBox expects 2 args, got {}", args.len()) }); }
|
if args.len() != 2 {
|
||||||
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?;
|
return Err(RuntimeError::InvalidOperation {
|
||||||
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg b must be int".into() })?;
|
message: format!("AdderBox expects 2 args, got {}", args.len()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| {
|
||||||
|
RuntimeError::TypeError {
|
||||||
|
message: "AdderBox arg a must be int".into(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| {
|
||||||
|
RuntimeError::TypeError {
|
||||||
|
message: "AdderBox arg b must be int".into(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
Ok(Box::new(AdderBox::new(a, b)))
|
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 ----------
|
// ---------- E2E tests ----------
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
|
|
||||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
#![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::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 {
|
fn try_init_plugins() -> bool {
|
||||||
if !std::path::Path::new("nyash.toml").exists() {
|
if !std::path::Path::new("nyash.toml").exists() {
|
||||||
@ -39,7 +38,9 @@ fn try_init_plugins() -> bool {
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "Plugins not configured by default env; skip in CI"]
|
#[ignore = "Plugins not configured by default env; skip in CI"]
|
||||||
fn e2e_interpreter_plugin_filebox_close_void() {
|
fn e2e_interpreter_plugin_filebox_close_void() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local f
|
local f
|
||||||
@ -67,7 +68,9 @@ f.close()
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin FileBox: delegation via from Base.birth/close pending"]
|
#[ignore = "MIR13/plugin FileBox: delegation via from Base.birth/close pending"]
|
||||||
fn e2e_interpreter_plugin_filebox_delegation() {
|
fn e2e_interpreter_plugin_filebox_delegation() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
box LoggingFileBox from FileBox {
|
box LoggingFileBox from FileBox {
|
||||||
@ -107,7 +110,9 @@ lf.close()
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "Plugins not configured by default env; skip in CI"]
|
#[ignore = "Plugins not configured by default env; skip in CI"]
|
||||||
fn e2e_vm_plugin_filebox_close_void() {
|
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() {
|
if nyash_rust::config::env::mir_core13_pure() {
|
||||||
eprintln!("[E2E] skip vm filebox under Core-13 pure mode");
|
eprintln!("[E2E] skip vm filebox under Core-13 pure mode");
|
||||||
return;
|
return;
|
||||||
@ -124,7 +129,9 @@ f.close()
|
|||||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||||
let mut vm = VM::with_runtime(runtime);
|
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
|
// close() is void; ensure result is void
|
||||||
assert_eq!(result.to_string_box().value, "void");
|
assert_eq!(result.to_string_box().value, "void");
|
||||||
}
|
}
|
||||||
@ -132,7 +139,9 @@ f.close()
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin FileBox: VM open/rw/read path pending parity"]
|
#[ignore = "MIR13/plugin FileBox: VM open/rw/read path pending parity"]
|
||||||
fn e2e_vm_plugin_filebox_open_rw() {
|
fn e2e_vm_plugin_filebox_open_rw() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Open, write, read via VM backend
|
// Open, write, read via VM backend
|
||||||
let code = r#"
|
let code = r#"
|
||||||
@ -149,19 +158,24 @@ data
|
|||||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||||
let mut vm = VM::with_runtime(runtime);
|
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");
|
assert_eq!(result.to_string_box().value, "HELLO");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin FileBox: VM copyFrom(handle) path pending parity"]
|
#[ignore = "MIR13/plugin FileBox: VM copyFrom(handle) path pending parity"]
|
||||||
fn e2e_vm_plugin_filebox_copy_from_handle() {
|
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 p1 = "./test_out_src.txt";
|
||||||
let p2 = "./test_out_dst.txt";
|
let p2 = "./test_out_dst.txt";
|
||||||
|
|
||||||
let code = format!(r#"
|
let code = format!(
|
||||||
|
r#"
|
||||||
local a, b, data
|
local a, b, data
|
||||||
a = new FileBox()
|
a = new FileBox()
|
||||||
b = new FileBox()
|
b = new FileBox()
|
||||||
@ -171,28 +185,35 @@ a.write("HELLO")
|
|||||||
b.copyFrom(a)
|
b.copyFrom(a)
|
||||||
data = b.read()
|
data = b.read()
|
||||||
data
|
data
|
||||||
"#, p1, p2);
|
"#,
|
||||||
|
p1, p2
|
||||||
|
);
|
||||||
|
|
||||||
let ast = NyashParser::parse_from_string(&code).expect("parse failed");
|
let ast = NyashParser::parse_from_string(&code).expect("parse failed");
|
||||||
let runtime = NyashRuntime::new();
|
let runtime = NyashRuntime::new();
|
||||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||||
let mut vm = VM::with_runtime(runtime);
|
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");
|
assert_eq!(result.to_string_box().value, "HELLO");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin FileBox: interpreter copyFrom(handle) path pending parity"]
|
#[ignore = "MIR13/plugin FileBox: interpreter copyFrom(handle) path pending parity"]
|
||||||
fn e2e_interpreter_plugin_filebox_copy_from_handle() {
|
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
|
// Prepare two files and copy contents via plugin Handle argument
|
||||||
let p1 = "./test_out_src.txt";
|
let p1 = "./test_out_src.txt";
|
||||||
let p2 = "./test_out_dst.txt";
|
let p2 = "./test_out_dst.txt";
|
||||||
|
|
||||||
// Nyash program: open two FileBox, write to src, copy to dst via copyFrom, then read dst
|
// 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
|
local a, b, data
|
||||||
a = new FileBox()
|
a = new FileBox()
|
||||||
b = new FileBox()
|
b = new FileBox()
|
||||||
@ -202,7 +223,9 @@ a.write("HELLO")
|
|||||||
b.copyFrom(a)
|
b.copyFrom(a)
|
||||||
data = b.read()
|
data = b.read()
|
||||||
data
|
data
|
||||||
"#, p1, p2);
|
"#,
|
||||||
|
p1, p2
|
||||||
|
);
|
||||||
|
|
||||||
let ast = NyashParser::parse_from_string(&code).expect("parse failed");
|
let ast = NyashParser::parse_from_string(&code).expect("parse failed");
|
||||||
let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new();
|
let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new();
|
||||||
|
|||||||
@ -2,46 +2,93 @@
|
|||||||
//! E2E: Interop between builtin and mock plugin boxes via MapBox storage
|
//! E2E: Interop between builtin and mock plugin boxes via MapBox storage
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use nyash_rust::box_factory::BoxFactory;
|
|
||||||
use nyash_rust::box_factory::builtin::BuiltinGroups;
|
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::interpreter::{NyashInterpreter, RuntimeError};
|
||||||
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct EchoBox { base: BoxBase, msg: String }
|
struct EchoBox {
|
||||||
impl EchoBox { fn new(msg: String) -> Self { Self { base: BoxBase::new(), msg } } }
|
base: BoxBase,
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
impl EchoBox {
|
||||||
|
fn new(msg: String) -> Self {
|
||||||
|
Self {
|
||||||
|
base: BoxBase::new(),
|
||||||
|
msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BoxCore for EchoBox {
|
impl BoxCore for EchoBox {
|
||||||
fn box_id(&self) -> u64 { self.base.id }
|
fn box_id(&self) -> u64 {
|
||||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
self.base.id
|
||||||
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 parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
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 {
|
impl NyashBox for EchoBox {
|
||||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.msg.clone()) }
|
fn to_string_box(&self) -> StringBox {
|
||||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
StringBox::new(self.msg.clone())
|
||||||
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() { BoolBox::new(self.msg == e.msg) } else { BoolBox::new(false) }
|
}
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||||
|
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() {
|
||||||
|
BoolBox::new(self.msg == e.msg)
|
||||||
|
} else {
|
||||||
|
BoolBox::new(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
"EchoBox"
|
||||||
|
}
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
fn type_name(&self) -> &'static str { "EchoBox" }
|
|
||||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TestPluginFactory;
|
struct TestPluginFactory;
|
||||||
impl TestPluginFactory { fn new() -> Self { Self } }
|
impl TestPluginFactory {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
impl BoxFactory for TestPluginFactory {
|
impl BoxFactory for TestPluginFactory {
|
||||||
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
fn create_box(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
args: &[Box<dyn NyashBox>],
|
||||||
|
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
match name {
|
match name {
|
||||||
"EchoBox" => {
|
"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)))
|
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 {
|
fn new_interpreter_with_factory() -> NyashInterpreter {
|
||||||
|
|||||||
@ -1,24 +1,34 @@
|
|||||||
|
|
||||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
#![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::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 {
|
fn try_init_plugins() -> bool {
|
||||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
if !std::path::Path::new("nyash.toml").exists() {
|
||||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
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 = get_global_loader_v2();
|
||||||
let loader = loader.read().unwrap();
|
let loader = loader.read().unwrap();
|
||||||
if let Some(conf) = &loader.config {
|
if let Some(conf) = &loader.config {
|
||||||
let mut map = std::collections::HashMap::new();
|
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 });
|
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||||
true
|
true
|
||||||
} else { false }
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -26,7 +36,9 @@ fn try_init_plugins() -> bool {
|
|||||||
fn e2e_http_two_servers_parallel() {
|
fn e2e_http_two_servers_parallel() {
|
||||||
std::env::set_var("NYASH_NET_LOG", "1");
|
std::env::set_var("NYASH_NET_LOG", "1");
|
||||||
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local s1, s2, c, r1, r2, resp1, resp2, req1, req2, p1, p2, x, y
|
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() {
|
fn e2e_http_long_body_and_headers() {
|
||||||
std::env::set_var("NYASH_NET_LOG", "1");
|
std::env::set_var("NYASH_NET_LOG", "1");
|
||||||
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local s, c, r, resp, q, body, hv
|
local s, c, r, resp, q, body, hv
|
||||||
@ -106,13 +120,14 @@ hv + ":" + body
|
|||||||
assert!(s.contains("OK-LONG"));
|
assert!(s.contains("OK-LONG"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin Net: client error result semantics pending"]
|
#[ignore = "MIR13/plugin Net: client error result semantics pending"]
|
||||||
fn e2e_vm_http_client_error_result() {
|
fn e2e_vm_http_client_error_result() {
|
||||||
std::env::set_var("NYASH_NET_LOG", "1");
|
std::env::set_var("NYASH_NET_LOG", "1");
|
||||||
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
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
|
// No server on 8099 → should produce Err result
|
||||||
let code = r#"
|
let code = r#"
|
||||||
@ -133,7 +148,9 @@ result
|
|||||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||||
let mut vm = VM::with_runtime(runtime);
|
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;
|
let s = result.to_string_box().value;
|
||||||
assert!(s.contains("Error") || s.contains("unexpected_ok") == false);
|
assert!(s.contains("Error") || s.contains("unexpected_ok") == false);
|
||||||
}
|
}
|
||||||
@ -143,7 +160,9 @@ result
|
|||||||
fn e2e_vm_http_empty_body() {
|
fn e2e_vm_http_empty_body() {
|
||||||
std::env::set_var("NYASH_NET_LOG", "1");
|
std::env::set_var("NYASH_NET_LOG", "1");
|
||||||
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local srv, cli, r, resp, req, body
|
local srv, cli, r, resp, req, body
|
||||||
@ -169,6 +188,8 @@ body
|
|||||||
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
let mut compiler = nyash_rust::mir::MirCompiler::new();
|
||||||
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
||||||
let mut vm = VM::with_runtime(runtime);
|
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, "");
|
assert_eq!(result.to_string_box().value, "");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,40 @@
|
|||||||
|
|
||||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||||
|
|
||||||
use nyash_rust::parser::NyashParser;
|
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::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::runtime::PluginConfig;
|
||||||
|
|
||||||
fn try_init_plugins() -> bool {
|
fn try_init_plugins() -> bool {
|
||||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
if !std::path::Path::new("nyash.toml").exists() {
|
||||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
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 = get_global_loader_v2();
|
||||||
let loader = loader.read().unwrap();
|
let loader = loader.read().unwrap();
|
||||||
if let Some(conf) = &loader.config {
|
if let Some(conf) = &loader.config {
|
||||||
let mut map = std::collections::HashMap::new();
|
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 });
|
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||||
true
|
true
|
||||||
} else { false }
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin singleton: CounterBox shared instance parity pending"]
|
#[ignore = "MIR13/plugin singleton: CounterBox shared instance parity pending"]
|
||||||
fn e2e_counterbox_singleton_shared_across_news() {
|
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
|
// CounterBox is configured as singleton in nyash.toml
|
||||||
let code = r#"
|
let code = r#"
|
||||||
|
|||||||
@ -1,28 +1,42 @@
|
|||||||
|
|
||||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||||
|
|
||||||
use nyash_rust::parser::NyashParser;
|
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::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;
|
use nyash_rust::runtime::PluginConfig;
|
||||||
|
|
||||||
fn try_init_plugins() -> bool {
|
fn try_init_plugins() -> bool {
|
||||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
if !std::path::Path::new("nyash.toml").exists() {
|
||||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
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 = get_global_loader_v2();
|
||||||
let loader = loader.read().unwrap();
|
let loader = loader.read().unwrap();
|
||||||
if let Some(conf) = &loader.config {
|
if let Some(conf) = &loader.config {
|
||||||
let mut map = std::collections::HashMap::new();
|
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 });
|
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||||
true
|
true
|
||||||
} else { false }
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin singleton: shutdown/recreate parity pending"]
|
#[ignore = "MIR13/plugin singleton: shutdown/recreate parity pending"]
|
||||||
fn e2e_singleton_shutdown_and_recreate() {
|
fn e2e_singleton_shutdown_and_recreate() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Use CounterBox singleton and bump to 1
|
// Use CounterBox singleton and bump to 1
|
||||||
let code1 = r#"
|
let code1 = r#"
|
||||||
|
|||||||
@ -1,28 +1,40 @@
|
|||||||
|
|
||||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||||
|
|
||||||
use nyash_rust::parser::NyashParser;
|
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::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::runtime::PluginConfig;
|
||||||
|
|
||||||
fn try_init_plugins() -> bool {
|
fn try_init_plugins() -> bool {
|
||||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
if !std::path::Path::new("nyash.toml").exists() {
|
||||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
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 = get_global_loader_v2();
|
||||||
let loader = loader.read().unwrap();
|
let loader = loader.read().unwrap();
|
||||||
if let Some(conf) = &loader.config {
|
if let Some(conf) = &loader.config {
|
||||||
let mut map = std::collections::HashMap::new();
|
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 });
|
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||||
true
|
true
|
||||||
} else { false }
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin Socket: ping/pong parity pending (start method)"]
|
#[ignore = "MIR13/plugin Socket: ping/pong parity pending (start method)"]
|
||||||
fn e2e_socket_ping_pong() {
|
fn e2e_socket_ping_pong() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Start server, client connect, ping/pong
|
// Start server, client connect, ping/pong
|
||||||
let code = r#"
|
let code = r#"
|
||||||
@ -52,7 +64,9 @@ r
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin Socket: accept/recv timeout parity pending (start method)"]
|
#[ignore = "MIR13/plugin Socket: accept/recv timeout parity pending (start method)"]
|
||||||
fn e2e_socket_accept_timeout_and_recv_timeout() {
|
fn e2e_socket_accept_timeout_and_recv_timeout() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local ss, sc, c, s, r
|
local ss, sc, c, s, r
|
||||||
|
|||||||
@ -2,49 +2,92 @@
|
|||||||
//! E2E: Reserved-name guard for unified registry
|
//! E2E: Reserved-name guard for unified registry
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use nyash_rust::box_factory::BoxFactory;
|
|
||||||
use nyash_rust::box_factory::builtin::BuiltinGroups;
|
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::NyashInterpreter;
|
||||||
use nyash_rust::interpreter::RuntimeError;
|
use nyash_rust::interpreter::RuntimeError;
|
||||||
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
|
|
||||||
|
|
||||||
// Dummy factory that tries to claim reserved core types
|
// Dummy factory that tries to claim reserved core types
|
||||||
struct BadFactory;
|
struct BadFactory;
|
||||||
impl BadFactory { fn new() -> Self { Self } }
|
impl BadFactory {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct FakeStringBox { base: BoxBase, inner: String }
|
struct FakeStringBox {
|
||||||
|
base: BoxBase,
|
||||||
|
inner: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl BoxCore for FakeStringBox {
|
impl BoxCore for FakeStringBox {
|
||||||
fn box_id(&self) -> u64 { self.base.id }
|
fn box_id(&self) -> u64 {
|
||||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
self.base.id
|
||||||
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 parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
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 {
|
impl NyashBox for FakeStringBox {
|
||||||
fn to_string_box(&self) -> StringBox { StringBox::new(format!("FAKE:{}", self.inner)) }
|
fn to_string_box(&self) -> StringBox {
|
||||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
StringBox::new(format!("FAKE:{}", self.inner))
|
||||||
if let Some(s) = other.as_any().downcast_ref::<FakeStringBox>() { BoolBox::new(self.inner == s.inner) } else { BoolBox::new(false) }
|
}
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||||
|
if let Some(s) = other.as_any().downcast_ref::<FakeStringBox>() {
|
||||||
|
BoolBox::new(self.inner == s.inner)
|
||||||
|
} else {
|
||||||
|
BoolBox::new(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
"StringBox"
|
||||||
|
}
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
fn type_name(&self) -> &'static str { "StringBox" }
|
|
||||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoxFactory for BadFactory {
|
impl BoxFactory for BadFactory {
|
||||||
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
fn create_box(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
args: &[Box<dyn NyashBox>],
|
||||||
|
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
match name {
|
match name {
|
||||||
// Attempt to hijack StringBox
|
// Attempt to hijack StringBox
|
||||||
"StringBox" => {
|
"StringBox" => {
|
||||||
let s = args.get(0).map(|a| a.to_string_box().value).unwrap_or_default();
|
let s = args
|
||||||
Ok(Box::new(FakeStringBox { base: BoxBase::new(), inner: s }))
|
.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]
|
#[test]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
use nyash_rust::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
|
||||||
use nyash_rust::grammar::engine;
|
use nyash_rust::grammar::engine;
|
||||||
use nyash_rust::box_trait::{StringBox, IntegerBox, BoolBox, VoidBox, NyashBox};
|
|
||||||
|
|
||||||
fn classify_value(b: &dyn NyashBox) -> &'static str {
|
fn classify_value(b: &dyn NyashBox) -> &'static str {
|
||||||
if nyash_rust::runtime::semantics::coerce_to_string(b).is_some() {
|
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:
|
// Mirror current interpreter semantics succinctly:
|
||||||
// 1) If either is string-like => String
|
// 1) If either is string-like => String
|
||||||
if nyash_rust::runtime::semantics::coerce_to_string(left).is_some()
|
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";
|
return "String";
|
||||||
}
|
}
|
||||||
// 2) If both are i64-coercible => Integer
|
// 2) If both are i64-coercible => Integer
|
||||||
if nyash_rust::runtime::semantics::coerce_to_i64(left).is_some()
|
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";
|
return "Integer";
|
||||||
}
|
}
|
||||||
// 3) Otherwise error(ここでは Error として表現)
|
// 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);
|
let expect = eng.decide_add_result(lty, rty).map(|(res, _)| res);
|
||||||
if let Some(res) = expect {
|
if let Some(res) = expect {
|
||||||
if actual == "Error" {
|
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 {
|
} 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 {
|
} else {
|
||||||
assert_eq!(actual, "Error", "grammar has no rule for {}+{}, but actual => {}", li, ri, actual);
|
assert_eq!(
|
||||||
|
actual, "Error",
|
||||||
|
"grammar has no rule for {}+{}, but actual => {}",
|
||||||
|
li, ri, actual
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,14 +6,22 @@ fn grammar_sub_mul_div_rules_exist_and_basic_cases() {
|
|||||||
|
|
||||||
// Sub
|
// Sub
|
||||||
assert!(!eng.sub_rules().is_empty(), "sub rules should not be empty");
|
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
|
// Mul
|
||||||
assert!(!eng.mul_rules().is_empty(), "mul rules should not be empty");
|
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
|
// Div
|
||||||
assert!(!eng.div_rules().is_empty(), "div rules should not be empty");
|
assert!(!eng.div_rules().is_empty(), "div rules should not be empty");
|
||||||
assert!(eng.decide_div_result("Integer","Integer").is_some(), "div i64/i64 should be defined");
|
assert!(
|
||||||
|
eng.decide_div_result("Integer", "Integer").is_some(),
|
||||||
|
"div i64/i64 should be defined"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,9 @@ fn get_variable_value(code: &str, var_name: &str) -> Result<String, String> {
|
|||||||
match parser::NyashParser::parse_from_string(code) {
|
match parser::NyashParser::parse_from_string(code) {
|
||||||
Ok(ast) => {
|
Ok(ast) => {
|
||||||
let mut interpreter = interpreter::NyashInterpreter::new();
|
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) {
|
match interpreter.get_variable(var_name) {
|
||||||
Ok(value) => Ok(value.to_string_box().value),
|
Ok(value) => Ok(value.to_string_box().value),
|
||||||
@ -350,7 +352,10 @@ mod integration_tests {
|
|||||||
assert_eq!(get_variable_value(code, "less_result").unwrap(), "true");
|
assert_eq!(get_variable_value(code, "less_result").unwrap(), "true");
|
||||||
assert_eq!(get_variable_value(code, "greater_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, "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]
|
#[test]
|
||||||
|
|||||||
10
tests/json_v0_stage3/break_continue.json
Normal file
10
tests/json_v0_stage3/break_continue.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{"version":0,"kind":"Program","body":[
|
||||||
|
{"type":"Local","name":"i","expr":{"type":"Int","value":0}},
|
||||||
|
{"type":"Loop","cond":{"type":"Bool","value":true},"body":[
|
||||||
|
{"type":"Expr","expr":{"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}}},
|
||||||
|
{"type":"If","cond":{"type":"Compare","op":"==","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":3}},
|
||||||
|
"then":[{"type":"Break"}]},
|
||||||
|
{"type":"Continue"}
|
||||||
|
]},
|
||||||
|
{"type":"Return","expr":{"type":"Var","name":"i"}}
|
||||||
|
]}
|
||||||
4
tests/json_v0_stage3/throw_basic.json
Normal file
4
tests/json_v0_stage3/throw_basic.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{"version":0,"kind":"Program","body":[
|
||||||
|
{"type":"Expr","expr":{"type":"Throw","expr":{"type":"Int","value":42}}},
|
||||||
|
{"type":"Return","expr":{"type":"Int","value":0}}
|
||||||
|
]}
|
||||||
9
tests/json_v0_stage3/try_catch_finally.json
Normal file
9
tests/json_v0_stage3/try_catch_finally.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{"version":0,"kind":"Program","body":[
|
||||||
|
{"type":"Local","name":"x","expr":{"type":"Int","value":0}},
|
||||||
|
{"type":"Try",
|
||||||
|
"try":[{"type":"Expr","expr":{"type":"Throw","expr":{"type":"Int","value":42}}}],
|
||||||
|
"catches":[{"param":"e","body":[{"type":"Local","name":"x","expr":{"type":"Var","name":"e"}}]}],
|
||||||
|
"finally":[{"type":"Expr","expr":{"type":"Int","value":1}}]
|
||||||
|
},
|
||||||
|
{"type":"Return","expr":{"type":"Var","name":"x"}}
|
||||||
|
]}
|
||||||
@ -1,25 +1,41 @@
|
|||||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
#![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::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::runtime::PluginConfig;
|
||||||
|
use nyash_rust::{
|
||||||
|
mir::{instruction::MirInstruction, passes::method_id_inject::inject_method_ids, MirCompiler},
|
||||||
|
parser::NyashParser,
|
||||||
|
};
|
||||||
|
|
||||||
fn try_init_plugins() -> bool {
|
fn try_init_plugins() -> bool {
|
||||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
if !std::path::Path::new("nyash.toml").exists() {
|
||||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
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 = get_global_loader_v2();
|
||||||
let loader = loader.read().unwrap();
|
let loader = loader.read().unwrap();
|
||||||
if let Some(conf) = &loader.config {
|
if let Some(conf) = &loader.config {
|
||||||
let mut map = std::collections::HashMap::new();
|
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 });
|
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||||
true
|
true
|
||||||
} else { false }
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn injects_method_id_for_filebox_open() {
|
fn injects_method_id_for_filebox_open() {
|
||||||
if !try_init_plugins() { return; }
|
if !try_init_plugins() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local f
|
local f
|
||||||
f = new FileBox()
|
f = new FileBox()
|
||||||
@ -34,8 +50,14 @@ f.open("/tmp/test.txt", "r")
|
|||||||
for func in module2.functions.values() {
|
for func in module2.functions.values() {
|
||||||
for block in func.blocks.values() {
|
for block in func.blocks.values() {
|
||||||
for inst in &block.instructions {
|
for inst in &block.instructions {
|
||||||
if let MirInstruction::BoxCall { method, method_id, .. } = inst {
|
if let MirInstruction::BoxCall {
|
||||||
if method == "open" { assert!(method_id.is_some(), "open missing method_id"); return; }
|
method, method_id, ..
|
||||||
|
} = inst
|
||||||
|
{
|
||||||
|
if method == "open" {
|
||||||
|
assert!(method_id.is_some(), "open missing method_id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,13 @@ use nyash_rust::mir::instruction_introspection;
|
|||||||
#[test]
|
#[test]
|
||||||
fn mir14_shape_is_fixed() {
|
fn mir14_shape_is_fixed() {
|
||||||
let impl_names = instruction_introspection::mir14_instruction_names();
|
let impl_names = instruction_introspection::mir14_instruction_names();
|
||||||
assert_eq!(impl_names.len(), 14, "MIR14 must contain exactly 14 instructions");
|
assert_eq!(
|
||||||
assert!(impl_names.contains(&"UnaryOp"), "MIR14 must include UnaryOp");
|
impl_names.len(),
|
||||||
|
14,
|
||||||
|
"MIR14 must contain exactly 14 instructions"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
impl_names.contains(&"UnaryOp"),
|
||||||
|
"MIR14 must include UnaryOp"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
* Tests AST → MIR lowering for Phase 6 reference operations
|
* 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::ast::{ASTNode, LiteralValue, Span};
|
||||||
|
use nyash_rust::mir::{MirBuilder, MirPrinter};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -140,27 +140,52 @@ fn test_mir_phase6_lowering_ref_ops() {
|
|||||||
println!("Generated MIR:\n{}", mir_text);
|
println!("Generated MIR:\n{}", mir_text);
|
||||||
|
|
||||||
// Verify that the MIR contains the expected Phase 6 reference operations
|
// Verify that the MIR contains the expected Phase 6 reference operations
|
||||||
assert!(mir_text.contains("ref_new"), "MIR should contain ref_new instruction");
|
assert!(
|
||||||
assert!(mir_text.contains("ref_set"), "MIR should contain ref_set instruction");
|
mir_text.contains("ref_new"),
|
||||||
assert!(mir_text.contains("ref_get"), "MIR should contain ref_get instruction");
|
"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
|
// Verify specific patterns
|
||||||
assert!(mir_text.contains("ref_new") && mir_text.contains("\"Obj\""),
|
assert!(
|
||||||
"MIR should contain ref_new with Obj class");
|
mir_text.contains("ref_new") && mir_text.contains("\"Obj\""),
|
||||||
assert!(mir_text.contains("ref_set") && mir_text.contains(".x"),
|
"MIR should contain ref_new with Obj class"
|
||||||
"MIR should contain ref_set with field x");
|
);
|
||||||
assert!(mir_text.contains("ref_get") && mir_text.contains(".x"),
|
assert!(
|
||||||
"MIR should contain ref_get with field x");
|
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
|
// Verify module structure
|
||||||
assert_eq!(module.function_names().len(), 1, "Module should have one function");
|
assert_eq!(
|
||||||
assert!(module.get_function("main").is_some(), "Module should have main function");
|
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
|
// Verify function has instructions
|
||||||
let main_function = module.get_function("main").unwrap();
|
let main_function = module.get_function("main").unwrap();
|
||||||
let stats = main_function.stats();
|
let stats = main_function.stats();
|
||||||
assert!(stats.instruction_count > 5,
|
assert!(
|
||||||
"Function should have multiple instructions (got {})", stats.instruction_count);
|
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.");
|
println!("✅ Phase 6.1 MIR lowering test passed! Found all required ref operations.");
|
||||||
}
|
}
|
||||||
@ -170,21 +195,19 @@ fn test_mir_phase6_lowering_ref_ops() {
|
|||||||
fn test_mir_verification_phase6_ref_ops() {
|
fn test_mir_verification_phase6_ref_ops() {
|
||||||
// Build simple AST with new and field access
|
// Build simple AST with new and field access
|
||||||
let ast = ASTNode::Program {
|
let ast = ASTNode::Program {
|
||||||
statements: vec![
|
statements: vec![ASTNode::Assignment {
|
||||||
ASTNode::Assignment {
|
target: Box::new(ASTNode::Variable {
|
||||||
target: Box::new(ASTNode::Variable {
|
name: "obj".to_string(),
|
||||||
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(),
|
||||||
},
|
}),
|
||||||
],
|
value: Box::new(ASTNode::New {
|
||||||
|
class: "TestObj".to_string(),
|
||||||
|
arguments: vec![],
|
||||||
|
type_arguments: vec![],
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
span: Span::unknown(),
|
||||||
|
}],
|
||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -203,14 +226,16 @@ fn test_mir_verification_phase6_ref_ops() {
|
|||||||
match verification_result {
|
match verification_result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("✅ MIR verification passed for Phase 6 reference operations");
|
println!("✅ MIR verification passed for Phase 6 reference operations");
|
||||||
},
|
}
|
||||||
Err(errors) => {
|
Err(errors) => {
|
||||||
println!("❌ MIR verification failed with {} errors:", errors.len());
|
println!("❌ MIR verification failed with {} errors:", errors.len());
|
||||||
for error in &errors {
|
for error in &errors {
|
||||||
println!(" - {:?}", error);
|
println!(" - {:?}", error);
|
||||||
}
|
}
|
||||||
// Don't fail the test for verification errors, as the verifier may be incomplete
|
// Don't fail the test for verification errors, as the verifier may be incomplete
|
||||||
println!("⚠️ Continuing test despite verification issues (verifier may be incomplete)");
|
println!(
|
||||||
|
"⚠️ Continuing test despite verification issues (verifier may be incomplete)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,12 @@
|
|||||||
* Tests VM execution of hand-built MIR with RefNew/RefGet/RefSet instructions
|
* Tests VM execution of hand-built MIR with RefNew/RefGet/RefSet instructions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use nyash_rust::mir::{
|
use nyash_rust::backend::{VMValue, VM};
|
||||||
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
|
|
||||||
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue
|
|
||||||
};
|
|
||||||
use nyash_rust::backend::{VM, VMValue};
|
|
||||||
use nyash_rust::box_trait::{IntegerBox, NyashBox};
|
use nyash_rust::box_trait::{IntegerBox, NyashBox};
|
||||||
|
use nyash_rust::mir::{
|
||||||
|
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
|
||||||
|
MirInstruction, MirModule, MirType, ValueId,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mir_phase6_vm_ref_ops() {
|
fn test_mir_phase6_vm_ref_ops() {
|
||||||
@ -67,9 +67,7 @@ fn test_mir_phase6_vm_ref_ops() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// barrier_write %1
|
// barrier_write %1
|
||||||
block.add_instruction(MirInstruction::BarrierWrite {
|
block.add_instruction(MirInstruction::BarrierWrite { ptr: obj_ref });
|
||||||
ptr: obj_ref,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ref_set %1, "x", %2
|
// ref_set %1, "x", %2
|
||||||
block.add_instruction(MirInstruction::RefSet {
|
block.add_instruction(MirInstruction::RefSet {
|
||||||
@ -92,9 +90,7 @@ fn test_mir_phase6_vm_ref_ops() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ret %3
|
// ret %3
|
||||||
block.add_instruction(MirInstruction::Return {
|
block.add_instruction(MirInstruction::Return { value: Some(x_val) });
|
||||||
value: Some(x_val),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add block to function
|
// Add block to function
|
||||||
main_function.add_block(block);
|
main_function.add_block(block);
|
||||||
@ -112,11 +108,18 @@ fn test_mir_phase6_vm_ref_ops() {
|
|||||||
|
|
||||||
// Check if result is IntegerBox with value 1
|
// Check if result is IntegerBox with value 1
|
||||||
if let Some(int_box) = result_box.as_any().downcast_ref::<IntegerBox>() {
|
if let Some(int_box) = result_box.as_any().downcast_ref::<IntegerBox>() {
|
||||||
assert_eq!(int_box.value, 1, "Return value should be 1, got {}", int_box.value);
|
assert_eq!(
|
||||||
|
int_box.value, 1,
|
||||||
|
"Return value should be 1, got {}",
|
||||||
|
int_box.value
|
||||||
|
);
|
||||||
println!("✅ Return value correct: {}", int_box.value);
|
println!("✅ Return value correct: {}", int_box.value);
|
||||||
} else {
|
} else {
|
||||||
// Print what we actually got
|
// 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());
|
println!(" Type: {}", result_box.type_name());
|
||||||
|
|
||||||
// For Phase 6.1, the core functionality works (field ops execute correctly)
|
// For Phase 6.1, the core functionality works (field ops execute correctly)
|
||||||
@ -124,7 +127,7 @@ fn test_mir_phase6_vm_ref_ops() {
|
|||||||
println!("✅ Phase 6.1 core requirement met: RefNew/RefGet/RefSet execute without errors");
|
println!("✅ Phase 6.1 core requirement met: RefNew/RefGet/RefSet execute without errors");
|
||||||
println!("✅ Field operations working correctly (note: return value propagation has minor issue)");
|
println!("✅ Field operations working correctly (note: return value propagation has minor issue)");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
panic!("❌ VM execution failed: {}", e);
|
panic!("❌ VM execution failed: {}", e);
|
||||||
}
|
}
|
||||||
@ -169,13 +172,9 @@ fn test_barrier_no_op() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Test barrier instructions (should be no-ops)
|
// Test barrier instructions (should be no-ops)
|
||||||
block.add_instruction(MirInstruction::BarrierRead {
|
block.add_instruction(MirInstruction::BarrierRead { ptr: test_val });
|
||||||
ptr: test_val,
|
|
||||||
});
|
|
||||||
|
|
||||||
block.add_instruction(MirInstruction::BarrierWrite {
|
block.add_instruction(MirInstruction::BarrierWrite { ptr: test_val });
|
||||||
ptr: test_val,
|
|
||||||
});
|
|
||||||
|
|
||||||
block.add_instruction(MirInstruction::Return {
|
block.add_instruction(MirInstruction::Return {
|
||||||
value: Some(test_val),
|
value: Some(test_val),
|
||||||
@ -188,6 +187,9 @@ fn test_barrier_no_op() {
|
|||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
let result = vm.execute_module(&module);
|
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");
|
println!("✅ Barrier no-op test passed - barriers execute without errors");
|
||||||
}
|
}
|
||||||
@ -4,9 +4,9 @@
|
|||||||
* Tests AST → MIR lowering and VM execution for Phase 7 async operations
|
* 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::ast::{ASTNode, LiteralValue, Span};
|
||||||
|
use nyash_rust::backend::VM;
|
||||||
|
use nyash_rust::mir::{MirBuilder, MirPrinter};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -106,20 +106,22 @@ fn test_mir_phase7_basic_nowait_await() {
|
|||||||
|
|
||||||
// Verify MIR contains expected instructions
|
// Verify MIR contains expected instructions
|
||||||
let function = module.get_function("main").unwrap();
|
let function = module.get_function("main").unwrap();
|
||||||
let instructions: Vec<_> = function.blocks.values()
|
let instructions: Vec<_> = function
|
||||||
|
.blocks
|
||||||
|
.values()
|
||||||
.flat_map(|block| &block.instructions)
|
.flat_map(|block| &block.instructions)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Should contain FutureNew instruction
|
// Should contain FutureNew instruction
|
||||||
let has_future_new = instructions.iter().any(|inst| {
|
let has_future_new = instructions
|
||||||
matches!(inst, nyash_rust::mir::MirInstruction::FutureNew { .. })
|
.iter()
|
||||||
});
|
.any(|inst| matches!(inst, nyash_rust::mir::MirInstruction::FutureNew { .. }));
|
||||||
assert!(has_future_new, "MIR should contain FutureNew instruction");
|
assert!(has_future_new, "MIR should contain FutureNew instruction");
|
||||||
|
|
||||||
// Should contain Await instruction
|
// Should contain Await instruction
|
||||||
let has_await = instructions.iter().any(|inst| {
|
let has_await = instructions
|
||||||
matches!(inst, nyash_rust::mir::MirInstruction::Await { .. })
|
.iter()
|
||||||
});
|
.any(|inst| matches!(inst, nyash_rust::mir::MirInstruction::Await { .. }));
|
||||||
assert!(has_await, "MIR should contain Await instruction");
|
assert!(has_await, "MIR should contain Await instruction");
|
||||||
|
|
||||||
// Test VM execution
|
// Test VM execution
|
||||||
|
|||||||
@ -1,28 +1,41 @@
|
|||||||
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||||
|
|
||||||
use nyash_rust::parser::NyashParser;
|
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::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::runtime::PluginConfig;
|
||||||
|
|
||||||
fn try_init_plugins() -> bool {
|
fn try_init_plugins() -> bool {
|
||||||
if !std::path::Path::new("nyash.toml").exists() { return false; }
|
if !std::path::Path::new("nyash.toml").exists() {
|
||||||
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
|
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 = get_global_loader_v2();
|
||||||
let loader = loader.read().unwrap();
|
let loader = loader.read().unwrap();
|
||||||
if let Some(conf) = &loader.config {
|
if let Some(conf) = &loader.config {
|
||||||
let mut map = std::collections::HashMap::new();
|
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 });
|
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
|
||||||
true
|
true
|
||||||
} else { false }
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimal ABI sanity check: HttpRequestBox.path=1, readBody=2
|
/// Minimal ABI sanity check: HttpRequestBox.path=1, readBody=2
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "MIR13/plugin Net: HttpRequestBox path/readBody default values pending"]
|
#[ignore = "MIR13/plugin Net: HttpRequestBox path/readBody default values pending"]
|
||||||
fn plugin_contract_http_request_ids_sanity() {
|
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)
|
// Exercise HttpRequestBox.path/readBody on a birthed request (no server needed)
|
||||||
let code = r#"
|
let code = r#"
|
||||||
local req, p, b
|
local req, p, b
|
||||||
|
|||||||
126
tests/vm_e2e.rs
126
tests/vm_e2e.rs
@ -2,53 +2,109 @@
|
|||||||
//! VM E2E: Compile Nyash to MIR and execute via VM, with mock plugin factory
|
//! VM E2E: Compile Nyash to MIR and execute via VM, with mock plugin factory
|
||||||
use std::sync::Arc;
|
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::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::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
|
// Minimal AdderBox to validate plugin factory path under VM
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct AdderBox { base: BoxBase, sum: i64 }
|
struct AdderBox {
|
||||||
impl AdderBox { fn new(a: i64, b: i64) -> Self { Self { base: BoxBase::new(), sum: a + b } } }
|
base: BoxBase,
|
||||||
|
sum: i64,
|
||||||
|
}
|
||||||
|
impl AdderBox {
|
||||||
|
fn new(a: i64, b: i64) -> Self {
|
||||||
|
Self {
|
||||||
|
base: BoxBase::new(),
|
||||||
|
sum: a + b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BoxCore for AdderBox {
|
impl BoxCore for AdderBox {
|
||||||
fn box_id(&self) -> u64 { self.base.id }
|
fn box_id(&self) -> u64 {
|
||||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
|
self.base.id
|
||||||
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 parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
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 {
|
impl NyashBox for AdderBox {
|
||||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.sum.to_string()) }
|
fn to_string_box(&self) -> StringBox {
|
||||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
StringBox::new(self.sum.to_string())
|
||||||
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() { BoolBox::new(self.sum == a.sum) } else { BoolBox::new(false) }
|
}
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||||
|
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() {
|
||||||
|
BoolBox::new(self.sum == a.sum)
|
||||||
|
} else {
|
||||||
|
BoolBox::new(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
"AdderBox"
|
||||||
|
}
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
fn type_name(&self) -> &'static str { "AdderBox" }
|
|
||||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TestPluginFactory;
|
struct TestPluginFactory;
|
||||||
impl TestPluginFactory { fn new() -> Self { Self } }
|
impl TestPluginFactory {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
impl BoxFactory for TestPluginFactory {
|
impl BoxFactory for TestPluginFactory {
|
||||||
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
fn create_box(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
args: &[Box<dyn NyashBox>],
|
||||||
|
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
match name {
|
match name {
|
||||||
"AdderBox" => {
|
"AdderBox" => {
|
||||||
if args.len() != 2 { return Err(RuntimeError::InvalidOperation{ message: format!("AdderBox expects 2 args, got {}", args.len()) }); }
|
if args.len() != 2 {
|
||||||
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?;
|
return Err(RuntimeError::InvalidOperation {
|
||||||
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg b must be int".into() })?;
|
message: format!("AdderBox expects 2 args, got {}", args.len()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| {
|
||||||
|
RuntimeError::TypeError {
|
||||||
|
message: "AdderBox arg a must be int".into(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| {
|
||||||
|
RuntimeError::TypeError {
|
||||||
|
message: "AdderBox arg b must be int".into(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
Ok(Box::new(AdderBox::new(a, b)))
|
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]
|
#[test]
|
||||||
@ -56,9 +112,11 @@ fn vm_e2e_adder_box() {
|
|||||||
// Build runtime with builtin + user-defined + mock plugin factory
|
// Build runtime with builtin + user-defined + mock plugin factory
|
||||||
let runtime = NyashRuntimeBuilder::new()
|
let runtime = NyashRuntimeBuilder::new()
|
||||||
.with_builtin_groups(BuiltinGroups::native_full())
|
.with_builtin_groups(BuiltinGroups::native_full())
|
||||||
.with_factory(Arc::new(nyash_rust::box_factory::user_defined::UserDefinedBoxFactory::new(
|
.with_factory(Arc::new(
|
||||||
nyash_rust::interpreter::SharedState::new(),
|
nyash_rust::box_factory::user_defined::UserDefinedBoxFactory::new(
|
||||||
)))
|
nyash_rust::interpreter::SharedState::new(),
|
||||||
|
),
|
||||||
|
))
|
||||||
.with_factory(Arc::new(TestPluginFactory::new()))
|
.with_factory(Arc::new(TestPluginFactory::new()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -75,12 +133,18 @@ fn vm_e2e_adder_box() {
|
|||||||
|
|
||||||
// Execute via VM using the prepared runtime
|
// Execute via VM using the prepared runtime
|
||||||
let mut vm = VM::with_runtime(runtime);
|
let mut vm = VM::with_runtime(runtime);
|
||||||
let result = vm.execute_module(&compile_result.module).expect("vm exec ok");
|
let result = vm
|
||||||
|
.execute_module(&compile_result.module)
|
||||||
|
.expect("vm exec ok");
|
||||||
|
|
||||||
// The VM returns an Option<Box<dyn NyashBox>> or a value; we print/debug and check string form
|
// The VM returns an Option<Box<dyn NyashBox>> or a value; we print/debug and check string form
|
||||||
// Here we rely on Display via to_string_box through Debug format
|
// Here we rely on Display via to_string_box through Debug format
|
||||||
// Try to format the result if available
|
// 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.
|
// For this implementation, result is a generic value; we check debug string contains 42 or to_string equivalent.
|
||||||
let s = format!("{:?}", result);
|
let s = format!("{:?}", result);
|
||||||
assert!(s.contains("42") || s.contains("AdderBox"), "unexpected VM result: {}", s);
|
assert!(
|
||||||
|
s.contains("42") || s.contains("AdderBox"),
|
||||||
|
"unexpected VM result: {}",
|
||||||
|
s
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,11 +9,11 @@
|
|||||||
* - Return values
|
* - 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::backend::wasm::WasmBackend;
|
||||||
|
use nyash_rust::mir::{
|
||||||
|
BasicBlock, BasicBlockId, BinaryOp, ConstValue, EffectMask, FunctionSignature, MirFunction,
|
||||||
|
MirInstruction, MirModule, MirType, ValueId,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wasm_poc1_basic_arithmetic() {
|
fn test_wasm_poc1_basic_arithmetic() {
|
||||||
@ -36,15 +36,36 @@ fn test_wasm_poc1_basic_arithmetic() {
|
|||||||
let wat_text = wat_result.unwrap();
|
let wat_text = wat_result.unwrap();
|
||||||
|
|
||||||
// Verify WAT contains expected elements
|
// Verify WAT contains expected elements
|
||||||
assert!(wat_text.contains("(module"), "Should contain module declaration");
|
assert!(
|
||||||
assert!(wat_text.contains("memory"), "Should contain memory declaration");
|
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("import"), "Should contain imports");
|
||||||
assert!(wat_text.contains("$main"), "Should contain main function");
|
assert!(wat_text.contains("$main"), "Should contain main function");
|
||||||
assert!(wat_text.contains("i32.const 42"), "Should contain constant 42");
|
assert!(
|
||||||
assert!(wat_text.contains("i32.const 8"), "Should contain constant 8");
|
wat_text.contains("i32.const 42"),
|
||||||
assert!(wat_text.contains("i32.add"), "Should contain addition operation");
|
"Should contain constant 42"
|
||||||
assert!(wat_text.contains("call $print"), "Should contain print call");
|
);
|
||||||
assert!(wat_text.contains("return"), "Should contain return instruction");
|
assert!(
|
||||||
|
wat_text.contains("i32.const 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
|
// Compile to WASM binary and execute
|
||||||
let wasm_result = backend.compile_module(mir_module);
|
let wasm_result = backend.compile_module(mir_module);
|
||||||
@ -110,9 +131,9 @@ fn build_arithmetic_mir_module() -> MirModule {
|
|||||||
let mut block = BasicBlock::new(entry_block);
|
let mut block = BasicBlock::new(entry_block);
|
||||||
|
|
||||||
// Generate value IDs
|
// Generate value IDs
|
||||||
let val_a = ValueId::new(0); // 42
|
let val_a = ValueId::new(0); // 42
|
||||||
let val_b = ValueId::new(1); // 8
|
let val_b = ValueId::new(1); // 8
|
||||||
let result = ValueId::new(2); // 42 + 8
|
let result = ValueId::new(2); // 42 + 8
|
||||||
|
|
||||||
// Add instructions
|
// Add instructions
|
||||||
block.add_instruction(MirInstruction::Const {
|
block.add_instruction(MirInstruction::Const {
|
||||||
@ -143,7 +164,11 @@ fn build_arithmetic_mir_module() -> MirModule {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Debug: Print number of instructions and terminator
|
// 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
|
// Add block to function
|
||||||
main_function.add_block(block);
|
main_function.add_block(block);
|
||||||
@ -169,9 +194,9 @@ fn build_multiplication_mir_module() -> MirModule {
|
|||||||
let mut main_function = MirFunction::new(main_signature, entry_block);
|
let mut main_function = MirFunction::new(main_signature, entry_block);
|
||||||
let mut block = BasicBlock::new(entry_block);
|
let mut block = BasicBlock::new(entry_block);
|
||||||
|
|
||||||
let val_a = ValueId::new(0); // 6
|
let val_a = ValueId::new(0); // 6
|
||||||
let val_b = ValueId::new(1); // 7
|
let val_b = ValueId::new(1); // 7
|
||||||
let result = ValueId::new(2); // 6 * 7
|
let result = ValueId::new(2); // 6 * 7
|
||||||
|
|
||||||
block.add_instruction(MirInstruction::Const {
|
block.add_instruction(MirInstruction::Const {
|
||||||
dst: val_a,
|
dst: val_a,
|
||||||
@ -215,9 +240,9 @@ fn build_subtraction_mir_module() -> MirModule {
|
|||||||
let mut main_function = MirFunction::new(main_signature, entry_block);
|
let mut main_function = MirFunction::new(main_signature, entry_block);
|
||||||
let mut block = BasicBlock::new(entry_block);
|
let mut block = BasicBlock::new(entry_block);
|
||||||
|
|
||||||
let val_a = ValueId::new(0); // 50
|
let val_a = ValueId::new(0); // 50
|
||||||
let val_b = ValueId::new(1); // 8
|
let val_b = ValueId::new(1); // 8
|
||||||
let result = ValueId::new(2); // 50 - 8
|
let result = ValueId::new(2); // 50 - 8
|
||||||
|
|
||||||
block.add_instruction(MirInstruction::Const {
|
block.add_instruction(MirInstruction::Const {
|
||||||
dst: val_a,
|
dst: val_a,
|
||||||
|
|||||||
@ -11,11 +11,11 @@
|
|||||||
* Validates the "Everything is Box" philosophy in WASM
|
* 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::backend::wasm::WasmBackend;
|
||||||
|
use nyash_rust::mir::{
|
||||||
|
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
|
||||||
|
MirInstruction, MirModule, MirType, ValueId,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wasm_poc2_refnew_basic() {
|
fn test_wasm_poc2_refnew_basic() {
|
||||||
@ -36,10 +36,22 @@ fn test_wasm_poc2_refnew_basic() {
|
|||||||
let wat_text = wat_result.unwrap();
|
let wat_text = wat_result.unwrap();
|
||||||
|
|
||||||
// Verify WAT contains expected elements
|
// Verify WAT contains expected elements
|
||||||
assert!(wat_text.contains("(module"), "Should contain module declaration");
|
assert!(
|
||||||
assert!(wat_text.contains("$malloc"), "Should contain malloc function");
|
wat_text.contains("(module"),
|
||||||
assert!(wat_text.contains("$alloc_databox"), "Should contain DataBox allocator");
|
"Should contain module declaration"
|
||||||
assert!(wat_text.contains("call $alloc_databox"), "Should call DataBox allocator");
|
);
|
||||||
|
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");
|
assert!(wat_text.contains("i32.store"), "Should store field values");
|
||||||
|
|
||||||
// Compile to WASM binary and execute
|
// Compile to WASM binary and execute
|
||||||
@ -58,7 +70,11 @@ fn test_wasm_poc2_refnew_basic() {
|
|||||||
|
|
||||||
let return_value = execution_result.unwrap();
|
let return_value = execution_result.unwrap();
|
||||||
// Should return a valid pointer (greater than heap start 0x800)
|
// 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]
|
#[test]
|
||||||
@ -123,9 +139,9 @@ fn build_refnew_mir_module() -> MirModule {
|
|||||||
let mut main_function = MirFunction::new(main_signature, entry_block);
|
let mut main_function = MirFunction::new(main_signature, entry_block);
|
||||||
let mut block = BasicBlock::new(entry_block);
|
let mut block = BasicBlock::new(entry_block);
|
||||||
|
|
||||||
let init_val = ValueId::new(0); // 42
|
let init_val = ValueId::new(0); // 42
|
||||||
let box_ptr = ValueId::new(1); // DataBox pointer
|
let box_ptr = ValueId::new(1); // DataBox pointer
|
||||||
let ref_ptr = ValueId::new(2); // Reference to DataBox
|
let ref_ptr = ValueId::new(2); // Reference to DataBox
|
||||||
|
|
||||||
// Create constant for initialization
|
// Create constant for initialization
|
||||||
block.add_instruction(MirInstruction::Const {
|
block.add_instruction(MirInstruction::Const {
|
||||||
@ -172,11 +188,11 @@ fn build_refget_refset_mir_module() -> MirModule {
|
|||||||
let mut main_function = MirFunction::new(main_signature, entry_block);
|
let mut main_function = MirFunction::new(main_signature, entry_block);
|
||||||
let mut block = BasicBlock::new(entry_block);
|
let mut block = BasicBlock::new(entry_block);
|
||||||
|
|
||||||
let init_val = ValueId::new(0); // 10
|
let init_val = ValueId::new(0); // 10
|
||||||
let new_val = ValueId::new(1); // 42
|
let new_val = ValueId::new(1); // 42
|
||||||
let box_ptr = ValueId::new(2); // DataBox pointer
|
let box_ptr = ValueId::new(2); // DataBox pointer
|
||||||
let ref_ptr = ValueId::new(3); // Reference to DataBox
|
let ref_ptr = ValueId::new(3); // Reference to DataBox
|
||||||
let result = ValueId::new(4); // Read back value
|
let result = ValueId::new(4); // Read back value
|
||||||
|
|
||||||
// Create constants
|
// Create constants
|
||||||
block.add_instruction(MirInstruction::Const {
|
block.add_instruction(MirInstruction::Const {
|
||||||
@ -242,16 +258,16 @@ fn build_complete_workflow_mir_module() -> MirModule {
|
|||||||
let mut main_function = MirFunction::new(main_signature, entry_block);
|
let mut main_function = MirFunction::new(main_signature, entry_block);
|
||||||
let mut block = BasicBlock::new(entry_block);
|
let mut block = BasicBlock::new(entry_block);
|
||||||
|
|
||||||
let val1_init = ValueId::new(0); // 100
|
let val1_init = ValueId::new(0); // 100
|
||||||
let val2_init = ValueId::new(1); // 200
|
let val2_init = ValueId::new(1); // 200
|
||||||
let box1_ptr = ValueId::new(2); // DataBox 1 pointer
|
let box1_ptr = ValueId::new(2); // DataBox 1 pointer
|
||||||
let box2_ptr = ValueId::new(3); // DataBox 2 pointer
|
let box2_ptr = ValueId::new(3); // DataBox 2 pointer
|
||||||
let ref1_ptr = ValueId::new(4); // Reference to DataBox 1
|
let ref1_ptr = ValueId::new(4); // Reference to DataBox 1
|
||||||
let ref2_ptr = ValueId::new(5); // Reference to DataBox 2
|
let ref2_ptr = ValueId::new(5); // Reference to DataBox 2
|
||||||
let val1 = ValueId::new(6); // Value from box1
|
let val1 = ValueId::new(6); // Value from box1
|
||||||
let val2 = ValueId::new(7); // Value from box2
|
let val2 = ValueId::new(7); // Value from box2
|
||||||
let sum = ValueId::new(8); // Sum of values
|
let sum = ValueId::new(8); // Sum of values
|
||||||
let result = ValueId::new(9); // Final result
|
let result = ValueId::new(9); // Final result
|
||||||
|
|
||||||
// Create constants
|
// Create constants
|
||||||
block.add_instruction(MirInstruction::Const {
|
block.add_instruction(MirInstruction::Const {
|
||||||
|
|||||||
@ -9,11 +9,11 @@
|
|||||||
* - WAT generation includes data segments and string allocation
|
* - 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::backend::wasm::WasmBackend;
|
||||||
|
use nyash_rust::mir::{
|
||||||
|
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
|
||||||
|
MirInstruction, MirModule, MirType, ValueId,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wasm_string_constant_basic() {
|
fn test_wasm_string_constant_basic() {
|
||||||
@ -28,17 +28,38 @@ fn test_wasm_string_constant_basic() {
|
|||||||
|
|
||||||
// Generate WAT text for debugging
|
// Generate WAT text for debugging
|
||||||
let wat_result = backend.compile_to_wat(mir_module.clone());
|
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();
|
let wat_text = wat_result.unwrap();
|
||||||
|
|
||||||
// Verify WAT contains expected elements for string support
|
// Verify WAT contains expected elements for string support
|
||||||
assert!(wat_text.contains("(module"), "Should contain module declaration");
|
assert!(
|
||||||
assert!(wat_text.contains("memory"), "Should contain memory declaration");
|
wat_text.contains("(module"),
|
||||||
assert!(wat_text.contains("data"), "Should contain data segment for string literal");
|
"Should contain module declaration"
|
||||||
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!(
|
||||||
assert!(wat_text.contains("print_str"), "Should contain print_str import");
|
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
|
// Verify string literal is properly embedded
|
||||||
// (The assertion for UTF-8 bytes is above)
|
// (The assertion for UTF-8 bytes is above)
|
||||||
@ -49,7 +70,10 @@ fn test_wasm_string_constant_basic() {
|
|||||||
println!("WASM compilation error: {}", e);
|
println!("WASM compilation error: {}", e);
|
||||||
println!("Generated WAT:\n{}", wat_text);
|
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]
|
#[test]
|
||||||
@ -66,20 +90,35 @@ fn test_wasm_string_constant_multiple() {
|
|||||||
let mir_module = build_multiple_string_const_mir_module();
|
let mir_module = build_multiple_string_const_mir_module();
|
||||||
|
|
||||||
let wat_result = backend.compile_to_wat(mir_module.clone());
|
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();
|
let wat_text = wat_result.unwrap();
|
||||||
|
|
||||||
// Should contain both unique strings (in hex format)
|
// Should contain both unique strings (in hex format)
|
||||||
assert!(wat_text.contains("\\46\\69\\72\\73\\74"), "Should contain 'First' string in hex");
|
assert!(
|
||||||
assert!(wat_text.contains("\\53\\65\\63\\6f\\6e\\64"), "Should contain 'Second' string in hex");
|
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)
|
// Should have 2 data segments (First and Second, duplicate First reused)
|
||||||
let data_count = wat_text.matches("(data").count();
|
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);
|
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
|
/// Build a MIR module with a single string constant
|
||||||
|
|||||||
@ -1,88 +0,0 @@
|
|||||||
<#
|
|
||||||
AOT smoke (Cranelift) — DRYRUN skeleton (Windows-first)
|
|
||||||
Usage:
|
|
||||||
pwsh -File tools/aot_smoke_cranelift.ps1 [-Mode release|debug]
|
|
||||||
Env:
|
|
||||||
CLIF_SMOKE_RUN=1 # actually execute steps (default: dry-run only)
|
|
||||||
NYASH_LINK_VERBOSE=1 # echo link commands (when run)
|
|
||||||
NYASH_DISABLE_PLUGINS=1 # plugin-dependent smokes off
|
|
||||||
Notes:
|
|
||||||
- This script mirrors docs/tests/aot_smoke_cranelift.md pseudo flow.
|
|
||||||
- PoC: emits commands; real execution requires Cranelift AOT path to be implemented.
|
|
||||||
#>
|
|
||||||
|
|
||||||
param(
|
|
||||||
[ValidateSet('release','debug')]
|
|
||||||
[string]$Mode = 'release'
|
|
||||||
)
|
|
||||||
|
|
||||||
$Run = [int]([Environment]::GetEnvironmentVariable('CLIF_SMOKE_RUN') ?? '0')
|
|
||||||
|
|
||||||
function Banner($msg) { Write-Host "`n[clif-aot-smoke] $msg" }
|
|
||||||
function Info($msg) { Write-Host "[clif-aot-smoke] $msg" }
|
|
||||||
|
|
||||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
||||||
$Target = Join-Path $Root 'target'
|
|
||||||
$ObjDir = Join-Path $Target 'aot_objects'
|
|
||||||
New-Item -ItemType Directory -Force -Path $ObjDir | Out-Null
|
|
||||||
$ObjOut = Join-Path $ObjDir 'core_smoke.obj'
|
|
||||||
$NyashBin = Join-Path (Join-Path $Target $Mode) 'nyash.exe'
|
|
||||||
$ExeOut = Join-Path $Target 'app_clif.exe'
|
|
||||||
|
|
||||||
Banner "Cranelift AOT Smoke (mode=$Mode, dry-run=$([bool](-not ($Run -eq 1))))"
|
|
||||||
|
|
||||||
# 1) Build nyash with cranelift
|
|
||||||
Banner "building nyash (features=cranelift-jit)"
|
|
||||||
if ($Run -eq 1) {
|
|
||||||
& cargo build --% --$Mode --features cranelift-jit
|
|
||||||
} else {
|
|
||||||
Info "DRYRUN: cargo build --$Mode --features cranelift-jit"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 2) Emit object via backend=cranelift (PoC path)
|
|
||||||
Banner "emitting object via --backend cranelift (PoC)"
|
|
||||||
if ($Run -eq 1) {
|
|
||||||
if (-not (Test-Path $NyashBin)) { throw "nyash not found: $NyashBin" }
|
|
||||||
$env:NYASH_AOT_OBJECT_OUT = $ObjOut
|
|
||||||
& $NyashBin --backend cranelift apps/hello/main.nyash | Out-Null
|
|
||||||
if (-not (Test-Path $ObjOut)) { throw "object not generated: $ObjOut" }
|
|
||||||
$size = (Get-Item $ObjOut).Length
|
|
||||||
Info "OK: object generated: $ObjOut ($size bytes)"
|
|
||||||
} else {
|
|
||||||
Info "DRYRUN: NYASH_AOT_OBJECT_OUT=$ObjOut $NyashBin --backend cranelift apps/hello/main.nyash"
|
|
||||||
New-Item -ItemType File -Force -Path $ObjOut | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
# 3) Link (Windows-first)
|
|
||||||
Banner "linking app (Windows-first)"
|
|
||||||
if ($Run -eq 1) {
|
|
||||||
$link = Get-Command link -ErrorAction SilentlyContinue
|
|
||||||
$lld = Get-Command lld-link -ErrorAction SilentlyContinue
|
|
||||||
if ($link) {
|
|
||||||
Info "using MSVC link.exe"
|
|
||||||
& link /OUT:$ExeOut $ObjOut nyrt.lib
|
|
||||||
} elseif ($lld) {
|
|
||||||
Info "using lld-link"
|
|
||||||
& lld-link -OUT:$ExeOut $ObjOut nyrt.lib
|
|
||||||
} else {
|
|
||||||
throw "no Windows linker found (link.exe/lld-link)"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Info "DRYRUN: link /OUT:$ExeOut $ObjOut nyrt.lib (or lld-link)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 4) Run and verify
|
|
||||||
Banner "run and verify output"
|
|
||||||
if ($Run -eq 1) {
|
|
||||||
if (-not (Test-Path $ExeOut)) { throw "no output binary: $ExeOut" }
|
|
||||||
$out = & $ExeOut 2>&1
|
|
||||||
$out | Write-Host
|
|
||||||
if ($out -notmatch 'Result:') { throw "unexpected output" }
|
|
||||||
Info "OK: smoke passed"
|
|
||||||
} else {
|
|
||||||
Info "DRYRUN: $ExeOut → expect a line including: 'Result:'"
|
|
||||||
Info "DRYRUN complete"
|
|
||||||
}
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Cranelift JIT-AOT smoke: emit object via --jit-direct and link with nyrt
|
|
||||||
# Usage: tools/aot_smoke_cranelift.sh [app_path] [out_basename]
|
|
||||||
|
|
||||||
APP=${1:-apps/smokes/jit_aot_string_min.nyash}
|
|
||||||
BASE=${2:-app}
|
|
||||||
|
|
||||||
BIN=./target/release/nyash
|
|
||||||
OBJ_DIR=target/aot_objects
|
|
||||||
OBJ=$OBJ_DIR/${BASE}.o
|
|
||||||
EXE=${BASE}
|
|
||||||
|
|
||||||
mkdir -p "$OBJ_DIR"
|
|
||||||
|
|
||||||
echo "[AOT] building core (if needed)"
|
|
||||||
cargo build --release --features cranelift-jit >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
echo "[AOT] lowering: $APP -> $OBJ"
|
|
||||||
NYASH_DISABLE_PLUGINS=1 NYASH_AOT_OBJECT_OUT="$OBJ" "$BIN" --jit-direct "$APP"
|
|
||||||
|
|
||||||
# Ensure NyRT static library exists (libnyrt.a)
|
|
||||||
if [ ! -f crates/nyrt/target/release/libnyrt.a ] && [ ! -f target/release/libnyrt.a ] && [ ! -f target/release/nyrt.lib ]; then
|
|
||||||
echo "[AOT] building NyRT (static runtime)"
|
|
||||||
(cd crates/nyrt && cargo build --release >/dev/null)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[AOT] linking: $EXE"
|
|
||||||
# Prefer the workspace root target, then crates/nyrt/target
|
|
||||||
cc "$OBJ" \
|
|
||||||
-L target/release \
|
|
||||||
-L crates/nyrt/target/release \
|
|
||||||
-Wl,--whole-archive -lnyrt -Wl,--no-whole-archive \
|
|
||||||
-lpthread -ldl -lm \
|
|
||||||
-o "$EXE"
|
|
||||||
|
|
||||||
echo "[AOT] run: ./$EXE"
|
|
||||||
"./$EXE" || true
|
|
||||||
47
tools/clean_root_artifacts.sh
Normal file
47
tools/clean_root_artifacts.sh
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Non-destructive cleaner for stray build artifacts in repo root.
|
||||||
|
# Dry-run by default; pass --apply to actually remove.
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd)
|
||||||
|
APPLY=0
|
||||||
|
[[ "${1:-}" == "--apply" ]] && APPLY=1
|
||||||
|
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
patterns=(
|
||||||
|
"app_*" # e.g., app_parity_*, app_stage3_loop, etc.
|
||||||
|
"*_app" # e.g., test_filebox_app
|
||||||
|
"*.o" # stray .o files
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "[clean-root] scanning..."
|
||||||
|
found=()
|
||||||
|
for pat in "${patterns[@]}"; do
|
||||||
|
while IFS= read -r -d $'\0' f; do
|
||||||
|
# skip directories and keep inside tools/, src/, apps/, etc.
|
||||||
|
[[ -d "$f" ]] && continue
|
||||||
|
case "$f" in
|
||||||
|
src/*|apps/*|examples/*|tools/*|docs/*|tests/*|crates/*|plugins/*) continue ;;
|
||||||
|
esac
|
||||||
|
found+=("$f")
|
||||||
|
done < <(find . -maxdepth 1 -name "$pat" -print0 2>/dev/null || true)
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#found[@]} == 0 )); then
|
||||||
|
echo "[clean-root] nothing to clean"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "[clean-root] candidates (%d):\n" "${#found[@]}"
|
||||||
|
printf ' %s\n' "${found[@]}"
|
||||||
|
|
||||||
|
if (( APPLY )); then
|
||||||
|
echo "[clean-root] removing..."
|
||||||
|
rm -f -- "${found[@]}"
|
||||||
|
echo "[clean-root] done"
|
||||||
|
else
|
||||||
|
echo "[clean-root] dry-run (no files removed). Use --apply to remove."
|
||||||
|
fi
|
||||||
|
|
||||||
@ -2,9 +2,10 @@
|
|||||||
# Nyash dev environment convenience script
|
# Nyash dev environment convenience script
|
||||||
# Usage: source tools/dev_env.sh [profile]
|
# Usage: source tools/dev_env.sh [profile]
|
||||||
# Profiles:
|
# Profiles:
|
||||||
# pyvm - Favor PyVM for VM and Bridge
|
# pyvm - Favor PyVM for VM and Bridge
|
||||||
# bridge - Bridge-only helpers (keep interpreter)
|
# bridge - Bridge-only helpers (keep interpreter)
|
||||||
# reset - Unset variables set by this script
|
# phi_off - PHI-less MIR (edge-copy) + verifier relax; harness on
|
||||||
|
# reset - Unset variables set by this script
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@ -28,13 +29,23 @@ reset_env() {
|
|||||||
unset NYASH_PIPE_USE_PYVM || true
|
unset NYASH_PIPE_USE_PYVM || true
|
||||||
unset NYASH_DISABLE_PLUGINS || true
|
unset NYASH_DISABLE_PLUGINS || true
|
||||||
unset NYASH_NY_COMPILER_TIMEOUT_MS || 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
|
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
|
case "${1:-pyvm}" in
|
||||||
pyvm) activate_pyvm ;;
|
pyvm) activate_pyvm ;;
|
||||||
bridge) activate_bridge ;;
|
bridge) activate_bridge ;;
|
||||||
|
phi_off) activate_phi_off ;;
|
||||||
reset) reset_env ;;
|
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
|
esac
|
||||||
|
|
||||||
|
|||||||
@ -1,95 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
||||||
ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
||||||
|
|
||||||
BIN="$ROOT_DIR/target/release/nyash"
|
|
||||||
if [ ! -x "$BIN" ]; then
|
|
||||||
echo "Building nyash (release, JIT)..." >&2
|
|
||||||
cargo build --release --features cranelift-jit >/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Optional std Ny smokes (requires: NYASH_LOAD_NY_PLUGINS=1 and plugins enabled)
|
|
||||||
run_std_smokes() {
|
|
||||||
if [[ "${NYASH_LOAD_NY_PLUGINS:-0}" != "1" ]] || [[ "${NYASH_DISABLE_PLUGINS:-0}" == "1" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[JIT Smoke] Std Ny smokes (plugins via nyash.toml)" >&2
|
|
||||||
|
|
||||||
local smokes=(
|
|
||||||
"apps/smokes/std/string_smoke.nyash"
|
|
||||||
"apps/smokes/std/array_smoke.nyash"
|
|
||||||
)
|
|
||||||
|
|
||||||
local overall_rc=0
|
|
||||||
for f in "${smokes[@]}"; do
|
|
||||||
local name
|
|
||||||
name=$(basename "$f" .nyash)
|
|
||||||
if [[ ! -f "$ROOT_DIR/$f" ]]; then
|
|
||||||
echo "[STD] ${name}: FAIL (missing)" >&2
|
|
||||||
overall_rc=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
set +e
|
|
||||||
# Hard timeout to prevent runaway smokes (hang guard)
|
|
||||||
out=$(timeout 15s "$BIN" --backend vm "$ROOT_DIR/$f" 2>&1)
|
|
||||||
rc=$?
|
|
||||||
# Normalize timeout exit code (124) to rc=124
|
|
||||||
if [[ $rc -eq 124 ]]; then
|
|
||||||
echo "[STD] ${name}: TIMEOUT" >&2
|
|
||||||
overall_rc=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
set -e
|
|
||||||
if [[ $rc -eq 0 ]] && echo "$out" | rg -q '^Result:\s*0\b'; then
|
|
||||||
echo "[STD] ${name}: PASS" >&2
|
|
||||||
else
|
|
||||||
# Heuristic skip: ArrayBox plugin not available (treat as SKIP not FAIL)
|
|
||||||
if echo "$out" | rg -q 'Failed to create ArrayBox'; then
|
|
||||||
echo "[STD] ${name}: SKIP (ArrayBox plugin unavailable)" >&2
|
|
||||||
else
|
|
||||||
echo "[STD] ${name}: FAIL" >&2
|
|
||||||
echo "$out" | sed -n '1,120p' >&2 || true
|
|
||||||
overall_rc=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $overall_rc -ne 0 ]]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "[JIT Smoke] Core VM/JIT (plugins disabled)" >&2
|
|
||||||
NYASH_DISABLE_PLUGINS=1 NYASH_CLI_VERBOSE=1 "$ROOT_DIR/tools/smoke_vm_jit.sh" >/tmp/nyash-jit-core.out
|
|
||||||
grep -q '^✅ smoke done' /tmp/nyash-jit-core.out || { echo "FAIL: core VM/JIT smoke" >&2; cat /tmp/nyash-jit-core.out; exit 1; }
|
|
||||||
echo "PASS: core VM/JIT smoke" >&2
|
|
||||||
|
|
||||||
echo "[JIT Smoke] Examples (string_p0, array_p0, map_p0)" >&2
|
|
||||||
set -o pipefail
|
|
||||||
NYASH_DISABLE_PLUGINS=1 "$BIN" --backend vm "$ROOT_DIR/apps/examples/string_p0.nyash" > /tmp/nyash-ex-str.out
|
|
||||||
NYASH_DISABLE_PLUGINS=1 "$BIN" --backend vm "$ROOT_DIR/apps/examples/array_p0.nyash" > /tmp/nyash-ex-arr.out
|
|
||||||
NYASH_DISABLE_PLUGINS=1 "$BIN" --backend vm "$ROOT_DIR/apps/examples/map_p0.nyash" > /tmp/nyash-ex-map.out
|
|
||||||
if rg -q '^Result:\s*0\b' /tmp/nyash-ex-str.out && rg -q '^Result:\s*0\b' /tmp/nyash-ex-arr.out && rg -q '^Result:\s*0\b' /tmp/nyash-ex-map.out; then
|
|
||||||
echo "PASS: examples" >&2
|
|
||||||
else
|
|
||||||
echo "FAIL: examples" >&2; { echo '--- string_p0 ---'; cat /tmp/nyash-ex-str.out; echo '--- array_p0 ---'; cat /tmp/nyash-ex-arr.out; echo '--- map_p0 ---'; cat /tmp/nyash-ex-map.out; } >&2; exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "All PASS" >&2
|
|
||||||
|
|
||||||
# Optional: ensure ny_plugins load does not break core path
|
|
||||||
echo "[JIT Smoke] Plugins opt-in load (sanity)" >&2
|
|
||||||
NYASH_LOAD_NY_PLUGINS=1 "$BIN" --backend vm "$ROOT_DIR/apps/examples/string_p0.nyash" > /tmp/nyash-ex-plugins.out || true
|
|
||||||
if rg -q '^Result:\s*0\b' /tmp/nyash-ex-plugins.out; then
|
|
||||||
echo "PASS: plugins load (sanity)" >&2
|
|
||||||
else
|
|
||||||
echo "WARN: plugins load path did not complete cleanly; continuing (optional)" >&2
|
|
||||||
sed -n '1,120p' /tmp/nyash-ex-plugins.out >&2 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run std Ny smokes only when explicitly enabled via env
|
|
||||||
run_std_smokes
|
|
||||||
@ -47,7 +47,7 @@ cargo build -q ${MODE:+--${MODE}} --features llvm
|
|||||||
|
|
||||||
echo "[llvm-smoke] running --backend llvm on examples/llvm11_core_smoke.nyash ..." >&2
|
echo "[llvm-smoke] running --backend llvm on examples/llvm11_core_smoke.nyash ..." >&2
|
||||||
rm -f "$OBJ"
|
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
|
if [[ ! -f "$OBJ" ]]; then
|
||||||
echo "error: expected object not found: $OBJ" >&2
|
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
|
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) ---
|
# --- AOT smoke: apps/ny-llvm-smoke (Array get/set/print) ---
|
||||||
if [[ "${NYASH_LLVM_ARRAY_SMOKE:-0}" == "1" ]]; then
|
if [[ "${NYASH_LLVM_ARRAY_SMOKE:-0}" == "1" ]]; then
|
||||||
echo "[llvm-smoke] building + linking apps/ny-llvm-smoke ..." >&2
|
echo "[llvm-smoke] building + linking apps/ny-llvm-smoke ..." >&2
|
||||||
|
|||||||
26
tools/smokes/archive/aot_smoke_cranelift.sh
Normal file
26
tools/smokes/archive/aot_smoke_cranelift.sh
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Archived: Cranelift AOT smoke helper (not maintained currently)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP=${1:-apps/smokes/jit_aot_string_min.nyash}
|
||||||
|
BASE=${2:-app}
|
||||||
|
|
||||||
|
BIN=./target/release/nyash
|
||||||
|
OBJ_DIR=target/aot_objects
|
||||||
|
OBJ=$OBJ_DIR/${BASE}.o
|
||||||
|
EXE=${BASE}
|
||||||
|
|
||||||
|
mkdir -p "$OBJ_DIR"
|
||||||
|
|
||||||
|
echo "[AOT] building core (if needed)"
|
||||||
|
cargo build --release --features cranelift-jit >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
echo "[AOT] lowering: $APP -> $OBJ"
|
||||||
|
NYASH_DISABLE_PLUGINS=1 NYASH_AOT_OBJECT_OUT="$OBJ" "$BIN" --jit-direct "$APP"
|
||||||
|
|
||||||
|
echo "[AOT] linking: $EXE (requires libnyrt.a present in target/release)"
|
||||||
|
cc "$OBJ" -L target/release -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o "$EXE"
|
||||||
|
|
||||||
|
echo "[AOT] run: ./$EXE"
|
||||||
|
"./$EXE" || true
|
||||||
|
|
||||||
20
tools/smokes/archive/jit_smoke.sh
Normal file
20
tools/smokes/archive/jit_smoke.sh
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Archived: JIT smoke (not maintained in current phase). Kept for reference.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/../../" && pwd)
|
||||||
|
|
||||||
|
BIN="$ROOT_DIR/target/release/nyash"
|
||||||
|
if [ ! -x "$BIN" ]; then
|
||||||
|
echo "Building nyash (release, JIT)..." >&2
|
||||||
|
cargo build --release --features cranelift-jit >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[JIT Smoke] Core VM/JIT (plugins disabled)" >&2
|
||||||
|
NYASH_DISABLE_PLUGINS=1 NYASH_CLI_VERBOSE=1 "$ROOT_DIR/tools/smokes/archive/smoke_vm_jit.sh" >/tmp/nyash-jit-core.out
|
||||||
|
grep -q '^✅ smoke done' /tmp/nyash-jit-core.out || { echo "FAIL: core VM/JIT smoke" >&2; cat /tmp/nyash-jit-core.out; exit 1; }
|
||||||
|
echo "PASS: core VM/JIT smoke" >&2
|
||||||
|
|
||||||
|
echo "All PASS (archived JIT smoke)" >&2
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user