Skip to main content

intent_parser/
ast.rs

1//! Typed AST for the IntentLang specification language.
2//!
3//! Every node carries a `Span` with byte offsets into the source text,
4//! supporting Phase 3 (Audit Bridge) source-location tracing.
5
6use serde::Serialize;
7
8/// Byte-offset span in the source text: `[start, end)`.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
10pub struct Span {
11    pub start: usize,
12    pub end: usize,
13}
14
15// ── Top-level ────────────────────────────────────────────────
16
17/// A complete `.intent` file.
18#[derive(Debug, Clone, Serialize)]
19pub struct File {
20    pub module: ModuleDecl,
21    pub doc: Option<DocBlock>,
22    pub items: Vec<TopLevelItem>,
23    pub span: Span,
24}
25
26/// `module ModuleName`
27#[derive(Debug, Clone, Serialize)]
28pub struct ModuleDecl {
29    pub name: String,
30    pub span: Span,
31}
32
33/// A sequence of `---` documentation lines.
34#[derive(Debug, Clone, Serialize)]
35pub struct DocBlock {
36    pub lines: Vec<String>,
37    pub span: Span,
38}
39
40/// Any top-level declaration.
41#[derive(Debug, Clone, Serialize)]
42pub enum TopLevelItem {
43    Entity(EntityDecl),
44    Action(ActionDecl),
45    Invariant(InvariantDecl),
46    EdgeCases(EdgeCasesDecl),
47}
48
49// ── Entity ───────────────────────────────────────────────────
50
51/// `entity EntityName { field* }`
52#[derive(Debug, Clone, Serialize)]
53pub struct EntityDecl {
54    pub doc: Option<DocBlock>,
55    pub name: String,
56    pub fields: Vec<FieldDecl>,
57    pub span: Span,
58}
59
60/// `name: Type` — a field or parameter declaration.
61#[derive(Debug, Clone, Serialize)]
62pub struct FieldDecl {
63    pub name: String,
64    pub ty: TypeExpr,
65    pub span: Span,
66}
67
68// ── Action ───────────────────────────────────────────────────
69
70/// `action ActionName { ... }`
71#[derive(Debug, Clone, Serialize)]
72pub struct ActionDecl {
73    pub doc: Option<DocBlock>,
74    pub name: String,
75    pub params: Vec<FieldDecl>,
76    pub requires: Option<RequiresBlock>,
77    pub ensures: Option<EnsuresBlock>,
78    pub properties: Option<PropertiesBlock>,
79    pub span: Span,
80}
81
82/// `requires { expr* }`
83#[derive(Debug, Clone, Serialize)]
84pub struct RequiresBlock {
85    pub conditions: Vec<Expr>,
86    pub span: Span,
87}
88
89/// `ensures { item* }`
90#[derive(Debug, Clone, Serialize)]
91pub struct EnsuresBlock {
92    pub items: Vec<EnsuresItem>,
93    pub span: Span,
94}
95
96/// A postcondition — either a bare expression or a `when` clause.
97#[derive(Debug, Clone, Serialize)]
98pub enum EnsuresItem {
99    Expr(Expr),
100    When(WhenClause),
101}
102
103/// `when condition => consequence`
104#[derive(Debug, Clone, Serialize)]
105pub struct WhenClause {
106    pub condition: Expr,
107    pub consequence: Expr,
108    pub span: Span,
109}
110
111/// `properties { entry* }`
112#[derive(Debug, Clone, Serialize)]
113pub struct PropertiesBlock {
114    pub entries: Vec<PropEntry>,
115    pub span: Span,
116}
117
118/// `key: value` inside a properties block.
119#[derive(Debug, Clone, Serialize)]
120pub struct PropEntry {
121    pub key: String,
122    pub value: PropValue,
123    pub span: Span,
124}
125
126/// The right-hand side of a property entry.
127#[derive(Debug, Clone, Serialize)]
128pub enum PropValue {
129    Literal(Literal),
130    Ident(String),
131    List(Vec<PropValue>),
132    Object(Vec<(String, PropValue)>),
133}
134
135// ── Invariant ────────────────────────────────────────────────
136
137/// `invariant InvariantName { doc? expr }`
138#[derive(Debug, Clone, Serialize)]
139pub struct InvariantDecl {
140    pub doc: Option<DocBlock>,
141    pub name: String,
142    pub body: Expr,
143    pub span: Span,
144}
145
146// ── Edge cases ───────────────────────────────────────────────
147
148/// `edge_cases { rule* }`
149#[derive(Debug, Clone, Serialize)]
150pub struct EdgeCasesDecl {
151    pub rules: Vec<EdgeRule>,
152    pub span: Span,
153}
154
155/// `when condition => action(args)`
156#[derive(Debug, Clone, Serialize)]
157pub struct EdgeRule {
158    pub condition: Expr,
159    pub action: ActionCall,
160    pub span: Span,
161}
162
163/// A function-call-style action on the RHS of an edge rule.
164#[derive(Debug, Clone, Serialize)]
165pub struct ActionCall {
166    pub name: String,
167    pub args: Vec<CallArg>,
168    pub span: Span,
169}
170
171/// A call argument — either named (`key: value`) or positional.
172#[derive(Debug, Clone, Serialize)]
173pub enum CallArg {
174    Named {
175        key: String,
176        value: Expr,
177        span: Span,
178    },
179    Positional(Expr),
180}
181
182// ── Types ────────────────────────────────────────────────────
183
184/// A full type expression: a union type optionally marked optional with `?`.
185#[derive(Debug, Clone, Serialize)]
186pub struct TypeExpr {
187    pub ty: TypeKind,
188    pub optional: bool,
189    pub span: Span,
190}
191
192/// The shape of a type.
193#[derive(Debug, Clone, Serialize)]
194pub enum TypeKind {
195    /// A single named type: `UUID`, `String`, `Active`.
196    Simple(String),
197    /// A union of two or more types: `Active | Frozen | Closed`.
198    Union(Vec<TypeKind>),
199    /// `List<T>`
200    List(Box<TypeExpr>),
201    /// `Set<T>`
202    Set(Box<TypeExpr>),
203    /// `Map<K, V>`
204    Map(Box<TypeExpr>, Box<TypeExpr>),
205    /// `Decimal(precision: 2)` — a type with parameters.
206    Parameterized {
207        name: String,
208        params: Vec<TypeParam>,
209    },
210}
211
212/// `name: value` inside a parameterized type like `Decimal(precision: 2)`.
213#[derive(Debug, Clone, Serialize)]
214pub struct TypeParam {
215    pub name: String,
216    pub value: Literal,
217    pub span: Span,
218}
219
220// ── Expressions ──────────────────────────────────────────────
221
222/// An expression node with its source span.
223#[derive(Debug, Clone, Serialize)]
224pub struct Expr {
225    pub kind: ExprKind,
226    pub span: Span,
227}
228
229/// Expression variants.
230#[derive(Debug, Clone, Serialize)]
231pub enum ExprKind {
232    /// `a => b` — logical implication.
233    Implies(Box<Expr>, Box<Expr>),
234    /// `a || b`
235    Or(Box<Expr>, Box<Expr>),
236    /// `a && b`
237    And(Box<Expr>, Box<Expr>),
238    /// `!a`
239    Not(Box<Expr>),
240    /// `a == b`, `a > b`, etc.
241    Compare {
242        left: Box<Expr>,
243        op: CmpOp,
244        right: Box<Expr>,
245    },
246    /// `a + b`, `a - b`
247    Arithmetic {
248        left: Box<Expr>,
249        op: ArithOp,
250        right: Box<Expr>,
251    },
252    /// `old(expr)` — pre-state reference.
253    Old(Box<Expr>),
254    /// `forall x: T => body` or `exists x: T => body`
255    Quantifier {
256        kind: QuantifierKind,
257        binding: String,
258        ty: String,
259        body: Box<Expr>,
260    },
261    /// `name(args)` — function call.
262    Call { name: String, args: Vec<CallArg> },
263    /// Field access chain: `a.b.c` or `f(x).y.z`.
264    /// `root` is the base expression, `fields` are the `.`-accessed names.
265    FieldAccess {
266        root: Box<Expr>,
267        fields: Vec<String>,
268    },
269    /// A plain identifier: `amount`, `Active`, `email`.
270    Ident(String),
271    /// A literal value.
272    Literal(Literal),
273}
274
275impl Expr {
276    /// Call `f` for each immediate child expression.
277    ///
278    /// Handles all `ExprKind` variants so callers don't need to duplicate
279    /// the recursive descent boilerplate.
280    pub fn for_each_child(&self, mut f: impl FnMut(&Expr)) {
281        match &self.kind {
282            ExprKind::Implies(a, b)
283            | ExprKind::Or(a, b)
284            | ExprKind::And(a, b)
285            | ExprKind::Compare {
286                left: a, right: b, ..
287            }
288            | ExprKind::Arithmetic {
289                left: a, right: b, ..
290            } => {
291                f(a);
292                f(b);
293            }
294            ExprKind::Not(inner) | ExprKind::Old(inner) => f(inner),
295            ExprKind::Call { args, .. } => {
296                for arg in args {
297                    match arg {
298                        CallArg::Named { value, .. } => f(value),
299                        CallArg::Positional(e) => f(e),
300                    }
301                }
302            }
303            ExprKind::FieldAccess { root, .. } => f(root),
304            ExprKind::Quantifier { body, .. } => f(body),
305            ExprKind::Ident(_) | ExprKind::Literal(_) => {}
306        }
307    }
308}
309
310/// Comparison operators.
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
312pub enum CmpOp {
313    Eq,
314    Ne,
315    Lt,
316    Gt,
317    Le,
318    Ge,
319}
320
321/// Arithmetic operators.
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
323pub enum ArithOp {
324    Add,
325    Sub,
326}
327
328/// Quantifier kind.
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
330pub enum QuantifierKind {
331    Forall,
332    Exists,
333}
334
335/// Literal values.
336#[derive(Debug, Clone, Serialize)]
337pub enum Literal {
338    Null,
339    Bool(bool),
340    Int(i64),
341    Decimal(String),
342    String(String),
343}