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// Transition graphs (v3)
118// ---------------------------------------------------------------------------
119
120/// A directed edge in a transition graph: `from -> to`.
121#[derive(Debug, Clone, Serialize)]
122pub struct TransitionEdge {
123    pub span: Span,
124    pub from: Ident,
125    pub to: Ident,
126}
127
128/// A transition graph block: `transitions field_name { edges..., terminal: states }`.
129#[derive(Debug, Clone, Serialize)]
130pub struct TransitionGraph {
131    pub span: Span,
132    pub field: Ident,
133    pub edges: Vec<TransitionEdge>,
134    pub terminal: Vec<Ident>,
135}
136
137// ---------------------------------------------------------------------------
138// When clauses (v3)
139// ---------------------------------------------------------------------------
140
141/// A `when` clause on a field declaration: `when status = shipped | delivered`.
142#[derive(Debug, Clone, Serialize)]
143pub struct WhenClause {
144    pub span: Span,
145    pub status_field: Ident,
146    pub qualifying_states: Vec<Ident>,
147}
148
149// ---------------------------------------------------------------------------
150// Block items — uniform representation for declaration bodies
151// ---------------------------------------------------------------------------
152
153#[derive(Debug, Clone, Serialize)]
154pub struct BlockItem {
155    pub span: Span,
156    pub kind: BlockItemKind,
157}
158
159#[derive(Debug, Clone, Serialize)]
160pub enum BlockItemKind {
161    /// `keyword: value` — when:, requires:, ensures:, facing:, etc.
162    Clause { keyword: String, value: Expr },
163    /// `name: value` — field, relationship, projection, derived value.
164    Assignment { name: Ident, value: Expr },
165    /// `name(params): value` — parameterised derived value.
166    ParamAssignment {
167        name: Ident,
168        params: Vec<Ident>,
169        value: Expr,
170    },
171    /// `let name = value`
172    Let { name: Ident, value: Expr },
173    /// Bare name inside an enum body — `pending`, `shipped`, `` `de-CH-1996` ``, etc.
174    EnumVariant { name: Ident, backtick_quoted: bool },
175    /// `for binding in collection [where filter]: ...` at block level (rule iteration)
176    ForBlock {
177        binding: ForBinding,
178        collection: Expr,
179        filter: Option<Expr>,
180        items: Vec<BlockItem>,
181    },
182    /// `if condition: ... else if ...: ... else: ...` at block level
183    IfBlock {
184        branches: Vec<CondBlockBranch>,
185        else_items: Option<Vec<BlockItem>>,
186    },
187    /// `Shard.shard_cache: value` — dot-path reverse relationship
188    PathAssignment { path: Expr, value: Expr },
189    /// `open question "text"` (nested within a block)
190    OpenQuestion { text: StringLiteral },
191    /// `contracts:` clause in a surface body
192    ContractsClause {
193        entries: Vec<ContractBinding>,
194    },
195    /// `@invariant`, `@guidance`, `@guarantee` prose annotation
196    Annotation(Annotation),
197    /// `invariant Name { expr }` inside an entity/value block
198    InvariantBlock { name: Ident, body: Expr },
199    /// `transitions field { ... }` — transition graph declaration inside an entity
200    TransitionsBlock(TransitionGraph),
201    /// `name: Type when status_field = state1 | state2` — field with lifecycle-dependent presence
202    FieldWithWhen {
203        name: Ident,
204        value: Expr,
205        when_clause: WhenClause,
206    },
207}
208
209// ---------------------------------------------------------------------------
210// Contract bindings (ALP-15)
211// ---------------------------------------------------------------------------
212
213/// Direction marker for contract bindings in surfaces.
214#[derive(Debug, Clone, Serialize)]
215pub enum ContractDirection {
216    Demands,
217    Fulfils,
218}
219
220/// A single entry in a `contracts:` clause.
221#[derive(Debug, Clone, Serialize)]
222pub struct ContractBinding {
223    pub direction: ContractDirection,
224    pub name: Ident,
225    pub span: Span,
226}
227
228// ---------------------------------------------------------------------------
229// Annotations (ALP-16)
230// ---------------------------------------------------------------------------
231
232/// Prose annotation kinds.
233#[derive(Debug, Clone, Serialize)]
234pub enum AnnotationKind {
235    Invariant,
236    Guidance,
237    Guarantee,
238}
239
240/// A prose annotation: `@invariant Name`, `@guidance`, `@guarantee Name`.
241#[derive(Debug, Clone, Serialize)]
242pub struct Annotation {
243    pub kind: AnnotationKind,
244    pub name: Option<Ident>,
245    pub body: Vec<String>,
246    pub span: Span,
247}
248
249// ---------------------------------------------------------------------------
250// Expressions
251// ---------------------------------------------------------------------------
252
253#[derive(Debug, Clone, Serialize)]
254pub enum Expr {
255    /// `identifier` or `_`
256    Ident(Ident),
257
258    /// `"text"` possibly with `{interpolation}`
259    StringLiteral(StringLiteral),
260
261    /// `` `de-CH-1996` `` — backtick-quoted enum literal
262    BacktickLiteral { span: Span, value: String },
263
264    /// `42`, `100_000`, `3.14`
265    NumberLiteral { span: Span, value: String },
266
267    /// `true`, `false`
268    BoolLiteral { span: Span, value: bool },
269
270    /// `null`
271    Null { span: Span },
272
273    /// `now`
274    Now { span: Span },
275
276    /// `this`
277    This { span: Span },
278
279    /// `within`
280    Within { span: Span },
281
282    /// `24.hours`, `7.days`
283    DurationLiteral { span: Span, value: String },
284
285    /// `{ a, b, c }` — set literal
286    SetLiteral { span: Span, elements: Vec<Expr> },
287
288    /// `{ key: value, ... }` — object literal
289    ObjectLiteral { span: Span, fields: Vec<NamedArg> },
290
291    /// `Set<T>`, `List<T>` — generic type annotation
292    GenericType {
293        span: Span,
294        name: Box<Expr>,
295        args: Vec<Expr>,
296    },
297
298    /// `a.b`
299    MemberAccess {
300        span: Span,
301        object: Box<Expr>,
302        field: Ident,
303    },
304
305    /// `a?.b`
306    OptionalAccess {
307        span: Span,
308        object: Box<Expr>,
309        field: Ident,
310    },
311
312    /// `a ?? b`
313    NullCoalesce {
314        span: Span,
315        left: Box<Expr>,
316        right: Box<Expr>,
317    },
318
319    /// `func(args)` or `entity.method(args)`
320    Call {
321        span: Span,
322        function: Box<Expr>,
323        args: Vec<CallArg>,
324    },
325
326    /// `Entity{field1, field2}` or `Entity{field: value}`
327    JoinLookup {
328        span: Span,
329        entity: Box<Expr>,
330        fields: Vec<JoinField>,
331    },
332
333    /// `a + b`, `a - b`, `a * b`, `a / b`
334    BinaryOp {
335        span: Span,
336        left: Box<Expr>,
337        op: BinaryOp,
338        right: Box<Expr>,
339    },
340
341    /// `a = b`, `a != b`, `a < b`, `a <= b`, `a > b`, `a >= b`
342    Comparison {
343        span: Span,
344        left: Box<Expr>,
345        op: ComparisonOp,
346        right: Box<Expr>,
347    },
348
349    /// `a and b`, `a or b`
350    LogicalOp {
351        span: Span,
352        left: Box<Expr>,
353        op: LogicalOp,
354        right: Box<Expr>,
355    },
356
357    /// `not expr`
358    Not { span: Span, operand: Box<Expr> },
359
360    /// `x in collection`
361    In {
362        span: Span,
363        element: Box<Expr>,
364        collection: Box<Expr>,
365    },
366
367    /// `x not in collection`
368    NotIn {
369        span: Span,
370        element: Box<Expr>,
371        collection: Box<Expr>,
372    },
373
374    /// `exists expr`
375    Exists { span: Span, operand: Box<Expr> },
376
377    /// `not exists expr`
378    NotExists { span: Span, operand: Box<Expr> },
379
380    /// `collection where condition`
381    Where {
382        span: Span,
383        source: Box<Expr>,
384        condition: Box<Expr>,
385    },
386
387    /// `collection with predicate` (in relationship declarations)
388    With {
389        span: Span,
390        source: Box<Expr>,
391        predicate: Box<Expr>,
392    },
393
394    /// `a | b` — pipe, used for inline enums and sum type discriminators
395    Pipe {
396        span: Span,
397        left: Box<Expr>,
398        right: Box<Expr>,
399    },
400
401    /// `x => body`
402    Lambda {
403        span: Span,
404        param: Box<Expr>,
405        body: Box<Expr>,
406    },
407
408    /// `if cond: a else if cond: b else: c`
409    Conditional {
410        span: Span,
411        branches: Vec<CondBranch>,
412        else_body: Option<Box<Expr>>,
413    },
414
415    /// `for x in collection [where cond]: body`
416    For {
417        span: Span,
418        binding: ForBinding,
419        collection: Box<Expr>,
420        filter: Option<Box<Expr>>,
421        body: Box<Expr>,
422    },
423
424    /// `collection where cond -> field` — projection mapping
425    ProjectionMap {
426        span: Span,
427        source: Box<Expr>,
428        field: Ident,
429    },
430
431    /// `Entity.status transitions_to state`
432    TransitionsTo {
433        span: Span,
434        subject: Box<Expr>,
435        new_state: Box<Expr>,
436    },
437
438    /// `Entity.status becomes state`
439    Becomes {
440        span: Span,
441        subject: Box<Expr>,
442        new_state: Box<Expr>,
443    },
444
445    /// `name: expr` — binding inside a clause value (when triggers, facing, context)
446    Binding {
447        span: Span,
448        name: Ident,
449        value: Box<Expr>,
450    },
451
452    /// `action when condition` — guard on a provides/related item
453    WhenGuard {
454        span: Span,
455        action: Box<Expr>,
456        condition: Box<Expr>,
457    },
458
459    /// `T?` — optional type annotation
460    TypeOptional {
461        span: Span,
462        inner: Box<Expr>,
463    },
464
465    /// `let name = value` inside an expression block (ensures, provides, etc.)
466    LetExpr {
467        span: Span,
468        name: Ident,
469        value: Box<Expr>,
470    },
471
472    /// `oauth/Session` — qualified name with module prefix
473    QualifiedName(QualifiedName),
474
475    /// A sequence of expressions from a multi-line block.
476    Block { span: Span, items: Vec<Expr> },
477
478}
479
480impl Expr {
481    pub fn span(&self) -> Span {
482        match self {
483            Expr::Ident(id) => id.span,
484            Expr::StringLiteral(s) => s.span,
485            Expr::BacktickLiteral { span, .. }
486            | Expr::NumberLiteral { span, .. }
487            | Expr::BoolLiteral { span, .. }
488            | Expr::Null { span }
489            | Expr::Now { span }
490            | Expr::This { span }
491            | Expr::Within { span }
492            | Expr::DurationLiteral { span, .. }
493            | Expr::SetLiteral { span, .. }
494            | Expr::ObjectLiteral { span, .. }
495            | Expr::GenericType { span, .. }
496            | Expr::MemberAccess { span, .. }
497            | Expr::OptionalAccess { span, .. }
498            | Expr::NullCoalesce { span, .. }
499            | Expr::Call { span, .. }
500            | Expr::JoinLookup { span, .. }
501            | Expr::BinaryOp { span, .. }
502            | Expr::Comparison { span, .. }
503            | Expr::LogicalOp { span, .. }
504            | Expr::Not { span, .. }
505            | Expr::In { span, .. }
506            | Expr::NotIn { span, .. }
507            | Expr::Exists { span, .. }
508            | Expr::NotExists { span, .. }
509            | Expr::Where { span, .. }
510            | Expr::With { span, .. }
511            | Expr::Pipe { span, .. }
512            | Expr::Lambda { span, .. }
513            | Expr::Conditional { span, .. }
514            | Expr::For { span, .. }
515            | Expr::ProjectionMap { span, .. }
516            | Expr::TransitionsTo { span, .. }
517            | Expr::Becomes { span, .. }
518            | Expr::Binding { span, .. }
519            | Expr::WhenGuard { span, .. }
520            | Expr::TypeOptional { span, .. }
521            | Expr::LetExpr { span, .. }
522            | Expr::Block { span, .. } => *span,
523            Expr::QualifiedName(q) => q.span,
524        }
525    }
526}
527
528#[derive(Debug, Clone, Serialize)]
529pub struct CondBranch {
530    pub span: Span,
531    pub condition: Expr,
532    pub body: Expr,
533}
534
535/// A branch of a block-level `if`/`else if` chain.
536#[derive(Debug, Clone, Serialize)]
537pub struct CondBlockBranch {
538    pub span: Span,
539    pub condition: Expr,
540    pub items: Vec<BlockItem>,
541}
542
543/// Binding in a `for` loop — either a single identifier or a
544/// destructured tuple like `(a, b)`.
545#[derive(Debug, Clone, Serialize)]
546pub enum ForBinding {
547    Single(Ident),
548    Destructured(Vec<Ident>, Span),
549}
550
551// ---------------------------------------------------------------------------
552// Shared types
553// ---------------------------------------------------------------------------
554
555#[derive(Debug, Clone, Serialize)]
556pub struct Ident {
557    pub span: Span,
558    pub name: String,
559}
560
561#[derive(Debug, Clone, Serialize)]
562pub struct QualifiedName {
563    pub span: Span,
564    pub qualifier: Option<String>,
565    pub name: String,
566}
567
568#[derive(Debug, Clone, Serialize)]
569pub struct StringLiteral {
570    pub span: Span,
571    pub parts: Vec<StringPart>,
572}
573
574#[derive(Debug, Clone, Serialize)]
575pub enum StringPart {
576    Text(String),
577    Interpolation(Ident),
578}
579
580#[derive(Debug, Clone, Serialize)]
581pub struct NamedArg {
582    pub span: Span,
583    pub name: Ident,
584    pub value: Expr,
585}
586
587#[derive(Debug, Clone, Serialize)]
588pub enum CallArg {
589    Positional(Expr),
590    Named(NamedArg),
591}
592
593#[derive(Debug, Clone, Serialize)]
594pub struct JoinField {
595    pub span: Span,
596    pub field: Ident,
597    /// If absent, matches a local variable with the same name.
598    pub value: Option<Expr>,
599}
600
601#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
602pub enum BinaryOp {
603    Add,
604    Sub,
605    Mul,
606    Div,
607}
608
609#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
610pub enum ComparisonOp {
611    Eq,
612    NotEq,
613    Lt,
614    LtEq,
615    Gt,
616    GtEq,
617}
618
619#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
620pub enum LogicalOp {
621    And,
622    Or,
623    Implies,
624}