1use crate::error::{AamlError, ErrorDiagnostics};
4
5#[must_use]
10pub fn strip_comment(line: &str) -> &str {
11 let mut quote_state: Option<char> = None;
12 let bytes = line.as_bytes();
13
14 for (idx, c) in line.char_indices() {
15 match (quote_state, c) {
16 (None, '#') => {
17 let preceded_by_space =
18 idx == 0 || bytes.get(idx - 1).is_some_and(u8::is_ascii_whitespace);
19 let followed_by_space = bytes.get(idx + 1).is_none_or(u8::is_ascii_whitespace);
20 if preceded_by_space && followed_by_space {
21 return &line[..idx];
22 }
23 }
24 (None, '"' | '\'') => quote_state = Some(c),
25 (Some(q), c) if c == q => quote_state = None,
26 _ => {}
27 }
28 }
29 line
30}
31
32#[cfg(feature = "legacy")]
40pub(crate) fn parse_assignment(line: &str) -> Result<(&str, &str), AamlError> {
41 let mut depth: i32 = 0;
43 let mut eq_pos: Option<usize> = None;
44 for (i, ch) in line.char_indices() {
45 match ch {
46 '{' | '[' => depth += 1,
47 '}' | ']' => depth -= 1,
48 '=' if depth == 0 => {
49 eq_pos = Some(i);
50 break;
51 }
52 _ => {}
53 }
54 }
55
56 let pos = eq_pos.ok_or_else(|| AamlError::MalformedLiteral {
57 literal_type: "assignment".to_string(),
58 content: line.to_string(),
59 diagnostics: Some(Box::new(ErrorDiagnostics::new(
60 "Missing assignment operator",
61 format!("Line '{line}' does not contain '=' separator"),
62 "Use format: key = value",
63 ))),
64 })?;
65 let key = line[..pos].trim();
66 let raw_val = line[pos + 1..].trim();
67
68 if key.is_empty() {
69 return Err(AamlError::InvalidValue {
70 details: "Key is empty".to_string(),
71 expected: "non-empty key name".to_string(),
72 diagnostics: Some(Box::new(ErrorDiagnostics::new(
73 "Empty key in assignment",
74 format!("Line '{line}' has no key before '='"),
75 "Provide a valid key name before the '=' operator",
76 ))),
77 });
78 }
79
80 validate_balanced_delimiters(raw_val, line)?;
81
82 let val = if raw_val.starts_with('{') || raw_val.starts_with('[') {
84 raw_val
85 } else {
86 unwrap_quotes(raw_val)
87 };
88
89 Ok((key, val))
90}
91
92#[cfg(feature = "legacy")]
94fn validate_balanced_delimiters(value: &str, line: &str) -> Result<(), AamlError> {
95 let mut stack: Vec<char> = Vec::new();
96 let mut quote_state: Option<char> = None;
97 let mut escaped = false;
98
99 for ch in value.chars() {
100 if let Some(q) = quote_state {
101 if escaped {
102 escaped = false;
103 continue;
104 }
105 if ch == '\\' {
106 escaped = true;
107 continue;
108 }
109 if ch == q {
110 quote_state = None;
111 }
112 continue;
113 }
114
115 match ch {
116 '"' | '\'' => quote_state = Some(ch),
117 '{' | '[' => stack.push(ch),
118 '}' if stack.pop() != Some('{') => {
119 return Err(AamlError::MalformedLiteral {
120 literal_type: "assignment".to_string(),
121 content: line.to_string(),
122 diagnostics: Some(Box::new(ErrorDiagnostics::new(
123 "Mismatched delimiters",
124 format!("Assignment '{line}' has an unmatched '}}'"),
125 "Ensure inline objects and lists use balanced braces/brackets",
126 ))),
127 });
128 }
129 ']' if stack.pop() != Some('[') => {
130 return Err(AamlError::MalformedLiteral {
131 literal_type: "assignment".to_string(),
132 content: line.to_string(),
133 diagnostics: Some(Box::new(ErrorDiagnostics::new(
134 "Mismatched delimiters",
135 format!("Assignment '{line}' has an unmatched ']'"),
136 "Ensure inline objects and lists use balanced braces/brackets",
137 ))),
138 });
139 }
140 _ => {}
141 }
142 }
143
144 if quote_state.is_some() {
145 return Err(AamlError::MalformedLiteral {
146 literal_type: "assignment".to_string(),
147 content: line.to_string(),
148 diagnostics: Some(Box::new(ErrorDiagnostics::new(
149 "Unterminated quote",
150 format!("Assignment '{line}' contains an unterminated quoted value"),
151 "Close the opening quote in the assignment value",
152 ))),
153 });
154 }
155
156 if !stack.is_empty() {
157 return Err(AamlError::MalformedLiteral {
158 literal_type: "assignment".to_string(),
159 content: line.to_string(),
160 diagnostics: Some(Box::new(ErrorDiagnostics::new(
161 "Unclosed delimiters",
162 format!("Assignment '{line}' has unclosed '{{' or '['"),
163 "Ensure inline objects and lists use balanced braces/brackets",
164 ))),
165 });
166 }
167
168 Ok(())
169}
170
171#[must_use]
175pub fn unwrap_quotes(s: &str) -> &str {
176 let s = s.trim();
177 if s.len() >= 2 {
178 if s.starts_with('"') && s.ends_with('"') {
179 return &s[1..s.len() - 1];
180 }
181 if s.starts_with('\'') && s.ends_with('\'') {
182 return &s[1..s.len() - 1];
183 }
184 }
185 s
186}
187
188#[cfg(feature = "legacy")]
191pub(super) fn needs_accumulation(text: &str) -> bool {
192 if !text.starts_with('@') {
193 return false;
194 }
195 let opens = text.chars().filter(|&c| c == '{').count();
196 let closes = text.chars().filter(|&c| c == '}').count();
197 opens > closes
198}
199
200#[cfg(feature = "legacy")]
202pub(super) fn block_is_complete(buf: &str) -> bool {
203 let opens = buf.chars().filter(|&c| c == '{').count();
204 let closes = buf.chars().filter(|&c| c == '}').count();
205 closes >= opens
206}
207
208#[must_use]
210pub fn is_inline_object(value: &str) -> bool {
211 let v = value.trim();
212 v.starts_with('{') && v.ends_with('}')
213}
214
215pub fn parse_inline_object(value: &str) -> Result<Vec<(String, String)>, AamlError> {
220 let inner = value
221 .trim()
222 .strip_prefix('{')
223 .and_then(|s| s.strip_suffix('}'))
224 .ok_or_else(|| AamlError::MalformedLiteral {
225 literal_type: "inline object".to_string(),
226 content: value.to_string(),
227 diagnostics: Some(Box::new(ErrorDiagnostics::new(
228 "Malformed inline object",
229 format!("Inline object must be wrapped in '{{}}', got: '{value}'"),
230 "Wrap your object with curly braces: { key = value }",
231 ))),
232 })?;
233
234 split_top_level_fields(inner)
235 .into_iter()
236 .map(str::trim)
237 .filter(|s| !s.is_empty())
238 .map(|entry| {
239 let (k, v) = split_field_pair(entry)?;
240 let k = k.trim();
241
242 if k.is_empty() {
243 return Err(AamlError::InvalidValue {
244 details: format!("Empty key in field '{entry}'"),
245 expected: "non-empty field name".to_string(),
246 diagnostics: Some(Box::new(ErrorDiagnostics::new(
247 "Empty field name in inline object",
248 format!("Field entry '{entry}' has no valid key"),
249 "Provide a valid key name before '=' or ':'",
250 ))),
251 });
252 }
253
254 let v = v.trim();
255 let final_v = match v.chars().next() {
256 Some('{' | '[') => v,
257 _ => unwrap_quotes(v),
258 };
259
260 Ok((k.to_string(), final_v.to_string()))
261 })
262 .collect()
263}
264
265fn split_top_level_fields(s: &str) -> Vec<&str> {
267 let mut items = Vec::new();
268 let mut depth: i32 = 0;
269 let mut start = 0;
270 let mut in_quote: Option<char> = None;
271
272 for (i, ch) in s.char_indices() {
273 match ch {
274 '"' | '\'' => match in_quote {
275 Some(q) if q == ch => in_quote = None,
276 None => in_quote = Some(ch),
277 _ => {}
278 },
279 '{' | '[' if in_quote.is_none() => depth += 1,
280 '}' | ']' if in_quote.is_none() => depth -= 1,
281 ',' if depth == 0 && in_quote.is_none() => {
282 items.push(&s[start..i]);
283 start = i + 1;
284 }
285 _ => {}
286 }
287 }
288 if start <= s.len() {
289 items.push(&s[start..]);
290 }
291
292 items
293}
294
295fn split_field_pair(entry: &str) -> Result<(&str, &str), AamlError> {
297 let mut depth: i32 = 0;
298 for (i, ch) in entry.char_indices() {
299 match ch {
300 '{' | '[' => depth += 1,
301 '}' | ']' => depth -= 1,
302 '=' | ':' if depth == 0 => return Ok((&entry[..i], &entry[i + 1..])),
303 _ => {}
304 }
305 }
306 Err(AamlError::MalformedLiteral {
307 literal_type: "field pair".to_string(),
308 content: entry.to_string(),
309 diagnostics: Some(Box::new(ErrorDiagnostics::new(
310 "Missing field separator",
311 format!("Field entry '{entry}' has no '=' or ':' separator"),
312 "Use format: key = value or key: value",
313 ))),
314 })
315}