datex_core/runtime/
execution_context.rs

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