Skip to main content

fabula_dsl/
ast.rs

1//! Abstract syntax tree types for the fabula DSL.
2
3/// Top-level document — contains patterns, graphs, and compose directives
4/// in declaration order (needed for name resolution).
5#[derive(Debug, Clone)]
6pub struct Document {
7    pub items: Vec<DocumentItem>,
8}
9
10impl Document {
11    /// All pattern ASTs in declaration order.
12    pub fn patterns(&self) -> Vec<&PatternAst> {
13        self.items
14            .iter()
15            .filter_map(|i| match i {
16                DocumentItem::Pattern(p) => Some(p),
17                _ => None,
18            })
19            .collect()
20    }
21
22    /// All graph ASTs in declaration order.
23    pub fn graphs(&self) -> Vec<&GraphAst> {
24        self.items
25            .iter()
26            .filter_map(|i| match i {
27                DocumentItem::Graph(g) => Some(g),
28                _ => None,
29            })
30            .collect()
31    }
32}
33
34/// A single item in a document.
35#[derive(Debug, Clone)]
36pub enum DocumentItem {
37    Pattern(PatternAst),
38    Graph(GraphAst),
39    Compose(ComposeAst),
40}
41
42/// A compose directive — builds a pattern from named sub-patterns.
43#[derive(Debug, Clone)]
44pub struct ComposeAst {
45    pub name: String,
46    pub body: ComposeBody,
47}
48
49/// The body of a compose directive.
50#[derive(Debug, Clone)]
51pub enum ComposeBody {
52    /// `A >> B sharing(x, y)`
53    Sequence {
54        left: String,
55        right: String,
56        shared: Vec<String>,
57    },
58    /// `A | B | C` (exclusive choice by default, `nonexclusive` keyword opts out)
59    Choice {
60        alternatives: Vec<String>,
61        exclusive: bool,
62    },
63    /// `A * 3 sharing(x, y)` (exact) or `A * 3..5 sharing(x, y)` (range)
64    Repeat {
65        pattern: String,
66        min: usize,
67        max: Option<usize>,
68        shared: Vec<String>,
69    },
70}
71
72/// A pattern declaration.
73#[derive(Debug, Clone)]
74pub struct PatternAst {
75    pub name: String,
76    pub stages: Vec<StageAst>,
77    pub negations: Vec<NegationAst>,
78    pub temporals: Vec<TemporalAst>,
79    pub metadata: Vec<(String, String)>,
80    pub deadline: Option<f64>,
81    /// Unordered stage groups (concurrent blocks). Each entry contains the
82    /// stage indices (into `stages`) that form a concurrent group.
83    pub unordered_groups: Vec<Vec<usize>>,
84    /// If true, this pattern was declared with `private` keyword.
85    pub private: bool,
86}
87
88/// The interior of a pattern — stages, negations, and temporal constraints,
89/// without the `pattern name { }` wrapper.
90///
91/// Used by [`crate::parser::Parser::parse_pattern_body()`] for composable
92/// parsing — downstream DSLs can parse a pattern body embedded in their own
93/// block syntax.
94#[derive(Debug, Clone)]
95pub struct PatternBody {
96    pub stages: Vec<StageAst>,
97    pub negations: Vec<NegationAst>,
98    pub temporals: Vec<TemporalAst>,
99    pub metadata: Vec<(String, String)>,
100    pub deadline: Option<f64>,
101    pub unordered_groups: Vec<Vec<usize>>,
102    pub private: bool,
103}
104
105/// A stage within a pattern.
106#[derive(Debug, Clone)]
107pub struct StageAst {
108    pub anchor: String,
109    pub clauses: Vec<ClauseAst>,
110}
111
112/// Whether a clause source is a variable reference or a literal node name.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum SourceKind {
115    /// `?var` — references a previously bound variable or the stage anchor.
116    Var,
117    /// `name` — a literal graph node name (or stage anchor, or negation scan root).
118    Literal,
119}
120
121/// A single clause in a stage or negation body.
122#[derive(Debug, Clone)]
123pub struct ClauseAst {
124    /// The source node identifier (without `?` prefix).
125    pub source: String,
126    /// Whether the source is a `?var` reference or a literal node name.
127    pub source_kind: SourceKind,
128    /// The edge label.
129    pub label: String,
130    /// What the clause matches against.
131    pub target: ClauseTarget,
132    /// Whether this clause is negated (prefixed with `!`).
133    pub negated: bool,
134}
135
136/// The right-hand side of a clause.
137#[derive(Debug, Clone)]
138pub enum ClauseTarget {
139    /// A literal string value: `= "foo"`
140    LiteralStr(String),
141    /// A literal number: `= 42` or `< 0.5`
142    LiteralNum(f64),
143    /// A literal boolean: `= true`
144    LiteralBool(bool),
145    /// Bind to a variable: `-> ?var`
146    Bind(String),
147    /// A node reference: `-> nodeName`
148    NodeRef(String),
149    /// A value constraint: `< 0.5`, `> 10`, `<= 100`, `>= 0`
150    Constraint(ConstraintOp, ConstraintValue),
151    /// A constraint comparing against a bound variable: `> ?var`, `= ?var`
152    ConstraintVar(ConstraintOp, String),
153}
154
155/// Constraint operator.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum ConstraintOp {
158    Eq,
159    Lt,
160    Gt,
161    Lte,
162    Gte,
163}
164
165/// Value in a constraint.
166#[derive(Debug, Clone)]
167pub enum ConstraintValue {
168    Num(f64),
169    Str(String),
170}
171
172/// A negation block.
173#[derive(Debug, Clone)]
174pub struct NegationAst {
175    pub kind: NegationKind,
176    pub clauses: Vec<ClauseAst>,
177}
178
179/// The type of negation.
180#[derive(Debug, Clone)]
181pub enum NegationKind {
182    /// `unless between start end { ... }`
183    Between(String, String),
184    /// `unless after start { ... }`
185    After(String),
186    /// `unless { ... }` (global)
187    Global,
188}
189
190/// An explicit temporal constraint.
191#[derive(Debug, Clone)]
192pub struct TemporalAst {
193    pub left: String,
194    pub relation: String,
195    pub right: String,
196    /// Optional metric gap lower bound.
197    pub gap_min: Option<f64>,
198    /// Optional metric gap upper bound.
199    pub gap_max: Option<f64>,
200}
201
202/// A graph declaration.
203#[derive(Debug, Clone)]
204pub struct GraphAst {
205    pub edges: Vec<EdgeAst>,
206    pub now: Option<i64>,
207}
208
209/// A single edge in a graph declaration.
210#[derive(Debug, Clone)]
211pub struct EdgeAst {
212    pub time_start: i64,
213    /// If Some, this is a bounded interval [start, end).
214    pub time_end: Option<i64>,
215    pub source: String,
216    pub label: String,
217    pub target: EdgeTarget,
218}
219
220/// The target of an edge in a graph.
221#[derive(Debug, Clone)]
222pub enum EdgeTarget {
223    /// String literal: `= "foo"`
224    Str(String),
225    /// Number literal: `= 42`
226    Num(f64),
227    /// Boolean: `= true`
228    Bool(bool),
229    /// Node reference: `-> alice`
230    NodeRef(String),
231}