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:
Selfhosting Dev
2025-09-16 23:49:36 +09:00
parent 97a76c0571
commit 5c9213cd03
104 changed files with 8094 additions and 2930 deletions

View File

@ -1,4 +1,4 @@
name: Smoke (Phase 10.10)
name: LLVM Curated Smoke
on:
push:
@ -20,12 +20,13 @@ on:
- 'docs/**'
jobs:
smoke:
llvm-curated:
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
# Disable external plugins to keep CI deterministic
NYASH_DISABLE_PLUGINS: '1'
NYASH_LLVM_USE_HARNESS: '1'
steps:
- name: Checkout
uses: actions/checkout@v4
@ -44,10 +45,14 @@ jobs:
restore-keys: |
${{ runner.os }}-cargo-
- name: Run smoke script
run: bash tools/smoke_phase_10_10.sh
- name: Run curated LLVM smokes (PHI-on)
run: bash tools/smokes/curated_llvm.sh
- name: Run curated LLVM smokes (PHI-off)
run: bash tools/smokes/curated_llvm.sh --phi-off
jit-direct-smoke:
if: false # archived: JIT path not maintained in current phase
runs-on: ubuntu-latest
strategy:
fail-fast: false
@ -101,6 +106,7 @@ jobs:
timeout 15s "$BIN" --jit-direct apps/tests/mir-branch-multi/main.nyash
smoke-compile-events:
if: false # archived: JIT compile-events not maintained in current phase
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always

View File

@ -13,6 +13,23 @@ What Changed (today)
- CLI `--using` を追加(`--using "ns as Alias"` / `--using '"apps/foo.nyash" as Foo'`)。
- フィールドは box 先頭のみルールのリンタを Runner に追加(`NYASH_FIELDS_TOP_STRICT=1` でエラー)。
- Syntax Torture スイートの実行正規化(末行比較)。一部テスト本文を Nyash 仕様に合わせて修正。
- JSON v0 仕様に Stage3 ードBreak/Continue/Throw/Tryを追記。Parser Stage3 設計メモの現状/残課題を更新。
- LLVM smoke に Stage3 loop サンプルbreak/continue + throw/try/catch/finally 付き)を追加(`NYASH_LLVM_STAGE3_SMOKE=1`)。
- Bridge (`json_v0`) に Stage3 throw/try の実稼働ルートを追加(`NYASH_BRIDGE_THROW_ENABLE=1` / `NYASH_BRIDGE_TRY_ENABLE=1` で MIR Throw/Catch を生成)。
Decision (Phase15 wrapup)
- MIR13 移行PHI 非生成): Phase15 の締めとして、MIR 生成層Bridge/Builderは PHI を生成しない方針に切替。PHI 合成は LLVM 層llvmlite/Resolverに集約。
- LoopForm は次フェーズMIR18で導入: まずは MIR14 を維持し、次フェーズで `LoopHeader/Enter/Latch` 等の占位命令を追加。現行 Phase15 は CFG パターン検知でループ搬送値を合成。
- 例外は段階導入: Throw/Catch は現行維持Bridge ゲートで出力可。Try/Finally の構造化は将来の TryRegion で検討。
Next Focus (Throw/Try — LLVM first)
- ブリッジ設計: `emit_degraded_throw` の差し替え方針を策定し、JSON v0 `Try` ノード → MIR 変換の仕様を決めるStage-3 例外モデル)。
- MIR Builder/Runtime 調査: Rust VM/PyVM の `ControlFlow::Throw` 経路と既存 TryCatch 降格の挙動を整理。必要に応じて docs と CURRENT_TASK に反映。
- PyVM 設計: 例外モデルをどこまで Python 側に実装するか決め、最小テスト計画を用意。
- LLVM 実装方針: Throw/Try の MIR 命令を LLVM 側がどう扱うかpanic扱い or fallbackを設計し、smoke 更新案を作る。
- テスト計画: JSON フィクスチャと `tools/llvm_smoke.sh` を中心に Stage-3 例外用のスモーク/単体テストを整備。
※ Cranelift/JIT 系は当面対象外。ビルド時も LLVM のみを有効化JIT 関連 feature/CI は無視)。
- llvmlite/AOT本戦強化 — コアコレクション配線とエントリ統一
- Array/Map の BoxCall を NyRT ハンドルAPIに直結
@ -42,11 +59,12 @@ Quick Next (today)
- `--stage3` CLI フラグから ParserBox へ渡す導線を追加。
- `docs/reference/architecture/parser_mvp_stage3.md` に Stage3 設計を記録。
5) 自己ホスト経路で Ny 実装切替のゲート準備(現状は Python MVP 優先を維持)。
6) テスト:
6) テスト:
- `source tools/dev_env.sh pyvm`
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_smoke.sh`
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_bridge_smoke.sh`
- TortureVM中心: `(cd tests/nyash_syntax_torture_20250916 && BACKENDS="vm" NYASH_BIN=../../target/release/nyash bash run_spec_smoke.sh)`
- LLVM Stage3 smoke (手動): `NYASH_LLVM_STAGE3_SMOKE=1 ./tools/llvm_smoke.sh release`
- Runner/Bridge 実行系
- `--ny-parser-pipe``NYASH_PIPE_USE_PYVM=1` で PyVM に委譲exit code 判定に統一)。
- 自己ホスト JSON 生成は Python MVP を優先、LLVM EXE/インラインVMを段階フォールバック。
@ -148,6 +166,17 @@ Recommended Next (short list)
- `mod.rs` の残置ヘルパusingの候補提示・環境注入ログ`pipeline/dispatch` へ集約し、`mod.rs` を最小のオーケストレーションに。
- Namespaces Phase1実装着手: BoxIndex 構築・3段階解決・toml aliases・曖昧エラー改善・トレース
Smoke Policy (Phase15)
- PyVM: 一部チェックのみasync/nowait/await/GC/sync は対象外)
- LLVM: フル対応llvmlite harness`tools/smokes/curated_llvm.sh [--phi-off]` を利用
- JIT: 未整備JIT向けスモークは `tools/smokes/archive/` に移管)
MIR13 PlanPhase15 終盤)
- Bridge/Builder: PHI を生成しない受理は維持。If/Loop の合流は LLVM Resolver に任せる。
- llvmlite: Resolver を使い、BB 先頭で PHI 合成。ループは preheader/cond/body の CFG から搬送値を復元break は exit 側でマージ)。
- Smoke: LLVM はまず looponlybreak/continueを常時緑化。例外系throw/tryは IR 降ろし込み整備後に復帰。
- 詳細設計: `docs/private/papers/paper-e-loop-signal-ir/mir-evolution-plan.md` に MIR14→MIR13→MIR17 の段階的移行計画を記載。
Array/Map Literals PlanSyntax Sugar
- Stage1: Array literal `[e1, e2, ...]` を実装(ゲート: `NYASH_SYNTAX_SUGAR_LEVEL=basic|full` または `NYASH_ENABLE_ARRAY_LITERAL=1`)。
- Lowering: `new ArrayBox()` → 各要素を評価 → `.push(elem)` を左から右に順に発行 → 最後に配列値を返す。

BIN
app_stage3_loop Normal file

Binary file not shown.

View 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
}
}

View 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
View File

@ -40,7 +40,10 @@ type_rules = [
]
"#;
fs::write(&grammar_file, minimal).expect("write minimal unified-grammar.toml");
println!("cargo:warning=Created minimal grammar at {}", grammar_file.display());
println!(
"cargo:warning=Created minimal grammar at {}",
grammar_file.display()
);
}
// Read and very light parse: collect
@ -69,7 +72,10 @@ type_rules = [
for line in content.lines() {
let s = line.trim();
if s.starts_with("[keywords.") && s.ends_with("]") {
let name = s.trim_start_matches("[keywords.").trim_end_matches("]").to_string();
let name = s
.trim_start_matches("[keywords.")
.trim_end_matches("]")
.to_string();
current_key = Some(name);
in_operators_add = false;
in_operators_sub = false;
@ -77,25 +83,66 @@ type_rules = [
in_operators_div = false;
continue;
}
if s == "[operators.add]" { current_key = None; in_operators_add = true; in_operators_sub=false; in_operators_mul=false; in_operators_div=false; in_type_rules = false; continue; }
if s == "[operators.sub]" { current_key = None; in_operators_add = false; in_operators_sub=true; in_operators_mul=false; in_operators_div=false; in_type_rules = false; continue; }
if s == "[operators.mul]" { current_key = None; in_operators_add = false; in_operators_sub=false; in_operators_mul=true; in_operators_div=false; in_type_rules = false; continue; }
if s == "[operators.div]" { current_key = None; in_operators_add = false; in_operators_sub=false; in_operators_mul=false; in_operators_div=true; in_type_rules = false; continue; }
if s == "[operators.add]" {
current_key = None;
in_operators_add = true;
in_operators_sub = false;
in_operators_mul = false;
in_operators_div = false;
in_type_rules = false;
continue;
}
if s == "[operators.sub]" {
current_key = None;
in_operators_add = false;
in_operators_sub = true;
in_operators_mul = false;
in_operators_div = false;
in_type_rules = false;
continue;
}
if s == "[operators.mul]" {
current_key = None;
in_operators_add = false;
in_operators_sub = false;
in_operators_mul = true;
in_operators_div = false;
in_type_rules = false;
continue;
}
if s == "[operators.div]" {
current_key = None;
in_operators_add = false;
in_operators_sub = false;
in_operators_mul = false;
in_operators_div = true;
in_type_rules = false;
continue;
}
if let Some(ref key) = current_key {
if let Some(rest) = s.strip_prefix("token") {
if let Some(eq) = rest.find('=') {
let val = rest[eq+1..].trim().trim_matches('"').to_string();
let val = rest[eq + 1..].trim().trim_matches('"').to_string();
entries.push((key.clone(), val));
}
}
}
if in_operators_add || in_operators_sub || in_operators_mul || in_operators_div {
if s.starts_with("type_rules") && s.contains('[') { in_type_rules = true; continue; }
if s.starts_with("type_rules") && s.contains('[') {
in_type_rules = true;
continue;
}
if in_type_rules {
if s.starts_with(']') { in_type_rules = false; continue; }
if s.starts_with(']') {
in_type_rules = false;
continue;
}
// Expect lines like: { left = "String", right = "String", result = "String", action = "concat" },
if s.starts_with('{') && s.ends_with("},") || s.ends_with('}') {
let inner = s.trim_start_matches('{').trim_end_matches('}').trim_end_matches(',');
let inner = s
.trim_start_matches('{')
.trim_end_matches('}')
.trim_end_matches(',');
let mut left = String::new();
let mut right = String::new();
let mut result = String::new();
@ -104,7 +151,7 @@ type_rules = [
let kv = part.trim();
if let Some(eq) = kv.find('=') {
let key = kv[..eq].trim();
let val = kv[eq+1..].trim().trim_matches('"').to_string();
let val = kv[eq + 1..].trim().trim_matches('"').to_string();
match key {
"left" => left = val,
"right" => right = val,
@ -114,21 +161,35 @@ type_rules = [
}
}
}
if !left.is_empty() && !right.is_empty() && !result.is_empty() && !action.is_empty() {
if in_operators_add { add_rules.push((left, right, result, action)); }
else if in_operators_sub { sub_rules.push((left, right, result, action)); }
else if in_operators_mul { mul_rules.push((left, right, result, action)); }
else if in_operators_div { div_rules.push((left, right, result, action)); }
if !left.is_empty()
&& !right.is_empty()
&& !result.is_empty()
&& !action.is_empty()
{
if in_operators_add {
add_rules.push((left, right, result, action));
} else if in_operators_sub {
sub_rules.push((left, right, result, action));
} else if in_operators_mul {
mul_rules.push((left, right, result, action));
} else if in_operators_div {
div_rules.push((left, right, result, action));
}
}
}
}
if let Some(rest) = s.strip_prefix("coercion_strategy") {
if let Some(eq) = rest.find('=') {
let val = rest[eq+1..].trim().trim_matches('"').to_string();
if in_operators_add { add_coercion = Some(val.clone()); }
else if in_operators_sub { sub_coercion = Some(val.clone()); }
else if in_operators_mul { mul_coercion = Some(val.clone()); }
else if in_operators_div { div_coercion = Some(val.clone()); }
let val = rest[eq + 1..].trim().trim_matches('"').to_string();
if in_operators_add {
add_coercion = Some(val.clone());
} else if in_operators_sub {
sub_coercion = Some(val.clone());
} else if in_operators_mul {
mul_coercion = Some(val.clone());
} else if in_operators_div {
div_coercion = Some(val.clone());
}
}
}
}
@ -136,27 +197,102 @@ type_rules = [
// Default rules if none present in TOML (keep codegen deterministic)
if add_rules.is_empty() {
add_rules.push(("String".into(), "String".into(), "String".into(), "concat".into()));
add_rules.push(("String".into(), "Integer".into(), "String".into(), "concat".into()));
add_rules.push(("Integer".into(), "String".into(), "String".into(), "concat".into()));
add_rules.push(("String".into(), "Bool".into(), "String".into(), "concat".into()));
add_rules.push(("Bool".into(), "String".into(), "String".into(), "concat".into()));
add_rules.push(("String".into(), "Other".into(), "String".into(), "concat".into()));
add_rules.push(("Other".into(), "String".into(), "String".into(), "concat".into()));
add_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "add_i64".into()));
add_rules.push(("Float".into(), "Float".into(), "Float".into(), "add_f64".into()));
add_rules.push((
"String".into(),
"String".into(),
"String".into(),
"concat".into(),
));
add_rules.push((
"String".into(),
"Integer".into(),
"String".into(),
"concat".into(),
));
add_rules.push((
"Integer".into(),
"String".into(),
"String".into(),
"concat".into(),
));
add_rules.push((
"String".into(),
"Bool".into(),
"String".into(),
"concat".into(),
));
add_rules.push((
"Bool".into(),
"String".into(),
"String".into(),
"concat".into(),
));
add_rules.push((
"String".into(),
"Other".into(),
"String".into(),
"concat".into(),
));
add_rules.push((
"Other".into(),
"String".into(),
"String".into(),
"concat".into(),
));
add_rules.push((
"Integer".into(),
"Integer".into(),
"Integer".into(),
"add_i64".into(),
));
add_rules.push((
"Float".into(),
"Float".into(),
"Float".into(),
"add_f64".into(),
));
}
if sub_rules.is_empty() {
sub_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "sub_i64".into()));
sub_rules.push(("Float".into(), "Float".into(), "Float".into(), "sub_f64".into()));
sub_rules.push((
"Integer".into(),
"Integer".into(),
"Integer".into(),
"sub_i64".into(),
));
sub_rules.push((
"Float".into(),
"Float".into(),
"Float".into(),
"sub_f64".into(),
));
}
if mul_rules.is_empty() {
mul_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "mul_i64".into()));
mul_rules.push(("Float".into(), "Float".into(), "Float".into(), "mul_f64".into()));
mul_rules.push((
"Integer".into(),
"Integer".into(),
"Integer".into(),
"mul_i64".into(),
));
mul_rules.push((
"Float".into(),
"Float".into(),
"Float".into(),
"mul_f64".into(),
));
}
if div_rules.is_empty() {
div_rules.push(("Integer".into(), "Integer".into(), "Integer".into(), "div_i64".into()));
div_rules.push(("Float".into(), "Float".into(), "Float".into(), "div_f64".into()));
div_rules.push((
"Integer".into(),
"Integer".into(),
"Integer".into(),
"div_i64".into(),
));
div_rules.push((
"Float".into(),
"Float".into(),
"Float".into(),
"div_f64".into(),
));
}
// Generate Rust code
@ -171,32 +307,56 @@ type_rules = [
let sub_coercion_val = sub_coercion.unwrap_or_else(|| "numeric_only".to_string());
let mul_coercion_val = mul_coercion.unwrap_or_else(|| "numeric_only".to_string());
let div_coercion_val = div_coercion.unwrap_or_else(|| "numeric_only".to_string());
code.push_str(&format!("\npub static OPERATORS_ADD_COERCION: &str = \"{}\";\n", add_coercion_val));
code.push_str(&format!("pub static OPERATORS_SUB_COERCION: &str = \"{}\";\n", sub_coercion_val));
code.push_str(&format!("pub static OPERATORS_MUL_COERCION: &str = \"{}\";\n", mul_coercion_val));
code.push_str(&format!("pub static OPERATORS_DIV_COERCION: &str = \"{}\";\n", div_coercion_val));
code.push_str(&format!(
"\npub static OPERATORS_ADD_COERCION: &str = \"{}\";\n",
add_coercion_val
));
code.push_str(&format!(
"pub static OPERATORS_SUB_COERCION: &str = \"{}\";\n",
sub_coercion_val
));
code.push_str(&format!(
"pub static OPERATORS_MUL_COERCION: &str = \"{}\";\n",
mul_coercion_val
));
code.push_str(&format!(
"pub static OPERATORS_DIV_COERCION: &str = \"{}\";\n",
div_coercion_val
));
// Emit add rules
code.push_str("pub static OPERATORS_ADD_RULES: &[(&str, &str, &str, &str)] = &[\n");
for (l, r, res, act) in &add_rules {
code.push_str(&format!(" (\"{}\", \"{}\", \"{}\", \"{}\"),\n", l, r, res, act));
code.push_str(&format!(
" (\"{}\", \"{}\", \"{}\", \"{}\"),\n",
l, r, res, act
));
}
code.push_str("];");
// Emit sub rules
code.push_str("\npub static OPERATORS_SUB_RULES: &[(&str, &str, &str, &str)] = &[\n");
for (l, r, res, act) in &sub_rules {
code.push_str(&format!(" (\"{}\", \"{}\", \"{}\", \"{}\"),\n", l, r, res, act));
code.push_str(&format!(
" (\"{}\", \"{}\", \"{}\", \"{}\"),\n",
l, r, res, act
));
}
code.push_str("];");
// Emit mul rules
code.push_str("\npub static OPERATORS_MUL_RULES: &[(&str, &str, &str, &str)] = &[\n");
for (l, r, res, act) in &mul_rules {
code.push_str(&format!(" (\"{}\", \"{}\", \"{}\", \"{}\"),\n", l, r, res, act));
code.push_str(&format!(
" (\"{}\", \"{}\", \"{}\", \"{}\"),\n",
l, r, res, act
));
}
code.push_str("];");
// Emit div rules
code.push_str("\npub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[\n");
for (l, r, res, act) in &div_rules {
code.push_str(&format!(" (\"{}\", \"{}\", \"{}\", \"{}\"),\n", l, r, res, act));
code.push_str(&format!(
" (\"{}\", \"{}\", \"{}\", \"{}\"),\n",
l, r, res, act
));
}
code.push_str("];");
code.push_str(
@ -207,7 +367,8 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
}
None
}
"#);
"#,
);
// --- Naive parse for syntax rules (statements/expressions) ---
let mut syntax_statements: Vec<String> = Vec::new();
@ -216,24 +377,43 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
let mut in_syntax_expressions = false;
for line in content.lines() {
let s = line.trim();
if s == "[syntax.statements]" { in_syntax_statements = true; in_syntax_expressions = false; continue; }
if s == "[syntax.expressions]" { in_syntax_statements = false; in_syntax_expressions = true; continue; }
if s.starts_with('[') { in_syntax_statements = false; in_syntax_expressions = false; }
if s == "[syntax.statements]" {
in_syntax_statements = true;
in_syntax_expressions = false;
continue;
}
if s == "[syntax.expressions]" {
in_syntax_statements = false;
in_syntax_expressions = true;
continue;
}
if s.starts_with('[') {
in_syntax_statements = false;
in_syntax_expressions = false;
}
if in_syntax_statements {
if let Some(rest) = s.strip_prefix("allow") {
if let Some(eq) = rest.find('=') { let arr = rest[eq+1..].trim();
if let Some(eq) = rest.find('=') {
let arr = rest[eq + 1..].trim();
// Expect [ "if", "loop", ... ] possibly spanning multiple lines; simple split for this snapshot
for part in arr.trim_matches(&['[',']'][..]).split(',') {
let v = part.trim().trim_matches('"'); if !v.is_empty() { syntax_statements.push(v.to_string()); }
for part in arr.trim_matches(&['[', ']'][..]).split(',') {
let v = part.trim().trim_matches('"');
if !v.is_empty() {
syntax_statements.push(v.to_string());
}
}
}
}
}
if in_syntax_expressions {
if let Some(rest) = s.strip_prefix("allow_binops") {
if let Some(eq) = rest.find('=') { let arr = rest[eq+1..].trim();
for part in arr.trim_matches(&['[',']'][..]).split(',') {
let v = part.trim().trim_matches('"'); if !v.is_empty() { syntax_binops.push(v.to_string()); }
if let Some(eq) = rest.find('=') {
let arr = rest[eq + 1..].trim();
for part in arr.trim_matches(&['[', ']'][..]).split(',') {
let v = part.trim().trim_matches('"');
if !v.is_empty() {
syntax_binops.push(v.to_string());
}
}
}
}
@ -241,9 +421,23 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
}
if syntax_statements.is_empty() {
syntax_statements = vec![
"box".into(), "global".into(), "function".into(), "static".into(),
"if".into(), "loop".into(), "break".into(), "return".into(), "print".into(),
"nowait".into(), "include".into(), "local".into(), "outbox".into(), "try".into(), "throw".into(), "using".into(), "from".into()
"box".into(),
"global".into(),
"function".into(),
"static".into(),
"if".into(),
"loop".into(),
"break".into(),
"return".into(),
"print".into(),
"nowait".into(),
"include".into(),
"local".into(),
"outbox".into(),
"try".into(),
"throw".into(),
"using".into(),
"from".into(),
];
}
if syntax_binops.is_empty() {
@ -251,10 +445,14 @@ pub fn lookup_keyword(word: &str) -> Option<&'static str> {
}
// Emit syntax arrays
code.push_str("\npub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[\n");
for k in &syntax_statements { code.push_str(&format!(" \"{}\",\n", k)); }
for k in &syntax_statements {
code.push_str(&format!(" \"{}\",\n", k));
}
code.push_str("];");
code.push_str("\npub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[\n");
for k in &syntax_binops { code.push_str(&format!(" \"{}\",\n", k)); }
for k in &syntax_binops {
code.push_str(&format!(" \"{}\",\n", k));
}
code.push_str("];");
fs::write(&out_file, code).expect("write generated.rs");

View File

@ -6,4 +6,3 @@
pub fn version() -> &'static str {
"0.1.0-dev"
}

View File

@ -2,4 +2,3 @@ fn main() {
env_logger::init();
println!("nyash-next: workspace skeleton is ready.");
}

View File

@ -111,14 +111,26 @@ pub extern "C" fn nyash_string_substring_hii_export(h: i64, start: i64, end: i64
return 0;
}
let s = if let Some(obj) = handles::get(h as u64) {
if let Some(sb) = obj.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
} else { String::new() };
if let Some(sb) = obj.as_any().downcast_ref::<StringBox>() {
sb.value.clone()
} else {
String::new()
}
} else {
String::new()
};
let n = s.len() as i64;
let mut st = if start < 0 { 0 } else { start };
let mut en = if end < 0 { 0 } else { end };
if st > n { st = n; }
if en > n { en = n; }
if en < st { std::mem::swap(&mut st, &mut en); }
if st > n {
st = n;
}
if en > n {
en = n;
}
if en < st {
std::mem::swap(&mut st, &mut en);
}
let (st_u, en_u) = (st as usize, en as usize);
let sub = s.get(st_u.min(s.len())..en_u.min(s.len())).unwrap_or("");
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(StringBox::new(sub.to_string()));
@ -133,16 +145,38 @@ pub extern "C" fn nyash_string_lastindexof_hh_export(h: i64, n: i64) -> i64 {
use nyash_rust::{box_trait::StringBox, jit::rt::handles};
let hay = if h > 0 {
if let Some(o) = handles::get(h as u64) {
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
} else { String::new() }
} else { String::new() };
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() {
sb.value.clone()
} else {
String::new()
}
} else {
String::new()
}
} else {
String::new()
};
let nee = if n > 0 {
if let Some(o) = handles::get(n as u64) {
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() { sb.value.clone() } else { String::new() }
} else { String::new() }
} else { String::new() };
if nee.is_empty() { return hay.len() as i64; }
if let Some(pos) = hay.rfind(&nee) { pos as i64 } else { -1 }
if let Some(sb) = o.as_any().downcast_ref::<StringBox>() {
sb.value.clone()
} else {
String::new()
}
} else {
String::new()
}
} else {
String::new()
};
if nee.is_empty() {
return hay.len() as i64;
}
if let Some(pos) = hay.rfind(&nee) {
pos as i64
} else {
-1
}
}
// box.from_i8_string(ptr) -> handle
@ -181,7 +215,10 @@ pub extern "C" fn nyash_box_from_f64(val: f64) -> i64 {
// Helper: build an IntegerBox and return a handle
#[export_name = "nyash.box.from_i64"]
pub extern "C" fn nyash_box_from_i64(val: i64) -> i64 {
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
use nyash_rust::{
box_trait::{IntegerBox, NyashBox},
jit::rt::handles,
};
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(IntegerBox::new(val));
handles::to_handle(arc) as i64
}
@ -545,8 +582,6 @@ pub extern "C" fn nyash_console_birth_h_export() -> i64 {
0
}
// ArrayBox birth shim for AOT/JIT handle-based creation
#[export_name = "nyash.array.birth_h"]
pub extern "C" fn nyash_array_birth_h_export() -> i64 {

View File

@ -9,28 +9,23 @@ pub extern "C" fn nyash_box_birth_h_export(type_id: i64) -> i64 {
}
let tid = type_id as u32;
// Map type_id back to type name
let name_opt = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
.read()
.ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| m.into_iter().find(|(_k, v)| *v == tid).map(|(k, _v)| k));
if let Some(box_type) = name_opt {
if let Some(meta) = nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(tid) {
if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() {
if let Ok(b) = host_g.create_box(&box_type, &[]) {
if let Ok(b) = host_g.create_box(&meta.box_type, &[]) {
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
std::sync::Arc::from(b);
let h = nyash_rust::jit::rt::handles::to_handle(arc);
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
println!(
"nyrt: birth_h {} (type_id={}) -> handle={}",
box_type, tid, h
meta.box_type, meta.type_id, h
);
}
return h as i64;
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!(
"nyrt: birth_h {} (type_id={}) FAILED: create_box",
box_type, tid
meta.box_type, tid
);
}
}
@ -47,33 +42,19 @@ pub extern "C" fn nyash_box_birth_i64_export(type_id: i64, argc: i64, a1: i64, a
if type_id <= 0 {
return 0;
}
let mut invoke: Option<
unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32,
> = None;
// Resolve invoke_fn via temporary instance
let box_type_name = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
.read()
.ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| {
m.into_iter()
.find(|(_k, v)| *v == (type_id as u32))
.map(|(k, _v)| k)
})
.unwrap_or_else(|| "PluginBox".to_string());
if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() {
if let Ok(b) = host_g.create_box(&box_type_name, &[]) {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
invoke = Some(p.inner.invoke_fn);
}
}
}
if invoke.is_none() {
// Resolve invoke_fn via loader metadata
let meta = if let Some(meta) =
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(type_id as u32)
{
meta
} else {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("nyrt: birth_i64 (type_id={}) FAILED: no invoke", type_id);
eprintln!("nyrt: birth_i64 (type_id={}) FAILED: type map", type_id);
}
return 0;
}
};
let box_type_name = meta.box_type.clone();
let invoke_fn = meta.invoke_fn;
let method_id: u32 = 0; // birth
let instance_id: u32 = 0; // static
// Build TLV args
@ -186,7 +167,7 @@ pub extern "C" fn nyash_box_birth_i64_export(type_id: i64, argc: i64, a1: i64, a
let mut out = vec![0u8; 1024];
let mut out_len: usize = out.len();
let rc = unsafe {
invoke.unwrap()(
invoke_fn(
type_id as u32,
method_id,
instance_id,
@ -219,7 +200,7 @@ pub extern "C" fn nyash_box_birth_i64_export(type_id: i64, argc: i64, a1: i64, a
box_type_name.clone(),
r_type,
r_inst,
invoke.unwrap(),
invoke_fn,
);
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> = std::sync::Arc::new(pb);
let h = nyash_rust::jit::rt::handles::to_handle(arc);

View File

@ -197,22 +197,20 @@ pub extern "C" fn nyash_future_spawn_method_h(
let r_type = u32::from_le_bytes(t);
let r_inst = u32::from_le_bytes(i);
// Map type_id -> box type name (best-effort)
let box_type_name =
nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host(
)
.read()
.ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| {
m.into_iter().find(|(_k, v)| *v == r_type).map(|(k, _v)| k)
})
.unwrap_or_else(|| "PluginBox".to_string());
let meta_opt =
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
let (box_type_name, invoke_ptr, fini_id) = if let Some(meta) = meta_opt
{
(meta.box_type.clone(), meta.invoke_fn, meta.fini_method_id)
} else {
("PluginBox".to_string(), inv, None)
};
let pb = nyash_rust::runtime::plugin_loader_v2::construct_plugin_box(
box_type_name,
r_type,
inv,
invoke_ptr,
r_inst,
None,
fini_id,
);
fut_box.set_result(Box::new(pb));
return;
@ -260,15 +258,8 @@ pub extern "C" fn nyash_future_spawn_instance3_i64(a0: i64, a1: i64, a2: i64, ar
}
let invoke = invoke.unwrap();
// Determine box type name from type_id
let box_type_name = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
.read()
.ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| {
m.into_iter()
.find(|(_k, v)| *v == real_type_id)
.map(|(k, _v)| k)
})
let box_type_name = nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(real_type_id)
.map(|meta| meta.box_type)
.unwrap_or_else(|| "PluginBox".to_string());
// Determine method name string (from a1 handle→StringBox, or a1 as C string pointer, or legacy VM args)
let mut method_name: Option<String> = None;

View File

@ -295,20 +295,18 @@ pub extern "C" fn nyash_plugin_invoke3_i64(
let r_type = u32::from_le_bytes(t);
let r_inst = u32::from_le_bytes(i);
// Build PluginBoxV2 and register into handle-registry
let box_type_name =
nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host()
.read()
.ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| {
m.into_iter().find(|(_k, v)| *v == r_type).map(|(k, _v)| k)
})
.unwrap_or_else(|| "PluginBox".to_string());
let meta_opt =
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt {
(meta.box_type.clone(), meta.invoke_fn)
} else {
("PluginBox".to_string(), invoke.unwrap())
};
let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(
box_type_name.clone(),
r_type,
r_inst,
invoke.unwrap(),
invoke_ptr,
);
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
std::sync::Arc::new(pb);
@ -977,11 +975,18 @@ pub extern "C" fn nyash_plugin_invoke_by_name_i64(
i.copy_from_slice(&payload[4..8]);
let r_type = u32::from_le_bytes(t);
let r_inst = u32::from_le_bytes(i);
let meta_opt =
nyash_rust::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt {
(meta.box_type.clone(), meta.invoke_fn)
} else {
(box_type.clone(), invoke.unwrap())
};
let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(
box_type.clone(),
box_type_name,
r_type,
r_inst,
invoke.unwrap(),
invoke_ptr,
);
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
std::sync::Arc::new(pb);

View File

@ -64,13 +64,27 @@ pub extern "C" fn nyash_map_get_h(handle: i64, key: i64) -> i64 {
// get_hh: (map_handle, key_handle) -> value_handle
#[export_name = "nyash.map.get_hh"]
pub extern "C" fn nyash_map_get_hh(handle: i64, key_any: i64) -> i64 {
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
if handle <= 0 { return 0; }
use nyash_rust::{
box_trait::{IntegerBox, NyashBox},
jit::rt::handles,
};
if handle <= 0 {
return 0;
}
if let Some(obj) = handles::get(handle as u64) {
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
if let Some(map) = obj
.as_any()
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
{
let key_box: Box<dyn NyashBox> = if key_any > 0 {
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
} else { Box::new(IntegerBox::new(key_any)) };
if let Some(k) = handles::get(key_any as u64) {
k.clone_box()
} else {
Box::new(IntegerBox::new(key_any))
}
} else {
Box::new(IntegerBox::new(key_any))
};
let v = map.get(key_box);
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::from(v);
let h = handles::to_handle(arc);
@ -80,7 +94,6 @@ pub extern "C" fn nyash_map_get_hh(handle: i64, key_any: i64) -> i64 {
0
}
// set_h: (map_handle, key_i64, val) -> i64 (ignored/0)
#[export_name = "nyash.map.set_h"]
pub extern "C" fn nyash_map_set_h(handle: i64, key: i64, val: i64) -> i64 {
@ -125,20 +138,39 @@ pub extern "C" fn nyash_map_set_h(handle: i64, key: i64, val: i64) -> i64 {
0
}
// set_hh: (map_handle, key_any: handle or i64, val_any: handle or i64) -> i64
#[export_name = "nyash.map.set_hh"]
pub extern "C" fn nyash_map_set_hh(handle: i64, key_any: i64, val_any: i64) -> i64 {
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
if handle <= 0 { return 0; }
use nyash_rust::{
box_trait::{IntegerBox, NyashBox},
jit::rt::handles,
};
if handle <= 0 {
return 0;
}
if let Some(obj) = handles::get(handle as u64) {
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
if let Some(map) = obj
.as_any()
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
{
let kbox: Box<dyn NyashBox> = if key_any > 0 {
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
} else { Box::new(IntegerBox::new(key_any)) };
if let Some(k) = handles::get(key_any as u64) {
k.clone_box()
} else {
Box::new(IntegerBox::new(key_any))
}
} else {
Box::new(IntegerBox::new(key_any))
};
let vbox: Box<dyn NyashBox> = if val_any > 0 {
if let Some(v) = handles::get(val_any as u64) { v.clone_box() } else { Box::new(IntegerBox::new(val_any)) }
} else { Box::new(IntegerBox::new(val_any)) };
if let Some(v) = handles::get(val_any as u64) {
v.clone_box()
} else {
Box::new(IntegerBox::new(val_any))
}
} else {
Box::new(IntegerBox::new(val_any))
};
let _ = map.set(kbox, vbox);
return 0;
}
@ -149,15 +181,31 @@ pub extern "C" fn nyash_map_set_hh(handle: i64, key_any: i64, val_any: i64) -> i
// has_hh: (map_handle, key_any: handle or i64) -> i64 (0/1)
#[export_name = "nyash.map.has_hh"]
pub extern "C" fn nyash_map_has_hh(handle: i64, key_any: i64) -> i64 {
use nyash_rust::{box_trait::{NyashBox, IntegerBox, BoolBox}, jit::rt::handles};
if handle <= 0 { return 0; }
use nyash_rust::{
box_trait::{BoolBox, IntegerBox, NyashBox},
jit::rt::handles,
};
if handle <= 0 {
return 0;
}
if let Some(obj) = handles::get(handle as u64) {
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
if let Some(map) = obj
.as_any()
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
{
let kbox: Box<dyn NyashBox> = if key_any > 0 {
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
} else { Box::new(IntegerBox::new(key_any)) };
if let Some(k) = handles::get(key_any as u64) {
k.clone_box()
} else {
Box::new(IntegerBox::new(key_any))
}
} else {
Box::new(IntegerBox::new(key_any))
};
let v = map.has(kbox);
if let Some(b) = v.as_any().downcast_ref::<BoolBox>() { return if b.value { 1 } else { 0 }; }
if let Some(b) = v.as_any().downcast_ref::<BoolBox>() {
return if b.value { 1 } else { 0 };
}
}
}
0
@ -177,7 +225,9 @@ pub extern "C" fn nyash_map_has_h(handle: i64, key: i64) -> i64 {
{
let kbox = Box::new(IntegerBox::new(key));
let v = map.has(kbox);
if let Some(b) = v.as_any().downcast_ref::<nyash_rust::box_trait::BoolBox>() { return if b.value { 1 } else { 0 }; }
if let Some(b) = v.as_any().downcast_ref::<nyash_rust::box_trait::BoolBox>() {
return if b.value { 1 } else { 0 };
}
}
}
0

View File

@ -83,7 +83,7 @@ pub extern "C" fn nyash_string_concat_is(a: i64, b: *const i8) -> *mut i8 {
// Exported as: nyash.string.substring_sii(i8* s, i64 start, i64 end) -> i8*
#[export_name = "nyash.string.substring_sii"]
pub extern "C" fn nyash_string_substring_sii(s: *const i8, start: i64, end: i64) -> *mut i8 {
use std::ffi::CStr;
use std::ffi::CStr;
if s.is_null() {
return std::ptr::null_mut();
}
@ -95,9 +95,15 @@ use std::ffi::CStr;
let n = src.len() as i64;
let mut st = if start < 0 { 0 } else { start };
let mut en = if end < 0 { 0 } else { end };
if st > n { st = n; }
if en > n { en = n; }
if en < st { std::mem::swap(&mut st, &mut en); }
if st > n {
st = n;
}
if en > n {
en = n;
}
if en < st {
std::mem::swap(&mut st, &mut en);
}
let (st_u, en_u) = (st as usize, en as usize);
let sub = &src[st_u.min(src.len())..en_u.min(src.len())];
let mut bytes = sub.as_bytes().to_vec();
@ -111,15 +117,27 @@ use std::ffi::CStr;
#[export_name = "nyash.string.lastIndexOf_ss"]
pub extern "C" fn nyash_string_lastindexof_ss(s: *const i8, needle: *const i8) -> i64 {
use std::ffi::CStr;
if s.is_null() || needle.is_null() { return -1; }
if s.is_null() || needle.is_null() {
return -1;
}
let hs = unsafe { CStr::from_ptr(s) };
let ns = unsafe { CStr::from_ptr(needle) };
let h = match hs.to_str() { Ok(v) => v, Err(_) => return -1 };
let n = match ns.to_str() { Ok(v) => v, Err(_) => return -1 };
if n.is_empty() { return h.len() as i64; }
let h = match hs.to_str() {
Ok(v) => v,
Err(_) => return -1,
};
let n = match ns.to_str() {
Ok(v) => v,
Err(_) => return -1,
};
if n.is_empty() {
return h.len() as i64;
}
if let Some(pos) = h.rfind(n) {
pos as i64
} else { -1 }
} else {
-1
}
}
// Exported as: nyash.string.to_i8p_h(i64 handle) -> i8*

View File

@ -11,16 +11,16 @@ Module Layout
- `builder_cursor.rs`: central insertion/terminator guard.
Core Invariants
- Resolver-only reads: lowerers fetch MIR values through `Resolver` (no direct `vmap` access for cross-BB values).
- Localize at block start: PHIs created at the beginning of the current BB (before non-PHI) to guarantee dominance.
- Cast placement: perform ptr↔int and width casts outside PHIs, at BB start or just-before-pred-terminator via `with_block`.
- Sealed SSA: successor PHIs wired by predecessor snapshots and `seal_block`; branch/jump do not push incoming directly.
- Cursor discipline: only insert via `BuilderCursor`; post-terminator insertions are forbidden.
- Phase15 終盤MIR13運用: MIR 生成層では PHI を生成しない。PHI 合成は LLVM 層llvmlite/Resolverが担う。
- Resolver-only reads: lowerers fetch MIR values through `Resolver`クロスBBの vmap 直接参照は禁止)。
- Localize at block start: PHIs are created at the beginning of the current BBnonPHI より手前)で優位性を保証。
- Cast placement: ptr↔int/幅変換は PHI の外側BB先頭 or pred終端直前に配置。
- Sealed SSA: 後続ブロックの PHI は pred スナップショットと `seal_block` で配線し、branch/jump 自体は incoming を直接積まない。
- Cursor discipline: 生成は `BuilderCursor` 経由のみ。terminator 後の挿入は禁止。
LoopForm (gated)
- Shape: `preheaderheaderbody → dispatch(phi) → {latch|exit} → header` with PHIs centralized in `dispatch`.
- State: model loop-carried values via a `LoopState` aggregate (tag + payloads).
- Goals: move all PHIs to dispatch, ensure header uses are dominated by preheader/dispatch values.
LoopForm(次フェーズ予定/MIR18
- Phase15 では LoopForm を MIR に導入しない。既存 CFGpreheaderheader→{body|exit}; body→headerから llvmlite がループ搬送 PHI を合成。
- 次フェーズで LoopForm`LoopHeader/Enter/Latch` などの占位)を MIR に追加し、Resolver/PHI 合成は維持する。
Types and Bridges
- Box handle is `i64` across NyRT boundary; strings prefer `i8*` fast paths.
@ -34,4 +34,3 @@ References
- LOWERING_LLVM.md — lowering rules and runtime calls
- RESOLVER_API.md — Resolver design and usage
- LLVM_HARNESS.md — llvmlite harness interface and usage

View File

@ -1,6 +1,7 @@
# Resolver API (Minimal i64 Prototype)
Goals
- Phase15MIR13運用における方針: MIR 生成層は PHI を出さず、LLVM 層で PHI を合成する。
- Centralize "ValueId → current-block value" resolution.
- Guarantee dominance by localizing values at the start of the block (before non-PHI).
- De-duplicate per (block, value) to avoid redundant PHIs/casts.
@ -28,6 +29,7 @@ Ban: Direct `vmap.get(..)` for cross-BB reads
Next
- Migrate remaining `localize_to_i64` call sites to the resolver.
- Enforce vmap direct access ban in lowerers (Resolver-only for reads).
- ループwhile 形 CFGの検出と、ヘッダ BB での搬送 PHI 合成preheader/backedge の 2 incomingを実装。
Tracing
- `NYASH_LLVM_TRACE_PHI=1`: log PHI creation/wiring in the Rust/inkwell path.

View File

@ -15,7 +15,13 @@ LifeBox ModelLBMは「Box=Loop1」という見方でライフサイクル
- 論文AMIR13/IR設計: 本稿は将来の拡張。まずAを優先して仕上げ、その後に独立短論文としてまとめる。
- 論文BNyash言語: birth/fini・async/generator の設計と橋渡し要素。
## 2025-09-16 追記: MIR進化計画
- **MIR14→MIR13→MIR17の段階的移行**: `mir-evolution-plan.md` に詳細記載
- Codexとの協働でLoopForm設計を具体化
- PHI責務のLLVM層移管とLoopForm追加+4命令の戦略
---
- 下書き本文: `main-paper-jp.md`
- MIR進化計画: `mir-evolution-plan.md`(新規追加)
- 補助: 擬似MIRとLowering図、評価計画の雛形を本文内に記載

View 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追加
```
## MIR13Phase 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無視
## MIR17Phase 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 ModelLBMへの布石
## 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 16MIR17化
- [ ] MIR: LoopForm命令の追加
- [ ] Bridge: Loop構造の認識とLoopForm生成
- [ ] llvmlite: LoopFormからPHIへの変換
- [ ] ドキュメント: MIR17仕様書
## 関連文献
- 論文AMIR13/14: 基本設計
- 論文DSSA構築: PHI生成の理論
- 論文E本稿: LoopForm理論
- CURRENT_TASK.md: 実装計画

View File

@ -1,10 +1,10 @@
# 🎉 Nyash開発 完全事件コレクション - 世界記録級46事例の記録
# 🎉 Nyash開発 完全事件コレクション - 世界記録級47事例の記録
## 📝 概要
2025年8月9日から9月15日までのNyash爆速開発で発生した46個の「面白事件」の完全記録。
2025年8月9日から9月16日までのNyash爆速開発で発生した47個の「面白事件」の完全記録。
AI協働開発の歴史に残る世界記録級の事件から、開発現場の生々しいドラマまでを網羅。
2025年9月15日更新:5件追加)
2025年9月16日更新:6件追加)
## 🌟 世界記録級TOP10
@ -69,7 +69,7 @@ AI協働開発の歴史に残る世界記録級の事件から、開発現場の
- **意味**: Everything is Fold哲学へ
- **評価**: 「革命的アイデア」認定
## 📊 17パターン別分類全46事例)
## 📊 17パターン別分類全47事例)
### 1. 箱化による解決8事例
- 事例001: DebugBoxによる出力制御統一
@ -140,10 +140,11 @@ AI協働開発の歴史に残る世界記録級の事件から、開発現場の
### 16. 予防的設計1事例
- 事例039: ID衝突との戦い
### 17. 段階的洗練型(1事例)
### 17. 段階的洗練型(2事例)
- 事例046: initブロック vs 先頭のみ事件
- 事例047: LoopForm先取り抑制事件
### その他(10事例)
### その他(9事例)
- 事例020: 26日間の奇跡
- 事例021: 2段階パーサー理論
- 事例022: NyashFlowプロジェクト
@ -189,7 +190,7 @@ ChatGPT: 「!!!」(瞬時に理解)
- **世界記録**: 20日でネイティブEXE
### 成果
- **事件数**: 469/15更新)
- **事件数**: 479/16更新)
- **パターン**: 17種類
- **致命的破綻**: 0回
- **大規模リファクタ**: 0回
@ -206,7 +207,7 @@ ChatGPT: 「!!!」(瞬時に理解)
- [技術的ブレークスルー](../paper-l-technical-breakthroughs/README.md)
- [AI協働開発ログ](../paper-g-ai-collaboration/development-log.md)
## 🚀 2025年9月追加事例5件)
## 🚀 2025年9月追加事例6件)
### 事例042: PyVM迂回路の混乱
- **日付**: 2025年9月15日
@ -251,9 +252,18 @@ ChatGPT: 「!!!」(瞬時に理解)
- **パターン**: 段階的洗練型の典型例(複雑→単純)
- **教訓**: AIは複雑に考えがち、人間の直感が本質を突く
### 事例047: LoopForm先取り抑制事件
- **日付**: 2025年9月16日
- **状況**: CodoxがLoopForm実装を詳細提案
- **AI提案**: MIR14にLoopForm追加して一気に実装
- **人間の判断**: 「MIR13にへらしたら 4でMIR17になるかにゃ」
- **結果**: 段階的移行計画MIR14→13→17を策定
- **パターン**: 慎重な段階移行
- **教訓**: 正しい設計でも実装順序が重要
## 💫 まとめ
46個の事件は、単なる開発エピソードではなく、AI協働開発の新しい形を示す歴史的記録である。特に
47個の事件は、単なる開発エピソードではなく、AI協働開発の新しい形を示す歴史的記録である。特に
1. **世界記録級の開発速度**JIT1日、20日でEXE
2. **AI-人間の新しい関係**AIが相談、人間が救う

View File

@ -13,6 +13,37 @@ Guiding Principles
- Short-circuit semantics are already mirrored via logical nodes; Stage-3 should reuse the same block-building infrastructure (Bridge/VM/JIT) to avoid special cases.
- Continue the "degrade to expression" approach when code generation is not ready (e.g. throw/try) so that Stage-2 tests stay green while the full implementation is developed.
Current Status (Phase 15.3 2025-09-16)
- ParserBox / Selfhost compiler expose `stage3_enable` and `--stage3` CLI flag, defaulting to the safe Stage-2 surface.
- Break/Continue JSON emission and Bridge lowering are implemented. Bridge now emits `Jump` to loop exit/head and records instrumentation events.
- Throw/Try nodes are emitted when the gate is on, but still degrade to expression/no-op during lowering; runtime semantics remain TBD.
- Documentation for JSON v0 (Stage-3 nodes) is updated; remaining runtime work is tracked in CURRENT_TASK.md.
Runtime snapshot
- MIR Builder already lowers `ASTNode::Throw` into `MirInstruction::Throw` (unless disabled via `NYASH_BUILDER_DISABLE_THROW`) and has a provisional `build_try_catch_statement` that emits `Catch`/`Jump` scaffolding with env flags controlling fallback.
- Rust VM (`interpreter::ControlFlow::Throw`) supports catch/finally semantics and rethrows unhandled exceptions.
- Bridge degradation prevents these MIR paths from activating unless `NYASH_BRIDGE_THROW_ENABLE=1`;既定では Const0 を出し、フラグONで実際に `Throw` を生成する。
PyVM plan
- Current PyVM runner treats Stage-3 constructs as no-ops because JSON v0 never emits MIR throws; once Bridge emits them, PyVM must mirror Rust VM semantics:
- Introduce a lightweight `exception` representation (reuse ErrorBox JSON form) and propagate via structured returns.
- Implement try/catch/finally execution order identical to Rust VM (catch matches first, finally always runs, rethrow on miss).
- Add minimal smoke tests under `tools/pyvm_stage2_smoke.sh` (gated) to ensure PyVM and LLVM stay in sync when Stage-3 is enabled.
LLVM plan
- Short term: continue degrading throw/try to keep LLVM pipeline green while implementation lands (Stage-3 smoke ensures awareness).
- Implementation steps once runtime semantics are ready:
1. Ensure MIR output contains `Throw`/`Catch` instructions; update LLVM codegen to treat `Throw` as a call to a runtime helper (`nyash.rt.throw`) that unwinds or aborts.
2. Model catch/finally blocks using landing pads or structured IR (likely via `invoke`/`landingpad` in LLVM); document minimal ABI expected from NyRT.
3. Add gated smoke (`NYASH_LLVM_STAGE3_SMOKE`) that expects non-degraded behaviour (distinct exit codes or printed markers) once helper is active.
- Until landing pad support exists, document that Stage-3 throw/try is unsupported in LLVM release mode and falls back to interpreter/PyVM.
Testing plan
- JSON fixtures: create `tests/json_v0_stage3/{break_continue,throw_basic,try_catch_finally}.json` to lock parser/bridge output and allow regression diffs.
- PyVM/VM: extend Stage-3 smoke scripts with throw/try cases (under gate) to ensure runtime consistency before enabling by default.
- LLVM: `NYASH_LLVM_STAGE3_SMOKE=1``NYASH_BRIDGE_THROW_ENABLE=1` / `NYASH_BRIDGE_TRY_ENABLE=1` と組み合わせて実際の例外経路を確認。将来的に常時ONへ移行予定。
- CI gating: add optional job that runs Stage-3 smokes (PyVM + LLVM) nightly to guard against regressions while feature is still experimental.
JSON v0 Additions
| Construct | JSON v0 Node | Notes |
|------------|-------------------------------------------------|-------|
@ -29,7 +60,11 @@ Lowering Strategy (Bridge)
2. **Throw/Try**
- Phase 15 MVP keeps them syntax-only to avoid VM/JIT churn. Parser/Emitter produce nodes; Bridge either degrades (Expr) or logs a structured event for future handling.
- Document expectation: once runtime exception model is defined, nodes become non-degrading.
- Bridge helper `lower_throw` respects `NYASH_BRIDGE_THROW_ENABLE=1`; defaultは Const i64 0 のデグレード、フラグONで `MirInstruction::Throw` を実際に生成。
- Try lowering plan:
1. Parse-time JSON already includes `catches`/`finally`. Bridge should map `try` body into a fresh region, emit basic blocks for each `catch`, and wire `finally` as a postamble block.
2. MIR needs explicit instructions/metadata for exception edges. Evaluate whether existing `MirInstruction::Throw` + `ControlFlow::Throw` is sufficient or if `Catch` terminators are required.
3. Until runtime implementation lands, keep current degrade path but log a structured event to flag unhandled try/catch.
3. **Metadata Events**
- Augment `crate::jit::observe` with `lower_shortcircuit`/`lower_try` stubs so instrumentation remains coherent when full support is wired.
@ -37,14 +72,14 @@ Lowering Strategy (Bridge)
Testing Plan
- Extend selfhost Stage-2 smoke file with guard cases (`return break` etc.) once lowering is live.
- Create dedicated JSON fixtures under `tests/json_v0_stage3/` for break/continue/try once behaviour stabilises.
- Update `tools/ny_stage2_shortcircuit_smoke.sh` to ensure Stage-3 constructs do not regress Stage-2 semantics (break/continue degrade). Timing: after lowering is implemented.
- Update `tools/ny_stage2_shortcircuit_smoke.sh` to ensure Stage-3 constructs do not regress Stage-2 semantics (break/continue degrade when gate off, jump when on).
Migration Checklist
1. ParserBox emits Stage-3 nodes under `NYASH_PARSER_STAGE3=1` gate to allow gradual rollout.
2. Emitter attaches Stage-3 JSON when gate is enabled (otherwise degrade to existing Stage-2 forms).
3. Bridge honours Stage-3 nodes when gate is on; degrade with warning when off.
4. PyVM/VM/JIT semantics gradually enabled (throw/try remain degrade until corresponding runtime support is merged).
5. Documentation kept in sync (`CURRENT_TASK.md`, release notes).
1. ParserBox emits Stage-3 nodes under `stage3_enable` gate to allow gradual rollout.
2. Emitter attaches Stage-3 JSON when gate is enabled (otherwise degrade to existing Stage-2 forms).
3. Bridge honours Stage-3 nodes when gate is on; break/continue lowering implemented, throw/try still degrade. ✅ (partial)
4. PyVM/VM/JIT semantics gradually enabled (throw/try remain degrade until corresponding runtime support is merged). 🔄 Pending runtime work.
5. Documentation kept in sync (`CURRENT_TASK.md`, release notes). ✅ (break/continue) / 🔄 (throw/try runtime notes).
References
- Stage-2 design (`parser_mvp_stage2.md`)

View File

@ -14,6 +14,9 @@ Statements (`StmtV0`)
- `Local { name, expr }` (Stage2)
- `If { cond, then: Stmt[], else?: Stmt[] }` (Stage2)
- `Loop { cond, body: Stmt[] }` (Stage2; while(cond) body)
- `Break` (Stage3; exits current loop)
- `Continue` (Stage3; jumps to loop head)
- `Try { try: Stmt[], catches?: Catch[], finally?: Stmt[] }` (Stage3 skeleton; currently lowered as sequential `try` body only when runtime support is absent)
Expressions (`ExprV0`)
- `Int { value }` where `value` is JSON number or digit string
@ -26,6 +29,7 @@ Expressions (`ExprV0`)
- `Method { recv: Expr, method: string, args[] }` (box method)
- `New { class: string, args[] }` (construct Box)
- `Var { name: string }`
- `Throw { expr }` (Stage3; currently degrades to expression statement when runtime semantics are disabled)
CFG conventions (lowered by the bridge)
- If: create `then_bb`, `else_bb`, `merge_bb`. Both branches jump to merge if unterminated.
@ -33,12 +37,10 @@ CFG conventions (lowered by the bridge)
- Shortcircuit Logical: create `rhs_bb`, `fall_bb`, `merge_bb` with constants on fall path.
- All blocks end with a terminator (branch/jump/return).
PHI merging (current behavior)
- If: locals updated in `then`/`else` merge at `merge_bb` via `phi`.
- Else欠落時は else 側に分岐前(base)を採用
- 片側にしか存在しない新規変数はスコープ外として外へ未伝播
- Loop: `cond_bb` にヘッダ PHI を先置きpreheader/base と latch/body end を合流)。
- 目的: Stage2 を早期に安定化させるための橋渡し。将来LoopForm= MIR18では LoopForm からの逆Loweringで PHI を自動化予定。
PHI mergingPhase15 終盤の方針)
- MIR 生成層は PHI を生成しないMIR13 運用。If/Loop の合流は LLVM 層llvmlite/Resolverが PHI を合成。
- ループは既存 CFGpreheader→cond→{body|exit}; body→condの検出により、ヘッダ BB で搬送値の PHI を構築
- 将来LoopForm= MIR18では LoopForm 占位命令から逆 Lowering で PHI を自動化予定
Type meta (emitter/LLVM harness cooperation)
- `+` with any string operand → string concat pathhandle固定
@ -78,3 +80,5 @@ If with local + PHI merge
{"type":"Return","expr":{"type":"Var","name":"x"}}
]}
```
- `Break` / `Continue` are emitted when Stage3 gate is enabled. When the bridge is compiled without Stage3 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
View 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.

View File

@ -35,7 +35,8 @@ impl eframe::App for DebugApp {
ui.label("Single Line:");
let response = ui.text_edit_singleline(&mut self.single_line);
if response.changed() {
self.event_log.push(format!("Single line changed: '{}'", self.single_line));
self.event_log
.push(format!("Single line changed: '{}'", self.single_line));
}
});
@ -46,11 +47,12 @@ impl eframe::App for DebugApp {
let response = ui.add(
egui::TextEdit::multiline(&mut self.text)
.desired_width(f32::INFINITY)
.desired_rows(10)
.desired_rows(10),
);
if response.changed() {
self.event_log.push(format!("Multi line changed: {} chars", self.text.len()));
self.event_log
.push(format!("Multi line changed: {} chars", self.text.len()));
}
ui.separator();

View File

@ -8,11 +8,8 @@ use std::path::PathBuf;
use windows::{
core::*,
Win32::{
Foundation::*,
Storage::FileSystem::*,
UI::Shell::*,
Foundation::*, Storage::FileSystem::*, System::Com::*, UI::Shell::*,
UI::WindowsAndMessaging::*,
System::Com::*,
},
};
@ -40,7 +37,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
fonts.font_data.insert(
"noto_sans_jp".to_owned(),
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf"))
.into(),
);
fonts
@ -112,7 +110,10 @@ impl NyashExplorer {
// ドライブタイプを取得
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()
format!("{}\0", drive_path)
.encode_utf16()
.collect::<Vec<u16>>()
.as_ptr(),
));
drive_info.drive_type = match drive_type_code {
@ -132,13 +133,20 @@ impl NyashExplorer {
let mut file_system_flags = 0u32;
if GetVolumeInformationW(
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
PCWSTR::from_raw(
format!("{}\0", drive_path)
.encode_utf16()
.collect::<Vec<u16>>()
.as_ptr(),
),
Some(&mut volume_name),
Some(&mut serial_number),
Some(&mut max_component_len),
Some(&mut file_system_flags),
Some(&mut file_system),
).is_ok() {
)
.is_ok()
{
let volume_name_str = String::from_utf16_lossy(&volume_name)
.trim_end_matches('\0')
.to_string();
@ -157,11 +165,18 @@ impl NyashExplorer {
let mut total_free_bytes = 0u64;
if GetDiskFreeSpaceExW(
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
PCWSTR::from_raw(
format!("{}\0", drive_path)
.encode_utf16()
.collect::<Vec<u16>>()
.as_ptr(),
),
Some(&mut free_bytes_available),
Some(&mut total_bytes),
Some(&mut total_free_bytes),
).is_ok() {
)
.is_ok()
{
drive_info.total_bytes = total_bytes;
drive_info.free_bytes = total_free_bytes;
}
@ -309,7 +324,8 @@ impl eframe::App for NyashExplorer {
if drive.total_bytes > 0 {
let used_bytes = drive.total_bytes - drive.free_bytes;
let usage_percent = (used_bytes as f32 / drive.total_bytes as f32) * 100.0;
let usage_percent =
(used_bytes as f32 / drive.total_bytes as f32) * 100.0;
ui.horizontal(|ui| {
ui.label(format!(
@ -363,7 +379,8 @@ impl eframe::App for NyashExplorer {
ui.separator();
ui.horizontal(|ui| {
if ui.button("🐱 Nyashについて").clicked() {
self.status = "Nyash - Everything is Box! Windows APIも吸収できる化け物言語".to_string();
self.status = "Nyash - Everything is Box! Windows APIも吸収できる化け物言語"
.to_string();
}
if ui.button("📊 システム情報").clicked() {

View File

@ -1,7 +1,7 @@
// Nyash Explorer with Icons - Windows API Drive Icon Viewer
// エクスプローラー風ドライブアイコン付きビューアー
use eframe::egui::{self, FontFamily, ColorImage, TextureHandle};
use eframe::egui::{self, ColorImage, FontFamily, TextureHandle};
use std::fs::File;
use std::io::Read;
// use std::collections::HashMap;
@ -10,12 +10,7 @@ use std::io::Read;
#[cfg(windows)]
use windows::{
core::*,
Win32::{
Storage::FileSystem::*,
UI::Shell::*,
UI::WindowsAndMessaging::*,
System::Com::*,
},
Win32::{Storage::FileSystem::*, System::Com::*, UI::Shell::*, UI::WindowsAndMessaging::*},
};
fn main() -> eframe::Result {
@ -48,7 +43,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
fonts.font_data.insert(
"noto_sans_jp".to_owned(),
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf"))
.into(),
);
fonts
@ -98,7 +94,10 @@ impl NyashExplorer {
fn get_drive_icon(&self, drive_path: &str) -> Option<ColorImage> {
unsafe {
let mut shfi = SHFILEINFOW::default();
let drive_path_wide: Vec<u16> = drive_path.encode_utf16().chain(std::iter::once(0)).collect();
let drive_path_wide: Vec<u16> = drive_path
.encode_utf16()
.chain(std::iter::once(0))
.collect();
// アイコンを取得
let result = SHGetFileInfoW(
@ -158,9 +157,11 @@ impl NyashExplorer {
}
// ヘッダーから情報を読み取る
let data_offset = u32::from_le_bytes([buffer[10], buffer[11], buffer[12], buffer[13]]) as usize;
let data_offset =
u32::from_le_bytes([buffer[10], buffer[11], buffer[12], buffer[13]]) as usize;
let width = i32::from_le_bytes([buffer[18], buffer[19], buffer[20], buffer[21]]) as usize;
let height = i32::from_le_bytes([buffer[22], buffer[23], buffer[24], buffer[25]]).abs() as usize;
let height =
i32::from_le_bytes([buffer[22], buffer[23], buffer[24], buffer[25]]).abs() as usize;
let bits_per_pixel = u16::from_le_bytes([buffer[28], buffer[29]]);
// 32ビットBMPのみサポート
@ -280,7 +281,10 @@ impl NyashExplorer {
// ドライブタイプを取得
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()
format!("{}\0", drive_path)
.encode_utf16()
.collect::<Vec<u16>>()
.as_ptr(),
));
drive_info.drive_type = match drive_type_code {
@ -300,13 +304,20 @@ impl NyashExplorer {
let mut file_system_flags = 0u32;
if GetVolumeInformationW(
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
PCWSTR::from_raw(
format!("{}\0", drive_path)
.encode_utf16()
.collect::<Vec<u16>>()
.as_ptr(),
),
Some(&mut volume_name),
Some(&mut serial_number),
Some(&mut max_component_len),
Some(&mut file_system_flags),
Some(&mut file_system),
).is_ok() {
)
.is_ok()
{
let volume_name_str = String::from_utf16_lossy(&volume_name)
.trim_end_matches('\0')
.to_string();
@ -325,11 +336,18 @@ impl NyashExplorer {
let mut total_free_bytes = 0u64;
if GetDiskFreeSpaceExW(
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
PCWSTR::from_raw(
format!("{}\0", drive_path)
.encode_utf16()
.collect::<Vec<u16>>()
.as_ptr(),
),
Some(&mut free_bytes_available),
Some(&mut total_bytes),
Some(&mut total_free_bytes),
).is_ok() {
)
.is_ok()
{
drive_info.total_bytes = total_bytes;
drive_info.free_bytes = total_free_bytes;
}
@ -339,7 +357,7 @@ impl NyashExplorer {
let texture = self.ctx.load_texture(
format!("drive_icon_{}", drive_letter),
icon_image,
Default::default()
Default::default(),
);
drive_info.icon_texture = Some(texture);
}
@ -363,18 +381,19 @@ impl NyashExplorer {
};
if let Some(icon_image) = self.get_drive_icon("C:") {
let texture = self.ctx.load_texture(
"drive_icon_C:",
icon_image,
Default::default()
);
let texture =
self.ctx
.load_texture("drive_icon_C:", icon_image, Default::default());
drive_info.icon_texture = Some(texture);
}
self.drives.push(drive_info);
}
self.status = format!("{}個のドライブを検出しました(アイコン付き)", self.drives.len());
self.status = format!(
"{}個のドライブを検出しました(アイコン付き)",
self.drives.len()
);
}
fn format_bytes(bytes: u64) -> String {
@ -504,7 +523,8 @@ impl eframe::App for NyashExplorer {
if drive.total_bytes > 0 {
let used_bytes = drive.total_bytes - drive.free_bytes;
let usage_percent = (used_bytes as f32 / drive.total_bytes as f32) * 100.0;
let usage_percent =
(used_bytes as f32 / drive.total_bytes as f32) * 100.0;
ui.horizontal(|ui| {
ui.label(format!(
@ -558,7 +578,9 @@ impl eframe::App for NyashExplorer {
ui.separator();
ui.horizontal(|ui| {
if ui.button("🐱 Nyashについて").clicked() {
self.status = "Nyash - Everything is Box! Windows APIでアイコンも取得できる化け物言語".to_string();
self.status =
"Nyash - Everything is Box! Windows APIでアイコンも取得できる化け物言語"
.to_string();
}
if ui.button("📊 システム情報").clicked() {

View File

@ -30,7 +30,8 @@ fn setup_custom_fonts(ctx: &egui::Context) {
// 日本語フォント(可変ウェイト)を追加
fonts.font_data.insert(
"noto_sans_jp".to_owned(),
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf"))
.into(),
);
// フォントファミリーに追加
@ -91,7 +92,8 @@ impl eframe::App for NyashNotepad {
self.status = "Nyash - Everything is Box! 🐱".to_string();
}
if ui.button("使い方").clicked() {
self.status = "テキストを入力して、にゃっしゅプログラムを書こう!".to_string();
self.status =
"テキストを入力して、にゃっしゅプログラムを書こう!".to_string();
}
});
});
@ -102,7 +104,8 @@ impl eframe::App for NyashNotepad {
ui.horizontal(|ui| {
ui.label(&self.status);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(format!("文字数: {} | 行数: {}",
ui.label(format!(
"文字数: {} | 行数: {}",
self.text.chars().count(),
self.text.lines().count()
));
@ -158,7 +161,7 @@ impl eframe::App for NyashNotepad {
.font(egui::TextStyle::Monospace)
.desired_width(f32::INFINITY)
.desired_rows(20)
.hint_text("ここにテキストを入力してください... にゃ!🐱")
.hint_text("ここにテキストを入力してください... にゃ!🐱"),
);
});
@ -168,12 +171,16 @@ impl eframe::App for NyashNotepad {
ui.label("クイック挿入: ");
if ui.button("📝 Nyashサンプル").clicked() {
self.text.push_str("\n// Nyash - Everything is Box! すべてがBoxの世界へようこそ\n");
self.text.push_str(
"\n// Nyash - Everything is Box! すべてがBoxの世界へようこそ\n",
);
self.text.push_str("box こんにちは世界 {\n");
self.text.push_str(" init { メッセージ }\n");
self.text.push_str(" \n");
self.text.push_str(" こんにちは世界() {\n");
self.text.push_str(" me.メッセージ = \"こんにちは、Nyashの世界にゃ〜🐱\"\n");
self.text.push_str(
" me.メッセージ = \"こんにちは、Nyashの世界にゃ〜🐱\"\n",
);
self.text.push_str(" }\n");
self.text.push_str(" \n");
self.text.push_str(" 挨拶() {\n");
@ -189,7 +196,10 @@ impl eframe::App for NyashNotepad {
if ui.button("🕐 現在時刻").clicked() {
let now = chrono::Local::now();
self.text.push_str(&format!("\n// 挿入時刻: {}\n", now.format("%Y年%m月%d日 %H時%M分%S秒")));
self.text.push_str(&format!(
"\n// 挿入時刻: {}\n",
now.format("%Y年%m月%d日 %H時%M分%S秒")
));
self.status = "現在時刻を挿入しました".to_string();
}

View File

@ -107,7 +107,7 @@ impl eframe::App for NyashNotepad {
.font(egui::TextStyle::Monospace)
.desired_width(f32::INFINITY)
.desired_rows(20)
.hint_text("ここにテキストを入力してください... にゃ!")
.hint_text("ここにテキストを入力してください... にゃ!"),
);
});
@ -120,7 +120,8 @@ impl eframe::App for NyashNotepad {
self.text.push_str(" init { message }\n");
self.text.push_str(" \n");
self.text.push_str(" HelloWorld() {\n");
self.text.push_str(" me.message = \"Hello, Nyash World! にゃ!\"\n");
self.text
.push_str(" me.message = \"Hello, Nyash World! にゃ!\"\n");
self.text.push_str(" }\n");
self.text.push_str("}\n");
self.status = "Nyashサンプルコードを挿入しました".to_string();
@ -128,7 +129,8 @@ impl eframe::App for NyashNotepad {
if ui.button("時刻挿入").clicked() {
let now = chrono::Local::now();
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
self.text
.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
self.status = "現在時刻を挿入しました".to_string();
}
});

View File

@ -113,7 +113,7 @@ impl eframe::App for NyashNotepad {
.font(egui::TextStyle::Monospace)
.desired_width(f32::INFINITY)
.desired_rows(20)
.hint_text("Type your text here... nya!")
.hint_text("Type your text here... nya!"),
);
});
@ -128,7 +128,8 @@ impl eframe::App for NyashNotepad {
self.text.push_str(" init { message }\n");
self.text.push_str(" \n");
self.text.push_str(" HelloWorld() {\n");
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
self.text
.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
self.text.push_str(" }\n");
self.text.push_str(" \n");
self.text.push_str(" greet() {\n");
@ -144,7 +145,8 @@ impl eframe::App for NyashNotepad {
if ui.button("Current Time").clicked() {
let now = chrono::Local::now();
self.text.push_str(&format!("\n[{}]\n", now.format("%Y-%m-%d %H:%M:%S")));
self.text
.push_str(&format!("\n[{}]\n", now.format("%Y-%m-%d %H:%M:%S")));
self.status = "Timestamp inserted".to_string();
}

View File

@ -104,7 +104,7 @@ impl eframe::App for NyashNotepad {
.font(egui::TextStyle::Monospace)
.desired_width(f32::INFINITY)
.desired_rows(20)
.hint_text("Type your text here... nya!")
.hint_text("Type your text here... nya!"),
);
});
@ -117,7 +117,8 @@ impl eframe::App for NyashNotepad {
self.text.push_str(" init { message }\n");
self.text.push_str(" \n");
self.text.push_str(" HelloWorld() {\n");
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
self.text
.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
self.text.push_str(" }\n");
self.text.push_str("}\n");
self.status = "Nyash sample code inserted".to_string();
@ -125,7 +126,8 @@ impl eframe::App for NyashNotepad {
if ui.button("Insert Timestamp").clicked() {
let now = chrono::Local::now();
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
self.text
.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
self.status = "Timestamp inserted".to_string();
}
});

View File

@ -132,7 +132,7 @@ impl eframe::App for NyashNotepad {
.font(egui::TextStyle::Monospace)
.desired_width(f32::INFINITY)
.desired_rows(20)
.hint_text("Type your text here... nya!")
.hint_text("Type your text here... nya!"),
);
});
@ -145,7 +145,8 @@ impl eframe::App for NyashNotepad {
self.text.push_str(" init { message }\n");
self.text.push_str(" \n");
self.text.push_str(" HelloWorld() {\n");
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
self.text
.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
self.text.push_str(" }\n");
self.text.push_str("}\n");
self.status = "Nyash sample code inserted".to_string();
@ -153,7 +154,8 @@ impl eframe::App for NyashNotepad {
if ui.button("Insert Timestamp").clicked() {
let now = chrono::Local::now();
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
self.text
.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
self.status = "Timestamp inserted".to_string();
}
});

View File

@ -4,12 +4,7 @@
#[cfg(windows)]
use windows::{
core::*,
Win32::{
Storage::FileSystem::*,
UI::Shell::*,
UI::WindowsAndMessaging::*,
Graphics::Gdi::*,
},
Win32::{Graphics::Gdi::*, Storage::FileSystem::*, UI::Shell::*, UI::WindowsAndMessaging::*},
};
fn main() {
@ -19,7 +14,10 @@ fn main() {
// C:ドライブのアイコンを取得
let drive_path = "C:\\";
let drive_path_wide: Vec<u16> = drive_path.encode_utf16().chain(std::iter::once(0)).collect();
let drive_path_wide: Vec<u16> = drive_path
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let mut shfi = SHFILEINFOW::default();
@ -51,7 +49,7 @@ fn main() {
let size = GetObjectW(
icon_info.hbmColor.into(),
std::mem::size_of::<BITMAP>() as i32,
Some(&mut bitmap as *mut _ as *mut _)
Some(&mut bitmap as *mut _ as *mut _),
);
if size > 0 {
@ -69,14 +67,20 @@ fn main() {
let copied = GetBitmapBits(
icon_info.hbmColor,
pixels.len() as i32,
pixels.as_mut_ptr() as *mut _
pixels.as_mut_ptr() as *mut _,
);
println!("Copied {} bytes of pixel data", copied);
// 簡易的にBMPファイルとして保存
if copied > 0 {
save_as_bmp("c_drive_icon.bmp", &pixels, bitmap.bmWidth, bitmap.bmHeight, bitmap.bmBitsPixel);
save_as_bmp(
"c_drive_icon.bmp",
&pixels,
bitmap.bmWidth,
bitmap.bmHeight,
bitmap.bmBitsPixel,
);
println!("Saved as c_drive_icon.bmp");
}
}
@ -124,7 +128,8 @@ fn save_as_bmp(filename: &str, pixels: &[u8], width: i32, height: i32, bits_per_
file.write_all(&1u16.to_le_bytes()).unwrap(); // プレーン数
file.write_all(&bits_per_pixel.to_le_bytes()).unwrap();
file.write_all(&0u32.to_le_bytes()).unwrap(); // 圧縮なし
file.write_all(&(pixels.len() as u32).to_le_bytes()).unwrap();
file.write_all(&(pixels.len() as u32).to_le_bytes())
.unwrap();
file.write_all(&0i32.to_le_bytes()).unwrap(); // X解像度
file.write_all(&0i32.to_le_bytes()).unwrap(); // Y解像度
file.write_all(&0u32.to_le_bytes()).unwrap(); // カラーテーブル数

View File

@ -3,9 +3,12 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::os::raw::c_char;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
// ===== Error Codes (aligned with existing plugins) =====
const NYB_SUCCESS: i32 = 0;
@ -17,27 +20,34 @@ const NYB_E_PLUGIN_ERROR: i32 = -5;
const NYB_E_INVALID_HANDLE: i32 = -8;
// ===== Method IDs =====
const METHOD_BIRTH: u32 = 0; // constructor -> returns instance_id (u32 LE, no TLV)
const METHOD_LENGTH: u32 = 1; // returns TLV i64
const METHOD_GET: u32 = 2; // args: i64 index -> returns TLV i64
const METHOD_PUSH: u32 = 3; // args: i64 value -> returns TLV i64 (new length)
const METHOD_SET: u32 = 4; // args: i64 index, i64 value -> returns TLV i64 (new length)
const METHOD_FINI: u32 = u32::MAX; // destructor
const METHOD_BIRTH: u32 = 0; // constructor -> returns instance_id (u32 LE, no TLV)
const METHOD_LENGTH: u32 = 1; // returns TLV i64
const METHOD_GET: u32 = 2; // args: i64 index -> returns TLV i64
const METHOD_PUSH: u32 = 3; // args: i64 value -> returns TLV i64 (new length)
const METHOD_SET: u32 = 4; // args: i64 index, i64 value -> returns TLV i64 (new length)
const METHOD_FINI: u32 = u32::MAX; // destructor
// Assign a unique type_id for ArrayBox (as declared in nyash.toml)
const TYPE_ID_ARRAY: u32 = 10;
// ===== Instance state (PoC: store i64 values only) =====
struct ArrayInstance { data: Vec<i64> }
struct ArrayInstance {
data: Vec<i64>,
}
static INSTANCES: Lazy<Mutex<HashMap<u32, ArrayInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static INSTANCES: Lazy<Mutex<HashMap<u32, ArrayInstance>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
pub extern "C" fn nyash_plugin_init() -> i32 {
NYB_SUCCESS
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -49,17 +59,25 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_ARRAY { return NYB_E_INVALID_TYPE; }
if type_id != TYPE_ID_ARRAY {
return NYB_E_INVALID_TYPE;
}
unsafe {
match method_id {
METHOD_BIRTH => {
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
if result_len.is_null() {
return NYB_E_INVALID_ARGS;
}
if preflight(result, result_len, 4) {
return NYB_E_SHORT_BUFFER;
}
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut map) = INSTANCES.lock() {
map.insert(id, ArrayInstance { data: Vec::new() });
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_PLUGIN_ERROR;
}
let bytes = id.to_le_bytes();
std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
*result_len = 4;
@ -69,39 +87,71 @@ pub extern "C" fn nyash_plugin_invoke(
if let Ok(mut map) = INSTANCES.lock() {
map.remove(&instance_id);
NYB_SUCCESS
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_LENGTH => {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
return write_tlv_i64(inst.data.len() as i64, result, result_len);
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
METHOD_GET => {
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
if idx < 0 { return NYB_E_INVALID_ARGS; }
let idx = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if idx < 0 {
return NYB_E_INVALID_ARGS;
}
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
let i = idx as usize;
if i >= inst.data.len() { return NYB_E_INVALID_ARGS; }
if i >= inst.data.len() {
return NYB_E_INVALID_ARGS;
}
return write_tlv_i64(inst.data[i], result, result_len);
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
METHOD_PUSH => {
let val = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
let val = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
inst.data.push(val);
return write_tlv_i64(inst.data.len() as i64, result, result_len);
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
METHOD_SET => {
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
if idx < 0 { return NYB_E_INVALID_ARGS; }
let idx = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
let val = match read_arg_i64(args, args_len, 1) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if idx < 0 {
return NYB_E_INVALID_ARGS;
}
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
let i = idx as usize;
@ -114,8 +164,12 @@ pub extern "C" fn nyash_plugin_invoke(
return NYB_E_INVALID_ARGS;
}
return write_tlv_i64(inst.data.len() as i64, result, result_len);
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
_ => NYB_E_INVALID_METHOD,
}
@ -136,7 +190,9 @@ pub struct NyashTypeBoxFfi {
unsafe impl Sync for NyashTypeBoxFfi {}
extern "C" fn array_resolve(name: *const c_char) -> u32 {
if name.is_null() { return 0; }
if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"len" | "length" => METHOD_LENGTH,
@ -147,26 +203,95 @@ extern "C" fn array_resolve(name: *const c_char) -> u32 {
}
}
extern "C" fn array_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
extern "C" fn array_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
METHOD_LENGTH => {
if let Ok(map) = INSTANCES.lock() { if let Some(inst) = map.get(&instance_id) { return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; }
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
return write_tlv_i64(inst.data.len() as i64, result, result_len);
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
METHOD_GET => {
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
if idx < 0 { return NYB_E_INVALID_ARGS; }
if let Ok(map) = INSTANCES.lock() { if let Some(inst) = map.get(&instance_id) { let i = idx as usize; if i >= inst.data.len() { return NYB_E_INVALID_ARGS; } return write_tlv_i64(inst.data[i], result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; }
let idx = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if idx < 0 {
return NYB_E_INVALID_ARGS;
}
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
let i = idx as usize;
if i >= inst.data.len() {
return NYB_E_INVALID_ARGS;
}
return write_tlv_i64(inst.data[i], result, result_len);
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
METHOD_SET => {
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
if idx < 0 { return NYB_E_INVALID_ARGS; }
if let Ok(mut map) = INSTANCES.lock() { if let Some(inst) = map.get_mut(&instance_id) { let i = idx as usize; let len = inst.data.len(); if i < len { inst.data[i] = val; } else if i == len { inst.data.push(val); } else { return NYB_E_INVALID_ARGS; } return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; }
let idx = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
let val = match read_arg_i64(args, args_len, 1) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if idx < 0 {
return NYB_E_INVALID_ARGS;
}
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
let i = idx as usize;
let len = inst.data.len();
if i < len {
inst.data[i] = val;
} else if i == len {
inst.data.push(val);
} else {
return NYB_E_INVALID_ARGS;
}
return write_tlv_i64(inst.data.len() as i64, result, result_len);
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
METHOD_PUSH => {
let val = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
if let Ok(mut map) = INSTANCES.lock() { if let Some(inst) = map.get_mut(&instance_id) { inst.data.push(val); return write_tlv_i64(inst.data.len() as i64, result, result_len); } else { return NYB_E_INVALID_HANDLE; } } else { return NYB_E_PLUGIN_ERROR; }
let val = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
inst.data.push(val);
return write_tlv_i64(inst.data.len() as i64, result, result_len);
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
_ => NYB_E_INVALID_METHOD,
}
@ -187,7 +312,9 @@ pub static nyash_typebox_ArrayBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
// ===== Minimal TLV helpers (compatible with host expectations) =====
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe {
if result_len.is_null() { return false; }
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
@ -197,8 +324,11 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
if result_len.is_null() {
return NYB_E_INVALID_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes()); // version
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
for (tag, payload) in payloads {
@ -225,19 +355,27 @@ fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
/// Read nth TLV argument as i64 (tag 3)
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; // skip header
for i in 0..=n {
if buf.len() < off + 4 { return None; }
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let _rsv = buf[off+1];
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
if buf.len() < off + 4 + size { return None; }
let _rsv = buf[off + 1];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag != 3 || size != 8 { return None; }
let mut b = [0u8;8];
b.copy_from_slice(&buf[off+4 .. off+4+8]);
if tag != 3 || size != 8 {
return None;
}
let mut b = [0u8; 8];
b.copy_from_slice(&buf[off + 4..off + 4 + 8]);
return Some(i64::from_le_bytes(b));
}
off += 4 + size;

View File

@ -2,9 +2,12 @@
//! Provides simple stdout printing via ConsoleBox
use std::collections::HashMap;
use std::os::raw::c_char;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::ffi::CStr;
use std::os::raw::c_char;
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
// ===== Error Codes (BID-1) =====
const NYB_SUCCESS: i32 = 0;
@ -16,7 +19,7 @@ const NYB_E_PLUGIN_ERROR: i32 = -5;
// ===== Method IDs =====
const METHOD_BIRTH: u32 = 0;
const METHOD_LOG: u32 = 1; // log(text)
const METHOD_LOG: u32 = 1; // log(text)
const METHOD_PRINTLN: u32 = 2; // println(text)
const METHOD_FINI: u32 = u32::MAX;
@ -24,58 +27,126 @@ const METHOD_FINI: u32 = u32::MAX;
const TYPE_ID_CONSOLE_BOX: u32 = 5; // keep in sync with nyash.toml [box_types]
// ===== Instance management =====
struct ConsoleInstance { /* no state for now */ }
struct ConsoleInstance {/* no state for now */}
use once_cell::sync::Lazy;
static INSTANCES: Lazy<Mutex<HashMap<u32, ConsoleInstance>>> = Lazy::new(|| {
Mutex::new(HashMap::new())
});
static INSTANCES: Lazy<Mutex<HashMap<u32, ConsoleInstance>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
// ===== TLV helpers (minimal) =====
// TLV layout: [u16 ver=1][u16 argc][entries...]
// Entry: [u16 tag][u16 size][payload...]
fn parse_first_string(args: &[u8]) -> Result<String, ()> {
if args.len() < 4 { return Err(()); }
let argc = u16::from_le_bytes([args[2], args[3]]) as usize;
if argc == 0 { return Err(()); }
let mut p = 4usize;
// first entry
if args.len() < p + 4 { return Err(()); }
let tag = u16::from_le_bytes([args[p], args[p+1]]); p += 2;
let sz = u16::from_le_bytes([args[p], args[p+1]]) as usize; p += 2;
if tag != 6 && tag != 7 { // String or Bytes
if args.len() < 4 {
return Err(());
}
if args.len() < p + sz { return Err(()); }
let s = String::from_utf8_lossy(&args[p..p+sz]).to_string();
let argc = u16::from_le_bytes([args[2], args[3]]) as usize;
if argc == 0 {
return Err(());
}
let mut p = 4usize;
// first entry
if args.len() < p + 4 {
return Err(());
}
let tag = u16::from_le_bytes([args[p], args[p + 1]]);
p += 2;
let sz = u16::from_le_bytes([args[p], args[p + 1]]) as usize;
p += 2;
if tag != 6 && tag != 7 {
// String or Bytes
return Err(());
}
if args.len() < p + sz {
return Err(());
}
let s = String::from_utf8_lossy(&args[p..p + sz]).to_string();
Ok(s)
}
fn format_first_any(args: &[u8]) -> Option<String> {
if args.len() < 4 { return None; }
if args.len() < 4 {
return None;
}
let mut p = 4usize;
if args.len() < p + 4 { return None; }
let tag = u16::from_le_bytes([args[p], args[p+1]]); p += 2;
let sz = u16::from_le_bytes([args[p], args[p+1]]) as usize; p += 2;
if args.len() < p + sz { return None; }
let payload = &args[p..p+sz];
if args.len() < p + 4 {
return None;
}
let tag = u16::from_le_bytes([args[p], args[p + 1]]);
p += 2;
let sz = u16::from_le_bytes([args[p], args[p + 1]]) as usize;
p += 2;
if args.len() < p + sz {
return None;
}
let payload = &args[p..p + sz];
match tag {
1 => Some(if sz>0 && payload[0]!=0 { "true".into() } else { "false".into() }),
2 => { if sz!=4 { None } else { let mut b=[0u8;4]; b.copy_from_slice(payload); Some((i32::from_le_bytes(b)).to_string()) } },
3 => { if sz!=8 { None } else { let mut b=[0u8;8]; b.copy_from_slice(payload); Some((i64::from_le_bytes(b)).to_string()) } },
5 => { if sz!=8 { None } else { let mut b=[0u8;8]; b.copy_from_slice(payload); Some(f64::from_le_bytes(b).to_string()) } },
6 => { std::str::from_utf8(payload).ok().map(|s| s.to_string()) },
1 => Some(if sz > 0 && payload[0] != 0 {
"true".into()
} else {
"false".into()
}),
2 => {
if sz != 4 {
None
} else {
let mut b = [0u8; 4];
b.copy_from_slice(payload);
Some((i32::from_le_bytes(b)).to_string())
}
}
3 => {
if sz != 8 {
None
} else {
let mut b = [0u8; 8];
b.copy_from_slice(payload);
Some((i64::from_le_bytes(b)).to_string())
}
}
5 => {
if sz != 8 {
None
} else {
let mut b = [0u8; 8];
b.copy_from_slice(payload);
Some(f64::from_le_bytes(b).to_string())
}
}
6 => std::str::from_utf8(payload).ok().map(|s| s.to_string()),
7 => Some(format!("<bytes:{}>", sz)),
8 => { if sz==8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); Some(format!("<handle {}:{}>", u32::from_le_bytes(t), u32::from_le_bytes(i))) } else { None } },
8 => {
if sz == 8 {
let mut t = [0u8; 4];
t.copy_from_slice(&payload[0..4]);
let mut i = [0u8; 4];
i.copy_from_slice(&payload[4..8]);
Some(format!(
"<handle {}:{}>",
u32::from_le_bytes(t),
u32::from_le_bytes(i)
))
} else {
None
}
}
_ => None,
}
}
// Write TLV birth result: Handle(tag=8,size=8) with (type_id, instance_id)
unsafe fn write_tlv_birth(type_id: u32, instance_id: u32, out: *mut u8, out_len: *mut usize) -> i32 {
unsafe fn write_tlv_birth(
type_id: u32,
instance_id: u32,
out: *mut u8,
out_len: *mut usize,
) -> i32 {
let need = 4 + 4 + 8; // header + entry + payload
if *out_len < need { *out_len = need; return NYB_E_SHORT_BUFFER; }
if *out_len < need {
*out_len = need;
return NYB_E_SHORT_BUFFER;
}
let mut buf = Vec::with_capacity(need);
// header
buf.extend_from_slice(&1u16.to_le_bytes());
@ -92,7 +163,10 @@ unsafe fn write_tlv_birth(type_id: u32, instance_id: u32, out: *mut u8, out_len:
unsafe fn write_tlv_void(out: *mut u8, out_len: *mut usize) -> i32 {
let need = 4 + 4; // header + entry
if *out_len < need { *out_len = need; return NYB_E_SHORT_BUFFER; }
if *out_len < need {
*out_len = need;
return NYB_E_SHORT_BUFFER;
}
let mut buf = Vec::with_capacity(need);
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&1u16.to_le_bytes());
@ -105,7 +179,9 @@ unsafe fn write_tlv_void(out: *mut u8, out_len: *mut usize) -> i32 {
// ===== Entry points =====
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 {
@ -123,18 +199,24 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_CONSOLE_BOX { return NYB_E_INVALID_TYPE; }
if type_id != TYPE_ID_CONSOLE_BOX {
return NYB_E_INVALID_TYPE;
}
unsafe {
match method_id {
METHOD_BIRTH => {
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INSTANCES.lock() {
m.insert(id, ConsoleInstance{});
} else { return NYB_E_PLUGIN_ERROR; }
m.insert(id, ConsoleInstance {});
} else {
return NYB_E_PLUGIN_ERROR;
}
return write_tlv_birth(TYPE_ID_CONSOLE_BOX, id, result, result_len);
}
METHOD_FINI => {
if let Ok(mut m) = INSTANCES.lock() { m.remove(&instance_id); }
if let Ok(mut m) = INSTANCES.lock() {
m.remove(&instance_id);
}
return NYB_SUCCESS;
}
METHOD_LOG | METHOD_PRINTLN => {
@ -143,7 +225,11 @@ pub extern "C" fn nyash_plugin_invoke(
Ok(s) => s,
Err(_) => format_first_any(slice).unwrap_or_else(|| "".to_string()),
};
if method_id == METHOD_LOG { print!("{}", s); } else { println!("{}", s); }
if method_id == METHOD_LOG {
print!("{}", s);
} else {
println!("{}", s);
}
return write_tlv_void(result, result_len);
}
_ => NYB_E_INVALID_METHOD,
@ -165,7 +251,9 @@ pub struct NyashTypeBoxFfi {
unsafe impl Sync for NyashTypeBoxFfi {}
extern "C" fn console_resolve(name: *const c_char) -> u32 {
if name.is_null() { return 0; }
if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"log" => METHOD_LOG,
@ -174,7 +262,14 @@ extern "C" fn console_resolve(name: *const c_char) -> u32 {
}
}
extern "C" fn console_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
extern "C" fn console_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
METHOD_LOG | METHOD_PRINTLN => {
@ -183,7 +278,11 @@ extern "C" fn console_invoke_id(instance_id: u32, method_id: u32, args: *const u
Ok(s) => s,
Err(_) => format_first_any(slice).unwrap_or_else(|| "".to_string()),
};
if method_id == METHOD_LOG { print!("{}", s); } else { println!("{}", s); }
if method_id == METHOD_LOG {
print!("{}", s);
} else {
println!("{}", s);
}
return write_tlv_void(result, result_len);
}
_ => NYB_E_INVALID_METHOD,

View File

@ -1,7 +1,10 @@
//! Nyash CounterBox Plugin - BID-FFI v1 Implementation
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
use once_cell::sync::Lazy;
@ -15,25 +18,32 @@ const NYB_E_PLUGIN_ERROR: i32 = -5;
const NYB_E_INVALID_HANDLE: i32 = -8;
// ===== Method IDs =====
const METHOD_BIRTH: u32 = 0; // constructor
const METHOD_INC: u32 = 1; // increments and returns new count
const METHOD_GET: u32 = 2; // returns current count
const METHOD_FINI: u32 = u32::MAX; // destructor
const METHOD_BIRTH: u32 = 0; // constructor
const METHOD_INC: u32 = 1; // increments and returns new count
const METHOD_GET: u32 = 2; // returns current count
const METHOD_FINI: u32 = u32::MAX; // destructor
// Assign a unique type_id for CounterBox (distinct from FileBox=6)
const TYPE_ID_COUNTER: u32 = 7;
// ===== Instance state =====
struct CounterInstance { count: i32 }
struct CounterInstance {
count: i32,
}
static INSTANCES: Lazy<Mutex<HashMap<u32, CounterInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static INSTANCES: Lazy<Mutex<HashMap<u32, CounterInstance>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
pub extern "C" fn nyash_plugin_init() -> i32 {
NYB_SUCCESS
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -45,18 +55,26 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_COUNTER { return NYB_E_INVALID_TYPE; }
if type_id != TYPE_ID_COUNTER {
return NYB_E_INVALID_TYPE;
}
unsafe {
match method_id {
METHOD_BIRTH => {
// Return new instance handle (u32 id)
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
if result_len.is_null() {
return NYB_E_INVALID_ARGS;
}
if preflight(result, result_len, 4) {
return NYB_E_SHORT_BUFFER;
}
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut map) = INSTANCES.lock() {
map.insert(id, CounterInstance { count: 0 });
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_PLUGIN_ERROR;
}
let bytes = id.to_le_bytes();
std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4);
*result_len = 4;
@ -66,7 +84,9 @@ pub extern "C" fn nyash_plugin_invoke(
if let Ok(mut map) = INSTANCES.lock() {
map.remove(&instance_id);
NYB_SUCCESS
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_INC => {
// increments and returns new count as I32 TLV
@ -74,18 +94,30 @@ pub extern "C" fn nyash_plugin_invoke(
if let Some(inst) = map.get_mut(&instance_id) {
inst.count += 1;
let v = inst.count;
if preflight(result, result_len, 12) { return NYB_E_SHORT_BUFFER; }
if preflight(result, result_len, 12) {
return NYB_E_SHORT_BUFFER;
}
return write_tlv_i32(v, result, result_len);
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
METHOD_GET => {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
if preflight(result, result_len, 12) { return NYB_E_SHORT_BUFFER; }
if preflight(result, result_len, 12) {
return NYB_E_SHORT_BUFFER;
}
return write_tlv_i32(inst.count, result, result_len);
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
_ => NYB_E_INVALID_METHOD,
}
@ -94,8 +126,11 @@ pub extern "C" fn nyash_plugin_invoke(
// ===== TLV helpers =====
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
if result_len.is_null() {
return NYB_E_INVALID_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes()); // version
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
for (tag, payload) in payloads {
@ -122,7 +157,9 @@ fn write_tlv_i32(v: i32, result: *mut u8, result_len: *mut usize) -> i32 {
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe {
if result_len.is_null() { return false; }
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
@ -130,4 +167,3 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
}
false
}

View File

@ -3,7 +3,13 @@
//! - Windows GUI integration (egui/eframe) can be enabled later via `with-egui` feature
use once_cell::sync::Lazy;
use std::{collections::HashMap, sync::{Mutex, atomic::{AtomicU32, Ordering}}};
use std::{
collections::HashMap,
sync::{
atomic::{AtomicU32, Ordering},
Mutex,
},
};
// ===== Error/Status codes (BID-FFI v1 aligned) =====
const OK: i32 = 0;
@ -18,12 +24,12 @@ const TID_EGUI: u32 = 70; // match nyash.toml [box_types]
// methods
const M_BIRTH: u32 = 0;
const M_OPEN: u32 = 1; // open(width:int, height:int, title:str)
const M_UI_LABEL: u32 = 2; // uiLabel(text:str)
const M_UI_BUTTON: u32 = 3; // uiButton(text:str) -> future: events
const M_OPEN: u32 = 1; // open(width:int, height:int, title:str)
const M_UI_LABEL: u32 = 2; // uiLabel(text:str)
const M_UI_BUTTON: u32 = 3; // uiButton(text:str) -> future: events
const M_POLL_EVENT: u32 = 4; // pollEvent() -> Result.Ok(text) / Result.Err("none")
const M_RUN: u32 = 5; // run() -> enters loop or no-op
const M_CLOSE: u32 = 6; // close()
const M_RUN: u32 = 5; // run() -> enters loop or no-op
const M_CLOSE: u32 = 6; // close()
const M_FINI: u32 = u32::MAX;
#[derive(Default)]
@ -56,7 +62,9 @@ const ABI_TAG: u32 = 0x58594254; // 'T''Y''B''X' little-endian (TYBX)
extern "C" fn tb_resolve(name: *const std::os::raw::c_char) -> u32 {
unsafe {
if name.is_null() { return 0; }
if name.is_null() {
return 0;
}
let s = std::ffi::CStr::from_ptr(name).to_string_lossy();
match s.as_ref() {
"birth" => M_BIRTH,
@ -72,8 +80,23 @@ extern "C" fn tb_resolve(name: *const std::os::raw::c_char) -> u32 {
}
}
extern "C" fn tb_invoke_id(_method_id: u32, instance_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
nyash_plugin_invoke(TID_EGUI, _method_id, instance_id, args, args_len, result, result_len)
extern "C" fn tb_invoke_id(
_method_id: u32,
instance_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
nyash_plugin_invoke(
TID_EGUI,
_method_id,
instance_id,
args,
args_len,
result,
result_len,
)
}
static TYPE_NAME: &[u8] = b"EguiBox\0";
@ -90,10 +113,14 @@ pub static nyash_typebox_EguiBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
// ===== Plugin entry points =====
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
pub extern "C" fn nyash_plugin_init() -> i32 {
OK
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -105,34 +132,81 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TID_EGUI { return E_TYPE; }
if type_id != TID_EGUI {
return E_TYPE;
}
unsafe {
match method_id {
M_BIRTH => {
let need = 4; // instance_id (u32 LE)
if result_len.is_null() { return E_ARGS; }
if result.is_null() || *result_len < need { *result_len = need; return E_SHORT; }
if result_len.is_null() {
return E_ARGS;
}
if result.is_null() || *result_len < need {
*result_len = need;
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() { m.insert(id, EguiInstance::default()); } else { return E_FAIL; }
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
if let Ok(mut m) = INST.lock() {
m.insert(id, EguiInstance::default());
} else {
return E_FAIL;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_FAIL
}
}
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_FAIL } }
M_OPEN => {
eprintln!("[EGUI] M_OPEN invoked");
let (w, h, title) = match tlv_read_open_args(args, args_len) { Some(v) => v, None => return E_ARGS };
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.width=w; inst.height=h; inst.title=title; } else { return E_FAIL; } } else { return E_FAIL; }
let (w, h, title) = match tlv_read_open_args(args, args_len) {
Some(v) => v,
None => return E_ARGS,
};
if let Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) {
inst.width = w;
inst.height = h;
inst.title = title;
} else {
return E_FAIL;
}
} else {
return E_FAIL;
}
write_tlv_void(result, result_len)
}
M_UI_LABEL => {
eprintln!("[EGUI] M_UI_LABEL invoked");
let text = match tlv_read_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.labels.push(text); } else { return E_FAIL; } } else { return E_FAIL; }
let text = match tlv_read_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) {
inst.labels.push(text);
} else {
return E_FAIL;
}
} else {
return E_FAIL;
}
write_tlv_void(result, result_len)
}
M_UI_BUTTON => {
eprintln!("[EGUI] M_UI_BUTTON invoked");
// For now: stub, accept and return Void
if tlv_read_string(args, args_len, 0).is_none() { return E_ARGS; }
if tlv_read_string(args, args_len, 0).is_none() {
return E_ARGS;
}
write_tlv_void(result, result_len)
}
M_POLL_EVENT => {
@ -147,7 +221,12 @@ pub extern "C" fn nyash_plugin_invoke(
{
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
guirun::run_window(inst.width, inst.height, &inst.title, inst.labels.clone());
guirun::run_window(
inst.width,
inst.height,
&inst.title,
inst.labels.clone(),
);
}
}
}
@ -165,54 +244,108 @@ pub extern "C" fn nyash_plugin_invoke(
// ===== TLV helpers (version=1) =====
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return E_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
if result_len.is_null() {
return E_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads {
buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload);
buf.push(*tag);
buf.push(0);
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(payload);
}
unsafe {
let need = buf.len();
if result.is_null() || *result_len < need { *result_len = need; return E_SHORT; }
if result.is_null() || *result_len < need {
*result_len = need;
return E_SHORT;
}
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, need);
*result_len = need;
}
OK
}
fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(9u8, &[])], result, result_len) }
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(9u8, &[])], result, result_len)
}
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
}
unsafe fn tlv_parse_header(data: *const u8, len: usize) -> Option<(u16,u16,usize)> {
if data.is_null() || len < 4 { return None; }
unsafe fn tlv_parse_header(data: *const u8, len: usize) -> Option<(u16, u16, usize)> {
if data.is_null() || len < 4 {
return None;
}
let b = std::slice::from_raw_parts(data, len);
let ver = u16::from_le_bytes([b[0], b[1]]);
let argc = u16::from_le_bytes([b[2], b[3]]);
if ver != 1 { return None; }
if ver != 1 {
return None;
}
Some((ver, argc, 4))
}
unsafe fn tlv_read_entry_at(data: *const u8, len: usize, mut pos: usize) -> Option<(u8, usize, usize)> {
unsafe fn tlv_read_entry_at(
data: *const u8,
len: usize,
mut pos: usize,
) -> Option<(u8, usize, usize)> {
let b = std::slice::from_raw_parts(data, len);
if pos + 4 > len { return None; }
let tag = b[pos]; let _ = b[pos+1]; let size = u16::from_le_bytes([b[pos+2], b[pos+3]]) as usize; pos += 4;
if pos + size > len { return None; }
if pos + 4 > len {
return None;
}
let tag = b[pos];
let _ = b[pos + 1];
let size = u16::from_le_bytes([b[pos + 2], b[pos + 3]]) as usize;
pos += 4;
if pos + size > len {
return None;
}
Some((tag, size, pos))
}
unsafe fn tlv_read_i64(data: *const u8, len: usize, index: usize) -> Option<i64> {
let (_, argc, mut pos) = tlv_parse_header(data, len)?; if argc < (index as u16 + 1) { return None; }
for i in 0..=index { let (tag, size, p) = tlv_read_entry_at(data, len, pos)?; if tag == 3 && size == 8 { if i == index { let b = std::slice::from_raw_parts(data.add(p), 8); let mut t=[0u8;8]; t.copy_from_slice(b); return Some(i64::from_le_bytes(t)); } } pos = p + size; }
let (_, argc, mut pos) = tlv_parse_header(data, len)?;
if argc < (index as u16 + 1) {
return None;
}
for i in 0..=index {
let (tag, size, p) = tlv_read_entry_at(data, len, pos)?;
if tag == 3 && size == 8 {
if i == index {
let b = std::slice::from_raw_parts(data.add(p), 8);
let mut t = [0u8; 8];
t.copy_from_slice(b);
return Some(i64::from_le_bytes(t));
}
}
pos = p + size;
}
None
}
unsafe fn tlv_read_string(data: *const u8, len: usize, index: usize) -> Option<String> {
let (_, argc, mut pos) = tlv_parse_header(data, len)?; if argc < (index as u16 + 1) { return None; }
for i in 0..=index { let (tag, size, p) = tlv_read_entry_at(data, len, pos)?; if tag == 6 || tag == 7 { if i == index { let s = std::slice::from_raw_parts(data.add(p), size); return Some(String::from_utf8_lossy(s).to_string()); } } pos = p + size; }
let (_, argc, mut pos) = tlv_parse_header(data, len)?;
if argc < (index as u16 + 1) {
return None;
}
for i in 0..=index {
let (tag, size, p) = tlv_read_entry_at(data, len, pos)?;
if tag == 6 || tag == 7 {
if i == index {
let s = std::slice::from_raw_parts(data.add(p), size);
return Some(String::from_utf8_lossy(s).to_string());
}
}
pos = p + size;
}
None
}
unsafe fn tlv_read_open_args(args: *const u8, len: usize) -> Option<(i32,i32,String)> {
unsafe fn tlv_read_open_args(args: *const u8, len: usize) -> Option<(i32, i32, String)> {
let w = tlv_read_i64(args, len, 0)? as i32;
let h = tlv_read_i64(args, len, 1)? as i32;
let t = tlv_read_string(args, len, 2)?;
Some((w,h,t))
Some((w, h, t))
}
// ===== GUI 実行with-egui, クロスプラットフォーム) =====
@ -224,7 +357,9 @@ mod guirun {
pub fn run_window(w: i32, h: i32, title: &str, labels: Vec<String>) {
eprintln!("[EGUI] run_window: w={} h={} title='{}'", w, h, title);
let diag = std::env::var("NYASH_EGUI_DIAG").ok().as_deref() == Some("1");
let scale_override = std::env::var("NYASH_EGUI_SCALE").ok().and_then(|s| s.parse::<f32>().ok());
let scale_override = std::env::var("NYASH_EGUI_SCALE")
.ok()
.and_then(|s| s.parse::<f32>().ok());
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([w.max(100) as f32, h.max(100) as f32])
@ -232,7 +367,14 @@ mod guirun {
..Default::default()
};
struct App { labels: Vec<String>, diag: bool, printed: bool, init_w: i32, init_h: i32, scale: Option<f32> }
struct App {
labels: Vec<String>,
diag: bool,
printed: bool,
init_w: i32,
init_h: i32,
scale: Option<f32>,
}
impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
if self.diag && !self.printed {
@ -252,13 +394,21 @@ mod guirun {
let rect = ctx.screen_rect();
ui.small(format!(
"DPI: ppp={:.3} logical={:.1}x{:.1} init={}x{} scale={}",
ppp, rect.width(), rect.height(), self.init_w, self.init_h,
self.scale.map(|v| format!("{:.2}", v)).unwrap_or_else(|| "auto".into())
ppp,
rect.width(),
rect.height(),
self.init_w,
self.init_h,
self.scale
.map(|v| format!("{:.2}", v))
.unwrap_or_else(|| "auto".into())
));
}
});
egui::CentralPanel::default().show(ctx, |ui| {
for s in &self.labels { ui.label(s); }
for s in &self.labels {
ui.label(s);
}
});
}
}
@ -277,7 +427,14 @@ mod guirun {
cc.egui_ctx.set_pixels_per_point(ppp);
eprintln!("[EGUI][diag] override pixels_per_point to {:.3}", ppp);
}
Box::new(App { labels, diag, printed: false, init_w, init_h, scale: scale_override })
Box::new(App {
labels,
diag,
printed: false,
init_w,
init_h,
scale: scale_override,
})
}
}),
);

View File

@ -2,7 +2,10 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
const OK: i32 = 0;
const E_SHORT: i32 = -1;
@ -12,13 +15,13 @@ const E_ARGS: i32 = -4;
const E_PLUGIN: i32 = -5;
const E_HANDLE: i32 = -8;
const M_BIRTH: u32 = 0; // constructor (stateless)
const M_TO_UTF8_BYTES: u32 = 1; // toUtf8Bytes(s) -> bytes
const M_BIRTH: u32 = 0; // constructor (stateless)
const M_TO_UTF8_BYTES: u32 = 1; // toUtf8Bytes(s) -> bytes
const M_FROM_UTF8_BYTES: u32 = 2; // fromUtf8Bytes(bytes) -> string
const M_BASE64_ENC: u32 = 3; // base64Encode(s|bytes) -> string
const M_BASE64_DEC: u32 = 4; // base64Decode(str) -> bytes
const M_HEX_ENC: u32 = 5; // hexEncode(s|bytes) -> string
const M_HEX_DEC: u32 = 6; // hexDecode(str) -> bytes
const M_BASE64_ENC: u32 = 3; // base64Encode(s|bytes) -> string
const M_BASE64_DEC: u32 = 4; // base64Decode(str) -> bytes
const M_HEX_ENC: u32 = 5; // hexEncode(s|bytes) -> string
const M_HEX_DEC: u32 = 6; // hexDecode(str) -> bytes
const M_FINI: u32 = u32::MAX;
// Assign an unused type id
@ -30,10 +33,14 @@ static INST: Lazy<Mutex<HashMap<u32, EncInstance>>> = Lazy::new(|| Mutex::new(Ha
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
pub extern "C" fn nyash_plugin_init() -> i32 {
OK
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -45,44 +52,97 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_ENCODING { return E_TYPE; }
if type_id != TYPE_ID_ENCODING {
return E_TYPE;
}
unsafe {
match method_id {
M_BIRTH => {
if result_len.is_null() { return E_ARGS; }
if preflight(result, result_len, 4) { return E_SHORT; }
if result_len.is_null() {
return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() { m.insert(id, EncInstance); } else { return E_PLUGIN; }
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
if let Ok(mut m) = INST.lock() {
m.insert(id, EncInstance);
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
}
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
M_TO_UTF8_BYTES => {
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
let s = match read_arg_string(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
write_tlv_bytes(s.as_bytes(), result, result_len)
}
M_FROM_UTF8_BYTES => {
let bytes = match read_arg_bytes(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
match String::from_utf8(bytes) { Ok(s) => write_tlv_string(&s, result, result_len), Err(_) => write_tlv_string("", result, result_len) }
let bytes = match read_arg_bytes(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
match String::from_utf8(bytes) {
Ok(s) => write_tlv_string(&s, result, result_len),
Err(_) => write_tlv_string("", result, result_len),
}
}
M_BASE64_ENC => {
if let Some(b) = read_arg_bytes(args, args_len, 0) { let s = base64::encode(b); return write_tlv_string(&s, result, result_len); }
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
if let Some(b) = read_arg_bytes(args, args_len, 0) {
let s = base64::encode(b);
return write_tlv_string(&s, result, result_len);
}
let s = match read_arg_string(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
let enc = base64::encode(s.as_bytes());
write_tlv_string(&enc, result, result_len)
}
M_BASE64_DEC => {
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
match base64::decode(s.as_bytes()) { Ok(b) => write_tlv_bytes(&b, result, result_len), Err(_) => write_tlv_bytes(&[], result, result_len) }
let s = match read_arg_string(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
match base64::decode(s.as_bytes()) {
Ok(b) => write_tlv_bytes(&b, result, result_len),
Err(_) => write_tlv_bytes(&[], result, result_len),
}
}
M_HEX_ENC => {
if let Some(b) = read_arg_bytes(args, args_len, 0) { let s = hex::encode(b); return write_tlv_string(&s, result, result_len); }
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
if let Some(b) = read_arg_bytes(args, args_len, 0) {
let s = hex::encode(b);
return write_tlv_string(&s, result, result_len);
}
let s = match read_arg_string(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
let enc = hex::encode(s.as_bytes());
write_tlv_string(&enc, result, result_len)
}
M_HEX_DEC => {
let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
match hex::decode(s.as_bytes()) { Ok(b) => write_tlv_bytes(&b, result, result_len), Err(_) => write_tlv_bytes(&[], result, result_len) }
let s = match read_arg_string(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
match hex::decode(s.as_bytes()) {
Ok(b) => write_tlv_bytes(&b, result, result_len),
Err(_) => write_tlv_bytes(&[], result, result_len),
}
}
_ => E_METHOD,
}
@ -90,29 +150,98 @@ pub extern "C" fn nyash_plugin_invoke(
}
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
unsafe {
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
}
}
false
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return E_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
if result_len.is_null() {
return E_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads {
buf.push(*tag);
buf.push(0);
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(payload);
}
unsafe {
let needed = buf.len();
if result.is_null() || *result_len < needed {
*result_len = needed;
return E_SHORT;
}
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
*result_len = needed;
}
OK
}
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
fn write_tlv_bytes(b: &[u8], result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(7u8, b)], result, result_len) }
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
}
fn write_tlv_bytes(b: &[u8], result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(7u8, b)], result, result_len)
}
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; }
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag == 6 || tag == 7 {
return Some(String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string());
} else {
return None;
}
}
off += 4 + size;
}
None
}
fn read_arg_bytes(args: *const u8, args_len: usize, n: usize) -> Option<Vec<u8>> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 7 || tag == 6 { return Some(buf[off+4..off+4+size].to_vec()); } else { return None; } } off += 4 + size; }
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag == 7 || tag == 6 {
return Some(buf[off + 4..off + 4 + size].to_vec());
} else {
return None;
}
}
off += 4 + size;
}
None
}

View File

@ -4,7 +4,7 @@
use std::ffi::{c_char, c_void, CStr, CString};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write, Seek};
use std::io::{Read, Seek, Write};
use std::os::raw::c_int;
/// プラグインのマジックナンバー('NYAS'
@ -88,12 +88,10 @@ pub unsafe extern "C" fn nyash_file_read(handle: *mut c_void) -> *mut c_char {
}
match file_box.file.read_to_string(&mut content) {
Ok(_) => {
match CString::new(content) {
Ok(c_str) => c_str.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
Ok(_) => match CString::new(content) {
Ok(c_str) => c_str.into_raw(),
Err(_) => std::ptr::null_mut(),
},
Err(_) => std::ptr::null_mut(),
}
}
@ -104,10 +102,7 @@ pub unsafe extern "C" fn nyash_file_read(handle: *mut c_void) -> *mut c_char {
/// - handleはnyash_file_openから返された有効なポインタである必要がある
/// - contentは有効なUTF-8のC文字列である必要がある
#[no_mangle]
pub unsafe extern "C" fn nyash_file_write(
handle: *mut c_void,
content: *const c_char
) -> c_int {
pub unsafe extern "C" fn nyash_file_write(handle: *mut c_void, content: *const c_char) -> c_int {
if handle.is_null() || content.is_null() {
return 0;
}
@ -128,7 +123,7 @@ pub unsafe extern "C" fn nyash_file_write(
match file_box.file.write_all(content_str.as_bytes()) {
Ok(_) => 1, // 成功
Err(_) => 0, // 失敗
Err(_) => 0, // 失敗
}
}

View File

@ -5,8 +5,11 @@
use std::collections::HashMap;
use std::os::raw::c_char;
// std::ptr削除未使用
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::io::{Read, Write, Seek, SeekFrom};
use std::io::{Read, Seek, SeekFrom, Write};
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
// ============ FFI Types ============
@ -37,7 +40,7 @@ const NYB_E_PLUGIN_ERROR: i32 = -5;
const NYB_E_INVALID_HANDLE: i32 = -8;
// ============ Method IDs ============
const METHOD_BIRTH: u32 = 0; // Constructor
const METHOD_BIRTH: u32 = 0; // Constructor
const METHOD_OPEN: u32 = 1;
const METHOD_READ: u32 = 2;
const METHOD_WRITE: u32 = 3;
@ -45,20 +48,19 @@ const METHOD_CLOSE: u32 = 4;
const METHOD_EXISTS: u32 = 5;
const METHOD_COPY_FROM: u32 = 7; // New: copyFrom(other: Handle)
const METHOD_CLONE_SELF: u32 = 8; // New: cloneSelf() -> Handle
const METHOD_FINI: u32 = u32::MAX; // Destructor
const METHOD_FINI: u32 = u32::MAX; // Destructor
// ============ FileBox Instance ============
struct FileBoxInstance {
file: Option<std::fs::File>,
path: String,
buffer: Option<Vec<u8>>, // プラグインが管理するバッファ
buffer: Option<Vec<u8>>, // プラグインが管理するバッファ
}
// グローバルインスタンス管理(実際の実装ではより安全な方法を使用)
use once_cell::sync::Lazy;
static INSTANCES: Lazy<Mutex<HashMap<u32, FileBoxInstance>>> = Lazy::new(|| {
Mutex::new(HashMap::new())
});
static INSTANCES: Lazy<Mutex<HashMap<u32, FileBoxInstance>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
// ホスト関数テーブルは使用しないHost VTable廃止
@ -70,7 +72,7 @@ static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
/// ABI version
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 {
1 // BID-1 support
1 // BID-1 support
}
/// Plugin initialization (optional - global setup)
@ -119,11 +121,14 @@ pub extern "C" fn nyash_plugin_invoke(
// 新しいインスタンスを作成
let instance_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut map) = INSTANCES.lock() {
map.insert(instance_id, FileBoxInstance {
file: None,
path: String::new(),
buffer: None,
});
map.insert(
instance_id,
FileBoxInstance {
file: None,
path: String::new(),
buffer: None,
},
);
} else {
return NYB_E_PLUGIN_ERROR;
}
@ -149,7 +154,9 @@ pub extern "C" fn nyash_plugin_invoke(
match tlv_parse_two_strings(args) {
Ok((path, mode)) => {
// Preflight for Void TLV: header(4) + entry(4)
if preflight(_result, _result_len, 8) { return NYB_E_SHORT_BUFFER; }
if preflight(_result, _result_len, 8) {
return NYB_E_SHORT_BUFFER;
}
log_info(&format!("OPEN path='{}' mode='{}'", path, mode));
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&_instance_id) {
@ -161,8 +168,12 @@ pub extern "C" fn nyash_plugin_invoke(
}
Err(_) => return NYB_E_PLUGIN_ERROR,
}
} else { return NYB_E_PLUGIN_ERROR; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_PLUGIN_ERROR;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
Err(_) => NYB_E_INVALID_ARGS,
}
@ -172,18 +183,20 @@ pub extern "C" fn nyash_plugin_invoke(
let args = std::slice::from_raw_parts(_args, _args_len);
if _args_len > 0 {
match tlv_parse_string(args) {
Ok(path) => {
match open_file("r", &path) {
Ok(mut file) => {
let mut buf = Vec::new();
if let Err(_) = file.read_to_end(&mut buf) { return NYB_E_PLUGIN_ERROR; }
let need = 8usize.saturating_add(buf.len());
if preflight(_result, _result_len, need) { return NYB_E_SHORT_BUFFER; }
return write_tlv_bytes(&buf, _result, _result_len);
Ok(path) => match open_file("r", &path) {
Ok(mut file) => {
let mut buf = Vec::new();
if let Err(_) = file.read_to_end(&mut buf) {
return NYB_E_PLUGIN_ERROR;
}
Err(_) => return NYB_E_PLUGIN_ERROR,
let need = 8usize.saturating_add(buf.len());
if preflight(_result, _result_len, need) {
return NYB_E_SHORT_BUFFER;
}
return write_tlv_bytes(&buf, _result, _result_len);
}
}
Err(_) => return NYB_E_PLUGIN_ERROR,
},
Err(_) => return NYB_E_INVALID_ARGS,
}
} else {
@ -196,14 +209,22 @@ pub extern "C" fn nyash_plugin_invoke(
Ok(n) => {
log_info(&format!("READ {} bytes (entire file)", n));
let need = 8usize.saturating_add(buf.len());
if preflight(_result, _result_len, need) { return NYB_E_SHORT_BUFFER; }
if preflight(_result, _result_len, need) {
return NYB_E_SHORT_BUFFER;
}
return write_tlv_bytes(&buf, _result, _result_len);
}
Err(_) => return NYB_E_PLUGIN_ERROR,
}
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
}
METHOD_WRITE => {
@ -211,40 +232,58 @@ pub extern "C" fn nyash_plugin_invoke(
let args = std::slice::from_raw_parts(_args, _args_len);
match tlv_parse_optional_string_and_bytes(args) {
Ok((Some(path), data)) => {
if preflight(_result, _result_len, 12) { return NYB_E_SHORT_BUFFER; }
if preflight(_result, _result_len, 12) {
return NYB_E_SHORT_BUFFER;
}
match open_file("w", &path) {
Ok(mut file) => {
if let Err(_) = file.write_all(&data) { return NYB_E_PLUGIN_ERROR; }
if let Err(_) = file.flush() { return NYB_E_PLUGIN_ERROR; }
if let Err(_) = file.write_all(&data) {
return NYB_E_PLUGIN_ERROR;
}
if let Err(_) = file.flush() {
return NYB_E_PLUGIN_ERROR;
}
return write_tlv_i32(data.len() as i32, _result, _result_len);
}
Err(_) => return NYB_E_PLUGIN_ERROR,
}
}
Ok((None, data)) => {
if preflight(_result, _result_len, 12) { return NYB_E_SHORT_BUFFER; }
if preflight(_result, _result_len, 12) {
return NYB_E_SHORT_BUFFER;
}
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&_instance_id) {
if let Some(file) = inst.file.as_mut() {
match file.write(&data) {
Ok(n) => {
if let Err(_) = file.flush() { return NYB_E_PLUGIN_ERROR; }
if let Err(_) = file.flush() {
return NYB_E_PLUGIN_ERROR;
}
log_info(&format!("WRITE {} bytes", n));
inst.buffer = Some(data.clone());
return write_tlv_i32(n as i32, _result, _result_len);
}
Err(_) => return NYB_E_PLUGIN_ERROR,
}
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
Err(_) => NYB_E_INVALID_ARGS,
}
}
METHOD_CLOSE => {
// Preflight for Void TLV
if preflight(_result, _result_len, 8) { return NYB_E_SHORT_BUFFER; }
if preflight(_result, _result_len, 8) {
return NYB_E_SHORT_BUFFER;
}
log_info("CLOSE");
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&_instance_id) {
@ -262,8 +301,12 @@ pub extern "C" fn nyash_plugin_invoke(
let args = std::slice::from_raw_parts(_args, _args_len);
match tlv_parse_handle(args) {
Ok((type_id, other_id)) => {
if type_id != _type_id { return NYB_E_INVALID_TYPE; }
if preflight(_result, _result_len, 8) { return NYB_E_SHORT_BUFFER; }
if type_id != _type_id {
return NYB_E_INVALID_TYPE;
}
if preflight(_result, _result_len, 8) {
return NYB_E_SHORT_BUFFER;
}
if let Ok(mut map) = INSTANCES.lock() {
// 1) まずsrcからデータを取り出す不変参照のみ
let mut data: Vec<u8> = Vec::new();
@ -283,21 +326,31 @@ pub extern "C" fn nyash_plugin_invoke(
read_ok = true;
}
}
if !read_ok { return NYB_E_PLUGIN_ERROR; }
} else { return NYB_E_INVALID_HANDLE; }
if !read_ok {
return NYB_E_PLUGIN_ERROR;
}
} else {
return NYB_E_INVALID_HANDLE;
}
// 2) dstへ書き込み可変参照
if let Some(dst) = map.get_mut(&_instance_id) {
if let Some(fdst) = dst.file.as_mut() {
let _ = fdst.seek(SeekFrom::Start(0));
if fdst.write_all(&data).is_err() { return NYB_E_PLUGIN_ERROR; }
if fdst.write_all(&data).is_err() {
return NYB_E_PLUGIN_ERROR;
}
let _ = fdst.set_len(data.len() as u64);
let _ = fdst.flush();
}
dst.buffer = Some(data);
return write_tlv_void(_result, _result_len);
} else { return NYB_E_INVALID_HANDLE; }
} else { return NYB_E_PLUGIN_ERROR; }
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
Err(_) => NYB_E_INVALID_ARGS,
}
@ -305,11 +358,22 @@ pub extern "C" fn nyash_plugin_invoke(
METHOD_CLONE_SELF => {
// Return a new instance (handle) as TLV Handle
// Preflight for Handle TLV: header(4) + entry(4) + payload(8)
if preflight(_result, _result_len, 16) { return NYB_E_SHORT_BUFFER; }
if preflight(_result, _result_len, 16) {
return NYB_E_SHORT_BUFFER;
}
let new_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut map) = INSTANCES.lock() { map.insert(new_id, FileBoxInstance { file: None, path: String::new(), buffer: None }); }
if let Ok(mut map) = INSTANCES.lock() {
map.insert(
new_id,
FileBoxInstance {
file: None,
path: String::new(),
buffer: None,
},
);
}
// Build TLV result
let mut payload = [0u8;8];
let mut payload = [0u8; 8];
payload[0..4].copy_from_slice(&_type_id.to_le_bytes());
payload[4..8].copy_from_slice(&new_id.to_le_bytes());
return write_tlv_result(&[(8u8, &payload)], _result, _result_len);
@ -320,13 +384,15 @@ pub extern "C" fn nyash_plugin_invoke(
match tlv_parse_string(args) {
Ok(path) => {
let exists = std::path::Path::new(&path).exists();
if preflight(_result, _result_len, 9) { return NYB_E_SHORT_BUFFER; }
if preflight(_result, _result_len, 9) {
return NYB_E_SHORT_BUFFER;
}
return write_tlv_bool(exists, _result, _result_len);
}
Err(_) => NYB_E_INVALID_ARGS,
}
}
_ => NYB_SUCCESS
_ => NYB_SUCCESS,
}
}
}
@ -337,16 +403,27 @@ fn open_file(mode: &str, path: &str) -> Result<std::fs::File, std::io::Error> {
use std::fs::OpenOptions;
match mode {
"r" => OpenOptions::new().read(true).open(path),
"w" => OpenOptions::new().write(true).create(true).truncate(true).open(path),
"w" => OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path),
"a" => OpenOptions::new().append(true).create(true).open(path),
"rw" | "r+" => OpenOptions::new().read(true).write(true).create(true).open(path),
"rw" | "r+" => OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path),
_ => OpenOptions::new().read(true).open(path),
}
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
if result_len.is_null() {
return NYB_E_INVALID_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes()); // version
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc
for (tag, payload) in payloads {
@ -380,13 +457,15 @@ fn write_tlv_i32(v: i32, result: *mut u8, result_len: *mut usize) -> i32 {
}
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
let b = [if v {1u8} else {0u8}];
let b = [if v { 1u8 } else { 0u8 }];
write_tlv_result(&[(1u8, &b)], result, result_len)
}
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe {
if result_len.is_null() { return false; }
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
@ -395,58 +474,90 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
false
}
fn tlv_parse_header(data: &[u8]) -> Result<(u16,u16,usize), ()> {
if data.len() < 4 { return Err(()); }
fn tlv_parse_header(data: &[u8]) -> Result<(u16, u16, usize), ()> {
if data.len() < 4 {
return Err(());
}
let ver = u16::from_le_bytes([data[0], data[1]]);
let argc = u16::from_le_bytes([data[2], data[3]]);
if ver != 1 { return Err(()); }
if ver != 1 {
return Err(());
}
Ok((ver, argc, 4))
}
fn tlv_parse_two_strings(data: &[u8]) -> Result<(String, String), ()> {
let (_, argc, mut pos) = tlv_parse_header(data)?;
if argc < 2 { return Err(()); }
if argc < 2 {
return Err(());
}
let s1 = tlv_parse_string_at(data, &mut pos)?;
let s2 = tlv_parse_string_at(data, &mut pos)?;
Ok((s1, s2))
}
fn tlv_parse_string_at(data: &[u8], pos: &mut usize) -> Result<String, ()> {
if *pos + 4 > data.len() { return Err(()); }
let tag = data[*pos]; let _res = data[*pos+1];
let size = u16::from_le_bytes([data[*pos+2], data[*pos+3]]) as usize;
if *pos + 4 > data.len() {
return Err(());
}
let tag = data[*pos];
let _res = data[*pos + 1];
let size = u16::from_le_bytes([data[*pos + 2], data[*pos + 3]]) as usize;
*pos += 4;
if tag != 6 || *pos + size > data.len() { return Err(()); }
let slice = &data[*pos..*pos+size];
if tag != 6 || *pos + size > data.len() {
return Err(());
}
let slice = &data[*pos..*pos + size];
*pos += size;
std::str::from_utf8(slice).map(|s| s.to_string()).map_err(|_| ())
std::str::from_utf8(slice)
.map(|s| s.to_string())
.map_err(|_| ())
}
fn tlv_parse_i32(data: &[u8]) -> Result<i32, ()> {
let (_, argc, mut pos) = tlv_parse_header(data)?;
if argc < 1 { return Err(()); }
if pos + 8 > data.len() { return Err(()); }
let tag = data[pos]; let _res = data[pos+1];
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
if tag != 2 || size != 4 || pos + size > data.len() { return Err(()); }
let mut b = [0u8;4]; b.copy_from_slice(&data[pos..pos+4]);
if argc < 1 {
return Err(());
}
if pos + 8 > data.len() {
return Err(());
}
let tag = data[pos];
let _res = data[pos + 1];
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if tag != 2 || size != 4 || pos + size > data.len() {
return Err(());
}
let mut b = [0u8; 4];
b.copy_from_slice(&data[pos..pos + 4]);
Ok(i32::from_le_bytes(b))
}
fn tlv_parse_bytes(data: &[u8]) -> Result<Vec<u8>, ()> {
let (_, argc, mut pos) = tlv_parse_header(data)?;
if argc < 1 { return Err(()); }
if pos + 4 > data.len() { return Err(()); }
let tag = data[pos]; let _res = data[pos+1];
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
if argc < 1 {
return Err(());
}
if pos + 4 > data.len() {
return Err(());
}
let tag = data[pos];
let _res = data[pos + 1];
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
// StringタグもBytesタグも受け付ける互換性のため
if (tag != 6 && tag != 7) || pos + size > data.len() { return Err(()); }
Ok(data[pos..pos+size].to_vec())
if (tag != 6 && tag != 7) || pos + size > data.len() {
return Err(());
}
Ok(data[pos..pos + size].to_vec())
}
fn tlv_parse_string(data: &[u8]) -> Result<String, ()> {
let (_, argc, mut pos) = tlv_parse_header(data)?;
if argc < 1 { return Err(()); }
if argc < 1 {
return Err(());
}
tlv_parse_string_at(data, &mut pos)
}
@ -454,18 +565,30 @@ fn tlv_parse_optional_string_and_bytes(data: &[u8]) -> Result<(Option<String>, V
let (_, argc, mut pos) = tlv_parse_header(data)?;
if argc == 1 {
// only bytes
if pos + 4 > data.len() { return Err(()); }
let tag = data[pos]; let _res = data[pos+1];
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
if (tag != 6 && tag != 7) || pos + size > data.len() { return Err(()); }
return Ok((None, data[pos..pos+size].to_vec()));
if pos + 4 > data.len() {
return Err(());
}
let tag = data[pos];
let _res = data[pos + 1];
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if (tag != 6 && tag != 7) || pos + size > data.len() {
return Err(());
}
return Ok((None, data[pos..pos + size].to_vec()));
} else if argc >= 2 {
let s = tlv_parse_string_at(data, &mut pos)?;
if pos + 4 > data.len() { return Err(()); }
let tag = data[pos]; let _res = data[pos+1];
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
if (tag != 6 && tag != 7) || pos + size > data.len() { return Err(()); }
Ok((Some(s), data[pos..pos+size].to_vec()))
if pos + 4 > data.len() {
return Err(());
}
let tag = data[pos];
let _res = data[pos + 1];
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if (tag != 6 && tag != 7) || pos + size > data.len() {
return Err(());
}
Ok((Some(s), data[pos..pos + size].to_vec()))
} else {
Err(())
}
@ -473,13 +596,23 @@ fn tlv_parse_optional_string_and_bytes(data: &[u8]) -> Result<(Option<String>, V
fn tlv_parse_handle(data: &[u8]) -> Result<(u32, u32), ()> {
let (_, argc, mut pos) = tlv_parse_header(data)?;
if argc < 1 { return Err(()); }
if pos + 4 > data.len() { return Err(()); }
let tag = data[pos]; let _res = data[pos+1];
let size = u16::from_le_bytes([data[pos+2], data[pos+3]]) as usize; pos += 4;
if tag != 8 || size != 8 || pos + size > data.len() { return Err(()); }
let mut t = [0u8;4]; t.copy_from_slice(&data[pos..pos+4]);
let mut i = [0u8;4]; i.copy_from_slice(&data[pos+4..pos+8]);
if argc < 1 {
return Err(());
}
if pos + 4 > data.len() {
return Err(());
}
let tag = data[pos];
let _res = data[pos + 1];
let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if tag != 8 || size != 8 || pos + size > data.len() {
return Err(());
}
let mut t = [0u8; 4];
t.copy_from_slice(&data[pos..pos + 4]);
let mut i = [0u8; 4];
i.copy_from_slice(&data[pos + 4..pos + 8]);
Ok((u32::from_le_bytes(t), u32::from_le_bytes(i)))
}

View File

@ -3,9 +3,12 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::os::raw::c_char;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
// Error codes
const OK: i32 = 0;
@ -18,23 +21,29 @@ const E_HANDLE: i32 = -8;
// Methods
const M_BIRTH: u32 = 0;
const M_GET: u32 = 1;
const M_SET: u32 = 2;
const M_FINI: u32 = u32::MAX;
const M_GET: u32 = 1;
const M_SET: u32 = 2;
const M_FINI: u32 = u32::MAX;
// Assigned type id (nyash.toml must match)
const TYPE_ID_INTEGER: u32 = 12;
struct IntInstance { value: i64 }
struct IntInstance {
value: i64,
}
static INST: Lazy<Mutex<HashMap<u32, IntInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
pub extern "C" fn nyash_plugin_init() -> i32 {
OK
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -46,29 +55,65 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_INTEGER { return E_TYPE; }
if type_id != TYPE_ID_INTEGER {
return E_TYPE;
}
unsafe {
match method_id {
M_BIRTH => {
if result_len.is_null() { return E_ARGS; }
if preflight(result, result_len, 4) { return E_SHORT; }
if result_len.is_null() {
return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
// Optional initial value from first arg (i64/i32)
let init = read_arg_i64(args, args_len, 0).unwrap_or(0);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() { m.insert(id, IntInstance { value: init }); }
else { return E_PLUGIN; }
if let Ok(mut m) = INST.lock() {
m.insert(id, IntInstance { value: init });
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN }
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
}
M_GET => {
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
return write_tlv_i64(inst.value, result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_SET => {
let v = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.value = v; return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
let v = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
if let Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) {
inst.value = v;
return write_tlv_i64(inst.value, result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
_ => E_METHOD,
}
@ -89,7 +134,9 @@ pub struct NyashTypeBoxFfi {
unsafe impl Sync for NyashTypeBoxFfi {}
extern "C" fn integer_resolve(name: *const c_char) -> u32 {
if name.is_null() { return 0; }
if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"get" => M_GET,
@ -98,15 +145,42 @@ extern "C" fn integer_resolve(name: *const c_char) -> u32 {
}
}
extern "C" fn integer_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
extern "C" fn integer_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
M_GET => {
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
return write_tlv_i64(inst.value, result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_SET => {
let v = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.value = v; return write_tlv_i64(inst.value, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
let v = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
if let Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) {
inst.value = v;
return write_tlv_i64(inst.value, result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
_ => E_METHOD,
}
@ -125,34 +199,76 @@ pub static nyash_typebox_IntegerBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
};
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
unsafe {
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
}
}
false
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return E_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
if result_len.is_null() {
return E_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
for (tag, payload) in payloads {
buf.push(*tag);
buf.push(0);
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(payload);
}
unsafe {
let needed = buf.len();
if result.is_null() || *result_len < needed {
*result_len = needed;
return E_SHORT;
}
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
*result_len = needed;
}
OK
}
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
}
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; // header
for i in 0..=n {
if buf.len() < off + 4 { return None; }
let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
if buf.len() < off + 4 + size { return None; }
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
match (tag, size) {
(3, 8) => { let mut b=[0u8;8]; b.copy_from_slice(&buf[off+4..off+12]); return Some(i64::from_le_bytes(b)); }
(2, 4) => { let mut b=[0u8;4]; b.copy_from_slice(&buf[off+4..off+8]); let v = i32::from_le_bytes(b) as i64; return Some(v); }
(3, 8) => {
let mut b = [0u8; 8];
b.copy_from_slice(&buf[off + 4..off + 12]);
return Some(i64::from_le_bytes(b));
}
(2, 4) => {
let mut b = [0u8; 4];
b.copy_from_slice(&buf[off + 4..off + 8]);
let v = i32::from_le_bytes(b) as i64;
return Some(v);
}
_ => return None,
}
}

View File

@ -4,9 +4,12 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::os::raw::c_char;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
// Error codes
const NYB_SUCCESS: i32 = 0;
@ -19,37 +22,41 @@ const NYB_E_INVALID_HANDLE: i32 = -8;
// Methods
const METHOD_BIRTH: u32 = 0;
const METHOD_SIZE: u32 = 1;
const METHOD_GET: u32 = 2; // args: i64 key -> TLV i64
const METHOD_HAS: u32 = 3; // args: i64 key -> TLV bool
const METHOD_SET: u32 = 4; // args: key(int|string), value(i64) -> TLV i64 (size)
const METHOD_REMOVE:u32 = 6; // args: key(int|string) -> TLV bool (removed)
const METHOD_SIZE: u32 = 1;
const METHOD_GET: u32 = 2; // args: i64 key -> TLV i64
const METHOD_HAS: u32 = 3; // args: i64 key -> TLV bool
const METHOD_SET: u32 = 4; // args: key(int|string), value(i64) -> TLV i64 (size)
const METHOD_REMOVE: u32 = 6; // args: key(int|string) -> TLV bool (removed)
const METHOD_CLEAR: u32 = 7; // args: () -> TLV i64 (size after clear=0)
const METHOD_KEYS_S:u32 = 8; // args: () -> TLV string (newline-joined keys)
const METHOD_GET_OR:u32 = 9; // args: key(int|string), default(i64) -> TLV i64
const METHOD_FINI: u32 = u32::MAX;
const METHOD_KEYS_S: u32 = 8; // args: () -> TLV string (newline-joined keys)
const METHOD_GET_OR: u32 = 9; // args: key(int|string), default(i64) -> TLV i64
const METHOD_FINI: u32 = u32::MAX;
// Extended string-key methods
const METHOD_SET_STR: u32 = 10; // setS(name: string, val: i64) -> i64(size)
const METHOD_GET_STR: u32 = 11; // getS(name: string) -> i64
const METHOD_HAS_STR: u32 = 12; // hasS(name: string) -> bool
const METHOD_VALUES_S: u32 = 13; // valuesStr() -> string (newline-joined)
const METHOD_TO_JSON: u32 = 14; // toJson() -> string
const METHOD_TO_JSON: u32 = 14; // toJson() -> string
// Type id (nyash.toml に合わせる)
const TYPE_ID_MAP: u32 = 11;
struct MapInstance {
data_i64: HashMap<i64,i64>,
data_str: HashMap<String,i64>,
data_i64: HashMap<i64, i64>,
data_str: HashMap<String, i64>,
}
static INSTANCES: Lazy<Mutex<HashMap<u32, MapInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
pub extern "C" fn nyash_plugin_init() -> i32 {
NYB_SUCCESS
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -61,44 +68,81 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_MAP { return NYB_E_INVALID_TYPE; }
if type_id != TYPE_ID_MAP {
return NYB_E_INVALID_TYPE;
}
unsafe {
match method_id {
METHOD_BIRTH => {
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; }
if result_len.is_null() {
return NYB_E_INVALID_ARGS;
}
if preflight(result, result_len, 4) {
return NYB_E_SHORT_BUFFER;
}
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut map) = INSTANCES.lock() {
map.insert(id, MapInstance { data_i64: HashMap::new(), data_str: HashMap::new() });
} else { return NYB_E_PLUGIN_ERROR; }
map.insert(
id,
MapInstance {
data_i64: HashMap::new(),
data_str: HashMap::new(),
},
);
} else {
return NYB_E_PLUGIN_ERROR;
}
std::ptr::copy_nonoverlapping(id.to_le_bytes().as_ptr(), result, 4);
*result_len = 4;
NYB_SUCCESS
}
METHOD_FINI => {
if let Ok(mut map) = INSTANCES.lock() { map.remove(&instance_id); NYB_SUCCESS } else { NYB_E_PLUGIN_ERROR }
if let Ok(mut map) = INSTANCES.lock() {
map.remove(&instance_id);
NYB_SUCCESS
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_SIZE => {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
let sz = inst.data_i64.len() + inst.data_str.len();
write_tlv_i64(sz as i64, result, result_len)
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_GET => {
if let Some(ik) = read_arg_i64(args, args_len, 0) {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
match inst.data_i64.get(&ik).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
match inst.data_i64.get(&ik).copied() {
Some(v) => write_tlv_i64(v, result, result_len),
None => NYB_E_INVALID_ARGS,
}
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
match inst.data_str.get(&sk).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
match inst.data_str.get(&sk).copied() {
Some(v) => write_tlv_i64(v, result, result_len),
None => NYB_E_INVALID_ARGS,
}
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else {
NYB_E_INVALID_ARGS
}
@ -106,35 +150,58 @@ pub extern "C" fn nyash_plugin_invoke(
METHOD_HAS => {
if let Some(ik) = read_arg_i64(args, args_len, 0) {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len) } else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
if let Some(inst) = map.get(&instance_id) {
write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len)
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len) } else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
if let Some(inst) = map.get(&instance_id) {
write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len)
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else {
NYB_E_INVALID_ARGS
}
}
METHOD_SET => {
// value は i64 限定
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
let val = match read_arg_i64(args, args_len, 1) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if let Some(ik) = read_arg_i64(args, args_len, 0) {
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
inst.data_i64.insert(ik, val);
let sz = inst.data_i64.len() + inst.data_str.len();
return write_tlv_i64(sz as i64, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
inst.data_str.insert(sk, val);
let sz = inst.data_i64.len() + inst.data_str.len();
return write_tlv_i64(sz as i64, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else {
NYB_E_INVALID_ARGS
}
@ -143,12 +210,28 @@ pub extern "C" fn nyash_plugin_invoke(
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
// try int key
if let Some(ik) = read_arg_i64(args, args_len, 0) { return write_tlv_bool(inst.data_i64.remove(&ik).is_some(), result, result_len); }
if let Some(ik) = read_arg_i64(args, args_len, 0) {
return write_tlv_bool(
inst.data_i64.remove(&ik).is_some(),
result,
result_len,
);
}
// try string key
if let Some(sk) = read_arg_string(args, args_len, 0) { return write_tlv_bool(inst.data_str.remove(&sk).is_some(), result, result_len); }
if let Some(sk) = read_arg_string(args, args_len, 0) {
return write_tlv_bool(
inst.data_str.remove(&sk).is_some(),
result,
result_len,
);
}
NYB_E_INVALID_ARGS
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_CLEAR => {
if let Ok(mut map) = INSTANCES.lock() {
@ -156,23 +239,39 @@ pub extern "C" fn nyash_plugin_invoke(
inst.data_i64.clear();
inst.data_str.clear();
return write_tlv_i64(0, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_KEYS_S => {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
let mut keys: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
for k in inst.data_i64.keys() { keys.push(k.to_string()); }
for k in inst.data_str.keys() { keys.push(k.clone()); }
let mut keys: Vec<String> =
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
for k in inst.data_i64.keys() {
keys.push(k.to_string());
}
for k in inst.data_str.keys() {
keys.push(k.clone());
}
keys.sort();
let out = keys.join("\n");
return write_tlv_string(&out, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_GET_OR => {
let defv = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
let defv = match read_arg_i64(args, args_len, 1) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
// prefer exact match, else default
@ -185,44 +284,86 @@ pub extern "C" fn nyash_plugin_invoke(
return write_tlv_i64(v, result, result_len);
}
NYB_E_INVALID_ARGS
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_SET_STR => {
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS };
let key = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return NYB_E_INVALID_ARGS,
};
let val = match read_arg_i64(args, args_len, 1) {
Some(v) => v,
None => return NYB_E_INVALID_ARGS,
};
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
inst.data_str.insert(key, val);
let sz = inst.data_i64.len() + inst.data_str.len();
return write_tlv_i64(sz as i64, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_GET_STR => {
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
let key = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return NYB_E_INVALID_ARGS,
};
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
match inst.data_str.get(&key).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
match inst.data_str.get(&key).copied() {
Some(v) => write_tlv_i64(v, result, result_len),
None => NYB_E_INVALID_ARGS,
}
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_HAS_STR => {
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
let key = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return NYB_E_INVALID_ARGS,
};
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&key), result, result_len) } else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
if let Some(inst) = map.get(&instance_id) {
write_tlv_bool(inst.data_str.contains_key(&key), result, result_len)
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_VALUES_S => {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
let mut vals: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
for v in inst.data_i64.values() { vals.push(v.to_string()); }
for v in inst.data_str.values() { vals.push(v.to_string()); }
let mut vals: Vec<String> =
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
for v in inst.data_i64.values() {
vals.push(v.to_string());
}
for v in inst.data_str.values() {
vals.push(v.to_string());
}
let out = vals.join("\n");
return write_tlv_string(&out, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_TO_JSON => {
if let Ok(map) = INSTANCES.lock() {
@ -230,23 +371,35 @@ pub extern "C" fn nyash_plugin_invoke(
let mut s = String::from("{");
let mut first = true;
for (k, v) in inst.data_str.iter() {
if !first { s.push(','); }
if !first {
s.push(',');
}
first = false;
// JSON string key
s.push('"'); s.push_str(&escape_json(k)); s.push_str("\": ");
s.push('"');
s.push_str(&escape_json(k));
s.push_str("\": ");
s.push_str(&v.to_string());
}
for (k, v) in inst.data_i64.iter() {
if !first { s.push(','); }
if !first {
s.push(',');
}
first = false;
// numeric key as string per JSON
s.push('"'); s.push_str(&k.to_string()); s.push_str("\": ");
s.push('"');
s.push_str(&k.to_string());
s.push_str("\": ");
s.push_str(&v.to_string());
}
s.push('}');
return write_tlv_string(&s, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
_ => NYB_E_INVALID_METHOD,
}
@ -267,7 +420,9 @@ pub struct NyashTypeBoxFfi {
unsafe impl Sync for NyashTypeBoxFfi {}
extern "C" fn mapbox_resolve(name: *const c_char) -> u32 {
if name.is_null() { return 0; }
if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"size" | "len" => METHOD_SIZE,
@ -280,41 +435,82 @@ extern "C" fn mapbox_resolve(name: *const c_char) -> u32 {
}
}
extern "C" fn mapbox_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
extern "C" fn mapbox_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
match method_id {
METHOD_SIZE => {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
let sz = inst.data_i64.len() + inst.data_str.len();
return write_tlv_i64(sz as i64, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_GET => {
if let Some(ik) = read_arg_i64(args, args_len, 0) {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
match inst.data_i64.get(&ik).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
match inst.data_i64.get(&ik).copied() {
Some(v) => write_tlv_i64(v, result, result_len),
None => NYB_E_INVALID_ARGS,
}
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
match inst.data_str.get(&sk).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else { NYB_E_INVALID_ARGS }
match inst.data_str.get(&sk).copied() {
Some(v) => write_tlv_i64(v, result, result_len),
None => NYB_E_INVALID_ARGS,
}
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else {
NYB_E_INVALID_ARGS
}
}
METHOD_HAS => {
if let Some(ik) = read_arg_i64(args, args_len, 0) {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len) } else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
if let Some(inst) = map.get(&instance_id) {
write_tlv_bool(inst.data_i64.contains_key(&ik), result, result_len)
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len) } else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else { NYB_E_INVALID_ARGS }
if let Some(inst) = map.get(&instance_id) {
write_tlv_bool(inst.data_str.contains_key(&sk), result, result_len)
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else {
NYB_E_INVALID_ARGS
}
}
METHOD_SET => {
// key: i64 or string, value: i64
@ -325,55 +521,104 @@ extern "C" fn mapbox_invoke_id(instance_id: u32, method_id: u32, args: *const u8
inst.data_i64.insert(ik, val);
let sz = inst.data_i64.len() + inst.data_str.len();
return write_tlv_i64(sz as i64, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else if let Some(sk) = read_arg_string(args, args_len, 0) {
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
inst.data_str.insert(sk, val);
let sz = inst.data_i64.len() + inst.data_str.len();
return write_tlv_i64(sz as i64, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else { NYB_E_INVALID_ARGS }
} else { NYB_E_INVALID_ARGS }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
} else {
NYB_E_INVALID_ARGS
}
} else {
NYB_E_INVALID_ARGS
}
}
METHOD_GET_STR => {
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
let key = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return NYB_E_INVALID_ARGS,
};
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
match inst.data_str.get(&key).copied() { Some(v) => write_tlv_i64(v, result, result_len), None => NYB_E_INVALID_ARGS }
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
match inst.data_str.get(&key).copied() {
Some(v) => write_tlv_i64(v, result, result_len),
None => NYB_E_INVALID_ARGS,
}
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_HAS_STR => {
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return NYB_E_INVALID_ARGS };
let key = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return NYB_E_INVALID_ARGS,
};
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data_str.contains_key(&key), result, result_len) } else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
if let Some(inst) = map.get(&instance_id) {
write_tlv_bool(inst.data_str.contains_key(&key), result, result_len)
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_KEYS_S => {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
let mut keys: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
for k in inst.data_i64.keys() { keys.push(k.to_string()); }
for k in inst.data_str.keys() { keys.push(k.clone()); }
let mut keys: Vec<String> =
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
for k in inst.data_i64.keys() {
keys.push(k.to_string());
}
for k in inst.data_str.keys() {
keys.push(k.clone());
}
keys.sort();
let out = keys.join("\n");
return write_tlv_string(&out, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_VALUES_S => {
if let Ok(map) = INSTANCES.lock() {
if let Some(inst) = map.get(&instance_id) {
let mut vals: Vec<String> = Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
for v in inst.data_i64.values() { vals.push(v.to_string()); }
for v in inst.data_str.values() { vals.push(v.to_string()); }
let mut vals: Vec<String> =
Vec::with_capacity(inst.data_i64.len() + inst.data_str.len());
for v in inst.data_i64.values() {
vals.push(v.to_string());
}
for v in inst.data_str.values() {
vals.push(v.to_string());
}
let out = vals.join("\n");
return write_tlv_string(&out, result, result_len);
} else { NYB_E_INVALID_HANDLE }
} else { NYB_E_PLUGIN_ERROR }
} else {
NYB_E_INVALID_HANDLE
}
} else {
NYB_E_PLUGIN_ERROR
}
}
_ => NYB_E_INVALID_METHOD,
}
@ -391,7 +636,7 @@ pub static nyash_typebox_MapBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
};
fn escape_json(s: &str) -> String {
let mut out = String::with_capacity(s.len()+8);
let mut out = String::with_capacity(s.len() + 8);
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
@ -409,7 +654,9 @@ fn escape_json(s: &str) -> String {
// TLV helpers
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe {
if result_len.is_null() { return false; }
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
@ -419,8 +666,11 @@ fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return NYB_E_INVALID_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
if result_len.is_null() {
return NYB_E_INVALID_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads {
@ -441,27 +691,44 @@ fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut
NYB_SUCCESS
}
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
fn write_tlv_bool(bv: bool, result: *mut u8, result_len: *mut usize) -> i32 { let b = [if bv {1u8} else {0u8}]; write_tlv_result(&[(1u8, &b)], result, result_len) }
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
}
fn write_tlv_bool(bv: bool, result: *mut u8, result_len: *mut usize) -> i32 {
let b = [if bv { 1u8 } else { 0u8 }];
write_tlv_result(&[(1u8, &b)], result, result_len)
}
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
}
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 { return None; }
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
if buf.len() < off + 4 + size { return None; }
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag == 3 && size == 8 {
let mut b = [0u8;8]; b.copy_from_slice(&buf[off+4..off+12]);
let mut b = [0u8; 8];
b.copy_from_slice(&buf[off + 4..off + 12]);
return Some(i64::from_le_bytes(b));
} else if tag == 2 && size == 4 {
let mut b = [0u8;4]; b.copy_from_slice(&buf[off+4..off+8]);
let mut b = [0u8; 4];
b.copy_from_slice(&buf[off + 4..off + 8]);
return Some(i32::from_le_bytes(b) as i64);
} else { return None; }
} else {
return None;
}
}
off += 4 + size;
}
@ -469,19 +736,27 @@ fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
}
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 { return None; }
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
if buf.len() < off + 4 + size { return None; }
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag == 6 || tag == 7 {
let s = &buf[off+4..off+4+size];
let s = &buf[off + 4..off + 4 + size];
return Some(String::from_utf8_lossy(s).to_string());
} else { return None; }
} else {
return None;
}
}
off += 4 + size;
}

View File

@ -3,7 +3,10 @@
//! TimeBox: now() -> i64 (unix seconds)
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
// Error codes
const OK: i32 = 0;
@ -37,18 +40,56 @@ static ID: AtomicU32 = AtomicU32::new(1);
// TLV helpers
mod tlv {
pub fn header(argc: u16) -> Vec<u8> { let mut b=Vec::with_capacity(4); b.extend_from_slice(&1u16.to_le_bytes()); b.extend_from_slice(&argc.to_le_bytes()); b }
pub fn encode_handle(buf: &mut Vec<u8>, t: u32, i: u32) { buf.push(8); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&t.to_le_bytes()); buf.extend_from_slice(&i.to_le_bytes()); }
pub fn encode_i64(buf: &mut Vec<u8>, v: i64) { buf.push(3); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&v.to_le_bytes()); }
pub fn encode_void(buf: &mut Vec<u8>) { buf.push(9); buf.push(0); buf.push(0); buf.push(0); }
pub fn decode_first(args:&[u8]) -> Option<(u16,u16,usize)> { if args.len()<8 {return None;} let argc=u16::from_le_bytes([args[2],args[3]]); if argc==0{return None;} let tag=u16::from_le_bytes([args[4],args[5]]); let sz=u16::from_le_bytes([args[6],args[7]]); Some((tag,sz,8)) }
pub fn header(argc: u16) -> Vec<u8> {
let mut b = Vec::with_capacity(4);
b.extend_from_slice(&1u16.to_le_bytes());
b.extend_from_slice(&argc.to_le_bytes());
b
}
pub fn encode_handle(buf: &mut Vec<u8>, t: u32, i: u32) {
buf.push(8);
buf.push(0);
buf.push(8);
buf.push(0);
buf.extend_from_slice(&t.to_le_bytes());
buf.extend_from_slice(&i.to_le_bytes());
}
pub fn encode_i64(buf: &mut Vec<u8>, v: i64) {
buf.push(3);
buf.push(0);
buf.push(8);
buf.push(0);
buf.extend_from_slice(&v.to_le_bytes());
}
pub fn encode_void(buf: &mut Vec<u8>) {
buf.push(9);
buf.push(0);
buf.push(0);
buf.push(0);
}
pub fn decode_first(args: &[u8]) -> Option<(u16, u16, usize)> {
if args.len() < 8 {
return None;
}
let argc = u16::from_le_bytes([args[2], args[3]]);
if argc == 0 {
return None;
}
let tag = u16::from_le_bytes([args[4], args[5]]);
let sz = u16::from_le_bytes([args[6], args[7]]);
Some((tag, sz, 8))
}
}
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
pub extern "C" fn nyash_plugin_init() -> i32 {
OK
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -77,31 +118,67 @@ pub extern "C" fn nyash_plugin_invoke(
}
}
unsafe fn birth<T>(tid: u32, map: &Lazy<Mutex<HashMap<u32,T>>>, out: *mut u8, out_len: *mut usize) -> i32 where T: Default {
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
unsafe fn birth<T>(
tid: u32,
map: &Lazy<Mutex<HashMap<u32, T>>>,
out: *mut u8,
out_len: *mut usize,
) -> i32
where
T: Default,
{
let need = 4 + 4 + 8;
if *out_len < need {
*out_len = need;
return E_SHORT;
}
let id = ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = map.lock() { m.insert(id, T::default()); } else { return E_FAIL; }
let mut buf = tlv::header(1); tlv::encode_handle(&mut buf, tid, id);
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len(); OK
if let Ok(mut m) = map.lock() {
m.insert(id, T::default());
} else {
return E_FAIL;
}
let mut buf = tlv::header(1);
tlv::encode_handle(&mut buf, tid, id);
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
*out_len = buf.len();
OK
}
unsafe fn fini<T>(map: &Lazy<Mutex<HashMap<u32,T>>>, instance_id: u32) -> i32 {
if let Ok(mut m) = map.lock() { m.remove(&instance_id); OK } else { E_FAIL }
unsafe fn fini<T>(map: &Lazy<Mutex<HashMap<u32, T>>>, instance_id: u32) -> i32 {
if let Ok(mut m) = map.lock() {
m.remove(&instance_id);
OK
} else {
E_FAIL
}
}
unsafe fn sqrt_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mut usize) -> i32 {
if args_len < 8 { return E_ARGS; }
if args_len < 8 {
return E_ARGS;
}
let a = std::slice::from_raw_parts(args, args_len);
if let Some((tag, sz, p)) = tlv::decode_first(a) {
if tag == 3 && sz == 8 && a.len() >= p+8 {
let mut b=[0u8;8]; b.copy_from_slice(&a[p..p+8]);
if tag == 3 && sz == 8 && a.len() >= p + 8 {
let mut b = [0u8; 8];
b.copy_from_slice(&a[p..p + 8]);
let x = i64::from_le_bytes(b) as f64;
let r = x.sqrt();
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
let need = 4 + 4 + 8;
if *out_len < need {
*out_len = need;
return E_SHORT;
}
let mut buf = tlv::header(1);
// encode f64 (tag=5)
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
buf.push(5);
buf.push(0);
buf.push(8);
buf.push(0);
buf.extend_from_slice(&r.to_le_bytes());
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
*out_len = buf.len();
return OK;
}
}
@ -109,26 +186,53 @@ unsafe fn sqrt_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mu
}
unsafe fn now_call(out: *mut u8, out_len: *mut usize) -> i32 {
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs() as i64).unwrap_or(0);
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
let mut buf = tlv::header(1); tlv::encode_i64(&mut buf, ts);
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let need = 4 + 4 + 8;
if *out_len < need {
*out_len = need;
return E_SHORT;
}
let mut buf = tlv::header(1);
tlv::encode_i64(&mut buf, ts);
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
*out_len = buf.len();
OK
}
unsafe fn trig_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mut usize, is_sin: bool) -> i32 {
if args_len < 8 { return E_ARGS; }
unsafe fn trig_call(
args: *const u8,
args_len: usize,
out: *mut u8,
out_len: *mut usize,
is_sin: bool,
) -> i32 {
if args_len < 8 {
return E_ARGS;
}
let a = std::slice::from_raw_parts(args, args_len);
if let Some((tag, sz, p)) = tlv::decode_first(a) {
if tag == 3 && sz == 8 && a.len() >= p+8 {
let mut b=[0u8;8]; b.copy_from_slice(&a[p..p+8]);
if tag == 3 && sz == 8 && a.len() >= p + 8 {
let mut b = [0u8; 8];
b.copy_from_slice(&a[p..p + 8]);
let x = i64::from_le_bytes(b) as f64;
let r = if is_sin { x.sin() } else { x.cos() };
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
let need = 4 + 4 + 8;
if *out_len < need {
*out_len = need;
return E_SHORT;
}
let mut buf = tlv::header(1);
// encode f64 (tag=5)
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
buf.push(5);
buf.push(0);
buf.push(8);
buf.push(0);
buf.extend_from_slice(&r.to_le_bytes());
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
*out_len = buf.len();
return OK;
}
}
@ -136,18 +240,30 @@ unsafe fn trig_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mu
}
unsafe fn round_call(args: *const u8, args_len: usize, out: *mut u8, out_len: *mut usize) -> i32 {
if args_len < 8 { return E_ARGS; }
if args_len < 8 {
return E_ARGS;
}
let a = std::slice::from_raw_parts(args, args_len);
if let Some((tag, sz, p)) = tlv::decode_first(a) {
if tag == 3 && sz == 8 && a.len() >= p+8 {
let mut b=[0u8;8]; b.copy_from_slice(&a[p..p+8]);
if tag == 3 && sz == 8 && a.len() >= p + 8 {
let mut b = [0u8; 8];
b.copy_from_slice(&a[p..p + 8]);
let x = i64::from_le_bytes(b) as f64;
let r = x.round();
let need = 4+4+8; if *out_len < need { *out_len = need; return E_SHORT; }
let need = 4 + 4 + 8;
if *out_len < need {
*out_len = need;
return E_SHORT;
}
let mut buf = tlv::header(1);
// encode f64 (tag=5)
buf.push(5); buf.push(0); buf.push(8); buf.push(0); buf.extend_from_slice(&r.to_le_bytes());
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len()); *out_len = buf.len();
buf.push(5);
buf.push(0);
buf.push(8);
buf.push(0);
buf.extend_from_slice(&r.to_le_bytes());
std::ptr::copy_nonoverlapping(buf.as_ptr(), out, buf.len());
*out_len = buf.len();
return OK;
}
}

View File

@ -1,7 +1,7 @@
use chrono::{DateTime, Datelike, Timelike, Utc};
use rand::Rng;
use std::ffi::{c_char, c_void, CStr, CString};
use std::ptr;
use chrono::{DateTime, Utc, Datelike, Timelike};
use rand::Rng;
// MathBox構造体
pub struct MathBox {

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,11 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::path::{Path, Component};
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::path::{Component, Path};
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
const OK: i32 = 0;
const E_SHORT: i32 = -1;
@ -12,14 +15,14 @@ const E_METHOD: i32 = -3;
const E_ARGS: i32 = -4;
const E_PLUGIN: i32 = -5;
const M_BIRTH: u32 = 0; // constructor -> instance
const M_JOIN: u32 = 1; // join(base, rest) -> string
const M_DIRNAME: u32 = 2; // dirname(path) -> string
const M_BASENAME: u32 = 3; // basename(path) -> string
const M_EXTNAME: u32 = 4; // extname(path) -> string
const M_IS_ABS: u32 = 5; // isAbs(path) -> bool
const M_NORMALIZE: u32 = 6; // normalize(path) -> string
const M_FINI: u32 = u32::MAX; // fini
const M_BIRTH: u32 = 0; // constructor -> instance
const M_JOIN: u32 = 1; // join(base, rest) -> string
const M_DIRNAME: u32 = 2; // dirname(path) -> string
const M_BASENAME: u32 = 3; // basename(path) -> string
const M_EXTNAME: u32 = 4; // extname(path) -> string
const M_IS_ABS: u32 = 5; // isAbs(path) -> bool
const M_NORMALIZE: u32 = 6; // normalize(path) -> string
const M_FINI: u32 = u32::MAX; // fini
const TYPE_ID_PATH: u32 = 55;
@ -29,10 +32,14 @@ static INST: Lazy<Mutex<HashMap<u32, PathInstance>>> = Lazy::new(|| Mutex::new(H
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
pub extern "C" fn nyash_plugin_init() -> i32 {
OK
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -44,45 +51,99 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_PATH { return E_TYPE; }
if type_id != TYPE_ID_PATH {
return E_TYPE;
}
unsafe {
match method_id {
M_BIRTH => {
if result_len.is_null() { return E_ARGS; }
if preflight(result, result_len, 4) { return E_SHORT; }
if result_len.is_null() {
return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() { m.insert(id, PathInstance); } else { return E_PLUGIN; }
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
if let Ok(mut m) = INST.lock() {
m.insert(id, PathInstance);
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
}
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
M_JOIN => {
let base = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let rest = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS };
let joined = if base.ends_with('/') || base.ends_with('\\') { format!("{}{}", base, rest) } else { format!("{}/{}", base, rest) };
let base = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let rest = match read_arg_string(args, args_len, 1) {
Some(s) => s,
None => return E_ARGS,
};
let joined = if base.ends_with('/') || base.ends_with('\\') {
format!("{}{}", base, rest)
} else {
format!("{}/{}", base, rest)
};
write_tlv_string(&joined, result, result_len)
}
M_DIRNAME => {
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let d = Path::new(&p).parent().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| "".to_string());
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let d = Path::new(&p)
.parent()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_else(|| "".to_string());
write_tlv_string(&d, result, result_len)
}
M_BASENAME => {
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let b = Path::new(&p).file_name().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| "".to_string());
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let b = Path::new(&p)
.file_name()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_else(|| "".to_string());
write_tlv_string(&b, result, result_len)
}
M_EXTNAME => {
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let ext = Path::new(&p).extension().map(|x| format!(".{}", x.to_string_lossy())).unwrap_or_else(|| "".to_string());
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let ext = Path::new(&p)
.extension()
.map(|x| format!(".{}", x.to_string_lossy()))
.unwrap_or_else(|| "".to_string());
write_tlv_string(&ext, result, result_len)
}
M_IS_ABS => {
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let abs = Path::new(&p).is_absolute() || p.contains(":\\");
write_tlv_bool(abs, result, result_len)
}
M_NORMALIZE => {
let p = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let norm = path_clean::PathClean::clean(Path::new(&p));
write_tlv_string(norm.to_string_lossy().as_ref(), result, result_len)
}
@ -92,23 +153,72 @@ pub extern "C" fn nyash_plugin_invoke(
}
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
unsafe {
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
}
}
false
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return E_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
if result_len.is_null() {
return E_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads {
buf.push(*tag);
buf.push(0);
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(payload);
}
unsafe {
let needed = buf.len();
if result.is_null() || *result_len < needed {
*result_len = needed;
return E_SHORT;
}
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
*result_len = needed;
}
OK
}
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) }
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
}
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
}
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; }
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag == 6 || tag == 7 {
return Some(String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string());
} else {
return None;
}
}
off += 4 + size;
}
None
}

View File

@ -1,6 +1,6 @@
use once_cell::sync::Lazy;
use std::sync::Mutex;
use serde_json::Value as Json;
use std::sync::Mutex;
const NYB_SUCCESS: i32 = 0;
const NYB_E_INVALID_METHOD: i32 = -3;
@ -14,10 +14,14 @@ const METHOD_FINI: u32 = u32::MAX;
static NEXT_ID: Lazy<Mutex<u32>> = Lazy::new(|| Mutex::new(1));
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS }
pub extern "C" fn nyash_plugin_init() -> i32 {
NYB_SUCCESS
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -29,14 +33,20 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_COMPILER { return NYB_E_INVALID_METHOD; }
if type_id != TYPE_ID_COMPILER {
return NYB_E_INVALID_METHOD;
}
match method_id {
METHOD_BIRTH => {
unsafe {
let mut id_g = NEXT_ID.lock().unwrap();
let id = *id_g; *id_g += 1;
let id = *id_g;
*id_g += 1;
let need = 4usize;
if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; }
if *result_len < need {
*result_len = need;
return NYB_E_SHORT_BUFFER;
}
let out = std::slice::from_raw_parts_mut(result, *result_len);
out[0..4].copy_from_slice(&(id as u32).to_le_bytes());
*result_len = need;
@ -46,16 +56,20 @@ pub extern "C" fn nyash_plugin_invoke(
METHOD_COMPILE => {
// Decode TLV first string arg as JSON IR
let ir = unsafe {
if args.is_null() || args_len < 8 { None } else {
if args.is_null() || args_len < 8 {
None
} else {
let buf = std::slice::from_raw_parts(args, args_len);
let tag = u16::from_le_bytes([buf[4], buf[5]]);
let len = u16::from_le_bytes([buf[6], buf[7]]) as usize;
if tag == 6 && 8 + len <= buf.len() {
match std::str::from_utf8(&buf[8..8+len]) {
match std::str::from_utf8(&buf[8..8 + len]) {
Ok(s) => Some(s.to_string()),
Err(_) => None
Err(_) => None,
}
} else { None }
} else {
None
}
}
};
let nyash_source = if let Some(s) = ir.or_else(|| std::env::var("NYASH_PY_IR").ok()) {
@ -67,15 +81,22 @@ pub extern "C" fn nyash_plugin_invoke(
} else if let Some(module) = map.get("module") {
// Try module.functions[0].name and maybe return value
let mut ret_expr = "0".to_string();
if let Some(funcs) = module.get("functions").and_then(|v| v.as_array()) {
if let Some(funcs) = module.get("functions").and_then(|v| v.as_array())
{
if let Some(fun0) = funcs.get(0) {
if let Some(retv) = fun0.get("return_value") {
if retv.is_number() { ret_expr = retv.to_string(); }
else if let Some(s) = retv.as_str() { ret_expr = s.to_string(); }
if retv.is_number() {
ret_expr = retv.to_string();
} else if let Some(s) = retv.as_str() {
ret_expr = s.to_string();
}
}
}
}
format!("static box Generated {{\n main() {{\n return {}\n }}\n}}", ret_expr)
format!(
"static box Generated {{\n main() {{\n return {}\n }}\n}}",
ret_expr
)
} else {
"static box Generated { main() { return 0 } }".to_string()
}
@ -88,11 +109,14 @@ pub extern "C" fn nyash_plugin_invoke(
unsafe {
let bytes = nyash_source.as_bytes();
let need = 4 + bytes.len();
if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; }
if *result_len < need {
*result_len = need;
return NYB_E_SHORT_BUFFER;
}
let out = std::slice::from_raw_parts_mut(result, *result_len);
out[0..2].copy_from_slice(&6u16.to_le_bytes());
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
out[4..4+bytes.len()].copy_from_slice(bytes);
out[4..4 + bytes.len()].copy_from_slice(bytes);
*result_len = need;
}
NYB_SUCCESS

View File

@ -71,7 +71,8 @@ pub extern "C" fn nyash_plugin_invoke(
let code = unsafe {
if args.is_null() || args_len < 4 {
// 引数なしの場合は環境変数から取得
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
std::env::var("NYASH_PY_CODE")
.unwrap_or_else(|_| "def main():\n return 0".to_string())
} else {
// TLVデコード簡易版
let buf = std::slice::from_raw_parts(args, args_len);
@ -79,23 +80,24 @@ pub extern "C" fn nyash_plugin_invoke(
let tag = u16::from_le_bytes([buf[0], buf[1]]);
let len = u16::from_le_bytes([buf[2], buf[3]]) as usize;
if tag == 6 && 4 + len <= args_len {
match std::str::from_utf8(&buf[4..4+len]) {
match std::str::from_utf8(&buf[4..4 + len]) {
Ok(s) => s.to_string(),
Err(_) => std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
Err(_) => std::env::var("NYASH_PY_CODE")
.unwrap_or_else(|_| "def main():\n return 0".to_string()),
}
} else {
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
std::env::var("NYASH_PY_CODE")
.unwrap_or_else(|_| "def main():\n return 0".to_string())
}
} else {
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
std::env::var("NYASH_PY_CODE")
.unwrap_or_else(|_| "def main():\n return 0".to_string())
}
}
};
// パース実行
let parse_result = Python::with_gil(|py| {
parse_python_code(py, &code)
});
let parse_result = Python::with_gil(|py| parse_python_code(py, &code));
// JSONにシリアライズ
match serde_json::to_string(&parse_result) {
@ -111,22 +113,24 @@ pub extern "C" fn nyash_plugin_invoke(
// TLVエンコードtag=6:string
out[0..2].copy_from_slice(&6u16.to_le_bytes());
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
out[4..4+bytes.len()].copy_from_slice(bytes);
out[4..4 + bytes.len()].copy_from_slice(bytes);
*result_len = need;
}
0
}
Err(_) => -4 // エラー
Err(_) => -4, // エラー
}
}
METHOD_FINI => 0,
_ => -3 // NYB_E_INVALID_METHOD
_ => -3, // NYB_E_INVALID_METHOD
}
}
/// FFI: Pythonコードをパース
#[no_mangle]
pub extern "C" fn nyash_python_parse(code: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
pub extern "C" fn nyash_python_parse(
code: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
let code = unsafe {
if code.is_null() {
return std::ptr::null_mut();
@ -137,9 +141,7 @@ pub extern "C" fn nyash_python_parse(code: *const std::os::raw::c_char) -> *mut
}
};
let result = Python::with_gil(|py| {
parse_python_code(py, code)
});
let result = Python::with_gil(|py| parse_python_code(py, code));
match serde_json::to_string(&result) {
Ok(json) => {
@ -265,15 +267,25 @@ fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) {
"AsyncFunctionDef" => {
result.counts.functions += 1;
result.counts.unsupported += 1;
if !result.unsupported.contains(&"async function".to_string()) {
result.unsupported.push("async function".to_string());
if !result
.unsupported
.contains(&"async function".to_string())
{
result
.unsupported
.push("async function".to_string());
}
}
"ClassDef" => {
result.counts.classes += 1;
result.counts.unsupported += 1;
if !result.unsupported.contains(&"class definition".to_string()) {
result.unsupported.push("class definition".to_string());
if !result
.unsupported
.contains(&"class definition".to_string())
{
result
.unsupported
.push("class definition".to_string());
}
}
"For" | "While" | "If" => {
@ -281,8 +293,13 @@ fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) {
}
"Yield" | "YieldFrom" => {
result.counts.unsupported += 1;
if !result.unsupported.contains(&"generator".to_string()) {
result.unsupported.push("generator".to_string());
if !result
.unsupported
.contains(&"generator".to_string())
{
result
.unsupported
.push("generator".to_string());
}
}
_ => {}

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,10 @@
use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
// Error/status codes aligned with other plugins
const OK: i32 = 0;
@ -15,27 +18,33 @@ const E_PLUGIN: i32 = -5;
const E_HANDLE: i32 = -8;
// Methods
const M_BIRTH: u32 = 0; // birth(pattern?) -> instance
const M_COMPILE: u32 = 1; // compile(pattern) -> self (new compiled)
const M_IS_MATCH: u32 = 2; // isMatch(text) -> bool
const M_FIND: u32 = 3; // find(text) -> String (first match or empty)
const M_BIRTH: u32 = 0; // birth(pattern?) -> instance
const M_COMPILE: u32 = 1; // compile(pattern) -> self (new compiled)
const M_IS_MATCH: u32 = 2; // isMatch(text) -> bool
const M_FIND: u32 = 3; // find(text) -> String (first match or empty)
const M_REPLACE_ALL: u32 = 4; // replaceAll(text, repl) -> String
const M_SPLIT: u32 = 5; // split(text, limit) -> String (joined by '\n') minimal
const M_SPLIT: u32 = 5; // split(text, limit) -> String (joined by '\n') minimal
const M_FINI: u32 = u32::MAX; // fini()
// Assign an unused type id (see nyash.toml [box_types])
const TYPE_ID_REGEX: u32 = 52;
struct RegexInstance { re: Option<Regex> }
struct RegexInstance {
re: Option<Regex>,
}
static INST: Lazy<Mutex<HashMap<u32, RegexInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
pub extern "C" fn nyash_plugin_init() -> i32 {
OK
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -47,42 +56,155 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_REGEX { return E_TYPE; }
if type_id != TYPE_ID_REGEX {
return E_TYPE;
}
unsafe {
match method_id {
M_BIRTH => {
if result_len.is_null() { return E_ARGS; }
if preflight(result, result_len, 4) { return E_SHORT; }
if result_len.is_null() {
return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
// Optional pattern in arg0
let inst = if let Some(pat) = read_arg_string(args, args_len, 0) {
match Regex::new(&pat) { Ok(re) => RegexInstance { re: Some(re) }, Err(_) => RegexInstance { re: None } }
} else { RegexInstance { re: None } };
if let Ok(mut m) = INST.lock() { m.insert(id, inst); } else { return E_PLUGIN; }
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
match Regex::new(&pat) {
Ok(re) => RegexInstance { re: Some(re) },
Err(_) => RegexInstance { re: None },
}
} else {
RegexInstance { re: None }
};
if let Ok(mut m) = INST.lock() {
m.insert(id, inst);
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
}
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
M_COMPILE => {
let pat = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.re = Regex::new(&pat).ok(); OK } else { E_HANDLE } } else { E_PLUGIN }
let pat = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) {
inst.re = Regex::new(&pat).ok();
OK
} else {
E_HANDLE
}
} else {
E_PLUGIN
}
}
M_IS_MATCH => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { return write_tlv_bool(re.is_match(&text), result, result_len); } else { return write_tlv_bool(false, result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
let text = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re {
return write_tlv_bool(re.is_match(&text), result, result_len);
} else {
return write_tlv_bool(false, result, result_len);
}
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_FIND => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { let s = re.find(&text).map(|m| m.as_str().to_string()).unwrap_or_else(|| "".to_string()); return write_tlv_string(&s, result, result_len); } else { return write_tlv_string("", result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
let text = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re {
let s = re
.find(&text)
.map(|m| m.as_str().to_string())
.unwrap_or_else(|| "".to_string());
return write_tlv_string(&s, result, result_len);
} else {
return write_tlv_string("", result, result_len);
}
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_REPLACE_ALL => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let repl = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS };
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { let out = re.replace_all(&text, repl.as_str()).to_string(); return write_tlv_string(&out, result, result_len); } else { return write_tlv_string(&text, result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
let text = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let repl = match read_arg_string(args, args_len, 1) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re {
let out = re.replace_all(&text, repl.as_str()).to_string();
return write_tlv_string(&out, result, result_len);
} else {
return write_tlv_string(&text, result, result_len);
}
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_SPLIT => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let text = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let limit = read_arg_i64(args, args_len, 1).unwrap_or(0);
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { if let Some(re) = &inst.re { let mut parts: Vec<String> = if limit > 0 { re.splitn(&text, limit as usize).map(|s| s.to_string()).collect() } else { re.split(&text).map(|s| s.to_string()).collect() }; let out = parts.join("\n"); return write_tlv_string(&out, result, result_len); } else { return write_tlv_string(&text, result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re {
let mut parts: Vec<String> = if limit > 0 {
re.splitn(&text, limit as usize)
.map(|s| s.to_string())
.collect()
} else {
re.split(&text).map(|s| s.to_string()).collect()
};
let out = parts.join("\n");
return write_tlv_string(&out, result, result_len);
} else {
return write_tlv_string(&text, result, result_len);
}
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
_ => E_METHOD,
}
@ -90,31 +212,103 @@ pub extern "C" fn nyash_plugin_invoke(
}
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
unsafe {
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
}
}
false
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return E_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
if result_len.is_null() {
return E_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads {
buf.push(*tag);
buf.push(0);
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(payload);
}
unsafe {
let needed = buf.len();
if result.is_null() || *result_len < needed {
*result_len = needed;
return E_SHORT;
}
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
*result_len = needed;
}
OK
}
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) }
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
}
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
}
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
}
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag != 3 || size != 8 { return None; } let mut b=[0u8;8]; b.copy_from_slice(&buf[off+4..off+12]); return Some(i64::from_le_bytes(b)); } off += 4 + size; }
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag != 3 || size != 8 {
return None;
}
let mut b = [0u8; 8];
b.copy_from_slice(&buf[off + 4..off + 12]);
return Some(i64::from_le_bytes(b));
}
off += 4 + size;
}
None
}
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { let s = String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string(); return Some(s); } else { return None; } } off += 4 + size; }
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag == 6 || tag == 7 {
let s = String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string();
return Some(s);
} else {
return None;
}
}
off += 4 + size;
}
None
}

View File

@ -3,9 +3,12 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::os::raw::c_char;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
const OK: i32 = 0;
const E_SHORT: i32 = -1;
@ -19,23 +22,29 @@ const M_BIRTH: u32 = 0;
const M_LENGTH: u32 = 1;
const M_IS_EMPTY: u32 = 2;
const M_CHAR_CODE_AT: u32 = 3;
const M_CONCAT: u32 = 4; // concat(other: String|Handle) -> Handle(new)
const M_FROM_UTF8: u32 = 5; // fromUtf8(data: String|Bytes) -> Handle(new)
const M_TO_UTF8: u32 = 6; // toUtf8() -> String
const M_CONCAT: u32 = 4; // concat(other: String|Handle) -> Handle(new)
const M_FROM_UTF8: u32 = 5; // fromUtf8(data: String|Bytes) -> Handle(new)
const M_TO_UTF8: u32 = 6; // toUtf8() -> String
const M_FINI: u32 = u32::MAX;
const TYPE_ID_STRING: u32 = 13;
struct StrInstance { s: String }
struct StrInstance {
s: String,
}
static INST: Lazy<Mutex<HashMap<u32, StrInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
pub extern "C" fn nyash_plugin_init() -> i32 {
OK
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -47,29 +56,69 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_STRING { return E_TYPE; }
if type_id != TYPE_ID_STRING {
return E_TYPE;
}
unsafe {
match method_id {
M_BIRTH => {
if result_len.is_null() { return E_ARGS; }
if preflight(result, result_len, 4) { return E_SHORT; }
if result_len.is_null() {
return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
// Optional init from first arg (String/Bytes)
let init = read_arg_string(args, args_len, 0).unwrap_or_else(|| String::new());
if let Ok(mut m) = INST.lock() { m.insert(id, StrInstance { s: init }); }
else { return E_PLUGIN; }
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
if let Ok(mut m) = INST.lock() {
m.insert(id, StrInstance { s: init });
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
}
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
M_LENGTH => {
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.s.len() as i64, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
return write_tlv_i64(inst.s.len() as i64, result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_IS_EMPTY => {
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_bool(inst.s.is_empty(), result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
return write_tlv_bool(inst.s.is_empty(), result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_CHAR_CODE_AT => {
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
if idx < 0 { return E_ARGS; }
let idx = match read_arg_i64(args, args_len, 0) {
Some(v) => v,
None => return E_ARGS,
};
if idx < 0 {
return E_ARGS;
}
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
// Interpret index as char-index into Unicode scalar values
@ -77,40 +126,78 @@ pub extern "C" fn nyash_plugin_invoke(
let ch_opt = inst.s.chars().nth(i);
let code = ch_opt.map(|c| c as u32 as i64).unwrap_or(0);
return write_tlv_i64(code, result, result_len);
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_CONCAT => {
// Accept either Handle(tag=8) to another StringBox, or String/Bytes payload
let (ok, rhs) = if let Some((t, inst)) = read_arg_handle(args, args_len, 0) {
if t != TYPE_ID_STRING { return E_TYPE; }
if let Ok(m) = INST.lock() { if let Some(s2) = m.get(&inst) { (true, s2.s.clone()) } else { (false, String::new()) } } else { return E_PLUGIN; }
} else if let Some(s) = read_arg_string(args, args_len, 0) { (true, s) } else { (false, String::new()) };
if !ok { return E_ARGS; }
if t != TYPE_ID_STRING {
return E_TYPE;
}
if let Ok(m) = INST.lock() {
if let Some(s2) = m.get(&inst) {
(true, s2.s.clone())
} else {
(false, String::new())
}
} else {
return E_PLUGIN;
}
} else if let Some(s) = read_arg_string(args, args_len, 0) {
(true, s)
} else {
(false, String::new())
};
if !ok {
return E_ARGS;
}
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
let mut new_s = inst.s.clone();
new_s.push_str(&rhs);
drop(m);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut mm) = INST.lock() { mm.insert(id, StrInstance { s: new_s }); }
if let Ok(mut mm) = INST.lock() {
mm.insert(id, StrInstance { s: new_s });
}
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_FROM_UTF8 => {
// Create new instance from UTF-8 (accept String/Bytes)
let s = if let Some(s) = read_arg_string(args, args_len, 0) { s } else { return E_ARGS; };
let s = if let Some(s) = read_arg_string(args, args_len, 0) {
s
} else {
return E_ARGS;
};
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() { m.insert(id, StrInstance { s }); } else { return E_PLUGIN; }
if let Ok(mut m) = INST.lock() {
m.insert(id, StrInstance { s });
} else {
return E_PLUGIN;
}
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
}
M_TO_UTF8 => {
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
return write_tlv_string(&inst.s, result, result_len);
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
_ => E_METHOD,
}
@ -131,7 +218,9 @@ pub struct NyashTypeBoxFfi {
unsafe impl Sync for NyashTypeBoxFfi {}
extern "C" fn string_resolve(name: *const c_char) -> u32 {
if name.is_null() { return 0; }
if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"len" | "length" => M_LENGTH,
@ -141,30 +230,77 @@ extern "C" fn string_resolve(name: *const c_char) -> u32 {
}
}
extern "C" fn string_invoke_id(instance_id: u32, method_id: u32, args: *const u8, args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 {
extern "C" fn string_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
M_LENGTH => {
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_i64(inst.s.len() as i64, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
return write_tlv_i64(inst.s.len() as i64, result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_TO_UTF8 => {
if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { return write_tlv_string(&inst.s, result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
return write_tlv_string(&inst.s, result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_CONCAT => {
// support String/Bytes or StringBox handle
let (ok, rhs) = if let Some((t, inst)) = read_arg_handle(args, args_len, 0) {
if t != TYPE_ID_STRING { return E_TYPE; }
if let Ok(m) = INST.lock() { if let Some(s2) = m.get(&inst) { (true, s2.s.clone()) } else { (false, String::new()) } } else { return E_PLUGIN; }
} else if let Some(s) = read_arg_string(args, args_len, 0) { (true, s) } else { (false, String::new()) };
if !ok { return E_ARGS; }
if t != TYPE_ID_STRING {
return E_TYPE;
}
if let Ok(m) = INST.lock() {
if let Some(s2) = m.get(&inst) {
(true, s2.s.clone())
} else {
(false, String::new())
}
} else {
return E_PLUGIN;
}
} else if let Some(s) = read_arg_string(args, args_len, 0) {
(true, s)
} else {
(false, String::new())
};
if !ok {
return E_ARGS;
}
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
let mut new_s = inst.s.clone(); new_s.push_str(&rhs); drop(m);
let mut new_s = inst.s.clone();
new_s.push_str(&rhs);
drop(m);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut mm) = INST.lock() { mm.insert(id, StrInstance { s: new_s }); }
if let Ok(mut mm) = INST.lock() {
mm.insert(id, StrInstance { s: new_s });
}
return write_tlv_handle(TYPE_ID_STRING, id, result, result_len);
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
_ => E_METHOD,
}
@ -183,20 +319,54 @@ pub static nyash_typebox_StringBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
};
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
unsafe {
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
}
}
false
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return E_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
if result_len.is_null() {
return E_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads {
buf.push(*tag);
buf.push(0);
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(payload);
}
unsafe {
let needed = buf.len();
if result.is_null() || *result_len < needed {
*result_len = needed;
return E_SHORT;
}
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
*result_len = needed;
}
OK
}
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) }
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) }
fn write_tlv_handle(type_id: u32, instance_id: u32, result: *mut u8, result_len: *mut usize) -> i32 {
fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len)
}
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
}
fn write_tlv_handle(
type_id: u32,
instance_id: u32,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
let mut payload = Vec::with_capacity(8);
payload.extend_from_slice(&type_id.to_le_bytes());
payload.extend_from_slice(&instance_id.to_le_bytes());
@ -206,22 +376,85 @@ fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
}
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag != 3 || size != 8 { return None; } let mut b=[0u8;8]; b.copy_from_slice(&buf[off+4..off+12]); return Some(i64::from_le_bytes(b)); } off += 4 + size; }
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag != 3 || size != 8 {
return None;
}
let mut b = [0u8; 8];
b.copy_from_slice(&buf[off + 4..off + 12]);
return Some(i64::from_le_bytes(b));
}
off += 4 + size;
}
None
}
fn read_arg_handle(args: *const u8, args_len: usize, n: usize) -> Option<(u32,u32)> {
if args.is_null() || args_len < 4 { return None; }
fn read_arg_handle(args: *const u8, args_len: usize, n: usize) -> Option<(u32, u32)> {
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag != 8 || size != 8 { return None; } let mut t=[0u8;4]; t.copy_from_slice(&buf[off+4..off+8]); let mut id=[0u8;4]; id.copy_from_slice(&buf[off+8..off+12]); return Some((u32::from_le_bytes(t), u32::from_le_bytes(id))); } off += 4 + size; }
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag != 8 || size != 8 {
return None;
}
let mut t = [0u8; 4];
t.copy_from_slice(&buf[off + 4..off + 8]);
let mut id = [0u8; 4];
id.copy_from_slice(&buf[off + 8..off + 12]);
return Some((u32::from_le_bytes(t), u32::from_le_bytes(id)));
}
off += 4 + size;
}
None
}
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
if args.is_null() || args_len < 4 { return None; }
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n {
if tag == 6 || tag == 7 { let s = String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string(); return Some(s); } else { return None; }
} off += 4 + size; }
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag == 6 || tag == 7 {
let s = String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string();
return Some(s);
} else {
return None;
}
}
off += 4 + size;
}
None
}

View File

@ -125,13 +125,13 @@ static TESTBOX_B_INFO: NyashPluginInfo = NyashPluginInfo {
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 {
1 // BID-1 ABI version
1 // BID-1 ABI version
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init(
host: *const NyashHostVtable,
_info: *mut std::ffi::c_void, // For v2, we use get_box_info instead
_info: *mut std::ffi::c_void, // For v2, we use get_box_info instead
) -> i32 {
if host.is_null() {
return NYB_E_INVALID_ARGS;
@ -152,7 +152,7 @@ pub extern "C" fn nyash_plugin_init(
#[no_mangle]
pub extern "C" fn nyash_plugin_get_box_count() -> u32 {
2 // TestBoxA and TestBoxB
2 // TestBoxA and TestBoxB
}
#[no_mangle]
@ -195,26 +195,14 @@ pub extern "C" fn nyash_plugin_invoke(
unsafe {
match (type_id, method_id) {
// TestBoxA methods
(TYPE_ID_TESTBOX_A, METHOD_BIRTH) => {
create_instance_a(result, result_len)
}
(TYPE_ID_TESTBOX_A, METHOD_HELLO) => {
hello_method(instance_id, result, result_len)
}
(TYPE_ID_TESTBOX_A, METHOD_FINI) => {
destroy_instance(instance_id)
}
(TYPE_ID_TESTBOX_A, METHOD_BIRTH) => create_instance_a(result, result_len),
(TYPE_ID_TESTBOX_A, METHOD_HELLO) => hello_method(instance_id, result, result_len),
(TYPE_ID_TESTBOX_A, METHOD_FINI) => destroy_instance(instance_id),
// TestBoxB methods
(TYPE_ID_TESTBOX_B, METHOD_BIRTH) => {
create_instance_b(result, result_len)
}
(TYPE_ID_TESTBOX_B, METHOD_GREET) => {
greet_method(instance_id, result, result_len)
}
(TYPE_ID_TESTBOX_B, METHOD_FINI) => {
destroy_instance(instance_id)
}
(TYPE_ID_TESTBOX_B, METHOD_BIRTH) => create_instance_b(result, result_len),
(TYPE_ID_TESTBOX_B, METHOD_GREET) => greet_method(instance_id, result, result_len),
(TYPE_ID_TESTBOX_B, METHOD_FINI) => destroy_instance(instance_id),
_ => NYB_E_INVALID_ARGS,
}
@ -229,9 +217,12 @@ unsafe fn create_instance_a(result: *mut u8, result_len: *mut usize) -> i32 {
let id = INSTANCE_COUNTER;
INSTANCE_COUNTER += 1;
map.insert(id, TestInstance::BoxA {
message: "Hello from TestBoxA!".to_string(),
});
map.insert(
id,
TestInstance::BoxA {
message: "Hello from TestBoxA!".to_string(),
},
);
// Return instance ID
if *result_len >= 4 {
@ -253,9 +244,7 @@ unsafe fn create_instance_b(result: *mut u8, result_len: *mut usize) -> i32 {
let id = INSTANCE_COUNTER;
INSTANCE_COUNTER += 1;
map.insert(id, TestInstance::BoxB {
counter: 0,
});
map.insert(id, TestInstance::BoxB { counter: 0 });
// Return instance ID
if *result_len >= 4 {
@ -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 {
let bytes = s.as_bytes();
let needed = 8 + bytes.len(); // header(4) + entry(4) + string
let needed = 8 + bytes.len(); // header(4) + entry(4) + string
if *result_len < needed {
return NYB_E_INVALID_ARGS;
}
// TLV header
*result = 1; // version low
*result.offset(1) = 0; // version high
*result.offset(2) = 1; // argc low
*result.offset(3) = 0; // argc high
*result = 1; // version low
*result.offset(1) = 0; // version high
*result.offset(2) = 1; // argc low
*result.offset(3) = 0; // argc high
// String entry
*result.offset(4) = 6; // Tag::String
*result.offset(5) = 0; // padding
*result.offset(4) = 6; // Tag::String
*result.offset(5) = 0; // padding
let len_bytes = (bytes.len() as u16).to_le_bytes();
*result.offset(6) = len_bytes[0];
*result.offset(7) = len_bytes[1];

View File

@ -2,7 +2,10 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::{Mutex, atomic::{AtomicU32, Ordering}};
use std::sync::{
atomic::{AtomicU32, Ordering},
Mutex,
};
const OK: i32 = 0;
const E_SHORT: i32 = -1;
@ -12,24 +15,30 @@ const E_ARGS: i32 = -4;
const E_PLUGIN: i32 = -5;
const E_HANDLE: i32 = -8;
const M_BIRTH: u32 = 0; // constructor -> instance
const M_PARSE: u32 = 1; // parse(text) -> bool
const M_GET: u32 = 2; // get(path.dot.segments) -> string (toml-display) or empty
const M_TO_JSON: u32 = 3; // toJson() -> string (JSON)
const M_BIRTH: u32 = 0; // constructor -> instance
const M_PARSE: u32 = 1; // parse(text) -> bool
const M_GET: u32 = 2; // get(path.dot.segments) -> string (toml-display) or empty
const M_TO_JSON: u32 = 3; // toJson() -> string (JSON)
const M_FINI: u32 = u32::MAX; // fini()
const TYPE_ID_TOML: u32 = 54;
struct TomlInstance { value: Option<toml::Value> }
struct TomlInstance {
value: Option<toml::Value>,
}
static INST: Lazy<Mutex<HashMap<u32, TomlInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
#[no_mangle]
pub extern "C" fn nyash_plugin_abi() -> u32 { 1 }
pub extern "C" fn nyash_plugin_abi() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn nyash_plugin_init() -> i32 { OK }
pub extern "C" fn nyash_plugin_init() -> i32 {
OK
}
#[no_mangle]
pub extern "C" fn nyash_plugin_invoke(
@ -41,35 +50,84 @@ pub extern "C" fn nyash_plugin_invoke(
result: *mut u8,
result_len: *mut usize,
) -> i32 {
if type_id != TYPE_ID_TOML { return E_TYPE; }
if type_id != TYPE_ID_TOML {
return E_TYPE;
}
unsafe {
match method_id {
M_BIRTH => {
if result_len.is_null() { return E_ARGS; }
if preflight(result, result_len, 4) { return E_SHORT; }
if result_len.is_null() {
return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() { m.insert(id, TomlInstance { value: None }); } else { return E_PLUGIN; }
let b = id.to_le_bytes(); std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); *result_len = 4; OK
if let Ok(mut m) = INST.lock() {
m.insert(id, TomlInstance { value: None });
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
}
M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN } }
M_PARSE => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.value = toml::from_str::<toml::Value>(&text).ok(); return write_tlv_bool(inst.value.is_some(), result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; }
let text = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) {
inst.value = toml::from_str::<toml::Value>(&text).ok();
return write_tlv_bool(inst.value.is_some(), result, result_len);
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_GET => {
let path = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let path = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
let mut cur = match &inst.value { Some(v) => v, None => { return write_tlv_string("", result, result_len); } };
let mut cur = match &inst.value {
Some(v) => v,
None => {
return write_tlv_string("", result, result_len);
}
};
if !path.is_empty() {
for seg in path.split('.') {
match cur.get(seg) { Some(v) => cur = v, None => { return write_tlv_string("", result, result_len); } }
match cur.get(seg) {
Some(v) => cur = v,
None => {
return write_tlv_string("", result, result_len);
}
}
}
}
let out = cur.to_string();
return write_tlv_string(&out, result, result_len);
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
M_TO_JSON => {
if let Ok(m) = INST.lock() {
@ -77,10 +135,19 @@ pub extern "C" fn nyash_plugin_invoke(
if let Some(v) = &inst.value {
// Convert via serde_json::Value
let sv = toml_to_json(v);
return match serde_json::to_string(&sv) { Ok(s) => write_tlv_string(&s, result, result_len), Err(_) => write_tlv_string("{}", result, result_len) };
} else { return write_tlv_string("{}", result, result_len); }
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
return match serde_json::to_string(&sv) {
Ok(s) => write_tlv_string(&s, result, result_len),
Err(_) => write_tlv_string("{}", result, result_len),
};
} else {
return write_tlv_string("{}", result, result_len);
}
} else {
return E_HANDLE;
}
} else {
return E_PLUGIN;
}
}
_ => E_METHOD,
}
@ -97,31 +164,81 @@ fn toml_to_json(v: &toml::Value) -> serde_json::Value {
toml::Value::Array(arr) => serde_json::Value::Array(arr.iter().map(toml_to_json).collect()),
toml::Value::Table(map) => {
let mut m = serde_json::Map::new();
for (k, vv) in map.iter() { m.insert(k.clone(), toml_to_json(vv)); }
for (k, vv) in map.iter() {
m.insert(k.clone(), toml_to_json(vv));
}
serde_json::Value::Object(m)
}
}
}
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } }
unsafe {
if result_len.is_null() {
return false;
}
if result.is_null() || *result_len < needed {
*result_len = needed;
return true;
}
}
false
}
fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 {
if result_len.is_null() { return E_ARGS; }
let mut buf: Vec<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); }
unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; }
if result_len.is_null() {
return E_ARGS;
}
let mut buf: Vec<u8> =
Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::<usize>());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes());
for (tag, payload) in payloads {
buf.push(*tag);
buf.push(0);
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(payload);
}
unsafe {
let needed = buf.len();
if result.is_null() || *result_len < needed {
*result_len = needed;
return E_SHORT;
}
std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed);
*result_len = needed;
}
OK
}
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) }
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) }
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
if args.is_null() || args_len < 4 { return None; }
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; }
None
fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(1u8, &[if v { 1u8 } else { 0u8 }])], result, result_len)
}
fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 {
write_tlv_result(&[(6u8, s.as_bytes())], result, result_len)
}
fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option<String> {
if args.is_null() || args_len < 4 {
return None;
}
let buf = unsafe { std::slice::from_raw_parts(args, args_len) };
let mut off = 4usize;
for i in 0..=n {
if buf.len() < off + 4 {
return None;
}
let tag = buf[off];
let size = u16::from_le_bytes([buf[off + 2], buf[off + 3]]) as usize;
if buf.len() < off + 4 + size {
return None;
}
if i == n {
if tag == 6 || tag == 7 {
return Some(String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string());
} else {
return None;
}
}
off += 4 + size;
}
None
}

View File

@ -9,6 +9,8 @@ use super::super::types::{to_bool, map_mirtype_to_basic};
use super::builder_cursor::BuilderCursor;
use super::Resolver;
fn phi_trace_on() -> bool { std::env::var("NYASH_LLVM_TRACE_PHI").ok().as_deref() == Some("1") }
pub(in super::super) fn emit_return<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
@ -82,6 +84,9 @@ pub(in super::super) fn emit_jump<'ctx, 'b>(
cursor.emit_term(bid, |b| {
b.build_unconditional_branch(tbb).map_err(|e| e.to_string()).unwrap();
});
if phi_trace_on() {
eprintln!("[PHI:jump] pred={} -> succ={}", bid.as_u32(), target.as_u32());
}
Ok(())
}
@ -335,10 +340,10 @@ pub(in super::super) fn finalize_phis<'ctx, 'b>(
});
}
let pred_bb = *bb_map.get(pred).ok_or("pred bb missing")?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
if phi_trace_on() {
eprintln!(
"[PHI] finalize add pred_bb={} val={} ty={}",
pred.as_u32(), in_vid.as_u32(),
"[PHI:finalize] succ={} pred={} vid={} ty={}",
succ_bb.as_u32(), pred.as_u32(), in_vid.as_u32(),
phi.as_basic_value().get_type().print_to_string().to_string()
);
}
@ -359,10 +364,10 @@ pub(in super::super) fn finalize_phis<'ctx, 'b>(
BT::PointerType(pt) => pt.const_zero().into(),
_ => return Err("unsupported phi type for zero synth (finalize)".to_string()),
};
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
if phi_trace_on() {
eprintln!(
"[PHI] finalize add (synth) pred_bb={} zero-ty={}",
pred.as_u32(), bt.print_to_string().to_string()
"[PHI:finalize] succ={} pred={} vid=? ty={} src=synth_zero",
succ_bb.as_u32(), pred.as_u32(), bt.print_to_string().to_string()
);
}
match z {
@ -431,6 +436,12 @@ pub(in super::super) fn localize_to_i64<'ctx, 'b>(
};
});
phi.add_incoming(&[(&iv_out, pred_bb)]);
if phi_trace_on() {
eprintln!(
"[PHI:resolve] cur={} pred={} vid={} ty=i64",
cur_bid.as_u32(), p.as_u32(), vid.as_u32()
);
}
}
// Restore insertion point
if let Some(bb) = saved_ip { codegen.builder.position_at_end(bb); }

View File

@ -134,3 +134,49 @@ pub(in super::super) fn lower_load<'ctx, 'b>(
vmap.insert(*dst, lv);
Ok(())
}
// Lower Copy: define dst in the current block by localizing src via Resolver
pub(in super::super) fn lower_copy<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
cur_bid: BasicBlockId,
func: &crate::mir::function::MirFunction,
vmap: &mut HashMap<ValueId, BasicValueEnum<'ctx>>,
dst: &ValueId,
src: &ValueId,
bb_map: &std::collections::HashMap<crate::mir::BasicBlockId, inkwell::basic_block::BasicBlock<'ctx>>,
preds: &std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::BasicBlockId>>,
block_end_values: &std::collections::HashMap<crate::mir::BasicBlockId, std::collections::HashMap<ValueId, BasicValueEnum<'ctx>>>,
) -> Result<(), String> {
// Choose resolution kind based on metadata type preference
use inkwell::types::BasicTypeEnum as BT;
let expected_bt: Option<BT> = func
.metadata
.value_types
.get(dst)
.or_else(|| func.metadata.value_types.get(src))
.map(|mt| super::super::types::map_mirtype_to_basic(codegen.context, mt));
let out: BasicValueEnum<'ctx> = match expected_bt {
Some(BT::IntType(_)) | None => {
// Prefer i64 for unknown
let iv = resolver.resolve_i64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
iv.into()
}
Some(BT::PointerType(_)) => {
let pv = resolver.resolve_ptr(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
pv.into()
}
Some(BT::FloatType(_)) => {
let fv = resolver.resolve_f64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
fv.into()
}
_ => {
// Fallback i64
let iv = resolver.resolve_i64(codegen, cursor, cur_bid, *src, bb_map, preds, block_end_values, vmap)?;
iv.into()
}
};
vmap.insert(*dst, out);
Ok(())
}

View File

@ -24,6 +24,7 @@ pub(super) use newbox::lower_newbox;
pub(super) use boxcall::{lower_boxcall, lower_boxcall_boxed, lower_boxcall_via_ctx};
pub(super) use arith::lower_compare;
pub(super) use mem::{lower_load, lower_store};
pub(super) use mem::lower_copy;
pub(super) use consts::lower_const;
pub(super) use arith_ops::{lower_binop, lower_unary};
pub(super) use call::lower_call;

View File

@ -767,6 +767,22 @@ impl LLVMCompiler {
instructions::lower_load(&codegen, &mut cursor, *bid, &mut vmap, &mut allocas, &mut alloca_elem_types, dst, ptr)?;
defined_in_block.insert(*dst);
},
MirInstruction::Copy { dst, src } => {
instructions::lower_copy(
&codegen,
&mut cursor,
&mut resolver,
*bid,
func,
&mut vmap,
dst,
src,
&bb_map,
&preds,
&block_end_values,
)?;
defined_in_block.insert(*dst);
},
MirInstruction::Phi { .. } => {
// Already created in pre-pass; nothing to do here.
}
@ -799,6 +815,23 @@ impl LLVMCompiler {
&block_end_values,
)?;
}
MirInstruction::Throw { .. } => {
// Minimal lowering: call llvm.trap (optional) then unreachable
let use_trap = std::env::var("NYASH_LLVM_TRAP_ON_THROW").ok().as_deref() != Some("0");
if use_trap {
let fn_ty = codegen.context.void_type().fn_type(&[], false);
let trap = codegen
.module
.get_function("llvm.trap")
.unwrap_or_else(|| codegen.module.add_function("llvm.trap", fn_ty, None));
cursor.emit_term(*bid, |b| {
let _ = b.build_call(trap, &[], "trap");
});
}
// Ensure we end the block
cursor.at_end(*bid, bb);
let _ = codegen.builder.build_unreachable();
}
MirInstruction::Jump { target } => {
// LoopForm simple body→dispatch wiring: if this block is a loop body
// and jumps back to its header, redirect to dispatch and add PHI incoming

View File

@ -24,28 +24,47 @@ fn main() {
// Read stdin to tmp/ny_mir_builder_input.json for re-use
let mut buf = Vec::new();
io::stdin().read_to_end(&mut buf).expect("read stdin");
if buf.is_empty() { eprintln!("error: no input on stdin"); std::process::exit(2); }
let cwd_tmp = Path::new("tmp"); let _ = fs::create_dir_all(cwd_tmp);
if buf.is_empty() {
eprintln!("error: no input on stdin");
std::process::exit(2);
}
let cwd_tmp = Path::new("tmp");
let _ = fs::create_dir_all(cwd_tmp);
let cwd_path = cwd_tmp.join("ny_mir_builder_input.json");
fs::write(&cwd_path, &buf).expect("write cwd tmp json");
cwd_path
} else {
let p = PathBuf::from(matches.get_one::<String>("in").unwrap());
if !p.exists() { eprintln!("error: input not found: {}", p.display()); std::process::exit(2); }
if !p.exists() {
eprintln!("error: input not found: {}", p.display());
std::process::exit(2);
}
p
};
let emit = matches.get_one::<String>("emit").unwrap().as_str();
let out_path = matches.get_one::<String>("out").map(|s| s.to_string()).unwrap_or_else(|| match emit {
"obj" => format!("{}/target/aot_objects/a.o", std::env::current_dir().unwrap().display()),
"ll" => format!("{}/target/aot_objects/a.ll", std::env::current_dir().unwrap().display()),
"exe" => "a.out".to_string(),
"json" => "/dev/stdout".to_string(),
_ => unreachable!(),
});
let out_path = matches
.get_one::<String>("out")
.map(|s| s.to_string())
.unwrap_or_else(|| match emit {
"obj" => format!(
"{}/target/aot_objects/a.o",
std::env::current_dir().unwrap().display()
),
"ll" => format!(
"{}/target/aot_objects/a.ll",
std::env::current_dir().unwrap().display()
),
"exe" => "a.out".to_string(),
"json" => "/dev/stdout".to_string(),
_ => unreachable!(),
});
let verify = matches.get_flag("verify_llvm");
let quiet = matches.get_flag("quiet");
let nyrt_dir = matches.get_one::<String>("nyrt").map(|s| s.to_string()).unwrap_or("crates/nyrt".to_string());
let nyrt_dir = matches
.get_one::<String>("nyrt")
.map(|s| s.to_string())
.unwrap_or("crates/nyrt".to_string());
// Determine sibling nyash binary path (target dir)
let nyash_bin = current_dir_bin("nyash");
@ -56,47 +75,74 @@ fn main() {
} else {
fs::copy(&in_file, &out_path).expect("copy json");
}
if !quiet { println!("OK json:{}", out_path); }
if !quiet {
println!("OK json:{}", out_path);
}
return;
}
// Ensure build dirs
let aot_dir = Path::new("target/aot_objects"); let _ = fs::create_dir_all(aot_dir);
let aot_dir = Path::new("target/aot_objects");
let _ = fs::create_dir_all(aot_dir);
match emit {
"ll" => {
std::env::set_var("NYASH_LLVM_DUMP_LL", "1");
std::env::set_var("NYASH_LLVM_LL_OUT", &out_path);
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
if verify {
std::env::set_var("NYASH_LLVM_VERIFY", "1");
}
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
run_nyash_pipe(&nyash_bin, &in_file);
if !Path::new(&out_path).exists() { eprintln!("error: failed to produce {}", out_path); std::process::exit(4); }
if !quiet { println!("OK ll:{}", out_path); }
if !Path::new(&out_path).exists() {
eprintln!("error: failed to produce {}", out_path);
std::process::exit(4);
}
if !quiet {
println!("OK ll:{}", out_path);
}
}
"obj" => {
std::env::set_var("NYASH_LLVM_OBJ_OUT", &out_path);
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
if verify {
std::env::set_var("NYASH_LLVM_VERIFY", "1");
}
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
// remove stale
let _ = fs::remove_file(&out_path);
run_nyash_pipe(&nyash_bin, &in_file);
if !Path::new(&out_path).exists() { eprintln!("error: failed to produce {}", out_path); std::process::exit(4); }
if !quiet { println!("OK obj:{}", out_path); }
if !Path::new(&out_path).exists() {
eprintln!("error: failed to produce {}", out_path);
std::process::exit(4);
}
if !quiet {
println!("OK obj:{}", out_path);
}
}
"exe" => {
let obj_path = format!("{}/target/aot_objects/__tmp_mir_builder.o", std::env::current_dir().unwrap().display());
let obj_path = format!(
"{}/target/aot_objects/__tmp_mir_builder.o",
std::env::current_dir().unwrap().display()
);
std::env::set_var("NYASH_LLVM_OBJ_OUT", &obj_path);
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
if verify {
std::env::set_var("NYASH_LLVM_VERIFY", "1");
}
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
let _ = fs::remove_file(&obj_path);
run_nyash_pipe(&nyash_bin, &in_file);
if !Path::new(&obj_path).exists() { eprintln!("error: failed to produce object {}", obj_path); std::process::exit(4); }
if !Path::new(&obj_path).exists() {
eprintln!("error: failed to produce object {}", obj_path);
std::process::exit(4);
}
// Link with NyRT
if let Err(e) = link_exe(&obj_path, &out_path, &nyrt_dir) {
eprintln!("error: link failed: {}", e);
std::process::exit(5);
}
if !quiet { println!("OK exe:{}", out_path); }
if !quiet {
println!("OK exe:{}", out_path);
}
}
_ => unreachable!(),
}
@ -108,17 +154,23 @@ fn current_dir_bin(name: &str) -> PathBuf {
if let Ok(cur) = std::env::current_exe() {
if let Some(dir) = cur.parent() {
let cand = dir.join(name);
if cand.exists() { return cand; }
if cand.exists() {
return cand;
}
#[cfg(windows)]
{
let cand = dir.join(format!("{}.exe", name));
if cand.exists() { return cand; }
if cand.exists() {
return cand;
}
}
}
}
// Fallback to target/release
let cand = PathBuf::from("target/release").join(name);
if cand.exists() { return cand; }
if cand.exists() {
return cand;
}
#[cfg(windows)]
{
let cand = PathBuf::from("target/release").join(format!("{}.exe", name));
@ -157,10 +209,17 @@ fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String
args.push(obj_path.to_string());
// Provide LIBPATH and library name (prefer nyrt.lib)
args.push(format!("/LIBPATH:{}", nyrt_release));
if std::path::Path::new(&lib_nyrt_lib).exists() { args.push("nyrt.lib".to_string()); }
if std::path::Path::new(&lib_nyrt_lib).exists() {
args.push("nyrt.lib".to_string());
}
// lld-link cannot consume .a directly; rely on .lib
let status = PCommand::new("lld-link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?;
if status.success() { return Ok(()); }
let status = PCommand::new("lld-link")
.args(args.iter().map(|s| s.as_str()))
.status()
.map_err(|e| e.to_string())?;
if status.success() {
return Ok(());
}
return Err(format!("lld-link failed: status {:?}", status.code()));
}
if which::which("link").is_ok() {
@ -168,9 +227,16 @@ fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String
args.push(format!("/OUT:{}", out_path));
args.push(obj_path.to_string());
args.push(format!("/LIBPATH:{}", nyrt_release));
if std::path::Path::new(&lib_nyrt_lib).exists() { args.push("nyrt.lib".to_string()); }
let status = PCommand::new("link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?;
if status.success() { return Ok(()); }
if std::path::Path::new(&lib_nyrt_lib).exists() {
args.push("nyrt.lib".to_string());
}
let status = PCommand::new("link")
.args(args.iter().map(|s| s.as_str()))
.status()
.map_err(|e| e.to_string())?;
if status.success() {
return Ok(());
}
return Err(format!("link.exe failed: status {:?}", status.code()));
}
// Fallback: try cc with MinGW-like flags
@ -178,8 +244,11 @@ fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String
.args([obj_path])
.args(["-L", &format!("{}/target/release", nyrt_dir)])
.args(["-lnyrt", "-o", out_path])
.status().map_err(|e| e.to_string())?;
if status.success() { return Ok(()); }
.status()
.map_err(|e| e.to_string())?;
if status.success() {
return Ok(());
}
return Err(format!("cc link failed: status {:?}", status.code()));
}
#[cfg(not(target_os = "windows"))]
@ -190,7 +259,12 @@ fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String
.args(["-L", &format!("{}/target/release", nyrt_dir)])
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive"])
.args(["-lpthread", "-ldl", "-lm", "-o", out_path])
.status().map_err(|e| e.to_string())?;
if status.success() { Ok(()) } else { Err(format!("cc failed: status {:?}", status.code())) }
.status()
.map_err(|e| e.to_string())?;
if status.success() {
Ok(())
} else {
Err(format!("cc failed: status {:?}", status.code()))
}
}
}

View File

@ -44,7 +44,9 @@ fn main() {
println!("\nTesting box type resolution:");
for box_type in ["StringBox", "FileBox", "MapBox"] {
match config.find_library_for_box(box_type) {
Some((name, lib)) => println!(" {} -> library: {} (path={})", box_type, name, lib.path),
Some((name, lib)) => {
println!(" {} -> library: {} (path={})", box_type, name, lib.path)
}
None => println!(" {} -> not found in config", box_type),
}
}
@ -60,7 +62,8 @@ fn main() {
println!("\nReverse host-call (by-slot) quick test:");
// Create ArrayBox and obtain HostHandle
let mut arr = nyash_rust::boxes::ArrayBox::new();
arr.push(Box::new(nyash_rust::box_trait::StringBox::new("init")) as Box<dyn nyash_rust::box_trait::NyashBox>);
arr.push(Box::new(nyash_rust::box_trait::StringBox::new("init"))
as Box<dyn nyash_rust::box_trait::NyashBox>);
let handle = nyash_rust::runtime::host_handles::to_handle_box(Box::new(arr));
// Call Array.set(0, "hello") via slot=101
let mut tlv = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(2);
@ -68,21 +71,44 @@ fn main() {
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv, "hello");
let mut out = vec![0u8; 256];
let mut out_len: usize = out.len();
let code = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(handle, 101, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) };
let code = unsafe {
nyash_rust::runtime::host_api::nyrt_host_call_slot(
handle,
101,
tlv.as_ptr(),
tlv.len(),
out.as_mut_ptr(),
&mut out_len,
)
};
println!(" set(slot=101) -> code={}, out_len={}", code, out_len);
// Call Array.get(0) via slot=100 and decode
let mut tlv2 = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(1);
nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut tlv2, 0);
let mut out2 = vec![0u8; 256];
let mut out2_len: usize = out2.len();
let code2 = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(handle, 100, tlv2.as_ptr(), tlv2.len(), out2.as_mut_ptr(), &mut out2_len) };
let code2 = unsafe {
nyash_rust::runtime::host_api::nyrt_host_call_slot(
handle,
100,
tlv2.as_ptr(),
tlv2.len(),
out2.as_mut_ptr(),
&mut out2_len,
)
};
if code2 == 0 {
if let Some((tag, _sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out2[..out2_len]) {
if tag == 6 || tag == 7 { // string/bytes
if let Some((tag, _sz, payload)) =
nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out2[..out2_len])
{
if tag == 6 || tag == 7 {
// string/bytes
let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload);
println!(" get(slot=100) -> tag={}, value='{}'", tag, s);
} else if tag == 3 { // i64
let v = nyash_rust::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or_default();
} else if tag == 3 {
// i64
let v = nyash_rust::runtime::plugin_ffi_common::decode::i32(payload)
.unwrap_or_default();
println!(" get(slot=100) -> tag={}, i32={}", tag, v);
} else {
println!(" get(slot=100) -> tag={}, size={}", tag, _sz);
@ -102,16 +128,36 @@ fn main() {
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_set, "v");
let mut out_s = vec![0u8; 256];
let mut out_s_len: usize = out_s.len();
let code_s = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 204, tlv_set.as_ptr(), tlv_set.len(), out_s.as_mut_ptr(), &mut out_s_len) };
let code_s = unsafe {
nyash_rust::runtime::host_api::nyrt_host_call_slot(
map_h,
204,
tlv_set.as_ptr(),
tlv_set.len(),
out_s.as_mut_ptr(),
&mut out_s_len,
)
};
println!(" set(slot=204) -> code={}, out_len={}", code_s, out_s_len);
// get("k") → slot=203
let mut tlv_get = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(1);
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_get, "k");
let mut out_g = vec![0u8; 256];
let mut out_g_len: usize = out_g.len();
let code_g = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 203, tlv_get.as_ptr(), tlv_get.len(), out_g.as_mut_ptr(), &mut out_g_len) };
let code_g = unsafe {
nyash_rust::runtime::host_api::nyrt_host_call_slot(
map_h,
203,
tlv_get.as_ptr(),
tlv_get.len(),
out_g.as_mut_ptr(),
&mut out_g_len,
)
};
if code_g == 0 {
if let Some((tag, _sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out_g[..out_g_len]) {
if let Some((tag, _sz, payload)) =
nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out_g[..out_g_len])
{
if tag == 6 || tag == 7 {
let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload);
println!(" get(slot=203) -> '{}'", s);
@ -123,13 +169,37 @@ fn main() {
// has("k") → slot=202
let mut out_hb = vec![0u8; 16];
let mut out_hb_len: usize = out_hb.len();
let code_hb = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 202, tlv_get.as_ptr(), tlv_get.len(), out_hb.as_mut_ptr(), &mut out_hb_len) };
println!(" has(slot=202) -> code={}, out_len={}", code_hb, out_hb_len);
let code_hb = unsafe {
nyash_rust::runtime::host_api::nyrt_host_call_slot(
map_h,
202,
tlv_get.as_ptr(),
tlv_get.len(),
out_hb.as_mut_ptr(),
&mut out_hb_len,
)
};
println!(
" has(slot=202) -> code={}, out_len={}",
code_hb, out_hb_len
);
// size() → slot=200
let mut out_sz = vec![0u8; 32];
let mut out_sz_len: usize = out_sz.len();
let code_sz = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 200, std::ptr::null(), 0, out_sz.as_mut_ptr(), &mut out_sz_len) };
println!(" size(slot=200) -> code={}, out_len={}", code_sz, out_sz_len);
let code_sz = unsafe {
nyash_rust::runtime::host_api::nyrt_host_call_slot(
map_h,
200,
std::ptr::null(),
0,
out_sz.as_mut_ptr(),
&mut out_sz_len,
)
};
println!(
" size(slot=200) -> code={}, out_len={}",
code_sz, out_sz_len
);
println!("\nTest completed!");
}

View File

@ -85,6 +85,18 @@ pub fn await_max_ms() -> u64 {
std::env::var("NYASH_AWAIT_MAX_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(5000)
}
// ---- MIR PHI-less (edge-copy) mode ----
/// Enable MIR PHI non-generation. Bridge/Builder emit edge copies instead of PHI.
pub fn mir_no_phi() -> bool { std::env::var("NYASH_MIR_NO_PHI").ok().as_deref() == Some("1") }
/// Allow verifier to skip SSA/dominance/merge checks for PHI-less MIR.
pub fn verify_allow_no_phi() -> bool {
std::env::var("NYASH_VERIFY_ALLOW_NO_PHI").ok().as_deref() == Some("1") || mir_no_phi()
}
// ---- LLVM harness toggle (llvmlite) ----
pub fn llvm_use_harness() -> bool { std::env::var("NYASH_LLVM_USE_HARNESS").ok().as_deref() == Some("1") }
// ---- Phase 11.8 MIR cleanup toggles ----
/// Core-13 minimal MIR mode toggle
/// Default: ON (unless explicitly disabled with NYASH_MIR_CORE13=0)

View File

@ -95,7 +95,7 @@ pub(crate) extern "C" fn nyash_plugin_invoke3_i64(type_id: i64, method_id: i64,
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
match tag {
3 => { if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } }
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let box_type_name = crate::runtime::plugin_loader_unified::get_global_plugin_host().read().ok().and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())).and_then(|m| m.into_iter().find(|(_k,v)| *v == r_type).map(|(k,_v)| k)).unwrap_or_else(|| "PluginBox".to_string()); let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke.unwrap()); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let meta_opt = crate::runtime::plugin_loader_v2::metadata_for_type_id(r_type); let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt { (meta.box_type.clone(), meta.invoke_fn) } else { ("PluginBox".to_string(), invoke.unwrap()) }; let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke_ptr); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
1 => { return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; }
5 => { if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") { if _sz == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); let f = f64::from_le_bytes(b); return f as i64; } } }
_ => {}
@ -171,7 +171,7 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
match tag {
3 => { if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } }
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type, r_type, r_inst, invoke.unwrap()); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
8 => { if _sz == 8 { let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); let meta_opt = crate::runtime::plugin_loader_v2::metadata_for_type_id(r_type); let (meta_box, invoke_ptr) = if let Some(meta) = meta_opt { (meta.box_type, meta.invoke_fn) } else { (box_type.clone(), invoke.unwrap()) }; let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(meta_box, r_type, r_inst, invoke_ptr); let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb); let h = crate::jit::rt::handles::to_handle(arc); return h as i64; } }
1 => { return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; }
5 => { if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") { if _sz == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); let f = f64::from_le_bytes(b); return f as i64; } } }
_ => {}

View File

@ -9,8 +9,6 @@ use crate::jit::r#extern::collections as c;
#[cfg(feature = "cranelift-jit")]
use crate::jit::r#extern::host_bridge as hb;
#[cfg(feature = "cranelift-jit")]
use crate::runtime::plugin_loader_unified;
#[cfg(feature = "cranelift-jit")]
use crate::runtime::plugin_loader_v2::PluginBoxV2;
// ---- Generic Birth (handle) ----
@ -19,19 +17,15 @@ pub(super) extern "C" fn nyash_box_birth_h(type_id: i64) -> i64 {
// Map type_id -> type name and create via plugin host; return runtime handle
if type_id <= 0 { return 0; }
let tid = type_id as u32;
let name_opt = crate::runtime::plugin_loader_unified::get_global_plugin_host()
.read().ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| m.into_iter().find(|(_k,v)| *v == tid).map(|(k,_v)| k));
if let Some(box_type) = name_opt {
if let Some(meta) = crate::runtime::plugin_loader_v2::metadata_for_type_id(tid) {
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
if let Ok(b) = host.create_box(&box_type, &[]) {
if let Ok(b) = host.create_box(&meta.box_type, &[]) {
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(b);
let h = crate::jit::rt::handles::to_handle(arc);
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": box_type, "type_id": tid, "handle": h}), "hostcall", "<jit>");
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": meta.box_type, "type_id": meta.type_id, "handle": h}), "hostcall", "<jit>");
return h as i64;
} else {
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "create_failed", "box_type": box_type, "type_id": tid}), "hostcall", "<jit>");
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "create_failed", "box_type": meta.box_type, "type_id": meta.type_id}), "hostcall", "<jit>");
}
}
} else {
@ -44,25 +38,13 @@ pub(super) extern "C" fn nyash_box_birth_h(type_id: i64) -> i64 {
pub(super) extern "C" fn nyash_box_birth_i64(type_id: i64, argc: i64, a1: i64, a2: i64) -> i64 {
use crate::runtime::plugin_loader_v2::PluginBoxV2;
if type_id <= 0 { return 0; }
// Resolve invoke for the type by creating a temp instance
let mut invoke: Option<unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize)->i32> = None;
let mut box_type = String::new();
if let Some(name) = crate::runtime::plugin_loader_unified::get_global_plugin_host()
.read().ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| m.into_iter().find(|(_k,v)| *v == (type_id as u32)).map(|(k,_v)| k))
{
box_type = name;
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
if let Ok(b) = host.create_box(&box_type, &[]) {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() { invoke = Some(p.inner.invoke_fn); }
}
}
}
if invoke.is_none() {
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "no_invoke", "type_id": type_id}), "hostcall", "<jit>");
// Resolve invoke for the type via loader metadata
let Some(meta) = crate::runtime::plugin_loader_v2::metadata_for_type_id(type_id as u32) else {
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "type_map_failed", "type_id": type_id}), "hostcall", "<jit>");
return 0;
}
};
let invoke_fn = meta.invoke_fn;
let box_type = meta.box_type.clone();
let method_id: u32 = 0; let instance_id: u32 = 0;
// Build TLV from a1/a2
let nargs = argc.max(0) as usize;
@ -94,13 +76,13 @@ pub(super) extern "C" fn nyash_box_birth_i64(type_id: i64, argc: i64, a1: i64, a
if nargs >= 2 { encode_val(a2); }
// Invoke
let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len();
let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) };
let rc = unsafe { invoke_fn(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) };
if rc != 0 { events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "invoke_failed", "type_id": type_id}), "hostcall", "<jit>"); return 0; }
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
if tag == 8 && payload.len()==8 {
let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]);
let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i);
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type.clone(), r_type, r_inst, invoke.unwrap());
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type.clone(), r_type, r_inst, invoke_fn);
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb);
let h = crate::jit::rt::handles::to_handle(arc);
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "box_type": box_type, "type_id": type_id, "argc": nargs, "handle": h}), "hostcall", "<jit>");

View File

@ -87,12 +87,16 @@ pub struct MirBuilder {
/// Top of stack corresponds to the innermost active loop
pub(super) loop_header_stack: Vec<BasicBlockId>,
pub(super) loop_exit_stack: Vec<BasicBlockId>,
/// Whether PHI emission is disabled (edge-copy mode)
pub(super) no_phi_mode: bool,
}
impl MirBuilder {
/// Create a new MIR builder
pub fn new() -> Self {
let plugin_method_sigs = plugin_sigs::load_plugin_method_sigs();
let no_phi_mode = crate::config::env::mir_no_phi();
Self {
current_module: None,
current_function: None,
@ -112,6 +116,7 @@ impl MirBuilder {
include_box_map: HashMap::new(),
loop_header_stack: Vec::new(),
loop_exit_stack: Vec::new(),
no_phi_mode,
}
}
@ -652,6 +657,42 @@ impl MirBuilder {
}
}
pub(super) fn is_no_phi_mode(&self) -> bool {
self.no_phi_mode
}
/// Insert a Copy instruction into `block_id`, defining `dst` from `src`.
/// Skips blocks that terminate via return/throw, and avoids duplicate copies.
pub(super) fn insert_edge_copy(
&mut self,
block_id: BasicBlockId,
dst: ValueId,
src: ValueId,
) -> Result<(), String> {
if let Some(ref mut function) = self.current_function {
let block = function
.get_block_mut(block_id)
.ok_or_else(|| format!("Basic block {} does not exist", block_id))?;
if let Some(term) = &block.terminator {
if matches!(term, MirInstruction::Return { .. } | MirInstruction::Throw { .. }) {
return Ok(());
}
}
let already_present = block.instructions.iter().any(|inst| {
matches!(inst, MirInstruction::Copy { dst: existing_dst, .. } if *existing_dst == dst)
});
if !already_present {
block.add_instruction(MirInstruction::Copy { dst, src });
}
if let Some(ty) = self.value_types.get(&src).cloned() {
self.value_types.insert(dst, ty);
}
Ok(())
} else {
Err("No current function".to_string())
}
}
// moved to builder/utils.rs: ensure_block_exists
// build_loop_statement_legacy moved to builder/stmts.rs

View File

@ -40,7 +40,13 @@ impl super::MirBuilder {
phi_inputs.push((else_block, else_val));
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
self.start_new_block(merge_block)?;
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
if self.is_no_phi_mode() {
for (pred, val) in phi_inputs {
self.insert_edge_copy(pred, result_val, val)?;
}
} else {
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
}
return Ok(result_val);
}
@ -83,7 +89,13 @@ impl super::MirBuilder {
// Merge and yield result
self.start_new_block(merge_block)?;
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
if self.is_no_phi_mode() {
for (pred, val) in phi_inputs {
self.insert_edge_copy(pred, result_val, val)?;
}
} else {
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
}
Ok(result_val)
}
}

View File

@ -73,6 +73,30 @@ impl MirBuilder {
let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a));
let result_val = self.value_gen.next();
if self.is_no_phi_mode() {
if let Some(var_name) = assigned_var_then.clone() {
let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
let then_value_for_var = then_var_map_end.get(&var_name).copied().unwrap_or(then_value_raw);
let else_value_for_var = if else_assigns_same {
else_var_map_end_opt
.as_ref()
.and_then(|m| m.get(&var_name).copied())
.unwrap_or(else_value_raw)
} else {
pre_then_var_value.unwrap_or(else_value_raw)
};
self.insert_edge_copy(then_block, result_val, then_value_for_var)?;
self.insert_edge_copy(else_block, result_val, else_value_for_var)?;
self.variable_map = pre_if_var_map.clone();
self.variable_map.insert(var_name, result_val);
} else {
self.insert_edge_copy(then_block, result_val, then_value_raw)?;
self.insert_edge_copy(else_block, result_val, else_value_raw)?;
self.variable_map = pre_if_var_map.clone();
}
return Ok(result_val);
}
if let Some(var_name) = assigned_var_then.clone() {
let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
// Resolve branch-end values for the assigned variable

View File

@ -10,7 +10,7 @@ use super::{
ConstValue
};
use crate::ast::ASTNode;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
/// 不完全なPhi nodeの情報
#[derive(Debug, Clone)]
@ -40,6 +40,9 @@ pub struct LoopBuilder<'a> {
/// continue文からの変数スナップショット
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
/// PHI を生成しないモードかどうか
no_phi_mode: bool,
}
// Local copy: detect a variable name assigned within an AST fragment
@ -68,12 +71,14 @@ fn extract_assigned_var_local(ast: &ASTNode) -> Option<String> {
impl<'a> LoopBuilder<'a> {
/// 新しいループビルダーを作成
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
let no_phi_mode = parent.is_no_phi_mode();
Self {
parent_builder: parent,
incomplete_phis: HashMap::new(),
block_var_maps: HashMap::new(),
loop_header: None,
continue_snapshots: Vec::new(),
no_phi_mode,
}
}
@ -175,6 +180,10 @@ impl<'a> LoopBuilder<'a> {
incomplete_phis.push(incomplete_phi);
if self.no_phi_mode {
self.parent_builder.insert_edge_copy(preheader_id, phi_id, value_before)?;
}
// 変数マップを更新Phi nodeの結果を使用
self.update_variable(var_name.clone(), phi_id);
}
@ -208,7 +217,16 @@ impl<'a> LoopBuilder<'a> {
phi.known_inputs.push((latch_id, value_after));
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);
}
}
@ -422,7 +440,12 @@ impl<'a> LoopBuilder<'a> {
};
if let (Some(tv), Some(ev)) = (then_value_for_var, else_value_for_var) {
let phi_id = self.new_value();
self.emit_phi_at_block_start(merge_bb, phi_id, vec![(then_bb, tv), (else_bb, ev)])?;
if self.no_phi_mode {
self.parent_builder.insert_edge_copy(then_bb, phi_id, tv)?;
self.parent_builder.insert_edge_copy(else_bb, phi_id, ev)?;
} else {
self.emit_phi_at_block_start(merge_bb, phi_id, vec![(then_bb, tv), (else_bb, ev)])?;
}
// Reset to pre-if map and bind the phi result
self.parent_builder.variable_map = pre_if_var_map.clone();
self.parent_builder.variable_map.insert(var_name, phi_id);

View File

@ -428,6 +428,11 @@ impl MirVerifier {
/// Verify SSA form properties
fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
if crate::config::env::verify_allow_no_phi()
{
return Ok(());
}
let mut errors = Vec::new();
let mut definitions = HashMap::new();
@ -470,6 +475,11 @@ impl MirVerifier {
/// Verify dominance relations (def must dominate use across blocks)
fn verify_dominance(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
if crate::config::env::verify_allow_no_phi()
{
return Ok(());
}
let mut errors = Vec::new();
// Build def -> block map and dominators
@ -536,6 +546,11 @@ impl MirVerifier {
/// Verify that blocks with multiple predecessors do not use predecessor-defined values directly.
/// In merge blocks, values coming from predecessors must be routed through Phi.
fn verify_merge_uses(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
// Allow non-SSA (edge-copy) mode for PHI-less MIR when enabled via env
if crate::config::env::verify_allow_no_phi()
{
return Ok(());
}
let mut errors = Vec::new();
let preds = self.compute_predecessors(function);
let def_block = self.compute_def_blocks(function);

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
#[cfg(test)]
mod array_state_sharing_tests {
use nyash_rust::box_trait::{IntegerBox, NyashBox, StringBox};
use nyash_rust::boxes::array::ArrayBox;
use nyash_rust::interpreter::NyashInterpreter;
use nyash_rust::parser::NyashParser;
use nyash_rust::boxes::array::ArrayBox;
use nyash_rust::box_trait::{NyashBox, IntegerBox, StringBox};
#[test]
fn test_arraybox_state_sharing_bug_fix() {
@ -25,7 +25,7 @@ mod array_state_sharing_tests {
let ast = NyashParser::parse_from_string(program).unwrap();
let result = interpreter.execute(ast).unwrap();
let int_result = result.as_any().downcast_ref::<IntegerBox>().unwrap();
assert_eq!(int_result.value, 1); // 🎯 0ではなく1を返すべき
assert_eq!(int_result.value, 1); // 🎯 0ではなく1を返すべき
}
#[test]
@ -36,15 +36,15 @@ mod array_state_sharing_tests {
// share_box: 状態共有
let arr2 = arr1.share_box();
let arr2_array = arr2.as_any().downcast_ref::<ArrayBox>().unwrap();
assert_eq!(arr2_array.len(), 1); // 共有されている
assert_eq!(arr2_array.len(), 1); // 共有されている
// clone_box: 独立
let arr3 = arr1.clone_box();
let arr3_array = arr3.as_any().downcast_ref::<ArrayBox>().unwrap();
arr1.push(Box::new(StringBox::new("world")));
assert_eq!(arr3_array.len(), 1); // 影響を受けない
assert_eq!(arr1.len(), 2); // 元は2要素
assert_eq!(arr2_array.len(), 2); // 共有されているので2要素
assert_eq!(arr3_array.len(), 1); // 影響を受けない
assert_eq!(arr1.len(), 2); // 元は2要素
assert_eq!(arr2_array.len(), 2); // 共有されているので2要素
}
#[test]
@ -68,6 +68,6 @@ mod array_state_sharing_tests {
let ast = NyashParser::parse_from_string(program).unwrap();
let result = interpreter.execute(ast).unwrap();
let int_result = result.as_any().downcast_ref::<IntegerBox>().unwrap();
assert_eq!(int_result.value, 3); // 3要素が正しく保持されるべき
assert_eq!(int_result.value, 3); // 3要素が正しく保持されるべき
}
}

View File

@ -1,13 +1,14 @@
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
use nyash_rust::runtime::PluginConfig;
fn try_init_plugins() -> bool {
if !std::path::Path::new("nyash.toml").exists() { return false; }
if !std::path::Path::new("nyash.toml").exists() {
return false;
}
if let Err(e) = init_global_loader_v2("nyash.toml") {
eprintln!("[e2e] init_global_loader_v2 failed: {:?}", e);
return false;
@ -18,18 +19,24 @@ fn try_init_plugins() -> bool {
if let Some(conf) = &loader.config {
let mut map = std::collections::HashMap::new();
for (lib_name, lib_def) in &conf.libraries {
for b in &lib_def.boxes { map.insert(b.clone(), lib_name.clone()); }
for b in &lib_def.boxes {
map.insert(b.clone(), lib_name.clone());
}
}
let reg = get_global_registry();
reg.apply_plugin_config(&PluginConfig { plugins: map });
true
} else { false }
} else {
false
}
}
#[test]
#[ignore = "MIR13/plugin loader semantics: counter handle parity pending"]
fn e2e_counter_basic_inc_get() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local c, v1, v2
@ -54,7 +61,9 @@ v2
#[test]
#[ignore = "MIR13/plugin loader semantics: assignment sharing parity pending"]
fn e2e_counter_assignment_shares_handle() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local c, x, v
@ -79,7 +88,9 @@ v
#[test]
#[ignore = "MIR13/plugin loader semantics: MapBox shared handle parity pending"]
fn e2e_counter_mapbox_shares_handle() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local c, m, v

View File

@ -2,84 +2,173 @@
//! E2E test for unified registry with a mock plugin factory
use std::sync::Arc;
use nyash_rust::box_factory::BoxFactory;
use nyash_rust::box_factory::builtin::BuiltinGroups;
use nyash_rust::interpreter::{NyashInterpreter, SharedState, RuntimeError};
use nyash_rust::box_factory::BoxFactory;
use nyash_rust::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use nyash_rust::interpreter::{NyashInterpreter, RuntimeError, SharedState};
use nyash_rust::runtime::NyashRuntimeBuilder;
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
// ---------- Mock plugin boxes ----------
#[derive(Debug, Clone)]
struct EchoBox { base: BoxBase, msg: String }
struct EchoBox {
base: BoxBase,
msg: String,
}
impl EchoBox { fn new(msg: String) -> Self { Self { base: BoxBase::new(), msg } } }
impl EchoBox {
fn new(msg: String) -> Self {
Self {
base: BoxBase::new(),
msg,
}
}
}
impl BoxCore for EchoBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
None
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "EchoBox(\"{}\")", self.msg)
}
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl NyashBox for EchoBox {
fn to_string_box(&self) -> StringBox { StringBox::new(self.msg.clone()) }
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() { BoolBox::new(self.msg == e.msg) } else { BoolBox::new(false) }
fn to_string_box(&self) -> StringBox {
StringBox::new(self.msg.clone())
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() {
BoolBox::new(self.msg == e.msg)
} else {
BoolBox::new(false)
}
}
fn type_name(&self) -> &'static str {
"EchoBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn type_name(&self) -> &'static str { "EchoBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
}
#[derive(Debug, Clone)]
struct AdderBox { base: BoxBase, sum: i64 }
impl AdderBox { fn new(a: i64, b: i64) -> Self { Self { base: BoxBase::new(), sum: a + b } } }
struct AdderBox {
base: BoxBase,
sum: i64,
}
impl AdderBox {
fn new(a: i64, b: i64) -> Self {
Self {
base: BoxBase::new(),
sum: a + b,
}
}
}
impl BoxCore for AdderBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "AdderBox(sum={})", self.sum) }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
None
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AdderBox(sum={})", self.sum)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl NyashBox for AdderBox {
fn to_string_box(&self) -> StringBox { StringBox::new(self.sum.to_string()) }
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() { BoolBox::new(self.sum == a.sum) } else { BoolBox::new(false) }
fn to_string_box(&self) -> StringBox {
StringBox::new(self.sum.to_string())
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() {
BoolBox::new(self.sum == a.sum)
} else {
BoolBox::new(false)
}
}
fn type_name(&self) -> &'static str {
"AdderBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn type_name(&self) -> &'static str { "AdderBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
}
// ---------- Mock plugin factory ----------
struct TestPluginFactory;
impl TestPluginFactory { fn new() -> Self { Self } }
impl TestPluginFactory {
fn new() -> Self {
Self
}
}
impl BoxFactory for TestPluginFactory {
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
fn create_box(
&self,
name: &str,
args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
match name {
"EchoBox" => {
let msg = args.get(0).map(|a| a.to_string_box().value).unwrap_or_else(|| "".to_string());
let msg = args
.get(0)
.map(|a| a.to_string_box().value)
.unwrap_or_else(|| "".to_string());
Ok(Box::new(EchoBox::new(msg)))
}
"AdderBox" => {
if args.len() != 2 { return Err(RuntimeError::InvalidOperation{ message: format!("AdderBox expects 2 args, got {}", args.len()) }); }
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?;
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg b must be int".into() })?;
if args.len() != 2 {
return Err(RuntimeError::InvalidOperation {
message: format!("AdderBox expects 2 args, got {}", args.len()),
});
}
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| {
RuntimeError::TypeError {
message: "AdderBox arg a must be int".into(),
}
})?;
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| {
RuntimeError::TypeError {
message: "AdderBox arg b must be int".into(),
}
})?;
Ok(Box::new(AdderBox::new(a, b)))
}
_ => Err(RuntimeError::InvalidOperation{ message: format!("Unknown Box type: {}", name) })
_ => Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}", name),
}),
}
}
fn box_types(&self) -> Vec<&str> { vec!["EchoBox", "AdderBox"] }
fn box_types(&self) -> Vec<&str> {
vec!["EchoBox", "AdderBox"]
}
}
// ---------- E2E tests ----------

View File

@ -1,12 +1,11 @@
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::PluginConfig;
use nyash_rust::runtime::NyashRuntime;
use nyash_rust::backend::VM;
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
use nyash_rust::runtime::NyashRuntime;
use nyash_rust::runtime::PluginConfig;
fn try_init_plugins() -> bool {
if !std::path::Path::new("nyash.toml").exists() {
@ -39,7 +38,9 @@ fn try_init_plugins() -> bool {
#[test]
#[ignore = "Plugins not configured by default env; skip in CI"]
fn e2e_interpreter_plugin_filebox_close_void() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local f
@ -67,7 +68,9 @@ f.close()
#[test]
#[ignore = "MIR13/plugin FileBox: delegation via from Base.birth/close pending"]
fn e2e_interpreter_plugin_filebox_delegation() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
box LoggingFileBox from FileBox {
@ -107,7 +110,9 @@ lf.close()
#[test]
#[ignore = "Plugins not configured by default env; skip in CI"]
fn e2e_vm_plugin_filebox_close_void() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
if nyash_rust::config::env::mir_core13_pure() {
eprintln!("[E2E] skip vm filebox under Core-13 pure mode");
return;
@ -124,7 +129,9 @@ f.close()
let mut compiler = nyash_rust::mir::MirCompiler::new();
let compile_result = compiler.compile(ast).expect("mir compile failed");
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
let result = vm
.execute_module(&compile_result.module)
.expect("vm exec failed");
// close() is void; ensure result is void
assert_eq!(result.to_string_box().value, "void");
}
@ -132,7 +139,9 @@ f.close()
#[test]
#[ignore = "MIR13/plugin FileBox: VM open/rw/read path pending parity"]
fn e2e_vm_plugin_filebox_open_rw() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
// Open, write, read via VM backend
let code = r#"
@ -149,19 +158,24 @@ data
let mut compiler = nyash_rust::mir::MirCompiler::new();
let compile_result = compiler.compile(ast).expect("mir compile failed");
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
let result = vm
.execute_module(&compile_result.module)
.expect("vm exec failed");
assert_eq!(result.to_string_box().value, "HELLO");
}
#[test]
#[ignore = "MIR13/plugin FileBox: VM copyFrom(handle) path pending parity"]
fn e2e_vm_plugin_filebox_copy_from_handle() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let p1 = "./test_out_src.txt";
let p2 = "./test_out_dst.txt";
let code = format!(r#"
let code = format!(
r#"
local a, b, data
a = new FileBox()
b = new FileBox()
@ -171,28 +185,35 @@ a.write("HELLO")
b.copyFrom(a)
data = b.read()
data
"#, p1, p2);
"#,
p1, p2
);
let ast = NyashParser::parse_from_string(&code).expect("parse failed");
let runtime = NyashRuntime::new();
let mut compiler = nyash_rust::mir::MirCompiler::new();
let compile_result = compiler.compile(ast).expect("mir compile failed");
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
let result = vm
.execute_module(&compile_result.module)
.expect("vm exec failed");
assert_eq!(result.to_string_box().value, "HELLO");
}
#[test]
#[ignore = "MIR13/plugin FileBox: interpreter copyFrom(handle) path pending parity"]
fn e2e_interpreter_plugin_filebox_copy_from_handle() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
// Prepare two files and copy contents via plugin Handle argument
let p1 = "./test_out_src.txt";
let p2 = "./test_out_dst.txt";
// Nyash program: open two FileBox, write to src, copy to dst via copyFrom, then read dst
let code = format!(r#"
let code = format!(
r#"
local a, b, data
a = new FileBox()
b = new FileBox()
@ -202,7 +223,9 @@ a.write("HELLO")
b.copyFrom(a)
data = b.read()
data
"#, p1, p2);
"#,
p1, p2
);
let ast = NyashParser::parse_from_string(&code).expect("parse failed");
let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new();

View File

@ -2,46 +2,93 @@
//! E2E: Interop between builtin and mock plugin boxes via MapBox storage
use std::sync::Arc;
use nyash_rust::box_factory::BoxFactory;
use nyash_rust::box_factory::builtin::BuiltinGroups;
use nyash_rust::box_factory::BoxFactory;
use nyash_rust::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use nyash_rust::interpreter::{NyashInterpreter, RuntimeError};
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
#[derive(Debug, Clone)]
struct EchoBox { base: BoxBase, msg: String }
impl EchoBox { fn new(msg: String) -> Self { Self { base: BoxBase::new(), msg } } }
struct EchoBox {
base: BoxBase,
msg: String,
}
impl EchoBox {
fn new(msg: String) -> Self {
Self {
base: BoxBase::new(),
msg,
}
}
}
impl BoxCore for EchoBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "EchoBox(\"{}\")", self.msg) }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
None
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "EchoBox(\"{}\")", self.msg)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl NyashBox for EchoBox {
fn to_string_box(&self) -> StringBox { StringBox::new(self.msg.clone()) }
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() { BoolBox::new(self.msg == e.msg) } else { BoolBox::new(false) }
fn to_string_box(&self) -> StringBox {
StringBox::new(self.msg.clone())
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(e) = other.as_any().downcast_ref::<EchoBox>() {
BoolBox::new(self.msg == e.msg)
} else {
BoolBox::new(false)
}
}
fn type_name(&self) -> &'static str {
"EchoBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn type_name(&self) -> &'static str { "EchoBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
}
struct TestPluginFactory;
impl TestPluginFactory { fn new() -> Self { Self } }
impl TestPluginFactory {
fn new() -> Self {
Self
}
}
impl BoxFactory for TestPluginFactory {
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
fn create_box(
&self,
name: &str,
args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
match name {
"EchoBox" => {
let msg = args.get(0).map(|a| a.to_string_box().value).unwrap_or_else(|| "".to_string());
let msg = args
.get(0)
.map(|a| a.to_string_box().value)
.unwrap_or_else(|| "".to_string());
Ok(Box::new(EchoBox::new(msg)))
}
_ => Err(RuntimeError::InvalidOperation{ message: format!("Unknown Box type: {}", name) })
_ => Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}", name),
}),
}
}
fn box_types(&self) -> Vec<&str> { vec!["EchoBox"] }
fn box_types(&self) -> Vec<&str> {
vec!["EchoBox"]
}
}
fn new_interpreter_with_factory() -> NyashInterpreter {

View File

@ -1,24 +1,34 @@
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::PluginConfig;
use nyash_rust::runtime::NyashRuntime;
use nyash_rust::backend::VM;
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
use nyash_rust::runtime::NyashRuntime;
use nyash_rust::runtime::PluginConfig;
fn try_init_plugins() -> bool {
if !std::path::Path::new("nyash.toml").exists() { return false; }
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
if !std::path::Path::new("nyash.toml").exists() {
return false;
}
if let Err(e) = init_global_loader_v2("nyash.toml") {
eprintln!("init failed: {:?}", e);
return false;
}
let loader = get_global_loader_v2();
let loader = loader.read().unwrap();
if let Some(conf) = &loader.config {
let mut map = std::collections::HashMap::new();
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
for (lib, def) in &conf.libraries {
for b in &def.boxes {
map.insert(b.clone(), lib.clone());
}
}
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
true
} else { false }
} else {
false
}
}
#[test]
@ -26,7 +36,9 @@ fn try_init_plugins() -> bool {
fn e2e_http_two_servers_parallel() {
std::env::set_var("NYASH_NET_LOG", "1");
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local s1, s2, c, r1, r2, resp1, resp2, req1, req2, p1, p2, x, y
@ -73,7 +85,9 @@ x + ":" + y
fn e2e_http_long_body_and_headers() {
std::env::set_var("NYASH_NET_LOG", "1");
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local s, c, r, resp, q, body, hv
@ -106,13 +120,14 @@ hv + ":" + body
assert!(s.contains("OK-LONG"));
}
#[test]
#[ignore = "MIR13/plugin Net: client error result semantics pending"]
fn e2e_vm_http_client_error_result() {
std::env::set_var("NYASH_NET_LOG", "1");
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
// No server on 8099 → should produce Err result
let code = r#"
@ -133,7 +148,9 @@ result
let mut compiler = nyash_rust::mir::MirCompiler::new();
let compile_result = compiler.compile(ast).expect("mir compile failed");
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
let result = vm
.execute_module(&compile_result.module)
.expect("vm exec failed");
let s = result.to_string_box().value;
assert!(s.contains("Error") || s.contains("unexpected_ok") == false);
}
@ -143,7 +160,9 @@ result
fn e2e_vm_http_empty_body() {
std::env::set_var("NYASH_NET_LOG", "1");
std::env::set_var("NYASH_NET_LOG_FILE", "net_plugin.log");
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local srv, cli, r, resp, req, body
@ -169,6 +188,8 @@ body
let mut compiler = nyash_rust::mir::MirCompiler::new();
let compile_result = compiler.compile(ast).expect("mir compile failed");
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
let result = vm
.execute_module(&compile_result.module)
.expect("vm exec failed");
assert_eq!(result.to_string_box().value, "");
}

View File

@ -1,28 +1,40 @@
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
use nyash_rust::runtime::PluginConfig;
fn try_init_plugins() -> bool {
if !std::path::Path::new("nyash.toml").exists() { return false; }
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
if !std::path::Path::new("nyash.toml").exists() {
return false;
}
if let Err(e) = init_global_loader_v2("nyash.toml") {
eprintln!("init failed: {:?}", e);
return false;
}
let loader = get_global_loader_v2();
let loader = loader.read().unwrap();
if let Some(conf) = &loader.config {
let mut map = std::collections::HashMap::new();
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
for (lib, def) in &conf.libraries {
for b in &def.boxes {
map.insert(b.clone(), lib.clone());
}
}
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
true
} else { false }
} else {
false
}
}
#[test]
#[ignore = "MIR13/plugin singleton: CounterBox shared instance parity pending"]
fn e2e_counterbox_singleton_shared_across_news() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
// CounterBox is configured as singleton in nyash.toml
let code = r#"

View File

@ -1,28 +1,42 @@
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2, shutdown_plugins_v2};
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::plugin_loader_v2::{
get_global_loader_v2, init_global_loader_v2, shutdown_plugins_v2,
};
use nyash_rust::runtime::PluginConfig;
fn try_init_plugins() -> bool {
if !std::path::Path::new("nyash.toml").exists() { return false; }
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
if !std::path::Path::new("nyash.toml").exists() {
return false;
}
if let Err(e) = init_global_loader_v2("nyash.toml") {
eprintln!("init failed: {:?}", e);
return false;
}
let loader = get_global_loader_v2();
let loader = loader.read().unwrap();
if let Some(conf) = &loader.config {
let mut map = std::collections::HashMap::new();
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
for (lib, def) in &conf.libraries {
for b in &def.boxes {
map.insert(b.clone(), lib.clone());
}
}
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
true
} else { false }
} else {
false
}
}
#[test]
#[ignore = "MIR13/plugin singleton: shutdown/recreate parity pending"]
fn e2e_singleton_shutdown_and_recreate() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
// Use CounterBox singleton and bump to 1
let code1 = r#"

View File

@ -1,28 +1,40 @@
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
use nyash_rust::runtime::PluginConfig;
fn try_init_plugins() -> bool {
if !std::path::Path::new("nyash.toml").exists() { return false; }
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
if !std::path::Path::new("nyash.toml").exists() {
return false;
}
if let Err(e) = init_global_loader_v2("nyash.toml") {
eprintln!("init failed: {:?}", e);
return false;
}
let loader = get_global_loader_v2();
let loader = loader.read().unwrap();
if let Some(conf) = &loader.config {
let mut map = std::collections::HashMap::new();
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
for (lib, def) in &conf.libraries {
for b in &def.boxes {
map.insert(b.clone(), lib.clone());
}
}
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
true
} else { false }
} else {
false
}
}
#[test]
#[ignore = "MIR13/plugin Socket: ping/pong parity pending (start method)"]
fn e2e_socket_ping_pong() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
// Start server, client connect, ping/pong
let code = r#"
@ -52,7 +64,9 @@ r
#[test]
#[ignore = "MIR13/plugin Socket: accept/recv timeout parity pending (start method)"]
fn e2e_socket_accept_timeout_and_recv_timeout() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local ss, sc, c, s, r

View File

@ -2,49 +2,92 @@
//! E2E: Reserved-name guard for unified registry
use std::sync::Arc;
use nyash_rust::box_factory::BoxFactory;
use nyash_rust::box_factory::builtin::BuiltinGroups;
use nyash_rust::box_factory::BoxFactory;
use nyash_rust::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use nyash_rust::interpreter::NyashInterpreter;
use nyash_rust::interpreter::RuntimeError;
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
// Dummy factory that tries to claim reserved core types
struct BadFactory;
impl BadFactory { fn new() -> Self { Self } }
impl BadFactory {
fn new() -> Self {
Self
}
}
#[derive(Debug, Clone)]
struct FakeStringBox { base: BoxBase, inner: String }
struct FakeStringBox {
base: BoxBase,
inner: String,
}
impl BoxCore for FakeStringBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "FakeString(\"{}\")", self.inner) }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
None
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FakeString(\"{}\")", self.inner)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl NyashBox for FakeStringBox {
fn to_string_box(&self) -> StringBox { StringBox::new(format!("FAKE:{}", self.inner)) }
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(s) = other.as_any().downcast_ref::<FakeStringBox>() { BoolBox::new(self.inner == s.inner) } else { BoolBox::new(false) }
fn to_string_box(&self) -> StringBox {
StringBox::new(format!("FAKE:{}", self.inner))
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(s) = other.as_any().downcast_ref::<FakeStringBox>() {
BoolBox::new(self.inner == s.inner)
} else {
BoolBox::new(false)
}
}
fn type_name(&self) -> &'static str {
"StringBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn type_name(&self) -> &'static str { "StringBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
}
impl BoxFactory for BadFactory {
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
fn create_box(
&self,
name: &str,
args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
match name {
// Attempt to hijack StringBox
"StringBox" => {
let s = args.get(0).map(|a| a.to_string_box().value).unwrap_or_default();
Ok(Box::new(FakeStringBox { base: BoxBase::new(), inner: s }))
let s = args
.get(0)
.map(|a| a.to_string_box().value)
.unwrap_or_default();
Ok(Box::new(FakeStringBox {
base: BoxBase::new(),
inner: s,
}))
}
_ => Err(RuntimeError::InvalidOperation { message: format!("Unknown Box type: {}", name) })
_ => Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}", name),
}),
}
}
fn box_types(&self) -> Vec<&str> { vec!["StringBox"] }
fn box_types(&self) -> Vec<&str> {
vec!["StringBox"]
}
}
#[test]

View File

@ -1,5 +1,5 @@
use nyash_rust::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
use nyash_rust::grammar::engine;
use nyash_rust::box_trait::{StringBox, IntegerBox, BoolBox, VoidBox, NyashBox};
fn classify_value(b: &dyn NyashBox) -> &'static str {
if nyash_rust::runtime::semantics::coerce_to_string(b).is_some() {
@ -19,12 +19,14 @@ fn actual_add_result(left: &dyn NyashBox, right: &dyn NyashBox) -> &'static str
// Mirror current interpreter semantics succinctly:
// 1) If either is string-like => String
if nyash_rust::runtime::semantics::coerce_to_string(left).is_some()
|| nyash_rust::runtime::semantics::coerce_to_string(right).is_some() {
|| nyash_rust::runtime::semantics::coerce_to_string(right).is_some()
{
return "String";
}
// 2) If both are i64-coercible => Integer
if nyash_rust::runtime::semantics::coerce_to_i64(left).is_some()
&& nyash_rust::runtime::semantics::coerce_to_i64(right).is_some() {
&& nyash_rust::runtime::semantics::coerce_to_i64(right).is_some()
{
return "Integer";
}
// 3) Otherwise errorここでは Error として表現)
@ -54,12 +56,23 @@ fn snapshot_add_rules_align_with_current_semantics() {
let expect = eng.decide_add_result(lty, rty).map(|(res, _)| res);
if let Some(res) = expect {
if actual == "Error" {
panic!("grammar provides rule for {}+{} but actual semantics error", li, ri);
panic!(
"grammar provides rule for {}+{} but actual semantics error",
li, ri
);
} else {
assert_eq!(res, actual, "grammar expect {} + {} => {}, but actual => {}", li, ri, res, actual);
assert_eq!(
res, actual,
"grammar expect {} + {} => {}, but actual => {}",
li, ri, res, actual
);
}
} else {
assert_eq!(actual, "Error", "grammar has no rule for {}+{}, but actual => {}", li, ri, actual);
assert_eq!(
actual, "Error",
"grammar has no rule for {}+{}, but actual => {}",
li, ri, actual
);
}
}
}

View File

@ -6,14 +6,22 @@ fn grammar_sub_mul_div_rules_exist_and_basic_cases() {
// Sub
assert!(!eng.sub_rules().is_empty(), "sub rules should not be empty");
assert!(eng.decide_sub_result("Integer","Integer").is_some(), "sub i64+i64 should be defined");
assert!(
eng.decide_sub_result("Integer", "Integer").is_some(),
"sub i64+i64 should be defined"
);
// Mul
assert!(!eng.mul_rules().is_empty(), "mul rules should not be empty");
assert!(eng.decide_mul_result("Integer","Integer").is_some(), "mul i64*i64 should be defined");
assert!(
eng.decide_mul_result("Integer", "Integer").is_some(),
"mul i64*i64 should be defined"
);
// Div
assert!(!eng.div_rules().is_empty(), "div rules should not be empty");
assert!(eng.decide_div_result("Integer","Integer").is_some(), "div i64/i64 should be defined");
assert!(
eng.decide_div_result("Integer", "Integer").is_some(),
"div i64/i64 should be defined"
);
}

View File

@ -27,7 +27,9 @@ fn get_variable_value(code: &str, var_name: &str) -> Result<String, String> {
match parser::NyashParser::parse_from_string(code) {
Ok(ast) => {
let mut interpreter = interpreter::NyashInterpreter::new();
interpreter.execute(ast).map_err(|e| format!("Execution error: {}", e))?;
interpreter
.execute(ast)
.map_err(|e| format!("Execution error: {}", e))?;
match interpreter.get_variable(var_name) {
Ok(value) => Ok(value.to_string_box().value),
@ -350,7 +352,10 @@ mod integration_tests {
assert_eq!(get_variable_value(code, "less_result").unwrap(), "true");
assert_eq!(get_variable_value(code, "greater_result").unwrap(), "true");
assert_eq!(get_variable_value(code, "less_eq_result").unwrap(), "true");
assert_eq!(get_variable_value(code, "greater_eq_result").unwrap(), "true");
assert_eq!(
get_variable_value(code, "greater_eq_result").unwrap(),
"true"
);
}
#[test]

View 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"}}
]}

View 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}}
]}

View 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"}}
]}

View File

@ -1,25 +1,41 @@
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
use nyash_rust::{parser::NyashParser, mir::{MirCompiler, passes::method_id_inject::inject_method_ids, instruction::MirInstruction}};
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
use nyash_rust::runtime::PluginConfig;
use nyash_rust::{
mir::{instruction::MirInstruction, passes::method_id_inject::inject_method_ids, MirCompiler},
parser::NyashParser,
};
fn try_init_plugins() -> bool {
if !std::path::Path::new("nyash.toml").exists() { return false; }
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
if !std::path::Path::new("nyash.toml").exists() {
return false;
}
if let Err(e) = init_global_loader_v2("nyash.toml") {
eprintln!("init failed: {:?}", e);
return false;
}
let loader = get_global_loader_v2();
let loader = loader.read().unwrap();
if let Some(conf) = &loader.config {
let mut map = std::collections::HashMap::new();
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
for (lib, def) in &conf.libraries {
for b in &def.boxes {
map.insert(b.clone(), lib.clone());
}
}
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
true
} else { false }
} else {
false
}
}
#[test]
fn injects_method_id_for_filebox_open() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
let code = r#"
local f
f = new FileBox()
@ -34,8 +50,14 @@ f.open("/tmp/test.txt", "r")
for func in module2.functions.values() {
for block in func.blocks.values() {
for inst in &block.instructions {
if let MirInstruction::BoxCall { method, method_id, .. } = inst {
if method == "open" { assert!(method_id.is_some(), "open missing method_id"); return; }
if let MirInstruction::BoxCall {
method, method_id, ..
} = inst
{
if method == "open" {
assert!(method_id.is_some(), "open missing method_id");
return;
}
}
}
}

View File

@ -4,6 +4,13 @@ use nyash_rust::mir::instruction_introspection;
#[test]
fn mir14_shape_is_fixed() {
let impl_names = instruction_introspection::mir14_instruction_names();
assert_eq!(impl_names.len(), 14, "MIR14 must contain exactly 14 instructions");
assert!(impl_names.contains(&"UnaryOp"), "MIR14 must include UnaryOp");
assert_eq!(
impl_names.len(),
14,
"MIR14 must contain exactly 14 instructions"
);
assert!(
impl_names.contains(&"UnaryOp"),
"MIR14 must include UnaryOp"
);
}

View File

@ -4,8 +4,8 @@
* Tests AST → MIR lowering for Phase 6 reference operations
*/
use nyash_rust::mir::{MirBuilder, MirPrinter};
use nyash_rust::ast::{ASTNode, LiteralValue, Span};
use nyash_rust::mir::{MirBuilder, MirPrinter};
use std::collections::HashMap;
#[test]
@ -140,27 +140,52 @@ fn test_mir_phase6_lowering_ref_ops() {
println!("Generated MIR:\n{}", mir_text);
// Verify that the MIR contains the expected Phase 6 reference operations
assert!(mir_text.contains("ref_new"), "MIR should contain ref_new instruction");
assert!(mir_text.contains("ref_set"), "MIR should contain ref_set instruction");
assert!(mir_text.contains("ref_get"), "MIR should contain ref_get instruction");
assert!(
mir_text.contains("ref_new"),
"MIR should contain ref_new instruction"
);
assert!(
mir_text.contains("ref_set"),
"MIR should contain ref_set instruction"
);
assert!(
mir_text.contains("ref_get"),
"MIR should contain ref_get instruction"
);
// Verify specific patterns
assert!(mir_text.contains("ref_new") && mir_text.contains("\"Obj\""),
"MIR should contain ref_new with Obj class");
assert!(mir_text.contains("ref_set") && mir_text.contains(".x"),
"MIR should contain ref_set with field x");
assert!(mir_text.contains("ref_get") && mir_text.contains(".x"),
"MIR should contain ref_get with field x");
assert!(
mir_text.contains("ref_new") && mir_text.contains("\"Obj\""),
"MIR should contain ref_new with Obj class"
);
assert!(
mir_text.contains("ref_set") && mir_text.contains(".x"),
"MIR should contain ref_set with field x"
);
assert!(
mir_text.contains("ref_get") && mir_text.contains(".x"),
"MIR should contain ref_get with field x"
);
// Verify module structure
assert_eq!(module.function_names().len(), 1, "Module should have one function");
assert!(module.get_function("main").is_some(), "Module should have main function");
assert_eq!(
module.function_names().len(),
1,
"Module should have one function"
);
assert!(
module.get_function("main").is_some(),
"Module should have main function"
);
// Verify function has instructions
let main_function = module.get_function("main").unwrap();
let stats = main_function.stats();
assert!(stats.instruction_count > 5,
"Function should have multiple instructions (got {})", stats.instruction_count);
assert!(
stats.instruction_count > 5,
"Function should have multiple instructions (got {})",
stats.instruction_count
);
println!("✅ Phase 6.1 MIR lowering test passed! Found all required ref operations.");
}
@ -170,21 +195,19 @@ fn test_mir_phase6_lowering_ref_ops() {
fn test_mir_verification_phase6_ref_ops() {
// Build simple AST with new and field access
let ast = ASTNode::Program {
statements: vec![
ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "obj".to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::New {
class: "TestObj".to_string(),
arguments: vec![],
type_arguments: vec![],
span: Span::unknown(),
}),
statements: vec![ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "obj".to_string(),
span: Span::unknown(),
},
],
}),
value: Box::new(ASTNode::New {
class: "TestObj".to_string(),
arguments: vec![],
type_arguments: vec![],
span: Span::unknown(),
}),
span: Span::unknown(),
}],
span: Span::unknown(),
};
@ -203,14 +226,16 @@ fn test_mir_verification_phase6_ref_ops() {
match verification_result {
Ok(()) => {
println!("✅ MIR verification passed for Phase 6 reference operations");
},
}
Err(errors) => {
println!("❌ MIR verification failed with {} errors:", errors.len());
for error in &errors {
println!(" - {:?}", error);
}
// Don't fail the test for verification errors, as the verifier may be incomplete
println!("⚠️ Continuing test despite verification issues (verifier may be incomplete)");
println!(
"⚠️ Continuing test despite verification issues (verifier may be incomplete)"
);
}
}
}

View File

@ -4,12 +4,12 @@
* Tests VM execution of hand-built MIR with RefNew/RefGet/RefSet instructions
*/
use nyash_rust::mir::{
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue
};
use nyash_rust::backend::{VM, VMValue};
use nyash_rust::backend::{VMValue, VM};
use nyash_rust::box_trait::{IntegerBox, NyashBox};
use nyash_rust::mir::{
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
MirInstruction, MirModule, MirType, ValueId,
};
#[test]
fn test_mir_phase6_vm_ref_ops() {
@ -67,9 +67,7 @@ fn test_mir_phase6_vm_ref_ops() {
});
// barrier_write %1
block.add_instruction(MirInstruction::BarrierWrite {
ptr: obj_ref,
});
block.add_instruction(MirInstruction::BarrierWrite { ptr: obj_ref });
// ref_set %1, "x", %2
block.add_instruction(MirInstruction::RefSet {
@ -92,9 +90,7 @@ fn test_mir_phase6_vm_ref_ops() {
});
// ret %3
block.add_instruction(MirInstruction::Return {
value: Some(x_val),
});
block.add_instruction(MirInstruction::Return { value: Some(x_val) });
// Add block to function
main_function.add_block(block);
@ -112,11 +108,18 @@ fn test_mir_phase6_vm_ref_ops() {
// Check if result is IntegerBox with value 1
if let Some(int_box) = result_box.as_any().downcast_ref::<IntegerBox>() {
assert_eq!(int_box.value, 1, "Return value should be 1, got {}", int_box.value);
assert_eq!(
int_box.value, 1,
"Return value should be 1, got {}",
int_box.value
);
println!("✅ Return value correct: {}", int_box.value);
} else {
// Print what we actually got
println!("⚠️ Expected IntegerBox, got: {}", result_box.to_string_box().value);
println!(
"⚠️ Expected IntegerBox, got: {}",
result_box.to_string_box().value
);
println!(" Type: {}", result_box.type_name());
// For Phase 6.1, the core functionality works (field ops execute correctly)
@ -124,7 +127,7 @@ fn test_mir_phase6_vm_ref_ops() {
println!("✅ Phase 6.1 core requirement met: RefNew/RefGet/RefSet execute without errors");
println!("✅ Field operations working correctly (note: return value propagation has minor issue)");
}
},
}
Err(e) => {
panic!("❌ VM execution failed: {}", e);
}
@ -169,13 +172,9 @@ fn test_barrier_no_op() {
});
// Test barrier instructions (should be no-ops)
block.add_instruction(MirInstruction::BarrierRead {
ptr: test_val,
});
block.add_instruction(MirInstruction::BarrierRead { ptr: test_val });
block.add_instruction(MirInstruction::BarrierWrite {
ptr: test_val,
});
block.add_instruction(MirInstruction::BarrierWrite { ptr: test_val });
block.add_instruction(MirInstruction::Return {
value: Some(test_val),
@ -188,6 +187,9 @@ fn test_barrier_no_op() {
let mut vm = VM::new();
let result = vm.execute_module(&module);
assert!(result.is_ok(), "Barrier instructions should not cause VM errors");
assert!(
result.is_ok(),
"Barrier instructions should not cause VM errors"
);
println!("✅ Barrier no-op test passed - barriers execute without errors");
}

View File

@ -4,9 +4,9 @@
* Tests AST → MIR lowering and VM execution for Phase 7 async operations
*/
use nyash_rust::mir::{MirBuilder, MirPrinter};
use nyash_rust::backend::VM;
use nyash_rust::ast::{ASTNode, LiteralValue, Span};
use nyash_rust::backend::VM;
use nyash_rust::mir::{MirBuilder, MirPrinter};
use std::collections::HashMap;
#[test]
@ -106,20 +106,22 @@ fn test_mir_phase7_basic_nowait_await() {
// Verify MIR contains expected instructions
let function = module.get_function("main").unwrap();
let instructions: Vec<_> = function.blocks.values()
let instructions: Vec<_> = function
.blocks
.values()
.flat_map(|block| &block.instructions)
.collect();
// Should contain FutureNew instruction
let has_future_new = instructions.iter().any(|inst| {
matches!(inst, nyash_rust::mir::MirInstruction::FutureNew { .. })
});
let has_future_new = instructions
.iter()
.any(|inst| matches!(inst, nyash_rust::mir::MirInstruction::FutureNew { .. }));
assert!(has_future_new, "MIR should contain FutureNew instruction");
// Should contain Await instruction
let has_await = instructions.iter().any(|inst| {
matches!(inst, nyash_rust::mir::MirInstruction::Await { .. })
});
let has_await = instructions
.iter()
.any(|inst| matches!(inst, nyash_rust::mir::MirInstruction::Await { .. }));
assert!(has_await, "MIR should contain Await instruction");
// Test VM execution

View File

@ -1,28 +1,41 @@
#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2};
use nyash_rust::runtime::box_registry::get_global_registry;
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
use nyash_rust::runtime::PluginConfig;
fn try_init_plugins() -> bool {
if !std::path::Path::new("nyash.toml").exists() { return false; }
if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; }
if !std::path::Path::new("nyash.toml").exists() {
return false;
}
if let Err(e) = init_global_loader_v2("nyash.toml") {
eprintln!("init failed: {:?}", e);
return false;
}
let loader = get_global_loader_v2();
let loader = loader.read().unwrap();
if let Some(conf) = &loader.config {
let mut map = std::collections::HashMap::new();
for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } }
for (lib, def) in &conf.libraries {
for b in &def.boxes {
map.insert(b.clone(), lib.clone());
}
}
get_global_registry().apply_plugin_config(&PluginConfig { plugins: map });
true
} else { false }
} else {
false
}
}
/// Minimal ABI sanity check: HttpRequestBox.path=1, readBody=2
#[test]
#[ignore = "MIR13/plugin Net: HttpRequestBox path/readBody default values pending"]
fn plugin_contract_http_request_ids_sanity() {
if !try_init_plugins() { return; }
if !try_init_plugins() {
return;
}
// Exercise HttpRequestBox.path/readBody on a birthed request (no server needed)
let code = r#"
local req, p, b

View File

@ -2,53 +2,109 @@
//! VM E2E: Compile Nyash to MIR and execute via VM, with mock plugin factory
use std::sync::Arc;
use nyash_rust::box_factory::BoxFactory;
use nyash_rust::box_factory::builtin::BuiltinGroups;
use nyash_rust::runtime::NyashRuntimeBuilder;
use nyash_rust::parser::NyashParser;
use nyash_rust::mir::MirCompiler;
use nyash_rust::backend::VM;
use nyash_rust::box_factory::builtin::BuiltinGroups;
use nyash_rust::box_factory::BoxFactory;
use nyash_rust::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use nyash_rust::interpreter::RuntimeError;
use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
use nyash_rust::mir::MirCompiler;
use nyash_rust::parser::NyashParser;
use nyash_rust::runtime::NyashRuntimeBuilder;
// Minimal AdderBox to validate plugin factory path under VM
#[derive(Debug, Clone)]
struct AdderBox { base: BoxBase, sum: i64 }
impl AdderBox { fn new(a: i64, b: i64) -> Self { Self { base: BoxBase::new(), sum: a + b } } }
struct AdderBox {
base: BoxBase,
sum: i64,
}
impl AdderBox {
fn new(a: i64, b: i64) -> Self {
Self {
base: BoxBase::new(),
sum: a + b,
}
}
}
impl BoxCore for AdderBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "AdderBox(sum={})", self.sum) }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
None
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AdderBox(sum={})", self.sum)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl NyashBox for AdderBox {
fn to_string_box(&self) -> StringBox { StringBox::new(self.sum.to_string()) }
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() { BoolBox::new(self.sum == a.sum) } else { BoolBox::new(false) }
fn to_string_box(&self) -> StringBox {
StringBox::new(self.sum.to_string())
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(a) = other.as_any().downcast_ref::<AdderBox>() {
BoolBox::new(self.sum == a.sum)
} else {
BoolBox::new(false)
}
}
fn type_name(&self) -> &'static str {
"AdderBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn type_name(&self) -> &'static str { "AdderBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
fn share_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
}
struct TestPluginFactory;
impl TestPluginFactory { fn new() -> Self { Self } }
impl TestPluginFactory {
fn new() -> Self {
Self
}
}
impl BoxFactory for TestPluginFactory {
fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
fn create_box(
&self,
name: &str,
args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
match name {
"AdderBox" => {
if args.len() != 2 { return Err(RuntimeError::InvalidOperation{ message: format!("AdderBox expects 2 args, got {}", args.len()) }); }
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?;
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg b must be int".into() })?;
if args.len() != 2 {
return Err(RuntimeError::InvalidOperation {
message: format!("AdderBox expects 2 args, got {}", args.len()),
});
}
let a = args[0].to_string_box().value.parse::<i64>().map_err(|_| {
RuntimeError::TypeError {
message: "AdderBox arg a must be int".into(),
}
})?;
let b = args[1].to_string_box().value.parse::<i64>().map_err(|_| {
RuntimeError::TypeError {
message: "AdderBox arg b must be int".into(),
}
})?;
Ok(Box::new(AdderBox::new(a, b)))
}
_ => Err(RuntimeError::InvalidOperation{ message: format!("Unknown Box type: {}", name) })
_ => Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}", name),
}),
}
}
fn box_types(&self) -> Vec<&str> { vec!["AdderBox"] }
fn box_types(&self) -> Vec<&str> {
vec!["AdderBox"]
}
}
#[test]
@ -56,9 +112,11 @@ fn vm_e2e_adder_box() {
// Build runtime with builtin + user-defined + mock plugin factory
let runtime = NyashRuntimeBuilder::new()
.with_builtin_groups(BuiltinGroups::native_full())
.with_factory(Arc::new(nyash_rust::box_factory::user_defined::UserDefinedBoxFactory::new(
nyash_rust::interpreter::SharedState::new(),
)))
.with_factory(Arc::new(
nyash_rust::box_factory::user_defined::UserDefinedBoxFactory::new(
nyash_rust::interpreter::SharedState::new(),
),
))
.with_factory(Arc::new(TestPluginFactory::new()))
.build();
@ -75,12 +133,18 @@ fn vm_e2e_adder_box() {
// Execute via VM using the prepared runtime
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec ok");
let result = vm
.execute_module(&compile_result.module)
.expect("vm exec ok");
// The VM returns an Option<Box<dyn NyashBox>> or a value; we print/debug and check string form
// Here we rely on Display via to_string_box through Debug format
// Try to format the result if available
// For this implementation, result is a generic value; we check debug string contains 42 or to_string equivalent.
let s = format!("{:?}", result);
assert!(s.contains("42") || s.contains("AdderBox"), "unexpected VM result: {}", s);
assert!(
s.contains("42") || s.contains("AdderBox"),
"unexpected VM result: {}",
s
);
}

View File

@ -9,11 +9,11 @@
* - Return values
*/
use nyash_rust::mir::{
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue, BinaryOp
};
use nyash_rust::backend::wasm::WasmBackend;
use nyash_rust::mir::{
BasicBlock, BasicBlockId, BinaryOp, ConstValue, EffectMask, FunctionSignature, MirFunction,
MirInstruction, MirModule, MirType, ValueId,
};
#[test]
fn test_wasm_poc1_basic_arithmetic() {
@ -36,15 +36,36 @@ fn test_wasm_poc1_basic_arithmetic() {
let wat_text = wat_result.unwrap();
// Verify WAT contains expected elements
assert!(wat_text.contains("(module"), "Should contain module declaration");
assert!(wat_text.contains("memory"), "Should contain memory declaration");
assert!(
wat_text.contains("(module"),
"Should contain module declaration"
);
assert!(
wat_text.contains("memory"),
"Should contain memory declaration"
);
assert!(wat_text.contains("import"), "Should contain imports");
assert!(wat_text.contains("$main"), "Should contain main function");
assert!(wat_text.contains("i32.const 42"), "Should contain constant 42");
assert!(wat_text.contains("i32.const 8"), "Should contain constant 8");
assert!(wat_text.contains("i32.add"), "Should contain addition operation");
assert!(wat_text.contains("call $print"), "Should contain print call");
assert!(wat_text.contains("return"), "Should contain return instruction");
assert!(
wat_text.contains("i32.const 42"),
"Should contain constant 42"
);
assert!(
wat_text.contains("i32.const 8"),
"Should contain constant 8"
);
assert!(
wat_text.contains("i32.add"),
"Should contain addition operation"
);
assert!(
wat_text.contains("call $print"),
"Should contain print call"
);
assert!(
wat_text.contains("return"),
"Should contain return instruction"
);
// Compile to WASM binary and execute
let wasm_result = backend.compile_module(mir_module);
@ -110,9 +131,9 @@ fn build_arithmetic_mir_module() -> MirModule {
let mut block = BasicBlock::new(entry_block);
// Generate value IDs
let val_a = ValueId::new(0); // 42
let val_b = ValueId::new(1); // 8
let result = ValueId::new(2); // 42 + 8
let val_a = ValueId::new(0); // 42
let val_b = ValueId::new(1); // 8
let result = ValueId::new(2); // 42 + 8
// Add instructions
block.add_instruction(MirInstruction::Const {
@ -143,7 +164,11 @@ fn build_arithmetic_mir_module() -> MirModule {
});
// Debug: Print number of instructions and terminator
println!("Instructions: {}, Has terminator: {}", block.instructions.len(), block.terminator.is_some());
println!(
"Instructions: {}, Has terminator: {}",
block.instructions.len(),
block.terminator.is_some()
);
// Add block to function
main_function.add_block(block);
@ -169,9 +194,9 @@ fn build_multiplication_mir_module() -> MirModule {
let mut main_function = MirFunction::new(main_signature, entry_block);
let mut block = BasicBlock::new(entry_block);
let val_a = ValueId::new(0); // 6
let val_b = ValueId::new(1); // 7
let result = ValueId::new(2); // 6 * 7
let val_a = ValueId::new(0); // 6
let val_b = ValueId::new(1); // 7
let result = ValueId::new(2); // 6 * 7
block.add_instruction(MirInstruction::Const {
dst: val_a,
@ -215,9 +240,9 @@ fn build_subtraction_mir_module() -> MirModule {
let mut main_function = MirFunction::new(main_signature, entry_block);
let mut block = BasicBlock::new(entry_block);
let val_a = ValueId::new(0); // 50
let val_b = ValueId::new(1); // 8
let result = ValueId::new(2); // 50 - 8
let val_a = ValueId::new(0); // 50
let val_b = ValueId::new(1); // 8
let result = ValueId::new(2); // 50 - 8
block.add_instruction(MirInstruction::Const {
dst: val_a,

View File

@ -11,11 +11,11 @@
* Validates the "Everything is Box" philosophy in WASM
*/
use nyash_rust::mir::{
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue
};
use nyash_rust::backend::wasm::WasmBackend;
use nyash_rust::mir::{
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
MirInstruction, MirModule, MirType, ValueId,
};
#[test]
fn test_wasm_poc2_refnew_basic() {
@ -36,10 +36,22 @@ fn test_wasm_poc2_refnew_basic() {
let wat_text = wat_result.unwrap();
// Verify WAT contains expected elements
assert!(wat_text.contains("(module"), "Should contain module declaration");
assert!(wat_text.contains("$malloc"), "Should contain malloc function");
assert!(wat_text.contains("$alloc_databox"), "Should contain DataBox allocator");
assert!(wat_text.contains("call $alloc_databox"), "Should call DataBox allocator");
assert!(
wat_text.contains("(module"),
"Should contain module declaration"
);
assert!(
wat_text.contains("$malloc"),
"Should contain malloc function"
);
assert!(
wat_text.contains("$alloc_databox"),
"Should contain DataBox allocator"
);
assert!(
wat_text.contains("call $alloc_databox"),
"Should call DataBox allocator"
);
assert!(wat_text.contains("i32.store"), "Should store field values");
// Compile to WASM binary and execute
@ -58,7 +70,11 @@ fn test_wasm_poc2_refnew_basic() {
let return_value = execution_result.unwrap();
// Should return a valid pointer (greater than heap start 0x800)
assert!(return_value >= 0x800, "Should return valid Box pointer: {}", return_value);
assert!(
return_value >= 0x800,
"Should return valid Box pointer: {}",
return_value
);
}
#[test]
@ -123,9 +139,9 @@ fn build_refnew_mir_module() -> MirModule {
let mut main_function = MirFunction::new(main_signature, entry_block);
let mut block = BasicBlock::new(entry_block);
let init_val = ValueId::new(0); // 42
let box_ptr = ValueId::new(1); // DataBox pointer
let ref_ptr = ValueId::new(2); // Reference to DataBox
let init_val = ValueId::new(0); // 42
let box_ptr = ValueId::new(1); // DataBox pointer
let ref_ptr = ValueId::new(2); // Reference to DataBox
// Create constant for initialization
block.add_instruction(MirInstruction::Const {
@ -172,11 +188,11 @@ fn build_refget_refset_mir_module() -> MirModule {
let mut main_function = MirFunction::new(main_signature, entry_block);
let mut block = BasicBlock::new(entry_block);
let init_val = ValueId::new(0); // 10
let new_val = ValueId::new(1); // 42
let box_ptr = ValueId::new(2); // DataBox pointer
let ref_ptr = ValueId::new(3); // Reference to DataBox
let result = ValueId::new(4); // Read back value
let init_val = ValueId::new(0); // 10
let new_val = ValueId::new(1); // 42
let box_ptr = ValueId::new(2); // DataBox pointer
let ref_ptr = ValueId::new(3); // Reference to DataBox
let result = ValueId::new(4); // Read back value
// Create constants
block.add_instruction(MirInstruction::Const {
@ -242,16 +258,16 @@ fn build_complete_workflow_mir_module() -> MirModule {
let mut main_function = MirFunction::new(main_signature, entry_block);
let mut block = BasicBlock::new(entry_block);
let val1_init = ValueId::new(0); // 100
let val2_init = ValueId::new(1); // 200
let box1_ptr = ValueId::new(2); // DataBox 1 pointer
let box2_ptr = ValueId::new(3); // DataBox 2 pointer
let ref1_ptr = ValueId::new(4); // Reference to DataBox 1
let ref2_ptr = ValueId::new(5); // Reference to DataBox 2
let val1 = ValueId::new(6); // Value from box1
let val2 = ValueId::new(7); // Value from box2
let sum = ValueId::new(8); // Sum of values
let result = ValueId::new(9); // Final result
let val1_init = ValueId::new(0); // 100
let val2_init = ValueId::new(1); // 200
let box1_ptr = ValueId::new(2); // DataBox 1 pointer
let box2_ptr = ValueId::new(3); // DataBox 2 pointer
let ref1_ptr = ValueId::new(4); // Reference to DataBox 1
let ref2_ptr = ValueId::new(5); // Reference to DataBox 2
let val1 = ValueId::new(6); // Value from box1
let val2 = ValueId::new(7); // Value from box2
let sum = ValueId::new(8); // Sum of values
let result = ValueId::new(9); // Final result
// Create constants
block.add_instruction(MirInstruction::Const {

View File

@ -9,11 +9,11 @@
* - WAT generation includes data segments and string allocation
*/
use nyash_rust::mir::{
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue
};
use nyash_rust::backend::wasm::WasmBackend;
use nyash_rust::mir::{
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction,
MirInstruction, MirModule, MirType, ValueId,
};
#[test]
fn test_wasm_string_constant_basic() {
@ -28,17 +28,38 @@ fn test_wasm_string_constant_basic() {
// Generate WAT text for debugging
let wat_result = backend.compile_to_wat(mir_module.clone());
assert!(wat_result.is_ok(), "WAT generation should succeed for string constants");
assert!(
wat_result.is_ok(),
"WAT generation should succeed for string constants"
);
let wat_text = wat_result.unwrap();
// Verify WAT contains expected elements for string support
assert!(wat_text.contains("(module"), "Should contain module declaration");
assert!(wat_text.contains("memory"), "Should contain memory declaration");
assert!(wat_text.contains("data"), "Should contain data segment for string literal");
assert!(wat_text.contains("\\48\\65\\6c\\6c\\6f"), "Should contain UTF-8 bytes for 'Hello'");
assert!(wat_text.contains("$alloc_stringbox"), "Should contain StringBox allocator");
assert!(wat_text.contains("print_str"), "Should contain print_str import");
assert!(
wat_text.contains("(module"),
"Should contain module declaration"
);
assert!(
wat_text.contains("memory"),
"Should contain memory declaration"
);
assert!(
wat_text.contains("data"),
"Should contain data segment for string literal"
);
assert!(
wat_text.contains("\\48\\65\\6c\\6c\\6f"),
"Should contain UTF-8 bytes for 'Hello'"
);
assert!(
wat_text.contains("$alloc_stringbox"),
"Should contain StringBox allocator"
);
assert!(
wat_text.contains("print_str"),
"Should contain print_str import"
);
// Verify string literal is properly embedded
// (The assertion for UTF-8 bytes is above)
@ -49,7 +70,10 @@ fn test_wasm_string_constant_basic() {
println!("WASM compilation error: {}", e);
println!("Generated WAT:\n{}", wat_text);
}
assert!(wasm_result.is_ok(), "WASM compilation should succeed for string constants");
assert!(
wasm_result.is_ok(),
"WASM compilation should succeed for string constants"
);
}
#[test]
@ -66,20 +90,35 @@ fn test_wasm_string_constant_multiple() {
let mir_module = build_multiple_string_const_mir_module();
let wat_result = backend.compile_to_wat(mir_module.clone());
assert!(wat_result.is_ok(), "WAT generation should succeed for multiple strings");
assert!(
wat_result.is_ok(),
"WAT generation should succeed for multiple strings"
);
let wat_text = wat_result.unwrap();
// Should contain both unique strings (in hex format)
assert!(wat_text.contains("\\46\\69\\72\\73\\74"), "Should contain 'First' string in hex");
assert!(wat_text.contains("\\53\\65\\63\\6f\\6e\\64"), "Should contain 'Second' string in hex");
assert!(
wat_text.contains("\\46\\69\\72\\73\\74"),
"Should contain 'First' string in hex"
);
assert!(
wat_text.contains("\\53\\65\\63\\6f\\6e\\64"),
"Should contain 'Second' string in hex"
);
// Should have 2 data segments (First and Second, duplicate First reused)
let data_count = wat_text.matches("(data").count();
assert_eq!(data_count, 2, "Should have exactly 2 data segments for 2 unique strings");
assert_eq!(
data_count, 2,
"Should have exactly 2 data segments for 2 unique strings"
);
let wasm_result = backend.compile_module(mir_module);
assert!(wasm_result.is_ok(), "WASM compilation should succeed for multiple strings");
assert!(
wasm_result.is_ok(),
"WASM compilation should succeed for multiple strings"
);
}
/// Build a MIR module with a single string constant

View File

@ -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

View File

@ -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

View 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

View File

@ -2,9 +2,10 @@
# Nyash dev environment convenience script
# Usage: source tools/dev_env.sh [profile]
# Profiles:
# pyvm - Favor PyVM for VM and Bridge
# bridge - Bridge-only helpers (keep interpreter)
# reset - Unset variables set by this script
# pyvm - Favor PyVM for VM and Bridge
# bridge - Bridge-only helpers (keep interpreter)
# phi_off - PHI-less MIR (edge-copy) + verifier relax; harness on
# reset - Unset variables set by this script
set -euo pipefail
@ -28,13 +29,23 @@ reset_env() {
unset NYASH_PIPE_USE_PYVM || true
unset NYASH_DISABLE_PLUGINS || true
unset NYASH_NY_COMPILER_TIMEOUT_MS || true
unset NYASH_MIR_NO_PHI || true
unset NYASH_VERIFY_ALLOW_NO_PHI || true
unset NYASH_LLVM_USE_HARNESS || true
echo "[dev-env] environment reset" >&2
}
activate_phi_off() {
export NYASH_MIR_NO_PHI=1
export NYASH_VERIFY_ALLOW_NO_PHI=1
export NYASH_LLVM_USE_HARNESS=1
echo "[dev-env] PHI-off (edge-copy) profile activated (harness on)" >&2
}
case "${1:-pyvm}" in
pyvm) activate_pyvm ;;
bridge) activate_bridge ;;
phi_off) activate_phi_off ;;
reset) reset_env ;;
*) echo "usage: source tools/dev_env.sh [pyvm|bridge|reset]" >&2 ;;
*) echo "usage: source tools/dev_env.sh [pyvm|bridge|phi_off|reset]" >&2 ;;
esac

View File

@ -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

View File

@ -47,7 +47,7 @@ cargo build -q ${MODE:+--${MODE}} --features llvm
echo "[llvm-smoke] running --backend llvm on examples/llvm11_core_smoke.nyash ..." >&2
rm -f "$OBJ"
NYASH_LLVM_OBJ_OUT="$OBJ" "$BIN" --backend llvm examples/llvm11_core_smoke.nyash >/dev/null || true
NYASH_LLVM_USE_HARNESS=1 NYASH_LLVM_OBJ_OUT="$OBJ" "$BIN" --backend llvm examples/llvm11_core_smoke.nyash >/dev/null || true
if [[ ! -f "$OBJ" ]]; then
echo "error: expected object not found: $OBJ" >&2
@ -60,6 +60,28 @@ fi
echo "[llvm-smoke] OK: object generated: $OBJ ($(stat -c%s "$OBJ") bytes)" >&2
# --- Stage-3 loop control smoke (break/continue) ---
if [[ "${NYASH_LLVM_STAGE3_SMOKE:-0}" == "1" ]]; then
echo "[llvm-smoke] building + linking apps/tests/llvm_stage3_loop_only.nyash ..." >&2
OBJ_STAGE3="$PWD/target/aot_objects/stage3_loop_smoke.o"
rm -f "$OBJ_STAGE3"
# Loop-only case: harness should succeed (no exceptions in IR)
NYASH_LLVM_USE_HARNESS=1 NYASH_LLVM_OBJ_OUT="$OBJ_STAGE3" \
"$BIN" --backend llvm apps/tests/llvm_stage3_loop_only.nyash >/dev/null || true
NYASH_LLVM_SKIP_EMIT=1 NYASH_LLVM_OBJ_OUT="$OBJ_STAGE3" \
./tools/build_llvm.sh apps/tests/llvm_stage3_loop_only.nyash -o app_stage3_loop >/dev/null || true
echo "[llvm-smoke] running app_stage3_loop ..." >&2
out_stage3=$(./app_stage3_loop || true)
echo "[llvm-smoke] output: $out_stage3" >&2
if ! echo "$out_stage3" | grep -q "Result: 3"; then
echo "error: stage3 loop smoke unexpected output: $out_stage3" >&2
exit 1
fi
echo "[llvm-smoke] OK: Stage-3 break/continue smoke passed" >&2
else
echo "[llvm-smoke] skipping Stage-3 loop smoke (set NYASH_LLVM_STAGE3_SMOKE=1 to enable)" >&2
fi
# --- AOT smoke: apps/ny-llvm-smoke (Array get/set/print) ---
if [[ "${NYASH_LLVM_ARRAY_SMOKE:-0}" == "1" ]]; then
echo "[llvm-smoke] building + linking apps/ny-llvm-smoke ..." >&2

View 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

View 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