Skip to main content

runar_compiler_rust/ir/
mod.rs

1//! ANF IR types and loader.
2//!
3//! These types mirror the canonical Rúnar ANF IR JSON schema. Any conformant
4//! Rúnar compiler produces byte-identical ANF IR (when serialised with canonical
5//! JSON), so these types serve as the universal interchange format.
6
7pub mod loader;
8
9use serde::{Deserialize, Serialize};
10
11// ---------------------------------------------------------------------------
12// Source locations
13// ---------------------------------------------------------------------------
14
15/// Source location in the original file (used for debug source maps).
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SourceLocation {
18    pub file: String,
19    pub line: usize,
20    pub column: usize,
21}
22
23// ---------------------------------------------------------------------------
24// Program structure
25// ---------------------------------------------------------------------------
26
27/// Top-level ANF IR container.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ANFProgram {
30    #[serde(rename = "contractName")]
31    pub contract_name: String,
32    pub properties: Vec<ANFProperty>,
33    pub methods: Vec<ANFMethod>,
34}
35
36/// A contract-level property (constructor parameter).
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct ANFProperty {
39    pub name: String,
40    #[serde(rename = "type")]
41    pub prop_type: String,
42    pub readonly: bool,
43    #[serde(rename = "initialValue", skip_serializing_if = "Option::is_none")]
44    pub initial_value: Option<serde_json::Value>,
45}
46
47/// A single contract method.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ANFMethod {
50    pub name: String,
51    pub params: Vec<ANFParam>,
52    pub body: Vec<ANFBinding>,
53    #[serde(rename = "isPublic")]
54    pub is_public: bool,
55}
56
57/// A method parameter.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ANFParam {
60    pub name: String,
61    #[serde(rename = "type")]
62    pub param_type: String,
63}
64
65// ---------------------------------------------------------------------------
66// Bindings
67// ---------------------------------------------------------------------------
68
69/// A single let-binding: `let <name> = <value>`.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ANFBinding {
72    pub name: String,
73    pub value: ANFValue,
74    /// Debug-only: source location of the originating AST node. Not part of conformance.
75    #[serde(rename = "sourceLoc", skip_serializing_if = "Option::is_none")]
76    pub source_loc: Option<SourceLocation>,
77}
78
79// ---------------------------------------------------------------------------
80// ANF value types (discriminated on `kind`)
81// ---------------------------------------------------------------------------
82
83/// Discriminated union of all ANF value types.
84///
85/// Uses `#[serde(tag = "kind")]` to match the JSON `"kind"` discriminator.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(tag = "kind")]
88pub enum ANFValue {
89    #[serde(rename = "load_param")]
90    LoadParam { name: String },
91
92    #[serde(rename = "load_prop")]
93    LoadProp { name: String },
94
95    #[serde(rename = "load_const")]
96    LoadConst { value: serde_json::Value },
97
98    #[serde(rename = "bin_op")]
99    BinOp {
100        op: String,
101        left: String,
102        right: String,
103        #[serde(skip_serializing_if = "Option::is_none")]
104        result_type: Option<String>,
105    },
106
107    #[serde(rename = "unary_op")]
108    UnaryOp {
109        op: String,
110        operand: String,
111        #[serde(skip_serializing_if = "Option::is_none")]
112        result_type: Option<String>,
113    },
114
115    #[serde(rename = "call")]
116    Call {
117        func: String,
118        args: Vec<String>,
119    },
120
121    #[serde(rename = "method_call")]
122    MethodCall {
123        object: String,
124        method: String,
125        args: Vec<String>,
126    },
127
128    #[serde(rename = "if")]
129    If {
130        cond: String,
131        then: Vec<ANFBinding>,
132        #[serde(rename = "else")]
133        else_branch: Vec<ANFBinding>,
134    },
135
136    #[serde(rename = "loop")]
137    Loop {
138        count: usize,
139        body: Vec<ANFBinding>,
140        #[serde(rename = "iterVar")]
141        iter_var: String,
142    },
143
144    #[serde(rename = "assert")]
145    Assert { value: String },
146
147    #[serde(rename = "update_prop")]
148    UpdateProp { name: String, value: String },
149
150    #[serde(rename = "get_state_script")]
151    GetStateScript {},
152
153    #[serde(rename = "check_preimage")]
154    CheckPreimage { preimage: String },
155
156    #[serde(rename = "deserialize_state")]
157    DeserializeState { preimage: String },
158
159    #[serde(rename = "add_output")]
160    AddOutput {
161        satoshis: String,
162        #[serde(rename = "stateValues")]
163        state_values: Vec<String>,
164        #[serde(default)]
165        preimage: String,
166    },
167
168    #[serde(rename = "add_raw_output")]
169    AddRawOutput {
170        satoshis: String,
171        #[serde(rename = "scriptBytes")]
172        script_bytes: String,
173    },
174
175    #[serde(rename = "array_literal")]
176    ArrayLiteral {
177        elements: Vec<String>,
178    },
179}
180
181// ---------------------------------------------------------------------------
182// Constant value helpers
183// ---------------------------------------------------------------------------
184
185/// Typed constant value extracted from a `serde_json::Value`.
186#[derive(Debug, Clone)]
187pub enum ConstValue {
188    Bool(bool),
189    Int(i128),
190    Str(String),
191}
192
193impl ANFValue {
194    /// Extract the typed constant from a `LoadConst` value.
195    pub fn const_value(&self) -> Option<ConstValue> {
196        match self {
197            ANFValue::LoadConst { value } => parse_const_value(value),
198            _ => None,
199        }
200    }
201}
202
203/// Parse a `serde_json::Value` into a `ConstValue`.
204pub fn parse_const_value(v: &serde_json::Value) -> Option<ConstValue> {
205    match v {
206        serde_json::Value::Bool(b) => Some(ConstValue::Bool(*b)),
207        serde_json::Value::Number(n) => {
208            // Try i64 first (covers most values), then fall back to f64 for larger numbers
209            if let Some(i) = n.as_i64() {
210                Some(ConstValue::Int(i as i128))
211            } else if let Some(f) = n.as_f64() {
212                Some(ConstValue::Int(f as i128))
213            } else {
214                None
215            }
216        }
217        serde_json::Value::String(s) => Some(ConstValue::Str(s.clone())),
218        _ => None,
219    }
220}