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;