Every heap ref carries its heap tag, generation checks always on, rooted async values. Splash isolates get a domain table and curated minimal preludes; system modules under `mod` expand lazily on first touch. No wall-clock script caps: isolation and force-stop instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
94 lines
3.1 KiB
Rust
94 lines
3.1 KiB
Rust
//! Arithmetic on host objects (`ScriptVmBase::object_op_hook`): the VM has
|
|
//! none on objects (such a sum is NaN); a host can give `a op b` a meaning
|
|
//! for its own objects. Without a hook nothing changes, and a hook never
|
|
//! sees numbers, vectors or strings.
|
|
|
|
use makepad_script::*;
|
|
|
|
fn test_vm() -> ScriptVm<'static> {
|
|
let host = Box::leak(Box::new(ScriptVmHost::new(0i32, ())));
|
|
ScriptVm {
|
|
host: host.host_context(),
|
|
bx: Box::new(ScriptVmBase::new()),
|
|
}
|
|
}
|
|
|
|
const DOC: &str = "
|
|
let o = {k: 1}
|
|
{
|
|
add: o + 2
|
|
radd: 2 + o
|
|
sub: o - 2
|
|
mul: o * 2
|
|
div: o / 2
|
|
num: 1 + 2
|
|
vec: vec3(1, 2, 3) + vec3(1, 1, 1)
|
|
text: \"a\" + 1
|
|
plain: {k: 2} * 3
|
|
}
|
|
";
|
|
|
|
fn eval(vm: &mut ScriptVm) -> ScriptObject {
|
|
let v = vm.eval(ScriptMod::new("", "", "object_ops", 0, 0, &DOC, &[]));
|
|
v.as_object().expect("the document is an object")
|
|
}
|
|
|
|
fn field(vm: &ScriptVm, o: ScriptObject, name: &str) -> ScriptValue {
|
|
vm.bx.heap.value(o, LiveId::from_str(name).into(), NoTrap)
|
|
}
|
|
|
|
/// The test host: an object with `k` combines to `k * 1000 + op` (1 add,
|
|
/// 2 sub, 3 mul, 4 div) plus the other operand; objects with `k == 2` are
|
|
/// declined (the VM's own result stands).
|
|
fn hook(heap: &mut ScriptHeap, op: ScriptBinOp, a: ScriptValue, b: ScriptValue, _ip: ScriptIp) -> Option<ScriptValue> {
|
|
let (obj, other) = if a.as_object().is_some() { (a, b) } else { (b, a) };
|
|
let k = heap.value(obj.as_object()?, LiveId::from_str("k").into(), NoTrap).as_number()?;
|
|
if k == 2.0 {
|
|
return None;
|
|
}
|
|
let code = match op {
|
|
ScriptBinOp::Add => 1.0,
|
|
ScriptBinOp::Sub => 2.0,
|
|
ScriptBinOp::Mul => 3.0,
|
|
ScriptBinOp::Div => 4.0,
|
|
};
|
|
Some((k * 1000.0 + code + other.as_number().unwrap_or(0.0)).into())
|
|
}
|
|
|
|
#[test]
|
|
fn without_a_hook_object_arithmetic_stays_nan() {
|
|
let vm = &mut test_vm();
|
|
let o = eval(vm);
|
|
for name in ["add", "radd", "sub", "mul", "div", "plain"] {
|
|
assert!(field(vm, o, name).as_number().is_some_and(f64::is_nan), "{name}");
|
|
}
|
|
assert_eq!(field(vm, o, "num").as_number(), Some(3.0));
|
|
}
|
|
|
|
#[test]
|
|
fn a_hook_gives_object_arithmetic_a_meaning_and_nothing_else() {
|
|
let vm = &mut test_vm();
|
|
vm.set_object_op_hook(Some(hook));
|
|
let o = eval(vm);
|
|
let n = |name: &str| field(vm, o, name).as_number();
|
|
assert_eq!(n("add"), Some(1003.0));
|
|
assert_eq!(n("radd"), Some(1003.0));
|
|
assert_eq!(n("sub"), Some(1004.0));
|
|
assert_eq!(n("mul"), Some(1005.0));
|
|
assert_eq!(n("div"), Some(1006.0));
|
|
// Declined: the VM's own NaN.
|
|
assert!(n("plain").is_some_and(f64::is_nan));
|
|
// Numbers, vectors and strings never reach the hook.
|
|
assert_eq!(n("num"), Some(3.0));
|
|
let mut plain = test_vm();
|
|
let p = eval(&mut plain);
|
|
for name in ["vec", "text"] {
|
|
let (a, b) = (field(vm, o, name), field(&plain, p, name));
|
|
let show = |vm: &ScriptVm, v: ScriptValue| {
|
|
let mut out = String::new();
|
|
vm.bx.heap.to_debug_string(v, &mut Vec::new(), &mut out, false, 0);
|
|
out
|
|
};
|
|
assert_eq!(show(vm, a), show(&plain, b), "{name}");
|
|
}
|
|
}
|