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 http_module;
19mod llm_module;
20mod root_module;
21
22use std::cell::RefCell;
23use std::sync::{Mutex, OnceLock, Weak};
24static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
25pub fn ptr_type() -> types::Type {
26 PTR_TYPE.get().cloned().unwrap()
27}
28
29pub fn get_type(ty: &Type) -> Result<types::Type> {
30 if ty.is_f64() {
31 Ok(types::F64)
32 } else if ty.is_f32() {
33 Ok(types::F32)
34 } else if ty.is_int() | ty.is_uint() {
35 match ty.width() {
36 1 => Ok(types::I8),
37 2 => Ok(types::I16),
38 4 => Ok(types::I32),
39 8 => Ok(types::I64),
40 _ => Err(anyhow!("非法类型 {:?}", ty)),
41 }
42 } else if let Type::Bool = ty {
43 Ok(types::I8)
44 } else {
45 Ok(ptr_type())
46 }
47}
48
49use compiler::Symbol;
50use cranelift::prelude::*;
51
52pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
53 jit.add_all()?;
54 Ok(jit)
55}
56
57use std::sync::Arc;
58unsafe impl Send for JITRunTime {}
59unsafe impl Sync for JITRunTime {}
60
61thread_local! {
62 static CURRENT_VM: RefCell<Option<Weak<Mutex<JITRunTime>>>> = const { RefCell::new(None) };
63}
64
65fn set_current_vm(vm: &Vm) {
66 CURRENT_VM.with(|current| {
67 *current.borrow_mut() = Some(Arc::downgrade(&vm.jit));
68 });
69}
70
71fn with_current_vm<T>(f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
72 CURRENT_VM.with(|current| {
73 let jit = current.borrow().as_ref().and_then(Weak::upgrade).ok_or_else(|| anyhow!("当前线程没有 VM"))?;
74 let vm = Vm { jit };
75 f(&vm)
76 })
77}
78
79pub(crate) fn import_current(name: &str, path: &str) -> Result<()> {
80 with_current_vm(|vm| vm.import(name, path))
81}
82
83pub(crate) fn get_current_fn_ptr(name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
84 with_current_vm(|vm| vm.get_fn_ptr(name, arg_tys))
85}
86
87fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
88 let def_id = jit.get_id(def)?;
89 if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
90 if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
91 fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
92 }
93 }
94 Ok(())
95}
96
97fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
98 jit.add_module(module);
99 for (name, arg_tys, ret_ty, fn_ptr) in fns {
100 let full_name = format!("{}::{}", module, name);
101 jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
102 }
103 jit.pop_module();
104 Ok(())
105}
106
107impl JITRunTime {
108 pub fn add_module(&mut self, name: &str) {
109 self.compiler.symbols.add_module(name.into());
110 }
111
112 pub fn pop_module(&mut self) {
113 self.compiler.symbols.pop_module();
114 }
115
116 pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
117 self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
118 }
119
120 pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
121 match self.get_id(name) {
122 Ok(id) => Ok(id),
123 Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
124 }
125 }
126
127 pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
128 self.add_module(module);
129 let full_name = format!("{}::{}", module, name);
130 let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
131 self.pop_module();
132 result
133 }
134
135 pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
136 self.add_empty_type(def)?;
137 let full_name = format!("{}::{}", def, method);
138 let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
139 add_method_field(self, def, method, id)?;
140 Ok(id)
141 }
142
143 pub fn add_std(&mut self) -> Result<()> {
144 self.add_module("std");
145 for (name, arg_tys, ret_ty, fn_ptr) in STD {
146 self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
147 }
148 Ok(())
149 }
150
151 pub fn add_any(&mut self) -> Result<()> {
152 for (name, arg_tys, ret_ty, fn_ptr) in ANY {
153 let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
154 self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
155 }
156 Ok(())
157 }
158
159 pub fn add_vec(&mut self) -> Result<()> {
160 self.add_empty_type("Vec")?;
161 let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
162 self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
163 if let Some(ctx) = ctx {
164 let width = ctx.builder.ins().iconst(types::I64, 4);
165 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);
168 let dest_addr = ctx.builder.ins().iadd(args[0], dest); let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
170 let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
171 ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
172 ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
173 }
174 Err(anyhow!("无返回值"))
175 })?;
176
177 self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
178 if let Some(ctx) = ctx {
179 let width = ctx.builder.ins().iconst(types::I64, 4);
180 let offset_val = ctx.builder.ins().imul(args[1], width); let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
182 Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
183 } else {
184 Ok((None, Type::I32))
185 }
186 })?;
187 Ok(())
188 }
189
190 pub fn add_llm(&mut self) -> Result<()> {
191 add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
192 }
193
194 pub fn add_root(&mut self) -> Result<()> {
195 add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)
196 }
197
198 pub fn add_http(&mut self) -> Result<()> {
199 add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)
200 }
201
202 pub fn add_db(&mut self) -> Result<()> {
203 add_native_module_fns(self, "db", &db_module::DB_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 Ok(())
215 }
216}
217
218#[derive(Clone)]
219pub struct Vm {
220 jit: Arc<Mutex<JITRunTime>>,
221}
222
223#[derive(Clone)]
224pub struct CompiledFn {
225 ptr: usize,
226 ret: Type,
227 owner: Vm,
228}
229
230impl CompiledFn {
231 pub fn ptr(&self) -> *const u8 {
232 set_current_vm(&self.owner);
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 Self { jit: Arc::new(Mutex::new(JITRunTime::new(|_| {}))) }
248 }
249
250 pub fn with_all() -> Result<Self> {
251 let vm = Self::new();
252 vm.add_all()?;
253 Ok(vm)
254 }
255
256 pub fn add_module(&self, name: &str) {
257 self.jit.lock().unwrap().add_module(name)
258 }
259
260 pub fn pop_module(&self) {
261 self.jit.lock().unwrap().pop_module()
262 }
263
264 pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
265 self.jit.lock().unwrap().add_type(name, ty, is_pub)
266 }
267
268 pub fn add_empty_type(&self, name: &str) -> Result<u32> {
269 self.jit.lock().unwrap().add_empty_type(name)
270 }
271
272 pub fn add_std(&self) -> Result<()> {
273 self.jit.lock().unwrap().add_std()
274 }
275
276 pub fn add_any(&self) -> Result<()> {
277 self.jit.lock().unwrap().add_any()
278 }
279
280 pub fn add_vec(&self) -> Result<()> {
281 self.jit.lock().unwrap().add_vec()
282 }
283
284 pub fn add_llm(&self) -> Result<()> {
285 self.jit.lock().unwrap().add_llm()
286 }
287
288 pub fn add_root(&self) -> Result<()> {
289 self.jit.lock().unwrap().add_root()
290 }
291
292 pub fn add_http(&self) -> Result<()> {
293 self.jit.lock().unwrap().add_http()
294 }
295
296 pub fn add_db(&self) -> Result<()> {
297 self.jit.lock().unwrap().add_db()
298 }
299
300 pub fn add_all(&self) -> Result<()> {
301 self.jit.lock().unwrap().add_all()
302 }
303
304 pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
305 self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
306 }
307
308 pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
309 self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
310 }
311
312 pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
313 self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
314 }
315
316 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> {
317 self.jit.lock().unwrap().add_inline(name, args, ret, f)
318 }
319
320 pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
321 self.jit.lock().unwrap().import_code(name, code)
322 }
323
324 pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
325 self.jit.lock().unwrap().compiler.import_file(name, path)?;
326 Ok(())
327 }
328
329 pub fn import(&self, name: &str, path: &str) -> Result<()> {
330 if root::contains(path) {
331 let code = root::get(path).unwrap();
332 if code.is_str() {
333 self.import_code(name, code.as_str().as_bytes().to_vec())
334 } else {
335 self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
336 }
337 } else {
338 self.import_file(name, path)
339 }
340 }
341
342 pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
343 self.jit.lock().unwrap().get_type(name, arg_tys)
344 }
345
346 pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
347 self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
348 }
349
350 pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
351 set_current_vm(self);
352 let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
353 Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
354 }
355
356 pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
357 self.jit.lock().unwrap().load(code, arg_name)
358 }
359
360 pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
361 Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
362 }
363
364 pub fn disassemble(&self, name: &str) -> Result<String> {
365 self.jit.lock().unwrap().compiler.symbols.disassemble(name)
366 }
367
368 #[cfg(feature = "ir-disassembly")]
369 pub fn disassemble_ir(&self, name: &str) -> Result<String> {
370 self.jit.lock().unwrap().disassemble_ir(name)
371 }
372}
373
374impl Default for Vm {
375 fn default() -> Self {
376 Self::new()
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::Vm;
383 use dynamic::{Dynamic, ToJson, Type};
384
385 extern "C" fn math_double(value: i64) -> i64 {
386 value * 2
387 }
388
389 #[test]
390 fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
391 let vm = Vm::new();
392 vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
393 vm.import_code(
394 "vm_dynamic_native",
395 br#"
396 pub fn run(value: i64) {
397 math::double(value)
398 }
399 "#
400 .to_vec(),
401 )?;
402
403 let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
404 assert_eq!(compiled.ret_ty(), &Type::I64);
405 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
406 assert_eq!(run(21), 42);
407 Ok(())
408 }
409
410 #[test]
411 fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
412 let vm = Vm::with_all()?;
413 vm.import_code(
414 "vm_string_compare_any",
415 br#"
416 pub fn any_ne_empty(chat_path) {
417 chat_path != ""
418 }
419 "#
420 .to_vec(),
421 )?;
422
423 let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
424 assert_eq!(compiled.ret_ty(), &Type::Bool);
425
426 let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
427 let empty = Dynamic::from("");
428 let non_empty = Dynamic::from("chat");
429
430 assert!(!any_ne_empty(&empty));
431 assert!(any_ne_empty(&non_empty));
432 Ok(())
433 }
434
435 #[test]
436 fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
437 let vm = Vm::with_all()?;
438 vm.import_code(
439 "vm_string_compare_imm",
440 br#"
441 pub fn int_eq_str(value: i64) {
442 value == "42"
443 }
444
445 pub fn int_to_str(value: i64) {
446 value + ""
447 }
448 "#
449 .to_vec(),
450 )?;
451
452 let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
453 assert_eq!(compiled.ret_ty(), &Type::Bool);
454
455 let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
456
457 let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
458 assert_eq!(compiled.ret_ty(), &Type::Any);
459 let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
460 let text = int_to_str(42);
461 assert_eq!(unsafe { &*text }.as_str(), "42");
462
463 assert!(int_eq_str(42));
464 assert!(!int_eq_str(7));
465 Ok(())
466 }
467
468 #[test]
469 fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
470 let vm = Vm::with_all()?;
471 vm.import_code(
472 "vm_dynamic_field_or",
473 r#"
474 pub fn next_or_start() {
475 let choice = {
476 label: "颜色",
477 next: "color"
478 };
479 choice.next || "start"
480 }
481
482 pub fn direct_next() {
483 let choice = {
484 label: "颜色",
485 next: "color"
486 };
487 choice.next
488 }
489
490 pub fn bracket_next() {
491 let choice = {
492 label: "颜色",
493 next: "color"
494 };
495 choice["next"]
496 }
497
498 pub fn assigned_preview() {
499 let choice = {
500 next: "tax_free"
501 };
502 choice.preview = choice.next || "start";
503 choice
504 }
505 "#
506 .as_bytes()
507 .to_vec(),
508 )?;
509
510 let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
511 assert_eq!(compiled.ret_ty(), &Type::Any);
512 let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
513 assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
514
515 let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
516 assert_eq!(compiled.ret_ty(), &Type::Any);
517 let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
518 assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
519
520 let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
521 assert_eq!(compiled.ret_ty(), &Type::Any);
522 let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
523 assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
524
525 let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
526 assert_eq!(compiled.ret_ty(), &Type::Any);
527 let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
528 let choice = unsafe { &*assigned_preview() };
529 assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
530 Ok(())
531 }
532
533 #[test]
534 fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
535 let vm = Vm::with_all()?;
536 vm.import_code(
537 "vm_if_empty_object_branch",
538 r#"
539 pub fn first_note(steps) {
540 let first = if steps.len() > 0 { steps[0] } else { {} };
541 let first_note = first.note || "fallback";
542 first_note
543 }
544
545 pub fn first_ja(steps) {
546 let first = if steps.len() > 0 { steps[0] } else { {} };
547 first.ja || "すみません"
548 }
549
550 pub fn assign_first_note(steps) {
551 let first = {};
552 first = if steps.len() > 0 { steps[0] } else { {} };
553 first.note || "fallback"
554 }
555 "#
556 .as_bytes()
557 .to_vec(),
558 )?;
559
560 let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
561 assert_eq!(compiled.ret_ty(), &Type::Any);
562 let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
563
564 let empty_steps = Dynamic::list(Vec::new());
565 assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
566
567 let mut step = std::collections::BTreeMap::new();
568 step.insert("note".into(), "hello".into());
569 let steps = Dynamic::list(vec![Dynamic::map(step)]);
570 assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
571
572 let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
573 assert_eq!(compiled.ret_ty(), &Type::Any);
574 let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
575 assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
576
577 let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
578 assert_eq!(compiled.ret_ty(), &Type::Any);
579 let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
580 assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
581 assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
582 Ok(())
583 }
584
585 #[test]
586 fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
587 let vm = Vm::with_all()?;
588 vm.import_code(
589 "vm_tail_list_literal",
590 r#"
591 pub fn numbers() {
592 [1, 2, 3]
593 }
594
595 pub fn maps() {
596 [
597 {note: "first"},
598 {note: "second"}
599 ]
600 }
601
602 pub fn object_with_maps() {
603 {
604 steps: [
605 {note: "first"},
606 {note: "second"}
607 ]
608 }
609 }
610
611 pub fn return_maps() {
612 return [
613 {note: "first"},
614 {note: "second"}
615 ];
616 }
617
618 pub fn return_maps_without_semicolon() {
619 return [
620 {note: "first"},
621 {note: "second"}
622 ]
623 }
624
625 pub fn tail_bare_variable() {
626 let value = [
627 {note: "first"},
628 {note: "second"}
629 ];
630 value
631 }
632
633 pub fn return_bare_variable_without_semicolon() {
634 let value = [
635 {note: "first"},
636 {note: "second"}
637 ];
638 return value
639 }
640
641 pub fn tail_object_variable() {
642 let result = {
643 steps: [
644 {note: "first"},
645 {note: "second"}
646 ]
647 };
648 result
649 }
650 "#
651 .as_bytes()
652 .to_vec(),
653 )?;
654
655 let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
656 assert_eq!(compiled.ret_ty(), &Type::Any);
657 let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
658 let result = unsafe { &*numbers() };
659 assert_eq!(result.len(), 3);
660 assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
661
662 let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
663 assert_eq!(compiled.ret_ty(), &Type::Any);
664 let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
665 let result = unsafe { &*maps() };
666 assert_eq!(result.len(), 2);
667 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
668
669 let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
670 assert_eq!(compiled.ret_ty(), &Type::Any);
671 let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
672 let result = unsafe { &*object_with_maps() };
673 let steps = result.get_dynamic("steps").expect("steps");
674 assert_eq!(steps.len(), 2);
675 assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
676
677 let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
678 assert_eq!(compiled.ret_ty(), &Type::Any);
679 let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
680 let result = unsafe { &*return_maps() };
681 assert_eq!(result.len(), 2);
682 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
683
684 let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
685 assert_eq!(compiled.ret_ty(), &Type::Any);
686 let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
687 let result = unsafe { &*return_maps_without_semicolon() };
688 assert_eq!(result.len(), 2);
689 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
690
691 let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
692 assert_eq!(compiled.ret_ty(), &Type::Any);
693 let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
694 let result = unsafe { &*tail_bare_variable() };
695 assert_eq!(result.len(), 2);
696 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
697
698 let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
699 assert_eq!(compiled.ret_ty(), &Type::Any);
700 let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
701 let result = unsafe { &*return_bare_variable_without_semicolon() };
702 assert_eq!(result.len(), 2);
703 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
704
705 let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
706 assert_eq!(compiled.ret_ty(), &Type::Any);
707 let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
708 let result = unsafe { &*tail_object_variable() };
709 let steps = result.get_dynamic("steps").expect("steps");
710 assert_eq!(steps.len(), 2);
711 assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
712 Ok(())
713 }
714
715 #[test]
716 fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
717 fn extra_page_literal(depth: usize) -> String {
718 let mut value = "{leaf: \"done\"}".to_string();
719 for idx in 0..depth {
720 value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
721 }
722 value
723 }
724
725 let extra = extra_page_literal(48);
726 let code = format!(
727 r#"
728 pub fn script() {{
729 return [
730 {{ja: "一つ目", note: "first", extra: {extra}}},
731 {{ja: "二つ目", note: "second", extra: {extra}}},
732 {{ja: "三つ目", note: "third", extra: {extra}}}
733 ]
734 }}
735 "#
736 );
737
738 let vm = Vm::with_all()?;
739 vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
740 let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
741 assert_eq!(compiled.ret_ty(), &Type::Any);
742 let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
743 let result = unsafe { &*script() };
744 assert_eq!(result.len(), 3);
745 assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
746 Ok(())
747 }
748
749 #[test]
750 fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
751 let vm = Vm::with_all()?;
752 vm.import_code(
753 "vm_object_last_call_field",
754 r#"
755 pub fn extra_page() {
756 {
757 title: "extra",
758 pages: [
759 {note: "nested"}
760 ]
761 }
762 }
763
764 pub fn data() {
765 return [
766 {
767 note: "first",
768 choices: ["a", "b"],
769 extras: extra_page()
770 },
771 {
772 note: "second",
773 choices: ["c"],
774 extras: extra_page()
775 }
776 ]
777 }
778 "#
779 .as_bytes()
780 .to_vec(),
781 )?;
782
783 let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
784 assert_eq!(compiled.ret_ty(), &Type::Any);
785 let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
786 let result = unsafe { &*data() };
787 assert_eq!(result.len(), 2);
788 let first = result.get_idx(0).expect("first step");
789 assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
790 Ok(())
791 }
792
793 #[test]
794 fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
795 let vm = Vm::with_all()?;
796 vm.import_code(
797 "vm_root_clone_bridge",
798 br#"
799 pub fn add_then_reuse(arg) {
800 let user = {
801 address: "test-wallet",
802 points: 20
803 };
804 root::add("local/root-clone-bridge-user", user);
805 user.points = user.points - 7;
806 root::add("local/root-clone-bridge-user", user);
807 {
808 user: user,
809 points: user.points
810 }
811 }
812 "#
813 .to_vec(),
814 )?;
815
816 let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
817 assert_eq!(compiled.ret_ty(), &Type::Any);
818 let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
819 let arg = Dynamic::Null;
820 let result = add_then_reuse(&arg);
821 let result = unsafe { &*result };
822
823 assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
824 let mut json = String::new();
825 result.to_json(&mut json);
826 assert!(json.contains("\"points\": 13"));
827 Ok(())
828 }
829}