Skip to main content

runmat_core/
abi.rs

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}