bubbles/compiler/ast.rs
1//! AST types: node/statement types and expression tree - data only, no logic.
2
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6
7/// A shared, read-only slice of [`Stmt`]s used for every body throughout the
8/// AST (node bodies, `if` branches, `once` bodies, option bodies, line groups).
9///
10/// Stored behind an [`Arc`] so the runner can push them onto the call stack
11/// without cloning the underlying statement list - frame pushes become a
12/// reference-count bump regardless of body size.
13pub type StmtList = Arc<[Stmt]>;
14
15/// A complete parsed node from a `.bub` script.
16#[derive(Debug, Clone)]
17pub struct Node {
18 /// The node title (must be unique within the program, or share a `when:` clause for groups).
19 pub title: String,
20 /// Tags declared in the `tags:` header.
21 pub tags: Vec<String>,
22 /// All other header key-value pairs, preserved verbatim (minus `title` / `tags` / `when`).
23 pub headers: IndexMap<String, String>,
24 /// Optional `when:` condition for node-group selection (parsed at compile time).
25 pub when: Option<Arc<Expr>>,
26 /// The statements making up the node body.
27 pub body: StmtList,
28}
29
30/// One branch of an `<<if>>` chain: condition AST + body statements.
31#[derive(Debug, Clone)]
32pub struct IfBranch {
33 /// Parsed condition (same as would be produced from the source string at compile time).
34 pub cond: Arc<Expr>,
35 /// Statements when this branch is taken.
36 pub body: StmtList,
37}
38
39/// A statement in a node body.
40#[derive(Debug, Clone)]
41pub enum Stmt {
42 /// A line of dialogue.
43 Line {
44 /// Optional speaker prefix (`Alice:`).
45 speaker: Option<String>,
46 /// Pre-parsed text segments (literals and `{expr}` fragments).
47 text: Vec<TextSegment>,
48 /// Trailing `#tag` metadata.
49 tags: Vec<String>,
50 },
51 /// A `<<set $var = expr>>` assignment.
52 Set {
53 /// Variable name including the `$` sigil.
54 name: String,
55 /// Parsed right-hand expression (compile-time).
56 expr: Arc<Expr>,
57 },
58 /// A conditional block.
59 If {
60 /// `if` / `elseif` branches in order.
61 branches: Vec<IfBranch>,
62 /// `else` body.
63 else_body: StmtList,
64 },
65 /// A `<<jump NodeTitle>>` statement.
66 Jump(String),
67 /// A `<<detour NodeTitle>>` statement.
68 Detour(String),
69 /// A `<<return>>` statement.
70 Return,
71 /// A `<<stop>>` statement - terminates the whole dialogue, clearing the
72 /// call stack and emitting [`crate::DialogueEvent::DialogueComplete`].
73 Stop,
74 /// A generic host command `<<name args…>>`.
75 Command {
76 /// Command name.
77 name: String,
78 /// Pre-parsed argument segments (literals and `{expr}` fragments).
79 args: Vec<TextSegment>,
80 /// Trailing `#tag` metadata.
81 tags: Vec<String>,
82 },
83 /// A `<<once>>` … `<<endonce>>` block.
84 Once {
85 /// Stable block id (line number–based), used to track seen state.
86 block_id: String,
87 /// Optional condition for `<<once if …>>` (parsed at compile time).
88 cond: Option<Arc<Expr>>,
89 /// Body that runs the first time.
90 body: StmtList,
91 /// Body that runs after the first time.
92 else_body: StmtList,
93 },
94 /// A `<<declare $var = expr>>` smart-variable declaration.
95 Declare {
96 /// Variable name.
97 name: String,
98 /// Parsed default expression.
99 expr: Arc<Expr>,
100 /// Expression source as written (for [`crate::VariableDecl`] / tooling).
101 default_src: String,
102 },
103 /// A shortcut-option block.
104 Options(Vec<OptionItem>),
105 /// A line-group block (alternatives selected by saliency).
106 LineGroup(Vec<LineVariant>),
107}
108
109/// A single shortcut option.
110#[derive(Debug, Clone)]
111pub struct OptionItem {
112 /// Stable id for once/saliency tracking.
113 pub id: String,
114 /// Pre-parsed display text (literals and `{expr}` fragments).
115 pub text: Vec<TextSegment>,
116 /// Optional guard (`-> text <<if cond>>`); `None` = always available if not `once` exhausted.
117 pub cond: Option<Arc<Expr>>,
118 /// Whether this option is a once-option.
119 pub once: bool,
120 /// Trailing tags.
121 pub tags: Vec<String>,
122 /// Indented body statements executed after selection.
123 pub body: StmtList,
124}
125
126/// A line variant inside a `=>` line-group.
127#[derive(Debug, Clone)]
128pub struct LineVariant {
129 /// Stable id.
130 pub id: String,
131 /// Optional speaker.
132 pub speaker: Option<String>,
133 /// Pre-parsed text segments (literals and `{expr}` fragments).
134 pub text: Vec<TextSegment>,
135 /// Optional guard; `None` = always considered with saliency.
136 pub cond: Option<Arc<Expr>>,
137 /// Whether this variant is a once-variant.
138 pub once: bool,
139 /// Trailing tags.
140 pub tags: Vec<String>,
141}
142
143// ── interpolated text ─────────────────────────────────────────────────────────
144
145/// One segment of text that may contain `{expr}` fragments or inline markup.
146///
147/// Line text, option text, line-variant text, and command argument strings are
148/// all stored as `Vec<TextSegment>` so that `{expr}` fragments are parsed once
149/// at compile time and evaluated cheaply at runtime. Markup open/close/self-close
150/// segments record tag boundaries without carrying any text; byte offsets into
151/// the final rendered string are computed at runtime by [`crate::runtime`].
152#[derive(Debug, Clone)]
153pub enum TextSegment {
154 /// A literal string with no interpolation.
155 Literal(String),
156 /// An `{expr}` fragment whose source has already been parsed.
157 Expr(Arc<Expr>),
158 /// An opening markup tag: `[name]` or `[name key=val …]`.
159 MarkupOpen {
160 /// Tag name, e.g. `wave` in `[wave]`.
161 name: String,
162 /// Zero or more `key=value` pairs.
163 properties: Vec<(String, String)>,
164 },
165 /// A closing markup tag: `[/name]`.
166 MarkupClose {
167 /// Tag name matched against the most recent open, e.g. `wave` in `[/wave]`.
168 name: String,
169 },
170 /// A self-closing markup tag: `[name /]` or `[name key=val … /]`.
171 MarkupSelfClose {
172 /// Tag name, e.g. `pause` in `[pause /]`.
173 name: String,
174 /// Zero or more `key=value` pairs.
175 properties: Vec<(String, String)>,
176 },
177}
178
179impl TextSegment {
180 /// Convenience: construct a literal segment from any `Into<String>`.
181 pub fn literal(s: impl Into<String>) -> Self {
182 Self::Literal(s.into())
183 }
184}
185
186// ── expression AST ────────────────────────────────────────────────────────────
187
188/// A node in the expression AST.
189#[derive(Debug, Clone, PartialEq)]
190pub enum Expr {
191 /// Numeric literal.
192 Number(f64),
193 /// String literal.
194 Text(String),
195 /// Boolean literal.
196 Bool(bool),
197 /// Variable read, e.g. `$gold`.
198 Var(String),
199 /// Function call, e.g. `random(1, 6)`.
200 Call {
201 /// Function name.
202 name: String,
203 /// Argument expressions.
204 args: Vec<Self>,
205 },
206 /// Unary operator.
207 Unary {
208 /// Operator.
209 op: UnOp,
210 /// Operand.
211 expr: Box<Self>,
212 },
213 /// Binary operator.
214 Binary {
215 /// Left operand.
216 left: Box<Self>,
217 /// Operator.
218 op: BinOp,
219 /// Right operand.
220 right: Box<Self>,
221 },
222}
223
224/// Binary operator kinds.
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum BinOp {
227 /// `+`
228 Add,
229 /// `-`
230 Sub,
231 /// `*`
232 Mul,
233 /// `/`
234 Div,
235 /// `%`
236 Rem,
237 /// `==`
238 Eq,
239 /// `!=`
240 Neq,
241 /// `<`
242 Lt,
243 /// `<=`
244 Lte,
245 /// `>`
246 Gt,
247 /// `>=`
248 Gte,
249 /// `&&`
250 And,
251 /// `||`
252 Or,
253}
254
255/// Unary operator kinds.
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257pub enum UnOp {
258 /// Arithmetic negation `-`.
259 Neg,
260 /// Logical negation `!`.
261 Not,
262}