pyvm: split op handlers into ops_core/ops_box/ops_ctrl; add ops_flow + intrinsic; delegate vm.py without behavior change
net-plugin: modularize constants (consts.rs) and sockets (sockets.rs); remove legacy commented socket code; fix unused imports mir: move instruction unit tests to tests/mir_instruction_unit.rs (file lean-up); no semantic changes runner/pyvm: ensure using pre-strip; misc docs updates Build: cargo build ok; legacy cfg warnings remain as before
This commit is contained in:
239
src/llvm_py/pyvm/ops_box.py
Normal file
239
src/llvm_py/pyvm/ops_box.py
Normal file
@ -0,0 +1,239 @@
|
||||
"""
|
||||
Box-related operations for the Nyash PyVM: newbox and boxcall.
|
||||
Kept behaviorally identical to the original vm.py code.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
import os
|
||||
|
||||
|
||||
def op_newbox(owner, inst: Dict[str, Any], regs: Dict[int, Any]) -> None:
|
||||
btype = inst.get("type")
|
||||
# Sandbox gate: only allow minimal boxes when sandbox is active
|
||||
if not owner._sandbox_allow_newbox(str(btype)):
|
||||
val = {"__box__": str(btype), "__denied__": True}
|
||||
elif btype == "ConsoleBox":
|
||||
val = {"__box__": "ConsoleBox"}
|
||||
elif btype == "StringBox":
|
||||
# empty string instance
|
||||
val = ""
|
||||
elif btype == "ArrayBox":
|
||||
val = {"__box__": "ArrayBox", "__arr": []}
|
||||
elif btype == "MapBox":
|
||||
val = {"__box__": "MapBox", "__map": {}}
|
||||
else:
|
||||
# Unknown box -> opaque
|
||||
val = {"__box__": btype}
|
||||
owner._set(regs, inst.get("dst"), val)
|
||||
|
||||
|
||||
def op_boxcall(owner, fn, inst: Dict[str, Any], regs: Dict[int, Any]) -> None:
|
||||
recv = owner._read(regs, inst.get("box"))
|
||||
method = inst.get("method")
|
||||
args: List[Any] = [owner._read(regs, a) for a in inst.get("args", [])]
|
||||
out: Any = None
|
||||
owner._dbg(f"[pyvm] boxcall recv={recv} method={method} args={args}")
|
||||
|
||||
# Sandbox gate: disallow unsafe/unknown boxcalls
|
||||
if not owner._sandbox_allow_boxcall(recv, method):
|
||||
owner._set(regs, inst.get("dst"), out)
|
||||
return
|
||||
|
||||
# Special-case: inside a method body, 'me.method(...)' lowers to a
|
||||
# boxcall with a synthetic receiver marker '__me__'. Resolve it by
|
||||
# dispatching to the current box's lowered function if available.
|
||||
if isinstance(recv, str) and recv == "__me__" and isinstance(method, str):
|
||||
box_name = ""
|
||||
try:
|
||||
if "." in fn.name:
|
||||
box_name = fn.name.split(".")[0]
|
||||
except Exception:
|
||||
box_name = ""
|
||||
if box_name:
|
||||
cand = f"{box_name}.{method}/{len(args)}"
|
||||
callee = owner.functions.get(cand)
|
||||
if callee is not None:
|
||||
owner._dbg(f"[pyvm] boxcall(__me__) -> {cand} args={args}")
|
||||
out = owner._exec_function(callee, args)
|
||||
owner._set(regs, inst.get("dst"), out)
|
||||
return
|
||||
|
||||
# User-defined box: dispatch to lowered function if available (Box.method/N)
|
||||
if isinstance(recv, dict) and isinstance(method, str) and "__box__" in recv:
|
||||
box_name = recv.get("__box__")
|
||||
cand = f"{box_name}.{method}/{len(args)}"
|
||||
callee = owner.functions.get(cand)
|
||||
if callee is not None:
|
||||
owner._dbg(f"[pyvm] boxcall dispatch -> {cand} args={args}")
|
||||
out = owner._exec_function(callee, args)
|
||||
owner._set(regs, inst.get("dst"), out)
|
||||
return
|
||||
else:
|
||||
if owner._debug:
|
||||
prefix = f"{box_name}.{method}/"
|
||||
cands = sorted([k for k in owner.functions.keys() if k.startswith(prefix)])
|
||||
if cands:
|
||||
owner._dbg(f"[pyvm] boxcall unresolved: '{cand}' — available: {cands}")
|
||||
else:
|
||||
any_for_box = sorted([k for k in owner.functions.keys() if k.startswith(f"{box_name}.")])
|
||||
owner._dbg(f"[pyvm] boxcall unresolved: '{cand}' — no candidates; methods for {box_name}: {any_for_box}")
|
||||
|
||||
# ConsoleBox methods
|
||||
if method in ("print", "println", "log") and owner._is_console(recv):
|
||||
s = args[0] if args else ""
|
||||
if s is None:
|
||||
s = ""
|
||||
# println is the primary one used by smokes; keep print/log equivalent
|
||||
print(str(s))
|
||||
out = 0
|
||||
|
||||
# FileBox methods (minimal read-only)
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "FileBox":
|
||||
if method == "open":
|
||||
path = str(args[0]) if len(args) > 0 else ""
|
||||
mode = str(args[1]) if len(args) > 1 else "r"
|
||||
ok = 0
|
||||
content = None
|
||||
if mode == "r":
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
ok = 1
|
||||
except Exception:
|
||||
ok = 0
|
||||
content = None
|
||||
recv["__open"] = (ok == 1)
|
||||
recv["__path"] = path
|
||||
recv["__content"] = content
|
||||
out = ok
|
||||
elif method == "read":
|
||||
if isinstance(recv.get("__content"), str):
|
||||
out = recv.get("__content")
|
||||
else:
|
||||
out = None
|
||||
elif method == "close":
|
||||
recv["__open"] = False
|
||||
out = 0
|
||||
else:
|
||||
out = None
|
||||
|
||||
# PathBox methods (posix-like)
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "PathBox":
|
||||
if method == "dirname":
|
||||
p = str(args[0]) if args else ""
|
||||
out = os.path.dirname(p)
|
||||
if out == "":
|
||||
out = "."
|
||||
elif method == "join":
|
||||
base = str(args[0]) if len(args) > 0 else ""
|
||||
rel = str(args[1]) if len(args) > 1 else ""
|
||||
out = os.path.join(base, rel)
|
||||
else:
|
||||
out = None
|
||||
|
||||
# ArrayBox minimal methods
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "ArrayBox":
|
||||
arr = recv.get("__arr", [])
|
||||
if method in ("len", "size"):
|
||||
out = len(arr)
|
||||
elif method == "get":
|
||||
idx = int(args[0]) if args else 0
|
||||
out = arr[idx] if 0 <= idx < len(arr) else None
|
||||
elif method == "set":
|
||||
idx = int(args[0]) if len(args) > 0 else 0
|
||||
val = args[1] if len(args) > 1 else None
|
||||
if 0 <= idx < len(arr):
|
||||
arr[idx] = val
|
||||
elif idx == len(arr):
|
||||
arr.append(val)
|
||||
else:
|
||||
while len(arr) < idx:
|
||||
arr.append(None)
|
||||
arr.append(val)
|
||||
out = 0
|
||||
elif method == "push":
|
||||
val = args[0] if args else None
|
||||
arr.append(val)
|
||||
out = len(arr)
|
||||
elif method == "toString":
|
||||
out = "[" + ",".join(str(x) for x in arr) + "]"
|
||||
else:
|
||||
out = None
|
||||
recv["__arr"] = arr
|
||||
|
||||
# MapBox minimal methods
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "MapBox":
|
||||
m = recv.get("__map", {})
|
||||
if method == "size":
|
||||
out = len(m)
|
||||
elif method == "has":
|
||||
key = str(args[0]) if args else ""
|
||||
out = 1 if key in m else 0
|
||||
elif method == "get":
|
||||
key = str(args[0]) if args else ""
|
||||
out = m.get(key)
|
||||
elif method == "set":
|
||||
key = str(args[0]) if len(args) > 0 else ""
|
||||
val = args[1] if len(args) > 1 else None
|
||||
m[key] = val
|
||||
out = 0
|
||||
elif method == "toString":
|
||||
items = ",".join(f"{k}:{m[k]}" for k in m)
|
||||
out = "{" + items + "}"
|
||||
else:
|
||||
out = None
|
||||
recv["__map"] = m
|
||||
|
||||
elif method == "esc_json":
|
||||
s = args[0] if args else ""
|
||||
s = "" if s is None else str(s)
|
||||
out_chars: List[str] = []
|
||||
for ch in s:
|
||||
if ch == "\\":
|
||||
out_chars.append("\\\\")
|
||||
elif ch == '"':
|
||||
out_chars.append('\\"')
|
||||
else:
|
||||
out_chars.append(ch)
|
||||
out = "".join(out_chars)
|
||||
|
||||
elif method == "length":
|
||||
out = len(str(recv))
|
||||
|
||||
elif method == "substring":
|
||||
s = str(recv)
|
||||
start = int(args[0]) if (len(args) > 0 and args[0] is not None) else 0
|
||||
end = int(args[1]) if (len(args) > 1 and args[1] is not None) else len(s)
|
||||
out = s[start:end]
|
||||
|
||||
elif method == "lastIndexOf":
|
||||
s = str(recv)
|
||||
needle = str(args[0]) if args else ""
|
||||
if len(args) > 1 and args[1] is not None:
|
||||
try:
|
||||
start = int(args[1])
|
||||
except Exception:
|
||||
start = 0
|
||||
out = s.rfind(needle, start)
|
||||
else:
|
||||
out = s.rfind(needle)
|
||||
|
||||
elif method == "indexOf":
|
||||
s = str(recv)
|
||||
needle = str(args[0]) if args else ""
|
||||
if len(args) > 1 and args[1] is not None:
|
||||
try:
|
||||
start = int(args[1])
|
||||
except Exception:
|
||||
start = 0
|
||||
out = s.find(needle, start)
|
||||
else:
|
||||
out = s.find(needle)
|
||||
|
||||
else:
|
||||
# Unimplemented method -> no-op
|
||||
out = None
|
||||
|
||||
owner._set(regs, inst.get("dst"), out)
|
||||
|
||||
Reference in New Issue
Block a user