Skip to main content

bubbles/compiler/
interpolation.rs

1//! Shared `{expr}` brace-scanning used by both the compile-time interpolation
2//! parser and the runtime translation-template evaluator.
3//!
4//! Both sites need to walk a raw string looking for `{…}` placeholders; this
5//! module provides one implementation that they both call.
6
7/// A segment produced by scanning `{expr}` interpolation syntax.
8#[derive(Debug, PartialEq, Eq)]
9pub enum BraceSegment<'a> {
10    /// A literal run of text with no substitution.
11    Literal(&'a str),
12    /// The source text between `{` and `}`.
13    Expr(&'a str),
14}
15
16/// Scans `text` for `{expr}` placeholder syntax, yielding segments in order.
17///
18/// Returns `Ok(Vec<BraceSegment>)` on success, or `Err(offset)` where
19/// `offset` is the byte position of an unclosed `{` so the caller can build
20/// an appropriate error message with its own file/line context.
21///
22/// # Errors
23///
24/// Returns the byte offset of the unclosed `{` when no matching `}` is found.
25pub fn scan_brace_segments(text: &str) -> Result<Vec<BraceSegment<'_>>, usize> {
26    let mut segments = Vec::new();
27    let mut remaining = text;
28    let mut consumed = 0;
29
30    while let Some(open) = remaining.find('{') {
31        if open > 0 {
32            segments.push(BraceSegment::Literal(&remaining[..open]));
33        }
34        let after = &remaining[open + 1..];
35        let close = after.find('}').ok_or(consumed + open)?;
36        segments.push(BraceSegment::Expr(&after[..close]));
37        consumed += open + 1 + close + 1;
38        remaining = &after[close + 1..];
39    }
40
41    if !remaining.is_empty() {
42        segments.push(BraceSegment::Literal(remaining));
43    }
44
45    Ok(segments)
46}
47
48#[cfg(test)]
49#[path = "interpolation_tests.rs"]
50mod tests;