/home/noah/src/ruchy/src/frontend/parser/functions.rs
Line | Count | Source |
1 | | //! Function-related parsing (function definitions, lambdas, calls) |
2 | | |
3 | | use super::{ParserState, *}; |
4 | | use crate::frontend::ast::{DataFrameOp, Literal, Pattern}; |
5 | | |
6 | | /// # Errors |
7 | | /// |
8 | | /// Returns an error if the operation fails |
9 | | /// # Errors |
10 | | /// |
11 | | /// Returns an error if the operation fails |
12 | 47 | pub fn parse_function(state: &mut ParserState) -> Result<Expr> { |
13 | 47 | parse_function_with_visibility(state, false) |
14 | 47 | } |
15 | | |
16 | 47 | pub fn parse_function_with_visibility(state: &mut ParserState, is_pub: bool) -> Result<Expr> { |
17 | 47 | let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume fun |
18 | | |
19 | | // Check for async modifier - currently not implemented in lexer |
20 | | // When async keyword is added to lexer, this will be: |
21 | | // let is_async = state.tokens.check(&Token::Async); |
22 | 47 | let is_async = false; |
23 | | |
24 | | // Parse function name |
25 | 47 | let name = if let Some((Token::Identifier(n46 ), _)) = state.tokens.peek() { |
26 | 46 | let name = n.clone(); |
27 | 46 | state.tokens.advance(); |
28 | 46 | name |
29 | | } else { |
30 | 1 | "anonymous".to_string() |
31 | | }; |
32 | | |
33 | | // Parse optional type parameters <T, U, ...> |
34 | 47 | let type_params = if matches!44 (state.tokens.peek(), Some((Token::Less, _))) { |
35 | 3 | utils::parse_type_parameters(state)?0 |
36 | | } else { |
37 | 44 | Vec::new() |
38 | | }; |
39 | | |
40 | | // Parse parameters |
41 | 47 | let params46 = utils::parse_params(state)?1 ; |
42 | | |
43 | | // Parse return type if present |
44 | 46 | let return_type = if matches!37 (state.tokens.peek(), Some((Token::Arrow, _))) { |
45 | 9 | state.tokens.advance(); // consume -> |
46 | 9 | Some(utils::parse_type(state)?0 ) |
47 | | } else { |
48 | 37 | None |
49 | | }; |
50 | | |
51 | | // Parse body |
52 | 46 | let body = super::parse_expr_recursive(state)?0 ; |
53 | | |
54 | 46 | Ok(Expr::new( |
55 | 46 | ExprKind::Function { |
56 | 46 | name, |
57 | 46 | type_params, |
58 | 46 | params, |
59 | 46 | return_type, |
60 | 46 | body: Box::new(body), |
61 | 46 | is_async, |
62 | 46 | is_pub, |
63 | 46 | }, |
64 | 46 | start_span, |
65 | 46 | )) |
66 | 47 | } |
67 | | |
68 | 0 | fn parse_lambda_params(state: &mut ParserState) -> Result<Vec<Param>> { |
69 | 0 | let mut params = Vec::new(); |
70 | | |
71 | | // Parse parameters until we hit a pipe or arrow |
72 | | loop { |
73 | | // Check if we've reached the end of parameters |
74 | 0 | if matches!(state.tokens.peek(), Some((Token::Pipe, _))) { |
75 | 0 | break; |
76 | 0 | } |
77 | | |
78 | | // Parse parameter name |
79 | 0 | let name = if let Some((Token::Identifier(n), _)) = state.tokens.peek() { |
80 | 0 | let name = n.clone(); |
81 | 0 | state.tokens.advance(); |
82 | 0 | name |
83 | | } else { |
84 | 0 | break; // No more parameters |
85 | | }; |
86 | | |
87 | | // Parse optional type annotation |
88 | 0 | let ty = if matches!(state.tokens.peek(), Some((Token::Colon, _))) { |
89 | 0 | state.tokens.advance(); // consume : |
90 | 0 | utils::parse_type(state)? |
91 | | } else { |
92 | | // Default to inferred type - use _ as placeholder |
93 | 0 | Type { |
94 | 0 | kind: TypeKind::Named("_".to_string()), |
95 | 0 | span: Span { start: 0, end: 0 }, |
96 | 0 | } |
97 | | }; |
98 | | |
99 | 0 | params.push(Param { |
100 | 0 | pattern: Pattern::Identifier(name), |
101 | 0 | ty, |
102 | 0 | span: Span { start: 0, end: 0 }, |
103 | 0 | is_mutable: false, |
104 | 0 | default_value: None, |
105 | 0 | }); |
106 | | |
107 | | // Check for comma |
108 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
109 | 0 | state.tokens.advance(); // consume comma |
110 | 0 | } else { |
111 | 0 | break; |
112 | | } |
113 | | } |
114 | | |
115 | 0 | Ok(params) |
116 | 0 | } |
117 | | |
118 | | /// # Errors |
119 | | /// |
120 | | /// Returns an error if the operation fails |
121 | | /// # Errors |
122 | | /// |
123 | | /// Returns an error if the operation fails |
124 | 0 | pub fn parse_empty_lambda(state: &mut ParserState) -> Result<Expr> { |
125 | 0 | let start_span = state.tokens.advance().expect("checked by parser logic").1; // consume || |
126 | | |
127 | | // Lambda syntax: || expr (no => allowed) |
128 | | |
129 | | // Parse the body |
130 | 0 | let body = super::parse_expr_recursive(state)?; |
131 | | |
132 | 0 | Ok(Expr::new( |
133 | 0 | ExprKind::Lambda { |
134 | 0 | params: Vec::new(), |
135 | 0 | body: Box::new(body), |
136 | 0 | }, |
137 | 0 | start_span, |
138 | 0 | )) |
139 | 0 | } |
140 | | |
141 | | /// # Errors |
142 | | /// |
143 | | /// Returns an error if the operation fails |
144 | | /// # Errors |
145 | | /// |
146 | | /// Returns an error if the operation fails |
147 | 0 | pub fn parse_lambda(state: &mut ParserState) -> Result<Expr> { |
148 | 0 | let start_span = state |
149 | 0 | .tokens |
150 | 0 | .peek() |
151 | 0 | .map_or(Span { start: 0, end: 0 }, |(_, s)| *s); |
152 | | |
153 | | // Check if it's backslash syntax (\x -> ...) or pipe syntax (|x| ...) |
154 | 0 | if matches!(state.tokens.peek(), Some((Token::Backslash, _))) { |
155 | 0 | state.tokens.advance(); // consume \ |
156 | | |
157 | | // Parse parameters (simple identifiers separated by commas) |
158 | 0 | let mut params = Vec::new(); |
159 | | |
160 | | // Parse first parameter |
161 | 0 | if let Some((Token::Identifier(name), _)) = state.tokens.peek() { |
162 | 0 | params.push(Param { |
163 | 0 | pattern: Pattern::Identifier(name.clone()), |
164 | 0 | ty: Type { |
165 | 0 | kind: TypeKind::Named("Any".to_string()), |
166 | 0 | span: Span { start: 0, end: 0 }, |
167 | 0 | }, |
168 | 0 | span: Span { start: 0, end: 0 }, |
169 | 0 | is_mutable: false, |
170 | 0 | default_value: None, |
171 | 0 | }); |
172 | 0 | state.tokens.advance(); |
173 | | |
174 | | // Parse additional parameters |
175 | 0 | while matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
176 | 0 | state.tokens.advance(); // consume comma |
177 | 0 | if let Some((Token::Identifier(name), _)) = state.tokens.peek() { |
178 | 0 | params.push(Param { |
179 | 0 | pattern: Pattern::Identifier(name.clone()), |
180 | 0 | ty: Type { |
181 | 0 | kind: TypeKind::Named("Any".to_string()), |
182 | 0 | span: Span { start: 0, end: 0 }, |
183 | 0 | }, |
184 | 0 | span: Span { start: 0, end: 0 }, |
185 | 0 | is_mutable: false, |
186 | 0 | default_value: None, |
187 | 0 | }); |
188 | 0 | state.tokens.advance(); |
189 | 0 | } |
190 | | } |
191 | 0 | } |
192 | | |
193 | | // Expect arrow |
194 | 0 | state.tokens.expect(&Token::Arrow)?; |
195 | | |
196 | | // Parse body |
197 | 0 | let body = super::parse_expr_recursive(state)?; |
198 | | |
199 | 0 | return Ok(Expr::new( |
200 | 0 | ExprKind::Lambda { |
201 | 0 | params, |
202 | 0 | body: Box::new(body), |
203 | 0 | }, |
204 | 0 | start_span, |
205 | 0 | )); |
206 | 0 | } |
207 | | |
208 | | // Otherwise, handle pipe syntax |x| ... |
209 | 0 | state.tokens.advance(); // consume | |
210 | | |
211 | | // Handle || as a special case for empty parameter lambdas |
212 | 0 | if matches!(state.tokens.peek(), Some((Token::Pipe, _))) { |
213 | 0 | state.tokens.advance(); // consume second | |
214 | | |
215 | | // Lambda syntax: || expr (no => allowed) |
216 | | |
217 | | // Parse the body |
218 | 0 | let body = super::parse_expr_recursive(state)?; |
219 | 0 | return Ok(Expr::new( |
220 | 0 | ExprKind::Lambda { |
221 | 0 | params: Vec::new(), |
222 | 0 | body: Box::new(body), |
223 | 0 | }, |
224 | 0 | start_span, |
225 | 0 | )); |
226 | 0 | } |
227 | | |
228 | | // Parse parameters between pipes: |x, y| |
229 | 0 | let params = parse_lambda_params(state)?; |
230 | | |
231 | | // Check for empty params with single | |
232 | 0 | if !matches!(state.tokens.peek(), Some((Token::Pipe, _))) { |
233 | 0 | bail!("Expected '|' after lambda parameters"); |
234 | 0 | } |
235 | 0 | state.tokens.advance(); // consume | |
236 | | |
237 | | // Lambda syntax: |x| expr (no => allowed) |
238 | | |
239 | | // Parse the body |
240 | 0 | let body = super::parse_expr_recursive(state)?; |
241 | | |
242 | 0 | Ok(Expr::new( |
243 | 0 | ExprKind::Lambda { |
244 | 0 | params, |
245 | 0 | body: Box::new(body), |
246 | 0 | }, |
247 | 0 | start_span, |
248 | 0 | )) |
249 | 0 | } |
250 | | |
251 | | |
252 | | /// # Errors |
253 | | /// |
254 | | /// Returns an error if the operation fails |
255 | | /// # Errors |
256 | | /// |
257 | | /// Returns an error if the operation fails |
258 | 129 | pub fn parse_call(state: &mut ParserState, func: Expr) -> Result<Expr> { |
259 | 129 | state.tokens.advance(); // consume ( |
260 | | |
261 | 129 | let mut args = Vec::new(); |
262 | 156 | while !matches!151 (state.tokens.peek(), Some((Token::RightParen, _))) { |
263 | 151 | args.push(super::parse_expr_recursive(state)?0 ); |
264 | | |
265 | 151 | if matches!124 (state.tokens.peek(), Some((Token::Comma, _))) { |
266 | 27 | state.tokens.advance(); // consume comma |
267 | 27 | } else { |
268 | 124 | break; |
269 | | } |
270 | | } |
271 | | |
272 | 129 | state.tokens.expect(&Token::RightParen)?0 ; |
273 | | |
274 | 129 | Ok(Expr { |
275 | 129 | kind: ExprKind::Call { |
276 | 129 | func: Box::new(func), |
277 | 129 | args, |
278 | 129 | }, |
279 | 129 | span: Span { start: 0, end: 0 }, |
280 | 129 | attributes: Vec::new(), |
281 | 129 | }) |
282 | 129 | } |
283 | | |
284 | | /// # Errors |
285 | | /// |
286 | | /// Returns an error if the operation fails |
287 | | /// # Errors |
288 | | /// |
289 | | /// Returns an error if the operation fails |
290 | | #[allow(clippy::too_many_lines)] |
291 | 53 | pub fn parse_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> { |
292 | | // Check for special postfix operators like .await |
293 | 53 | if let Some((Token::Await, _)) = state.tokens.peek() { |
294 | 1 | state.tokens.advance(); // consume await |
295 | 1 | return Ok(Expr { |
296 | 1 | kind: ExprKind::Await { |
297 | 1 | expr: Box::new(receiver), |
298 | 1 | }, |
299 | 1 | span: Span { start: 0, end: 0 }, |
300 | 1 | attributes: Vec::new(), |
301 | 1 | }); |
302 | 52 | } |
303 | | |
304 | | // Parse method name or tuple index |
305 | 52 | match state.tokens.peek() { |
306 | 51 | Some((Token::Identifier(name), _)) => { |
307 | 51 | let method = name.clone(); |
308 | 51 | state.tokens.advance(); |
309 | 51 | parse_method_or_field_access(state, receiver, method) |
310 | | } |
311 | 0 | Some((Token::Integer(index), _)) => { |
312 | | // Handle tuple access like t.0, t.1, etc. |
313 | 0 | let index = *index; |
314 | 0 | state.tokens.advance(); |
315 | 0 | Ok(Expr { |
316 | 0 | kind: ExprKind::FieldAccess { |
317 | 0 | object: Box::new(receiver), |
318 | 0 | field: index.to_string(), |
319 | 0 | }, |
320 | 0 | span: Span { start: 0, end: 0 }, |
321 | 0 | attributes: Vec::new(), |
322 | 0 | }) |
323 | | } |
324 | | _ => { |
325 | 1 | bail!("Expected method name, tuple index, or 'await' after '.'"); |
326 | | } |
327 | | } |
328 | 53 | } |
329 | | |
330 | 0 | pub fn parse_optional_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> { |
331 | | // Parse method name or tuple index for optional chaining |
332 | 0 | match state.tokens.peek() { |
333 | 0 | Some((Token::Identifier(name), _)) => { |
334 | 0 | let method = name.clone(); |
335 | 0 | state.tokens.advance(); |
336 | 0 | parse_optional_method_or_field_access(state, receiver, method) |
337 | | } |
338 | 0 | Some((Token::Integer(index), _)) => { |
339 | | // Handle optional tuple access like t?.0, t?.1, etc. |
340 | 0 | let index = *index; |
341 | 0 | state.tokens.advance(); |
342 | 0 | Ok(Expr { |
343 | 0 | kind: ExprKind::OptionalFieldAccess { |
344 | 0 | object: Box::new(receiver), |
345 | 0 | field: index.to_string(), |
346 | 0 | }, |
347 | 0 | span: Span { start: 0, end: 0 }, |
348 | 0 | attributes: Vec::new(), |
349 | 0 | }) |
350 | | } |
351 | | _ => { |
352 | 0 | bail!("Expected method name or tuple index after '?.'"); |
353 | | } |
354 | | } |
355 | 0 | } |
356 | | |
357 | 51 | fn parse_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> { |
358 | | |
359 | | // Check if this is a DataFrame-specific operation method |
360 | | // Note: filter, map, reduce are array methods, not DataFrame methods |
361 | | // Only include methods that are DataFrame-exclusive |
362 | 51 | let is_dataframe_method = matches!1 ( |
363 | 51 | method.as_str(), |
364 | 51 | "select" |
365 | 50 | | "groupby" |
366 | 50 | | "group_by" |
367 | 50 | | "agg" |
368 | 50 | | "pivot" |
369 | 50 | | "melt" |
370 | 50 | | "join" |
371 | 50 | | "rolling" |
372 | 50 | | "shift" |
373 | 50 | | "diff" |
374 | 50 | | "pct_change" |
375 | 50 | | "corr" |
376 | 50 | | "cov" |
377 | | ); |
378 | | |
379 | | // Check if it's a method call (with parentheses) or field access |
380 | 51 | if matches!2 (state.tokens.peek(), Some((Token::LeftParen, _))) { |
381 | | // Method call |
382 | 49 | state.tokens.advance(); // consume ( |
383 | | |
384 | 49 | let mut args = Vec::new(); |
385 | 51 | while !matches!27 (state.tokens.peek(), Some((Token::RightParen, _))) { |
386 | 27 | args.push(super::parse_expr_recursive(state)?0 ); |
387 | | |
388 | 27 | if matches!25 (state.tokens.peek(), Some((Token::Comma, _))) { |
389 | 2 | state.tokens.advance(); // consume comma |
390 | 2 | } else { |
391 | 25 | break; |
392 | | } |
393 | | } |
394 | | |
395 | 49 | state.tokens.expect(&Token::RightParen)?0 ; |
396 | | |
397 | | // Check if this is a DataFrame operation |
398 | 49 | if is_dataframe_method { |
399 | | // Convert to DataFrame operation based on method name |
400 | 1 | let operation = match method.as_str() { |
401 | 1 | "select" => { |
402 | | // Extract column names from arguments |
403 | 1 | let mut columns = Vec::new(); |
404 | 2 | for arg1 in args { |
405 | 0 | match arg.kind { |
406 | | // Handle bare identifiers: .select(age, name) |
407 | 0 | ExprKind::Identifier(name) => { |
408 | 0 | columns.push(name); |
409 | 0 | } |
410 | | // Handle list literals: .select(["age", "name"]) |
411 | 1 | ExprKind::List(items) => { |
412 | 2 | for item1 in items { |
413 | 1 | if let ExprKind::Literal(Literal::String(col_name)) = item.kind |
414 | 1 | { |
415 | 1 | columns.push(col_name); |
416 | 1 | }0 |
417 | | } |
418 | | } |
419 | | // Handle single string literals: .select("age") |
420 | 0 | ExprKind::Literal(Literal::String(col_name)) => { |
421 | 0 | columns.push(col_name); |
422 | 0 | } |
423 | 0 | _ => {} |
424 | | } |
425 | | } |
426 | 1 | DataFrameOp::Select(columns) |
427 | | } |
428 | 0 | "groupby" | "group_by" => { |
429 | 0 | let columns = args |
430 | 0 | .into_iter() |
431 | 0 | .filter_map(|arg| { |
432 | 0 | if let ExprKind::Identifier(name) = arg.kind { |
433 | 0 | Some(name) |
434 | | } else { |
435 | 0 | None |
436 | | } |
437 | 0 | }) |
438 | 0 | .collect(); |
439 | 0 | DataFrameOp::GroupBy(columns) |
440 | | } |
441 | | _ => { |
442 | | // For other methods, fall back to regular method call |
443 | 0 | return Ok(Expr { |
444 | 0 | kind: ExprKind::MethodCall { |
445 | 0 | receiver: Box::new(receiver), |
446 | 0 | method, |
447 | 0 | args, |
448 | 0 | }, |
449 | 0 | span: Span { start: 0, end: 0 }, |
450 | 0 | attributes: Vec::new(), |
451 | 0 | }); |
452 | | } |
453 | | }; |
454 | | |
455 | 1 | Ok(Expr { |
456 | 1 | kind: ExprKind::DataFrameOperation { |
457 | 1 | source: Box::new(receiver), |
458 | 1 | operation, |
459 | 1 | }, |
460 | 1 | span: Span { start: 0, end: 0 }, |
461 | 1 | attributes: Vec::new(), |
462 | 1 | }) |
463 | | } else { |
464 | 48 | Ok(Expr { |
465 | 48 | kind: ExprKind::MethodCall { |
466 | 48 | receiver: Box::new(receiver), |
467 | 48 | method, |
468 | 48 | args, |
469 | 48 | }, |
470 | 48 | span: Span { start: 0, end: 0 }, |
471 | 48 | attributes: Vec::new(), |
472 | 48 | }) |
473 | | } |
474 | | } else { |
475 | | // Field access |
476 | 2 | Ok(Expr { |
477 | 2 | kind: ExprKind::FieldAccess { |
478 | 2 | object: Box::new(receiver), |
479 | 2 | field: method, |
480 | 2 | }, |
481 | 2 | span: Span { start: 0, end: 0 }, |
482 | 2 | attributes: Vec::new(), |
483 | 2 | }) |
484 | | } |
485 | 51 | } |
486 | | |
487 | 0 | fn parse_optional_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> { |
488 | | // Check if it's a method call (with parentheses) or field access |
489 | 0 | if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) { |
490 | | // Optional method call - convert to OptionalMethodCall AST node |
491 | | // For now, we'll just parse as regular method call but with optional semantics |
492 | 0 | state.tokens.advance(); // consume ( |
493 | | |
494 | 0 | let mut args = Vec::new(); |
495 | 0 | while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) { |
496 | 0 | args.push(super::parse_expr_recursive(state)?); |
497 | | |
498 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
499 | 0 | state.tokens.advance(); // consume comma |
500 | 0 | } else { |
501 | 0 | break; |
502 | | } |
503 | | } |
504 | | |
505 | 0 | state.tokens.expect(&Token::RightParen)?; |
506 | | |
507 | | // Create an OptionalMethodCall expression |
508 | 0 | Ok(Expr { |
509 | 0 | kind: ExprKind::OptionalMethodCall { |
510 | 0 | receiver: Box::new(receiver), |
511 | 0 | method, |
512 | 0 | args, |
513 | 0 | }, |
514 | 0 | span: Span { start: 0, end: 0 }, |
515 | 0 | attributes: Vec::new(), |
516 | 0 | }) |
517 | | } else { |
518 | | // Optional field access |
519 | 0 | Ok(Expr { |
520 | 0 | kind: ExprKind::OptionalFieldAccess { |
521 | 0 | object: Box::new(receiver), |
522 | 0 | field: method, |
523 | 0 | }, |
524 | 0 | span: Span { start: 0, end: 0 }, |
525 | 0 | attributes: Vec::new(), |
526 | 0 | }) |
527 | | } |
528 | 0 | } |