intuicio_backend_vm/
scope.rs

1use crate::debugger::VmDebuggerHandle;
2use intuicio_core::{
3    context::Context,
4    function::FunctionBody,
5    registry::Registry,
6    script::{ScriptExpression, ScriptFunctionGenerator, ScriptHandle, ScriptOperation},
7};
8use typid::ID;
9
10pub type VmScopeSymbol = ID<()>;
11
12pub struct VmScope<'a, SE: ScriptExpression> {
13    handle: ScriptHandle<'a, SE>,
14    symbol: VmScopeSymbol,
15    position: usize,
16    child: Option<Box<Self>>,
17    debugger: Option<VmDebuggerHandle<SE>>,
18}
19
20impl<'a, SE: ScriptExpression> VmScope<'a, SE> {
21    pub fn new(handle: ScriptHandle<'a, SE>, symbol: VmScopeSymbol) -> Self {
22        Self {
23            handle,
24            symbol,
25            position: 0,
26            child: None,
27            debugger: None,
28        }
29    }
30
31    pub fn with_debugger(mut self, debugger: Option<VmDebuggerHandle<SE>>) -> Self {
32        self.debugger = debugger;
33        self
34    }
35
36    pub fn symbol(&self) -> VmScopeSymbol {
37        self.symbol
38    }
39
40    pub fn position(&self) -> usize {
41        self.position
42    }
43
44    pub fn has_completed(&self) -> bool {
45        self.position >= self.handle.len()
46    }
47
48    pub fn run(&mut self, context: &mut Context, registry: &Registry) {
49        while self.step(context, registry) {}
50    }
51
52    pub fn step(&mut self, context: &mut Context, registry: &Registry) -> bool {
53        if let Some(child) = &mut self.child {
54            if child.step(context, registry) {
55                return true;
56            } else {
57                self.child = None;
58            }
59        }
60        if self.position == 0
61            && let Some(debugger) = self.debugger.as_ref()
62            && let Ok(mut debugger) = debugger.try_write()
63        {
64            debugger.on_enter_scope(self, context, registry);
65        }
66        let result = if let Some(operation) = self.handle.get(self.position) {
67            if let Some(debugger) = self.debugger.as_ref()
68                && let Ok(mut debugger) = debugger.try_write()
69            {
70                debugger.on_enter_operation(self, operation, self.position, context, registry);
71            }
72            let position = self.position;
73            let result = match operation {
74                ScriptOperation::None => {
75                    self.position += 1;
76                    true
77                }
78                ScriptOperation::Expression { expression } => {
79                    expression.evaluate(context, registry);
80                    self.position += 1;
81                    true
82                }
83                ScriptOperation::DefineRegister { query } => {
84                    let handle = registry
85                        .types()
86                        .find(|handle| query.is_valid(handle))
87                        .unwrap_or_else(|| {
88                            panic!("Could not define register for non-existent type: {query:#?}")
89                        });
90                    unsafe {
91                        context
92                            .registers()
93                            .push_register_raw(handle.type_hash(), *handle.layout())
94                    };
95                    self.position += 1;
96                    true
97                }
98                ScriptOperation::DropRegister { index } => {
99                    let index = context.absolute_register_index(*index);
100                    context
101                        .registers()
102                        .access_register(index)
103                        .unwrap_or_else(|| {
104                            panic!("Could not access non-existent register: {index}")
105                        })
106                        .free();
107                    self.position += 1;
108                    true
109                }
110                ScriptOperation::PushFromRegister { index } => {
111                    let index = context.absolute_register_index(*index);
112                    let (stack, registers) = context.stack_and_registers();
113                    let mut register = registers.access_register(index).unwrap_or_else(|| {
114                        panic!("Could not access non-existent register: {index}")
115                    });
116                    if !stack.push_from_register(&mut register) {
117                        panic!("Could not push data from register: {index}");
118                    }
119                    self.position += 1;
120                    true
121                }
122                ScriptOperation::PopToRegister { index } => {
123                    let index = context.absolute_register_index(*index);
124                    let (stack, registers) = context.stack_and_registers();
125                    let mut register = registers.access_register(index).unwrap_or_else(|| {
126                        panic!("Could not access non-existent register: {index}")
127                    });
128                    if !stack.pop_to_register(&mut register) {
129                        panic!("Could not pop data to register: {index}");
130                    }
131                    self.position += 1;
132                    true
133                }
134                ScriptOperation::MoveRegister { from, to } => {
135                    let from = context.absolute_register_index(*from);
136                    let to = context.absolute_register_index(*to);
137                    let (mut source, mut target) = context
138                        .registers()
139                        .access_registers_pair(from, to)
140                        .unwrap_or_else(|| {
141                            panic!("Could not access non-existent registers pair: {from} and {to}")
142                        });
143                    source.move_to(&mut target);
144                    self.position += 1;
145                    true
146                }
147                ScriptOperation::CallFunction { query } => {
148                    let handle = registry
149                        .functions()
150                        .find(|handle| query.is_valid(handle.signature()))
151                        .unwrap_or_else(|| {
152                            panic!("Could not call non-existent function: {query:#?}")
153                        });
154                    handle.invoke(context, registry);
155                    self.position += 1;
156                    true
157                }
158                ScriptOperation::BranchScope {
159                    scope_success,
160                    scope_failure,
161                } => {
162                    if context.stack().pop::<bool>().unwrap() {
163                        self.child = Some(Box::new(
164                            Self::new(scope_success.clone(), self.symbol)
165                                .with_debugger(self.debugger.clone()),
166                        ));
167                    } else if let Some(scope_failure) = scope_failure {
168                        self.child = Some(Box::new(
169                            Self::new(scope_failure.clone(), self.symbol)
170                                .with_debugger(self.debugger.clone()),
171                        ));
172                    }
173                    self.position += 1;
174                    true
175                }
176                ScriptOperation::LoopScope { scope } => {
177                    if !context.stack().pop::<bool>().unwrap() {
178                        self.position += 1;
179                    } else {
180                        self.child = Some(Box::new(
181                            Self::new(scope.clone(), self.symbol)
182                                .with_debugger(self.debugger.clone()),
183                        ));
184                    }
185                    true
186                }
187                ScriptOperation::PushScope { scope } => {
188                    context.store_registers();
189                    self.child = Some(Box::new(
190                        Self::new(scope.clone(), self.symbol).with_debugger(self.debugger.clone()),
191                    ));
192                    self.position += 1;
193                    true
194                }
195                ScriptOperation::PopScope => {
196                    context.restore_registers();
197                    self.position = self.handle.len();
198                    false
199                }
200                ScriptOperation::ContinueScopeConditionally => {
201                    let result = context.stack().pop::<bool>().unwrap();
202                    if result {
203                        self.position += 1;
204                    } else {
205                        self.position = self.handle.len();
206                    }
207                    result
208                }
209            };
210            if let Some(debugger) = self.debugger.as_ref()
211                && let Ok(mut debugger) = debugger.try_write()
212            {
213                debugger.on_exit_operation(self, operation, position, context, registry);
214            }
215            result
216        } else {
217            false
218        };
219        if (!result || self.position >= self.handle.len())
220            && let Some(debugger) = self.debugger.as_ref()
221            && let Ok(mut debugger) = debugger.try_write()
222        {
223            debugger.on_exit_scope(self, context, registry);
224        }
225        result
226    }
227}
228
229impl<SE: ScriptExpression + 'static> ScriptFunctionGenerator<SE> for VmScope<'static, SE> {
230    type Input = Option<VmDebuggerHandle<SE>>;
231    type Output = VmScopeSymbol;
232
233    fn generate_function_body(
234        script: ScriptHandle<'static, SE>,
235        debugger: Self::Input,
236    ) -> Option<(FunctionBody, Self::Output)> {
237        let symbol = VmScopeSymbol::new();
238        Some((
239            FunctionBody::closure(move |context, registry| {
240                Self::new(script.clone(), symbol)
241                    .with_debugger(debugger.clone())
242                    .run(context, registry);
243            }),
244            symbol,
245        ))
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use crate::scope::*;
252    use intuicio_core::{
253        Visibility,
254        function::{Function, FunctionParameter, FunctionQuery, FunctionSignature},
255        script::{ScriptBuilder, ScriptFunction, ScriptFunctionParameter, ScriptFunctionSignature},
256        types::{TypeQuery, struct_type::NativeStructBuilder},
257    };
258
259    #[test]
260    fn test_vm_scope() {
261        let i32_handle = NativeStructBuilder::new::<i32>()
262            .build()
263            .into_type()
264            .into_handle();
265        let mut registry = Registry::default().with_basic_types();
266        registry.add_function(Function::new(
267            FunctionSignature::new("add")
268                .with_input(FunctionParameter::new("a", i32_handle.clone()))
269                .with_input(FunctionParameter::new("b", i32_handle.clone()))
270                .with_output(FunctionParameter::new("result", i32_handle.clone())),
271            FunctionBody::closure(|context, _| {
272                let a = context.stack().pop::<i32>().unwrap();
273                let b = context.stack().pop::<i32>().unwrap();
274                context.stack().push(a + b);
275            }),
276        ));
277        registry.add_function(
278            VmScope::<()>::generate_function(
279                &ScriptFunction {
280                    signature: ScriptFunctionSignature {
281                        meta: None,
282                        name: "add_script".to_owned(),
283                        module_name: None,
284                        type_query: None,
285                        visibility: Visibility::Public,
286                        inputs: vec![
287                            ScriptFunctionParameter {
288                                meta: None,
289                                name: "a".to_owned(),
290                                type_query: TypeQuery::of::<i32>(),
291                            },
292                            ScriptFunctionParameter {
293                                meta: None,
294                                name: "b".to_owned(),
295                                type_query: TypeQuery::of::<i32>(),
296                            },
297                        ],
298                        outputs: vec![ScriptFunctionParameter {
299                            meta: None,
300                            name: "result".to_owned(),
301                            type_query: TypeQuery::of::<i32>(),
302                        }],
303                    },
304                    script: ScriptBuilder::<()>::default()
305                        .define_register(TypeQuery::of::<i32>())
306                        .pop_to_register(0)
307                        .push_from_register(0)
308                        .call_function(FunctionQuery {
309                            name: Some("add".into()),
310                            ..Default::default()
311                        })
312                        .build(),
313                },
314                &registry,
315                None,
316            )
317            .unwrap()
318            .0,
319        );
320        registry.add_type_handle(i32_handle);
321        let mut context = Context::new(10240, 10240);
322        let (result,) = registry
323            .find_function(FunctionQuery {
324                name: Some("add".into()),
325                ..Default::default()
326            })
327            .unwrap()
328            .call::<(i32,), _>(&mut context, &registry, (40, 2), true);
329        assert_eq!(result, 42);
330        assert_eq!(context.stack().position(), 0);
331        assert_eq!(context.registers().position(), 0);
332        let (result,) = registry
333            .find_function(FunctionQuery {
334                name: Some("add_script".into()),
335                ..Default::default()
336            })
337            .unwrap()
338            .call::<(i32,), _>(&mut context, &registry, (40, 2), true);
339        assert_eq!(result, 42);
340        assert_eq!(context.stack().position(), 0);
341        assert_eq!(context.registers().position(), 0);
342    }
343}