bubbles/compiler/mod.rs
1//! Compilation pipeline: source text → [`Program`].
2
3pub mod ast;
4pub mod expr;
5pub mod lexer;
6pub mod markup;
7pub(crate) mod parser;
8pub mod program;
9pub(crate) mod validate;
10
11pub use ast::{BinOp, Expr, IfBranch, LineVariant, Node, OptionItem, Stmt, TextSegment, UnOp};
12pub use lexer::{Spanned, Token, tokenise};
13pub use program::{Program, VariableDecl};
14pub use validate::validate;
15
16use crate::error::Result;
17
18/// Compiles a single `.bub` source string into a [`Program`].
19///
20/// Jump and detour targets are validated immediately; a
21/// [`crate::DialogueError::Validation`] error is returned for any reference
22/// to a node that does not exist in the compiled program.
23///
24/// For compiling multiple source files together use [`compile_many`].
25///
26/// # Errors
27/// Returns [`crate::DialogueError::Parse`] if the source is malformed,
28/// [`crate::DialogueError::DuplicateNode`] if two nodes share a title without
29/// `when:` grouping conditions, or [`crate::DialogueError::Validation`] if a
30/// jump or detour target cannot be resolved.
31pub fn compile(source: &str) -> Result<Program> {
32 compile_many(&[("<source>", source)])
33}
34
35/// Compiles multiple named `.bub` sources into a single [`Program`].
36///
37/// Sources are merged in order; duplicate node titles without `when:` grouping
38/// conditions cause a [`crate::DialogueError::DuplicateNode`] error. Jump and
39/// detour targets are validated across all sources after merging.
40///
41/// # Errors
42/// Returns a [`crate::DialogueError`] variant on any parse, merge, validation,
43/// or empty-source failure.
44pub fn compile_many(sources: &[(&str, &str)]) -> Result<Program> {
45 let mut all_nodes = Vec::new();
46 for (name, source) in sources {
47 if source.trim().is_empty() {
48 return Err(crate::error::DialogueError::Validation(format!(
49 "source is empty ({name})"
50 )));
51 }
52 let nodes = parser::parse(name, source)?;
53 all_nodes.extend(nodes);
54 }
55 let prog = Program::from_nodes(all_nodes)?;
56 if prog.node_titles().next().is_none() {
57 return Err(crate::error::DialogueError::Validation(
58 "no nodes in source".into(),
59 ));
60 }
61 validate(&prog)?;
62 Ok(prog)
63}