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