Skip to main content

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