rustleaf/lexer/
string_interpolation.rs1use 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 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 let expr_start = actual_start + 2; if let Some(end_pos) = self.find_matching_brace(text, expr_start) {
44 result.push(Token::simple(TokenType::InterpolationStart));
46
47 let expr_text = &text[expr_start..end_pos];
49 let expr_tokens = Self::tokenize(expr_text)?;
50
51 for expr_token in expr_tokens {
53 if expr_token.token_type != TokenType::Eof {
54 result.push(expr_token);
55 }
56 }
57
58 result.push(Token::simple(TokenType::InterpolationEnd));
60
61 current_pos = end_pos + 1; } else {
63 return Err(anyhow::anyhow!(
64 "Unclosed interpolation in string: missing '}}' after '${{' at position {}",
65 actual_start
66 ));
67 }
68 }
69
70 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 }
107}