1mod 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); let final_addr = ctx.builder.ins().iadd(args[0], offset_val); let dest = ctx.builder.ins().imul(args[2], width);
196 let dest_addr = ctx.builder.ins().iadd(args[0], dest); 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); 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(¤cy) };
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}