/home/noah/src/ruchy/src/frontend/parser/collections.rs
Line | Count | Source |
1 | | //! Collections parsing (lists, dataframes, comprehensions, blocks, object literals) |
2 | | |
3 | | use super::{ParserState, *}; |
4 | | use crate::frontend::ast::{DataFrameColumn, Literal, ObjectField}; |
5 | | |
6 | | /// Parse a block expression or object literal |
7 | | /// |
8 | | /// Blocks are sequences of expressions enclosed in braces `{}`. This function |
9 | | /// intelligently detects whether the content represents a block of statements |
10 | | /// or an object literal based on the syntax patterns. |
11 | | /// |
12 | | /// # Examples |
13 | | /// |
14 | | /// ``` |
15 | | /// use ruchy::Parser; |
16 | | /// |
17 | | /// let input = "{ let x = 5; x + 1 }"; |
18 | | /// let mut parser = Parser::new(input); |
19 | | /// let result = parser.parse(); |
20 | | /// assert!(result.is_ok()); |
21 | | /// ``` |
22 | | /// |
23 | | /// # Errors |
24 | | /// |
25 | | /// Returns an error if: |
26 | | /// - The opening brace is missing (should be handled by caller) |
27 | | /// - Failed to parse any expression within the block |
28 | | /// - Missing closing brace |
29 | | /// - Invalid object literal syntax when detected as object |
30 | | /// # Errors |
31 | | /// |
32 | | /// Returns an error if the operation fails |
33 | | /// Parse a block expression { ... } (complexity: 7) |
34 | | /// |
35 | | /// Handles both regular blocks and let-statement conversion to let-expressions |
36 | 0 | pub fn parse_block(state: &mut ParserState) -> Result<Expr> { |
37 | 0 | let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume { |
38 | | |
39 | | // Check if this might be an object literal |
40 | 0 | if is_object_literal(state) { |
41 | 0 | return parse_object_literal_body(state, start_span); |
42 | 0 | } |
43 | | |
44 | 0 | let exprs = parse_block_expressions(state, start_span)?; |
45 | 0 | state.tokens.expect(&Token::RightBrace)?; |
46 | | |
47 | 0 | Ok(create_block_result(exprs, start_span)) |
48 | 0 | } |
49 | | |
50 | | /// Parse all expressions within a block (complexity: 8) |
51 | 0 | fn parse_block_expressions(state: &mut ParserState, start_span: Span) -> Result<Vec<Expr>> { |
52 | 0 | let mut exprs = Vec::new(); |
53 | | |
54 | 0 | while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) { |
55 | 0 | let expr = parse_next_block_expression(state, start_span)?; |
56 | 0 | exprs.push(expr); |
57 | | |
58 | 0 | consume_optional_semicolon(state); |
59 | | |
60 | 0 | if matches!(state.tokens.peek(), Some((Token::RightBrace, _))) { |
61 | 0 | break; |
62 | 0 | } |
63 | | } |
64 | | |
65 | 0 | Ok(exprs) |
66 | 0 | } |
67 | | |
68 | | /// Parse the next expression in a block, handling let statements (complexity: 9) |
69 | 0 | fn parse_next_block_expression(state: &mut ParserState, start_span: Span) -> Result<Expr> { |
70 | 0 | if matches!(state.tokens.peek(), Some((Token::Let, _))) { |
71 | 0 | parse_potential_let_statement(state, start_span) |
72 | | } else { |
73 | 0 | super::parse_expr_recursive(state) |
74 | | } |
75 | 0 | } |
76 | | |
77 | | /// Handle potential let statement with lookahead (complexity: 10) |
78 | 0 | fn parse_potential_let_statement(state: &mut ParserState, start_span: Span) -> Result<Expr> { |
79 | 0 | let saved_pos = state.tokens.position(); |
80 | 0 | state.tokens.advance(); // consume let |
81 | | |
82 | 0 | if let Some(let_info) = try_parse_let_binding(state)? { |
83 | 0 | if is_let_expression(state) { |
84 | | // Let expression - restore and parse normally |
85 | 0 | state.tokens.set_position(saved_pos); |
86 | 0 | super::parse_expr_recursive(state) |
87 | | } else { |
88 | | // Let statement - convert to let expression |
89 | 0 | create_let_statement_expression(state, let_info, start_span) |
90 | | } |
91 | | } else { |
92 | | // Not a valid let - restore and parse as expression |
93 | 0 | state.tokens.set_position(saved_pos); |
94 | 0 | super::parse_expr_recursive(state) |
95 | | } |
96 | 0 | } |
97 | | |
98 | | /// Try to parse let binding info (complexity: 6) |
99 | 0 | fn try_parse_let_binding(state: &mut ParserState) -> Result<Option<LetBindingInfo>> { |
100 | 0 | if let Some((Token::Identifier(name), _)) = state.tokens.peek() { |
101 | 0 | let name = name.clone(); |
102 | 0 | state.tokens.advance(); |
103 | | |
104 | 0 | if matches!(state.tokens.peek(), Some((Token::Equal, _))) { |
105 | 0 | state.tokens.advance(); // consume = |
106 | 0 | let value = super::parse_expr_recursive(state)?; |
107 | 0 | return Ok(Some(LetBindingInfo { name, value })); |
108 | 0 | } |
109 | 0 | } |
110 | 0 | Ok(None) |
111 | 0 | } |
112 | | |
113 | | /// Check if this is a let expression (has 'in' keyword) (complexity: 2) |
114 | 0 | fn is_let_expression(state: &mut ParserState) -> bool { |
115 | 0 | matches!(state.tokens.peek(), Some((Token::In, _))) |
116 | 0 | } |
117 | | |
118 | | /// Create let statement expression from binding info (complexity: 8) |
119 | 0 | fn create_let_statement_expression(state: &mut ParserState, let_info: LetBindingInfo, start_span: Span) -> Result<Expr> { |
120 | 0 | consume_optional_semicolon(state); |
121 | | |
122 | 0 | let body = parse_remaining_block_body(state, start_span)?; |
123 | | |
124 | 0 | Ok(Expr::new( |
125 | 0 | ExprKind::Let { |
126 | 0 | name: let_info.name, |
127 | 0 | type_annotation: None, |
128 | 0 | value: Box::new(let_info.value), |
129 | 0 | body: Box::new(body), |
130 | 0 | is_mutable: false, |
131 | 0 | }, |
132 | 0 | start_span, |
133 | 0 | )) |
134 | 0 | } |
135 | | |
136 | | /// Parse remaining expressions as block body (complexity: 8) |
137 | 0 | fn parse_remaining_block_body(state: &mut ParserState, start_span: Span) -> Result<Expr> { |
138 | 0 | let mut body_exprs = Vec::new(); |
139 | | |
140 | 0 | while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) { |
141 | 0 | body_exprs.push(super::parse_expr_recursive(state)?); |
142 | | |
143 | 0 | consume_optional_semicolon(state); |
144 | | |
145 | 0 | if matches!(state.tokens.peek(), Some((Token::RightBrace, _))) { |
146 | 0 | break; |
147 | 0 | } |
148 | | } |
149 | | |
150 | 0 | Ok(create_body_expression(body_exprs, start_span)) |
151 | 0 | } |
152 | | |
153 | | /// Create body expression from parsed expressions (complexity: 4) |
154 | 0 | fn create_body_expression(body_exprs: Vec<Expr>, start_span: Span) -> Expr { |
155 | 0 | if body_exprs.is_empty() { |
156 | 0 | Expr::new(ExprKind::Literal(Literal::Unit), start_span) |
157 | 0 | } else if body_exprs.len() == 1 { |
158 | 0 | body_exprs.into_iter().next().expect("checked: len == 1") |
159 | | } else { |
160 | 0 | Expr::new(ExprKind::Block(body_exprs), start_span) |
161 | | } |
162 | 0 | } |
163 | | |
164 | | /// Create final block result (complexity: 3) |
165 | 0 | fn create_block_result(exprs: Vec<Expr>, start_span: Span) -> Expr { |
166 | 0 | if exprs.is_empty() { |
167 | 0 | Expr::new(ExprKind::Literal(Literal::Unit), start_span) |
168 | | } else { |
169 | 0 | Expr::new(ExprKind::Block(exprs), start_span) |
170 | | } |
171 | 0 | } |
172 | | |
173 | | /// Consume optional semicolon (complexity: 2) |
174 | 0 | fn consume_optional_semicolon(state: &mut ParserState) { |
175 | 0 | if matches!(state.tokens.peek(), Some((Token::Semicolon, _))) { |
176 | 0 | state.tokens.advance(); |
177 | 0 | } |
178 | 0 | } |
179 | | |
180 | | /// Information about a let binding (complexity: 1) |
181 | | #[derive(Debug, Clone)] |
182 | | struct LetBindingInfo { |
183 | | name: String, |
184 | | value: Expr, |
185 | | } |
186 | | |
187 | | /// Check if the current position looks like an object literal |
188 | | /// |
189 | | /// Analyzes the upcoming tokens to determine if the content should be parsed |
190 | | /// as an object literal rather than a regular block. Object literals have |
191 | | /// specific patterns like `key: value` pairs or spread operators `...expr`. |
192 | | /// |
193 | | /// # Examples |
194 | | /// |
195 | | /// Returns `true` for patterns like: |
196 | | /// - `{ name: "John" }` |
197 | | /// - `{ ...other }` |
198 | | /// - `{ "key": value }` |
199 | | /// |
200 | | /// Returns `false` for: |
201 | | /// - `{ x + 1 }` |
202 | | /// - `{ let x = 5 }` |
203 | | /// - `{ }` |
204 | | /// |
205 | | /// # Errors |
206 | | /// |
207 | | /// Returns an error if token stream operations fail during lookahead. |
208 | 0 | fn is_object_literal(state: &mut ParserState) -> bool { |
209 | | // Peek at the next few tokens to determine if this is an object literal |
210 | | // Object literal patterns: |
211 | | // 1. { key: value, ... } |
212 | | // 2. { ...spread } |
213 | | // 3. { } (empty) |
214 | | |
215 | | // Empty braces could be either - default to block |
216 | 0 | if matches!(state.tokens.peek(), Some((Token::RightBrace, _))) { |
217 | 0 | return false; |
218 | 0 | } |
219 | | |
220 | | // Check for spread operator |
221 | 0 | if matches!(state.tokens.peek(), Some((Token::DotDotDot, _))) { |
222 | 0 | return true; |
223 | 0 | } |
224 | | |
225 | | // Check for identifier/string followed by colon or fat arrow (book compatibility) |
226 | 0 | match state.tokens.peek() { |
227 | | Some((Token::Identifier(_) | Token::String(_) | Token::RawString(_), _)) => { |
228 | | // Look ahead for colon or fat arrow |
229 | 0 | let saved_pos = state.tokens.position(); |
230 | 0 | state.tokens.advance(); // skip identifier/string |
231 | 0 | let has_separator = matches!( |
232 | 0 | state.tokens.peek(), |
233 | | Some((Token::Colon | Token::FatArrow, _)) |
234 | | ); |
235 | 0 | state.tokens.set_position(saved_pos); // restore position |
236 | 0 | has_separator |
237 | | } |
238 | 0 | _ => false, |
239 | | } |
240 | 0 | } |
241 | | |
242 | | /// Parse an object key, handling identifiers, strings, and reserved words |
243 | | /// Complexity: 8 (extracted from `parse_object_literal_body`) |
244 | 0 | fn parse_object_key(state: &mut ParserState) -> Result<String> { |
245 | 0 | if let Some((token, _)) = state.tokens.peek() { |
246 | 0 | let key = token_to_object_key(token)?; |
247 | 0 | state.tokens.advance(); |
248 | 0 | Ok(key) |
249 | | } else { |
250 | 0 | bail!("Expected key in object literal") |
251 | | } |
252 | 0 | } |
253 | | |
254 | | /// Convert a token to an object key string |
255 | | /// Complexity: 7 (simple match statement) |
256 | 0 | fn token_to_object_key(token: &Token) -> Result<String> { |
257 | 0 | match token { |
258 | 0 | Token::Identifier(name) => Ok(name.clone()), |
259 | 0 | Token::String(s) | Token::RawString(s) => Ok(s.clone()), |
260 | | // Allow reserved words as object keys |
261 | 0 | Token::Command => Ok("command".to_string()), |
262 | 0 | Token::Type => Ok("type".to_string()), |
263 | 0 | Token::Module => Ok("module".to_string()), |
264 | 0 | Token::Import => Ok("import".to_string()), |
265 | 0 | Token::Export => Ok("export".to_string()), |
266 | 0 | Token::Fun => Ok("fun".to_string()), |
267 | 0 | Token::Fn => Ok("fn".to_string()), |
268 | 0 | Token::Return => Ok("return".to_string()), |
269 | 0 | Token::If => Ok("if".to_string()), |
270 | 0 | Token::Else => Ok("else".to_string()), |
271 | 0 | Token::For => Ok("for".to_string()), |
272 | 0 | Token::While => Ok("while".to_string()), |
273 | 0 | Token::Loop => Ok("loop".to_string()), |
274 | 0 | Token::Match => Ok("match".to_string()), |
275 | 0 | Token::Let => Ok("let".to_string()), |
276 | 0 | Token::Var => Ok("var".to_string()), |
277 | 0 | Token::Const => Ok("const".to_string()), |
278 | 0 | Token::Static => Ok("static".to_string()), |
279 | 0 | Token::Pub => Ok("pub".to_string()), |
280 | 0 | Token::Mut => Ok("mut".to_string()), |
281 | 0 | Token::Struct => Ok("struct".to_string()), |
282 | 0 | Token::Enum => Ok("enum".to_string()), |
283 | 0 | Token::Impl => Ok("impl".to_string()), |
284 | 0 | Token::Trait => Ok("trait".to_string()), |
285 | 0 | Token::Use => Ok("use".to_string()), |
286 | 0 | Token::As => Ok("as".to_string()), |
287 | 0 | Token::In => Ok("in".to_string()), |
288 | 0 | Token::Where => Ok("where".to_string()), |
289 | 0 | Token::Async => Ok("async".to_string()), |
290 | 0 | Token::Await => Ok("await".to_string()), |
291 | 0 | Token::Try => Ok("try".to_string()), |
292 | 0 | Token::Catch => Ok("catch".to_string()), |
293 | 0 | Token::Throw => Ok("throw".to_string()), |
294 | 0 | Token::Break => Ok("break".to_string()), |
295 | 0 | Token::Continue => Ok("continue".to_string()), |
296 | 0 | Token::State => Ok("state".to_string()), |
297 | 0 | _ => bail!("Expected identifier or string key in object literal"), |
298 | | } |
299 | 0 | } |
300 | | |
301 | | /// Parse the body of an object literal after the opening brace |
302 | | /// |
303 | | /// Parses the contents of an object literal including key-value pairs and |
304 | | /// spread expressions. Handles both string and identifier keys. |
305 | | /// |
306 | | /// # Examples |
307 | | /// |
308 | | /// ``` |
309 | | /// use ruchy::Parser; |
310 | | /// |
311 | | /// let input = r#"{ name: "John", age: 30 }"#; |
312 | | /// let mut parser = Parser::new(input); |
313 | | /// let result = parser.parse(); |
314 | | /// assert!(result.is_ok()); |
315 | | /// ``` |
316 | | /// |
317 | | /// # Errors |
318 | | /// |
319 | | /// Returns an error if: |
320 | | /// - Invalid key type (neither identifier nor string) |
321 | | /// - Missing colon after key |
322 | | /// - Failed to parse value expression |
323 | | /// - Missing comma between fields |
324 | | /// - Missing closing brace |
325 | 0 | fn parse_object_literal_body(state: &mut ParserState, start_span: Span) -> Result<Expr> { |
326 | 0 | let mut fields = Vec::new(); |
327 | | |
328 | 0 | while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) { |
329 | 0 | parse_single_object_field(state, &mut fields)?; |
330 | 0 | handle_object_field_separator(state)?; |
331 | | } |
332 | | |
333 | 0 | state.tokens.expect(&Token::RightBrace)?; |
334 | 0 | Ok(Expr::new(ExprKind::ObjectLiteral { fields }, start_span)) |
335 | 0 | } |
336 | | |
337 | | /// Parse a single object field (either spread or key-value) - complexity: 6 |
338 | 0 | fn parse_single_object_field(state: &mut ParserState, fields: &mut Vec<ObjectField>) -> Result<()> { |
339 | 0 | if matches!(state.tokens.peek(), Some((Token::DotDotDot, _))) { |
340 | 0 | parse_object_spread_field(state, fields) |
341 | | } else { |
342 | 0 | parse_object_key_value_field(state, fields) |
343 | | } |
344 | 0 | } |
345 | | |
346 | | /// Parse object spread field (...expr) - complexity: 3 |
347 | 0 | fn parse_object_spread_field(state: &mut ParserState, fields: &mut Vec<ObjectField>) -> Result<()> { |
348 | 0 | state.tokens.advance(); // consume ... |
349 | 0 | let expr = super::parse_expr_recursive(state)?; |
350 | 0 | fields.push(ObjectField::Spread { expr }); |
351 | 0 | Ok(()) |
352 | 0 | } |
353 | | |
354 | | /// Parse object key-value field (key: value or key => value) - complexity: 5 |
355 | 0 | fn parse_object_key_value_field(state: &mut ParserState, fields: &mut Vec<ObjectField>) -> Result<()> { |
356 | 0 | let key = parse_object_key(state)?; |
357 | | |
358 | | // Accept either : or => for object key-value pairs (book compatibility) |
359 | 0 | if matches!(state.tokens.peek(), Some((Token::FatArrow, _))) { |
360 | 0 | state.tokens.advance(); // consume => |
361 | 0 | } else { |
362 | 0 | state.tokens.expect(&Token::Colon)?; |
363 | | } |
364 | | |
365 | 0 | let value = super::parse_expr_recursive(state)?; |
366 | 0 | fields.push(ObjectField::KeyValue { key, value }); |
367 | 0 | Ok(()) |
368 | 0 | } |
369 | | |
370 | | /// Handle comma separator between object fields - complexity: 4 |
371 | 0 | fn handle_object_field_separator(state: &mut ParserState) -> Result<()> { |
372 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
373 | 0 | state.tokens.advance(); |
374 | 0 | Ok(()) |
375 | 0 | } else if !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) { |
376 | 0 | bail!("Expected comma or closing brace in object literal") |
377 | | } else { |
378 | 0 | Ok(()) |
379 | | } |
380 | 0 | } |
381 | | |
382 | | /// Parse a list expression or list comprehension |
383 | | /// |
384 | | /// Parses list literals enclosed in brackets `[]`. Automatically detects |
385 | | /// list comprehensions when the `for` keyword is encountered after the |
386 | | /// first element. |
387 | | /// |
388 | | /// # Examples |
389 | | /// |
390 | | /// ``` |
391 | | /// use ruchy::Parser; |
392 | | /// |
393 | | /// let input = "[1, 2, 3]"; |
394 | | /// let mut parser = Parser::new(input); |
395 | | /// let result = parser.parse(); |
396 | | /// assert!(result.is_ok()); |
397 | | /// ``` |
398 | | /// |
399 | | /// # Errors |
400 | | /// |
401 | | /// Returns an error if: |
402 | | /// - Failed to parse any element expression |
403 | | /// - Missing closing bracket |
404 | | /// - Invalid list comprehension syntax |
405 | | /// - Malformed comma-separated elements |
406 | | /// # Errors |
407 | | /// |
408 | | /// Returns an error if the operation fails |
409 | 0 | pub fn parse_list(state: &mut ParserState) -> Result<Expr> { |
410 | 0 | let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume [ |
411 | | |
412 | | // Check for empty list |
413 | 0 | if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) { |
414 | 0 | state.tokens.advance(); // consume ] |
415 | 0 | return Ok(Expr::new(ExprKind::List(Vec::new()), start_span)); |
416 | 0 | } |
417 | | |
418 | | // Parse the first element (checking for spread syntax) |
419 | 0 | let first_element = parse_list_element(state)?; |
420 | | |
421 | | // Check if this is a list comprehension by looking for 'for' |
422 | 0 | if matches!(state.tokens.peek(), Some((Token::For, _))) { |
423 | 0 | return parse_list_comprehension(state, start_span, first_element); |
424 | 0 | } |
425 | | |
426 | | // Regular list - continue parsing elements |
427 | 0 | let mut elements = vec![first_element]; |
428 | | |
429 | 0 | while matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
430 | 0 | state.tokens.advance(); // consume comma |
431 | | |
432 | 0 | if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) { |
433 | 0 | break; // trailing comma |
434 | 0 | } |
435 | | |
436 | 0 | elements.push(parse_list_element(state)?); |
437 | | } |
438 | | |
439 | 0 | state.tokens.expect(&Token::RightBracket)?; |
440 | | |
441 | 0 | Ok(Expr::new(ExprKind::List(elements), start_span)) |
442 | 0 | } |
443 | | |
444 | | /// Parse a single list element, handling both regular expressions and spread syntax |
445 | 0 | fn parse_list_element(state: &mut ParserState) -> Result<Expr> { |
446 | | // Check for spread syntax (...) |
447 | 0 | if matches!(state.tokens.peek(), Some((Token::DotDotDot, _))) { |
448 | 0 | let start_pos = state.tokens.advance().expect("checked above").1.start; // consume ... |
449 | 0 | let expr = super::parse_expr_recursive(state)?; |
450 | 0 | let span = Span { |
451 | 0 | start: start_pos, |
452 | 0 | end: expr.span.end |
453 | 0 | }; |
454 | 0 | Ok(Expr::new(ExprKind::Spread { expr: Box::new(expr) }, span)) |
455 | | } else { |
456 | | // Regular element |
457 | 0 | super::parse_expr_recursive(state) |
458 | | } |
459 | 0 | } |
460 | | |
461 | | /// Parse a list comprehension after the element expression |
462 | | /// |
463 | | /// Parses the remaining parts of a list comprehension: the `for` clause, |
464 | | /// variable binding, iterable expression, and optional `if` condition. |
465 | | /// |
466 | | /// # Examples |
467 | | /// |
468 | | /// ``` |
469 | | /// use ruchy::Parser; |
470 | | /// |
471 | | /// let input = "[x * 2 for x in range(10)]"; |
472 | | /// let mut parser = Parser::new(input); |
473 | | /// let result = parser.parse(); |
474 | | /// assert!(result.is_ok()); |
475 | | /// ``` |
476 | | /// |
477 | | /// # Errors |
478 | | /// |
479 | | /// Returns an error if: |
480 | | /// - Missing `for` keyword |
481 | | /// - Invalid variable name |
482 | | /// - Missing `in` keyword |
483 | | /// - Failed to parse iterable expression |
484 | | /// - Failed to parse condition expression (when present) |
485 | | /// - Missing closing bracket |
486 | | /// |
487 | | /// Parse a condition expression for list comprehension that stops at ] |
488 | 0 | fn parse_condition_expr(state: &mut ParserState) -> Result<Expr> { |
489 | | // Save the current position in case we need to backtrack |
490 | 0 | let _start_pos = state.tokens.position(); |
491 | | |
492 | | // Try to parse an expression, but we need to be careful about ] |
493 | | // We'll parse terms and operators manually to avoid consuming ] |
494 | 0 | let mut left = parse_condition_term(state)?; |
495 | | |
496 | | // Check for comparison operators |
497 | 0 | while let Some((token, _)) = state.tokens.peek() { |
498 | 0 | match token { |
499 | | Token::Greater |
500 | | | Token::Less |
501 | | | Token::GreaterEqual |
502 | | | Token::LessEqual |
503 | | | Token::EqualEqual |
504 | | | Token::NotEqual |
505 | | | Token::AndAnd |
506 | | | Token::OrOr => { |
507 | 0 | let op = expressions::token_to_binary_op(token).expect("checked: valid op"); |
508 | 0 | state.tokens.advance(); // consume operator |
509 | 0 | let right = parse_condition_term(state)?; |
510 | 0 | left = Expr::new( |
511 | 0 | ExprKind::Binary { |
512 | 0 | left: Box::new(left), |
513 | 0 | op, |
514 | 0 | right: Box::new(right), |
515 | 0 | }, |
516 | 0 | Span { start: 0, end: 0 }, |
517 | | ); |
518 | | } |
519 | 0 | _ => break, // Stop at closing bracket or any other token |
520 | | } |
521 | | } |
522 | | |
523 | 0 | Ok(left) |
524 | 0 | } |
525 | | |
526 | | /// Parse a single term in a condition expression |
527 | 0 | fn parse_condition_term(state: &mut ParserState) -> Result<Expr> { |
528 | | // Parse a primary expression (identifier, literal, call, etc.) |
529 | 0 | let mut expr = expressions::parse_prefix(state)?; |
530 | | |
531 | | // Handle postfix operations like method calls and field access |
532 | 0 | while let Some((token, _)) = state.tokens.peek() { |
533 | 0 | expr = match token { |
534 | 0 | Token::Dot => parse_dot_operation(state, expr)?, |
535 | 0 | Token::LeftParen => functions::parse_call(state, expr)?, |
536 | 0 | _ => break, // Stop at other tokens |
537 | | }; |
538 | | } |
539 | | |
540 | 0 | Ok(expr) |
541 | 0 | } |
542 | | |
543 | | /// Parse dot operation (field access or method call) - complexity: 8 |
544 | 0 | fn parse_dot_operation(state: &mut ParserState, expr: Expr) -> Result<Expr> { |
545 | 0 | state.tokens.advance(); // consume . |
546 | | |
547 | 0 | let Some((Token::Identifier(name), _)) = state.tokens.peek() else { |
548 | 0 | return Ok(expr); // No identifier after dot |
549 | | }; |
550 | | |
551 | 0 | let name = name.clone(); |
552 | 0 | state.tokens.advance(); |
553 | | |
554 | | // Check if it's a method call or field access |
555 | 0 | if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) { |
556 | 0 | parse_method_call(state, expr, name) |
557 | | } else { |
558 | 0 | Ok(create_field_access(expr, name)) |
559 | | } |
560 | 0 | } |
561 | | |
562 | | /// Parse method call arguments (complexity: 5) |
563 | 0 | fn parse_method_call(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> { |
564 | 0 | state.tokens.advance(); // consume ( |
565 | | |
566 | 0 | let args = parse_method_arguments(state)?; |
567 | | |
568 | 0 | state.tokens.expect(&Token::RightParen)?; |
569 | | |
570 | 0 | Ok(Expr::new( |
571 | 0 | ExprKind::MethodCall { |
572 | 0 | receiver: Box::new(receiver), |
573 | 0 | method, |
574 | 0 | args, |
575 | 0 | }, |
576 | 0 | Span { start: 0, end: 0 }, |
577 | 0 | )) |
578 | 0 | } |
579 | | |
580 | | /// Parse method call arguments (complexity: 4) |
581 | 0 | fn parse_method_arguments(state: &mut ParserState) -> Result<Vec<Expr>> { |
582 | 0 | let mut args = Vec::new(); |
583 | | |
584 | 0 | while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) { |
585 | 0 | args.push(super::parse_expr_recursive(state)?); |
586 | | |
587 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
588 | 0 | state.tokens.advance(); |
589 | 0 | } else { |
590 | 0 | break; |
591 | | } |
592 | | } |
593 | | |
594 | 0 | Ok(args) |
595 | 0 | } |
596 | | |
597 | | /// Create field access expression (complexity: 1) |
598 | 0 | fn create_field_access(object: Expr, field: String) -> Expr { |
599 | 0 | Expr::new( |
600 | 0 | ExprKind::FieldAccess { |
601 | 0 | object: Box::new(object), |
602 | 0 | field, |
603 | 0 | }, |
604 | 0 | Span { start: 0, end: 0 }, |
605 | | ) |
606 | 0 | } |
607 | | |
608 | 0 | pub fn parse_list_comprehension( |
609 | 0 | state: &mut ParserState, |
610 | 0 | start_span: Span, |
611 | 0 | element: Expr, |
612 | 0 | ) -> Result<Expr> { |
613 | | // We've already parsed the element expression |
614 | | // Now expect: for variable in iterable [if condition] |
615 | | |
616 | 0 | state.tokens.expect(&Token::For)?; |
617 | | |
618 | | // Parse variable name |
619 | 0 | let variable = if let Some((Token::Identifier(name), _)) = state.tokens.peek() { |
620 | 0 | let name = name.clone(); |
621 | 0 | state.tokens.advance(); |
622 | 0 | name |
623 | | } else { |
624 | 0 | bail!("Expected variable name in list comprehension"); |
625 | | }; |
626 | | |
627 | 0 | state.tokens.expect(&Token::In)?; |
628 | | |
629 | | // Parse iterable expression |
630 | 0 | let iterable = super::parse_expr_recursive(state)?; |
631 | | |
632 | | // Check for optional if condition |
633 | 0 | let condition = if matches!(state.tokens.peek(), Some((Token::If, _))) { |
634 | 0 | state.tokens.advance(); // consume 'if' |
635 | | // Parse condition expression - this needs to stop at the closing bracket |
636 | | // We'll parse a simple expression that stops at ] |
637 | 0 | let cond = parse_condition_expr(state)?; |
638 | 0 | Some(Box::new(cond)) |
639 | | } else { |
640 | 0 | None |
641 | | }; |
642 | | |
643 | 0 | state.tokens.expect(&Token::RightBracket)?; |
644 | | |
645 | 0 | Ok(Expr::new( |
646 | 0 | ExprKind::ListComprehension { |
647 | 0 | element: Box::new(element), |
648 | 0 | variable, |
649 | 0 | iterable: Box::new(iterable), |
650 | 0 | condition, |
651 | 0 | }, |
652 | 0 | start_span, |
653 | 0 | )) |
654 | 0 | } |
655 | | |
656 | | /// Parse a `DataFrame` literal expression |
657 | | /// |
658 | | /// Parses `DataFrame` literals with column headers and data rows. The first |
659 | | /// row defines column names, subsequent rows contain data values. |
660 | | /// |
661 | | /// # Examples |
662 | | /// |
663 | | /// ``` |
664 | | /// use ruchy::Parser; |
665 | | /// |
666 | | /// let input = r#"df![name => ["Alice", "Bob"], age => [30, 25]]"#; |
667 | | /// let mut parser = Parser::new(input); |
668 | | /// let result = parser.parse(); |
669 | | /// assert!(result.is_ok()); |
670 | | /// ``` |
671 | | /// |
672 | | /// # Errors |
673 | | /// |
674 | | /// Returns an error if: |
675 | | /// - Missing opening brace after `DataFrame` |
676 | | /// - Invalid column name (must be identifier) |
677 | | /// - Missing semicolon between rows |
678 | | /// - Failed to parse data value expressions |
679 | | /// - Missing closing brace |
680 | | /// - Inconsistent number of values per row |
681 | | /// # Errors |
682 | | /// |
683 | | /// Returns an error if the operation fails |
684 | | /// Parse `DataFrame` header: df![ (complexity: 3) |
685 | 0 | fn parse_dataframe_header(state: &mut ParserState) -> Result<Span> { |
686 | 0 | let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume df |
687 | 0 | state.tokens.expect(&Token::Bang)?; |
688 | 0 | state.tokens.expect(&Token::LeftBracket)?; |
689 | 0 | Ok(start_span) |
690 | 0 | } |
691 | | |
692 | | /// Parse column name identifier (complexity: 3) |
693 | 0 | fn parse_dataframe_column_name(state: &mut ParserState) -> Result<String> { |
694 | 0 | if let Some((Token::Identifier(name), _)) = state.tokens.peek() { |
695 | 0 | let name = name.clone(); |
696 | 0 | state.tokens.advance(); |
697 | 0 | Ok(name) |
698 | | } else { |
699 | 0 | bail!("Expected column name in DataFrame literal"); |
700 | | } |
701 | 0 | } |
702 | | |
703 | | /// Parse column values after => (complexity: 4) |
704 | 0 | fn parse_dataframe_column_values(state: &mut ParserState) -> Result<Vec<Expr>> { |
705 | 0 | state.tokens.expect(&Token::FatArrow)?; // consume => |
706 | | |
707 | 0 | let values = if matches!(state.tokens.peek(), Some((Token::LeftBracket, _))) { |
708 | | // Values are in a list |
709 | 0 | parse_list(state)? |
710 | | } else { |
711 | | // Parse individual expression |
712 | 0 | super::parse_expr_recursive(state)? |
713 | | }; |
714 | | |
715 | | // Convert to vector of expressions |
716 | 0 | let value_vec = match values.kind { |
717 | 0 | ExprKind::List(exprs) => exprs, |
718 | 0 | _ => vec![values], |
719 | | }; |
720 | | |
721 | 0 | Ok(value_vec) |
722 | 0 | } |
723 | | |
724 | | /// Handle legacy syntax column (complexity: 3) |
725 | 0 | fn handle_dataframe_legacy_syntax_column(col_name: String) -> DataFrameColumn { |
726 | | // Legacy syntax: just column names, then semicolon and rows |
727 | | // For backward compatibility, create empty column for now |
728 | 0 | DataFrameColumn { |
729 | 0 | name: col_name, |
730 | 0 | values: Vec::new(), |
731 | 0 | } |
732 | 0 | } |
733 | | |
734 | | /// Parse column definitions loop (complexity: 6) |
735 | 0 | fn parse_dataframe_column_definitions(state: &mut ParserState) -> Result<Vec<DataFrameColumn>> { |
736 | 0 | let mut columns = Vec::new(); |
737 | | |
738 | | loop { |
739 | 0 | let col_name = parse_dataframe_column_name(state)?; |
740 | 0 | parse_single_dataframe_column(state, col_name, &mut columns)?; |
741 | | |
742 | 0 | if !handle_dataframe_column_continuation(state, &mut columns)? { |
743 | 0 | break; |
744 | 0 | } |
745 | | } |
746 | | |
747 | 0 | Ok(columns) |
748 | 0 | } |
749 | | |
750 | | /// Parse a single `DataFrame` column (either new or legacy syntax) - complexity: 5 |
751 | 0 | fn parse_single_dataframe_column( |
752 | 0 | state: &mut ParserState, |
753 | 0 | col_name: String, |
754 | 0 | columns: &mut Vec<DataFrameColumn> |
755 | 0 | ) -> Result<()> { |
756 | 0 | if matches!(state.tokens.peek(), Some((Token::FatArrow, _))) { |
757 | | // New syntax: col => [values] |
758 | 0 | let values = parse_dataframe_column_values(state)?; |
759 | 0 | columns.push(DataFrameColumn { |
760 | 0 | name: col_name, |
761 | 0 | values, |
762 | 0 | }); |
763 | 0 | } else if is_dataframe_legacy_syntax_token(state) { |
764 | 0 | columns.push(handle_dataframe_legacy_syntax_column(col_name)); |
765 | 0 | } else { |
766 | 0 | bail!("Expected '=>' or ',' after column name in DataFrame literal"); |
767 | | } |
768 | 0 | Ok(()) |
769 | 0 | } |
770 | | |
771 | | /// Check if current token indicates legacy `DataFrame` syntax - complexity: 4 |
772 | 0 | fn is_dataframe_legacy_syntax_token(state: &mut ParserState) -> bool { |
773 | 0 | matches!(state.tokens.peek(), Some((Token::Comma, _))) |
774 | 0 | || matches!(state.tokens.peek(), Some((Token::Semicolon, _))) |
775 | 0 | || matches!(state.tokens.peek(), Some((Token::RightBracket, _))) |
776 | 0 | } |
777 | | |
778 | | /// Handle `DataFrame` column continuation tokens - complexity: 5 |
779 | 0 | fn handle_dataframe_column_continuation( |
780 | 0 | state: &mut ParserState, |
781 | 0 | columns: &mut Vec<DataFrameColumn> |
782 | 0 | ) -> Result<bool> { |
783 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
784 | 0 | state.tokens.advance(); |
785 | 0 | Ok(true) |
786 | 0 | } else if matches!(state.tokens.peek(), Some((Token::Semicolon, _))) { |
787 | | // Legacy row-based syntax |
788 | 0 | state.tokens.advance(); |
789 | 0 | parse_legacy_dataframe_rows(state, columns)?; |
790 | 0 | Ok(false) |
791 | | } else { |
792 | 0 | Ok(false) |
793 | | } |
794 | 0 | } |
795 | | |
796 | | /// Create final `DataFrame` expression (complexity: 3) |
797 | 0 | fn create_dataframe_result(columns: Vec<DataFrameColumn>, start_span: Span) -> Result<Expr> { |
798 | 0 | Ok(Expr::new(ExprKind::DataFrame { columns }, start_span)) |
799 | 0 | } |
800 | | |
801 | | /// Parse `DataFrame` literal: df![...] (complexity: 6) |
802 | 0 | pub fn parse_dataframe(state: &mut ParserState) -> Result<Expr> { |
803 | 0 | let start_span = parse_dataframe_header(state)?; |
804 | | |
805 | | // Check for empty DataFrame df![] |
806 | 0 | if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) { |
807 | 0 | state.tokens.advance(); |
808 | 0 | return create_dataframe_result(Vec::new(), start_span); |
809 | 0 | } |
810 | | |
811 | | // Parse column definitions |
812 | 0 | let columns = parse_dataframe_column_definitions(state)?; |
813 | | |
814 | 0 | state.tokens.expect(&Token::RightBracket)?; |
815 | 0 | create_dataframe_result(columns, start_span) |
816 | 0 | } |
817 | | |
818 | | /// Parse legacy row-based `DataFrame` syntax for backward compatibility |
819 | | #[allow(clippy::ptr_arg)] // We need to mutate the Vec, not just read it |
820 | 0 | fn parse_legacy_dataframe_rows( |
821 | 0 | state: &mut ParserState, |
822 | 0 | columns: &mut Vec<DataFrameColumn>, |
823 | 0 | ) -> Result<()> { |
824 | 0 | let rows = parse_all_dataframe_rows(state)?; |
825 | 0 | populate_dataframe_columns(columns, &rows); |
826 | 0 | Ok(()) |
827 | 0 | } |
828 | | |
829 | | /// Parse all dataframe rows (complexity: 2) |
830 | 0 | fn parse_all_dataframe_rows(state: &mut ParserState) -> Result<Vec<Vec<Expr>>> { |
831 | 0 | let mut rows = Vec::new(); |
832 | | |
833 | | loop { |
834 | 0 | if is_end_bracket(state) { |
835 | 0 | break; |
836 | 0 | } |
837 | | |
838 | 0 | let row = parse_single_dataframe_row(state)?; |
839 | 0 | add_non_empty_row(&mut rows, row); |
840 | | |
841 | 0 | if !consume_row_separator(state) { |
842 | 0 | break; |
843 | 0 | } |
844 | | } |
845 | | |
846 | 0 | Ok(rows) |
847 | 0 | } |
848 | | |
849 | | /// Check if current token is end bracket (complexity: 1) |
850 | 0 | fn is_end_bracket(state: &mut ParserState) -> bool { |
851 | 0 | matches!(state.tokens.peek(), Some((Token::RightBracket, _))) |
852 | 0 | } |
853 | | |
854 | | /// Parse a single dataframe row (complexity: 2) |
855 | 0 | fn parse_single_dataframe_row(state: &mut ParserState) -> Result<Vec<Expr>> { |
856 | 0 | let mut row = Vec::new(); |
857 | | |
858 | | loop { |
859 | 0 | if is_row_boundary(state) { |
860 | 0 | break; |
861 | 0 | } |
862 | | |
863 | 0 | row.push(super::parse_expr_recursive(state)?); |
864 | | |
865 | 0 | if !consume_value_separator(state) { |
866 | 0 | break; |
867 | 0 | } |
868 | | } |
869 | | |
870 | 0 | Ok(row) |
871 | 0 | } |
872 | | |
873 | | /// Check if current token is a row boundary (complexity: 2) |
874 | 0 | fn is_row_boundary(state: &mut ParserState) -> bool { |
875 | 0 | matches!(state.tokens.peek(), Some((Token::Semicolon, _))) |
876 | 0 | || matches!(state.tokens.peek(), Some((Token::RightBracket, _))) |
877 | 0 | } |
878 | | |
879 | | /// Consume comma separator if present (complexity: 2) |
880 | 0 | fn consume_value_separator(state: &mut ParserState) -> bool { |
881 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
882 | 0 | state.tokens.advance(); |
883 | 0 | true |
884 | | } else { |
885 | 0 | false |
886 | | } |
887 | 0 | } |
888 | | |
889 | | /// Consume semicolon row separator if present (complexity: 2) |
890 | 0 | fn consume_row_separator(state: &mut ParserState) -> bool { |
891 | 0 | if matches!(state.tokens.peek(), Some((Token::Semicolon, _))) { |
892 | 0 | state.tokens.advance(); |
893 | 0 | true |
894 | | } else { |
895 | 0 | false |
896 | | } |
897 | 0 | } |
898 | | |
899 | | /// Add non-empty row to collection (complexity: 2) |
900 | 0 | fn add_non_empty_row(rows: &mut Vec<Vec<Expr>>, row: Vec<Expr>) { |
901 | 0 | if !row.is_empty() { |
902 | 0 | rows.push(row); |
903 | 0 | } |
904 | 0 | } |
905 | | |
906 | | /// Populate columns from row data (complexity: 3) |
907 | 0 | fn populate_dataframe_columns(columns: &mut [DataFrameColumn], rows: &[Vec<Expr>]) { |
908 | 0 | for (col_idx, column) in columns.iter_mut().enumerate() { |
909 | 0 | for row in rows { |
910 | 0 | if col_idx < row.len() { |
911 | 0 | column.values.push(row[col_idx].clone()); |
912 | 0 | } |
913 | | } |
914 | | } |
915 | 0 | } |