datex_core/runtime/
execution_context.rs

1use crate::compiler::error::CompilerError;
2use crate::compiler::scope::CompilationScope;
3use crate::compiler::{CompileOptions, compile_template};
4use crate::decompiler::{DecompileOptions, decompile_body};
5use crate::global::dxb_block::OutgoingContextId;
6use crate::runtime::RuntimeInternal;
7use crate::runtime::execution::{
8    ExecutionError, ExecutionInput, ExecutionOptions, MemoryDump,
9    RuntimeExecutionContext, execute_dxb, execute_dxb_sync,
10};
11use crate::values::core_values::endpoint::Endpoint;
12use crate::values::value_container::ValueContainer;
13use std::cell::RefCell;
14use std::fmt::Display;
15use std::rc::Rc;
16
17#[derive(Debug)]
18pub enum ScriptExecutionError {
19    CompilerError(CompilerError),
20    ExecutionError(ExecutionError),
21}
22
23impl From<CompilerError> for ScriptExecutionError {
24    fn from(err: CompilerError) -> Self {
25        ScriptExecutionError::CompilerError(err)
26    }
27}
28
29impl From<ExecutionError> for ScriptExecutionError {
30    fn from(err: ExecutionError) -> Self {
31        ScriptExecutionError::ExecutionError(err)
32    }
33}
34
35impl Display for ScriptExecutionError {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            ScriptExecutionError::CompilerError(err) => {
39                write!(f, "Compiler Error: {}", err)
40            }
41            ScriptExecutionError::ExecutionError(err) => {
42                write!(f, "Execution Error: {}", err)
43            }
44        }
45    }
46}
47
48#[derive(Debug, Clone, Default)]
49pub struct RemoteExecutionContext {
50    pub compile_scope: CompilationScope,
51    pub endpoint: Endpoint,
52    pub context_id: Option<OutgoingContextId>,
53}
54
55impl RemoteExecutionContext {
56    /// Creates a new remote execution context with the given endpoint.
57    pub fn new(endpoint: impl Into<Endpoint>, once: bool) -> Self {
58        RemoteExecutionContext {
59            compile_scope: CompilationScope::new(once),
60            endpoint: endpoint.into(),
61            context_id: None,
62        }
63    }
64}
65
66#[derive(Debug, Clone, Default)]
67pub struct LocalExecutionContext {
68    compile_scope: CompilationScope,
69    runtime_execution_context: Rc<RefCell<RuntimeExecutionContext>>,
70    execution_options: ExecutionOptions,
71    verbose: bool,
72}
73
74impl LocalExecutionContext {
75    pub fn new(once: bool) -> Self {
76        LocalExecutionContext {
77            compile_scope: CompilationScope::new(once),
78            runtime_execution_context: Rc::new(RefCell::new(
79                RuntimeExecutionContext::default(),
80            )),
81            execution_options: ExecutionOptions::default(),
82            verbose: false,
83        }
84    }
85
86    /// Creates a new local execution context with the given compile scope.
87    pub fn debug(once: bool) -> Self {
88        LocalExecutionContext {
89            compile_scope: CompilationScope::new(once),
90            execution_options: ExecutionOptions { verbose: true },
91            verbose: true,
92            ..Default::default()
93        }
94    }
95
96    pub fn debug_with_runtime_internal(
97        runtime_internal: Rc<RuntimeInternal>,
98        once: bool,
99    ) -> Self {
100        LocalExecutionContext {
101            compile_scope: CompilationScope::new(once),
102            runtime_execution_context: Rc::new(RefCell::new(
103                RuntimeExecutionContext::new(runtime_internal),
104            )),
105            execution_options: ExecutionOptions { verbose: true },
106            verbose: true,
107        }
108    }
109
110    pub fn new_with_runtime_internal(
111        runtime_internal: Rc<RuntimeInternal>,
112        once: bool,
113    ) -> Self {
114        LocalExecutionContext {
115            compile_scope: CompilationScope::new(once),
116            runtime_execution_context: Rc::new(RefCell::new(
117                RuntimeExecutionContext::new(runtime_internal),
118            )),
119            ..Default::default()
120        }
121    }
122
123    pub fn set_runtime_internal(
124        &mut self,
125        runtime_internal: Rc<RuntimeInternal>,
126    ) {
127        self.runtime_execution_context
128            .borrow_mut()
129            .set_runtime_internal(runtime_internal);
130    }
131
132    /// Returns a memory dump of the current state of the execution context.
133    pub fn memory_dump(&self) -> MemoryDump {
134        self.runtime_execution_context.borrow().memory_dump()
135    }
136}
137
138/// An execution context holds the persistent state for executing multiple scripts sequentially within the same context.
139/// This can be either a local context, which is used for executing scripts in the same process, or a remote context,
140/// which is used for executing scripts on a remote endpoint.
141#[derive(Debug, Clone)]
142pub enum ExecutionContext {
143    Local(LocalExecutionContext),
144    Remote(RemoteExecutionContext),
145}
146
147impl ExecutionContext {
148    /// Creates a new local execution context (can only be used once).
149    pub fn local_once() -> Self {
150        ExecutionContext::Local(LocalExecutionContext::new(true))
151    }
152
153    /// Creates a new local execution context (can be used multiple times).
154    pub fn local() -> Self {
155        ExecutionContext::Local(LocalExecutionContext::new(false))
156    }
157
158    /// Creates a new local execution context with a runtime.
159    pub fn local_with_runtime_internal(
160        runtime_internal: Rc<RuntimeInternal>,
161        once: bool,
162    ) -> Self {
163        ExecutionContext::Local(
164            LocalExecutionContext::new_with_runtime_internal(
165                runtime_internal,
166                once,
167            ),
168        )
169    }
170
171    /// Creates a new local execution context with verbose mode enabled,
172    /// providing more log outputs for debugging purposes.
173    pub fn local_debug(once: bool) -> Self {
174        ExecutionContext::Local(LocalExecutionContext::debug(once))
175    }
176
177    /// Creates a new local execution context with verbose mode enabled and a runtime.
178    pub fn local_debug_with_runtime_internal(
179        runtime_internal: Rc<RuntimeInternal>,
180        once: bool,
181    ) -> Self {
182        ExecutionContext::Local(
183            LocalExecutionContext::debug_with_runtime_internal(
184                runtime_internal,
185                once,
186            ),
187        )
188    }
189
190    pub fn remote_once(endpoint: impl Into<Endpoint>) -> Self {
191        ExecutionContext::Remote(RemoteExecutionContext::new(endpoint, true))
192    }
193
194    pub fn remote(endpoint: impl Into<Endpoint>) -> Self {
195        ExecutionContext::Remote(RemoteExecutionContext::new(endpoint, false))
196    }
197
198    fn compile_scope(&self) -> &CompilationScope {
199        match self {
200            ExecutionContext::Local(LocalExecutionContext {
201                compile_scope,
202                ..
203            }) => compile_scope,
204            ExecutionContext::Remote(RemoteExecutionContext {
205                compile_scope,
206                ..
207            }) => compile_scope,
208        }
209    }
210
211    fn set_compile_scope(&mut self, new_compile_scope: CompilationScope) {
212        match self {
213            ExecutionContext::Local(LocalExecutionContext {
214                compile_scope,
215                ..
216            }) => *compile_scope = new_compile_scope,
217            ExecutionContext::Remote(RemoteExecutionContext {
218                compile_scope,
219                ..
220            }) => *compile_scope = new_compile_scope,
221        }
222    }
223
224    /// Compiles a script using the compile scope of the execution context
225    pub fn compile(
226        &mut self,
227        script: &str,
228        inserted_values: &[ValueContainer],
229    ) -> Result<Vec<u8>, CompilerError> {
230        let compile_scope = self.compile_scope();
231        // TODO #107: don't clone compile_scope if possible
232        let res = compile_template(
233            script,
234            inserted_values,
235            CompileOptions::new_with_scope(compile_scope.clone()),
236        );
237        match res {
238            Ok((bytes, compile_scope)) => {
239                self.set_compile_scope(compile_scope);
240                Ok(bytes)
241            }
242            Err(err) => Err(err),
243        }
244    }
245
246    fn print_dxb_debug(&self, dxb: &[u8]) -> Result<(), ExecutionError> {
247        println!(
248            "\x1b[32m[Compiled Bytecode] {}",
249            dxb.iter()
250                .map(|b| format!("{b:02x}"))
251                .collect::<Vec<_>>()
252                .join(", ")
253        );
254
255        let decompiled = decompile_body(dxb, DecompileOptions::colorized());
256        if let Err(e) = decompiled {
257            println!("\x1b[31m[Decompiler Error] {e}\x1b[0m");
258        } else {
259            let decompiled = decompiled?;
260            println!("[Decompiled]: {decompiled}");
261        }
262
263        Ok(())
264    }
265
266    fn get_local_execution_input<'a>(
267        &'a mut self,
268        dxb: &'a [u8],
269        end_execution: bool,
270    ) -> Result<ExecutionInput<'a>, ExecutionError> {
271        let (local_execution_context, execution_options, verbose) = match &self
272        {
273            ExecutionContext::Local(LocalExecutionContext {
274                runtime_execution_context: local_execution_context,
275                execution_options,
276                verbose,
277                ..
278            }) => (local_execution_context, execution_options, *verbose),
279            // remote execution is not supported directly in execution context
280            ExecutionContext::Remote(_) => {
281                panic!("Remote execution requires a Runtime");
282            }
283        };
284
285        // show DXB and decompiled code if verbose is enabled
286        if verbose {
287            self.print_dxb_debug(dxb)?;
288        }
289
290        local_execution_context.borrow_mut().reset_index();
291        Ok(ExecutionInput {
292            // FIXME #108: no clone here
293            context: (*local_execution_context).clone(),
294            options: (*execution_options).clone(),
295            dxb_body: dxb,
296            end_execution,
297        })
298    }
299
300    /// Executes DXB in a local execution context.
301    pub fn execute_dxb_sync(
302        &mut self,
303        dxb: &[u8],
304        end_execution: bool,
305    ) -> Result<Option<ValueContainer>, ExecutionError> {
306        let execution_input =
307            self.get_local_execution_input(dxb, end_execution)?;
308        execute_dxb_sync(execution_input)
309    }
310
311    /// Executes a script in a local execution context.
312    pub fn execute_sync(
313        &mut self,
314        script: &str,
315        inserted_values: &[ValueContainer],
316    ) -> Result<Option<ValueContainer>, ScriptExecutionError> {
317        let dxb = self.compile(script, inserted_values)?;
318        self.execute_dxb_sync(&dxb, true)
319            .map_err(ScriptExecutionError::from)
320    }
321
322    pub async fn execute_dxb(
323        &mut self,
324        dxb: &[u8],
325        end_execution: bool,
326    ) -> Result<Option<ValueContainer>, ExecutionError> {
327        match self {
328            ExecutionContext::Local { .. } => {
329                let execution_input =
330                    self.get_local_execution_input(dxb, end_execution)?;
331                execute_dxb(execution_input).await
332            }
333            // remote execution is not supported directly in execution context
334            ExecutionContext::Remote { .. } => {
335                panic!("Remote execution requires a Runtime");
336            }
337        }
338    }
339
340    pub async fn execute(
341        &mut self,
342        script: &str,
343        inserted_values: &[ValueContainer],
344    ) -> Result<Option<ValueContainer>, ScriptExecutionError> {
345        let dxb = self.compile(script, inserted_values)?;
346        self.execute_dxb(&dxb, true)
347            .await
348            .map_err(ScriptExecutionError::from)
349    }
350
351    /// Returns a memory dump of the current state of the execution context if available.
352    pub fn memory_dump(&self) -> Option<MemoryDump> {
353        match self {
354            ExecutionContext::Local(local_context) => {
355                Some(local_context.memory_dump())
356            }
357            // TODO #397: also support remote memory dump if possible
358            ExecutionContext::Remote(_) => None,
359        }
360    }
361}