1mod binary;
3mod native;
4pub use native::{ANY, STD};
5
6mod fns;
7use anyhow::{Result, anyhow};
8pub use fns::{FnInfo, FnVariant};
9mod context;
10pub use context::BuildContext;
11
12mod rt;
13use cranelift::prelude::types;
14use dynamic::Type;
15pub use rt::JITRunTime;
16use smol_str::SmolStr;
17mod db_module;
18mod gpu_layout;
19mod gpu_module;
20mod http_module;
21mod llm_module;
22mod root_module;
23pub use gpu_layout::{GpuFieldLayout, GpuStructLayout};
24
25use std::sync::{Mutex, OnceLock, Weak};
26static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
27pub fn ptr_type() -> types::Type {
28 PTR_TYPE.get().cloned().unwrap()
29}
30
31pub fn get_type(ty: &Type) -> Result<types::Type> {
32 if ty.is_f64() {
33 Ok(types::F64)
34 } else if ty.is_f32() {
35 Ok(types::F32)
36 } else if ty.is_int() | ty.is_uint() {
37 match ty.width() {
38 1 => Ok(types::I8),
39 2 => Ok(types::I16),
40 4 => Ok(types::I32),
41 8 => Ok(types::I64),
42 _ => Err(anyhow!("非法类型 {:?}", ty)),
43 }
44 } else if let Type::Bool = ty {
45 Ok(types::I8)
46 } else {
47 Ok(ptr_type())
48 }
49}
50
51use compiler::Symbol;
52use cranelift::prelude::*;
53
54pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
55 jit.add_all()?;
56 Ok(jit)
57}
58
59use std::sync::Arc;
60unsafe impl Send for JITRunTime {}
61unsafe impl Sync for JITRunTime {}
62
63pub(crate) fn with_vm_context<T>(context: *const Weak<Mutex<JITRunTime>>, f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
64 if context.is_null() {
65 return Err(anyhow!("VM context is null"));
66 }
67 let jit = unsafe { &*context }.upgrade().ok_or_else(|| anyhow!("VM context has expired"))?;
68 let vm = Vm { jit };
69 f(&vm)
70}
71
72fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
73 let def_id = jit.get_id(def)?;
74 if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
75 if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
76 fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
77 }
78 }
79 Ok(())
80}
81
82fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
83 jit.add_module(module);
84 for (name, arg_tys, ret_ty, fn_ptr) in fns {
85 let full_name = format!("{}::{}", module, name);
86 jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
87 }
88 jit.pop_module();
89 Ok(())
90}
91
92impl JITRunTime {
93 pub fn add_module(&mut self, name: &str) {
94 self.compiler.symbols.add_module(name.into());
95 }
96
97 pub fn pop_module(&mut self) {
98 self.compiler.symbols.pop_module();
99 }
100
101 pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
102 self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
103 }
104
105 pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
106 match self.get_id(name) {
107 Ok(id) => Ok(id),
108 Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
109 }
110 }
111
112 pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
113 self.add_module(module);
114 let full_name = format!("{}::{}", module, name);
115 let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
116 self.pop_module();
117 result
118 }
119
120 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> {
121 self.add_module(module);
122 let full_name = format!("{}::{}", module, name);
123 let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
124 self.pop_module();
125 result
126 }
127
128 pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
129 self.add_empty_type(def)?;
130 let full_name = format!("{}::{}", def, method);
131 let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
132 add_method_field(self, def, method, id)?;
133 Ok(id)
134 }
135
136 pub fn add_std(&mut self) -> Result<()> {
137 self.add_module("std");
138 for (name, arg_tys, ret_ty, fn_ptr) in STD {
139 self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
140 }
141 self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
142 Ok(())
143 }
144
145 pub fn add_any(&mut self) -> Result<()> {
146 for (name, arg_tys, ret_ty, fn_ptr) in ANY {
147 let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
148 self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
149 }
150 Ok(())
151 }
152
153 pub fn add_vec(&mut self) -> Result<()> {
154 self.add_empty_type("Vec")?;
155 let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
156 self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
157 if let Some(ctx) = ctx {
158 let width = ctx.builder.ins().iconst(types::I64, 4);
159 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);
162 let dest_addr = ctx.builder.ins().iadd(args[0], dest); let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
164 let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
165 ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
166 ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
167 }
168 Err(anyhow!("无返回值"))
169 })?;
170
171 self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
172 if let Some(ctx) = ctx {
173 let width = ctx.builder.ins().iconst(types::I64, 4);
174 let offset_val = ctx.builder.ins().imul(args[1], width); let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
176 Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
177 } else {
178 Ok((None, Type::I32))
179 }
180 })?;
181 Ok(())
182 }
183
184 pub fn add_llm(&mut self) -> Result<()> {
185 add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
186 }
187
188 pub fn add_root(&mut self) -> Result<()> {
189 add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
190 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)?;
191 Ok(())
192 }
193
194 pub fn add_http(&mut self) -> Result<()> {
195 add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)
196 }
197
198 pub fn add_db(&mut self) -> Result<()> {
199 add_native_module_fns(self, "db", &db_module::DB_NATIVE)
200 }
201
202 pub fn add_gpu(&mut self) -> Result<()> {
203 add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
204 }
205
206 pub fn add_all(&mut self) -> Result<()> {
207 self.add_std()?;
208 self.add_any()?;
209 self.add_vec()?;
210 self.add_llm()?;
211 self.add_root()?;
212 self.add_http()?;
213 self.add_db()?;
214 self.add_gpu()?;
215 Ok(())
216 }
217}
218
219#[derive(Clone)]
220pub struct Vm {
221 jit: Arc<Mutex<JITRunTime>>,
222}
223
224#[derive(Clone)]
225pub struct CompiledFn {
226 ptr: usize,
227 ret: Type,
228 owner: Vm,
229}
230
231impl CompiledFn {
232 pub fn ptr(&self) -> *const u8 {
233 self.ptr as *const u8
234 }
235
236 pub fn ret_ty(&self) -> &Type {
237 &self.ret
238 }
239
240 pub fn owner(&self) -> &Vm {
241 &self.owner
242 }
243}
244
245impl Vm {
246 pub fn new() -> Self {
247 let jit = Arc::new(Mutex::new(JITRunTime::new(|_| {})));
248 jit.lock().unwrap().set_owner(Arc::downgrade(&jit));
249 Self { jit }
250 }
251
252 pub fn with_all() -> Result<Self> {
253 let vm = Self::new();
254 vm.add_all()?;
255 Ok(vm)
256 }
257
258 pub fn add_module(&self, name: &str) {
259 self.jit.lock().unwrap().add_module(name)
260 }
261
262 pub fn pop_module(&self) {
263 self.jit.lock().unwrap().pop_module()
264 }
265
266 pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
267 self.jit.lock().unwrap().add_type(name, ty, is_pub)
268 }
269
270 pub fn add_empty_type(&self, name: &str) -> Result<u32> {
271 self.jit.lock().unwrap().add_empty_type(name)
272 }
273
274 pub fn add_std(&self) -> Result<()> {
275 self.jit.lock().unwrap().add_std()
276 }
277
278 pub fn add_any(&self) -> Result<()> {
279 self.jit.lock().unwrap().add_any()
280 }
281
282 pub fn add_vec(&self) -> Result<()> {
283 self.jit.lock().unwrap().add_vec()
284 }
285
286 pub fn add_llm(&self) -> Result<()> {
287 self.jit.lock().unwrap().add_llm()
288 }
289
290 pub fn add_root(&self) -> Result<()> {
291 self.jit.lock().unwrap().add_root()
292 }
293
294 pub fn add_http(&self) -> Result<()> {
295 self.jit.lock().unwrap().add_http()
296 }
297
298 pub fn add_db(&self) -> Result<()> {
299 self.jit.lock().unwrap().add_db()
300 }
301
302 pub fn add_gpu(&self) -> Result<()> {
303 self.jit.lock().unwrap().add_gpu()
304 }
305
306 pub fn add_all(&self) -> Result<()> {
307 self.jit.lock().unwrap().add_all()
308 }
309
310 pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
311 self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
312 }
313
314 pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
315 self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
316 }
317
318 pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
319 self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
320 }
321
322 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> {
323 self.jit.lock().unwrap().add_inline(name, args, ret, f)
324 }
325
326 pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
327 self.jit.lock().unwrap().import_code(name, code)
328 }
329
330 pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
331 self.jit.lock().unwrap().compiler.import_file(name, path)?;
332 Ok(())
333 }
334
335 pub fn import(&self, name: &str, path: &str) -> Result<()> {
336 if root::contains(path) {
337 let code = root::get(path).unwrap();
338 if code.is_str() {
339 self.import_code(name, code.as_str().as_bytes().to_vec())
340 } else {
341 self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
342 }
343 } else {
344 self.import_file(name, path)
345 }
346 }
347
348 pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
349 self.jit.lock().unwrap().get_type(name, arg_tys)
350 }
351
352 pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
353 self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
354 }
355
356 pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
357 let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
358 Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
359 }
360
361 pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
362 self.jit.lock().unwrap().load(code, arg_name)
363 }
364
365 pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
366 Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
367 }
368
369 pub fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> Result<GpuStructLayout> {
370 let jit = self.jit.lock().unwrap();
371 GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
372 }
373
374 pub fn disassemble(&self, name: &str) -> Result<String> {
375 self.jit.lock().unwrap().compiler.symbols.disassemble(name)
376 }
377
378 #[cfg(feature = "ir-disassembly")]
379 pub fn disassemble_ir(&self, name: &str) -> Result<String> {
380 self.jit.lock().unwrap().disassemble_ir(name)
381 }
382}
383
384impl Default for Vm {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::Vm;
393 use dynamic::{Dynamic, ToJson, Type};
394
395 extern "C" fn math_double(value: i64) -> i64 {
396 value * 2
397 }
398
399 #[test]
400 fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
401 let vm = Vm::new();
402 vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
403 vm.import_code(
404 "vm_dynamic_native",
405 br#"
406 pub fn run(value: i64) {
407 math::double(value)
408 }
409 "#
410 .to_vec(),
411 )?;
412
413 let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
414 assert_eq!(compiled.ret_ty(), &Type::I64);
415 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
416 assert_eq!(run(21), 42);
417 Ok(())
418 }
419
420 #[test]
421 fn any_push_does_not_consume_reused_value() -> anyhow::Result<()> {
422 let vm = Vm::with_all()?;
423 vm.import_code(
424 "vm_any_push_reused_value",
425 br#"
426 pub fn run() {
427 let role_id = "acct_role_2";
428 let updated = [];
429 updated.push(role_id);
430 {
431 ok: true,
432 user_id: role_id,
433 first: updated.get_idx(0)
434 }
435 }
436 "#
437 .to_vec(),
438 )?;
439
440 let compiled = vm.get_fn("vm_any_push_reused_value::run", &[])?;
441 assert_eq!(compiled.ret_ty(), &Type::Any);
442 let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
443 let result = unsafe { &*run() };
444 assert_eq!(result.get_dynamic("ok").and_then(|value| value.as_bool()), Some(true));
445 assert_eq!(result.get_dynamic("user_id").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
446 assert_eq!(result.get_dynamic("first").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
447 Ok(())
448 }
449
450 #[test]
451 fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
452 let vm = Vm::with_all()?;
453 vm.import_code(
454 "vm_string_compare_any",
455 br#"
456 pub fn any_ne_empty(chat_path) {
457 chat_path != ""
458 }
459 "#
460 .to_vec(),
461 )?;
462
463 let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
464 assert_eq!(compiled.ret_ty(), &Type::Bool);
465
466 let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
467 let empty = Dynamic::from("");
468 let non_empty = Dynamic::from("chat");
469
470 assert!(!any_ne_empty(&empty));
471 assert!(any_ne_empty(&non_empty));
472 Ok(())
473 }
474
475 #[test]
476 fn compares_bool_values_and_bool_literals() -> anyhow::Result<()> {
477 let vm = Vm::with_all()?;
478 vm.import_code(
479 "vm_bool_compare",
480 br#"
481 pub fn eq_true(value: bool) {
482 value == true
483 }
484
485 pub fn ne_false(value: bool) {
486 value != false
487 }
488
489 pub fn literal_left(value: bool) {
490 true == value
491 }
492
493 pub fn eq_pair(left: bool, right: bool) {
494 left == right
495 }
496
497 pub fn logic_pair(left: bool, right: bool) {
498 (left && right) || (left == true && right != false)
499 }
500 "#
501 .to_vec(),
502 )?;
503
504 let compiled = vm.get_fn("vm_bool_compare::eq_true", &[Type::Bool])?;
505 assert_eq!(compiled.ret_ty(), &Type::Bool);
506 let eq_true: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
507 assert!(eq_true(true));
508 assert!(!eq_true(false));
509
510 let compiled = vm.get_fn("vm_bool_compare::ne_false", &[Type::Bool])?;
511 assert_eq!(compiled.ret_ty(), &Type::Bool);
512 let ne_false: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
513 assert!(ne_false(true));
514 assert!(!ne_false(false));
515
516 let compiled = vm.get_fn("vm_bool_compare::literal_left", &[Type::Bool])?;
517 assert_eq!(compiled.ret_ty(), &Type::Bool);
518 let literal_left: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
519 assert!(literal_left(true));
520 assert!(!literal_left(false));
521
522 let compiled = vm.get_fn("vm_bool_compare::eq_pair", &[Type::Bool, Type::Bool])?;
523 assert_eq!(compiled.ret_ty(), &Type::Bool);
524 let eq_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
525 assert!(eq_pair(true, true));
526 assert!(eq_pair(false, false));
527 assert!(!eq_pair(true, false));
528 assert!(!eq_pair(false, true));
529
530 let compiled = vm.get_fn("vm_bool_compare::logic_pair", &[Type::Bool, Type::Bool])?;
531 assert_eq!(compiled.ret_ty(), &Type::Bool);
532 let logic_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
533 assert!(logic_pair(true, true));
534 assert!(!logic_pair(true, false));
535 assert!(!logic_pair(false, true));
536 assert!(!logic_pair(false, false));
537 Ok(())
538 }
539
540 #[test]
541 fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
542 let vm = Vm::with_all()?;
543 vm.import_code(
544 "vm_parenthesized_method_call",
545 br#"
546 pub fn run(value) {
547 (value + 2).to_i64()
548 }
549 "#
550 .to_vec(),
551 )?;
552
553 let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
554 assert_eq!(compiled.ret_ty(), &Type::I64);
555 let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
556 let value = Dynamic::from(40i64);
557
558 assert_eq!(run(&value), 42);
559 Ok(())
560 }
561
562 #[test]
563 fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
564 let vm = Vm::with_all()?;
565 vm.import_code(
566 "vm_any_keys",
567 br#"
568 pub fn map_keys(value) {
569 let keys = value.keys();
570 keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
571 }
572
573 pub fn non_map_keys(value) {
574 value.keys().len() == 0
575 }
576 "#
577 .to_vec(),
578 )?;
579
580 let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
581 assert_eq!(compiled.ret_ty(), &Type::Bool);
582 let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
583 let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
584 assert!(map_keys(&value));
585
586 let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
587 assert_eq!(compiled.ret_ty(), &Type::Bool);
588 let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
589 let value = Dynamic::from("alpha");
590 assert!(non_map_keys(&value));
591 Ok(())
592 }
593
594 #[test]
595 fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
596 let vm = Vm::with_all()?;
597 vm.import_code(
598 "vm_string_compare_imm",
599 br#"
600 pub fn int_eq_str(value: i64) {
601 value == "42"
602 }
603
604 pub fn int_to_str(value: i64) {
605 value + ""
606 }
607 "#
608 .to_vec(),
609 )?;
610
611 let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
612 assert_eq!(compiled.ret_ty(), &Type::Bool);
613
614 let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
615
616 let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
617 assert_eq!(compiled.ret_ty(), &Type::Any);
618 let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
619 let text = int_to_str(42);
620 assert_eq!(unsafe { &*text }.as_str(), "42");
621
622 assert!(int_eq_str(42));
623 assert!(!int_eq_str(7));
624 Ok(())
625 }
626
627 #[test]
628 fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
629 let vm = Vm::with_all()?;
630 vm.import_code(
631 "vm_string_concat_integer",
632 br#"
633 pub fn idx_key(idx: i64) {
634 "" + idx
635 }
636
637 pub fn level_text(level: i64) {
638 "" + level + " level"
639 }
640
641 pub fn gold_text(currency) {
642 "" + currency.gold
643 }
644 "#
645 .to_vec(),
646 )?;
647
648 let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
649 let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
650 let result = unsafe { &*idx_key(7) };
651 assert_eq!(result.as_str(), "7");
652
653 let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
654 let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
655 let result = unsafe { &*level_text(12) };
656 assert_eq!(result.as_str(), "12 level");
657
658 let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
659 let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
660 let currency = dynamic::map!("gold"=> 345i64);
661 let result = unsafe { &*gold_text(¤cy) };
662 assert_eq!(result.as_str(), "345");
663 Ok(())
664 }
665
666 #[test]
667 fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
668 let vm = Vm::with_all()?;
669 vm.import_code(
670 "vm_string_concat_to_i64",
671 br#"
672 pub fn run(idx: i64) {
673 ("" + idx) as i64
674 }
675 "#
676 .to_vec(),
677 )?;
678
679 let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
680 assert_eq!(compiled.ret_ty(), &Type::I64);
681 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
682 assert_eq!(run(7), 0);
683 Ok(())
684 }
685
686 #[test]
687 fn unifies_explicit_return_and_tail_integer_widths() -> anyhow::Result<()> {
688 let vm = Vm::with_all()?;
689 vm.import_code(
690 "vm_return_integer_widths",
691 br#"
692 pub fn selected(flag, slot) {
693 if flag {
694 return slot;
695 }
696 0
697 }
698 "#
699 .to_vec(),
700 )?;
701
702 let compiled = vm.get_fn("vm_return_integer_widths::selected", &[Type::Bool, Type::I64])?;
703 assert_eq!(compiled.ret_ty(), &Type::I64);
704 let selected: extern "C" fn(bool, i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
705
706 assert_eq!(selected(true, 7), 7);
707 assert_eq!(selected(false, 7), 0);
708 Ok(())
709 }
710
711 #[test]
712 fn root_contains_string_concat_is_bool_condition() -> anyhow::Result<()> {
713 let vm = Vm::with_all()?;
714 vm.import_code(
715 "vm_root_contains_condition",
716 br#"
717 pub fn exists(user_id) {
718 if root::contains("redis/user/" + user_id) {
719 return 1;
720 }
721 0
722 }
723 "#
724 .to_vec(),
725 )?;
726
727 assert_eq!(vm.infer("root::contains", &[Type::Any])?, Type::Bool);
728 let compiled = vm.get_fn("vm_root_contains_condition::exists", &[Type::Any])?;
729 assert_eq!(compiled.ret_ty(), &Type::I32);
730 Ok(())
731 }
732
733 #[test]
734 fn root_add_map_can_be_printed() -> anyhow::Result<()> {
735 let vm = Vm::with_all()?;
736 assert_eq!(vm.infer("root::add_map", &[Type::Any])?, Type::Bool);
737 vm.import_code(
738 "vm_root_add_map_print",
739 br#"
740 pub fn run() {
741 print(root::add_map("local/world_handlers/til_map_novicevillage"));
742 }
743 "#
744 .to_vec(),
745 )?;
746
747 let compiled = vm.get_fn("vm_root_add_map_print::run", &[])?;
748 assert!(compiled.ret_ty().is_void());
749 Ok(())
750 }
751
752 #[test]
753 fn unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
754 let vm = Vm::with_all()?;
755 vm.import_code(
756 "vm_unary_not_any_loop_var",
757 br#"
758 pub fn count_missing(flags) {
759 let missing = 0;
760 for exists in flags {
761 if !exists {
762 missing = missing + 1;
763 }
764 }
765 missing
766 }
767 "#
768 .to_vec(),
769 )?;
770
771 let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
772 assert_eq!(compiled.ret_ty(), &Type::I32);
773 Ok(())
774 }
775
776 #[test]
777 fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
778 let vm = Vm::with_all()?;
779 vm.import_code(
780 "vm_semicolon_tail_void",
781 br#"
782 pub fn send_role_select(idx, account_id, selected_slot) {
783 root::send("local/ui/send_dialog", {
784 idx: idx,
785 account_id: account_id,
786 selected_slot: selected_slot
787 });
788 }
789 "#
790 .to_vec(),
791 )?;
792
793 let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
794 assert_eq!(compiled.ret_ty(), &Type::Void);
795 Ok(())
796 }
797
798 #[test]
799 fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
800 let vm = Vm::with_all()?;
801 vm.import_code(
802 "vm_bare_return_conflict",
803 br#"
804 pub fn run(flag) {
805 if flag {
806 return;
807 }
808 1
809 }
810 "#
811 .to_vec(),
812 )?;
813
814 let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
815 Ok(_) => panic!("expected mismatched return types to fail"),
816 Err(err) => err,
817 };
818 assert!(format!("{err:#}").contains("返回类型不一致"));
819 Ok(())
820 }
821
822 #[test]
823 fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
824 let vm = Vm::with_all()?;
825 vm.import_code(
826 "vm_root_get_dynamic_concat",
827 br#"
828 pub fn get_action(req) {
829 root::get("local/game/panel_actions/" + req.idx)
830 }
831 "#
832 .to_vec(),
833 )?;
834
835 root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
836 let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
837 assert_eq!(compiled.ret_ty(), &Type::Any);
838 let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
839 let req = dynamic::map!("idx"=> 7i64);
840 let result = unsafe { &*get_action(&req) };
841
842 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
843 Ok(())
844 }
845
846 #[test]
847 fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
848 let vm = Vm::with_all()?;
849 vm.import_code(
850 "vm_registered_panel_action",
851 br#"
852 pub fn panel_action(req) {
853 root::get("local/game/panel_actions/" + req.idx)
854 }
855
856 pub fn register() {
857 root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
858 }
859 "#
860 .to_vec(),
861 )?;
862
863 let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
864 assert_eq!(compiled.ret_ty(), &Type::Bool);
865 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
866 assert!(register());
867 Ok(())
868 }
869
870 #[test]
871 fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
872 let vm = Vm::with_all()?;
873 vm.import_code(
874 "vm_registered_string_concat",
875 br#"
876 pub fn send_panel(idx: i64) {
877 let idx_key = "" + idx;
878 idx_key
879 }
880 "#
881 .to_vec(),
882 )?;
883
884 assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
885 Ok(())
886 }
887
888 #[test]
889 fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
890 let vm = Vm::with_all()?;
891 vm.import_code(
892 "vm_public_hotspots",
893 br#"
894 pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
895 {
896 path: action_map_path,
897 panel_id: panel_id,
898 action_id: action_id,
899 id: hotspot.id
900 }
901 }
902
903 pub fn public_hotspots(idx, panel_id, hotspots) {
904 let idx_key = "" + idx;
905 let action_map_path = "local/game/panel_actions/" + idx_key;
906
907 let existing_action_map = root::get(action_map_path);
908 if !existing_action_map.is_map() {
909 root::add_map(action_map_path);
910 }
911
912 if hotspots.is_map() {
913 let public_items = {};
914 for action_id in hotspots.keys() {
915 public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
916 }
917 return public_items;
918 }
919
920 let public_items = [];
921 let i = 0;
922 while i < hotspots.len() {
923 let hotspot = hotspots.get_idx(i);
924 let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
925 public_items.push(item);
926 i = i + 1;
927 }
928
929 public_items
930 }
931 "#
932 .to_vec(),
933 )?;
934
935 assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
936 assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
937 Ok(())
938 }
939
940 #[test]
941 fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
942 let vm = Vm::with_all()?;
943 vm.import_code(
944 "vm_send_panel_public_hotspots",
945 br#"
946 pub fn ok(value) {
947 value
948 }
949
950 pub fn panel_from_node(req) {
951 {
952 panel_id: req.panel_id,
953 hotspots: req.hotspots
954 }
955 }
956
957 pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
958 {
959 path: action_map_path,
960 panel_id: panel_id,
961 action_id: action_id,
962 id: hotspot.id
963 }
964 }
965
966 pub fn public_hotspots(idx, panel_id, hotspots) {
967 let idx_key = "" + idx;
968 let action_map_path = "local/game/panel_actions/" + idx_key;
969
970 let existing_action_map = root::get(action_map_path);
971 if !existing_action_map.is_map() {
972 root::add_map(action_map_path);
973 }
974
975 if hotspots.is_map() {
976 let public_items = {};
977 for action_id in hotspots.keys() {
978 public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
979 }
980 return public_items;
981 }
982
983 let public_items = [];
984 let i = 0;
985 while i < hotspots.len() {
986 let hotspot = hotspots.get_idx(i);
987 let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
988 public_items.push(item);
989 i = i + 1;
990 }
991
992 public_items
993 }
994
995 pub fn send_panel(req) {
996 let panel = req.panel;
997 if !panel.is_map() {
998 panel = panel_from_node(req);
999 }
1000 if !panel.is_map() {
1001 return ok({
1002 id: 4,
1003 type: "panel_rejected",
1004 reason: "invalid panel"
1005 });
1006 }
1007 panel.id = 4;
1008 panel.idx = req.idx;
1009 if !panel.contains("type") {
1010 panel.type = "panel";
1011 }
1012 if panel.contains("hotspots") {
1013 panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
1014 }
1015 root::send_idx("local/ws", req.idx, panel);
1016 ok({
1017 id: 4,
1018 type: "panel",
1019 panel_id: panel.panel_id
1020 })
1021 }
1022 "#
1023 .to_vec(),
1024 )?;
1025
1026 let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
1027 assert_eq!(compiled.ret_ty(), &Type::Any);
1028 let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1029 let req = dynamic::map!(
1030 "idx"=> 7i64,
1031 "panel"=> dynamic::map!(
1032 "panel_id"=> "main",
1033 "hotspots"=> dynamic::map!(
1034 "open"=> dynamic::map!("id"=> "open")
1035 )
1036 )
1037 );
1038 let result = unsafe { &*send_panel(&req) };
1039
1040 assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
1041 assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
1042 Ok(())
1043 }
1044
1045 #[test]
1046 fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
1047 let vm = Vm::with_all()?;
1048 vm.import_code(
1049 "vm_string_concat_map_key",
1050 br##"
1051 pub fn write_action(action_map, panel_id, action_id, action) {
1052 action_map[panel_id + "#" + action_id] = action;
1053 action_map[panel_id + "#" + action_id]
1054 }
1055 "##
1056 .to_vec(),
1057 )?;
1058
1059 let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
1060 let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1061 let action_map = dynamic::map!();
1062 let panel_id: Dynamic = "panel".into();
1063 let action_id: Dynamic = "open".into();
1064 let action = dynamic::map!("id"=> "open");
1065
1066 let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
1067
1068 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1069 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()));
1070 Ok(())
1071 }
1072
1073 #[test]
1074 fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
1075 let vm = Vm::with_all()?;
1076 vm.import_code(
1077 "vm_get_key_string_concat_key",
1078 br##"
1079 pub fn read_action(action_map, panel_id, action_id) {
1080 let action_key = panel_id + "#" + action_id;
1081 action_map.get_key(action_key)
1082 }
1083 "##
1084 .to_vec(),
1085 )?;
1086
1087 let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1088 let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1089 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1090 let panel_id: Dynamic = "panel".into();
1091 let action_id: Dynamic = "open".into();
1092
1093 let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1094
1095 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1096 Ok(())
1097 }
1098
1099 #[test]
1100 fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
1101 let vm = Vm::with_all()?;
1102 vm.import_code(
1103 "vm_get_key_helper_string_key",
1104 br##"
1105 pub fn make_action_key(panel_id, action_id) {
1106 panel_id + "#" + action_id
1107 }
1108
1109 pub fn read_action(action_map, panel_id, action_id) {
1110 let action_key = make_action_key(panel_id, action_id);
1111 let action = action_map.get_key(action_key);
1112 action
1113 }
1114 "##
1115 .to_vec(),
1116 )?;
1117
1118 let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1119 let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1120 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1121 let panel_id: Dynamic = "panel".into();
1122 let action_id: Dynamic = "open".into();
1123
1124 let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1125
1126 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1127 Ok(())
1128 }
1129
1130 #[test]
1131 fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
1132 let vm = Vm::with_all()?;
1133 vm.import_code(
1134 "vm_del_key_string_key",
1135 br##"
1136 pub fn remove_action(action_map, panel_id, action_id) {
1137 let action_key = panel_id + "#" + action_id;
1138 let removed = action_map.del_key(action_key);
1139 [removed, action_map.get_key(action_key)]
1140 }
1141 "##
1142 .to_vec(),
1143 )?;
1144
1145 let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
1146 let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1147 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1148 let panel_id: Dynamic = "panel".into();
1149 let action_id: Dynamic = "open".into();
1150
1151 let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
1152
1153 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1154 assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
1155 assert!(action_map.get_dynamic("panel#open").is_none());
1156 Ok(())
1157 }
1158
1159 #[test]
1160 fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
1161 let vm = Vm::with_all()?;
1162 vm.import_code(
1163 "vm_dynamic_field_or",
1164 r#"
1165 pub fn next_or_start() {
1166 let choice = {
1167 label: "颜色",
1168 next: "color"
1169 };
1170 choice.next || "start"
1171 }
1172
1173 pub fn direct_next() {
1174 let choice = {
1175 label: "颜色",
1176 next: "color"
1177 };
1178 choice.next
1179 }
1180
1181 pub fn bracket_next() {
1182 let choice = {
1183 label: "颜色",
1184 next: "color"
1185 };
1186 choice["next"]
1187 }
1188
1189 pub fn assigned_preview() {
1190 let choice = {
1191 next: "tax_free"
1192 };
1193 choice.preview = choice.next || "start";
1194 choice
1195 }
1196 "#
1197 .as_bytes()
1198 .to_vec(),
1199 )?;
1200
1201 let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
1202 assert_eq!(compiled.ret_ty(), &Type::Any);
1203 let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1204 assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
1205
1206 let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
1207 assert_eq!(compiled.ret_ty(), &Type::Any);
1208 let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1209 assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
1210
1211 let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
1212 assert_eq!(compiled.ret_ty(), &Type::Any);
1213 let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1214 assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
1215
1216 let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
1217 assert_eq!(compiled.ret_ty(), &Type::Any);
1218 let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1219 let choice = unsafe { &*assigned_preview() };
1220 assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
1221 Ok(())
1222 }
1223
1224 #[test]
1225 fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
1226 let vm = Vm::with_all()?;
1227 vm.import_code(
1228 "vm_if_empty_object_branch",
1229 r#"
1230 pub fn first_note(steps) {
1231 let first = if steps.len() > 0 { steps[0] } else { {} };
1232 let first_note = first.note || "fallback";
1233 first_note
1234 }
1235
1236 pub fn first_ja(steps) {
1237 let first = if steps.len() > 0 { steps[0] } else { {} };
1238 first.ja || "すみません"
1239 }
1240
1241 pub fn assign_first_note(steps) {
1242 let first = {};
1243 first = if steps.len() > 0 { steps[0] } else { {} };
1244 first.note || "fallback"
1245 }
1246 "#
1247 .as_bytes()
1248 .to_vec(),
1249 )?;
1250
1251 let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
1252 assert_eq!(compiled.ret_ty(), &Type::Any);
1253 let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1254
1255 let empty_steps = Dynamic::list(Vec::new());
1256 assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
1257
1258 let mut step = std::collections::BTreeMap::new();
1259 step.insert("note".into(), "hello".into());
1260 let steps = Dynamic::list(vec![Dynamic::map(step)]);
1261 assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
1262
1263 let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
1264 assert_eq!(compiled.ret_ty(), &Type::Any);
1265 let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1266 assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
1267
1268 let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
1269 assert_eq!(compiled.ret_ty(), &Type::Any);
1270 let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1271 assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
1272 assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
1273 Ok(())
1274 }
1275
1276 #[test]
1277 fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
1278 let vm = Vm::with_all()?;
1279 vm.import_code(
1280 "vm_tail_list_literal",
1281 r#"
1282 pub fn numbers() {
1283 [1, 2, 3]
1284 }
1285
1286 pub fn maps() {
1287 [
1288 {note: "first"},
1289 {note: "second"}
1290 ]
1291 }
1292
1293 pub fn object_with_maps() {
1294 {
1295 steps: [
1296 {note: "first"},
1297 {note: "second"}
1298 ]
1299 }
1300 }
1301
1302 pub fn return_maps() {
1303 return [
1304 {note: "first"},
1305 {note: "second"}
1306 ];
1307 }
1308
1309 pub fn return_maps_without_semicolon() {
1310 return [
1311 {note: "first"},
1312 {note: "second"}
1313 ]
1314 }
1315
1316 pub fn tail_bare_variable() {
1317 let value = [
1318 {note: "first"},
1319 {note: "second"}
1320 ];
1321 value
1322 }
1323
1324 pub fn return_bare_variable_without_semicolon() {
1325 let value = [
1326 {note: "first"},
1327 {note: "second"}
1328 ];
1329 return value
1330 }
1331
1332 pub fn tail_object_variable() {
1333 let result = {
1334 steps: [
1335 {note: "first"},
1336 {note: "second"}
1337 ]
1338 };
1339 result
1340 }
1341 "#
1342 .as_bytes()
1343 .to_vec(),
1344 )?;
1345
1346 let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1347 assert_eq!(compiled.ret_ty(), &Type::Any);
1348 let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1349 let result = unsafe { &*numbers() };
1350 assert_eq!(result.len(), 3);
1351 assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1352
1353 let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1354 assert_eq!(compiled.ret_ty(), &Type::Any);
1355 let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1356 let result = unsafe { &*maps() };
1357 assert_eq!(result.len(), 2);
1358 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1359
1360 let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1361 assert_eq!(compiled.ret_ty(), &Type::Any);
1362 let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1363 let result = unsafe { &*object_with_maps() };
1364 let steps = result.get_dynamic("steps").expect("steps");
1365 assert_eq!(steps.len(), 2);
1366 assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1367
1368 let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1369 assert_eq!(compiled.ret_ty(), &Type::Any);
1370 let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1371 let result = unsafe { &*return_maps() };
1372 assert_eq!(result.len(), 2);
1373 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1374
1375 let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1376 assert_eq!(compiled.ret_ty(), &Type::Any);
1377 let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1378 let result = unsafe { &*return_maps_without_semicolon() };
1379 assert_eq!(result.len(), 2);
1380 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1381
1382 let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1383 assert_eq!(compiled.ret_ty(), &Type::Any);
1384 let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1385 let result = unsafe { &*tail_bare_variable() };
1386 assert_eq!(result.len(), 2);
1387 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1388
1389 let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1390 assert_eq!(compiled.ret_ty(), &Type::Any);
1391 let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1392 let result = unsafe { &*return_bare_variable_without_semicolon() };
1393 assert_eq!(result.len(), 2);
1394 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1395
1396 let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1397 assert_eq!(compiled.ret_ty(), &Type::Any);
1398 let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1399 let result = unsafe { &*tail_object_variable() };
1400 let steps = result.get_dynamic("steps").expect("steps");
1401 assert_eq!(steps.len(), 2);
1402 assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1403 Ok(())
1404 }
1405
1406 #[test]
1407 fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
1408 let vm = Vm::with_all()?;
1409 vm.import_code(
1410 "vm_returned_list_get_idx",
1411 r#"
1412 pub fn ids() {
1413 [
1414 "base",
1415 "2",
1416 "3"
1417 ]
1418 }
1419
1420 pub fn combinations() {
1421 let result = [];
1422 let values = ids();
1423 let idx = 0;
1424 while idx < values.len() {
1425 result.push(values.get_idx(idx));
1426 idx = idx + 1;
1427 }
1428 result
1429 }
1430 "#
1431 .as_bytes()
1432 .to_vec(),
1433 )?;
1434
1435 let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
1436 assert_eq!(compiled.ret_ty(), &Type::Any);
1437 let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1438 let result = unsafe { &*combinations() };
1439
1440 assert_eq!(result.len(), 3);
1441 assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
1442 assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
1443 Ok(())
1444 }
1445
1446 #[test]
1447 fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1448 fn extra_page_literal(depth: usize) -> String {
1449 let mut value = "{leaf: \"done\"}".to_string();
1450 for idx in 0..depth {
1451 value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1452 }
1453 value
1454 }
1455
1456 let extra = extra_page_literal(48);
1457 let code = format!(
1458 r#"
1459 pub fn script() {{
1460 return [
1461 {{ja: "一つ目", note: "first", extra: {extra}}},
1462 {{ja: "二つ目", note: "second", extra: {extra}}},
1463 {{ja: "三つ目", note: "third", extra: {extra}}}
1464 ]
1465 }}
1466 "#
1467 );
1468
1469 let vm = Vm::with_all()?;
1470 vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1471 let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1472 assert_eq!(compiled.ret_ty(), &Type::Any);
1473 let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1474 let result = unsafe { &*script() };
1475 assert_eq!(result.len(), 3);
1476 assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1477 Ok(())
1478 }
1479
1480 #[test]
1481 fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1482 let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1483 std::fs::write(&module_path, "pub fn value() { 41 }")?;
1484 let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1485
1486 let vm1 = Vm::with_all()?;
1487 vm1.import_code(
1488 "vm_import_owner",
1489 format!(
1490 r#"
1491 pub fn run() {{
1492 import("vm_imported_owner", "{module_path}");
1493 }}
1494 "#
1495 )
1496 .into_bytes(),
1497 )?;
1498 let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1499
1500 let vm2 = Vm::with_all()?;
1501 vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1502 let _ = vm2.get_fn("vm_import_other::run", &[])?;
1503
1504 let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1505 run();
1506
1507 assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1508 assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1509 Ok(())
1510 }
1511
1512 #[test]
1513 fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1514 let vm = Vm::with_all()?;
1515 vm.import_code(
1516 "vm_object_last_call_field",
1517 r#"
1518 pub fn extra_page() {
1519 {
1520 title: "extra",
1521 pages: [
1522 {note: "nested"}
1523 ]
1524 }
1525 }
1526
1527 pub fn data() {
1528 return [
1529 {
1530 note: "first",
1531 choices: ["a", "b"],
1532 extras: extra_page()
1533 },
1534 {
1535 note: "second",
1536 choices: ["c"],
1537 extras: extra_page()
1538 }
1539 ]
1540 }
1541 "#
1542 .as_bytes()
1543 .to_vec(),
1544 )?;
1545
1546 let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1547 assert_eq!(compiled.ret_ty(), &Type::Any);
1548 let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1549 let result = unsafe { &*data() };
1550 assert_eq!(result.len(), 2);
1551 let first = result.get_idx(0).expect("first step");
1552 assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1553 Ok(())
1554 }
1555
1556 #[test]
1557 fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
1558 let vm = Vm::with_all()?;
1559 vm.import_code(
1560 "vm_gpu_layout",
1561 br#"
1562 pub struct Params {
1563 a: u32,
1564 b: u32,
1565 c: u32,
1566 }
1567 "#
1568 .to_vec(),
1569 )?;
1570
1571 let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
1572 assert_eq!(layout.size, 16);
1573 assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
1574
1575 let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
1576 let bytes = layout.pack_map(&value)?;
1577 assert_eq!(bytes.len(), 16);
1578 assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
1579 assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
1580 assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
1581
1582 let read = layout.unpack_map(&bytes)?;
1583 assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
1584 assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
1585 assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
1586 Ok(())
1587 }
1588
1589 #[test]
1590 fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
1591 let vm = Vm::with_all()?;
1592 vm.import_code(
1593 "vm_root_clone_bridge",
1594 br#"
1595 pub fn add_then_reuse(arg) {
1596 let user = {
1597 address: "test-wallet",
1598 points: 20
1599 };
1600 root::add("local/root-clone-bridge-user", user);
1601 user.points = user.points - 7;
1602 root::add("local/root-clone-bridge-user", user);
1603 {
1604 user: user,
1605 points: user.points
1606 }
1607 }
1608
1609 pub fn clone_then_mutate(arg) {
1610 let user = {
1611 profile: {
1612 points: 20
1613 }
1614 };
1615 let copied = user.clone();
1616 copied.profile.points = 13;
1617 user
1618 }
1619 "#
1620 .to_vec(),
1621 )?;
1622
1623 let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
1624 assert_eq!(compiled.ret_ty(), &Type::Any);
1625 let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1626 let arg = Dynamic::Null;
1627 let result = add_then_reuse(&arg);
1628 let result = unsafe { &*result };
1629
1630 assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
1631 let mut json = String::new();
1632 result.to_json(&mut json);
1633 assert!(json.contains("\"points\": 13"));
1634
1635 let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
1636 let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
1637 let result = clone_then_mutate(&arg);
1638 let result = unsafe { &*result };
1639 assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
1640 Ok(())
1641 }
1642
1643 struct CounterForTypedReceiver {
1644 value: i64,
1645 }
1646
1647 extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
1648 unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
1649 }
1650
1651 struct NavMapForFunctionArg;
1652
1653 extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
1654 Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
1655 }
1656
1657 #[test]
1658 fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
1659 let vm = Vm::with_all()?;
1660 vm.add_empty_type("Counter")?;
1661 let counter_ty = vm.get_symbol("Counter", Vec::new())?;
1662 vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
1663 vm.import_code(
1664 "vm_typed_receiver_method",
1665 br#"
1666 pub fn run(value) {
1667 value::<Counter>::get()
1668 }
1669 "#
1670 .to_vec(),
1671 )?;
1672
1673 let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
1674 assert_eq!(compiled.ret_ty(), &Type::I64);
1675 let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1676 let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
1677
1678 assert_eq!(run(&value), 42);
1679 Ok(())
1680 }
1681
1682 #[test]
1683 fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
1684 let vm = Vm::with_all()?;
1685 vm.add_empty_type("NavMap")?;
1686 vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
1687 vm.import_code(
1688 "vm_native_custom_arg",
1689 br#"
1690 pub fn add_nav_spawns(world, navmap) {
1691 navmap
1692 }
1693
1694 pub fn run(world) {
1695 let navmap = NavMap::new();
1696 let with_spawns = add_nav_spawns(world, navmap);
1697 with_spawns
1698 }
1699 "#
1700 .to_vec(),
1701 )?;
1702
1703 let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
1704 assert_eq!(compiled.ret_ty(), &Type::Any);
1705 let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1706 let world = Dynamic::Null;
1707 let result = run(&world);
1708 let result = unsafe { &*result };
1709
1710 assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
1711 Ok(())
1712 }
1713
1714 #[test]
1715 fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
1716 let vm = Vm::with_all()?;
1717 vm.add_empty_type("NavMap")?;
1718 let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
1719 vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
1720 vm.import_code(
1721 "vm_native_custom_typed_arg",
1722 br#"
1723 pub fn add_nav_spawns(world, navmap) {
1724 navmap
1725 }
1726
1727 pub fn run(world) {
1728 let navmap: NavMap = NavMap::new();
1729 let with_spawns = add_nav_spawns(world, navmap);
1730 with_spawns
1731 }
1732 "#
1733 .to_vec(),
1734 )?;
1735
1736 let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
1737 let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1738 let world = Dynamic::Null;
1739 let result = run(&world);
1740 let result = unsafe { &*result };
1741
1742 assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
1743 Ok(())
1744 }
1745}