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 ExecutionOutcome {
61 pub flow: RuntimeFlow,
62 pub workspace_delta: WorkspaceDelta,
63 pub display_events: Vec<DisplayEvent>,
64 pub streams: Vec<ExecutionStreamEntry>,
65 pub diagnostics: Vec<RuntimeDiagnostic>,
66 pub effects: Vec<ObservedEffect>,
67 pub suspension: Option<Suspension>,
68 pub profiling: Option<ExecutionProfiling>,
69 pub execution_time_ms: u64,
70 pub used_jit: bool,
71 pub type_info: Option<String>,
72 pub figures_touched: Vec<u32>,
73 pub stdin_events: Vec<StdinEvent>,
74 pub fusion_plan: Option<FusionPlanSnapshot>,
75}
76
77impl Default for ExecutionOutcome {
78 fn default() -> Self {
79 Self {
80 flow: RuntimeFlow::NoValue,
81 workspace_delta: WorkspaceDelta::default(),
82 display_events: Vec::new(),
83 streams: Vec::new(),
84 diagnostics: Vec::new(),
85 effects: Vec::new(),
86 suspension: None,
87 profiling: None,
88 execution_time_ms: 0,
89 used_jit: false,
90 type_info: None,
91 figures_touched: Vec::new(),
92 stdin_events: Vec::new(),
93 fusion_plan: None,
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
99pub enum RuntimeFlow {
100 NoValue,
101 Single(Value),
102 OutputList(Vec<Value>),
103 CommaList(Vec<Value>),
104 DynamicList(DynamicListHandle),
105}
106
107impl RuntimeFlow {
108 pub fn is_no_value(&self) -> bool {
109 matches!(self, Self::NoValue)
110 }
111
112 pub fn durable_workspace_value(&self) -> Option<&Value> {
113 match self {
114 Self::Single(value) => Some(value),
115 Self::NoValue | Self::OutputList(_) | Self::CommaList(_) | Self::DynamicList(_) => None,
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
121pub struct DynamicListHandle(pub Uuid);
122
123#[derive(Debug, Clone, PartialEq, Eq, Hash)]
124pub enum SourceIdentity {
125 Interactive { session: Uuid },
126 PathAndContentHash { path: String, hash: String },
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Hash)]
130pub enum WorkspaceBindingKey {
131 Interactive {
132 session: Uuid,
133 name: BindingName,
134 },
135 SourceBinding {
136 source: SourceIdentity,
137 def_path: DefPath,
138 binding: BindingName,
139 },
140 Global {
141 scope: GlobalScopeKey,
142 name: BindingName,
143 },
144 Persistent {
145 function: DefPath,
146 name: BindingName,
147 },
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Hash)]
151pub enum GlobalScopeKey {
152 Session(Uuid),
153 Package(String),
154}
155
156#[derive(Debug, Clone)]
157pub struct WorkspaceBindingValue {
158 pub key: WorkspaceBindingKey,
159 pub value: Value,
160}
161
162#[derive(Debug, Clone, Default)]
163pub struct WorkspaceDelta {
164 pub version: u64,
165 pub upserts: Vec<WorkspaceBindingValue>,
166 pub removals: Vec<WorkspaceBindingKey>,
167 pub full_snapshot_required: bool,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub enum InitFact {
172 Unassigned,
173 MaybeAssigned,
174 DefinitelyAssigned,
175}
176
177#[derive(Debug, Clone)]
178pub struct DisplayEvent {
179 pub label: DisplayLabel,
180 pub value: Value,
181 pub span: Span,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub enum DisplayLabel {
186 Binding(BindingName),
187 Literal(String),
188 Anonymous,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct RuntimeDiagnostic {
193 pub code: String,
194 pub severity: DiagnosticSeverity,
195 pub message: String,
196 pub span: Option<Span>,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub enum DiagnosticSeverity {
201 Error,
202 Warning,
203 Info,
204 Hint,
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub enum ObservedEffect {
209 Workspace(WorkspaceEffectKind),
210 Environment(EnvironmentEffectKind),
211}
212
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub enum WorkspaceEffectKind {
215 Load,
216 Clear,
217 AssignIn,
218 EvalIn,
219 DeclareGlobal,
220 DeclarePersistent,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub enum EnvironmentEffectKind {
225 ChangeDirectory,
226 MutatePath,
227 ClearFunctionCache,
228 ClearClassCache,
229 InvalidateDynamicLookup,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct Suspension {
234 pub task: Uuid,
235 pub frame: Uuid,
236 pub resume_point: ResumePoint,
237 pub pending: PendingOperation,
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub enum ResumePoint {
242 BytecodePc(usize),
243 Host(String),
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub enum PendingOperation {
248 HostInteraction,
249 Provider,
250 Filesystem,
251 Timer,
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn runtime_flow_distinguishes_durable_values_from_lists() {
260 let value = Value::Num(1.0);
261 assert!(RuntimeFlow::NoValue.is_no_value());
262 assert!(RuntimeFlow::Single(value)
263 .durable_workspace_value()
264 .is_some());
265 assert!(RuntimeFlow::OutputList(vec![Value::Num(1.0)])
266 .durable_workspace_value()
267 .is_none());
268 assert!(RuntimeFlow::CommaList(vec![Value::Num(1.0)])
269 .durable_workspace_value()
270 .is_none());
271 }
272
273 #[test]
274 fn execution_outcome_defaults_to_empty_no_value_contract() {
275 let outcome = ExecutionOutcome::default();
276 assert!(outcome.flow.is_no_value());
277 assert!(outcome.workspace_delta.upserts.is_empty());
278 assert!(outcome.workspace_delta.removals.is_empty());
279 assert!(!outcome.workspace_delta.full_snapshot_required);
280 assert!(outcome.display_events.is_empty());
281 assert!(outcome.streams.is_empty());
282 assert!(outcome.diagnostics.is_empty());
283 assert!(outcome.effects.is_empty());
284 assert!(outcome.suspension.is_none());
285 assert_eq!(outcome.execution_time_ms, 0);
286 assert!(!outcome.used_jit);
287 assert!(outcome.type_info.is_none());
288 assert!(outcome.figures_touched.is_empty());
289 assert!(outcome.stdin_events.is_empty());
290 assert!(outcome.fusion_plan.is_none());
291 }
292
293 #[test]
294 fn workspace_keys_are_stable_boundary_identities() {
295 let session = Uuid::from_u128(1);
296 let key = WorkspaceBindingKey::Interactive {
297 session,
298 name: BindingName("adjusted".to_string()),
299 };
300 assert_eq!(
301 key,
302 WorkspaceBindingKey::Interactive {
303 session,
304 name: BindingName("adjusted".to_string())
305 }
306 );
307 }
308}