Skip to main content

allium_parser/
ast.rs

1//! AST for the Allium specification language.
2//!
3//! The parse tree uses a uniform block-item representation for declaration
4//! bodies: every `name: value`, `keyword: value` and `let name = value` within
5//! braces is a [`BlockItem`]. Semantic classification into entity fields vs
6//! relationships vs derived values, or trigger types, happens in a later pass.
7//!
8//! Expressions are fully typed — the parser produces the rich [`Expr`] tree
9//! directly.
10
11use crate::Span;
12
13// ---------------------------------------------------------------------------
14// Top level
15// ---------------------------------------------------------------------------
16
17/// A parsed `.allium` file.
18#[derive(Debug, Clone)]
19pub struct Module {
20    pub span: Span,
21    /// Extracted from `-- allium: N` if present.
22    pub version: Option<u32>,
23    pub declarations: Vec<Decl>,
24}
25
26// ---------------------------------------------------------------------------
27// Declarations
28// ---------------------------------------------------------------------------
29
30#[derive(Debug, Clone)]
31pub enum Decl {
32    Use(UseDecl),
33    Block(BlockDecl),
34    Default(DefaultDecl),
35    Variant(VariantDecl),
36    Deferred(DeferredDecl),
37    OpenQuestion(OpenQuestionDecl),
38}
39
40/// `use "path" as alias`
41#[derive(Debug, Clone)]
42pub struct UseDecl {
43    pub span: Span,
44    pub path: StringLiteral,
45    pub alias: Option<Ident>,
46}
47
48/// A named or anonymous block: `entity User { ... }`, `config { ... }`, etc.
49#[derive(Debug, Clone)]
50pub struct BlockDecl {
51    pub span: Span,
52    pub kind: BlockKind,
53    /// `None` for `given` and local `config` blocks.
54    pub name: Option<Ident>,
55    pub items: Vec<BlockItem>,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum BlockKind {
60    Entity,
61    ExternalEntity,
62    Value,
63    Enum,
64    Given,
65    Config,
66    Rule,
67    Surface,
68    Actor,
69}
70
71/// `default [Type] name = value`
72#[derive(Debug, Clone)]
73pub struct DefaultDecl {
74    pub span: Span,
75    pub type_name: Option<Ident>,
76    pub name: Ident,
77    pub value: Expr,
78}
79
80/// `variant Name : Type { ... }`
81#[derive(Debug, Clone)]
82pub struct VariantDecl {
83    pub span: Span,
84    pub name: Ident,
85    pub base: Expr,
86    pub items: Vec<BlockItem>,
87}
88
89/// `deferred path.expression`
90#[derive(Debug, Clone)]
91pub struct DeferredDecl {
92    pub span: Span,
93    pub path: Expr,
94}
95
96/// `open question "text"`
97#[derive(Debug, Clone)]
98pub struct OpenQuestionDecl {
99    pub span: Span,
100    pub text: StringLiteral,
101}
102
103// ---------------------------------------------------------------------------
104// Block items — uniform representation for declaration bodies
105// ---------------------------------------------------------------------------
106
107#[derive(Debug, Clone)]
108pub struct BlockItem {
109    pub span: Span,
110    pub kind: BlockItemKind,
111}
112
113#[derive(Debug, Clone)]
114pub enum BlockItemKind {
115    /// `keyword: value` — when:, requires:, ensures:, facing:, etc.
116    Clause { keyword: String, value: Expr },
117    /// `name: value` — field, relationship, projection, derived value.
118    Assignment { name: Ident, value: Expr },
119    /// `name(params): value` — parameterised derived value.
120    ParamAssignment {
121        name: Ident,
122        params: Vec<Ident>,
123        value: Expr,
124    },
125    /// `let name = value`
126    Let { name: Ident, value: Expr },
127    /// Bare name inside an enum body — `pending`, `shipped`, etc.
128    EnumVariant { name: Ident },
129    /// `for binding in collection [where filter]: ...` at block level (rule iteration)
130    ForBlock {
131        binding: ForBinding,
132        collection: Expr,
133        filter: Option<Expr>,
134        items: Vec<BlockItem>,
135    },
136    /// `if condition: ... else if ...: ... else: ...` at block level
137    IfBlock {
138        branches: Vec<CondBlockBranch>,
139        else_items: Option<Vec<BlockItem>>,
140    },
141    /// `Shard.shard_cache: value` — dot-path reverse relationship
142    PathAssignment { path: Expr, value: Expr },
143    /// `open question "text"` (nested within a block)
144    OpenQuestion { text: StringLiteral },
145}
146
147// ---------------------------------------------------------------------------
148// Expressions
149// ---------------------------------------------------------------------------
150
151#[derive(Debug, Clone)]
152pub enum Expr {
153    /// `identifier` or `_`
154    Ident(Ident),
155
156    /// `"text"` possibly with `{interpolation}`
157    StringLiteral(StringLiteral),
158
159    /// `42`, `100_000`, `3.14`
160    NumberLiteral { span: Span, value: String },
161
162    /// `true`, `false`
163    BoolLiteral { span: Span, value: bool },
164
165    /// `null`
166    Null { span: Span },
167
168    /// `now`
169    Now { span: Span },
170
171    /// `this`
172    This { span: Span },
173
174    /// `within`
175    Within { span: Span },
176
177    /// `24.hours`, `7.days`
178    DurationLiteral { span: Span, value: String },
179
180    /// `{ a, b, c }` — set literal
181    SetLiteral { span: Span, elements: Vec<Expr> },
182
183    /// `[a, b, c]` — list literal
184    ListLiteral { span: Span, elements: Vec<Expr> },
185
186    /// `{ key: value, ... }` — object literal
187    ObjectLiteral { span: Span, fields: Vec<NamedArg> },
188
189    /// `Set<T>`, `List<T>` — generic type annotation
190    GenericType {
191        span: Span,
192        name: Box<Expr>,
193        args: Vec<Expr>,
194    },
195
196    /// `a.b`
197    MemberAccess {
198        span: Span,
199        object: Box<Expr>,
200        field: Ident,
201    },
202
203    /// `a?.b`
204    OptionalAccess {
205        span: Span,
206        object: Box<Expr>,
207        field: Ident,
208    },
209
210    /// `a ?? b`
211    NullCoalesce {
212        span: Span,
213        left: Box<Expr>,
214        right: Box<Expr>,
215    },
216
217    /// `func(args)` or `entity.method(args)`
218    Call {
219        span: Span,
220        function: Box<Expr>,
221        args: Vec<CallArg>,
222    },
223
224    /// `Entity{field1, field2}` or `Entity{field: value}`
225    JoinLookup {
226        span: Span,
227        entity: Box<Expr>,
228        fields: Vec<JoinField>,
229    },
230
231    /// `a + b`, `a - b`, `a * b`, `a / b`
232    BinaryOp {
233        span: Span,
234        left: Box<Expr>,
235        op: BinaryOp,
236        right: Box<Expr>,
237    },
238
239    /// `a = b`, `a != b`, `a < b`, `a <= b`, `a > b`, `a >= b`
240    Comparison {
241        span: Span,
242        left: Box<Expr>,
243        op: ComparisonOp,
244        right: Box<Expr>,
245    },
246
247    /// `a and b`, `a or b`
248    LogicalOp {
249        span: Span,
250        left: Box<Expr>,
251        op: LogicalOp,
252        right: Box<Expr>,
253    },
254
255    /// `not expr`
256    Not { span: Span, operand: Box<Expr> },
257
258    /// `x in collection`
259    In {
260        span: Span,
261        element: Box<Expr>,
262        collection: Box<Expr>,
263    },
264
265    /// `x not in collection`
266    NotIn {
267        span: Span,
268        element: Box<Expr>,
269        collection: Box<Expr>,
270    },
271
272    /// `exists expr`
273    Exists { span: Span, operand: Box<Expr> },
274
275    /// `not exists expr`
276    NotExists { span: Span, operand: Box<Expr> },
277
278    /// `collection where condition`
279    Where {
280        span: Span,
281        source: Box<Expr>,
282        condition: Box<Expr>,
283    },
284
285    /// `collection with predicate` (in relationship declarations)
286    With {
287        span: Span,
288        source: Box<Expr>,
289        predicate: Box<Expr>,
290    },
291
292    /// `a | b` — pipe, used for inline enums and sum type discriminators
293    Pipe {
294        span: Span,
295        left: Box<Expr>,
296        right: Box<Expr>,
297    },
298
299    /// `x => body`
300    Lambda {
301        span: Span,
302        param: Box<Expr>,
303        body: Box<Expr>,
304    },
305
306    /// `if cond: a else if cond: b else: c`
307    Conditional {
308        span: Span,
309        branches: Vec<CondBranch>,
310        else_body: Option<Box<Expr>>,
311    },
312
313    /// `for x in collection [where cond]: body`
314    For {
315        span: Span,
316        binding: ForBinding,
317        collection: Box<Expr>,
318        filter: Option<Box<Expr>>,
319        body: Box<Expr>,
320    },
321
322    /// `collection where cond -> field` — projection mapping
323    ProjectionMap {
324        span: Span,
325        source: Box<Expr>,
326        field: Ident,
327    },
328
329    /// `Entity.status transitions_to state`
330    TransitionsTo {
331        span: Span,
332        subject: Box<Expr>,
333        new_state: Box<Expr>,
334    },
335
336    /// `Entity.status becomes state`
337    Becomes {
338        span: Span,
339        subject: Box<Expr>,
340        new_state: Box<Expr>,
341    },
342
343    /// `name: expr` — binding inside a clause value (when triggers, facing, context)
344    Binding {
345        span: Span,
346        name: Ident,
347        value: Box<Expr>,
348    },
349
350    /// `action when condition` — guard on a provides/related item
351    WhenGuard {
352        span: Span,
353        action: Box<Expr>,
354        condition: Box<Expr>,
355    },
356
357    /// `T?` — optional type annotation
358    TypeOptional {
359        span: Span,
360        inner: Box<Expr>,
361    },
362
363    /// `let name = value` inside an expression block (ensures, provides, etc.)
364    LetExpr {
365        span: Span,
366        name: Ident,
367        value: Box<Expr>,
368    },
369
370    /// `oauth/Session` — qualified name with module prefix
371    QualifiedName(QualifiedName),
372
373    /// A sequence of expressions from a multi-line block.
374    Block { span: Span, items: Vec<Expr> },
375
376}
377
378impl Expr {
379    pub fn span(&self) -> Span {
380        match self {
381            Expr::Ident(id) => id.span,
382            Expr::StringLiteral(s) => s.span,
383            Expr::NumberLiteral { span, .. }
384            | Expr::BoolLiteral { span, .. }
385            | Expr::Null { span }
386            | Expr::Now { span }
387            | Expr::This { span }
388            | Expr::Within { span }
389            | Expr::DurationLiteral { span, .. }
390            | Expr::SetLiteral { span, .. }
391            | Expr::ListLiteral { span, .. }
392            | Expr::ObjectLiteral { span, .. }
393            | Expr::GenericType { span, .. }
394            | Expr::MemberAccess { span, .. }
395            | Expr::OptionalAccess { span, .. }
396            | Expr::NullCoalesce { span, .. }
397            | Expr::Call { span, .. }
398            | Expr::JoinLookup { span, .. }
399            | Expr::BinaryOp { span, .. }
400            | Expr::Comparison { span, .. }
401            | Expr::LogicalOp { span, .. }
402            | Expr::Not { span, .. }
403            | Expr::In { span, .. }
404            | Expr::NotIn { span, .. }
405            | Expr::Exists { span, .. }
406            | Expr::NotExists { span, .. }
407            | Expr::Where { span, .. }
408            | Expr::With { span, .. }
409            | Expr::Pipe { span, .. }
410            | Expr::Lambda { span, .. }
411            | Expr::Conditional { span, .. }
412            | Expr::For { span, .. }
413            | Expr::ProjectionMap { span, .. }
414            | Expr::TransitionsTo { span, .. }
415            | Expr::Becomes { span, .. }
416            | Expr::Binding { span, .. }
417            | Expr::WhenGuard { span, .. }
418            | Expr::TypeOptional { span, .. }
419            | Expr::LetExpr { span, .. }
420            | Expr::Block { span, .. } => *span,
421            Expr::QualifiedName(q) => q.span,
422        }
423    }
424}
425
426#[derive(Debug, Clone)]
427pub struct CondBranch {
428    pub span: Span,
429    pub condition: Expr,
430    pub body: Expr,
431}
432
433/// A branch of a block-level `if`/`else if` chain.
434#[derive(Debug, Clone)]
435pub struct CondBlockBranch {
436    pub span: Span,
437    pub condition: Expr,
438    pub items: Vec<BlockItem>,
439}
440
441/// Binding in a `for` loop — either a single identifier or a
442/// destructured tuple like `(a, b)`.
443#[derive(Debug, Clone)]
444pub enum ForBinding {
445    Single(Ident),
446    Destructured(Vec<Ident>, Span),
447}
448
449// ---------------------------------------------------------------------------
450// Shared types
451// ---------------------------------------------------------------------------
452
453#[derive(Debug, Clone)]
454pub struct Ident {
455    pub span: Span,
456    pub name: String,
457}
458
459#[derive(Debug, Clone)]
460pub struct QualifiedName {
461    pub span: Span,
462    pub qualifier: Option<String>,
463    pub name: String,
464}
465
466#[derive(Debug, Clone)]
467pub struct StringLiteral {
468    pub span: Span,
469    pub parts: Vec<StringPart>,
470}
471
472#[derive(Debug, Clone)]
473pub enum StringPart {
474    Text(String),
475    Interpolation(Ident),
476}
477
478#[derive(Debug, Clone)]
479pub struct NamedArg {
480    pub span: Span,
481    pub name: Ident,
482    pub value: Expr,
483}
484
485#[derive(Debug, Clone)]
486pub enum CallArg {
487    Positional(Expr),
488    Named(NamedArg),
489}
490
491#[derive(Debug, Clone)]
492pub struct JoinField {
493    pub span: Span,
494    pub field: Ident,
495    /// If absent, matches a local variable with the same name.
496    pub value: Option<Expr>,
497}
498
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500pub enum BinaryOp {
501    Add,
502    Sub,
503    Mul,
504    Div,
505}
506
507#[derive(Debug, Clone, Copy, PartialEq, Eq)]
508pub enum ComparisonOp {
509    Eq,
510    NotEq,
511    Lt,
512    LtEq,
513    Gt,
514    GtEq,
515}
516
517#[derive(Debug, Clone, Copy, PartialEq, Eq)]
518pub enum LogicalOp {
519    And,
520    Or,
521}