1use crate::{HostRequirementsRef, LashlangExecutionCallSite, ModuleRef, ProcessRef};
2
3use super::{ExecutionScratch, ProfileReport, ProjectedBindings, Record, RuntimeFailure, Value};
4use crate::LashlangExecutionObservation;
5use std::future::Future;
6use std::sync::Mutex;
7use thiserror::Error;
8
9#[derive(Clone, Debug)]
10pub enum AbilityOp {
11 ResourceOperation(ResourceOperation),
12 ResourceOperationBatch(ResourceOperationBatch),
13 Await(Value),
14 Cancel(Value),
15 Print(Value),
16 Finish(Value),
17 Fail(Value),
18 StartProcess(Box<ProcessStart>),
19 ProcessEvent(ProcessEvent),
20 Sleep(Sleep),
21 WaitSignal { name: String },
22 SignalRun(ProcessSignal),
23}
24
25#[derive(Clone, Debug)]
26pub enum AbilityResult {
27 Value(Value),
28 ResourceOperationBatch(ResourceOperationBatchResult),
29 Unit,
30}
31
32impl AbilityResult {
33 pub fn into_value(self, op: &'static str) -> Result<Value, ExecutionHostError> {
34 match self {
35 Self::Value(value) => Ok(value),
36 Self::ResourceOperationBatch(_) => Err(ExecutionHostError::new(format!(
37 "{op} returned a resource operation batch result"
38 ))),
39 Self::Unit => Err(ExecutionHostError::new(format!("{op} returned no value"))),
40 }
41 }
42}
43
44#[derive(Clone, Debug)]
45pub struct ProcessStart {
46 pub module_ref: ModuleRef,
47 pub process_ref: ProcessRef,
48 pub host_requirements_ref: HostRequirementsRef,
49 pub start_site: LashlangExecutionCallSite,
50 pub process_name: String,
51 pub args: Record,
52}
53
54#[derive(Clone, Debug)]
55pub struct ResourceOperation {
56 pub receiver: Value,
57 pub operation: String,
58 pub args: Vec<Value>,
59 pub call_site: Option<crate::LashlangExecutionCallSite>,
60}
61
62#[derive(Clone, Debug)]
63pub struct ResourceOperationBatch {
64 pub operations: Vec<ResourceOperation>,
65}
66
67#[derive(Clone, Debug)]
68pub struct ResourceOperationBatchResult {
69 pub results: Vec<ResourceOperationResult>,
70}
71
72#[derive(Clone, Debug)]
73pub enum ResourceOperationResult {
74 Value(Value),
75 Error(ExecutionHostError),
76}
77
78impl ResourceOperationResult {
79 pub fn from_result(result: Result<Value, ExecutionHostError>) -> Self {
80 match result {
81 Ok(value) => Self::Value(value),
82 Err(error) => Self::Error(error),
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum ProcessEventKind {
89 Yield,
90 Wake,
91}
92
93#[derive(Clone, Debug)]
94pub struct ProcessEvent {
95 pub kind: ProcessEventKind,
96 pub value: Value,
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100pub enum SleepKind {
101 For,
102 Until,
103}
104
105#[derive(Clone, Debug)]
106pub struct Sleep {
107 pub kind: SleepKind,
108 pub value: Value,
109}
110
111#[derive(Clone, Debug)]
112pub struct ProcessSignal {
113 pub run: Value,
114 pub name: String,
115 pub payload: Value,
116}
117
118#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
119pub enum ExecutionMode {
120 #[default]
121 Foreground,
122 Process,
123}
124
125pub trait ExecutionHost: Sync {
126 fn perform(
127 &self,
128 op: AbilityOp,
129 ) -> impl Future<Output = Result<AbilityResult, ExecutionHostError>> + Send;
130
131 fn yield_now(&self) -> impl Future<Output = ()> + Send {
132 async {}
133 }
134
135 fn execution_mode(&self) -> ExecutionMode {
136 ExecutionMode::Foreground
137 }
138
139 fn projected_bindings(&self) -> ProjectedBindings {
140 ProjectedBindings::default()
141 }
142
143 fn trace_runtime_errors(&self) -> bool {
144 false
145 }
146
147 fn profile_execution(&self) -> bool {
148 false
149 }
150
151 fn take_scratch(&self) -> Option<ExecutionScratch> {
152 None
153 }
154
155 fn store_scratch(&self, _scratch: ExecutionScratch) {}
156
157 fn observe_runtime_failure(&self, _failure: RuntimeFailure) {}
158
159 fn observe_profile(&self, _profile: ProfileReport) {}
160
161 fn observe_lashlang_execution(&self, _observation: LashlangExecutionObservation) {}
162}
163
164pub struct ExecutionEnvironment<'host, H: ExecutionHost> {
165 host: &'host H,
166 mode: ExecutionMode,
167 projected: ProjectedBindings,
168 scratch: Mutex<Option<ExecutionScratch>>,
169 trace_runtime_errors: bool,
170 profile_execution: bool,
171 runtime_failure: Mutex<Option<RuntimeFailure>>,
172 profile: Mutex<Option<ProfileReport>>,
173}
174
175impl<'host, H: ExecutionHost> ExecutionEnvironment<'host, H> {
176 pub fn new(host: &'host H) -> Self {
177 Self {
178 host,
179 mode: host.execution_mode(),
180 projected: host.projected_bindings(),
181 scratch: Mutex::new(host.take_scratch()),
182 trace_runtime_errors: host.trace_runtime_errors(),
183 profile_execution: host.profile_execution(),
184 runtime_failure: Mutex::new(None),
185 profile: Mutex::new(None),
186 }
187 }
188
189 pub fn with_mode(mut self, mode: ExecutionMode) -> Self {
190 self.mode = mode;
191 self
192 }
193
194 pub fn process(self) -> Self {
195 self.with_mode(ExecutionMode::Process)
196 }
197
198 pub fn foreground(self) -> Self {
199 self.with_mode(ExecutionMode::Foreground)
200 }
201
202 pub fn with_projected_bindings(mut self, projected: ProjectedBindings) -> Self {
203 self.projected = projected;
204 self
205 }
206
207 pub fn with_scratch(mut self, scratch: ExecutionScratch) -> Self {
208 self.scratch = Mutex::new(Some(scratch));
209 self
210 }
211
212 pub fn traced(mut self) -> Self {
213 self.trace_runtime_errors = true;
214 self
215 }
216
217 pub fn profiled(mut self) -> Self {
218 self.profile_execution = true;
219 self
220 }
221
222 pub fn take_runtime_failure(&self) -> Option<RuntimeFailure> {
223 self.runtime_failure.lock().ok()?.take()
224 }
225
226 pub fn take_profile(&self) -> Option<ProfileReport> {
227 self.profile.lock().ok()?.take()
228 }
229
230 pub fn take_recycled_scratch(&self) -> Option<ExecutionScratch> {
231 self.scratch.lock().ok()?.take()
232 }
233}
234
235impl<H: ExecutionHost> ExecutionHost for ExecutionEnvironment<'_, H> {
236 async fn perform(&self, op: AbilityOp) -> Result<AbilityResult, ExecutionHostError> {
237 self.host.perform(op).await
238 }
239
240 async fn yield_now(&self) {
241 self.host.yield_now().await;
242 }
243
244 fn execution_mode(&self) -> ExecutionMode {
245 self.mode
246 }
247
248 fn projected_bindings(&self) -> ProjectedBindings {
249 self.projected.clone()
250 }
251
252 fn trace_runtime_errors(&self) -> bool {
253 self.trace_runtime_errors
254 }
255
256 fn profile_execution(&self) -> bool {
257 self.profile_execution
258 }
259
260 fn take_scratch(&self) -> Option<ExecutionScratch> {
261 self.scratch.lock().ok()?.take()
262 }
263
264 fn store_scratch(&self, scratch: ExecutionScratch) {
265 if let Ok(mut guard) = self.scratch.lock() {
266 *guard = Some(scratch);
267 }
268 }
269
270 fn observe_runtime_failure(&self, failure: RuntimeFailure) {
271 self.host.observe_runtime_failure(failure.clone());
272 if let Ok(mut guard) = self.runtime_failure.lock() {
273 *guard = Some(failure);
274 }
275 }
276
277 fn observe_profile(&self, profile: ProfileReport) {
278 self.host.observe_profile(profile.clone());
279 if let Ok(mut guard) = self.profile.lock() {
280 *guard = Some(profile);
281 }
282 }
283
284 fn observe_lashlang_execution(&self, observation: LashlangExecutionObservation) {
285 self.host.observe_lashlang_execution(observation);
286 }
287}
288
289#[derive(Clone, Debug, Error, PartialEq, Eq)]
290#[error("{message}")]
291pub struct ExecutionHostError {
292 message: String,
293}
294
295impl ExecutionHostError {
296 pub fn new(message: impl Into<String>) -> Self {
297 Self {
298 message: message.into(),
299 }
300 }
301}