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