1use intuicio_core::{
2 IntuicioVersion, Visibility,
3 context::Context,
4 crate_version,
5 function::{FunctionQuery, FunctionQueryParameter},
6 registry::Registry,
7 script::{
8 BytesContentParser, ScriptContentProvider, ScriptEnum, ScriptEnumVariant, ScriptExpression,
9 ScriptFunction, ScriptFunctionParameter, ScriptFunctionSignature, ScriptHandle,
10 ScriptModule, ScriptOperation, ScriptPackage, ScriptStruct, ScriptStructField,
11 },
12 types::TypeQuery,
13};
14use serde::{Deserialize, Serialize};
15use std::{collections::HashMap, error::Error};
16
17pub fn frontend_vault_version() -> IntuicioVersion {
18 crate_version!()
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum VaultLiteral {
23 Unit,
24 Bool(bool),
25 I8(i8),
26 I16(i16),
27 I32(i32),
28 I64(i64),
29 I128(i128),
30 Isize(isize),
31 U8(u8),
32 U16(u16),
33 U32(u32),
34 U64(u64),
35 U128(u128),
36 Usize(usize),
37 F32(f32),
38 F64(f64),
39 Char(char),
40 String(String),
41}
42
43impl VaultLiteral {
44 fn evaluate(&self, context: &mut Context) {
45 match self {
46 Self::Unit => context.stack().push(()),
47 Self::Bool(value) => context.stack().push(*value),
48 Self::I8(value) => context.stack().push(*value),
49 Self::I16(value) => context.stack().push(*value),
50 Self::I32(value) => context.stack().push(*value),
51 Self::I64(value) => context.stack().push(*value),
52 Self::I128(value) => context.stack().push(*value),
53 Self::Isize(value) => context.stack().push(*value),
54 Self::U8(value) => context.stack().push(*value),
55 Self::U16(value) => context.stack().push(*value),
56 Self::U32(value) => context.stack().push(*value),
57 Self::U64(value) => context.stack().push(*value),
58 Self::U128(value) => context.stack().push(*value),
59 Self::Usize(value) => context.stack().push(*value),
60 Self::F32(value) => context.stack().push(*value),
61 Self::F64(value) => context.stack().push(*value),
62 Self::Char(value) => context.stack().push(*value),
63 Self::String(value) => context.stack().push(value.to_owned()),
64 };
65 }
66}
67
68#[derive(Debug)]
69pub enum VaultScriptExpression {
70 Literal(VaultLiteral),
71 StackDrop,
72 StackProduce { name: String },
73}
74
75impl ScriptExpression for VaultScriptExpression {
76 fn evaluate(&self, context: &mut Context, registry: &Registry) {
77 match self {
78 Self::Literal(literal) => {
79 literal.evaluate(context);
80 }
81 Self::StackDrop => {
82 context.stack().drop();
83 }
84 Self::StackProduce { name } => {
85 let type_hash = context.stack().peek().unwrap();
86 registry
87 .find_function(FunctionQuery {
88 name: Some(name.into()),
89 type_query: Some(TypeQuery {
90 type_hash: Some(type_hash),
91 ..Default::default()
92 }),
93 inputs: [FunctionQueryParameter {
94 type_query: Some(TypeQuery {
95 type_hash: Some(type_hash),
96 ..Default::default()
97 }),
98 ..Default::default()
99 }]
100 .as_slice()
101 .into(),
102 ..Default::default()
103 })
104 .unwrap()
105 .invoke(context, registry);
106 }
107 }
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub enum VaultExpression {
113 DefineVariable {
114 name: String,
115 },
116 TakeVariable {
117 name: String,
118 },
119 CloneVariable {
120 name: String,
121 },
122 VariableRef {
123 name: String,
124 },
125 VariableRefMut {
126 name: String,
127 },
128 Literal(VaultLiteral),
129 CallFunction {
130 #[serde(default, skip_serializing_if = "Option::is_none")]
131 module_name: Option<String>,
132 name: String,
133 #[serde(default, skip_serializing_if = "Vec::is_empty")]
134 arguments: Vec<VaultExpression>,
135 },
136 CallMethod {
137 #[serde(default, skip_serializing_if = "Option::is_none")]
138 module_name: Option<String>,
139 type_name: String,
140 name: String,
141 #[serde(default, skip_serializing_if = "Vec::is_empty")]
142 arguments: Vec<VaultExpression>,
143 },
144 If {
145 condition: Box<VaultExpression>,
146 success: Vec<VaultStatement>,
147 #[serde(default, skip_serializing_if = "Option::is_none")]
148 failure: Option<Vec<VaultStatement>>,
149 },
150}
151
152impl VaultExpression {
153 pub fn compile(
154 &self,
155 result: &mut Vec<ScriptOperation<VaultScriptExpression>>,
156 registers: &mut Vec<String>,
157 ) {
158 match self {
159 Self::DefineVariable { name } => {
160 if let Some(item) = registers.iter_mut().find(|n| n == &name) {
161 item.clone_from(name);
162 } else {
163 registers.push(name.to_owned());
164 }
165 }
166 Self::TakeVariable { name } => {
167 result.push(ScriptOperation::PushFromRegister {
168 index: registers.iter().position(|n| n == name.as_str()).unwrap(),
169 });
170 }
171 Self::CloneVariable { name } => {
172 result.push(ScriptOperation::PushFromRegister {
173 index: registers.iter().position(|n| n == name.as_str()).unwrap(),
174 });
175 result.push(ScriptOperation::Expression {
176 expression: VaultScriptExpression::StackProduce {
177 name: "clone".to_owned(),
178 },
179 });
180 result.push(ScriptOperation::PopToRegister {
181 index: registers.iter().position(|n| n == name.as_str()).unwrap(),
182 });
183 }
184 Self::VariableRef { name } => {
185 result.push(ScriptOperation::PushFromRegister {
186 index: registers.iter().position(|n| n == name.as_str()).unwrap(),
187 });
188 result.push(ScriptOperation::Expression {
189 expression: VaultScriptExpression::StackProduce {
190 name: "ref".to_owned(),
191 },
192 });
193 result.push(ScriptOperation::PopToRegister {
194 index: registers.iter().position(|n| n == name.as_str()).unwrap(),
195 });
196 }
197 Self::VariableRefMut { name } => {
198 result.push(ScriptOperation::PushFromRegister {
199 index: registers.iter().position(|n| n == name.as_str()).unwrap(),
200 });
201 result.push(ScriptOperation::Expression {
202 expression: VaultScriptExpression::StackProduce {
203 name: "ref_mut".to_owned(),
204 },
205 });
206 result.push(ScriptOperation::PopToRegister {
207 index: registers.iter().position(|n| n == name.as_str()).unwrap(),
208 });
209 }
210 Self::Literal(literal) => {
211 result.push(ScriptOperation::Expression {
212 expression: VaultScriptExpression::Literal(literal.to_owned()),
213 });
214 }
215 Self::CallFunction {
216 module_name,
217 name,
218 arguments,
219 } => {
220 for argument in arguments.iter().rev() {
221 argument.compile(result, registers);
222 }
223 result.push(ScriptOperation::CallFunction {
224 query: FunctionQuery {
225 name: Some(name.to_owned().into()),
226 module_name: module_name.as_ref().map(|name| name.to_owned().into()),
227 ..Default::default()
228 },
229 });
230 }
231 Self::CallMethod {
232 module_name,
233 type_name,
234 name,
235 arguments,
236 } => {
237 for argument in arguments.iter().rev() {
238 argument.compile(result, registers);
239 }
240 result.push(ScriptOperation::CallFunction {
241 query: FunctionQuery {
242 name: Some(name.to_owned().into()),
243 type_query: Some(TypeQuery {
244 name: Some(type_name.to_owned().into()),
245 ..Default::default()
246 }),
247 module_name: module_name.as_ref().map(|name| name.to_owned().into()),
248 ..Default::default()
249 },
250 });
251 }
252 Self::If {
253 condition,
254 success,
255 failure,
256 } => {
257 condition.compile(result, registers);
258 let mut success_operations = vec![];
259 for statement in success {
260 statement.compile(&mut success_operations, registers);
261 }
262 let failure_handle = failure.as_ref().map(|failure| {
263 let mut operations = vec![];
264 for statement in failure {
265 statement.compile(&mut operations, registers);
266 }
267 ScriptHandle::new(operations)
268 });
269 result.push(ScriptOperation::BranchScope {
270 scope_success: ScriptHandle::new(success_operations),
271 scope_failure: failure_handle,
272 });
273 }
274 }
275 }
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub enum VaultStatement {
280 MakeVariable {
281 name: String,
282 expression: VaultExpression,
283 },
284 Expression(VaultExpression),
285 Return(VaultExpression),
286 Scope(Vec<VaultStatement>),
287 While {
288 condition: Box<VaultExpression>,
289 statements: Vec<VaultStatement>,
290 },
291 For {
292 setup: Vec<VaultStatement>,
293 condition: Box<VaultExpression>,
294 advancement: Vec<VaultStatement>,
295 statements: Vec<VaultStatement>,
296 },
297}
298
299impl VaultStatement {
300 pub fn compile(
301 &self,
302 result: &mut Vec<ScriptOperation<VaultScriptExpression>>,
303 registers: &mut Vec<String>,
304 ) {
305 match self {
306 Self::MakeVariable { name, expression } => {
307 expression.compile(result, registers);
308 result.push(ScriptOperation::PopToRegister {
309 index: registers.iter().position(|n| n == name.as_str()).unwrap(),
310 });
311 }
312 Self::Expression(expression) => {
313 expression.compile(result, registers);
314 result.push(ScriptOperation::Expression {
315 expression: VaultScriptExpression::StackDrop,
316 });
317 }
318 Self::Return(expression) => {
319 expression.compile(result, registers);
320 result.push(ScriptOperation::Expression {
321 expression: VaultScriptExpression::Literal(VaultLiteral::Bool(false)),
322 });
323 result.push(ScriptOperation::ContinueScopeConditionally);
324 }
325 Self::Scope(expressions) => {
326 let mut operations = vec![];
327 for statement in expressions {
328 statement.compile(&mut operations, registers);
329 }
330 result.push(ScriptOperation::PushScope {
331 scope: ScriptHandle::new(operations),
332 });
333 }
334 Self::While {
335 condition,
336 statements,
337 } => {
338 let mut operations = vec![];
339 condition.compile(&mut operations, registers);
340 operations.push(ScriptOperation::ContinueScopeConditionally);
341 for statement in statements {
342 statement.compile(&mut operations, registers);
343 }
344 result.push(ScriptOperation::LoopScope {
345 scope: ScriptHandle::new(operations),
346 });
347 }
348 Self::For {
349 setup,
350 condition,
351 advancement,
352 statements,
353 } => {
354 for statement in setup {
355 statement.compile(result, registers);
356 }
357 let mut operations = vec![];
358 condition.compile(&mut operations, registers);
359 operations.push(ScriptOperation::ContinueScopeConditionally);
360 for statement in statements {
361 statement.compile(&mut operations, registers);
362 }
363 for statement in advancement {
364 statement.compile(result, registers);
365 }
366 result.push(ScriptOperation::LoopScope {
367 scope: ScriptHandle::new(operations),
368 });
369 }
370 }
371 }
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct VaultFunctionParameter {
376 pub name: String,
377 pub arg_type: String,
378}
379
380impl VaultFunctionParameter {
381 pub fn build(&self) -> ScriptFunctionParameter<'static> {
382 ScriptFunctionParameter {
383 meta: None,
384 name: self.name.to_owned(),
385 type_query: TypeQuery {
386 name: Some(self.arg_type.as_str().to_owned().into()),
387 ..Default::default()
388 },
389 }
390 }
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct VaultFunction {
395 pub name: String,
396 #[serde(default, skip_serializing_if = "Vec::is_empty")]
397 pub arguments: Vec<VaultFunctionParameter>,
398 #[serde(default, skip_serializing_if = "Option::is_none")]
399 pub return_type: Option<String>,
400 pub statements: Vec<VaultStatement>,
401}
402
403impl VaultFunction {
404 pub fn compile(
405 &self,
406 module_name: &str,
407 type_query: Option<TypeQuery<'static>>,
408 ) -> ScriptFunction<'static, VaultScriptExpression> {
409 let signature = ScriptFunctionSignature {
410 meta: None,
411 name: self.name.to_owned(),
412 module_name: module_name.to_owned().into(),
413 type_query,
414 visibility: Visibility::Public,
415 inputs: self.arguments.iter().map(|input| input.build()).collect(),
416 outputs: vec![ScriptFunctionParameter {
417 meta: None,
418 name: "result".to_owned(),
419 type_query: if let Some(return_type) = &self.return_type {
420 TypeQuery {
421 name: Some(return_type.to_owned().into()),
422 ..Default::default()
423 }
424 } else {
425 TypeQuery::of::<()>()
426 },
427 }],
428 };
429 let mut registers = Vec::<String>::new();
430 let mut operations = vec![];
431 for argument in &self.arguments {
432 if let Some(item) = registers.iter_mut().find(|n| *n == &argument.name) {
433 item.clone_from(&argument.name);
434 } else {
435 registers.push(argument.name.to_owned());
436 }
437 operations.push(ScriptOperation::DefineRegister {
438 query: TypeQuery {
439 name: Some(argument.arg_type.to_owned().into()),
440 ..Default::default()
441 },
442 });
443 operations.push(ScriptOperation::PopToRegister {
444 index: registers.iter().position(|n| n == &argument.name).unwrap(),
445 });
446 }
447 for statement in &self.statements {
448 statement.compile(&mut operations, &mut registers);
449 }
450 ScriptFunction {
451 signature,
452 script: ScriptHandle::new(operations),
453 }
454 }
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct VaultStructField {
459 pub name: String,
460 pub type_name: String,
461}
462
463impl VaultStructField {
464 pub fn build(&self) -> ScriptStructField<'static> {
465 ScriptStructField {
466 meta: None,
467 name: self.name.to_owned(),
468 visibility: Visibility::Public,
469 type_query: TypeQuery {
470 name: Some(self.type_name.as_str().to_owned().into()),
471 ..Default::default()
472 },
473 }
474 }
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct VaultStruct {
479 pub name: String,
480 #[serde(default, skip_serializing_if = "Vec::is_empty")]
481 pub fields: Vec<VaultStructField>,
482 #[serde(default, skip_serializing_if = "Vec::is_empty")]
483 pub methods: Vec<VaultFunction>,
484}
485
486impl VaultStruct {
487 pub fn compile_struct(&self, module_name: &str) -> ScriptStruct<'static> {
488 ScriptStruct {
489 meta: None,
490 name: self.name.to_owned(),
491 module_name: Some(module_name.to_owned()),
492 visibility: Visibility::Public,
493 fields: self.fields.iter().map(|field| field.build()).collect(),
494 }
495 }
496
497 pub fn compile_methods(
498 &self,
499 module_name: &str,
500 ) -> Vec<ScriptFunction<'static, VaultScriptExpression>> {
501 let type_query = TypeQuery {
502 name: Some(self.name.as_str().to_owned().into()),
503 module_name: Some(module_name.to_owned().into()),
504 ..Default::default()
505 };
506 self.methods
507 .iter()
508 .map(|method| method.compile(module_name, Some(type_query.clone())))
509 .collect()
510 }
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize)]
514pub struct VaultEnumVariant {
515 pub name: String,
516 pub fields: Vec<VaultStructField>,
517}
518
519impl VaultEnumVariant {
520 pub fn build(&self) -> ScriptEnumVariant<'static> {
521 ScriptEnumVariant {
522 meta: None,
523 name: self.name.to_owned(),
524 fields: self.fields.iter().map(|field| field.build()).collect(),
525 discriminant: None,
526 }
527 }
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct VaultEnum {
532 pub name: String,
533 #[serde(default, skip_serializing_if = "Vec::is_empty")]
534 pub variants: Vec<VaultEnumVariant>,
535 #[serde(default, skip_serializing_if = "Vec::is_empty")]
536 pub methods: Vec<VaultFunction>,
537}
538
539impl VaultEnum {
540 pub fn compile_enum(&self, module_name: &str) -> ScriptEnum<'static> {
541 ScriptEnum {
542 meta: None,
543 name: self.name.to_owned(),
544 module_name: Some(module_name.to_owned()),
545 visibility: Visibility::Public,
546 variants: self.variants.iter().map(|field| field.build()).collect(),
547 default_variant: None,
548 }
549 }
550
551 pub fn compile_methods(
552 &self,
553 module_name: &str,
554 ) -> Vec<ScriptFunction<'static, VaultScriptExpression>> {
555 let type_query = TypeQuery {
556 name: Some(self.name.as_str().to_owned().into()),
557 module_name: Some(module_name.to_owned().into()),
558 ..Default::default()
559 };
560 self.methods
561 .iter()
562 .map(|method| method.compile(module_name, Some(type_query.clone())))
563 .collect()
564 }
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
568pub enum VaultDefinition {
569 Function(VaultFunction),
570 Struct(VaultStruct),
571 Enum(VaultEnum),
572}
573
574#[derive(Debug, Clone, Serialize, Deserialize)]
575pub struct VaultModule {
576 pub name: String,
577 #[serde(default, skip_serializing_if = "Vec::is_empty")]
578 pub dependencies: Vec<String>,
579 #[serde(default, skip_serializing_if = "Vec::is_empty")]
580 pub definitions: Vec<VaultDefinition>,
581}
582
583impl VaultModule {
584 pub fn parse(content: &str) -> Result<Self, serde_lexpr::Error> {
585 serde_lexpr::from_str(content)
586 }
587
588 pub fn compile(&self) -> ScriptModule<'static, VaultScriptExpression> {
589 ScriptModule {
590 name: self.name.to_owned(),
591 structs: self
592 .definitions
593 .iter()
594 .filter_map(|definition| match definition {
595 VaultDefinition::Struct(struct_type) => {
596 Some(struct_type.compile_struct(&self.name))
597 }
598 _ => None,
599 })
600 .collect(),
601 enums: self
602 .definitions
603 .iter()
604 .filter_map(|definition| match definition {
605 VaultDefinition::Enum(enum_type) => Some(enum_type.compile_enum(&self.name)),
606 _ => None,
607 })
608 .collect(),
609 functions: self
610 .definitions
611 .iter()
612 .filter_map(|definition| match definition {
613 VaultDefinition::Function(function) => Some(function.compile(&self.name, None)),
614 _ => None,
615 })
616 .chain(
617 self.definitions
618 .iter()
619 .filter_map(|definition| match definition {
620 VaultDefinition::Struct(struct_type) => {
621 Some(struct_type.compile_methods(&self.name))
622 }
623 VaultDefinition::Enum(enum_type) => {
624 Some(enum_type.compile_methods(&self.name))
625 }
626 _ => None,
627 })
628 .flatten(),
629 )
630 .collect(),
631 }
632 }
633}
634
635#[derive(Default)]
636pub struct VaultPackage {
637 pub modules: HashMap<String, VaultModule>,
638}
639
640impl VaultPackage {
641 pub fn new<CP>(path: &str, content_provider: &mut CP) -> Result<Self, Box<dyn Error>>
642 where
643 CP: ScriptContentProvider<VaultModule>,
644 {
645 let mut result = Self::default();
646 result.load(path, content_provider)?;
647 Ok(result)
648 }
649
650 pub fn load<CP>(&mut self, path: &str, content_provider: &mut CP) -> Result<(), Box<dyn Error>>
651 where
652 CP: ScriptContentProvider<VaultModule>,
653 {
654 let path = content_provider.sanitize_path(path)?;
655 if self.modules.contains_key(&path) {
656 return Ok(());
657 }
658 for content in content_provider.unpack_load(&path)? {
659 if let Some(module) = content.data? {
660 let dependencies = module.dependencies.to_owned();
661 self.modules.insert(content.name, module);
662 for relative in dependencies {
663 let path = content_provider.join_paths(&content.path, &relative)?;
664 self.load(&path, content_provider)?;
665 }
666 }
667 }
668 Ok(())
669 }
670
671 pub fn compile(&self) -> ScriptPackage<'static, VaultScriptExpression> {
672 ScriptPackage {
673 modules: self
674 .modules
675 .values()
676 .map(|module| module.compile())
677 .collect(),
678 }
679 }
680}
681
682pub struct VaultContentParser;
683
684impl BytesContentParser<VaultModule> for VaultContentParser {
685 fn parse(&self, bytes: Vec<u8>) -> Result<VaultModule, Box<dyn Error>> {
686 let content = String::from_utf8(bytes)?;
687 Ok(VaultModule::parse(&content)?)
688 }
689}
690
691#[macro_export]
692macro_rules! define_vault_function {
693 (
694 $registry:expr
695 =>
696 $(mod $module_name:ident)?
697 fn
698 $name:ident
699 ($( $argument_name:ident : $argument_type:ty),*)
700 ->
701 $return_type:ty
702 $code:block
703 ) => {
704 intuicio_core::function::Function::new(
705 intuicio_core::function_signature! {
706 $registry
707 =>
708 $(mod $module_name)?
709 fn
710 $name
711 ($($argument_name : $argument_type),*)
712 ->
713 (result : $return_type)
714 },
715 intuicio_core::function::FunctionBody::closure(move |context, #[allow(unused_variables)] registry| {
716 use intuicio_data::data_stack::DataStackPack;
717 #[allow(unused_mut)]
718 let ($(mut $argument_name,)*) = <($($argument_type,)*)>::stack_pop(context.stack());
719 context.stack().push($code);
720 }),
721 )
722 };
723}
724
725#[macro_export]
726macro_rules! define_vault_method {
727 (
728 $registry:expr
729 =>
730 $(mod $module_name:ident)?
731 type ($type:ty)
732 fn
733 $name:ident
734 ($( $argument_name:ident : $argument_type:ty),*)
735 ->
736 $return_type:ty
737 $code:block
738 ) => {
739 intuicio_core::function::Function::new(
740 intuicio_core::function_signature! {
741 $registry
742 =>
743 $(mod $module_name)?
744 type ($type)
745 fn
746 $name
747 ($($argument_name : $argument_type),*)
748 ->
749 (result : $return_type)
750 },
751 intuicio_core::function::FunctionBody::closure(move |context, registry| {
752 use intuicio_data::data_stack::DataStackPack;
753 #[allow(unused_mut)]
754 let ($(mut $argument_name,)*) = <($($argument_type,)*)>::stack_pop(context.stack());
755 context.stack().push($code);
756 }),
757 )
758 };
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764 use intuicio_backend_vm::scope::VmScope;
765 use intuicio_core::{define_function, host::Host, script::FileContentProvider};
766
767 #[test]
768 fn test_vault_script() {
769 let mut registry = Registry::default().with_basic_types();
770 registry.add_function(define_vault_function! {
771 registry => mod intrinsics fn print(content: String) -> () {
772 println!("PRINT: {content}");
773 }
774 });
775 registry.add_function(define_vault_function! {
776 registry => mod intrinsics fn add(a: usize, b: usize) -> usize {
777 a + b
778 }
779 });
780 registry.add_function(define_vault_function! {
781 registry => mod intrinsics fn sub(a: usize, b: usize) -> usize {
782 a - b
783 }
784 });
785 registry.add_function(define_vault_function! {
786 registry => mod intrinsics fn less_than(a: usize, b: usize) -> bool {
787 a < b
788 }
789 });
790 registry.add_function(define_function! {
791 registry => mod intrinsics type (usize) fn clone(this: usize) -> (original: usize, clone: usize) {
792 (this, this)
793 }
794 });
795 let mut content_provider = FileContentProvider::new("vault", VaultContentParser);
796 VaultPackage::new("../../resources/package.vault", &mut content_provider)
797 .unwrap()
798 .compile()
799 .install::<VmScope<VaultScriptExpression>>(
800 &mut registry,
801 None,
802 );
810 let mut vm = Host::new(Context::new(10240, 10240), registry.into());
811 let (result,) = vm
812 .call_function::<(usize,), _>("main", "test", None)
813 .unwrap()
814 .run(());
815 assert_eq!(vm.context().stack().position(), 0);
816 assert_eq!(result, 42);
817 let (result,) = vm
818 .call_function::<(usize,), (usize,)>("fib", "test", None)
819 .unwrap()
820 .run((20,));
821 assert_eq!(vm.context().stack().position(), 0);
822 assert_eq!(result, 6765);
823 }
824}