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 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}