Skip to main content

vm/
lib.rs

1//使用 cranelift 作为后端 直接 jit 解释脚本
2mod 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); // i * 4 i32大小四字节
160                let final_addr = ctx.builder.ins().iadd(args[0], offset_val); // base + (i*4)
161                let dest = ctx.builder.ins().imul(args[2], width);
162                let dest_addr = ctx.builder.ins().iadd(args[0], dest); // base + (i*4)
163                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); // i * 4 i32大小四字节
175                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 compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
422        let vm = Vm::with_all()?;
423        vm.import_code(
424            "vm_string_compare_any",
425            br#"
426            pub fn any_ne_empty(chat_path) {
427                chat_path != ""
428            }
429            "#
430            .to_vec(),
431        )?;
432
433        let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
434        assert_eq!(compiled.ret_ty(), &Type::Bool);
435
436        let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
437        let empty = Dynamic::from("");
438        let non_empty = Dynamic::from("chat");
439
440        assert!(!any_ne_empty(&empty));
441        assert!(any_ne_empty(&non_empty));
442        Ok(())
443    }
444
445    #[test]
446    fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
447        let vm = Vm::with_all()?;
448        vm.import_code(
449            "vm_parenthesized_method_call",
450            br#"
451            pub fn run(value) {
452                (value + 2).to_i64()
453            }
454            "#
455            .to_vec(),
456        )?;
457
458        let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
459        assert_eq!(compiled.ret_ty(), &Type::I64);
460        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
461        let value = Dynamic::from(40i64);
462
463        assert_eq!(run(&value), 42);
464        Ok(())
465    }
466
467    #[test]
468    fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
469        let vm = Vm::with_all()?;
470        vm.import_code(
471            "vm_any_keys",
472            br#"
473            pub fn map_keys(value) {
474                let keys = value.keys();
475                keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
476            }
477
478            pub fn non_map_keys(value) {
479                value.keys().len() == 0
480            }
481            "#
482            .to_vec(),
483        )?;
484
485        let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
486        assert_eq!(compiled.ret_ty(), &Type::Bool);
487        let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
488        let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
489        assert!(map_keys(&value));
490
491        let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
492        assert_eq!(compiled.ret_ty(), &Type::Bool);
493        let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
494        let value = Dynamic::from("alpha");
495        assert!(non_map_keys(&value));
496        Ok(())
497    }
498
499    #[test]
500    fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
501        let vm = Vm::with_all()?;
502        vm.import_code(
503            "vm_string_compare_imm",
504            br#"
505            pub fn int_eq_str(value: i64) {
506                value == "42"
507            }
508
509            pub fn int_to_str(value: i64) {
510                value + ""
511            }
512            "#
513            .to_vec(),
514        )?;
515
516        let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
517        assert_eq!(compiled.ret_ty(), &Type::Bool);
518
519        let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
520
521        let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
522        assert_eq!(compiled.ret_ty(), &Type::Any);
523        let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
524        let text = int_to_str(42);
525        assert_eq!(unsafe { &*text }.as_str(), "42");
526
527        assert!(int_eq_str(42));
528        assert!(!int_eq_str(7));
529        Ok(())
530    }
531
532    #[test]
533    fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
534        let vm = Vm::with_all()?;
535        vm.import_code(
536            "vm_string_concat_integer",
537            br#"
538            pub fn idx_key(idx: i64) {
539                "" + idx
540            }
541
542            pub fn level_text(level: i64) {
543                "" + level + " level"
544            }
545
546            pub fn gold_text(currency) {
547                "" + currency.gold
548            }
549            "#
550            .to_vec(),
551        )?;
552
553        let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
554        let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
555        let result = unsafe { &*idx_key(7) };
556        assert_eq!(result.as_str(), "7");
557
558        let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
559        let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
560        let result = unsafe { &*level_text(12) };
561        assert_eq!(result.as_str(), "12 level");
562
563        let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
564        let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
565        let currency = dynamic::map!("gold"=> 345i64);
566        let result = unsafe { &*gold_text(&currency) };
567        assert_eq!(result.as_str(), "345");
568        Ok(())
569    }
570
571    #[test]
572    fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
573        let vm = Vm::with_all()?;
574        vm.import_code(
575            "vm_string_concat_to_i64",
576            br#"
577            pub fn run(idx: i64) {
578                ("" + idx) as i64
579            }
580            "#
581            .to_vec(),
582        )?;
583
584        let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
585        assert_eq!(compiled.ret_ty(), &Type::I64);
586        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
587        assert_eq!(run(7), 0);
588        Ok(())
589    }
590
591    #[test]
592    fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
593        let vm = Vm::with_all()?;
594        vm.import_code(
595            "vm_root_get_dynamic_concat",
596            br#"
597            pub fn get_action(req) {
598                root::get("local/game/panel_actions/" + req.idx)
599            }
600            "#
601            .to_vec(),
602        )?;
603
604        root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
605        let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
606        assert_eq!(compiled.ret_ty(), &Type::Any);
607        let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
608        let req = dynamic::map!("idx"=> 7i64);
609        let result = unsafe { &*get_action(&req) };
610
611        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
612        Ok(())
613    }
614
615    #[test]
616    fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
617        let vm = Vm::with_all()?;
618        vm.import_code(
619            "vm_registered_panel_action",
620            br#"
621            pub fn panel_action(req) {
622                root::get("local/game/panel_actions/" + req.idx)
623            }
624
625            pub fn register() {
626                root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
627            }
628            "#
629            .to_vec(),
630        )?;
631
632        let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
633        assert_eq!(compiled.ret_ty(), &Type::Bool);
634        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
635        assert!(register());
636        Ok(())
637    }
638
639    #[test]
640    fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
641        let vm = Vm::with_all()?;
642        vm.import_code(
643            "vm_registered_string_concat",
644            br#"
645            pub fn send_panel(idx: i64) {
646                let idx_key = "" + idx;
647                idx_key
648            }
649            "#
650            .to_vec(),
651        )?;
652
653        assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
654        Ok(())
655    }
656
657    #[test]
658    fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
659        let vm = Vm::with_all()?;
660        vm.import_code(
661            "vm_public_hotspots",
662            br#"
663            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
664                {
665                    path: action_map_path,
666                    panel_id: panel_id,
667                    action_id: action_id,
668                    id: hotspot.id
669                }
670            }
671
672            pub fn public_hotspots(idx, panel_id, hotspots) {
673                let idx_key = "" + idx;
674                let action_map_path = "local/game/panel_actions/" + idx_key;
675
676                let existing_action_map = root::get(action_map_path);
677                if !existing_action_map.is_map() {
678                    root::add_map(action_map_path);
679                }
680
681                if hotspots.is_map() {
682                    let public_items = {};
683                    for action_id in hotspots.keys() {
684                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
685                    }
686                    return public_items;
687                }
688
689                let public_items = [];
690                let i = 0;
691                while i < hotspots.len() {
692                    let hotspot = hotspots.get_idx(i);
693                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
694                    public_items.push(item);
695                    i = i + 1;
696                }
697
698                public_items
699            }
700            "#
701            .to_vec(),
702        )?;
703
704        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
705        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
706        Ok(())
707    }
708
709    #[test]
710    fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
711        let vm = Vm::with_all()?;
712        vm.import_code(
713            "vm_send_panel_public_hotspots",
714            br#"
715            pub fn ok(value) {
716                value
717            }
718
719            pub fn panel_from_node(req) {
720                {
721                    panel_id: req.panel_id,
722                    hotspots: req.hotspots
723                }
724            }
725
726            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
727                {
728                    path: action_map_path,
729                    panel_id: panel_id,
730                    action_id: action_id,
731                    id: hotspot.id
732                }
733            }
734
735            pub fn public_hotspots(idx, panel_id, hotspots) {
736                let idx_key = "" + idx;
737                let action_map_path = "local/game/panel_actions/" + idx_key;
738
739                let existing_action_map = root::get(action_map_path);
740                if !existing_action_map.is_map() {
741                    root::add_map(action_map_path);
742                }
743
744                if hotspots.is_map() {
745                    let public_items = {};
746                    for action_id in hotspots.keys() {
747                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
748                    }
749                    return public_items;
750                }
751
752                let public_items = [];
753                let i = 0;
754                while i < hotspots.len() {
755                    let hotspot = hotspots.get_idx(i);
756                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
757                    public_items.push(item);
758                    i = i + 1;
759                }
760
761                public_items
762            }
763
764            pub fn send_panel(req) {
765                let panel = req.panel;
766                if !panel.is_map() {
767                    panel = panel_from_node(req);
768                }
769                if !panel.is_map() {
770                    return ok({
771                        id: 4,
772                        type: "panel_rejected",
773                        reason: "invalid panel"
774                    });
775                }
776                panel.id = 4;
777                panel.idx = req.idx;
778                if !panel.contains("type") {
779                    panel.type = "panel";
780                }
781                if panel.contains("hotspots") {
782                    panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
783                }
784                root::send_idx("local/ws", req.idx, panel);
785                ok({
786                    id: 4,
787                    type: "panel",
788                    panel_id: panel.panel_id
789                })
790            }
791            "#
792            .to_vec(),
793        )?;
794
795        let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
796        assert_eq!(compiled.ret_ty(), &Type::Any);
797        let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
798        let req = dynamic::map!(
799            "idx"=> 7i64,
800            "panel"=> dynamic::map!(
801                "panel_id"=> "main",
802                "hotspots"=> dynamic::map!(
803                    "open"=> dynamic::map!("id"=> "open")
804                )
805            )
806        );
807        let result = unsafe { &*send_panel(&req) };
808
809        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
810        assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
811        Ok(())
812    }
813
814    #[test]
815    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
816        let vm = Vm::with_all()?;
817        vm.import_code(
818            "vm_dynamic_field_or",
819            r#"
820            pub fn next_or_start() {
821                let choice = {
822                    label: "颜色",
823                    next: "color"
824                };
825                choice.next || "start"
826            }
827
828            pub fn direct_next() {
829                let choice = {
830                    label: "颜色",
831                    next: "color"
832                };
833                choice.next
834            }
835
836            pub fn bracket_next() {
837                let choice = {
838                    label: "颜色",
839                    next: "color"
840                };
841                choice["next"]
842            }
843
844            pub fn assigned_preview() {
845                let choice = {
846                    next: "tax_free"
847                };
848                choice.preview = choice.next || "start";
849                choice
850            }
851            "#
852            .as_bytes()
853            .to_vec(),
854        )?;
855
856        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
857        assert_eq!(compiled.ret_ty(), &Type::Any);
858        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
859        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
860
861        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
862        assert_eq!(compiled.ret_ty(), &Type::Any);
863        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
864        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
865
866        let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
867        assert_eq!(compiled.ret_ty(), &Type::Any);
868        let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
869        assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
870
871        let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
872        assert_eq!(compiled.ret_ty(), &Type::Any);
873        let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
874        let choice = unsafe { &*assigned_preview() };
875        assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
876        Ok(())
877    }
878
879    #[test]
880    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
881        let vm = Vm::with_all()?;
882        vm.import_code(
883            "vm_if_empty_object_branch",
884            r#"
885            pub fn first_note(steps) {
886                let first = if steps.len() > 0 { steps[0] } else { {} };
887                let first_note = first.note || "fallback";
888                first_note
889            }
890
891            pub fn first_ja(steps) {
892                let first = if steps.len() > 0 { steps[0] } else { {} };
893                first.ja || "すみません"
894            }
895
896            pub fn assign_first_note(steps) {
897                let first = {};
898                first = if steps.len() > 0 { steps[0] } else { {} };
899                first.note || "fallback"
900            }
901            "#
902            .as_bytes()
903            .to_vec(),
904        )?;
905
906        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
907        assert_eq!(compiled.ret_ty(), &Type::Any);
908        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
909
910        let empty_steps = Dynamic::list(Vec::new());
911        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
912
913        let mut step = std::collections::BTreeMap::new();
914        step.insert("note".into(), "hello".into());
915        let steps = Dynamic::list(vec![Dynamic::map(step)]);
916        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
917
918        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
919        assert_eq!(compiled.ret_ty(), &Type::Any);
920        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
921        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
922
923        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
924        assert_eq!(compiled.ret_ty(), &Type::Any);
925        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
926        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
927        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
928        Ok(())
929    }
930
931    #[test]
932    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
933        let vm = Vm::with_all()?;
934        vm.import_code(
935            "vm_tail_list_literal",
936            r#"
937            pub fn numbers() {
938                [1, 2, 3]
939            }
940
941            pub fn maps() {
942                [
943                    {note: "first"},
944                    {note: "second"}
945                ]
946            }
947
948            pub fn object_with_maps() {
949                {
950                    steps: [
951                        {note: "first"},
952                        {note: "second"}
953                    ]
954                }
955            }
956
957            pub fn return_maps() {
958                return [
959                    {note: "first"},
960                    {note: "second"}
961                ];
962            }
963
964            pub fn return_maps_without_semicolon() {
965                return [
966                    {note: "first"},
967                    {note: "second"}
968                ]
969            }
970
971            pub fn tail_bare_variable() {
972                let value = [
973                    {note: "first"},
974                    {note: "second"}
975                ];
976                value
977            }
978
979            pub fn return_bare_variable_without_semicolon() {
980                let value = [
981                    {note: "first"},
982                    {note: "second"}
983                ];
984                return value
985            }
986
987            pub fn tail_object_variable() {
988                let result = {
989                    steps: [
990                        {note: "first"},
991                        {note: "second"}
992                    ]
993                };
994                result
995            }
996            "#
997            .as_bytes()
998            .to_vec(),
999        )?;
1000
1001        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1002        assert_eq!(compiled.ret_ty(), &Type::Any);
1003        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1004        let result = unsafe { &*numbers() };
1005        assert_eq!(result.len(), 3);
1006        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1007
1008        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1009        assert_eq!(compiled.ret_ty(), &Type::Any);
1010        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1011        let result = unsafe { &*maps() };
1012        assert_eq!(result.len(), 2);
1013        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1014
1015        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1016        assert_eq!(compiled.ret_ty(), &Type::Any);
1017        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1018        let result = unsafe { &*object_with_maps() };
1019        let steps = result.get_dynamic("steps").expect("steps");
1020        assert_eq!(steps.len(), 2);
1021        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1022
1023        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1024        assert_eq!(compiled.ret_ty(), &Type::Any);
1025        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1026        let result = unsafe { &*return_maps() };
1027        assert_eq!(result.len(), 2);
1028        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1029
1030        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1031        assert_eq!(compiled.ret_ty(), &Type::Any);
1032        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1033        let result = unsafe { &*return_maps_without_semicolon() };
1034        assert_eq!(result.len(), 2);
1035        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1036
1037        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1038        assert_eq!(compiled.ret_ty(), &Type::Any);
1039        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1040        let result = unsafe { &*tail_bare_variable() };
1041        assert_eq!(result.len(), 2);
1042        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1043
1044        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1045        assert_eq!(compiled.ret_ty(), &Type::Any);
1046        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1047        let result = unsafe { &*return_bare_variable_without_semicolon() };
1048        assert_eq!(result.len(), 2);
1049        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1050
1051        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1052        assert_eq!(compiled.ret_ty(), &Type::Any);
1053        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1054        let result = unsafe { &*tail_object_variable() };
1055        let steps = result.get_dynamic("steps").expect("steps");
1056        assert_eq!(steps.len(), 2);
1057        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1058        Ok(())
1059    }
1060
1061    #[test]
1062    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1063        fn extra_page_literal(depth: usize) -> String {
1064            let mut value = "{leaf: \"done\"}".to_string();
1065            for idx in 0..depth {
1066                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1067            }
1068            value
1069        }
1070
1071        let extra = extra_page_literal(48);
1072        let code = format!(
1073            r#"
1074            pub fn script() {{
1075                return [
1076                    {{ja: "一つ目", note: "first", extra: {extra}}},
1077                    {{ja: "二つ目", note: "second", extra: {extra}}},
1078                    {{ja: "三つ目", note: "third", extra: {extra}}}
1079                ]
1080            }}
1081            "#
1082        );
1083
1084        let vm = Vm::with_all()?;
1085        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1086        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1087        assert_eq!(compiled.ret_ty(), &Type::Any);
1088        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1089        let result = unsafe { &*script() };
1090        assert_eq!(result.len(), 3);
1091        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1092        Ok(())
1093    }
1094
1095    #[test]
1096    fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1097        let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1098        std::fs::write(&module_path, "pub fn value() { 41 }")?;
1099        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1100
1101        let vm1 = Vm::with_all()?;
1102        vm1.import_code(
1103            "vm_import_owner",
1104            format!(
1105                r#"
1106                pub fn run() {{
1107                    import("vm_imported_owner", "{module_path}");
1108                }}
1109                "#
1110            )
1111            .into_bytes(),
1112        )?;
1113        let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1114
1115        let vm2 = Vm::with_all()?;
1116        vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1117        let _ = vm2.get_fn("vm_import_other::run", &[])?;
1118
1119        let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1120        run();
1121
1122        assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1123        assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1124        Ok(())
1125    }
1126
1127    #[test]
1128    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1129        let vm = Vm::with_all()?;
1130        vm.import_code(
1131            "vm_object_last_call_field",
1132            r#"
1133            pub fn extra_page() {
1134                {
1135                    title: "extra",
1136                    pages: [
1137                        {note: "nested"}
1138                    ]
1139                }
1140            }
1141
1142            pub fn data() {
1143                return [
1144                    {
1145                        note: "first",
1146                        choices: ["a", "b"],
1147                        extras: extra_page()
1148                    },
1149                    {
1150                        note: "second",
1151                        choices: ["c"],
1152                        extras: extra_page()
1153                    }
1154                ]
1155            }
1156            "#
1157            .as_bytes()
1158            .to_vec(),
1159        )?;
1160
1161        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1162        assert_eq!(compiled.ret_ty(), &Type::Any);
1163        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1164        let result = unsafe { &*data() };
1165        assert_eq!(result.len(), 2);
1166        let first = result.get_idx(0).expect("first step");
1167        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1168        Ok(())
1169    }
1170
1171    #[test]
1172    fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
1173        let vm = Vm::with_all()?;
1174        vm.import_code(
1175            "vm_gpu_layout",
1176            br#"
1177            pub struct Params {
1178                a: u32,
1179                b: u32,
1180                c: u32,
1181            }
1182            "#
1183            .to_vec(),
1184        )?;
1185
1186        let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
1187        assert_eq!(layout.size, 16);
1188        assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
1189
1190        let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
1191        let bytes = layout.pack_map(&value)?;
1192        assert_eq!(bytes.len(), 16);
1193        assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
1194        assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
1195        assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
1196
1197        let read = layout.unpack_map(&bytes)?;
1198        assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
1199        assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
1200        assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
1201        Ok(())
1202    }
1203
1204    #[test]
1205    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
1206        let vm = Vm::with_all()?;
1207        vm.import_code(
1208            "vm_root_clone_bridge",
1209            br#"
1210            pub fn add_then_reuse(arg) {
1211                let user = {
1212                    address: "test-wallet",
1213                    points: 20
1214                };
1215                root::add("local/root-clone-bridge-user", user);
1216                user.points = user.points - 7;
1217                root::add("local/root-clone-bridge-user", user);
1218                {
1219                    user: user,
1220                    points: user.points
1221                }
1222            }
1223            "#
1224            .to_vec(),
1225        )?;
1226
1227        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
1228        assert_eq!(compiled.ret_ty(), &Type::Any);
1229        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1230        let arg = Dynamic::Null;
1231        let result = add_then_reuse(&arg);
1232        let result = unsafe { &*result };
1233
1234        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
1235        let mut json = String::new();
1236        result.to_json(&mut json);
1237        assert!(json.contains("\"points\": 13"));
1238        Ok(())
1239    }
1240}