Skip to main content

axon/effects/
ir.rs

1//! IR deserialization for AXON algebraic effects.
2//!
3//! Mirrors the 8 IR nodes emitted by the Python frontend (Fase 23.d):
4//! `IREffectDeclaration`, `IREffectOperation`, `IRPerform`,
5//! `IRHandlerFrame`, `IRHandlerClause`, `IRResume`, `IRAbort`,
6//! `IRForward`. Field shapes match the JSON output of
7//! `axon.compiler.ir_nodes.IR*.to_dict()` exactly — verified by the
8//! drift gate in 23.h (cross-stack opcode parity).
9//!
10//! `Instruction` is the discriminated union the runtime walks. Each
11//! variant carries the CPS state coordinate (`state_id` / `frame_id`)
12//! the FSM dispatch loop uses.
13
14use serde::Deserialize;
15
16use super::value::Value;
17
18/// Top-level effect declaration: `effect Name { ops... }`.
19#[derive(Debug, Clone, Deserialize)]
20pub struct IREffectDeclaration {
21    pub name: String,
22    #[serde(default)]
23    pub operations: Vec<IREffectOperation>,
24    #[serde(default)]
25    pub source_line: u32,
26    #[serde(default)]
27    pub source_column: u32,
28}
29
30/// One operation inside an effect declaration.
31#[derive(Debug, Clone, Deserialize)]
32pub struct IREffectOperation {
33    pub name: String,
34    #[serde(default)]
35    pub type_parameters: Vec<String>,
36    #[serde(default)]
37    pub parameter_names: Vec<String>,
38    #[serde(default)]
39    pub parameter_types: Vec<String>,
40    #[serde(default)]
41    pub return_type: String,
42    #[serde(default)]
43    pub source_line: u32,
44    #[serde(default)]
45    pub source_column: u32,
46}
47
48/// `perform Effect.Op(args)` — yields control to the matching handler frame.
49#[derive(Debug, Clone, Deserialize)]
50pub struct IRPerform {
51    pub effect_name: String,
52    pub operation_name: String,
53    #[serde(default)]
54    pub arguments: Vec<String>,
55    #[serde(default)]
56    pub state_id: u32,
57    #[serde(default)]
58    pub resume_label: String,
59}
60
61/// One clause of an `IRHandlerFrame`: `Op(params) -> { body }`.
62#[derive(Debug, Clone, Deserialize)]
63pub struct IRHandlerClause {
64    pub operation_name: String,
65    #[serde(default)]
66    pub parameter_names: Vec<String>,
67    #[serde(default)]
68    pub body: Vec<Instruction>,
69    #[serde(default)]
70    pub source_line: u32,
71    #[serde(default)]
72    pub source_column: u32,
73}
74
75/// `handle E1, E2 { clauses } in { body }` — a single handler frame.
76#[derive(Debug, Clone, Deserialize)]
77pub struct IRHandlerFrame {
78    pub effect_names: Vec<String>,
79    #[serde(default)]
80    pub clauses: Vec<IRHandlerClause>,
81    #[serde(default)]
82    pub body: Vec<Instruction>,
83    #[serde(default)]
84    pub frame_id: u32,
85    #[serde(default)]
86    pub body_states: Vec<u32>,
87    #[serde(default)]
88    pub source_line: u32,
89    #[serde(default)]
90    pub source_column: u32,
91}
92
93/// `resume(value)` — invoke the captured one-shot continuation.
94#[derive(Debug, Clone, Deserialize)]
95pub struct IRResume {
96    #[serde(default)]
97    pub value_expr: String,
98    #[serde(default)]
99    pub frame_id: u32,
100}
101
102/// `abort(value)` — terminate the handle without resuming.
103#[derive(Debug, Clone, Deserialize)]
104pub struct IRAbort {
105    #[serde(default)]
106    pub value_expr: String,
107    #[serde(default)]
108    pub frame_id: u32,
109}
110
111/// `forward Effect.Op(args)` — propagate to the next outer handler.
112#[derive(Debug, Clone, Deserialize)]
113pub struct IRForward {
114    pub effect_name: String,
115    pub operation_name: String,
116    #[serde(default)]
117    pub arguments: Vec<String>,
118    #[serde(default)]
119    pub source_frame_id: u32,
120    #[serde(default)]
121    pub state_id: u32,
122    #[serde(default)]
123    pub resume_label: String,
124}
125
126/// Discriminated union over the runtime instructions the FSM dispatch
127/// loop walks. Matches `node_type` in the JSON IR exactly.
128#[derive(Debug, Clone, Deserialize)]
129#[serde(tag = "node_type", rename_all = "snake_case")]
130pub enum Instruction {
131    /// `perform Effect.Op(args)` — yield to handler.
132    Perform(IRPerform),
133    /// `handle E { ... } in { ... }` — push handler frame, run body.
134    HandlerFrame(IRHandlerFrame),
135    /// `resume(value)` — return from handler to perform site.
136    Resume(IRResume),
137    /// `abort(value)` — exit handle expression.
138    Abort(IRAbort),
139    /// `forward Effect.Op(args)` — propagate to outer handler.
140    Forward(IRForward),
141    /// Catch-all for legacy IR opcodes the runtime currently
142    /// passes through unchanged (steps, conditionals, lets, etc.).
143    /// Carrying them here lets a handler body composed of mixed
144    /// algebraic-effect + legacy nodes deserialize without errors;
145    /// the runtime treats them as inert leaves (no-op for FSM).
146    #[serde(other)]
147    Passthrough,
148}
149
150/// Errors arising from IR shape mismatches at deserialization time.
151#[derive(Debug)]
152pub enum EffectIRError {
153    /// The JSON was not parseable into the expected shape.
154    DeserializeFailed(serde_json::Error),
155    /// A required field was missing or had an unexpected value.
156    InvalidShape(String),
157}
158
159impl std::fmt::Display for EffectIRError {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        match self {
162            EffectIRError::DeserializeFailed(e) => write!(f, "deserialize failed: {e}"),
163            EffectIRError::InvalidShape(s) => write!(f, "invalid IR shape: {s}"),
164        }
165    }
166}
167
168impl std::error::Error for EffectIRError {}
169
170impl From<serde_json::Error> for EffectIRError {
171    fn from(e: serde_json::Error) -> Self {
172        EffectIRError::DeserializeFailed(e)
173    }
174}
175
176/// Parse a `[Instruction]` block from a JSON array — convenience for
177/// loading a flow body or handler-clause body in tests / CLI tooling.
178pub fn parse_block(json: &str) -> Result<Vec<Instruction>, EffectIRError> {
179    Ok(serde_json::from_str(json)?)
180}
181
182/// Parse a single `IREffectDeclaration` from JSON.
183pub fn parse_effect(json: &str) -> Result<IREffectDeclaration, EffectIRError> {
184    Ok(serde_json::from_str(json)?)
185}
186
187/// Resolve an argument-text vector to runtime values via the
188/// (`Value::from_argument_text`) heuristic. Matches the Python
189/// convention for perform/forward arguments stored as strings.
190pub fn resolve_arguments(args: &[String]) -> Vec<Value> {
191    args.iter().map(|s| Value::from_argument_text(s)).collect()
192}