datex_core/runtime/execution/context/
mod.rs

1#[cfg(feature = "compiler")]
2use crate::compiler::{
3    CompileOptions, compile_template, error::SpannedCompilerError,
4    scope::CompilationScope,
5};
6use crate::runtime::execution::{
7    ExecutionError, ExecutionInput, MemoryDump, execute_dxb, execute_dxb_sync,
8};
9use crate::stdlib::format;
10use crate::stdlib::vec::Vec;
11use crate::values::value_container::ValueContainer;
12pub use local::*;
13use log::info;
14pub use remote::*;
15pub use script::*;
16
17mod local;
18mod remote;
19mod script;
20
21/// An execution context holds the persistent state for executing multiple scripts sequentially within the same context.
22/// This can be either a local context, which is used for executing scripts in the same process, or a remote context,
23/// which is used for executing scripts on a remote endpoint.
24#[derive(Debug)]
25pub enum ExecutionContext {
26    Local(LocalExecutionContext),
27    Remote(RemoteExecutionContext),
28}
29
30impl ExecutionContext {
31    #[cfg(feature = "compiler")]
32    fn compile_scope(&self) -> &CompilationScope {
33        match self {
34            ExecutionContext::Local(LocalExecutionContext {
35                compile_scope,
36                ..
37            }) => compile_scope,
38            ExecutionContext::Remote(RemoteExecutionContext {
39                compile_scope,
40                ..
41            }) => compile_scope,
42        }
43    }
44
45    #[cfg(feature = "compiler")]
46    fn set_compile_scope(&mut self, new_compile_scope: CompilationScope) {
47        match self {
48            ExecutionContext::Local(LocalExecutionContext {
49                compile_scope,
50                ..
51            }) => *compile_scope = new_compile_scope,
52            ExecutionContext::Remote(RemoteExecutionContext {
53                compile_scope,
54                ..
55            }) => *compile_scope = new_compile_scope,
56        }
57    }
58
59    /// Compiles a script using the compile scope of the execution context
60    #[cfg(feature = "compiler")]
61    pub fn compile(
62        &mut self,
63        script: &str,
64        inserted_values: &[ValueContainer],
65    ) -> Result<Vec<u8>, SpannedCompilerError> {
66        let compile_scope = self.compile_scope();
67        // TODO #107: don't clone compile_scope if possible
68        let res = compile_template(
69            script,
70            inserted_values,
71            CompileOptions::new_with_scope(compile_scope.clone()),
72        );
73        match res {
74            Ok((bytes, compile_scope)) => {
75                self.set_compile_scope(compile_scope);
76                Ok(bytes)
77            }
78            Err(err) => Err(err),
79        }
80    }
81
82    fn print_dxb_debug(&self, dxb: &[u8]) -> Result<(), ExecutionError> {
83        info!(
84            "\x1b[32m[Compiled Bytecode] {}",
85            dxb.iter()
86                .map(|b| format!("{b:02x}"))
87                .collect::<Vec<_>>()
88                .join(", ")
89        );
90
91        #[cfg(feature = "compiler")]
92        {
93            let decompiled = crate::decompiler::decompile_body(
94                dxb,
95                crate::decompiler::DecompileOptions::colorized(),
96            );
97            if let Err(e) = decompiled {
98                info!("\x1b[31m[Decompiler Error] {e}\x1b[0m");
99            } else {
100                let decompiled = decompiled?;
101                info!("[Decompiled]: {decompiled}");
102            }
103        }
104
105        Ok(())
106    }
107
108    fn get_local_execution_input<'a>(
109        &'a mut self,
110        dxb: &'a [u8],
111    ) -> Result<ExecutionInput<'a>, ExecutionError> {
112        match self {
113            ExecutionContext::Remote(_) => {
114                core::panic!("Remote execution requires a Runtime");
115            }
116            ExecutionContext::Local(LocalExecutionContext {
117                runtime,
118                loop_state,
119                execution_options,
120                verbose,
121                ..
122            }) => {
123                let input = ExecutionInput {
124                    runtime: runtime.clone(),
125                    loop_state: loop_state.take(),
126                    options: (*execution_options).clone(),
127                    dxb_body: dxb,
128                };
129
130                // show DXB and decompiled code if verbose is enabled
131                if *verbose {
132                    self.print_dxb_debug(dxb)?;
133                }
134
135                Ok(input)
136            }
137        }
138    }
139
140    /// Executes DXB in a local execution context.
141    pub fn execute_dxb_sync(
142        &mut self,
143        dxb: &[u8],
144    ) -> Result<Option<ValueContainer>, ExecutionError> {
145        let execution_input = self.get_local_execution_input(dxb)?;
146        let res = execute_dxb_sync(execution_input);
147        self.intercept_intermediate_result(res)
148    }
149
150    /// Executes a script in a local execution context.
151    #[cfg(feature = "compiler")]
152    pub fn execute_sync(
153        &mut self,
154        script: &str,
155        inserted_values: &[ValueContainer],
156    ) -> Result<Option<ValueContainer>, ScriptExecutionError> {
157        let dxb = self.compile(script, inserted_values)?;
158        self.execute_dxb_sync(&dxb)
159            .map_err(ScriptExecutionError::from)
160    }
161
162    pub async fn execute_dxb(
163        &mut self,
164        dxb: &[u8],
165    ) -> Result<Option<ValueContainer>, ExecutionError> {
166        match self {
167            ExecutionContext::Local(..) => {
168                let res = {
169                    let execution_input =
170                        self.get_local_execution_input(dxb)?;
171                    execute_dxb(execution_input).await
172                };
173                self.intercept_intermediate_result(res)
174            }
175            // remote execution is not supported directly in execution context
176            ExecutionContext::Remote(..) => {
177                core::panic!("Remote execution requires a Runtime");
178            }
179        }
180    }
181
182    /// Intercepts an intermediate execution result,
183    /// storing the loop state if present in the execution context and returning the intermediate result as an Ok value.
184    /// Note: this function assumes that self is a Local execution context
185    fn intercept_intermediate_result(
186        &mut self,
187        execution_result: Result<Option<ValueContainer>, ExecutionError>,
188    ) -> Result<Option<ValueContainer>, ExecutionError> {
189        match execution_result {
190            Err(ExecutionError::IntermediateResultWithState(
191                intermediate_result,
192                Some(state),
193            )) => {
194                match self {
195                    ExecutionContext::Local(LocalExecutionContext {
196                        loop_state,
197                        ..
198                    }) => {
199                        loop_state.replace(state);
200                        Ok(intermediate_result)
201                    }
202                    _ => unreachable!(), // note: this must be ensured by the caller
203                }
204            }
205            _ => execution_result,
206        }
207    }
208
209    #[cfg(feature = "compiler")]
210    pub async fn execute(
211        &mut self,
212        script: &str,
213        inserted_values: &[ValueContainer],
214    ) -> Result<Option<ValueContainer>, ScriptExecutionError> {
215        let dxb = self.compile(script, inserted_values)?;
216        self.execute_dxb(&dxb)
217            .await
218            .map_err(ScriptExecutionError::from)
219    }
220
221    /// Returns a memory dump of the current state of the execution context if available.
222    pub fn memory_dump(&self) -> Option<MemoryDump> {
223        match self {
224            ExecutionContext::Local(local_context) => {
225                todo!("#650 Undescribed by author.")
226                // Some(local_context.memory_dump())
227            }
228            // TODO #397: also support remote memory dump if possible
229            ExecutionContext::Remote(_) => None,
230        }
231    }
232}