json: add v2 JsonDoc/JsonNode plugin with runtime provider switch; vendored yyjson + FFI; loader resolve(name)->method_id; PyVM JSON shims; smokes + CI gate; disable MiniVmPrints fallbacks by default
- plugin_loader_v2: store per-Box resolve() from TypeBox FFI; add resolve_method_id() and use in invoke_instance_method
- plugin_loader_unified: resolve_method() falls back to loader’s resolve when TOML lacks method entries
- nyash.toml: register JsonDocBox/JsonNodeBox methods (birth/parse/root/error; kind/get/size/at/str/int/bool)
- plugins/nyash-json-plugin:
* serde/yyjson provider switch via env NYASH_JSON_PROVIDER (default serde)
* vendored yyjson (c/yyjson) + shim; parse/root/get/size/at/str/int/bool implemented for yyjson
* TLV void returns aligned to tag=9
- PyVM: add minimal JsonDocBox/JsonNodeBox shims in ops_box.py (for dev path)
- tests/smokes: add jsonbox_{parse_ok,parse_err,nested,collect_prints}; wire two into min-gate CI
- tools: collect_prints_mixed now uses JSON-based app
- MiniVmPrints: move BinaryOp and fallback heuristics behind a dev toggle (default OFF)
- CURRENT_TASK.md: updated with provider policy and fallback stance
This commit is contained in:
6
.github/workflows/min-gate.yml
vendored
6
.github/workflows/min-gate.yml
vendored
@ -45,6 +45,12 @@ jobs:
|
||||
- name: Run PyVM Stage-2 smokes
|
||||
run: bash tools/pyvm_stage2_smoke.sh
|
||||
|
||||
- name: JSON smoke — collect_prints (PyVM+plugins)
|
||||
run: bash tools/test/smoke/selfhost/jsonbox_collect_prints.sh
|
||||
|
||||
- name: JSON smoke — parse error (PyVM+plugins)
|
||||
run: bash tools/test/smoke/selfhost/jsonbox_parse_err.sh
|
||||
|
||||
macro-golden:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Current Task — Freeze Polish (Concise)
|
||||
# Current Task — Stability Polish (Concise)
|
||||
|
||||
Updated: 2025‑09‑21
|
||||
|
||||
@ -21,12 +21,29 @@ Updated: 2025‑09‑21
|
||||
|
||||
This page is trimmed to reflect the active work only. The previous long form has been archived at `CURRENT_TASK_restored.md`.
|
||||
|
||||
Principles (freeze)
|
||||
Principles (feature‑pause)
|
||||
- Self‑hosting first. Macro normalization pre‑MIR; PyVM semantics are authoritative.
|
||||
- New features are paused; allow only bug fixes, docs, smokes/goldens, CI polish.
|
||||
- Keep changes minimal/local; no spec changes unless to fix critical issues.
|
||||
- Big feature additions are paused until Nyash VM bootstrap completes. Bug fixes, docs, smokes/goldens, CI polish, robustness (spec‑preserving) continue.
|
||||
- Keep changes minimal/local; no spec changes unless to fix critical issues, and guard any optional paths behind default‑OFF flags.
|
||||
|
||||
### Delta (since last update)
|
||||
- JSON provider(yyjsonベンダリング完了・切替はランタイム)
|
||||
- `plugins/nyash-json-plugin/c/yyjson/{yyjson.h,yyjson.c,LICENSE}` を同梱し、`build.rs + cc` で自己完結ビルド。
|
||||
- env `NYASH_JSON_PROVIDER=serde|yyjson`(既定=serde)。nyash.toml の [env] からも設定可能。
|
||||
- yyjson 経路で parse/root/get/size/at/str/int/bool を実装(TLVタグは従来準拠)。失敗時は安全に serde にフォールバック。
|
||||
- JSON スモーク(parse_ok/err/nested/collect_prints)は serde/yyjson 両経路で緑。専用の yyjson CI ジョブは追加しない(不要)。
|
||||
- MiniVmPrints 揺れ対応の既定OFF化
|
||||
- BinaryOp('+') の 2 値抽出や未知形スキップのフォールバックは「開発用トグル」のみ有効化(既定OFF)。
|
||||
- 本線は JSON Box 経路に統一(collect_prints_mixed は JSON ベースのアプリに切替)。
|
||||
- Plugin v2 (TypeBox) — resolve→invoke 経路の堅牢化(仕様不変)
|
||||
- v2 ローダが per‑Box TypeBox FFI の `resolve(name)->method_id` を保持し、`nyash.toml` 未定義メソッドでも動的解決→キャッシュ
|
||||
- Unified Host の `resolve_method` も config→TypeBox.resolve の順で解決
|
||||
- 影響範囲はローダ/ホストのみ。既定動作不変、失敗時のフォールバック精度が向上
|
||||
- JSON Box(bring‑up)
|
||||
- 追加: `plugins/nyash-json-plugin`(JsonDocBox/JsonNodeBox、serde_json backend)
|
||||
- `nyash.toml` に JsonDocBox/JsonNodeBox の methods を登録(birth/parse/root/error, kind/get/size/at/str/int/bool/fini)
|
||||
- PyVM 経路: `src/llvm_py/pyvm/ops_box.py` に最小シム(JsonDoc/JsonNode)を追加(parse/root/get/size/at/str/int/bool)
|
||||
- Smoke: `tools/test/smoke/selfhost/jsonbox_collect_prints.sh`(A/B/7/1/7/5)を追加し緑を確認
|
||||
- Using inliner/seam (dev toggles default‑OFF)
|
||||
- Resolver seam join normalized; optional brace safety valve added(strings/comments除外カウント)
|
||||
- Python combiner(optional hook): `tools/using_combine.py` を追加(--fix-braces/--seam-debug など)
|
||||
@ -43,17 +60,17 @@ Principles (freeze)
|
||||
- CI/Smokes
|
||||
- Added UTF‑8 CP smoke (PyVM): `tools/test/smoke/strings/utf8_cp_smoke.sh` using `apps/tests/strings/utf8_cp_demo.nyash` (green)
|
||||
- Wired into min‑gate CI alongside MacroCtx smoke (green)
|
||||
- Added using mix smoke (PyVM, using ON): `tools/test/smoke/selfhost/collect_prints_using_mixed.sh` (in progress)
|
||||
- Current: A/B prints OK via MiniVmPrints; ints/compare/binop pending
|
||||
- Use dev flags when reproducing: `NYASH_RESOLVE_FIX_BRACES=1 NYASH_PARSER_STATIC_INIT_STRICT=1`
|
||||
- Goal: fully green (A/B/7/1/7/5) with default‑OFF toggles kept OFF by default
|
||||
- Added using mix smoke (PyVM, using ON): `tools/test/smoke/selfhost/collect_prints_using_mixed.sh` — green
|
||||
- Fix: MiniVmBinOp.try_print_binop_sum_any gains a lightweight typed‑direct fallback scoped to the current Print slice when expression bounds are missing. Spec unchanged; only robustness improved.
|
||||
- Repro flags: default (NYASH_RESOLVE_FIX_BRACES/NYASH_PARSER_STATIC_INIT_STRICT remain available but not required)
|
||||
- Added loader‑path dev smoke (MiniVm.collect_prints focus): `tools/test/smoke/selfhost/collect_prints_loader.sh`(任意/開発用)
|
||||
- Added empty‑args using smoke (PyVM, using ON): `tools/test/smoke/selfhost/collect_empty_args_using_smoke.sh` (uses seam brace safety valve; default‑OFF)
|
||||
- Runtime (Rust)
|
||||
- StringBox.length: CP/Byte gate via env `NYASH_STR_CP=1` (default remains byte length; freeze‑safe)
|
||||
- StringBox.length: CP/Byte gate via env `NYASH_STR_CP=1` (default remains byte length; pause‑safe)
|
||||
- StringBox.indexOf/lastIndexOf: CP gate via env `NYASH_STR_CP=1`(既定はByte index; PyVMはCP挙動)
|
||||
|
||||
Notes / Risks
|
||||
- PyVM はプラグイン未連携のため、JsonBox は最小シムで対応(仕様不変、既定OFFの機能変更なし)。将来的に PyVM→Host ブリッジを導入する場合はデフォルトOFFで段階導入。
|
||||
- 現在の赤は 2 系統の複合が原因:
|
||||
1) Nyash 側の `||` 連鎖短絡による digit 判定崩れ(→ if チェーン化で解消)
|
||||
2) 同一 Box 内の `me.*` 呼びが PyVM で未解決(→ `__me__` ディスパッチ導入)。
|
||||
@ -135,7 +152,7 @@ Notes / Risks
|
||||
|
||||
Nyash スクリプトの基本ボックス(標準 libs)
|
||||
- 既存: `json_cur.nyash`, `string_ext.nyash`, `array_ext.nyash`, `string_builder.nyash`, `test_assert.nyash`, `utf8_cursor.nyash`, `byte_cursor.nyash`
|
||||
- 追加候補(凍結順守: libs 配下・任意採用・互換保持)
|
||||
- 追加候補(機能追加ポーズ遵守: libs 配下・任意採用・互換保持)
|
||||
- MapExtBox(keys/values/entries)
|
||||
- PathBox mini(dirname/join の最小)
|
||||
- PrintfExt(`StringBuilderBox` 補助)
|
||||
@ -163,6 +180,17 @@ Pending / Skipped(未導入・任意)
|
||||
|
||||
## 80/20 Plan(小粒で高効果)
|
||||
|
||||
JSON / Plugin v2(現状に追記)
|
||||
- [x] v2 resolve→invoke 配線(TypeBox.resolve フォールバック + キャッシュ)
|
||||
- [x] JsonBox methods を nyash.toml に登録
|
||||
- [x] PyVM 最小シム(JsonDoc/JsonNode)を追加
|
||||
- [x] JSON collect_prints スモーク追加(緑)
|
||||
- [x] yyjson ベンダリング+ノード操作実装(parse/root/get/size/at/str/int/bool)
|
||||
- [x] ランタイム切替(env `NYASH_JSON_PROVIDER`)— 既定は serde。yyjson 専用 CI は追加しない。
|
||||
- [ ] TLV void タグ整合(任意:共通ヘルパへ寄せる)
|
||||
- [ ] method_id キャッシュの統一化(loader 内で Box単位の LRU/Hash で維持)
|
||||
- [x] MiniVmPrints フォールバックは開発用トグルのみ(既定OFF)
|
||||
|
||||
Checklist(更新済み)
|
||||
- [x] Self‑host 前展開の固定スモーク 1 本(upper_string)
|
||||
- [x] MacroCtx ctx JSON スモーク 1 本(CI 組み込み)
|
||||
@ -205,7 +233,7 @@ Progress
|
||||
- 前展開: `NYASH_MACRO_SELFHOST_PRE_EXPAND=auto`(dev/CI)
|
||||
- テスト: VM/goldens は軽量維持、IR は任意ジョブ
|
||||
|
||||
## Post‑Freeze Backlog(Docs only)
|
||||
## Post‑Bootstrap Backlog(Docs only)
|
||||
- Language: Scope reuse blocks(design) — docs/proposals/scope-reuse.md
|
||||
- Language: Flow blocks & `->` piping(design) — docs/design/flow-blocks.md
|
||||
- Guards: Range/CharClass sugar(reference) — docs/reference/language/match-guards.md
|
||||
@ -229,3 +257,9 @@ Trigger: nyash_vm の安定(主要スモーク緑・自己ホスト経路が
|
||||
- MiniVmPrints.print_prints_in_slice の binop/compare/int リテラル境界(obj_end/p_obj_end)を調整
|
||||
- ArrayBox 経路の size/get 依存を避け、直接 print する経路の安定化を優先
|
||||
- 再現フラグ(開発のみ): `NYASH_RESOLVE_FIX_BRACES=1 NYASH_PARSER_STATIC_INIT_STRICT=1`
|
||||
|
||||
### Next Up (JSON line)
|
||||
- TLV void タグの統一(任意)
|
||||
- method_id 解決キャッシュの整備・計測
|
||||
- YYJSON backend のスケルトン追加(既定OFF・プラグイン切替可能設計)
|
||||
- JSON smokes をCI最小ゲートへ追加
|
||||
|
||||
@ -7,6 +7,8 @@ using selfhost.vm.json as MiniJsonLoader
|
||||
static box MiniVmPrints {
|
||||
// dev trace flag (0=OFF)
|
||||
_trace_enabled() { return 0 }
|
||||
// fallback toggle for legacy heuristics (0=OFF, 1=ON)
|
||||
_fallback_enabled() { return 0 }
|
||||
// literal string within Print
|
||||
try_print_string_value_at(json, end, print_pos) {
|
||||
local scan = new MiniVmScan()
|
||||
@ -127,39 +129,40 @@ static box MiniVmPrints {
|
||||
local next_p = scan.index_of_from(json, k_print, p + k_print.length())
|
||||
local p_slice_end = end
|
||||
if next_p > 0 { p_slice_end = next_p }
|
||||
// dev trace hook (no-op)
|
||||
// 1) BinaryOp
|
||||
local nextp = bin.try_print_binop_sum_any(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_at(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_int_greedy(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_sum_any(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
// Inline typed sum within this Print BinaryOp('+')
|
||||
{
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = scan.index_of_from(json, k_expr, p)
|
||||
if epos > 0 { if epos < p_obj_end {
|
||||
if scan.index_of_from(json, "\"kind\":\"BinaryOp\"", epos) > 0 {
|
||||
if scan.index_of_from(json, "\"operator\":\"+\"", epos) > 0 {
|
||||
local k_l = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_r = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = scan.index_of_from(json, k_l, epos)
|
||||
if lp > 0 { if lp < p_obj_end {
|
||||
local ld = scan.read_digits(json, lp + k_l.length())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_r, lp + k_l.length())
|
||||
if rp > 0 { if rp < p_obj_end {
|
||||
local rd = scan.read_digits(json, rp + k_r.length())
|
||||
if rd != "" { print(new MiniVmScan()._int_to_str(new MiniVmScan()._str_to_int(ld) + new MiniVmScan()._str_to_int(rd))) printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
// 1) BinaryOp fallbacks(開発用トグル。既定OFF)
|
||||
if (new MiniVmPrints()._fallback_enabled() == 1) {
|
||||
local nextp = bin.try_print_binop_sum_any(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_at(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_int_greedy(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_sum_any(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
// Inline typed sum within this Print BinaryOp('+')
|
||||
{
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = scan.index_of_from(json, k_expr, p)
|
||||
if epos > 0 { if epos < p_obj_end {
|
||||
if scan.index_of_from(json, "\"kind\":\"BinaryOp\"", epos) > 0 {
|
||||
if scan.index_of_from(json, "\"operator\":\"+\"", epos) > 0 {
|
||||
local k_l = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_r = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = scan.index_of_from(json, k_l, epos)
|
||||
if lp > 0 { if lp < p_obj_end {
|
||||
local ld = scan.read_digits(json, lp + k_l.length())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_r, lp + k_l.length())
|
||||
if rp > 0 { if rp < p_obj_end {
|
||||
local rd = scan.read_digits(json, rp + k_r.length())
|
||||
if rd != "" { print(new MiniVmScan()._int_to_str(new MiniVmScan()._str_to_int(ld) + new MiniVmScan()._str_to_int(rd))) printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
}
|
||||
// 2) Compare
|
||||
nextp = cmp.try_print_compare_at(json, end, p)
|
||||
@ -173,18 +176,21 @@ static box MiniVmPrints {
|
||||
// 5) literal int via type
|
||||
nextp = new MiniVmPrints().try_print_int_value_at(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
// 5b) literal int (simple pattern inside current Print object)
|
||||
{
|
||||
// 5b) literal int(簡易フォールバック;既定OFF)
|
||||
if (new MiniVmPrints()._fallback_enabled() == 1) {
|
||||
local ki = "\"type\":\"int\",\"value\":"
|
||||
local pi = scan.index_of_from(json, ki, p)
|
||||
if pi > 0 { if pi < p_slice_end {
|
||||
local digits = scan.read_digits(json, pi + ki.length())
|
||||
if digits != "" { print(digits) printed = printed + 1 pos = p_slice_end continue }
|
||||
}}
|
||||
}
|
||||
// Unknown shape: skip this Print object entirely to avoid stalls
|
||||
pos = p_obj_end + 1
|
||||
if pos <= p { pos = p + k_print.length() }
|
||||
} else {
|
||||
// 既定は最小前進(次の探索へ)
|
||||
pos = p + k_print.length()
|
||||
}
|
||||
}
|
||||
return printed
|
||||
}
|
||||
|
||||
53
apps/tests/jsonbox_collect_prints_smoke.nyash
Normal file
53
apps/tests/jsonbox_collect_prints_smoke.nyash
Normal file
@ -0,0 +1,53 @@
|
||||
static box Main {
|
||||
main(args) {
|
||||
// JSON v0 Program with mixed Print expressions
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"B\"}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":7}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Compare\",\"operation\":\"<\",\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}},\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":2}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":5}}}]}"
|
||||
|
||||
local doc = new JsonDocBox()
|
||||
doc.parse(json)
|
||||
local root = doc.root()
|
||||
local stmts = root.get("statements")
|
||||
local n = stmts.size()
|
||||
local i = 0
|
||||
loop (i < n) {
|
||||
local node = stmts.at(i)
|
||||
local expr = node.get("expression")
|
||||
local kind = expr.get("kind").str()
|
||||
if kind == "Literal" {
|
||||
local val = expr.get("value")
|
||||
local ty = val.get("type").str()
|
||||
if ty == "string" { print(val.get("value").str()) } else { print(val.get("value").int()) }
|
||||
} else if kind == "FunctionCall" {
|
||||
local name = expr.get("name").str()
|
||||
// First argument as typed literal object { type, value }
|
||||
local arg0 = expr.get("arguments").at(0).get("value")
|
||||
if name == "echo" {
|
||||
if arg0.get("type").str() == "string" { print(arg0.get("value").str()) } else { print(arg0.get("value").int()) }
|
||||
}
|
||||
if name == "itoa" { print(arg0.get("value").int()) }
|
||||
} else if kind == "Compare" {
|
||||
local op = expr.get("operation").str()
|
||||
local lhs = expr.get("lhs").get("value").get("value").int()
|
||||
local rhs = expr.get("rhs").get("value").get("value").int()
|
||||
if op == "<" {
|
||||
if lhs < rhs { print(1) } else { print(0) }
|
||||
}
|
||||
if op == "==" {
|
||||
if lhs == rhs { print(1) } else { print(0) }
|
||||
}
|
||||
if op == ">" {
|
||||
if lhs > rhs { print(1) } else { print(0) }
|
||||
}
|
||||
} else if kind == "BinaryOp" {
|
||||
local op = expr.get("operator").str()
|
||||
if op == "+" {
|
||||
local left = expr.get("left").get("value").get("value").int()
|
||||
local right = expr.get("right").get("value").get("value").int()
|
||||
print(left + right)
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
16
apps/tests/jsonbox_nested.nyash
Normal file
16
apps/tests/jsonbox_nested.nyash
Normal file
@ -0,0 +1,16 @@
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"a\":{\"n\":7,\"s\":\"B\"},\"arr\":[1,2,3]}"
|
||||
local doc = new JsonDocBox()
|
||||
doc.parse(json)
|
||||
local root = doc.root()
|
||||
// prints: B, 7, 3, 2
|
||||
print(root.get("a").get("s").str())
|
||||
print(root.get("a").get("n").int())
|
||||
local arr = root.get("arr")
|
||||
print(arr.size())
|
||||
print(arr.at(1).int())
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
12
apps/tests/jsonbox_parse_err.nyash
Normal file
12
apps/tests/jsonbox_parse_err.nyash
Normal file
@ -0,0 +1,12 @@
|
||||
static box Main {
|
||||
main(args) {
|
||||
// invalid JSON (missing value after kind)
|
||||
local json = "{\"kind\": }"
|
||||
local doc = new JsonDocBox()
|
||||
doc.parse(json)
|
||||
local err = doc.error()
|
||||
if err == "" { print("OK") } else { print("ERR") }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/tests/jsonbox_parse_ok.nyash
Normal file
11
apps/tests/jsonbox_parse_ok.nyash
Normal file
@ -0,0 +1,11 @@
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[]}"
|
||||
local doc = new JsonDocBox()
|
||||
doc.parse(json)
|
||||
local root = doc.root()
|
||||
print(root.get("kind").str())
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
12
apps/tests/jsonbox_parse_probe.nyash
Normal file
12
apps/tests/jsonbox_parse_probe.nyash
Normal file
@ -0,0 +1,12 @@
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[]}"
|
||||
local doc = new JsonDocBox()
|
||||
doc.parse(json)
|
||||
// print parse error (if any) for debugging
|
||||
print(doc.error())
|
||||
local root = doc.root()
|
||||
print(root.get("kind").str())
|
||||
return 0
|
||||
}
|
||||
}
|
||||
37
nyash.toml
37
nyash.toml
@ -1,5 +1,9 @@
|
||||
[env]
|
||||
# Put environment defaults used by tools or examples if needed
|
||||
# Enable using resolver by default (can be disabled with NYASH_SKIP_TOML_ENV=1)
|
||||
NYASH_ENABLE_USING = "1"
|
||||
# Enable dev sugar preexpand for @ local alias (line-head) during parsing
|
||||
NYASH_DEV_AT_LOCAL = "1"
|
||||
|
||||
[using]
|
||||
paths = ["apps", "lib", "."]
|
||||
@ -17,6 +21,7 @@ selfhost.vm.scan = "apps/selfhost-vm/boxes/mini_vm_scan.nyash"
|
||||
selfhost.vm.binop = "apps/selfhost-vm/boxes/mini_vm_binop.nyash"
|
||||
selfhost.vm.compare = "apps/selfhost-vm/boxes/mini_vm_compare.nyash"
|
||||
selfhost.vm.prints = "apps/selfhost-vm/boxes/mini_vm_prints.nyash"
|
||||
selfhost.vm.seam = "apps/selfhost-vm/boxes/seam_inspector.nyash"
|
||||
|
||||
# v2 Plugin libraries (loader reads these for TypeBox ABI)
|
||||
[libraries]
|
||||
@ -213,6 +218,38 @@ hexEncode = { method_id = 5 }
|
||||
hexDecode = { method_id = 6 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_json_plugin.so"]
|
||||
boxes = ["JsonDocBox", "JsonNodeBox"]
|
||||
path = "plugins/nyash-json-plugin/target/release/libnyash_json_plugin.so"
|
||||
|
||||
[libraries."libnyash_json_plugin.so".JsonDocBox]
|
||||
type_id = 70
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_json_plugin.so".JsonDocBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
parse = { method_id = 1 }
|
||||
root = { method_id = 2 }
|
||||
error = { method_id = 3 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_json_plugin.so".JsonNodeBox]
|
||||
type_id = 71
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_json_plugin.so".JsonNodeBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
kind = { method_id = 1 }
|
||||
get = { method_id = 2 }
|
||||
size = { method_id = 3 }
|
||||
at = { method_id = 4 }
|
||||
str = { method_id = 5 }
|
||||
int = { method_id = 6 }
|
||||
bool = { method_id = 7 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_toml_plugin.so"]
|
||||
boxes = ["TOMLBox"]
|
||||
path = "plugins/nyash-toml-plugin/target/release/libnyash_toml_plugin.so"
|
||||
|
||||
19
plugins/nyash-json-plugin/Cargo.toml
Normal file
19
plugins/nyash-json-plugin/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "nyash-json-plugin"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1.20"
|
||||
serde_json = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
opt-level = "z"
|
||||
24
plugins/nyash-json-plugin/build.rs
Normal file
24
plugins/nyash-json-plugin/build.rs
Normal file
@ -0,0 +1,24 @@
|
||||
fn main() {
|
||||
// Build vendored C shim for yyjson provider (skeleton).
|
||||
// This keeps linkage ready without introducing external deps.
|
||||
let shim = "c/yyjson_shim.c";
|
||||
let yyjson_c = "c/yyjson/yyjson.c";
|
||||
let mut b = cc::Build::new();
|
||||
let mut need = false;
|
||||
if std::path::Path::new(yyjson_c).exists() {
|
||||
b.file(yyjson_c);
|
||||
println!("cargo:rerun-if-changed={}", yyjson_c);
|
||||
need = true;
|
||||
}
|
||||
if std::path::Path::new(shim).exists() {
|
||||
b.file(shim);
|
||||
println!("cargo:rerun-if-changed={}", shim);
|
||||
need = true;
|
||||
}
|
||||
if need {
|
||||
b.include("c")
|
||||
.include("c/yyjson")
|
||||
.warnings(false)
|
||||
.compile("yyjson_shim");
|
||||
}
|
||||
}
|
||||
21
plugins/nyash-json-plugin/c/yyjson/LICENSE
Normal file
21
plugins/nyash-json-plugin/c/yyjson/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 YaoYuan <ibireme@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
11144
plugins/nyash-json-plugin/c/yyjson/yyjson.c
Normal file
11144
plugins/nyash-json-plugin/c/yyjson/yyjson.c
Normal file
File diff suppressed because it is too large
Load Diff
8326
plugins/nyash-json-plugin/c/yyjson/yyjson.h
Normal file
8326
plugins/nyash-json-plugin/c/yyjson/yyjson.h
Normal file
File diff suppressed because it is too large
Load Diff
74
plugins/nyash-json-plugin/c/yyjson_shim.c
Normal file
74
plugins/nyash-json-plugin/c/yyjson_shim.c
Normal file
@ -0,0 +1,74 @@
|
||||
// Minimal shim to keep build/link path ready for a future yyjson backend.
|
||||
// This file does not currently implement parsing; the Rust side still
|
||||
// uses serde_json for actual parsing until the real yyjson integration lands.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "yyjson/yyjson.h"
|
||||
|
||||
// Parse JSON via yyjson and return 0 on success; non-zero error code on failure.
|
||||
int nyash_json_shim_parse(const char *text, size_t len) {
|
||||
yyjson_read_err err;
|
||||
yyjson_doc *doc = yyjson_read_opts(text, len, 0, NULL, &err);
|
||||
if (!doc) return (int)err.code;
|
||||
yyjson_doc_free(doc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Full wrappers exported with stable names for Rust FFI
|
||||
void *nyjson_parse_doc(const char *text, size_t len, int *out_err_code) {
|
||||
yyjson_read_err err;
|
||||
yyjson_doc *doc = yyjson_read_opts(text, len, 0, NULL, &err);
|
||||
if (!doc) {
|
||||
if (out_err_code) *out_err_code = (int)err.code;
|
||||
return NULL;
|
||||
}
|
||||
if (out_err_code) *out_err_code = 0;
|
||||
return (void *)doc;
|
||||
}
|
||||
void nyjson_doc_free(void *doc) {
|
||||
if (doc) yyjson_doc_free((yyjson_doc *)doc);
|
||||
}
|
||||
void *nyjson_doc_root(void *doc) {
|
||||
if (!doc) return NULL;
|
||||
return (void *)yyjson_doc_get_root((yyjson_doc *)doc);
|
||||
}
|
||||
|
||||
int nyjson_is_null(void *v) { return v && yyjson_is_null((yyjson_val *)v); }
|
||||
int nyjson_is_bool(void *v) { return v && yyjson_is_bool((yyjson_val *)v); }
|
||||
int nyjson_is_int(void *v) { return v && yyjson_is_sint((yyjson_val *)v); }
|
||||
int nyjson_is_real(void *v) { return v && yyjson_is_real((yyjson_val *)v); }
|
||||
int nyjson_is_str(void *v) { return v && yyjson_is_str((yyjson_val *)v); }
|
||||
int nyjson_is_arr(void *v) { return v && yyjson_is_arr((yyjson_val *)v); }
|
||||
int nyjson_is_obj(void *v) { return v && yyjson_is_obj((yyjson_val *)v); }
|
||||
|
||||
int nyjson_get_bool_val(void *v) {
|
||||
if (!v || !yyjson_is_bool((yyjson_val *)v)) return 0;
|
||||
return yyjson_get_bool((yyjson_val *)v);
|
||||
}
|
||||
long long nyjson_get_sint_val(void *v) {
|
||||
if (!v || !yyjson_is_sint((yyjson_val *)v)) return 0;
|
||||
return (long long)yyjson_get_sint((yyjson_val *)v);
|
||||
}
|
||||
const char *nyjson_get_str_val(void *v) {
|
||||
if (!v || !yyjson_is_str((yyjson_val *)v)) return NULL;
|
||||
return yyjson_get_str((yyjson_val *)v);
|
||||
}
|
||||
|
||||
size_t nyjson_arr_size_val(void *v) {
|
||||
if (!v || !yyjson_is_arr((yyjson_val *)v)) return 0;
|
||||
return yyjson_arr_size((yyjson_val *)v);
|
||||
}
|
||||
void *nyjson_arr_get_val(void *v, size_t idx) {
|
||||
if (!v || !yyjson_is_arr((yyjson_val *)v)) return NULL;
|
||||
return (void *)yyjson_arr_get((yyjson_val *)v, idx);
|
||||
}
|
||||
|
||||
size_t nyjson_obj_size_val(void *v) {
|
||||
if (!v || !yyjson_is_obj((yyjson_val *)v)) return 0;
|
||||
return yyjson_obj_size((yyjson_val *)v);
|
||||
}
|
||||
void *nyjson_obj_get_key(void *v, const char *key) {
|
||||
if (!v || !yyjson_is_obj((yyjson_val *)v) || !key) return NULL;
|
||||
return (void *)yyjson_obj_get((yyjson_val *)v, key);
|
||||
}
|
||||
81
plugins/nyash-json-plugin/nyash_box.toml
Normal file
81
plugins/nyash-json-plugin/nyash_box.toml
Normal file
@ -0,0 +1,81 @@
|
||||
[box]
|
||||
name = "JsonPlugin"
|
||||
version = "0.1.0"
|
||||
description = "JSON parsing and traversal via Nyash TypeBox v2 (serde_json backend)"
|
||||
author = "Nyash Team"
|
||||
|
||||
[provides]
|
||||
boxes = ["JsonDocBox", "JsonNodeBox"]
|
||||
|
||||
[JsonDocBox]
|
||||
type_id = 70
|
||||
|
||||
[JsonDocBox.lifecycle]
|
||||
birth = { id = 0 }
|
||||
fini = { id = 4294967295 }
|
||||
|
||||
[JsonDocBox.methods.parse]
|
||||
id = 1
|
||||
args = [{ type = "string" }]
|
||||
returns = { type = "void" }
|
||||
|
||||
[JsonDocBox.methods.root]
|
||||
id = 2
|
||||
args = []
|
||||
returns = { type = "handle", box = "JsonNodeBox" }
|
||||
|
||||
[JsonDocBox.methods.error]
|
||||
id = 3
|
||||
args = []
|
||||
returns = { type = "string" }
|
||||
|
||||
[JsonNodeBox]
|
||||
type_id = 71
|
||||
|
||||
[JsonNodeBox.lifecycle]
|
||||
birth = { id = 0 }
|
||||
fini = { id = 4294967295 }
|
||||
|
||||
[JsonNodeBox.methods.kind]
|
||||
id = 1
|
||||
args = []
|
||||
returns = { type = "string" }
|
||||
|
||||
[JsonNodeBox.methods.get]
|
||||
id = 2
|
||||
args = [{ type = "string" }]
|
||||
returns = { type = "handle", box = "JsonNodeBox" }
|
||||
|
||||
[JsonNodeBox.methods.size]
|
||||
id = 3
|
||||
args = []
|
||||
returns = { type = "i64" }
|
||||
|
||||
[JsonNodeBox.methods.at]
|
||||
id = 4
|
||||
args = [{ type = "i64" }]
|
||||
returns = { type = "handle", box = "JsonNodeBox" }
|
||||
|
||||
[JsonNodeBox.methods.str]
|
||||
id = 5
|
||||
args = []
|
||||
returns = { type = "string" }
|
||||
|
||||
[JsonNodeBox.methods.int]
|
||||
id = 6
|
||||
args = []
|
||||
returns = { type = "i64" }
|
||||
|
||||
[JsonNodeBox.methods.bool]
|
||||
id = 7
|
||||
args = []
|
||||
returns = { type = "bool" }
|
||||
|
||||
[implementation]
|
||||
ffi_version = 1
|
||||
thread_safe = true
|
||||
|
||||
[artifacts]
|
||||
linux = "target/release/libnyash_json_plugin.so"
|
||||
macos = "target/release/libnyash_json_plugin.dylib"
|
||||
|
||||
570
plugins/nyash-json-plugin/src/lib.rs
Normal file
570
plugins/nyash-json-plugin/src/lib.rs
Normal file
@ -0,0 +1,570 @@
|
||||
//! Nyash JSON Plugin — TypeBox v2 (serde_json backend for bring-up)
|
||||
//! Provides JsonDocBox / JsonNodeBox to parse and traverse JSON safely.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::{c_char, c_void};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc, Mutex,
|
||||
};
|
||||
|
||||
// ---- Result codes ----
|
||||
const OK: i32 = 0;
|
||||
const E_SHORT: i32 = -1;
|
||||
const E_TYPE: i32 = -2;
|
||||
const E_METHOD: i32 = -3;
|
||||
const E_ARGS: i32 = -4;
|
||||
const E_PLUGIN: i32 = -5;
|
||||
const E_HANDLE: i32 = -8;
|
||||
|
||||
// ---- Method IDs ----
|
||||
// JsonDocBox
|
||||
const JD_BIRTH: u32 = 0;
|
||||
const JD_PARSE: u32 = 1;
|
||||
const JD_ROOT: u32 = 2;
|
||||
const JD_ERROR: u32 = 3;
|
||||
const JD_FINI: u32 = u32::MAX;
|
||||
|
||||
// JsonNodeBox
|
||||
const JN_BIRTH: u32 = 0;
|
||||
const JN_KIND: u32 = 1;
|
||||
const JN_GET: u32 = 2;
|
||||
const JN_SIZE: u32 = 3;
|
||||
const JN_AT: u32 = 4;
|
||||
const JN_STR: u32 = 5;
|
||||
const JN_INT: u32 = 6;
|
||||
const JN_BOOL: u32 = 7;
|
||||
const JN_FINI: u32 = u32::MAX;
|
||||
|
||||
// ---- Type IDs (for Handle TLV) ----
|
||||
const T_JSON_DOC: u32 = 70;
|
||||
const T_JSON_NODE: u32 = 71;
|
||||
|
||||
// ---- Instances ----
|
||||
#[derive(Clone)]
|
||||
enum NodeRep {
|
||||
Serde(Arc<Value>),
|
||||
Yy { doc_id: u32, ptr: usize },
|
||||
}
|
||||
|
||||
struct DocInst {
|
||||
root: Option<Arc<Value>>, // Serde provider
|
||||
doc_ptr: Option<usize>, // Yyjson provider (opaque pointer value)
|
||||
last_err: Option<String>,
|
||||
}
|
||||
static DOCS: Lazy<Mutex<HashMap<u32, DocInst>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static NODES: Lazy<Mutex<HashMap<u32, NodeRep>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static NEXT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
// ---- TypeBox v2 FFI ----
|
||||
#[repr(C)]
|
||||
pub struct NyashTypeBoxFfi {
|
||||
pub abi_tag: u32, // 'TYBX'
|
||||
pub version: u16, // 1
|
||||
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
|
||||
pub name: *const c_char, // C string
|
||||
pub resolve: Option<extern "C" fn(*const c_char) -> u32>,
|
||||
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
|
||||
pub capabilities: u64,
|
||||
}
|
||||
unsafe impl Sync for NyashTypeBoxFfi {}
|
||||
|
||||
// ---- JsonDocBox ----
|
||||
extern "C" fn jsondoc_resolve(name: *const c_char) -> u32 {
|
||||
if name.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
|
||||
match s.as_ref() {
|
||||
"birth" => JD_BIRTH,
|
||||
"parse" => JD_PARSE,
|
||||
"root" => JD_ROOT,
|
||||
"error" => JD_ERROR,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Provider selection (serde_json vs yyjson skeleton)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum ProviderKind {
|
||||
Serde,
|
||||
Yyjson,
|
||||
}
|
||||
|
||||
fn provider_kind() -> ProviderKind {
|
||||
match std::env::var("NYASH_JSON_PROVIDER").ok().as_deref() {
|
||||
Some("yyjson") | Some("YYJSON") => ProviderKind::Yyjson,
|
||||
_ => ProviderKind::Serde,
|
||||
}
|
||||
}
|
||||
|
||||
fn provider_parse(text: &str) -> Result<Value, String> {
|
||||
match provider_kind() {
|
||||
ProviderKind::Serde => serde_json::from_str::<Value>(text).map_err(|e| e.to_string()),
|
||||
ProviderKind::Yyjson => {
|
||||
// Skeleton phase: call into C shim to validate linkage, then fallback to serde_json
|
||||
unsafe {
|
||||
if let Ok(c) = CString::new(text.as_bytes()) {
|
||||
let _ = nyash_json_shim_parse(c.as_ptr(), text.len());
|
||||
}
|
||||
}
|
||||
serde_json::from_str::<Value>(text).map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn nyash_json_shim_parse(text: *const std::os::raw::c_char, len: usize) -> i32;
|
||||
fn nyjson_parse_doc(text: *const c_char, len: usize, out_err_code: *mut i32) -> *mut c_void;
|
||||
fn nyjson_doc_free(doc: *mut c_void);
|
||||
fn nyjson_doc_root(doc: *mut c_void) -> *mut c_void;
|
||||
fn nyjson_is_null(v: *mut c_void) -> i32;
|
||||
fn nyjson_is_bool(v: *mut c_void) -> i32;
|
||||
fn nyjson_is_int(v: *mut c_void) -> i32;
|
||||
fn nyjson_is_real(v: *mut c_void) -> i32;
|
||||
fn nyjson_is_str(v: *mut c_void) -> i32;
|
||||
fn nyjson_is_arr(v: *mut c_void) -> i32;
|
||||
fn nyjson_is_obj(v: *mut c_void) -> i32;
|
||||
fn nyjson_get_bool_val(v: *mut c_void) -> i32;
|
||||
fn nyjson_get_sint_val(v: *mut c_void) -> i64;
|
||||
fn nyjson_get_str_val(v: *mut c_void) -> *const c_char;
|
||||
fn nyjson_arr_size_val(v: *mut c_void) -> usize;
|
||||
fn nyjson_arr_get_val(v: *mut c_void, idx: usize) -> *mut c_void;
|
||||
fn nyjson_obj_size_val(v: *mut c_void) -> usize;
|
||||
fn nyjson_obj_get_key(v: *mut c_void, key: *const c_char) -> *mut c_void;
|
||||
}
|
||||
|
||||
extern "C" fn jsondoc_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 {
|
||||
JD_BIRTH => {
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = DOCS.lock() {
|
||||
m.insert(id, DocInst { root: None, doc_ptr: None, last_err: None });
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
return write_u32(id, result, result_len);
|
||||
}
|
||||
JD_PARSE => {
|
||||
let text = match read_arg_string(args, args_len, 0) {
|
||||
Some(s) => s,
|
||||
None => return E_ARGS,
|
||||
};
|
||||
if let Ok(mut m) = DOCS.lock() {
|
||||
if let Some(doc) = m.get_mut(&instance_id) {
|
||||
match provider_kind() {
|
||||
ProviderKind::Serde => {
|
||||
match provider_parse(&text) {
|
||||
Ok(v) => { doc.root = Some(Arc::new(v)); doc.doc_ptr = None; doc.last_err = None; }
|
||||
Err(e) => { doc.root = None; doc.doc_ptr = None; doc.last_err = Some(e.to_string()); }
|
||||
}
|
||||
return write_tlv_void(result, result_len);
|
||||
}
|
||||
ProviderKind::Yyjson => {
|
||||
let c = CString::new(text.as_bytes()).unwrap_or_default();
|
||||
let mut ec: i32 = -1;
|
||||
let p = nyjson_parse_doc(c.as_ptr(), text.len(), &mut ec as *mut i32);
|
||||
if p.is_null() {
|
||||
doc.root = None; doc.doc_ptr = None; doc.last_err = Some(format!("E{}", ec));
|
||||
} else {
|
||||
doc.root = None; doc.doc_ptr = Some(p as usize); doc.last_err = None;
|
||||
}
|
||||
return write_tlv_void(result, result_len);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
JD_ROOT => {
|
||||
if let Ok(m) = DOCS.lock() {
|
||||
if let Some(doc) = m.get(&instance_id) {
|
||||
match provider_kind() {
|
||||
ProviderKind::Serde => {
|
||||
if let Some(root_arc) = doc.root.as_ref().map(|r| Arc::clone(r)) {
|
||||
let node_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut nn) = NODES.lock() { nn.insert(node_id, NodeRep::Serde(root_arc)); }
|
||||
return write_tlv_handle(T_JSON_NODE, node_id, result, result_len);
|
||||
}
|
||||
return E_PLUGIN;
|
||||
}
|
||||
ProviderKind::Yyjson => {
|
||||
if let Some(dp) = doc.doc_ptr {
|
||||
let vp = nyjson_doc_root(dp as *mut c_void);
|
||||
let node_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut nn) = NODES.lock() { nn.insert(node_id, NodeRep::Yy { doc_id: instance_id, ptr: vp as usize }); }
|
||||
return write_tlv_handle(T_JSON_NODE, node_id, result, result_len);
|
||||
}
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return E_PLUGIN;
|
||||
}
|
||||
JD_ERROR => {
|
||||
if let Ok(m) = DOCS.lock() {
|
||||
if let Some(doc) = m.get(&instance_id) {
|
||||
let s = doc.last_err.clone().unwrap_or_default();
|
||||
return write_tlv_string(&s, result, result_len);
|
||||
} else {
|
||||
return E_HANDLE;
|
||||
}
|
||||
} else {
|
||||
return E_PLUGIN;
|
||||
}
|
||||
}
|
||||
JD_FINI => {
|
||||
if let Ok(mut m) = DOCS.lock() {
|
||||
if let Some(mut di) = m.remove(&instance_id) {
|
||||
if let Some(dp) = di.doc_ptr.take() { nyjson_doc_free(dp as *mut c_void); }
|
||||
}
|
||||
}
|
||||
return write_tlv_void(result, result_len);
|
||||
}
|
||||
_ => E_METHOD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub static nyash_typebox_JsonDocBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
||||
abi_tag: 0x54594258, // 'TYBX'
|
||||
version: 1,
|
||||
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
|
||||
name: b"JsonDocBox\0".as_ptr() as *const c_char,
|
||||
resolve: Some(jsondoc_resolve),
|
||||
invoke_id: Some(jsondoc_invoke_id),
|
||||
capabilities: 0,
|
||||
};
|
||||
|
||||
// ---- JsonNodeBox ----
|
||||
extern "C" fn jsonnode_resolve(name: *const c_char) -> u32 {
|
||||
if name.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
|
||||
match s.as_ref() {
|
||||
"birth" => JN_BIRTH,
|
||||
"kind" => JN_KIND,
|
||||
"get" => JN_GET,
|
||||
"size" => JN_SIZE,
|
||||
"at" => JN_AT,
|
||||
"str" => JN_STR,
|
||||
"int" => JN_INT,
|
||||
"bool" => JN_BOOL,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn jsonnode_invoke_id(
|
||||
instance_id: u32,
|
||||
method_id: u32,
|
||||
args: *const u8,
|
||||
args_len: usize,
|
||||
result: *mut u8,
|
||||
result_len: *mut usize,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
let node_rep = match NODES.lock() {
|
||||
Ok(m) => match m.get(&instance_id) { Some(v) => v.clone(), None => return E_HANDLE },
|
||||
Err(_) => return E_PLUGIN,
|
||||
};
|
||||
match method_id {
|
||||
JN_BIRTH => {
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let Ok(mut m) = NODES.lock() { m.insert(id, NodeRep::Serde(Arc::new(Value::Null))); } else { return E_PLUGIN; }
|
||||
return write_u32(id, result, result_len);
|
||||
}
|
||||
JN_KIND => {
|
||||
match provider_kind() {
|
||||
ProviderKind::Serde => {
|
||||
let k = match node_rep { NodeRep::Serde(ref a) => match &**a {
|
||||
Value::Null => "null",
|
||||
Value::Bool(_) => "bool",
|
||||
Value::Number(n) => { if n.is_i64() { "int" } else { "real" } },
|
||||
Value::String(_) => "string",
|
||||
Value::Array(_) => "array",
|
||||
Value::Object(_) => "object",
|
||||
}, _ => "null"};
|
||||
write_tlv_string(k, result, result_len)
|
||||
}
|
||||
ProviderKind::Yyjson => {
|
||||
let v = if let NodeRep::Yy { ptr, .. } = node_rep { ptr as *mut c_void } else { std::ptr::null_mut() };
|
||||
let k = if v.is_null() { "null" }
|
||||
else if nyjson_is_obj(v) != 0 { "object" }
|
||||
else if nyjson_is_arr(v) != 0 { "array" }
|
||||
else if nyjson_is_str(v) != 0 { "string" }
|
||||
else if nyjson_is_int(v) != 0 { "int" }
|
||||
else if nyjson_is_real(v) != 0 { "real" }
|
||||
else if nyjson_is_bool(v) != 0 { "bool" }
|
||||
else { "null" };
|
||||
write_tlv_string(k, result, result_len)
|
||||
}
|
||||
}
|
||||
}
|
||||
JN_GET => {
|
||||
let key = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
|
||||
match provider_kind() {
|
||||
ProviderKind::Serde => {
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let NodeRep::Serde(ref a) = node_rep {
|
||||
if let Value::Object(map) = &**a {
|
||||
if let Some(child) = map.get(&key) {
|
||||
if let Ok(mut mm) = NODES.lock() { mm.insert(id, NodeRep::Serde(Arc::new(child.clone()))); }
|
||||
return write_tlv_handle(T_JSON_NODE, id, result, result_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(mut mm) = NODES.lock() { mm.insert(id, NodeRep::Serde(Arc::new(Value::Null))); }
|
||||
write_tlv_handle(T_JSON_NODE, id, result, result_len)
|
||||
}
|
||||
ProviderKind::Yyjson => {
|
||||
let v = if let NodeRep::Yy { ptr, .. } = node_rep { ptr as *mut c_void } else { std::ptr::null_mut() };
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
let mut out_ptr: *mut c_void = std::ptr::null_mut();
|
||||
if !v.is_null() && nyjson_is_obj(v) != 0 {
|
||||
let c = CString::new(key).unwrap_or_default();
|
||||
out_ptr = nyjson_obj_get_key(v, c.as_ptr());
|
||||
}
|
||||
let doc_id = if let NodeRep::Yy { doc_id, .. } = node_rep { doc_id } else { 0 };
|
||||
let rep = if out_ptr.is_null() {
|
||||
NodeRep::Yy { doc_id, ptr: 0 }
|
||||
} else {
|
||||
NodeRep::Yy { doc_id, ptr: out_ptr as usize }
|
||||
};
|
||||
if let Ok(mut mm) = NODES.lock() { mm.insert(id, rep); }
|
||||
write_tlv_handle(T_JSON_NODE, id, result, result_len)
|
||||
}
|
||||
}
|
||||
}
|
||||
JN_SIZE => {
|
||||
match provider_kind() {
|
||||
ProviderKind::Serde => {
|
||||
let n = match node_rep { NodeRep::Serde(ref a) => match &**a { Value::Array(a) => a.len() as i64, Value::Object(o) => o.len() as i64, _ => 0 }, _ => 0 };
|
||||
write_tlv_i64(n, result, result_len)
|
||||
}
|
||||
ProviderKind::Yyjson => {
|
||||
let v = if let NodeRep::Yy { ptr, .. } = node_rep { ptr as *mut c_void } else { std::ptr::null_mut() };
|
||||
let n = if !v.is_null() { if nyjson_is_arr(v) != 0 { nyjson_arr_size_val(v) as i64 } else if nyjson_is_obj(v) != 0 { nyjson_obj_size_val(v) as i64 } else { 0 } } else { 0 };
|
||||
write_tlv_i64(n, result, result_len)
|
||||
}
|
||||
}
|
||||
}
|
||||
JN_AT => {
|
||||
let idx = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return E_ARGS };
|
||||
if idx < 0 { return E_ARGS; }
|
||||
match provider_kind() {
|
||||
ProviderKind::Serde => {
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if let NodeRep::Serde(ref a) = node_rep {
|
||||
if let Value::Array(arr) = &**a {
|
||||
let i = idx as usize;
|
||||
if i < arr.len() { if let Ok(mut mm) = NODES.lock() { mm.insert(id, NodeRep::Serde(Arc::new(arr[i].clone()))); } return write_tlv_handle(T_JSON_NODE, id, result, result_len); }
|
||||
}
|
||||
}
|
||||
if let Ok(mut mm) = NODES.lock() { mm.insert(id, NodeRep::Serde(Arc::new(Value::Null))); }
|
||||
write_tlv_handle(T_JSON_NODE, id, result, result_len)
|
||||
}
|
||||
ProviderKind::Yyjson => {
|
||||
let v = if let NodeRep::Yy { ptr, .. } = node_rep { ptr as *mut c_void } else { std::ptr::null_mut() };
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
let mut child: *mut c_void = std::ptr::null_mut();
|
||||
if !v.is_null() && nyjson_is_arr(v) != 0 { child = nyjson_arr_get_val(v, idx as usize); }
|
||||
let doc_id = if let NodeRep::Yy { doc_id, .. } = node_rep { doc_id } else { 0 };
|
||||
let rep = if child.is_null() {
|
||||
NodeRep::Yy { doc_id, ptr: 0 }
|
||||
} else {
|
||||
NodeRep::Yy { doc_id, ptr: child as usize }
|
||||
};
|
||||
if let Ok(mut mm) = NODES.lock() { mm.insert(id, rep); }
|
||||
write_tlv_handle(T_JSON_NODE, id, result, result_len)
|
||||
}
|
||||
}
|
||||
}
|
||||
JN_STR => match provider_kind() {
|
||||
ProviderKind::Serde => {
|
||||
if let NodeRep::Serde(ref a) = node_rep { match &**a { Value::String(s) => write_tlv_string(s, result, result_len), Value::Object(o) => { if let Some(Value::String(s)) = o.get("value") { write_tlv_string(s, result, result_len) } else { write_tlv_string("", result, result_len) } }, _ => write_tlv_string("", result, result_len) } } else { write_tlv_string("", result, result_len) }
|
||||
}
|
||||
ProviderKind::Yyjson => {
|
||||
let v = if let NodeRep::Yy { ptr, .. } = node_rep { ptr as *mut c_void } else { std::ptr::null_mut() };
|
||||
if !v.is_null() && nyjson_is_str(v) != 0 { let s = nyjson_get_str_val(v); if s.is_null() { write_tlv_string("", result, result_len) } else { let rs = CStr::from_ptr(s).to_string_lossy().to_string(); write_tlv_string(&rs, result, result_len) } }
|
||||
else if !v.is_null() && nyjson_is_obj(v) != 0 { let key = CString::new("value").unwrap(); let child = nyjson_obj_get_key(v, key.as_ptr()); if !child.is_null() && nyjson_is_str(child) != 0 { let s = nyjson_get_str_val(child); if s.is_null() { write_tlv_string("", result, result_len) } else { let rs = CStr::from_ptr(s).to_string_lossy().to_string(); write_tlv_string(&rs, result, result_len) } } else { write_tlv_string("", result, result_len) } }
|
||||
else { write_tlv_string("", result, result_len) }
|
||||
}
|
||||
},
|
||||
JN_INT => match provider_kind() {
|
||||
ProviderKind::Serde => {
|
||||
if let NodeRep::Serde(ref a) = node_rep { match &**a { Value::Number(n) => write_tlv_i64(n.as_i64().unwrap_or(0), result, result_len), Value::Object(o) => { if let Some(Value::Number(n)) = o.get("value") { write_tlv_i64(n.as_i64().unwrap_or(0), result, result_len) } else { write_tlv_i64(0, result, result_len) } }, _ => write_tlv_i64(0, result, result_len) } } else { write_tlv_i64(0, result, result_len) }
|
||||
}
|
||||
ProviderKind::Yyjson => {
|
||||
let v = if let NodeRep::Yy { ptr, .. } = node_rep { ptr as *mut c_void } else { std::ptr::null_mut() };
|
||||
if !v.is_null() && nyjson_is_int(v) != 0 { write_tlv_i64(nyjson_get_sint_val(v) as i64, result, result_len) }
|
||||
else if !v.is_null() && nyjson_is_obj(v) != 0 { let key = CString::new("value").unwrap(); let child = nyjson_obj_get_key(v, key.as_ptr()); if !child.is_null() && nyjson_is_int(child) != 0 { write_tlv_i64(nyjson_get_sint_val(child) as i64, result, result_len) } else { write_tlv_i64(0, result, result_len) } }
|
||||
else { write_tlv_i64(0, result, result_len) }
|
||||
}
|
||||
},
|
||||
JN_BOOL => match provider_kind() {
|
||||
ProviderKind::Serde => { if let NodeRep::Serde(ref a) = node_rep { if let Value::Bool(b) = **a { write_tlv_bool(b, result, result_len) } else { write_tlv_bool(false, result, result_len) } } else { write_tlv_bool(false, result, result_len) } }
|
||||
ProviderKind::Yyjson => { let v = if let NodeRep::Yy { ptr, .. } = node_rep { ptr as *mut c_void } else { std::ptr::null_mut() }; if !v.is_null() && nyjson_is_bool(v) != 0 { write_tlv_bool(nyjson_get_bool_val(v) != 0, result, result_len) } else { write_tlv_bool(false, result, result_len) } }
|
||||
},
|
||||
_ => E_METHOD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub static nyash_typebox_JsonNodeBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
|
||||
abi_tag: 0x54594258, // 'TYBX'
|
||||
version: 1,
|
||||
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
|
||||
name: b"JsonNodeBox\0".as_ptr() as *const c_char,
|
||||
resolve: Some(jsonnode_resolve),
|
||||
invoke_id: Some(jsonnode_invoke_id),
|
||||
capabilities: 0,
|
||||
};
|
||||
|
||||
// ---- TLV helpers (copied/minimized) ----
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
OK
|
||||
}
|
||||
fn write_u32(v: u32, result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
if result_len.is_null() {
|
||||
return E_ARGS;
|
||||
}
|
||||
unsafe {
|
||||
if result.is_null() || *result_len < 4 {
|
||||
*result_len = 4;
|
||||
return E_SHORT;
|
||||
}
|
||||
let b = v.to_le_bytes();
|
||||
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
|
||||
*result_len = 4;
|
||||
}
|
||||
OK
|
||||
}
|
||||
fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 {
|
||||
// Align with common helpers: use tag=9 for void/host-handle-like empty
|
||||
write_tlv_result(&[(9u8, &[])], 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_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());
|
||||
write_tlv_result(&[(8u8, &payload)], 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 {
|
||||
return None;
|
||||
}
|
||||
let s = String::from_utf8_lossy(&buf[off + 4..off + 4 + size]).to_string();
|
||||
return Some(s);
|
||||
}
|
||||
off += 4 + size;
|
||||
}
|
||||
None
|
||||
}
|
||||
fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option<i64> {
|
||||
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;
|
||||
}
|
||||
None
|
||||
}
|
||||
@ -22,6 +22,12 @@ def op_newbox(owner, inst: Dict[str, Any], regs: Dict[int, Any]) -> None:
|
||||
val = {"__box__": "ArrayBox", "__arr": []}
|
||||
elif btype == "MapBox":
|
||||
val = {"__box__": "MapBox", "__map": {}}
|
||||
elif btype == "JsonDocBox":
|
||||
# Minimal JsonDocBox (PyVM-only): stores parsed JSON and last error
|
||||
val = {"__box__": "JsonDocBox", "__doc": None, "__err": None}
|
||||
elif btype == "JsonNodeBox":
|
||||
# Minimal JsonNodeBox with empty Null node
|
||||
val = {"__box__": "JsonNodeBox", "__node": None}
|
||||
else:
|
||||
# Unknown box -> opaque
|
||||
val = {"__box__": btype}
|
||||
@ -135,7 +141,10 @@ def op_boxcall(owner, fn, inst: Dict[str, Any], regs: Dict[int, Any]) -> None:
|
||||
# ArrayBox minimal methods
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "ArrayBox":
|
||||
arr = recv.get("__arr", [])
|
||||
if method in ("len", "size"):
|
||||
if method in ("birth",):
|
||||
# No-op initializer for parity with Nyash VM
|
||||
out = 0
|
||||
elif method in ("len", "size"):
|
||||
out = len(arr)
|
||||
elif method == "get":
|
||||
idx = int(args[0]) if args else 0
|
||||
@ -185,6 +194,87 @@ def op_boxcall(owner, fn, inst: Dict[str, Any], regs: Dict[int, Any]) -> None:
|
||||
out = None
|
||||
recv["__map"] = m
|
||||
|
||||
# JsonDocBox (PyVM-native shim)
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "JsonDocBox":
|
||||
import json
|
||||
if method == "parse":
|
||||
s = args[0] if args else ""
|
||||
try:
|
||||
recv["__doc"] = json.loads(str(s))
|
||||
recv["__err"] = None
|
||||
except Exception as e:
|
||||
recv["__doc"] = None
|
||||
recv["__err"] = str(e)
|
||||
out = 0
|
||||
elif method == "root":
|
||||
out = {"__box__": "JsonNodeBox", "__node": recv.get("__doc", None)}
|
||||
elif method == "error":
|
||||
out = recv.get("__err") or ""
|
||||
else:
|
||||
out = None
|
||||
|
||||
# JsonNodeBox (PyVM-native shim)
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "JsonNodeBox":
|
||||
node = recv.get("__node", None)
|
||||
if method == "kind":
|
||||
if node is None:
|
||||
out = "null"
|
||||
elif isinstance(node, bool):
|
||||
out = "bool"
|
||||
elif isinstance(node, int):
|
||||
out = "int"
|
||||
elif isinstance(node, float):
|
||||
out = "real"
|
||||
elif isinstance(node, str):
|
||||
out = "string"
|
||||
elif isinstance(node, list):
|
||||
out = "array"
|
||||
elif isinstance(node, dict):
|
||||
out = "object"
|
||||
else:
|
||||
out = "null"
|
||||
elif method == "get":
|
||||
key = str(args[0]) if args else ""
|
||||
if isinstance(node, dict) and key in node:
|
||||
out = {"__box__": "JsonNodeBox", "__node": node.get(key)}
|
||||
else:
|
||||
out = {"__box__": "JsonNodeBox", "__node": None}
|
||||
elif method == "size":
|
||||
if isinstance(node, list):
|
||||
out = len(node)
|
||||
elif isinstance(node, dict):
|
||||
out = len(node)
|
||||
else:
|
||||
out = 0
|
||||
elif method == "at":
|
||||
try:
|
||||
idx = int(args[0]) if args else 0
|
||||
except Exception:
|
||||
idx = 0
|
||||
if isinstance(node, list) and 0 <= idx < len(node):
|
||||
out = {"__box__": "JsonNodeBox", "__node": node[idx]}
|
||||
else:
|
||||
out = {"__box__": "JsonNodeBox", "__node": None}
|
||||
elif method == "str":
|
||||
if isinstance(node, str):
|
||||
out = node
|
||||
elif isinstance(node, dict) and isinstance(node.get("value"), str):
|
||||
out = node.get("value")
|
||||
else:
|
||||
out = ""
|
||||
elif method == "int":
|
||||
if isinstance(node, int):
|
||||
out = node
|
||||
elif isinstance(node, dict):
|
||||
v = node.get("value")
|
||||
out = int(v) if isinstance(v, int) else 0
|
||||
else:
|
||||
out = 0
|
||||
elif method == "bool":
|
||||
out = bool(node) if isinstance(node, bool) else False
|
||||
else:
|
||||
out = None
|
||||
|
||||
elif method == "esc_json":
|
||||
s = args[0] if args else ""
|
||||
s = "" if s is None else str(s)
|
||||
@ -236,4 +326,3 @@ def op_boxcall(owner, fn, inst: Dict[str, Any], regs: Dict[int, Any]) -> None:
|
||||
out = None
|
||||
|
||||
owner._set(regs, inst.get("dst"), out)
|
||||
|
||||
|
||||
@ -94,16 +94,22 @@ impl PluginHost {
|
||||
let box_conf = cfg
|
||||
.get_box_config(lib_name, box_type, &toml_value)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
let m = box_conf
|
||||
.methods
|
||||
.get(method_name)
|
||||
.ok_or(BidError::InvalidMethod)?;
|
||||
// Prefer config mapping; fallback to loader's TypeBox resolve(name)
|
||||
let (method_id, returns_result) = if let Some(m) = box_conf.methods.get(method_name) {
|
||||
(m.method_id, m.returns_result)
|
||||
} else {
|
||||
let l = self.loader.read().unwrap();
|
||||
let mid = l
|
||||
.resolve_method_id(box_type, method_name)
|
||||
.map_err(|_| BidError::InvalidMethod)?;
|
||||
(mid, false)
|
||||
};
|
||||
Ok(MethodHandle {
|
||||
lib: lib_name.to_string(),
|
||||
box_type: box_type.to_string(),
|
||||
type_id: box_conf.type_id,
|
||||
method_id: m.method_id,
|
||||
returns_result: m.returns_result,
|
||||
method_id,
|
||||
returns_result,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -23,6 +23,8 @@ struct LoadedBoxSpec {
|
||||
fini_method_id: Option<u32>,
|
||||
// Optional Nyash ABI v2 per-box invoke entry (not yet used for calls)
|
||||
invoke_id: Option<BoxInvokeFn>,
|
||||
// Optional resolve(name)->method_id provided by NyashTypeBoxFfi
|
||||
resolve_fn: Option<extern "C" fn(*const std::os::raw::c_char) -> u32>,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct MethodSpec {
|
||||
@ -172,7 +174,7 @@ impl PluginLoaderV2 {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Remember invoke_id in box_specs for (lib_name, box_type)
|
||||
// Remember invoke_id/resolve in box_specs for (lib_name, box_type)
|
||||
if let Some(invoke_id) = st.invoke_id {
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let mut map = self.box_specs.write().map_err(|_| BidError::PluginError)?;
|
||||
@ -181,8 +183,10 @@ impl PluginLoaderV2 {
|
||||
methods: HashMap::new(),
|
||||
fini_method_id: None,
|
||||
invoke_id: None,
|
||||
resolve_fn: None,
|
||||
});
|
||||
entry.invoke_id = Some(invoke_id);
|
||||
entry.resolve_fn = st.resolve;
|
||||
} else if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] WARN: TypeBox present but no invoke_id for {}.{} — plugin should export per-Box invoke",
|
||||
@ -311,6 +315,56 @@ impl PluginLoaderV2 {
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolve method_id for (box_type, method_name), consulting config first, then TypeBox resolve() if available.
|
||||
pub(crate) fn resolve_method_id(&self, box_type: &str, method_name: &str) -> BidResult<u32> {
|
||||
use std::ffi::CString;
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value = super::errors::from_toml(toml::from_str(
|
||||
&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?,
|
||||
))?;
|
||||
// 1) config mapping
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) {
|
||||
if let Some(m) = bc.methods.get(method_name) {
|
||||
return Ok(m.method_id);
|
||||
}
|
||||
}
|
||||
// 2) v2 TypeBox resolve (and cache)
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
if let Ok(mut map) = self.box_specs.write() {
|
||||
if let Some(spec) = map.get_mut(&key) {
|
||||
if let Some(ms) = spec.methods.get(method_name) {
|
||||
return Ok(ms.method_id);
|
||||
}
|
||||
if let Some(res_fn) = spec.resolve_fn {
|
||||
if let Ok(cstr) = CString::new(method_name) {
|
||||
let mid = res_fn(cstr.as_ptr());
|
||||
if mid != 0 {
|
||||
// Cache minimal MethodSpec (returns_result unknown → false)
|
||||
spec.methods.insert(
|
||||
method_name.to_string(),
|
||||
MethodSpec {
|
||||
method_id: mid,
|
||||
returns_result: false,
|
||||
},
|
||||
);
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] resolve(name) {}.{} -> id {}",
|
||||
box_type, method_name, mid
|
||||
);
|
||||
}
|
||||
return Ok(mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(BidError::InvalidMethod)
|
||||
}
|
||||
|
||||
pub fn construct_existing_instance(
|
||||
&self,
|
||||
type_id: u32,
|
||||
@ -634,10 +688,19 @@ impl PluginLoaderV2 {
|
||||
.get_box_config(lib_name, box_type, &toml_value)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
let type_id = box_conf.type_id;
|
||||
let method = box_conf
|
||||
.methods
|
||||
.get(method_name)
|
||||
.ok_or(BidError::InvalidMethod)?;
|
||||
// Resolve method id via config or TypeBox resolve()
|
||||
let method_id = match self.resolve_method_id(box_type, method_name) {
|
||||
Ok(mid) => mid,
|
||||
Err(e) => {
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] ERR: method resolve failed for {}.{}: {:?}",
|
||||
box_type, method_name, e
|
||||
);
|
||||
}
|
||||
return Err(BidError::InvalidMethod);
|
||||
}
|
||||
};
|
||||
// Get plugin handle
|
||||
let plugins = self.plugins.read().map_err(|_| BidError::PluginError)?;
|
||||
let _plugin = plugins.get(lib_name).ok_or(BidError::PluginError)?;
|
||||
@ -646,7 +709,7 @@ impl PluginLoaderV2 {
|
||||
let (_code, out_len, out) = super::host_bridge::invoke_alloc(
|
||||
super::super::nyash_plugin_invoke_v2_shim,
|
||||
type_id,
|
||||
method.method_id,
|
||||
method_id,
|
||||
instance_id,
|
||||
&tlv,
|
||||
);
|
||||
|
||||
31
tools/test/smoke/selfhost/collect_prints_mixed.sh
Normal file
31
tools/test/smoke/selfhost/collect_prints_mixed.sh
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT_DIR=$(cd "$(dirname "$0")/../../../.." && pwd)
|
||||
|
||||
echo "[smoke] collect_prints mixed order ..." >&2
|
||||
|
||||
pushd "$ROOT_DIR" >/dev/null
|
||||
|
||||
cargo build --release -q
|
||||
|
||||
export NYASH_ENABLE_USING=1
|
||||
export NYASH_VM_USE_PY=1
|
||||
BIN=./target/release/nyash
|
||||
# Use JSON Box based app to avoid reliance on MiniVmPrints fallbacks
|
||||
APP=apps/tests/jsonbox_collect_prints_smoke.nyash
|
||||
|
||||
out=$("$BIN" --backend vm "$APP")
|
||||
|
||||
expected=$'A\nB\n7\n1\n7\n5'
|
||||
|
||||
if [[ "$out" != "$expected" ]]; then
|
||||
echo "[smoke] FAIL: unexpected output" >&2
|
||||
echo "--- got ---" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
echo "--- exp ---" >&2
|
||||
printf '%s\n' "$expected" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[smoke] OK: collect_prints mixed order" >&2
|
||||
popd >/dev/null
|
||||
31
tools/test/smoke/selfhost/jsonbox_collect_prints.sh
Normal file
31
tools/test/smoke/selfhost/jsonbox_collect_prints.sh
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT_DIR=$(cd "$(dirname "$0")/../../../.." && pwd)
|
||||
|
||||
echo "[smoke] jsonbox collect_prints ..." >&2
|
||||
|
||||
pushd "$ROOT_DIR" >/dev/null
|
||||
|
||||
cargo build --release -q --manifest-path plugins/nyash-json-plugin/Cargo.toml
|
||||
|
||||
export NYASH_VM_USE_PY=1
|
||||
export NYASH_LOAD_NY_PLUGINS=1
|
||||
unset NYASH_DISABLE_PLUGINS || true
|
||||
|
||||
BIN=./target/release/nyash
|
||||
APP=apps/tests/jsonbox_collect_prints_smoke.nyash
|
||||
|
||||
out=$("$BIN" --backend vm "$APP")
|
||||
expected=$'A\nB\n7\n1\n7\n5'
|
||||
|
||||
if [[ "$out" != "$expected" ]]; then
|
||||
echo "[smoke] FAIL: unexpected output" >&2
|
||||
echo "--- got ---" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
echo "--- exp ---" >&2
|
||||
printf '%s\n' "$expected" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[smoke] OK: jsonbox collect_prints" >&2
|
||||
popd >/dev/null
|
||||
30
tools/test/smoke/selfhost/jsonbox_nested.sh
Normal file
30
tools/test/smoke/selfhost/jsonbox_nested.sh
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT_DIR=$(cd "$(dirname "$0")/../../../.." && pwd)
|
||||
|
||||
echo "[smoke] jsonbox nested ..." >&2
|
||||
pushd "$ROOT_DIR" >/dev/null
|
||||
|
||||
cargo build --release -q --manifest-path plugins/nyash-json-plugin/Cargo.toml
|
||||
|
||||
export NYASH_VM_USE_PY=1
|
||||
export NYASH_LOAD_NY_PLUGINS=1
|
||||
|
||||
BIN=./target/release/nyash
|
||||
APP=apps/tests/jsonbox_nested.nyash
|
||||
|
||||
out=$("$BIN" --backend vm "$APP")
|
||||
expected=$'B\n7\n3\n2'
|
||||
|
||||
if [[ "$out" != "$expected" ]]; then
|
||||
echo "[smoke] FAIL: unexpected output" >&2
|
||||
echo "--- got ---" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
echo "--- exp ---" >&2
|
||||
printf '%s\n' "$expected" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[smoke] OK: jsonbox nested" >&2
|
||||
popd >/dev/null
|
||||
|
||||
30
tools/test/smoke/selfhost/jsonbox_parse_err.sh
Normal file
30
tools/test/smoke/selfhost/jsonbox_parse_err.sh
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT_DIR=$(cd "$(dirname "$0")/../../../.." && pwd)
|
||||
|
||||
echo "[smoke] jsonbox parse ERR ..." >&2
|
||||
pushd "$ROOT_DIR" >/dev/null
|
||||
|
||||
cargo build --release -q --manifest-path plugins/nyash-json-plugin/Cargo.toml
|
||||
|
||||
export NYASH_VM_USE_PY=1
|
||||
export NYASH_LOAD_NY_PLUGINS=1
|
||||
|
||||
BIN=./target/release/nyash
|
||||
APP=apps/tests/jsonbox_parse_err.nyash
|
||||
|
||||
out=$("$BIN" --backend vm "$APP")
|
||||
expected=$'ERR'
|
||||
|
||||
if [[ "$out" != "$expected" ]]; then
|
||||
echo "[smoke] FAIL: unexpected output" >&2
|
||||
echo "--- got ---" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
echo "--- exp ---" >&2
|
||||
printf '%s\n' "$expected" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[smoke] OK: jsonbox parse ERR" >&2
|
||||
popd >/dev/null
|
||||
|
||||
30
tools/test/smoke/selfhost/jsonbox_parse_ok.sh
Normal file
30
tools/test/smoke/selfhost/jsonbox_parse_ok.sh
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT_DIR=$(cd "$(dirname "$0")/../../../.." && pwd)
|
||||
|
||||
echo "[smoke] jsonbox parse OK ..." >&2
|
||||
pushd "$ROOT_DIR" >/dev/null
|
||||
|
||||
cargo build --release -q --manifest-path plugins/nyash-json-plugin/Cargo.toml
|
||||
|
||||
export NYASH_VM_USE_PY=1
|
||||
export NYASH_LOAD_NY_PLUGINS=1
|
||||
|
||||
BIN=./target/release/nyash
|
||||
APP=apps/tests/jsonbox_parse_ok.nyash
|
||||
|
||||
out=$("$BIN" --backend vm "$APP")
|
||||
expected=$'Program'
|
||||
|
||||
if [[ "$out" != "$expected" ]]; then
|
||||
echo "[smoke] FAIL: unexpected output" >&2
|
||||
echo "--- got ---" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
echo "--- exp ---" >&2
|
||||
printf '%s\n' "$expected" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[smoke] OK: jsonbox parse OK" >&2
|
||||
popd >/dev/null
|
||||
|
||||
Reference in New Issue
Block a user