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