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 ®istry,
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, ®istry, (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, ®istry, (40, 2), true);
339 assert_eq!(result, 42);
340 assert_eq!(context.stack().position(), 0);
341 assert_eq!(context.registers().position(), 0);
342 }
343}