Skip to main content

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}