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