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