Skip to main content

vm/
lib.rs

1//使用 cranelift 作为后端 直接 jit 解释脚本
2mod binary;
3mod memory;
4mod native;
5pub use native::{ANY, STD};
6
7mod fns;
8use anyhow::{Result, anyhow};
9pub use fns::{FnInfo, FnVariant};
10mod context;
11pub use context::BuildContext;
12
13mod rt;
14use cranelift::prelude::types;
15use dynamic::Type;
16pub use rt::JITRunTime;
17use smol_str::SmolStr;
18mod db_module;
19mod gpu_layout;
20mod gpu_module;
21mod http_module;
22mod llm_module;
23mod root_module;
24pub use gpu_layout::{GpuFieldLayout, GpuStructLayout};
25
26use std::sync::{Mutex, OnceLock, Weak};
27static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
28pub fn ptr_type() -> types::Type {
29    PTR_TYPE.get().cloned().unwrap()
30}
31
32pub fn get_type(ty: &Type) -> Result<types::Type> {
33    if ty.is_f64() {
34        Ok(types::F64)
35    } else if ty.is_f32() {
36        Ok(types::F32)
37    } else if ty.is_int() | ty.is_uint() {
38        match ty.width() {
39            1 => Ok(types::I8),
40            2 => Ok(types::I16),
41            4 => Ok(types::I32),
42            8 => Ok(types::I64),
43            _ => Err(anyhow!("非法类型 {:?}", ty)),
44        }
45    } else if let Type::Bool = ty {
46        Ok(types::I8)
47    } else {
48        Ok(ptr_type())
49    }
50}
51
52use compiler::Symbol;
53use cranelift::prelude::*;
54use cranelift_module::Module;
55
56pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
57    jit.add_all()?;
58    Ok(jit)
59}
60
61use std::sync::Arc;
62unsafe impl Send for JITRunTime {}
63unsafe impl Sync for JITRunTime {}
64
65pub(crate) fn with_vm_context<T>(context: *const Weak<Mutex<JITRunTime>>, f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
66    if context.is_null() {
67        return Err(anyhow!("VM context is null"));
68    }
69    let jit = unsafe { &*context }.upgrade().ok_or_else(|| anyhow!("VM context has expired"))?;
70    let vm = Vm { jit };
71    f(&vm)
72}
73
74fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
75    let def_id = jit.get_id(def)?;
76    if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
77        if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
78            fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
79        }
80    }
81    Ok(())
82}
83
84fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
85    jit.add_module(module);
86    for (name, arg_tys, ret_ty, fn_ptr) in fns {
87        let full_name = format!("{}::{}", module, name);
88        jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
89    }
90    jit.pop_module();
91    Ok(())
92}
93
94impl JITRunTime {
95    fn add_memory_runtime(&mut self) -> Result<()> {
96        self.native_symbols.write().unwrap().insert("__vm_scope_enter".to_string(), memory::scope_enter as *const () as usize);
97        self.native_symbols.write().unwrap().insert("__vm_scope_exit_void".to_string(), memory::scope_exit_void as *const () as usize);
98        self.native_symbols.write().unwrap().insert("__vm_scope_exit_dynamic".to_string(), memory::scope_exit_dynamic as *const () as usize);
99        self.native_symbols.write().unwrap().insert("__vm_scope_exit_bytes".to_string(), memory::scope_exit_bytes as *const () as usize);
100        self.native_symbols.write().unwrap().insert("__vm_struct_alloc".to_string(), native::struct_alloc as *const () as usize);
101        self.native_symbols.write().unwrap().insert("__vm_struct_from_ptr".to_string(), native::struct_from_ptr as *const () as usize);
102
103        let void_sig = self.get_sig(&[], Type::Void)?;
104        self.scope_enter_fn = Some(self.module.declare_function("__vm_scope_enter", cranelift_module::Linkage::Import, &void_sig)?);
105        self.scope_exit_void_fn = Some(self.module.declare_function("__vm_scope_exit_void", cranelift_module::Linkage::Import, &void_sig)?);
106
107        let dynamic_sig = self.get_sig(&[Type::Any], Type::Any)?;
108        self.scope_exit_dynamic_fn = Some(self.module.declare_function("__vm_scope_exit_dynamic", cranelift_module::Linkage::Import, &dynamic_sig)?);
109
110        let bytes_sig = self.get_sig(&[Type::Any, Type::I64], Type::Any)?;
111        self.scope_exit_bytes_fn = Some(self.module.declare_function("__vm_scope_exit_bytes", cranelift_module::Linkage::Import, &bytes_sig)?);
112
113        let struct_alloc_sig = self.get_sig(&[Type::I64], Type::Any)?;
114        self.struct_alloc_fn = Some(self.module.declare_function("__vm_struct_alloc", cranelift_module::Linkage::Import, &struct_alloc_sig)?);
115
116        let struct_from_ptr_sig = self.get_sig(&[Type::I64, Type::I64], Type::Any)?;
117        self.struct_from_ptr_fn = Some(self.module.declare_function("__vm_struct_from_ptr", cranelift_module::Linkage::Import, &struct_from_ptr_sig)?);
118        Ok(())
119    }
120
121    pub fn add_module(&mut self, name: &str) {
122        self.compiler.symbols.add_module(name.into());
123    }
124
125    pub fn pop_module(&mut self) {
126        self.compiler.symbols.pop_module();
127    }
128
129    pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
130        self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
131    }
132
133    pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
134        match self.get_id(name) {
135            Ok(id) => Ok(id),
136            Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
137        }
138    }
139
140    pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
141        self.add_module(module);
142        let full_name = format!("{}::{}", module, name);
143        let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
144        self.pop_module();
145        result
146    }
147
148    pub(crate) fn add_native_module_context_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
149        self.add_module(module);
150        let full_name = format!("{}::{}", module, name);
151        let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
152        self.pop_module();
153        result
154    }
155
156    pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
157        self.add_empty_type(def)?;
158        let full_name = format!("{}::{}", def, method);
159        let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
160        add_method_field(self, def, method, id)?;
161        Ok(id)
162    }
163
164    pub fn add_std(&mut self) -> Result<()> {
165        if self.compiler.symbols.get_id("std::print").is_ok() {
166            return Ok(());
167        }
168        self.add_module("std");
169        for (name, arg_tys, ret_ty, fn_ptr) in STD {
170            self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
171        }
172        self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
173        Ok(())
174    }
175
176    pub fn add_any(&mut self) -> Result<()> {
177        if self.compiler.symbols.get_id("Any").is_ok() && self.compiler.symbols.get_id("Any::is_map").is_ok() {
178            return Ok(());
179        }
180        for (name, arg_tys, ret_ty, fn_ptr) in ANY {
181            let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
182            self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
183        }
184        Ok(())
185    }
186
187    pub fn add_vec(&mut self) -> Result<()> {
188        self.add_empty_type("Vec")?;
189        let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
190        self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
191            if let Some(ctx) = ctx {
192                let width = ctx.builder.ins().iconst(types::I64, 4);
193                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
194                let final_addr = ctx.builder.ins().iadd(args[0], offset_val); // base + (i*4)
195                let dest = ctx.builder.ins().imul(args[2], width);
196                let dest_addr = ctx.builder.ins().iadd(args[0], dest); // base + (i*4)
197                let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
198                let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
199                ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
200                ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
201            }
202            Err(anyhow!("无返回值"))
203        })?;
204
205        self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
206            if let Some(ctx) = ctx {
207                let width = ctx.builder.ins().iconst(types::I64, 4);
208                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
209                let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
210                Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
211            } else {
212                Ok((None, Type::I32))
213            }
214        })?;
215        Ok(())
216    }
217
218    pub fn add_llm(&mut self) -> Result<()> {
219        add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
220    }
221
222    pub fn add_root(&mut self) -> Result<()> {
223        add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
224        self.add_native_module_context_ptr("root", "add_fn", &[Type::Any, Type::Any], Type::Bool, root_module::root_add_fn_with_vm as *const u8)?;
225        Ok(())
226    }
227
228    pub fn add_http(&mut self) -> Result<()> {
229        add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)
230    }
231
232    pub fn add_db(&mut self) -> Result<()> {
233        add_native_module_fns(self, "db", &db_module::DB_NATIVE)
234    }
235
236    pub fn add_gpu(&mut self) -> Result<()> {
237        add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
238    }
239
240    pub fn add_all(&mut self) -> Result<()> {
241        self.add_std()?;
242        self.add_any()?;
243        self.add_vec()?;
244        self.add_llm()?;
245        self.add_root()?;
246        self.add_http()?;
247        self.add_db()?;
248        self.add_gpu()?;
249        Ok(())
250    }
251}
252
253#[derive(Clone)]
254pub struct Vm {
255    jit: Arc<Mutex<JITRunTime>>,
256}
257
258#[derive(Clone)]
259pub struct CompiledFn {
260    ptr: usize,
261    ret: Type,
262    owner: Vm,
263}
264
265impl CompiledFn {
266    pub fn ptr(&self) -> *const u8 {
267        self.ptr as *const u8
268    }
269
270    pub fn ret_ty(&self) -> &Type {
271        &self.ret
272    }
273
274    pub fn owner(&self) -> &Vm {
275        &self.owner
276    }
277}
278
279impl Vm {
280    pub fn new() -> Self {
281        dynamic::set_dynamic_return_handler(memory::take_dynamic_return);
282        let jit = Arc::new(Mutex::new(JITRunTime::new(|_| {})));
283        {
284            let mut guard = jit.lock().unwrap();
285            guard.set_owner(Arc::downgrade(&jit));
286            guard.add_memory_runtime().expect("register VM memory runtime");
287            guard.add_std().expect("register VM std runtime");
288            guard.add_any().expect("register VM Any runtime");
289        }
290        Self { jit }
291    }
292
293    pub fn with_all() -> Result<Self> {
294        let vm = Self::new();
295        vm.add_all()?;
296        Ok(vm)
297    }
298
299    pub fn add_module(&self, name: &str) {
300        self.jit.lock().unwrap().add_module(name)
301    }
302
303    pub fn pop_module(&self) {
304        self.jit.lock().unwrap().pop_module()
305    }
306
307    pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
308        self.jit.lock().unwrap().add_type(name, ty, is_pub)
309    }
310
311    pub fn add_empty_type(&self, name: &str) -> Result<u32> {
312        self.jit.lock().unwrap().add_empty_type(name)
313    }
314
315    pub fn add_std(&self) -> Result<()> {
316        self.jit.lock().unwrap().add_std()
317    }
318
319    pub fn add_any(&self) -> Result<()> {
320        self.jit.lock().unwrap().add_any()
321    }
322
323    pub fn add_vec(&self) -> Result<()> {
324        self.jit.lock().unwrap().add_vec()
325    }
326
327    pub fn add_llm(&self) -> Result<()> {
328        self.jit.lock().unwrap().add_llm()
329    }
330
331    pub fn add_root(&self) -> Result<()> {
332        self.jit.lock().unwrap().add_root()
333    }
334
335    pub fn add_http(&self) -> Result<()> {
336        self.jit.lock().unwrap().add_http()
337    }
338
339    pub fn add_db(&self) -> Result<()> {
340        self.jit.lock().unwrap().add_db()
341    }
342
343    pub fn add_gpu(&self) -> Result<()> {
344        self.jit.lock().unwrap().add_gpu()
345    }
346
347    pub fn add_all(&self) -> Result<()> {
348        self.jit.lock().unwrap().add_all()
349    }
350
351    pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
352        self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
353    }
354
355    pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
356        self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
357    }
358
359    pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
360        self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
361    }
362
363    pub fn add_inline(&self, name: &str, args: Vec<Type>, ret: Type, f: fn(Option<&mut BuildContext>, Vec<Value>) -> Result<(Option<Value>, Type)>) -> Result<u32> {
364        self.jit.lock().unwrap().add_inline(name, args, ret, f)
365    }
366
367    pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
368        self.jit.lock().unwrap().import_code(name, code)
369    }
370
371    pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
372        self.jit.lock().unwrap().compiler.import_file(name, path)?;
373        Ok(())
374    }
375
376    pub fn import(&self, name: &str, path: &str) -> Result<()> {
377        if root::contains(path) {
378            let code = root::get(path).unwrap();
379            if code.is_str() {
380                self.import_code(name, code.as_str().as_bytes().to_vec())
381            } else {
382                self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
383            }
384        } else {
385            self.import_file(name, path)
386        }
387    }
388
389    pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
390        self.jit.lock().unwrap().get_type(name, arg_tys)
391    }
392
393    pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
394        self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
395    }
396
397    pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
398        let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
399        Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
400    }
401
402    pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
403        self.jit.lock().unwrap().load(code, arg_name)
404    }
405
406    pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
407        Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
408    }
409
410    pub fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> Result<GpuStructLayout> {
411        let jit = self.jit.lock().unwrap();
412        GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
413    }
414
415    pub fn disassemble(&self, name: &str) -> Result<String> {
416        self.jit.lock().unwrap().compiler.symbols.disassemble(name)
417    }
418
419    #[cfg(feature = "ir-disassembly")]
420    pub fn disassemble_ir(&self, name: &str) -> Result<String> {
421        self.jit.lock().unwrap().disassemble_ir(name)
422    }
423}
424
425impl Default for Vm {
426    fn default() -> Self {
427        Self::new()
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use super::Vm;
434    use dynamic::{Dynamic, ToJson, Type};
435
436    extern "C" fn math_double(value: i64) -> i64 {
437        value * 2
438    }
439
440    #[test]
441    fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
442        let vm = Vm::new();
443        vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
444        vm.import_code(
445            "vm_dynamic_native",
446            br#"
447            pub fn run(value: i64) {
448                math::double(value)
449            }
450            "#
451            .to_vec(),
452        )?;
453
454        let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
455        assert_eq!(compiled.ret_ty(), &Type::I64);
456        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
457        assert_eq!(run(21), 42);
458        Ok(())
459    }
460
461    #[test]
462    fn vm_new_registers_std_and_any() -> anyhow::Result<()> {
463        let vm = Vm::new();
464        vm.add_std()?;
465        vm.add_any()?;
466        assert_eq!(vm.infer("std::print", &[Type::Any])?, Type::Void);
467
468        vm.import_code(
469            "vm_new_default_any",
470            br#"
471            pub fn has_items(content) {
472                if content.is_map() {
473                    if content.contains("items") {
474                        return content.items.len() > 0;
475                    }
476                }
477                false
478            }
479            "#
480            .to_vec(),
481        )?;
482
483        assert_eq!(vm.infer("vm_new_default_any::has_items", &[Type::Any])?, Type::Bool);
484        let compiled = vm.get_fn("vm_new_default_any::has_items", &[Type::Any])?;
485        assert_eq!(compiled.ret_ty(), &Type::Bool);
486        Ok(())
487    }
488
489    #[test]
490    fn nested_struct_arg_return_struct_field_is_static_field_access() -> anyhow::Result<()> {
491        let vm = Vm::with_all()?;
492        vm.import_code(
493            "vm_nested_struct_return_field",
494            br#"
495            pub struct Inner {
496                value: i64,
497            }
498
499            pub struct RoleMini {
500                inner: Inner,
501                hp: i64,
502            }
503
504            pub struct TeamMini {
505                role: RoleMini,
506            }
507
508            pub struct BigSummary {
509                winner: i64,
510                loser: i64,
511            }
512
513            pub fn make_big_with_team(team: TeamMini) {
514                let score = team.role.inner.value;
515                BigSummary{winner: score, loser: 0}
516            }
517
518            pub fn read_team_winner_direct() {
519                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
520                make_big_with_team(team).winner
521            }
522
523            pub fn read_team_winner_bound() {
524                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
525                let summary = make_big_with_team(team);
526                summary.winner
527            }
528            "#
529            .to_vec(),
530        )?;
531
532        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_direct", &[])?;
533        assert_eq!(compiled.ret_ty(), &Type::I64);
534        let direct: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
535        assert_eq!(direct(), 9);
536
537        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_bound", &[])?;
538        assert_eq!(compiled.ret_ty(), &Type::I64);
539        let bound: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
540        assert_eq!(bound(), 9);
541        Ok(())
542    }
543
544    #[test]
545    fn any_push_does_not_consume_reused_value() -> anyhow::Result<()> {
546        let vm = Vm::with_all()?;
547        vm.import_code(
548            "vm_any_push_reused_value",
549            br#"
550            pub fn run() {
551                let role_id = "acct_role_2";
552                let updated = [];
553                updated.push(role_id);
554                {
555                    ok: true,
556                    user_id: role_id,
557                    first: updated.get_idx(0)
558                }
559            }
560            "#
561            .to_vec(),
562        )?;
563
564        let compiled = vm.get_fn("vm_any_push_reused_value::run", &[])?;
565        assert_eq!(compiled.ret_ty(), &Type::Any);
566        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
567        let result = unsafe { &*run() };
568        assert_eq!(result.get_dynamic("ok").and_then(|value| value.as_bool()), Some(true));
569        assert_eq!(result.get_dynamic("user_id").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
570        assert_eq!(result.get_dynamic("first").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
571        Ok(())
572    }
573
574    #[test]
575    fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
576        let vm = Vm::with_all()?;
577        vm.import_code(
578            "vm_string_compare_any",
579            br#"
580            pub fn any_ne_empty(chat_path) {
581                chat_path != ""
582            }
583            "#
584            .to_vec(),
585        )?;
586
587        let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
588        assert_eq!(compiled.ret_ty(), &Type::Bool);
589
590        let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
591        let empty = Dynamic::from("");
592        let non_empty = Dynamic::from("chat");
593
594        assert!(!any_ne_empty(&empty));
595        assert!(any_ne_empty(&non_empty));
596        Ok(())
597    }
598
599    #[test]
600    fn compares_bool_values_and_bool_literals() -> anyhow::Result<()> {
601        let vm = Vm::with_all()?;
602        vm.import_code(
603            "vm_bool_compare",
604            br#"
605            pub fn eq_true(value: bool) {
606                value == true
607            }
608
609            pub fn ne_false(value: bool) {
610                value != false
611            }
612
613            pub fn literal_left(value: bool) {
614                true == value
615            }
616
617            pub fn eq_pair(left: bool, right: bool) {
618                left == right
619            }
620
621            pub fn logic_pair(left: bool, right: bool) {
622                (left && right) || (left == true && right != false)
623            }
624            "#
625            .to_vec(),
626        )?;
627
628        let compiled = vm.get_fn("vm_bool_compare::eq_true", &[Type::Bool])?;
629        assert_eq!(compiled.ret_ty(), &Type::Bool);
630        let eq_true: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
631        assert!(eq_true(true));
632        assert!(!eq_true(false));
633
634        let compiled = vm.get_fn("vm_bool_compare::ne_false", &[Type::Bool])?;
635        assert_eq!(compiled.ret_ty(), &Type::Bool);
636        let ne_false: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
637        assert!(ne_false(true));
638        assert!(!ne_false(false));
639
640        let compiled = vm.get_fn("vm_bool_compare::literal_left", &[Type::Bool])?;
641        assert_eq!(compiled.ret_ty(), &Type::Bool);
642        let literal_left: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
643        assert!(literal_left(true));
644        assert!(!literal_left(false));
645
646        let compiled = vm.get_fn("vm_bool_compare::eq_pair", &[Type::Bool, Type::Bool])?;
647        assert_eq!(compiled.ret_ty(), &Type::Bool);
648        let eq_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
649        assert!(eq_pair(true, true));
650        assert!(eq_pair(false, false));
651        assert!(!eq_pair(true, false));
652        assert!(!eq_pair(false, true));
653
654        let compiled = vm.get_fn("vm_bool_compare::logic_pair", &[Type::Bool, Type::Bool])?;
655        assert_eq!(compiled.ret_ty(), &Type::Bool);
656        let logic_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
657        assert!(logic_pair(true, true));
658        assert!(!logic_pair(true, false));
659        assert!(!logic_pair(false, true));
660        assert!(!logic_pair(false, false));
661        Ok(())
662    }
663
664    #[test]
665    fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
666        let vm = Vm::with_all()?;
667        vm.import_code(
668            "vm_parenthesized_method_call",
669            br#"
670            pub fn run(value) {
671                (value + 2).to_i64()
672            }
673            "#
674            .to_vec(),
675        )?;
676
677        let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
678        assert_eq!(compiled.ret_ty(), &Type::I64);
679        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
680        let value = Dynamic::from(40i64);
681
682        assert_eq!(run(&value), 42);
683        Ok(())
684    }
685
686    #[test]
687    fn casts_any_float_to_i32_without_zeroing() -> anyhow::Result<()> {
688        let vm = Vm::with_all()?;
689        vm.import_code(
690            "vm_any_float_to_i32",
691            br#"
692            pub fn direct(value) {
693                value as i32
694            }
695
696            pub fn map_field(value) {
697                let field = value.v;
698                field as i32
699            }
700
701            pub fn damage(attacker, def_rate) {
702                let x = attacker.atk * (1.0 - def_rate);
703                x as i32
704            }
705            "#
706            .to_vec(),
707        )?;
708
709        let compiled = vm.get_fn("vm_any_float_to_i32::direct", &[Type::Any])?;
710        assert_eq!(compiled.ret_ty(), &Type::I32);
711        let direct: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
712        let value = Dynamic::from(9.5f64);
713        assert_eq!(direct(&value), 9);
714
715        let compiled = vm.get_fn("vm_any_float_to_i32::map_field", &[Type::Any])?;
716        assert_eq!(compiled.ret_ty(), &Type::I32);
717        let map_field: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
718        let value = dynamic::map!("v"=> 9.5f64);
719        assert_eq!(map_field(&value), 9);
720
721        let compiled = vm.get_fn("vm_any_float_to_i32::damage", &[Type::Any, Type::Any])?;
722        assert_eq!(compiled.ret_ty(), &Type::I32);
723        let damage: extern "C" fn(*const Dynamic, *const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
724        let attacker = dynamic::map!("atk"=> 64i64);
725        let def_rate = Dynamic::from(0.17f64);
726        assert_eq!(damage(&attacker, &def_rate), 53);
727        Ok(())
728    }
729
730    #[test]
731    fn binary_imm_promotes_integer_literals_for_float_left_values() -> anyhow::Result<()> {
732        let vm = Vm::with_all()?;
733        vm.import_code(
734            "vm_float_binary_imm",
735            br#"
736            pub fn add_f32(value: f32) {
737                value + 1i32
738            }
739
740            pub fn sub_f32(value: f32) {
741                value - 1i32
742            }
743
744            pub fn mul_f32(value: f32) {
745                value * 2i32
746            }
747
748            pub fn div_f32(value: f32) {
749                value / 2i32
750            }
751
752            pub fn gt_f32(value: f32) {
753                value > 2i32
754            }
755            "#
756            .to_vec(),
757        )?;
758
759        let compiled = vm.get_fn("vm_float_binary_imm::add_f32", &[Type::F32])?;
760        assert_eq!(compiled.ret_ty(), &Type::F32);
761        let add_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
762        assert_eq!(add_f32(2.5), 3.5);
763
764        let compiled = vm.get_fn("vm_float_binary_imm::sub_f32", &[Type::F32])?;
765        assert_eq!(compiled.ret_ty(), &Type::F32);
766        let sub_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
767        assert_eq!(sub_f32(2.5), 1.5);
768
769        let compiled = vm.get_fn("vm_float_binary_imm::mul_f32", &[Type::F32])?;
770        assert_eq!(compiled.ret_ty(), &Type::F32);
771        let mul_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
772        assert_eq!(mul_f32(2.5), 5.0);
773
774        let compiled = vm.get_fn("vm_float_binary_imm::div_f32", &[Type::F32])?;
775        assert_eq!(compiled.ret_ty(), &Type::F32);
776        let div_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
777        assert_eq!(div_f32(5.0), 2.5);
778
779        let compiled = vm.get_fn("vm_float_binary_imm::gt_f32", &[Type::F32])?;
780        assert_eq!(compiled.ret_ty(), &Type::Bool);
781        let gt_f32: extern "C" fn(f32) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
782        assert!(gt_f32(2.5));
783        assert!(!gt_f32(1.5));
784        Ok(())
785    }
786
787    #[test]
788    fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
789        let vm = Vm::with_all()?;
790        vm.import_code(
791            "vm_any_keys",
792            br#"
793            pub fn map_keys(value) {
794                let keys = value.keys();
795                keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
796            }
797
798            pub fn non_map_keys(value) {
799                value.keys().len() == 0
800            }
801            "#
802            .to_vec(),
803        )?;
804
805        let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
806        assert_eq!(compiled.ret_ty(), &Type::Bool);
807        let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
808        let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
809        assert!(map_keys(&value));
810
811        let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
812        assert_eq!(compiled.ret_ty(), &Type::Bool);
813        let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
814        let value = Dynamic::from("alpha");
815        assert!(non_map_keys(&value));
816        Ok(())
817    }
818
819    #[test]
820    fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
821        let vm = Vm::with_all()?;
822        vm.import_code(
823            "vm_string_compare_imm",
824            br#"
825            pub fn int_eq_str(value: i64) {
826                value == "42"
827            }
828
829            pub fn int_to_str(value: i64) {
830                value + ""
831            }
832            "#
833            .to_vec(),
834        )?;
835
836        let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
837        assert_eq!(compiled.ret_ty(), &Type::Bool);
838
839        let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
840
841        let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
842        assert_eq!(compiled.ret_ty(), &Type::Any);
843        let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
844        let text = int_to_str(42);
845        assert_eq!(unsafe { &*text }.as_str(), "42");
846
847        assert!(int_eq_str(42));
848        assert!(!int_eq_str(7));
849        Ok(())
850    }
851
852    #[test]
853    fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
854        let vm = Vm::with_all()?;
855        vm.import_code(
856            "vm_string_concat_integer",
857            br#"
858            pub fn idx_key(idx: i64) {
859                "" + idx
860            }
861
862            pub fn level_text(level: i64) {
863                "" + level + " level"
864            }
865
866            pub fn gold_text(currency) {
867                "" + currency.gold
868            }
869            "#
870            .to_vec(),
871        )?;
872
873        let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
874        let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
875        let result = unsafe { &*idx_key(7) };
876        assert_eq!(result.as_str(), "7");
877
878        let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
879        let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
880        let result = unsafe { &*level_text(12) };
881        assert_eq!(result.as_str(), "12 level");
882
883        let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
884        let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
885        let currency = dynamic::map!("gold"=> 345i64);
886        let result = unsafe { &*gold_text(&currency) };
887        assert_eq!(result.as_str(), "345");
888        Ok(())
889    }
890
891    #[test]
892    fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
893        let vm = Vm::with_all()?;
894        vm.import_code(
895            "vm_string_concat_to_i64",
896            br#"
897            pub fn run(idx: i64) {
898                ("" + idx) as i64
899            }
900            "#
901            .to_vec(),
902        )?;
903
904        let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
905        assert_eq!(compiled.ret_ty(), &Type::I64);
906        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
907        assert_eq!(run(7), 0);
908        Ok(())
909    }
910
911    #[test]
912    fn unifies_explicit_return_and_tail_integer_widths() -> anyhow::Result<()> {
913        let vm = Vm::with_all()?;
914        vm.import_code(
915            "vm_return_integer_widths",
916            br#"
917            pub fn selected(flag, slot) {
918                if flag {
919                    return slot;
920                }
921                0
922            }
923            "#
924            .to_vec(),
925        )?;
926
927        let compiled = vm.get_fn("vm_return_integer_widths::selected", &[Type::Bool, Type::I64])?;
928        assert_eq!(compiled.ret_ty(), &Type::I64);
929        let selected: extern "C" fn(bool, i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
930
931        assert_eq!(selected(true, 7), 7);
932        assert_eq!(selected(false, 7), 0);
933        Ok(())
934    }
935
936    #[test]
937    fn root_contains_string_concat_is_bool_condition() -> anyhow::Result<()> {
938        let vm = Vm::with_all()?;
939        vm.import_code(
940            "vm_root_contains_condition",
941            br#"
942            pub fn exists(user_id) {
943                if root::contains("redis/user/" + user_id) {
944                    return 1;
945                }
946                0
947            }
948            "#
949            .to_vec(),
950        )?;
951
952        assert_eq!(vm.infer("root::contains", &[Type::Any])?, Type::Bool);
953        let compiled = vm.get_fn("vm_root_contains_condition::exists", &[Type::Any])?;
954        assert_eq!(compiled.ret_ty(), &Type::I32);
955        Ok(())
956    }
957
958    #[test]
959    fn root_add_map_can_be_printed() -> anyhow::Result<()> {
960        let vm = Vm::with_all()?;
961        assert_eq!(vm.infer("root::add_map", &[Type::Any])?, Type::Bool);
962        vm.import_code(
963            "vm_root_add_map_print",
964            br#"
965            pub fn run() {
966                print(root::add_map("local/world_handlers/til_map_novicevillage"));
967            }
968            "#
969            .to_vec(),
970        )?;
971
972        let compiled = vm.get_fn("vm_root_add_map_print::run", &[])?;
973        assert!(compiled.ret_ty().is_void());
974        Ok(())
975    }
976
977    #[test]
978    fn std_log_accepts_any_and_returns_void() -> anyhow::Result<()> {
979        let vm = Vm::with_all()?;
980        vm.import_code(
981            "vm_std_log",
982            br#"
983            pub fn run(value) {
984                log({ ok: true, value: value });
985            }
986            "#
987            .to_vec(),
988        )?;
989
990        let compiled = vm.get_fn("vm_std_log::run", &[Type::Any])?;
991        assert!(compiled.ret_ty().is_void());
992        let run: extern "C" fn(*const Dynamic) = unsafe { std::mem::transmute(compiled.ptr()) };
993        let value = Dynamic::from(7i64);
994        run(&value);
995        Ok(())
996    }
997
998    #[test]
999    fn unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
1000        let vm = Vm::with_all()?;
1001        vm.import_code(
1002            "vm_unary_not_any_loop_var",
1003            br#"
1004            pub fn count_missing(flags) {
1005                let missing = 0;
1006                for exists in flags {
1007                    if !exists {
1008                        missing = missing + 1;
1009                    }
1010                }
1011                missing
1012            }
1013            "#
1014            .to_vec(),
1015        )?;
1016
1017        let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
1018        assert_eq!(compiled.ret_ty(), &Type::I32);
1019        Ok(())
1020    }
1021
1022    #[test]
1023    fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
1024        let vm = Vm::with_all()?;
1025        vm.import_code(
1026            "vm_semicolon_tail_void",
1027            br#"
1028            pub fn send_role_select(idx, account_id, selected_slot) {
1029                root::send("local/ui/send_dialog", {
1030                    idx: idx,
1031                    account_id: account_id,
1032                    selected_slot: selected_slot
1033                });
1034            }
1035            "#
1036            .to_vec(),
1037        )?;
1038
1039        let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
1040        assert_eq!(compiled.ret_ty(), &Type::Void);
1041        Ok(())
1042    }
1043
1044    #[test]
1045    fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
1046        let vm = Vm::with_all()?;
1047        vm.import_code(
1048            "vm_bare_return_conflict",
1049            br#"
1050            pub fn run(flag) {
1051                if flag {
1052                    return;
1053                }
1054                1
1055            }
1056            "#
1057            .to_vec(),
1058        )?;
1059
1060        let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
1061            Ok(_) => panic!("expected mismatched return types to fail"),
1062            Err(err) => err,
1063        };
1064        assert!(format!("{err:#}").contains("返回类型不一致"));
1065        Ok(())
1066    }
1067
1068    #[test]
1069    fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
1070        let vm = Vm::with_all()?;
1071        vm.import_code(
1072            "vm_root_get_dynamic_concat",
1073            br#"
1074            pub fn get_action(req) {
1075                root::get("local/game/panel_actions/" + req.idx)
1076            }
1077            "#
1078            .to_vec(),
1079        )?;
1080
1081        root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
1082        let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
1083        assert_eq!(compiled.ret_ty(), &Type::Any);
1084        let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1085        let req = dynamic::map!("idx"=> 7i64);
1086        let result = unsafe { &*get_action(&req) };
1087
1088        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
1089        Ok(())
1090    }
1091
1092    #[test]
1093    fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
1094        let vm = Vm::with_all()?;
1095        vm.import_code(
1096            "vm_registered_panel_action",
1097            br#"
1098            pub fn panel_action(req) {
1099                root::get("local/game/panel_actions/" + req.idx)
1100            }
1101
1102            pub fn register() {
1103                root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
1104            }
1105            "#
1106            .to_vec(),
1107        )?;
1108
1109        let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
1110        assert_eq!(compiled.ret_ty(), &Type::Bool);
1111        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1112        assert!(register());
1113        Ok(())
1114    }
1115
1116    #[test]
1117    fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
1118        let vm = Vm::with_all()?;
1119        vm.import_code(
1120            "vm_registered_string_concat",
1121            br#"
1122            pub fn send_panel(idx: i64) {
1123                let idx_key = "" + idx;
1124                idx_key
1125            }
1126            "#
1127            .to_vec(),
1128        )?;
1129
1130        assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
1131        Ok(())
1132    }
1133
1134    #[test]
1135    fn root_send_idx_returns_handler_value() -> anyhow::Result<()> {
1136        fn echo_handler(msg: Dynamic) -> Dynamic {
1137            dynamic::map!("type"=> "echo", "id"=> msg.get_dynamic("id").unwrap_or(Dynamic::Null))
1138        }
1139
1140        let vm = Vm::with_all()?;
1141        vm.import_code(
1142            "vm_root_send_idx_return",
1143            br#"
1144            pub fn call(req) {
1145                root::send_idx("local/send_idx_return_handlers", 0, req)
1146            }
1147            "#
1148            .to_vec(),
1149        )?;
1150
1151        root::add_list("local/send_idx_return_handlers")?;
1152        let (mount, name) = root::get_mount("local/send_idx_return_handlers")?;
1153        mount.push(name, root::Object::Native(echo_handler))?;
1154
1155        assert_eq!(vm.infer("root::send_idx", &[Type::Any, Type::I64, Type::Any])?, Type::Any);
1156        let compiled = vm.get_fn("vm_root_send_idx_return::call", &[Type::Any])?;
1157        assert_eq!(compiled.ret_ty(), &Type::Any);
1158        let call: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1159        let req = dynamic::map!("id"=> 42i64);
1160        let result = unsafe { &*call(&req) };
1161
1162        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("echo".to_string()));
1163        assert_eq!(result.get_dynamic("id").and_then(|value| value.as_int()), Some(42));
1164        Ok(())
1165    }
1166
1167    #[test]
1168    fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
1169        let vm = Vm::with_all()?;
1170        vm.import_code(
1171            "vm_public_hotspots",
1172            br#"
1173            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1174                {
1175                    path: action_map_path,
1176                    panel_id: panel_id,
1177                    action_id: action_id,
1178                    id: hotspot.id
1179                }
1180            }
1181
1182            pub fn public_hotspots(idx, panel_id, hotspots) {
1183                let idx_key = "" + idx;
1184                let action_map_path = "local/game/panel_actions/" + idx_key;
1185
1186                let existing_action_map = root::get(action_map_path);
1187                if !existing_action_map.is_map() {
1188                    root::add_map(action_map_path);
1189                }
1190
1191                if hotspots.is_map() {
1192                    let public_items = {};
1193                    for action_id in hotspots.keys() {
1194                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1195                    }
1196                    return public_items;
1197                }
1198
1199                let public_items = [];
1200                let i = 0;
1201                while i < hotspots.len() {
1202                    let hotspot = hotspots.get_idx(i);
1203                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1204                    public_items.push(item);
1205                    i = i + 1;
1206                }
1207
1208                public_items
1209            }
1210            "#
1211            .to_vec(),
1212        )?;
1213
1214        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
1215        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
1216        Ok(())
1217    }
1218
1219    #[test]
1220    fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
1221        let vm = Vm::with_all()?;
1222        vm.import_code(
1223            "vm_send_panel_public_hotspots",
1224            br#"
1225            pub fn ok(value) {
1226                value
1227            }
1228
1229            pub fn panel_from_node(req) {
1230                {
1231                    panel_id: req.panel_id,
1232                    hotspots: req.hotspots
1233                }
1234            }
1235
1236            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1237                {
1238                    path: action_map_path,
1239                    panel_id: panel_id,
1240                    action_id: action_id,
1241                    id: hotspot.id
1242                }
1243            }
1244
1245            pub fn public_hotspots(idx, panel_id, hotspots) {
1246                let idx_key = "" + idx;
1247                let action_map_path = "local/game/panel_actions/" + idx_key;
1248
1249                let existing_action_map = root::get(action_map_path);
1250                if !existing_action_map.is_map() {
1251                    root::add_map(action_map_path);
1252                }
1253
1254                if hotspots.is_map() {
1255                    let public_items = {};
1256                    for action_id in hotspots.keys() {
1257                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1258                    }
1259                    return public_items;
1260                }
1261
1262                let public_items = [];
1263                let i = 0;
1264                while i < hotspots.len() {
1265                    let hotspot = hotspots.get_idx(i);
1266                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1267                    public_items.push(item);
1268                    i = i + 1;
1269                }
1270
1271                public_items
1272            }
1273
1274            pub fn send_panel(req) {
1275                let panel = req.panel;
1276                if !panel.is_map() {
1277                    panel = panel_from_node(req);
1278                }
1279                if !panel.is_map() {
1280                    return ok({
1281                        id: 4,
1282                        type: "panel_rejected",
1283                        reason: "invalid panel"
1284                    });
1285                }
1286                panel.id = 4;
1287                panel.idx = req.idx;
1288                if !panel.contains("type") {
1289                    panel.type = "panel";
1290                }
1291                if panel.contains("hotspots") {
1292                    panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
1293                }
1294                root::send_idx("local/ws", req.idx, panel);
1295                ok({
1296                    id: 4,
1297                    type: "panel",
1298                    panel_id: panel.panel_id
1299                })
1300            }
1301            "#
1302            .to_vec(),
1303        )?;
1304
1305        let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
1306        assert_eq!(compiled.ret_ty(), &Type::Any);
1307        let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1308        let req = dynamic::map!(
1309            "idx"=> 7i64,
1310            "panel"=> dynamic::map!(
1311                "panel_id"=> "main",
1312                "hotspots"=> dynamic::map!(
1313                    "open"=> dynamic::map!("id"=> "open")
1314                )
1315            )
1316        );
1317        let result = unsafe { &*send_panel(&req) };
1318
1319        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
1320        assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
1321        Ok(())
1322    }
1323
1324    #[test]
1325    fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
1326        let vm = Vm::with_all()?;
1327        vm.import_code(
1328            "vm_string_concat_map_key",
1329            br##"
1330            pub fn write_action(action_map, panel_id, action_id, action) {
1331                action_map[panel_id + "#" + action_id] = action;
1332                action_map[panel_id + "#" + action_id]
1333            }
1334            "##
1335            .to_vec(),
1336        )?;
1337
1338        let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
1339        let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1340        let action_map = dynamic::map!();
1341        let panel_id: Dynamic = "panel".into();
1342        let action_id: Dynamic = "open".into();
1343        let action = dynamic::map!("id"=> "open");
1344
1345        let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
1346
1347        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1348        assert_eq!(action_map.get_dynamic("panel#open").and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1349        Ok(())
1350    }
1351
1352    #[test]
1353    fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
1354        let vm = Vm::with_all()?;
1355        vm.import_code(
1356            "vm_get_key_string_concat_key",
1357            br##"
1358            pub fn read_action(action_map, panel_id, action_id) {
1359                let action_key = panel_id + "#" + action_id;
1360                action_map.get_key(action_key)
1361            }
1362            "##
1363            .to_vec(),
1364        )?;
1365
1366        let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1367        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1368        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1369        let panel_id: Dynamic = "panel".into();
1370        let action_id: Dynamic = "open".into();
1371
1372        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1373
1374        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1375        Ok(())
1376    }
1377
1378    #[test]
1379    fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
1380        let vm = Vm::with_all()?;
1381        vm.import_code(
1382            "vm_get_key_helper_string_key",
1383            br##"
1384            pub fn make_action_key(panel_id, action_id) {
1385                panel_id + "#" + action_id
1386            }
1387
1388            pub fn read_action(action_map, panel_id, action_id) {
1389                let action_key = make_action_key(panel_id, action_id);
1390                let action = action_map.get_key(action_key);
1391                action
1392            }
1393            "##
1394            .to_vec(),
1395        )?;
1396
1397        let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1398        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1399        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1400        let panel_id: Dynamic = "panel".into();
1401        let action_id: Dynamic = "open".into();
1402
1403        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1404
1405        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1406        Ok(())
1407    }
1408
1409    #[test]
1410    fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
1411        let vm = Vm::with_all()?;
1412        vm.import_code(
1413            "vm_del_key_string_key",
1414            br##"
1415            pub fn remove_action(action_map, panel_id, action_id) {
1416                let action_key = panel_id + "#" + action_id;
1417                let removed = action_map.del_key(action_key);
1418                [removed, action_map.get_key(action_key)]
1419            }
1420            "##
1421            .to_vec(),
1422        )?;
1423
1424        let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
1425        let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1426        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1427        let panel_id: Dynamic = "panel".into();
1428        let action_id: Dynamic = "open".into();
1429
1430        let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
1431
1432        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1433        assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
1434        assert!(action_map.get_dynamic("panel#open").is_none());
1435        Ok(())
1436    }
1437
1438    #[test]
1439    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
1440        let vm = Vm::with_all()?;
1441        vm.import_code(
1442            "vm_dynamic_field_or",
1443            r#"
1444            pub fn next_or_start() {
1445                let choice = {
1446                    label: "颜色",
1447                    next: "color"
1448                };
1449                choice.next || "start"
1450            }
1451
1452            pub fn direct_next() {
1453                let choice = {
1454                    label: "颜色",
1455                    next: "color"
1456                };
1457                choice.next
1458            }
1459
1460            pub fn bracket_next() {
1461                let choice = {
1462                    label: "颜色",
1463                    next: "color"
1464                };
1465                choice["next"]
1466            }
1467
1468            pub fn assigned_preview() {
1469                let choice = {
1470                    next: "tax_free"
1471                };
1472                choice.preview = choice.next || "start";
1473                choice
1474            }
1475            "#
1476            .as_bytes()
1477            .to_vec(),
1478        )?;
1479
1480        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
1481        assert_eq!(compiled.ret_ty(), &Type::Any);
1482        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1483        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
1484
1485        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
1486        assert_eq!(compiled.ret_ty(), &Type::Any);
1487        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1488        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
1489
1490        let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
1491        assert_eq!(compiled.ret_ty(), &Type::Any);
1492        let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1493        assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
1494
1495        let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
1496        assert_eq!(compiled.ret_ty(), &Type::Any);
1497        let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1498        let choice = unsafe { &*assigned_preview() };
1499        assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
1500        Ok(())
1501    }
1502
1503    #[test]
1504    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
1505        let vm = Vm::with_all()?;
1506        vm.import_code(
1507            "vm_if_empty_object_branch",
1508            r#"
1509            pub fn first_note(steps) {
1510                let first = if steps.len() > 0 { steps[0] } else { {} };
1511                let first_note = first.note || "fallback";
1512                first_note
1513            }
1514
1515            pub fn first_ja(steps) {
1516                let first = if steps.len() > 0 { steps[0] } else { {} };
1517                first.ja || "すみません"
1518            }
1519
1520            pub fn assign_first_note(steps) {
1521                let first = {};
1522                first = if steps.len() > 0 { steps[0] } else { {} };
1523                first.note || "fallback"
1524            }
1525            "#
1526            .as_bytes()
1527            .to_vec(),
1528        )?;
1529
1530        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
1531        assert_eq!(compiled.ret_ty(), &Type::Any);
1532        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1533
1534        let empty_steps = Dynamic::list(Vec::new());
1535        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
1536
1537        let mut step = std::collections::BTreeMap::new();
1538        step.insert("note".into(), "hello".into());
1539        let steps = Dynamic::list(vec![Dynamic::map(step)]);
1540        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
1541
1542        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
1543        assert_eq!(compiled.ret_ty(), &Type::Any);
1544        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1545        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
1546
1547        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
1548        assert_eq!(compiled.ret_ty(), &Type::Any);
1549        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1550        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
1551        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
1552        Ok(())
1553    }
1554
1555    #[test]
1556    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
1557        let vm = Vm::with_all()?;
1558        vm.import_code(
1559            "vm_tail_list_literal",
1560            r#"
1561            pub fn numbers() {
1562                [1, 2, 3]
1563            }
1564
1565            pub fn maps() {
1566                [
1567                    {note: "first"},
1568                    {note: "second"}
1569                ]
1570            }
1571
1572            pub fn object_with_maps() {
1573                {
1574                    steps: [
1575                        {note: "first"},
1576                        {note: "second"}
1577                    ]
1578                }
1579            }
1580
1581            pub fn return_maps() {
1582                return [
1583                    {note: "first"},
1584                    {note: "second"}
1585                ];
1586            }
1587
1588            pub fn return_maps_without_semicolon() {
1589                return [
1590                    {note: "first"},
1591                    {note: "second"}
1592                ]
1593            }
1594
1595            pub fn tail_bare_variable() {
1596                let value = [
1597                    {note: "first"},
1598                    {note: "second"}
1599                ];
1600                value
1601            }
1602
1603            pub fn return_bare_variable_without_semicolon() {
1604                let value = [
1605                    {note: "first"},
1606                    {note: "second"}
1607                ];
1608                return value
1609            }
1610
1611            pub fn tail_object_variable() {
1612                let result = {
1613                    steps: [
1614                        {note: "first"},
1615                        {note: "second"}
1616                    ]
1617                };
1618                result
1619            }
1620            "#
1621            .as_bytes()
1622            .to_vec(),
1623        )?;
1624
1625        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1626        assert_eq!(compiled.ret_ty(), &Type::Any);
1627        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1628        let result = unsafe { &*numbers() };
1629        assert_eq!(result.len(), 3);
1630        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1631
1632        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1633        assert_eq!(compiled.ret_ty(), &Type::Any);
1634        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1635        let result = unsafe { &*maps() };
1636        assert_eq!(result.len(), 2);
1637        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1638
1639        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1640        assert_eq!(compiled.ret_ty(), &Type::Any);
1641        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1642        let result = unsafe { &*object_with_maps() };
1643        let steps = result.get_dynamic("steps").expect("steps");
1644        assert_eq!(steps.len(), 2);
1645        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1646
1647        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1648        assert_eq!(compiled.ret_ty(), &Type::Any);
1649        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1650        let result = unsafe { &*return_maps() };
1651        assert_eq!(result.len(), 2);
1652        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1653
1654        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1655        assert_eq!(compiled.ret_ty(), &Type::Any);
1656        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1657        let result = unsafe { &*return_maps_without_semicolon() };
1658        assert_eq!(result.len(), 2);
1659        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1660
1661        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1662        assert_eq!(compiled.ret_ty(), &Type::Any);
1663        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1664        let result = unsafe { &*tail_bare_variable() };
1665        assert_eq!(result.len(), 2);
1666        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1667
1668        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1669        assert_eq!(compiled.ret_ty(), &Type::Any);
1670        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1671        let result = unsafe { &*return_bare_variable_without_semicolon() };
1672        assert_eq!(result.len(), 2);
1673        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1674
1675        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1676        assert_eq!(compiled.ret_ty(), &Type::Any);
1677        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1678        let result = unsafe { &*tail_object_variable() };
1679        let steps = result.get_dynamic("steps").expect("steps");
1680        assert_eq!(steps.len(), 2);
1681        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1682        Ok(())
1683    }
1684
1685    #[test]
1686    fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
1687        let vm = Vm::with_all()?;
1688        vm.import_code(
1689            "vm_returned_list_get_idx",
1690            r#"
1691            pub fn ids() {
1692                [
1693                    "base",
1694                    "2",
1695                    "3"
1696                ]
1697            }
1698
1699            pub fn combinations() {
1700                let result = [];
1701                let values = ids();
1702                let idx = 0;
1703                while idx < values.len() {
1704                    result.push(values.get_idx(idx));
1705                    idx = idx + 1;
1706                }
1707                result
1708            }
1709            "#
1710            .as_bytes()
1711            .to_vec(),
1712        )?;
1713
1714        let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
1715        assert_eq!(compiled.ret_ty(), &Type::Any);
1716        let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1717        let result = unsafe { &*combinations() };
1718
1719        assert_eq!(result.len(), 3);
1720        assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
1721        assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
1722        Ok(())
1723    }
1724
1725    #[test]
1726    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1727        fn extra_page_literal(depth: usize) -> String {
1728            let mut value = "{leaf: \"done\"}".to_string();
1729            for idx in 0..depth {
1730                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1731            }
1732            value
1733        }
1734
1735        let extra = extra_page_literal(48);
1736        let code = format!(
1737            r#"
1738            pub fn script() {{
1739                return [
1740                    {{ja: "一つ目", note: "first", extra: {extra}}},
1741                    {{ja: "二つ目", note: "second", extra: {extra}}},
1742                    {{ja: "三つ目", note: "third", extra: {extra}}}
1743                ]
1744            }}
1745            "#
1746        );
1747
1748        let vm = Vm::with_all()?;
1749        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1750        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1751        assert_eq!(compiled.ret_ty(), &Type::Any);
1752        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1753        let result = unsafe { &*script() };
1754        assert_eq!(result.len(), 3);
1755        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1756        Ok(())
1757    }
1758
1759    #[test]
1760    fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1761        let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1762        std::fs::write(&module_path, "pub fn value() { 41 }")?;
1763        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1764
1765        let vm1 = Vm::with_all()?;
1766        vm1.import_code(
1767            "vm_import_owner",
1768            format!(
1769                r#"
1770                pub fn run() {{
1771                    import("vm_imported_owner", "{module_path}");
1772                }}
1773                "#
1774            )
1775            .into_bytes(),
1776        )?;
1777        let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1778
1779        let vm2 = Vm::with_all()?;
1780        vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1781        let _ = vm2.get_fn("vm_import_other::run", &[])?;
1782
1783        let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1784        run();
1785
1786        assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1787        assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1788        Ok(())
1789    }
1790
1791    #[test]
1792    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1793        let vm = Vm::with_all()?;
1794        vm.import_code(
1795            "vm_object_last_call_field",
1796            r#"
1797            pub fn extra_page() {
1798                {
1799                    title: "extra",
1800                    pages: [
1801                        {note: "nested"}
1802                    ]
1803                }
1804            }
1805
1806            pub fn data() {
1807                return [
1808                    {
1809                        note: "first",
1810                        choices: ["a", "b"],
1811                        extras: extra_page()
1812                    },
1813                    {
1814                        note: "second",
1815                        choices: ["c"],
1816                        extras: extra_page()
1817                    }
1818                ]
1819            }
1820            "#
1821            .as_bytes()
1822            .to_vec(),
1823        )?;
1824
1825        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1826        assert_eq!(compiled.ret_ty(), &Type::Any);
1827        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1828        let result = unsafe { &*data() };
1829        assert_eq!(result.len(), 2);
1830        let first = result.get_idx(0).expect("first step");
1831        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1832        Ok(())
1833    }
1834
1835    #[test]
1836    fn string_return_survives_scope_exit() -> anyhow::Result<()> {
1837        let vm = Vm::with_all()?;
1838        vm.import_code(
1839            "vm_string_return_scope",
1840            r#"
1841            pub fn source_root() {
1842                "../assets/character/男主角换装"
1843            }
1844
1845            pub fn binary_root() {
1846                "character_binary/男主角换装"
1847            }
1848
1849            pub fn runtime_binary_url() {
1850                "/" + binary_root()
1851            }
1852
1853            pub fn action_groups() {
1854                let root = source_root();
1855                let binary_url = runtime_binary_url();
1856                let binary_root = binary_root();
1857                [
1858                    {
1859                        id: "field_bottom",
1860                        source_spine: root + "/战斗外/boy_b.spine",
1861                        skeleton: binary_url + "/战斗外/boy_b/boy_b.skel.bytes",
1862                        export_skeleton: binary_root + "/战斗外/boy_b/boy_b.skel.bytes"
1863                    }
1864                ]
1865            }
1866            "#
1867            .as_bytes()
1868            .to_vec(),
1869        )?;
1870
1871        let compiled = vm.get_fn("vm_string_return_scope::source_root", &[])?;
1872        assert_eq!(compiled.ret_ty(), &Type::Str);
1873        let source_root: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1874        let source_root = unsafe { &*source_root() };
1875        assert_eq!(source_root.as_str(), "../assets/character/男主角换装");
1876
1877        let compiled = vm.get_fn("vm_string_return_scope::action_groups", &[])?;
1878        assert_eq!(compiled.ret_ty(), &Type::Any);
1879        let action_groups: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1880        let groups = unsafe { &*action_groups() };
1881        let first = groups.get_idx(0).expect("first action group");
1882        assert_eq!(first.get_dynamic("source_spine").map(|value| value.as_str().to_string()), Some("../assets/character/男主角换装/战斗外/boy_b.spine".to_string()));
1883        assert_eq!(first.get_dynamic("skeleton").map(|value| value.as_str().to_string()), Some("/character_binary/男主角换装/战斗外/boy_b/boy_b.skel.bytes".to_string()));
1884        Ok(())
1885    }
1886
1887    #[test]
1888    fn large_dynamic_object_accepts_inline_call_fields() -> anyhow::Result<()> {
1889        let vm = Vm::with_all()?;
1890        let model_count = 180;
1891        let combination_count = 90;
1892        let models = (0..model_count)
1893            .map(|idx| {
1894                format!(
1895                    r#"{{id: "model_{idx}", name: "模型_{idx}", source: "/美术资源/角色/少年/套装_{idx}/模型_{idx}.model.json", parts: [
1896                        {{slot: "hair", path: "/模型/头发/颜色_{idx}/默认.png", z: 10}},
1897                        {{slot: "body", path: "/模型/身体/套装_{idx}/默认.png", z: 1}},
1898                        {{slot: "face", path: "/模型/表情/表情_{idx}/默认.png", z: 20}}
1899                    ]}}"#
1900                )
1901            })
1902            .collect::<Vec<_>>()
1903            .join(",\n");
1904        let combinations = (0..combination_count)
1905            .map(|idx| format!(r#"{{hair: "color_{idx}", body: "set_{idx}", face: "face_{idx}"}}"#))
1906            .collect::<Vec<_>>()
1907            .join(",\n");
1908        let code = format!(
1909            r#"
1910            pub fn source_root() {{
1911                "/美术资源/角色/少年/默认"
1912            }}
1913
1914            pub fn runtime_boy_url() {{
1915                "/cdn/runtime/角色/少年/少年.model.json"
1916            }}
1917
1918            pub fn parts() {{
1919                [
1920                    {{id: "hair", path: "/模型/头发/黑色/默认.png", z: 10}},
1921                    {{id: "body", path: "/模型/身体/校服/默认.png", z: 1}},
1922                    {{id: "face", path: "/模型/表情/微笑/默认.png", z: 20}}
1923                ]
1924            }}
1925
1926            pub fn action_groups() {{
1927                {{
1928                    idle: [
1929                        {{id: "stand", name: "站立", frames: ["待机/0001.png", "待机/0002.png"]}},
1930                        {{id: "blink", name: "眨眼", frames: ["表情/眨眼/0001.png", "表情/眨眼/0002.png"]}}
1931                    ],
1932                    move: [
1933                        {{id: "walk", name: "行走", frames: ["行走/0001.png", "行走/0002.png"]}},
1934                        {{id: "run", name: "奔跑", frames: ["奔跑/0001.png", "奔跑/0002.png"]}}
1935                    ]
1936                }}
1937            }}
1938
1939            pub fn default_model() {{
1940                {{
1941                    id: "runtime_boy",
1942                    name: "运行时少年",
1943                    skins: [
1944                        {{id: "school", title: "校服", source: "/套装/校服/model.json"}},
1945                        {{id: "casual", title: "便服", source: "/套装/便服/model.json"}}
1946                    ],
1947                    models: [
1948                        {models}
1949                    ]
1950                }}
1951            }}
1952
1953            pub fn first_nine_combinations() {{
1954                [
1955                    {combinations}
1956                ]
1957            }}
1958
1959            pub fn config() {{
1960                {{
1961                    source_root: source_root(),
1962                    runtime_boy_url: runtime_boy_url(),
1963                    parts: parts(),
1964                    action_groups: action_groups(),
1965                    default_model: default_model(),
1966                    first_nine_combinations: first_nine_combinations()
1967                }}
1968            }}
1969
1970            pub fn start() {{
1971                root::add("local/vm_large_inline_call_object/config", {{
1972                    source_root: source_root(),
1973                    runtime_boy_url: runtime_boy_url(),
1974                    parts: parts(),
1975                    action_groups: action_groups(),
1976                    default_model: default_model(),
1977                    first_nine_combinations: first_nine_combinations()
1978                }})
1979            }}
1980            "#
1981        );
1982        vm.import_code(
1983            "vm_large_inline_call_object",
1984            code.into_bytes(),
1985        )?;
1986
1987        let compiled = vm.get_fn("vm_large_inline_call_object::config", &[])?;
1988        assert_eq!(compiled.ret_ty(), &Type::Any);
1989        let config: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1990        let result = unsafe { &*config() };
1991        assert_eq!(result.get_dynamic("source_root").map(|value| value.as_str().to_string()), Some("/美术资源/角色/少年/默认".to_string()));
1992        assert_eq!(result.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
1993
1994        let compiled = vm.get_fn("vm_large_inline_call_object::start", &[])?;
1995        assert_eq!(compiled.ret_ty(), &Type::Bool);
1996        let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1997        assert!(start());
1998        let saved = root::get("local/vm_large_inline_call_object/config")?;
1999        assert_eq!(saved.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
2000        Ok(())
2001    }
2002
2003    #[test]
2004    fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
2005        let vm = Vm::with_all()?;
2006        vm.import_code(
2007            "vm_gpu_layout",
2008            br#"
2009            pub struct Params {
2010                a: u32,
2011                b: u32,
2012                c: u32,
2013            }
2014            "#
2015            .to_vec(),
2016        )?;
2017
2018        let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
2019        assert_eq!(layout.size, 16);
2020        assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
2021
2022        let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
2023        let bytes = layout.pack_map(&value)?;
2024        assert_eq!(bytes.len(), 16);
2025        assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
2026        assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
2027        assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
2028
2029        let read = layout.unpack_map(&bytes)?;
2030        assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
2031        assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
2032        assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
2033        Ok(())
2034    }
2035
2036    #[test]
2037    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
2038        let vm = Vm::with_all()?;
2039        vm.import_code(
2040            "vm_root_clone_bridge",
2041            br#"
2042            pub fn add_then_reuse(arg) {
2043                let user = {
2044                    address: "test-wallet",
2045                    points: 20
2046                };
2047                root::add("local/root-clone-bridge-user", user);
2048                user.points = user.points - 7;
2049                root::add("local/root-clone-bridge-user", user);
2050                {
2051                    user: user,
2052                    points: user.points
2053                }
2054            }
2055
2056            pub fn clone_then_mutate(arg) {
2057                let user = {
2058                    profile: {
2059                        points: 20
2060                    }
2061                };
2062                let copied = user.clone();
2063                copied.profile.points = 13;
2064                user
2065            }
2066            "#
2067            .to_vec(),
2068        )?;
2069
2070        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
2071        assert_eq!(compiled.ret_ty(), &Type::Any);
2072        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2073        let arg = Dynamic::Null;
2074        let result = add_then_reuse(&arg);
2075        let result = unsafe { &*result };
2076
2077        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
2078        let mut json = String::new();
2079        result.to_json(&mut json);
2080        assert!(json.contains("\"points\": 13"));
2081
2082        let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
2083        let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
2084        let result = clone_then_mutate(&arg);
2085        let result = unsafe { &*result };
2086        assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
2087        Ok(())
2088    }
2089
2090    struct CounterForTypedReceiver {
2091        value: i64,
2092    }
2093
2094    extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
2095        unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
2096    }
2097
2098    struct NavMapForFunctionArg;
2099
2100    extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
2101        Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
2102    }
2103
2104    #[test]
2105    fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
2106        let vm = Vm::with_all()?;
2107        vm.add_empty_type("Counter")?;
2108        let counter_ty = vm.get_symbol("Counter", Vec::new())?;
2109        vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
2110        vm.import_code(
2111            "vm_typed_receiver_method",
2112            br#"
2113            pub fn run(value) {
2114                value::<Counter>::get()
2115            }
2116            "#
2117            .to_vec(),
2118        )?;
2119
2120        let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
2121        assert_eq!(compiled.ret_ty(), &Type::I64);
2122        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2123        let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
2124
2125        assert_eq!(run(&value), 42);
2126        Ok(())
2127    }
2128
2129    #[test]
2130    fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
2131        let vm = Vm::with_all()?;
2132        vm.add_empty_type("NavMap")?;
2133        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
2134        vm.import_code(
2135            "vm_native_custom_arg",
2136            br#"
2137            pub fn add_nav_spawns(world, navmap) {
2138                navmap
2139            }
2140
2141            pub fn run(world) {
2142                let navmap = NavMap::new();
2143                let with_spawns = add_nav_spawns(world, navmap);
2144                with_spawns
2145            }
2146            "#
2147            .to_vec(),
2148        )?;
2149
2150        let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
2151        assert_eq!(compiled.ret_ty(), &Type::Any);
2152        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2153        let world = Dynamic::Null;
2154        let result = run(&world);
2155        let result = unsafe { &*result };
2156
2157        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
2158        Ok(())
2159    }
2160
2161    #[test]
2162    fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
2163        let vm = Vm::with_all()?;
2164        vm.add_empty_type("NavMap")?;
2165        let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
2166        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
2167        vm.import_code(
2168            "vm_native_custom_typed_arg",
2169            br#"
2170            pub fn add_nav_spawns(world, navmap) {
2171                navmap
2172            }
2173
2174            pub fn run(world) {
2175                let navmap: NavMap = NavMap::new();
2176                let with_spawns = add_nav_spawns(world, navmap);
2177                with_spawns
2178            }
2179            "#
2180            .to_vec(),
2181        )?;
2182
2183        let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
2184        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2185        let world = Dynamic::Null;
2186        let result = run(&world);
2187        let result = unsafe { &*result };
2188
2189        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
2190        Ok(())
2191    }
2192}