rustleaf/lexer/
string_interpolation.rs

1use crate::lexer::{Lexer, Token, TokenType};
2use anyhow::Result;
3
4impl Lexer {
5    pub fn expand_string_interpolation(&self, tokens: Vec<Token>) -> Result<Vec<Token>> {
6        let mut result = Vec::new();
7
8        for token in tokens {
9            if token.token_type == TokenType::String && self.contains_interpolation(&token) {
10                result.extend(self.expand_interpolated_string(token)?);
11            } else {
12                result.push(token);
13            }
14        }
15
16        Ok(result)
17    }
18
19    fn contains_interpolation(&self, token: &Token) -> bool {
20        if let Some(text) = &token.text {
21            text.contains("${")
22        } else {
23            false
24        }
25    }
26
27    fn expand_interpolated_string(&self, token: Token) -> Result<Vec<Token>> {
28        let text = token.text.as_ref().unwrap();
29        let mut result = Vec::new();
30        let mut current_pos = 0;
31
32        while let Some(start_pos) = text[current_pos..].find("${") {
33            let actual_start = current_pos + start_pos;
34
35            // Add text part before interpolation (if any)
36            if actual_start > current_pos {
37                let text_part = &text[current_pos..actual_start];
38                result.push(Token::with_text(TokenType::StringPart, text_part));
39            }
40
41            // Find the matching closing brace
42            let expr_start = actual_start + 2; // Skip ${
43            if let Some(end_pos) = self.find_matching_brace(text, expr_start) {
44                // Add interpolation start marker
45                result.push(Token::simple(TokenType::InterpolationStart));
46
47                // Extract and tokenize the expression
48                let expr_text = &text[expr_start..end_pos];
49                let expr_tokens = Self::tokenize(expr_text)?;
50
51                // Add expression tokens (excluding EOF)
52                for expr_token in expr_tokens {
53                    if expr_token.token_type != TokenType::Eof {
54                        result.push(expr_token);
55                    }
56                }
57
58                // Add interpolation end marker
59                result.push(Token::simple(TokenType::InterpolationEnd));
60
61                current_pos = end_pos + 1; // Skip closing brace
62            } else {
63                return Err(anyhow::anyhow!(
64                    "Unclosed interpolation in string: missing '}}' after '${{' at position {}",
65                    actual_start
66                ));
67            }
68        }
69
70        // Add remaining text (if any)
71        if current_pos < text.len() {
72            let remaining = &text[current_pos..];
73            result.push(Token::with_text(TokenType::StringPart, remaining));
74        }
75
76        Ok(result)
77    }
78
79    fn find_matching_brace(&self, text: &str, start: usize) -> Option<usize> {
80        let chars: Vec<char> = text.chars().collect();
81        let mut brace_count = 1;
82        let mut in_string = false;
83        let mut escape_next = false;
84
85        for (i, &ch) in chars.iter().enumerate().skip(start) {
86            if escape_next {
87                escape_next = false;
88                continue;
89            }
90
91            match ch {
92                '\\' if in_string => escape_next = true,
93                '"' => in_string = !in_string,
94                '{' if !in_string => brace_count += 1,
95                '}' if !in_string => {
96                    brace_count -= 1;
97                    if brace_count == 0 {
98                        return Some(i);
99                    }
100                }
101                _ => {}
102            }
103        }
104
105        None // No matching brace found
106    }
107}