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