1use runmat_builtins::Value;
2use runmat_hir::{BindingName, DefPath, Span};
3use runmat_parser::CompatMode;
4use uuid::Uuid;
5
6use crate::execution::{ExecutionProfiling, ExecutionStreamEntry, StdinEvent};
7use crate::fusion::FusionPlanSnapshot;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum SourceInput {
11 Path(String),
12 Text { name: String, text: String },
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct HostExecutionPolicy {
17 pub top_level_await: bool,
18 pub dynamic_eval: bool,
19}
20
21impl Default for HostExecutionPolicy {
22 fn default() -> Self {
23 Self {
24 top_level_await: true,
25 dynamic_eval: true,
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub struct WorkspaceHandle(pub Uuid);
32
33#[derive(Debug, Clone)]
34pub struct ExecutionRequest {
35 pub source: SourceInput,
36 pub compatibility: CompatMode,
37 pub host_policy: HostExecutionPolicy,
38 pub requested_outputs: runmat_hir::RequestedOutputCount,
39 pub workspace: WorkspaceHandle,
40}
41
42impl ExecutionRequest {
43 pub fn for_source(
44 source: SourceInput,
45 compatibility: CompatMode,
46 host_policy: HostExecutionPolicy,
47 workspace: WorkspaceHandle,
48 ) -> Self {
49 Self {
50 source,
51 compatibility,
52 host_policy,
53 requested_outputs: runmat_hir::RequestedOutputCount::One,
54 workspace,
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
60pub struct ExecutionSourceContext {
61 pub name: String,
62 pub text: Option<String>,
63 pub identity: Option<SourceIdentity>,
64}
65
66impl ExecutionSourceContext {
67 pub fn source_name(&self) -> &str {
68 &self.name
69 }
70
71 pub fn source_text(&self) -> Option<&str> {
72 self.text.as_deref()
73 }
74}
75
76#[derive(Debug)]
77pub struct ExecutionResponse {
78 pub source_context: ExecutionSourceContext,
79 pub result: std::result::Result<ExecutionOutcome, crate::RunError>,
80}
81
82#[derive(Debug, Clone)]
83pub struct ExecutionOutcome {
84 pub flow: RuntimeFlow,
85 pub workspace_delta: WorkspaceDelta,
86 pub display_events: Vec<DisplayEvent>,
87 pub streams: Vec<ExecutionStreamEntry>,
88 pub diagnostics: Vec<RuntimeDiagnostic>,
89 pub effects: Vec<ObservedEffect>,
90 pub suspension: Option<Suspension>,
91 pub profiling: Option<ExecutionProfiling>,
92 pub execution_time_ms: u64,
93 pub used_jit: bool,
94 pub type_info: Option<String>,
95 pub figures_touched: Vec<u32>,
96 pub stdin_events: Vec<StdinEvent>,
97 pub fusion_plan: Option<FusionPlanSnapshot>,
98}
99
100impl Default for ExecutionOutcome {
101 fn default() -> Self {
102 Self {
103 flow: RuntimeFlow::NoValue,
104 workspace_delta: WorkspaceDelta::default(),
105 display_events: Vec::new(),
106 streams: Vec::new(),
107 diagnostics: Vec::new(),
108 effects: Vec::new(),
109 suspension: None,
110 profiling: None,
111 execution_time_ms: 0,
112 used_jit: false,
113 type_info: None,
114 figures_touched: Vec::new(),
115 stdin_events: Vec::new(),
116 fusion_plan: None,
117 }
118 }
119}
120
121#[derive(Debug, Clone)]
122pub enum RuntimeFlow {
123 NoValue,
124 Single(Value),
125 OutputList(Vec<Value>),
126 CommaList(Vec<Value>),
127 DynamicList(DynamicListHandle),
128}
129
130impl RuntimeFlow {
131 pub fn is_no_value(&self) -> bool {
132 matches!(self, Self::NoValue)
133 }
134
135 pub fn durable_workspace_value(&self) -> Option<&Value> {
136 match self {
137 Self::Single(value) => Some(value),
138 Self::NoValue | Self::OutputList(_) | Self::CommaList(_) | Self::DynamicList(_) => None,
139 }
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
144pub struct DynamicListHandle(pub Uuid);
145
146#[derive(Debug, Clone, PartialEq, Eq, Hash)]
147pub enum SourceIdentity {
148 Interactive { session: Uuid },
149 PathAndContentHash { path: String, hash: String },
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Hash)]
153pub enum WorkspaceBindingKey {
154 Interactive {
155 session: Uuid,
156 name: BindingName,
157 },
158 SourceBinding {
159 source: SourceIdentity,
160 def_path: DefPath,
161 binding: BindingName,
162 },
163 Global {
164 scope: GlobalScopeKey,
165 name: BindingName,
166 },
167 Persistent {
168 function: DefPath,
169 name: BindingName,
170 },
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub enum GlobalScopeKey {
175 Session(Uuid),
176 Package(String),
177}
178
179#[derive(Debug, Clone)]
180pub struct WorkspaceBindingValue {
181 pub key: WorkspaceBindingKey,
182 pub value: Value,
183}
184
185#[derive(Debug, Clone, Default)]
186pub struct WorkspaceDelta {
187 pub version: u64,
188 pub upserts: Vec<WorkspaceBindingValue>,
189 pub removals: Vec<WorkspaceBindingKey>,
190 pub full_snapshot_required: bool,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum InitFact {
195 Unassigned,
196 MaybeAssigned,
197 DefinitelyAssigned,
198}
199
200#[derive(Debug, Clone)]
201pub struct DisplayEvent {
202 pub label: DisplayLabel,
203 pub value: Value,
204 pub span: Span,
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub enum DisplayLabel {
209 Binding(BindingName),
210 Literal(String),
211 Anonymous,
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
215pub struct RuntimeDiagnostic {
216 pub code: String,
217 pub severity: DiagnosticSeverity,
218 pub message: String,
219 pub span: Option<Span>,
220 pub callstack: Vec<String>,
221 pub callstack_elided: usize,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum DiagnosticSeverity {
226 Error,
227 Warning,
228 Info,
229 Hint,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
233pub enum ObservedEffect {
234 Workspace(WorkspaceEffectKind),
235 Environment(EnvironmentEffectKind),
236}
237
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub enum WorkspaceEffectKind {
240 Load,
241 Clear,
242 AssignIn,
243 EvalIn,
244 DeclareGlobal,
245 DeclarePersistent,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq)]
249pub enum EnvironmentEffectKind {
250 ChangeDirectory,
251 MutatePath,
252 ClearFunctionCache,
253 ClearClassCache,
254 InvalidateDynamicLookup,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct Suspension {
259 pub task: Uuid,
260 pub frame: Uuid,
261 pub resume_point: ResumePoint,
262 pub pending: PendingOperation,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
266pub enum ResumePoint {
267 BytecodePc(usize),
268 Host(String),
269}
270
271#[derive(Debug, Clone, PartialEq, Eq)]
272pub enum PendingOperation {
273 HostInteraction,
274 Provider,
275 Filesystem,
276 Timer,
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn runtime_flow_distinguishes_durable_values_from_lists() {
285 let value = Value::Num(1.0);
286 assert!(RuntimeFlow::NoValue.is_no_value());
287 assert!(RuntimeFlow::Single(value)
288 .durable_workspace_value()
289 .is_some());
290 assert!(RuntimeFlow::OutputList(vec![Value::Num(1.0)])
291 .durable_workspace_value()
292 .is_none());
293 assert!(RuntimeFlow::CommaList(vec![Value::Num(1.0)])
294 .durable_workspace_value()
295 .is_none());
296 }
297
298 #[test]
299 fn execution_outcome_defaults_to_empty_no_value_contract() {
300 let outcome = ExecutionOutcome::default();
301 assert!(outcome.flow.is_no_value());
302 assert!(outcome.workspace_delta.upserts.is_empty());
303 assert!(outcome.workspace_delta.removals.is_empty());
304 assert!(!outcome.workspace_delta.full_snapshot_required);
305 assert!(outcome.display_events.is_empty());
306 assert!(outcome.streams.is_empty());
307 assert!(outcome.diagnostics.is_empty());
308 assert!(outcome.effects.is_empty());
309 assert!(outcome.suspension.is_none());
310 assert_eq!(outcome.execution_time_ms, 0);
311 assert!(!outcome.used_jit);
312 assert!(outcome.type_info.is_none());
313 assert!(outcome.figures_touched.is_empty());
314 assert!(outcome.stdin_events.is_empty());
315 assert!(outcome.fusion_plan.is_none());
316 }
317
318 #[test]
319 fn workspace_keys_are_stable_boundary_identities() {
320 let session = Uuid::from_u128(1);
321 let key = WorkspaceBindingKey::Interactive {
322 session,
323 name: BindingName("adjusted".to_string()),
324 };
325 assert_eq!(
326 key,
327 WorkspaceBindingKey::Interactive {
328 session,
329 name: BindingName("adjusted".to_string())
330 }
331 );
332 }
333}