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:
@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user