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    /// `{ key: value, ... }` — object literal
184    ObjectLiteral { span: Span, fields: Vec<NamedArg> },
185
186    /// `Set<T>`, `List<T>` — generic type annotation
187    GenericType {
188        span: Span,
189        name: Box<Expr>,
190        args: Vec<Expr>,
191    },
192
193    /// `a.b`
194    MemberAccess {
195        span: Span,
196        object: Box<Expr>,
197        field: Ident,
198    },
199
200    /// `a?.b`
201    OptionalAccess {
202        span: Span,
203        object: Box<Expr>,
204        field: Ident,
205    },
206
207    /// `a ?? b`
208    NullCoalesce {
209        span: Span,
210        left: Box<Expr>,
211        right: Box<Expr>,
212    },
213
214    /// `func(args)` or `entity.method(args)`
215    Call {
216        span: Span,
217        function: Box<Expr>,
218        args: Vec<CallArg>,
219    },
220
221    /// `Entity{field1, field2}` or `Entity{field: value}`
222    JoinLookup {
223        span: Span,
224        entity: Box<Expr>,
225        fields: Vec<JoinField>,
226    },
227
228    /// `a + b`, `a - b`, `a * b`, `a / b`
229    BinaryOp {
230        span: Span,
231        left: Box<Expr>,
232        op: BinaryOp,
233        right: Box<Expr>,
234    },
235
236    /// `a = b`, `a != b`, `a < b`, `a <= b`, `a > b`, `a >= b`
237    Comparison {
238        span: Span,
239        left: Box<Expr>,
240        op: ComparisonOp,
241        right: Box<Expr>,
242    },
243
244    /// `a and b`, `a or b`
245    LogicalOp {
246        span: Span,
247        left: Box<Expr>,
248        op: LogicalOp,
249        right: Box<Expr>,
250    },
251
252    /// `not expr`
253    Not { span: Span, operand: Box<Expr> },
254
255    /// `x in collection`
256    In {
257        span: Span,
258        element: Box<Expr>,
259        collection: Box<Expr>,
260    },
261
262    /// `x not in collection`
263    NotIn {
264        span: Span,
265        element: Box<Expr>,
266        collection: Box<Expr>,
267    },
268
269    /// `exists expr`
270    Exists { span: Span, operand: Box<Expr> },
271
272    /// `not exists expr`
273    NotExists { span: Span, operand: Box<Expr> },
274
275    /// `collection where condition`
276    Where {
277        span: Span,
278        source: Box<Expr>,
279        condition: Box<Expr>,
280    },
281
282    /// `collection with predicate` (in relationship declarations)
283    With {
284        span: Span,
285        source: Box<Expr>,
286        predicate: Box<Expr>,
287    },
288
289    /// `a | b` — pipe, used for inline enums and sum type discriminators
290    Pipe {
291        span: Span,
292        left: Box<Expr>,
293        right: Box<Expr>,
294    },
295
296    /// `x => body`
297    Lambda {
298        span: Span,
299        param: Box<Expr>,
300        body: Box<Expr>,
301    },
302
303    /// `if cond: a else if cond: b else: c`
304    Conditional {
305        span: Span,
306        branches: Vec<CondBranch>,
307        else_body: Option<Box<Expr>>,
308    },
309
310    /// `for x in collection [where cond]: body`
311    For {
312        span: Span,
313        binding: ForBinding,
314        collection: Box<Expr>,
315        filter: Option<Box<Expr>>,
316        body: Box<Expr>,
317    },
318
319    /// `collection where cond -> field` — projection mapping
320    ProjectionMap {
321        span: Span,
322        source: Box<Expr>,
323        field: Ident,
324    },
325
326    /// `Entity.status transitions_to state`
327    TransitionsTo {
328        span: Span,
329        subject: Box<Expr>,
330        new_state: Box<Expr>,
331    },
332
333    /// `Entity.status becomes state`
334    Becomes {
335        span: Span,
336        subject: Box<Expr>,
337        new_state: Box<Expr>,
338    },
339
340    /// `name: expr` — binding inside a clause value (when triggers, facing, context)
341    Binding {
342        span: Span,
343        name: Ident,
344        value: Box<Expr>,
345    },
346
347    /// `action when condition` — guard on a provides/related item
348    WhenGuard {
349        span: Span,
350        action: Box<Expr>,
351        condition: Box<Expr>,
352    },
353
354    /// `T?` — optional type annotation
355    TypeOptional {
356        span: Span,
357        inner: Box<Expr>,
358    },
359
360    /// `let name = value` inside an expression block (ensures, provides, etc.)
361    LetExpr {
362        span: Span,
363        name: Ident,
364        value: Box<Expr>,
365    },
366
367    /// `oauth/Session` — qualified name with module prefix
368    QualifiedName(QualifiedName),
369
370    /// A sequence of expressions from a multi-line block.
371    Block { span: Span, items: Vec<Expr> },
372
373}
374
375impl Expr {
376    pub fn span(&self) -> Span {
377        match self {
378            Expr::Ident(id) => id.span,
379            Expr::StringLiteral(s) => s.span,
380            Expr::NumberLiteral { span, .. }
381            | Expr::BoolLiteral { span, .. }
382            | Expr::Null { span }
383            | Expr::Now { span }
384            | Expr::This { span }
385            | Expr::Within { span }
386            | Expr::DurationLiteral { span, .. }
387            | Expr::SetLiteral { span, .. }
388            | Expr::ObjectLiteral { span, .. }
389            | Expr::GenericType { span, .. }
390            | Expr::MemberAccess { span, .. }
391            | Expr::OptionalAccess { span, .. }
392            | Expr::NullCoalesce { span, .. }
393            | Expr::Call { span, .. }
394            | Expr::JoinLookup { span, .. }
395            | Expr::BinaryOp { span, .. }
396            | Expr::Comparison { span, .. }
397            | Expr::LogicalOp { span, .. }
398            | Expr::Not { span, .. }
399            | Expr::In { span, .. }
400            | Expr::NotIn { span, .. }
401            | Expr::Exists { span, .. }
402            | Expr::NotExists { span, .. }
403            | Expr::Where { span, .. }
404            | Expr::With { span, .. }
405            | Expr::Pipe { span, .. }
406            | Expr::Lambda { span, .. }
407            | Expr::Conditional { span, .. }
408            | Expr::For { span, .. }
409            | Expr::ProjectionMap { span, .. }
410            | Expr::TransitionsTo { span, .. }
411            | Expr::Becomes { span, .. }
412            | Expr::Binding { span, .. }
413            | Expr::WhenGuard { span, .. }
414            | Expr::TypeOptional { span, .. }
415            | Expr::LetExpr { span, .. }
416            | Expr::Block { span, .. } => *span,
417            Expr::QualifiedName(q) => q.span,
418        }
419    }
420}
421
422#[derive(Debug, Clone)]
423pub struct CondBranch {
424    pub span: Span,
425    pub condition: Expr,
426    pub body: Expr,
427}
428
429/// A branch of a block-level `if`/`else if` chain.
430#[derive(Debug, Clone)]
431pub struct CondBlockBranch {
432    pub span: Span,
433    pub condition: Expr,
434    pub items: Vec<BlockItem>,
435}
436
437/// Binding in a `for` loop — either a single identifier or a
438/// destructured tuple like `(a, b)`.
439#[derive(Debug, Clone)]
440pub enum ForBinding {
441    Single(Ident),
442    Destructured(Vec<Ident>, Span),
443}
444
445// ---------------------------------------------------------------------------
446// Shared types
447// ---------------------------------------------------------------------------
448
449#[derive(Debug, Clone)]
450pub struct Ident {
451    pub span: Span,
452    pub name: String,
453}
454
455#[derive(Debug, Clone)]
456pub struct QualifiedName {
457    pub span: Span,
458    pub qualifier: Option<String>,
459    pub name: String,
460}
461
462#[derive(Debug, Clone)]
463pub struct StringLiteral {
464    pub span: Span,
465    pub parts: Vec<StringPart>,
466}
467
468#[derive(Debug, Clone)]
469pub enum StringPart {
470    Text(String),
471    Interpolation(Ident),
472}
473
474#[derive(Debug, Clone)]
475pub struct NamedArg {
476    pub span: Span,
477    pub name: Ident,
478    pub value: Expr,
479}
480
481#[derive(Debug, Clone)]
482pub enum CallArg {
483    Positional(Expr),
484    Named(NamedArg),
485}
486
487#[derive(Debug, Clone)]
488pub struct JoinField {
489    pub span: Span,
490    pub field: Ident,
491    /// If absent, matches a local variable with the same name.
492    pub value: Option<Expr>,
493}
494
495#[derive(Debug, Clone, Copy, PartialEq, Eq)]
496pub enum BinaryOp {
497    Add,
498    Sub,
499    Mul,
500    Div,
501}
502
503#[derive(Debug, Clone, Copy, PartialEq, Eq)]
504pub enum ComparisonOp {
505    Eq,
506    NotEq,
507    Lt,
508    LtEq,
509    Gt,
510    GtEq,
511}
512
513#[derive(Debug, Clone, Copy, PartialEq, Eq)]
514pub enum LogicalOp {
515    And,
516    Or,
517}