oak_valkyrie/parser/
parse_string_segments.rs1use crate::ast::{Expr, Identifier, Span, StringSegment};
2
3const FLUENT_MARKER: char = '\u{07DF}';
5
6pub fn parse_string_segments(content: &str, span_start: usize, is_raw: bool) -> Vec<StringSegment> {
42 if is_raw {
43 return vec![StringSegment::Text { content: content.to_string(), span: Span { start: span_start, end: span_start + content.len() } }];
44 }
45
46 let mut segments = Vec::new();
47 let mut current_text = String::new();
48 let mut text_start = span_start;
49 let mut chars = content.char_indices().peekable();
50 let content_len = content.len();
51
52 while let Some((idx, ch)) = chars.next() {
53 match ch {
54 '\\' => {
55 if let Some((_, next_ch)) = chars.peek() {
56 if *next_ch == '{' || *next_ch == '}' {
57 current_text.push(*next_ch);
58 chars.next();
59 continue;
60 }
61 }
62 current_text.push(ch);
63 }
64 '{' => {
65 if !current_text.is_empty() {
66 segments.push(StringSegment::Text { content: current_text.clone(), span: Span { start: text_start, end: span_start + idx } });
67 current_text.clear();
68 }
69
70 let is_fluent = if let Some((_, next_ch)) = chars.peek() { *next_ch == FLUENT_MARKER } else { false };
71
72 if is_fluent {
73 chars.next();
74 }
75
76 let expr_start = span_start + idx;
77 let mut expr_content = String::new();
78 let mut brace_count = 1;
79 let mut expr_end = span_start + idx + 1;
80
81 while let Some((inner_idx, inner_ch)) = chars.next() {
82 match inner_ch {
83 '{' => {
84 brace_count += 1;
85 expr_content.push(inner_ch);
86 }
87 '}' => {
88 brace_count -= 1;
89 if brace_count == 0 {
90 expr_end = span_start + inner_idx;
91 break;
92 }
93 expr_content.push(inner_ch);
94 }
95 _ => {
96 expr_content.push(inner_ch);
97 }
98 }
99 }
100
101 let trimmed_expr = expr_content.trim();
102 segments.push(StringSegment::Interpolation { expr: Box::new(Expr::Ident(Identifier { name: trimmed_expr.to_string(), span: Span { start: expr_start, end: expr_end } })), is_fluent, span: Span { start: expr_start, end: expr_end + 1 } });
103
104 text_start = expr_end + 1;
105 }
106 _ => {
107 current_text.push(ch);
108 }
109 }
110 }
111
112 if !current_text.is_empty() {
113 segments.push(StringSegment::Text { content: current_text, span: Span { start: text_start, end: span_start + content_len } });
114 }
115
116 segments
117}