/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 | 0 | pub fn parse_function(state: &mut ParserState) -> Result<Expr> { |
13 | 0 | parse_function_with_visibility(state, false) |
14 | 0 | } |
15 | | |
16 | 0 | pub fn parse_function_with_visibility(state: &mut ParserState, is_pub: bool) -> Result<Expr> { |
17 | 0 | 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 | 0 | let is_async = false; |
23 | | |
24 | | // Parse function name |
25 | 0 | let name = if let Some((Token::Identifier(n), _)) = state.tokens.peek() { |
26 | 0 | let name = n.clone(); |
27 | 0 | state.tokens.advance(); |
28 | 0 | name |
29 | | } else { |
30 | 0 | "anonymous".to_string() |
31 | | }; |
32 | | |
33 | | // Parse optional type parameters <T, U, ...> |
34 | 0 | let type_params = if matches!(state.tokens.peek(), Some((Token::Less, _))) { |
35 | 0 | utils::parse_type_parameters(state)? |
36 | | } else { |
37 | 0 | Vec::new() |
38 | | }; |
39 | | |
40 | | // Parse parameters |
41 | 0 | let params = utils::parse_params(state)?; |
42 | | |
43 | | // Parse return type if present |
44 | 0 | let return_type = if matches!(state.tokens.peek(), Some((Token::Arrow, _))) { |
45 | 0 | state.tokens.advance(); // consume -> |
46 | 0 | Some(utils::parse_type(state)?) |
47 | | } else { |
48 | 0 | None |
49 | | }; |
50 | | |
51 | | // Parse body |
52 | 0 | let body = super::parse_expr_recursive(state)?; |
53 | | |
54 | 0 | Ok(Expr::new( |
55 | 0 | ExprKind::Function { |
56 | 0 | name, |
57 | 0 | type_params, |
58 | 0 | params, |
59 | 0 | return_type, |
60 | 0 | body: Box::new(body), |
61 | 0 | is_async, |
62 | 0 | is_pub, |
63 | 0 | }, |
64 | 0 | start_span, |
65 | 0 | )) |
66 | 0 | } |
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 syntax type and parse accordingly |
154 | 0 | let params = if matches!(state.tokens.peek(), Some((Token::Backslash, _))) { |
155 | 0 | parse_backslash_lambda(state)? |
156 | | } else { |
157 | 0 | parse_pipe_lambda(state)? |
158 | | }; |
159 | | |
160 | | // Parse body |
161 | 0 | let body = super::parse_expr_recursive(state)?; |
162 | | |
163 | 0 | Ok(Expr::new( |
164 | 0 | ExprKind::Lambda { |
165 | 0 | params, |
166 | 0 | body: Box::new(body), |
167 | 0 | }, |
168 | 0 | start_span, |
169 | 0 | )) |
170 | 0 | } |
171 | | |
172 | | /// Parse backslash-style lambda: \x, y -> body (complexity: 6) |
173 | 0 | fn parse_backslash_lambda(state: &mut ParserState) -> Result<Vec<Param>> { |
174 | 0 | state.tokens.advance(); // consume \ |
175 | | |
176 | 0 | let params = parse_simple_params(state)?; |
177 | | |
178 | | // Expect arrow |
179 | 0 | state.tokens.expect(&Token::Arrow)?; |
180 | | |
181 | 0 | Ok(params) |
182 | 0 | } |
183 | | |
184 | | /// Parse pipe-style lambda: |x, y| body (complexity: 5) |
185 | 0 | fn parse_pipe_lambda(state: &mut ParserState) -> Result<Vec<Param>> { |
186 | 0 | state.tokens.advance(); // consume | |
187 | | |
188 | | // Handle || as empty params |
189 | 0 | if matches!(state.tokens.peek(), Some((Token::Pipe, _))) { |
190 | 0 | state.tokens.advance(); // consume second | |
191 | 0 | return Ok(Vec::new()); |
192 | 0 | } |
193 | | |
194 | | // Parse parameters between pipes |
195 | 0 | let params = parse_lambda_params(state)?; |
196 | | |
197 | | // Expect closing pipe |
198 | 0 | if !matches!(state.tokens.peek(), Some((Token::Pipe, _))) { |
199 | 0 | bail!("Expected '|' after lambda parameters"); |
200 | 0 | } |
201 | 0 | state.tokens.advance(); // consume | |
202 | | |
203 | 0 | Ok(params) |
204 | 0 | } |
205 | | |
206 | | /// Parse simple comma-separated parameters (complexity: 6) |
207 | 0 | fn parse_simple_params(state: &mut ParserState) -> Result<Vec<Param>> { |
208 | 0 | let mut params = Vec::new(); |
209 | | |
210 | | // Parse first parameter if present |
211 | 0 | if let Some((Token::Identifier(name), _)) = state.tokens.peek() { |
212 | 0 | params.push(create_simple_param(name.clone())); |
213 | 0 | state.tokens.advance(); |
214 | | |
215 | | // Parse additional parameters |
216 | 0 | while matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
217 | 0 | state.tokens.advance(); // consume comma |
218 | 0 | if let Some((Token::Identifier(name), _)) = state.tokens.peek() { |
219 | 0 | params.push(create_simple_param(name.clone())); |
220 | 0 | state.tokens.advance(); |
221 | 0 | } |
222 | | } |
223 | 0 | } |
224 | | |
225 | 0 | Ok(params) |
226 | 0 | } |
227 | | |
228 | | /// Create a simple parameter with default type (complexity: 1) |
229 | 0 | fn create_simple_param(name: String) -> Param { |
230 | 0 | Param { |
231 | 0 | pattern: Pattern::Identifier(name), |
232 | 0 | ty: Type { |
233 | 0 | kind: TypeKind::Named("Any".to_string()), |
234 | 0 | span: Span { start: 0, end: 0 }, |
235 | 0 | }, |
236 | 0 | span: Span { start: 0, end: 0 }, |
237 | 0 | is_mutable: false, |
238 | 0 | default_value: None, |
239 | 0 | } |
240 | 0 | } |
241 | | |
242 | | |
243 | | /// # Errors |
244 | | /// |
245 | | /// Returns an error if the operation fails |
246 | | /// # Errors |
247 | | /// |
248 | | /// Returns an error if the operation fails |
249 | 0 | pub fn parse_call(state: &mut ParserState, func: Expr) -> Result<Expr> { |
250 | 0 | state.tokens.advance(); // consume ( |
251 | | |
252 | 0 | let mut args = Vec::new(); |
253 | 0 | while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) { |
254 | 0 | args.push(super::parse_expr_recursive(state)?); |
255 | | |
256 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
257 | 0 | state.tokens.advance(); // consume comma |
258 | 0 | } else { |
259 | 0 | break; |
260 | | } |
261 | | } |
262 | | |
263 | 0 | state.tokens.expect(&Token::RightParen)?; |
264 | | |
265 | 0 | Ok(Expr { |
266 | 0 | kind: ExprKind::Call { |
267 | 0 | func: Box::new(func), |
268 | 0 | args, |
269 | 0 | }, |
270 | 0 | span: Span { start: 0, end: 0 }, |
271 | 0 | attributes: Vec::new(), |
272 | 0 | }) |
273 | 0 | } |
274 | | |
275 | | /// # Errors |
276 | | /// |
277 | | /// Returns an error if the operation fails |
278 | | /// # Errors |
279 | | /// |
280 | | /// Returns an error if the operation fails |
281 | | #[allow(clippy::too_many_lines)] |
282 | 0 | pub fn parse_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> { |
283 | | // Check for special postfix operators like .await |
284 | 0 | if let Some((Token::Await, _)) = state.tokens.peek() { |
285 | 0 | state.tokens.advance(); // consume await |
286 | 0 | return Ok(Expr { |
287 | 0 | kind: ExprKind::Await { |
288 | 0 | expr: Box::new(receiver), |
289 | 0 | }, |
290 | 0 | span: Span { start: 0, end: 0 }, |
291 | 0 | attributes: Vec::new(), |
292 | 0 | }); |
293 | 0 | } |
294 | | |
295 | | // Parse method name or tuple index |
296 | 0 | match state.tokens.peek() { |
297 | 0 | Some((Token::Identifier(name), _)) => { |
298 | 0 | let method = name.clone(); |
299 | 0 | state.tokens.advance(); |
300 | 0 | parse_method_or_field_access(state, receiver, method) |
301 | | } |
302 | 0 | Some((Token::Integer(index), _)) => { |
303 | | // Handle tuple access like t.0, t.1, etc. |
304 | 0 | let index = *index; |
305 | 0 | state.tokens.advance(); |
306 | 0 | Ok(Expr { |
307 | 0 | kind: ExprKind::FieldAccess { |
308 | 0 | object: Box::new(receiver), |
309 | 0 | field: index.to_string(), |
310 | 0 | }, |
311 | 0 | span: Span { start: 0, end: 0 }, |
312 | 0 | attributes: Vec::new(), |
313 | 0 | }) |
314 | | } |
315 | | _ => { |
316 | 0 | bail!("Expected method name, tuple index, or 'await' after '.'"); |
317 | | } |
318 | | } |
319 | 0 | } |
320 | | |
321 | 0 | pub fn parse_optional_method_call(state: &mut ParserState, receiver: Expr) -> Result<Expr> { |
322 | | // Parse method name or tuple index for optional chaining |
323 | 0 | match state.tokens.peek() { |
324 | 0 | Some((Token::Identifier(name), _)) => { |
325 | 0 | let method = name.clone(); |
326 | 0 | state.tokens.advance(); |
327 | 0 | parse_optional_method_or_field_access(state, receiver, method) |
328 | | } |
329 | 0 | Some((Token::Integer(index), _)) => { |
330 | | // Handle optional tuple access like t?.0, t?.1, etc. |
331 | 0 | let index = *index; |
332 | 0 | state.tokens.advance(); |
333 | 0 | Ok(Expr { |
334 | 0 | kind: ExprKind::OptionalFieldAccess { |
335 | 0 | object: Box::new(receiver), |
336 | 0 | field: index.to_string(), |
337 | 0 | }, |
338 | 0 | span: Span { start: 0, end: 0 }, |
339 | 0 | attributes: Vec::new(), |
340 | 0 | }) |
341 | | } |
342 | | _ => { |
343 | 0 | bail!("Expected method name or tuple index after '?.'"); |
344 | | } |
345 | | } |
346 | 0 | } |
347 | | |
348 | 0 | fn parse_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> { |
349 | | // Check if it's a method call (with parentheses) or field access |
350 | 0 | if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) { |
351 | 0 | parse_method_call_access(state, receiver, method) |
352 | | } else { |
353 | | // Field access |
354 | 0 | Ok(create_field_access(receiver, method)) |
355 | | } |
356 | 0 | } |
357 | | |
358 | | /// Parse method call with arguments (complexity: 6) |
359 | 0 | fn parse_method_call_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> { |
360 | 0 | state.tokens.advance(); // consume ( |
361 | 0 | let args = parse_method_arguments(state)?; |
362 | 0 | state.tokens.expect(&Token::RightParen)?; |
363 | | |
364 | | // Check if this is a DataFrame operation |
365 | 0 | if is_dataframe_method(&method) { |
366 | 0 | handle_dataframe_method(receiver, method, args) |
367 | | } else { |
368 | 0 | Ok(create_method_call(receiver, method, args)) |
369 | | } |
370 | 0 | } |
371 | | |
372 | | /// Parse method arguments (complexity: 5) |
373 | 0 | fn parse_method_arguments(state: &mut ParserState) -> Result<Vec<Expr>> { |
374 | 0 | let mut args = Vec::new(); |
375 | 0 | while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) { |
376 | 0 | args.push(super::parse_expr_recursive(state)?); |
377 | | |
378 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
379 | 0 | state.tokens.advance(); // consume comma |
380 | 0 | } else { |
381 | 0 | break; |
382 | | } |
383 | | } |
384 | 0 | Ok(args) |
385 | 0 | } |
386 | | |
387 | | /// Check if method is DataFrame-specific (complexity: 1) |
388 | 0 | fn is_dataframe_method(method: &str) -> bool { |
389 | 0 | matches!( |
390 | 0 | method, |
391 | 0 | "select" | "groupby" | "group_by" | "agg" | "pivot" | "melt" | |
392 | 0 | "join" | "rolling" | "shift" | "diff" | "pct_change" | "corr" | "cov" |
393 | | ) |
394 | 0 | } |
395 | | |
396 | | /// Handle DataFrame-specific methods (complexity: 4) |
397 | 0 | fn handle_dataframe_method(receiver: Expr, method: String, args: Vec<Expr>) -> Result<Expr> { |
398 | 0 | let operation = match method.as_str() { |
399 | 0 | "select" => DataFrameOp::Select(extract_select_columns(args)), |
400 | 0 | "groupby" | "group_by" => DataFrameOp::GroupBy(extract_groupby_columns(args)), |
401 | 0 | _ => return Ok(create_method_call(receiver, method, args)), |
402 | | }; |
403 | | |
404 | 0 | Ok(Expr { |
405 | 0 | kind: ExprKind::DataFrameOperation { |
406 | 0 | source: Box::new(receiver), |
407 | 0 | operation, |
408 | 0 | }, |
409 | 0 | span: Span { start: 0, end: 0 }, |
410 | 0 | attributes: Vec::new(), |
411 | 0 | }) |
412 | 0 | } |
413 | | |
414 | | /// Extract column names from select arguments (complexity: 8) |
415 | 0 | fn extract_select_columns(args: Vec<Expr>) -> Vec<String> { |
416 | 0 | let mut columns = Vec::new(); |
417 | 0 | for arg in args { |
418 | 0 | match arg.kind { |
419 | | // Handle bare identifiers: .select(age, name) |
420 | 0 | ExprKind::Identifier(name) => { |
421 | 0 | columns.push(name); |
422 | 0 | } |
423 | | // Handle list literals: .select(["age", "name"]) |
424 | 0 | ExprKind::List(items) => { |
425 | 0 | for item in items { |
426 | 0 | if let ExprKind::Literal(Literal::String(col_name)) = item.kind { |
427 | 0 | columns.push(col_name); |
428 | 0 | } |
429 | | } |
430 | | } |
431 | | // Handle single string literals: .select("age") |
432 | 0 | ExprKind::Literal(Literal::String(col_name)) => { |
433 | 0 | columns.push(col_name); |
434 | 0 | } |
435 | 0 | _ => {} |
436 | | } |
437 | | } |
438 | 0 | columns |
439 | 0 | } |
440 | | |
441 | | /// Extract column names from groupby arguments (complexity: 3) |
442 | 0 | fn extract_groupby_columns(args: Vec<Expr>) -> Vec<String> { |
443 | 0 | args.into_iter() |
444 | 0 | .filter_map(|arg| { |
445 | 0 | if let ExprKind::Identifier(name) = arg.kind { |
446 | 0 | Some(name) |
447 | | } else { |
448 | 0 | None |
449 | | } |
450 | 0 | }) |
451 | 0 | .collect() |
452 | 0 | } |
453 | | |
454 | | /// Create a method call expression (complexity: 1) |
455 | 0 | fn create_method_call(receiver: Expr, method: String, args: Vec<Expr>) -> Expr { |
456 | 0 | Expr { |
457 | 0 | kind: ExprKind::MethodCall { |
458 | 0 | receiver: Box::new(receiver), |
459 | 0 | method, |
460 | 0 | args, |
461 | 0 | }, |
462 | 0 | span: Span { start: 0, end: 0 }, |
463 | 0 | attributes: Vec::new(), |
464 | 0 | } |
465 | 0 | } |
466 | | |
467 | | /// Create a field access expression (complexity: 1) |
468 | 0 | fn create_field_access(receiver: Expr, field: String) -> Expr { |
469 | 0 | Expr { |
470 | 0 | kind: ExprKind::FieldAccess { |
471 | 0 | object: Box::new(receiver), |
472 | 0 | field, |
473 | 0 | }, |
474 | 0 | span: Span { start: 0, end: 0 }, |
475 | 0 | attributes: Vec::new(), |
476 | 0 | } |
477 | 0 | } |
478 | | |
479 | 0 | fn parse_optional_method_or_field_access(state: &mut ParserState, receiver: Expr, method: String) -> Result<Expr> { |
480 | | // Check if it's a method call (with parentheses) or field access |
481 | 0 | if matches!(state.tokens.peek(), Some((Token::LeftParen, _))) { |
482 | | // Optional method call - convert to OptionalMethodCall AST node |
483 | | // For now, we'll just parse as regular method call but with optional semantics |
484 | 0 | state.tokens.advance(); // consume ( |
485 | | |
486 | 0 | let mut args = Vec::new(); |
487 | 0 | while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) { |
488 | 0 | args.push(super::parse_expr_recursive(state)?); |
489 | | |
490 | 0 | if matches!(state.tokens.peek(), Some((Token::Comma, _))) { |
491 | 0 | state.tokens.advance(); // consume comma |
492 | 0 | } else { |
493 | 0 | break; |
494 | | } |
495 | | } |
496 | | |
497 | 0 | state.tokens.expect(&Token::RightParen)?; |
498 | | |
499 | | // Create an OptionalMethodCall expression |
500 | 0 | Ok(Expr { |
501 | 0 | kind: ExprKind::OptionalMethodCall { |
502 | 0 | receiver: Box::new(receiver), |
503 | 0 | method, |
504 | 0 | args, |
505 | 0 | }, |
506 | 0 | span: Span { start: 0, end: 0 }, |
507 | 0 | attributes: Vec::new(), |
508 | 0 | }) |
509 | | } else { |
510 | | // Optional field access |
511 | 0 | Ok(Expr { |
512 | 0 | kind: ExprKind::OptionalFieldAccess { |
513 | 0 | object: Box::new(receiver), |
514 | 0 | field: method, |
515 | 0 | }, |
516 | 0 | span: Span { start: 0, end: 0 }, |
517 | 0 | attributes: Vec::new(), |
518 | 0 | }) |
519 | | } |
520 | 0 | } |