/home/noah/src/ruchy/src/frontend/parser/mod.rs
Line | Count | Source |
1 | | //! Modular parser implementation for Ruchy |
2 | | #![allow(clippy::wildcard_imports)] |
3 | | #![allow(clippy::expect_used)] |
4 | | //! |
5 | | //! The parser is split into logical modules to improve maintainability: |
6 | | //! - `core` - Main parser entry points and precedence handling |
7 | | //! - `expressions` - Basic expressions (literals, binary/unary ops) |
8 | | //! - `control_flow` - Control flow constructs (if, match, loops) |
9 | | //! - `functions` - Function definitions, lambdas, and calls |
10 | | //! - `types` - Type-related parsing (struct, trait, impl) |
11 | | //! - `collections` - Collections (lists, dataframes, comprehensions) |
12 | | //! - `actors` - Actor system constructs |
13 | | //! - `utils` - Parsing utilities and error recovery |
14 | | |
15 | | mod actors; |
16 | | mod collections; |
17 | | mod core; |
18 | | mod expressions; |
19 | | mod functions; |
20 | | mod operator_precedence; |
21 | | mod types; |
22 | | mod utils; |
23 | | |
24 | | // Re-export the main parser |
25 | | pub use core::Parser; |
26 | | |
27 | | use crate::frontend::arena::{Arena, StringInterner}; |
28 | | use crate::frontend::ast::{ |
29 | | ActorHandler, Attribute, BinaryOp, DataFrameColumn, EnumVariant, Expr, ExprKind, ImportItem, Literal, MatchArm, Param, |
30 | | Pattern, PipelineStage, Span, StringPart, StructField, TraitMethod, Type, TypeKind, UnaryOp, |
31 | | }; |
32 | | use crate::frontend::lexer::{Token, TokenStream}; |
33 | | use crate::parser::error_recovery::{ErrorNode, ErrorRecovery}; |
34 | | use anyhow::{bail, Result}; |
35 | | |
36 | | /// Shared parser state and utilities |
37 | | pub(crate) struct ParserState<'a> { |
38 | | pub tokens: TokenStream<'a>, |
39 | | #[allow(dead_code)] |
40 | | pub error_recovery: ErrorRecovery, |
41 | | pub errors: Vec<ErrorNode>, |
42 | | /// Arena allocator for AST nodes |
43 | | #[allow(dead_code)] |
44 | | pub arena: Arena, |
45 | | /// String interner for deduplicating identifiers |
46 | | #[allow(dead_code)] |
47 | | pub interner: StringInterner, |
48 | | } |
49 | | |
50 | | impl<'a> ParserState<'a> { |
51 | | #[must_use] |
52 | 0 | pub fn new(input: &'a str) -> Self { |
53 | 0 | Self { |
54 | 0 | tokens: TokenStream::new(input), |
55 | 0 | error_recovery: ErrorRecovery::new(), |
56 | 0 | errors: Vec::new(), |
57 | 0 | arena: Arena::new(), |
58 | 0 | interner: StringInterner::new(), |
59 | 0 | } |
60 | 0 | } |
61 | | |
62 | | /// Get all errors encountered during parsing |
63 | 0 | pub fn get_errors(&self) -> &[ErrorNode] { |
64 | 0 | &self.errors |
65 | 0 | } |
66 | | |
67 | | /// Get arena statistics for performance monitoring |
68 | | #[allow(dead_code)] |
69 | 0 | pub fn arena_stats(&self) -> (usize, usize) { |
70 | 0 | (self.arena.total_allocated(), self.arena.num_items()) |
71 | 0 | } |
72 | | |
73 | | /// Get interner statistics |
74 | | #[allow(dead_code)] |
75 | 0 | pub fn interner_stats(&self) -> (usize, usize) { |
76 | 0 | self.interner.stats() |
77 | 0 | } |
78 | | } |
79 | | |
80 | | /// Forward declarations for recursive parsing |
81 | 0 | pub(crate) fn parse_expr_recursive(state: &mut ParserState) -> Result<Expr> { |
82 | 0 | parse_expr_with_precedence_recursive(state, 0) |
83 | 0 | } |
84 | | |
85 | 0 | pub(crate) fn parse_expr_with_precedence_recursive( |
86 | 0 | state: &mut ParserState, |
87 | 0 | min_prec: i32, |
88 | 0 | ) -> Result<Expr> { |
89 | 0 | let mut left = expressions::parse_prefix(state)?; |
90 | | |
91 | | loop { |
92 | | // Handle postfix operators |
93 | 0 | left = handle_postfix_operators(state, left)?; |
94 | | |
95 | | // Try to handle infix operators |
96 | 0 | if let Some(new_left) = try_handle_infix_operators(state, left.clone(), min_prec)? { |
97 | 0 | left = new_left; |
98 | 0 | } else { |
99 | 0 | break; |
100 | | } |
101 | | } |
102 | | |
103 | 0 | Ok(left) |
104 | 0 | } |
105 | | |
106 | | /// Try to handle any infix operator (complexity: 7) |
107 | 0 | fn try_handle_infix_operators( |
108 | 0 | state: &mut ParserState, |
109 | 0 | left: Expr, |
110 | 0 | min_prec: i32, |
111 | 0 | ) -> Result<Option<Expr>> { |
112 | | // Get current token for infix processing |
113 | 0 | let Some((token, _)) = state.tokens.peek() else { |
114 | 0 | return Ok(None); |
115 | | }; |
116 | 0 | let token_clone = token.clone(); |
117 | | |
118 | | // Try operators in order of priority |
119 | 0 | let handlers = [ |
120 | 0 | try_new_actor_operators, |
121 | 0 | try_type_cast_operator, |
122 | 0 | try_binary_operators, |
123 | 0 | try_assignment_operators, |
124 | 0 | try_pipeline_operators, |
125 | 0 | try_range_operators, |
126 | 0 | ]; |
127 | | |
128 | 0 | for handler in &handlers { |
129 | 0 | if let Some(new_left) = handler(state, left.clone(), &token_clone, min_prec)? { |
130 | 0 | return Ok(Some(new_left)); |
131 | 0 | } |
132 | | } |
133 | | |
134 | 0 | Ok(None) |
135 | 0 | } |
136 | | |
137 | | /// Handle all postfix operators in a loop |
138 | 0 | fn handle_postfix_operators(state: &mut ParserState, mut left: Expr) -> Result<Expr> { |
139 | 0 | let mut handled_postfix = true; |
140 | 0 | while handled_postfix { |
141 | 0 | handled_postfix = false; |
142 | 0 | match state.tokens.peek() { |
143 | | Some((Token::Dot, _)) => { |
144 | 0 | state.tokens.advance(); |
145 | 0 | left = functions::parse_method_call(state, left)?; |
146 | 0 | handled_postfix = true; |
147 | | } |
148 | | Some((Token::SafeNav, _)) => { |
149 | 0 | state.tokens.advance(); |
150 | 0 | left = functions::parse_optional_method_call(state, left)?; |
151 | 0 | handled_postfix = true; |
152 | | } |
153 | | Some((Token::LeftParen, _)) => { |
154 | 0 | left = functions::parse_call(state, left)?; |
155 | 0 | handled_postfix = true; |
156 | | } |
157 | | Some((Token::LeftBracket, _)) => { |
158 | 0 | left = handle_array_indexing(state, left)?; |
159 | 0 | handled_postfix = true; |
160 | | } |
161 | | Some((Token::LeftBrace, _)) => { |
162 | 0 | if let Some(new_left) = try_parse_struct_literal(state, &left)? { |
163 | 0 | left = new_left; |
164 | 0 | handled_postfix = true; |
165 | 0 | } |
166 | | } |
167 | 0 | Some((Token::Increment, _)) => { |
168 | 0 | state.tokens.advance(); |
169 | 0 | left = create_post_increment(left); |
170 | 0 | handled_postfix = true; |
171 | 0 | } |
172 | 0 | Some((Token::Decrement, _)) => { |
173 | 0 | state.tokens.advance(); |
174 | 0 | left = create_post_decrement(left); |
175 | 0 | handled_postfix = true; |
176 | 0 | } |
177 | 0 | Some((Token::Question, _)) => { |
178 | 0 | state.tokens.advance(); |
179 | 0 | left = Expr::new( |
180 | 0 | ExprKind::Try { |
181 | 0 | expr: Box::new(left), |
182 | 0 | }, |
183 | 0 | Span { start: 0, end: 0 }, |
184 | 0 | ); |
185 | 0 | handled_postfix = true; |
186 | 0 | } |
187 | 0 | _ => {} |
188 | | } |
189 | | } |
190 | 0 | Ok(left) |
191 | 0 | } |
192 | | |
193 | | /// Handle array indexing and slicing syntax `[expr]` or `[start:end]` |
194 | 0 | fn handle_array_indexing(state: &mut ParserState, left: Expr) -> Result<Expr> { |
195 | 0 | state.tokens.advance(); // consume [ |
196 | | |
197 | | // Check for empty slice [:end] |
198 | 0 | if is_colon_next(state) { |
199 | 0 | return parse_empty_start_slice(state, left); |
200 | 0 | } |
201 | | |
202 | 0 | let first_expr = parse_expr_recursive(state)?; |
203 | | |
204 | | // Check if this is a slice [start:end] or just indexing [index] |
205 | 0 | if is_colon_next(state) { |
206 | 0 | parse_slice_with_start(state, left, first_expr) |
207 | | } else { |
208 | 0 | parse_index_access(state, left, first_expr) |
209 | | } |
210 | 0 | } |
211 | | |
212 | | /// Check if next token is colon (complexity: 1) |
213 | 0 | fn is_colon_next(state: &mut ParserState) -> bool { |
214 | 0 | matches!(state.tokens.peek(), Some((Token::Colon, _))) |
215 | 0 | } |
216 | | |
217 | | /// Parse slice with empty start `[:end]` (complexity: 4) |
218 | 0 | fn parse_empty_start_slice(state: &mut ParserState, left: Expr) -> Result<Expr> { |
219 | 0 | state.tokens.advance(); // consume : |
220 | 0 | let end = parse_optional_slice_end(state)?; |
221 | 0 | state.tokens.expect(&Token::RightBracket)?; |
222 | | |
223 | 0 | Ok(create_slice_expr(left, None, end)) |
224 | 0 | } |
225 | | |
226 | | /// Parse slice with start `[start:end]` (complexity: 3) |
227 | 0 | fn parse_slice_with_start(state: &mut ParserState, left: Expr, start: Expr) -> Result<Expr> { |
228 | 0 | state.tokens.advance(); // consume : |
229 | 0 | let end = parse_optional_slice_end(state)?; |
230 | 0 | state.tokens.expect(&Token::RightBracket)?; |
231 | | |
232 | 0 | Ok(create_slice_expr(left, Some(Box::new(start)), end)) |
233 | 0 | } |
234 | | |
235 | | /// Parse optional slice end expression (complexity: 3) |
236 | 0 | fn parse_optional_slice_end(state: &mut ParserState) -> Result<Option<Box<Expr>>> { |
237 | 0 | if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) { |
238 | 0 | Ok(None) |
239 | | } else { |
240 | 0 | Ok(Some(Box::new(parse_expr_recursive(state)?))) |
241 | | } |
242 | 0 | } |
243 | | |
244 | | /// Parse index access `[index]` (complexity: 2) |
245 | 0 | fn parse_index_access(state: &mut ParserState, left: Expr, index: Expr) -> Result<Expr> { |
246 | 0 | state.tokens.expect(&Token::RightBracket)?; |
247 | | |
248 | 0 | Ok(Expr { |
249 | 0 | kind: ExprKind::IndexAccess { |
250 | 0 | object: Box::new(left), |
251 | 0 | index: Box::new(index), |
252 | 0 | }, |
253 | 0 | span: Span { start: 0, end: 0 }, |
254 | 0 | attributes: Vec::new(), |
255 | 0 | }) |
256 | 0 | } |
257 | | |
258 | | /// Create slice expression (complexity: 1) |
259 | 0 | fn create_slice_expr(object: Expr, start: Option<Box<Expr>>, end: Option<Box<Expr>>) -> Expr { |
260 | 0 | Expr { |
261 | 0 | kind: ExprKind::Slice { |
262 | 0 | object: Box::new(object), |
263 | 0 | start, |
264 | 0 | end, |
265 | 0 | }, |
266 | 0 | span: Span { start: 0, end: 0 }, |
267 | 0 | attributes: Vec::new(), |
268 | 0 | } |
269 | 0 | } |
270 | | |
271 | | |
272 | | /// Try to parse struct literal |
273 | 0 | fn try_parse_struct_literal(state: &mut ParserState, left: &Expr) -> Result<Option<Expr>> { |
274 | 0 | if let ExprKind::Identifier(name) = &left.kind { |
275 | 0 | if name.chars().next().is_some_and(char::is_uppercase) { |
276 | 0 | let name = name.clone(); |
277 | 0 | let span = left.span; |
278 | 0 | return Ok(Some(types::parse_struct_literal(state, name, span)?)); |
279 | 0 | } |
280 | 0 | } |
281 | 0 | Ok(None) |
282 | 0 | } |
283 | | |
284 | | /// Create post-increment expression |
285 | 0 | fn create_post_increment(left: Expr) -> Expr { |
286 | 0 | Expr { |
287 | 0 | kind: ExprKind::PostIncrement { |
288 | 0 | target: Box::new(left), |
289 | 0 | }, |
290 | 0 | span: Span { start: 0, end: 0 }, |
291 | 0 | attributes: Vec::new(), |
292 | 0 | } |
293 | 0 | } |
294 | | |
295 | | /// Create post-decrement expression |
296 | 0 | fn create_post_decrement(left: Expr) -> Expr { |
297 | 0 | Expr { |
298 | 0 | kind: ExprKind::PostDecrement { |
299 | 0 | target: Box::new(left), |
300 | 0 | }, |
301 | 0 | span: Span { start: 0, end: 0 }, |
302 | 0 | attributes: Vec::new(), |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | | |
307 | | /// Try to parse binary operators |
308 | 0 | fn try_binary_operators( |
309 | 0 | state: &mut ParserState, |
310 | 0 | left: Expr, |
311 | 0 | token: &Token, |
312 | 0 | min_prec: i32, |
313 | 0 | ) -> Result<Option<Expr>> { |
314 | 0 | if let Some(bin_op) = expressions::token_to_binary_op(token) { |
315 | 0 | let prec = expressions::get_precedence(bin_op); |
316 | 0 | if prec < min_prec { |
317 | 0 | return Ok(None); |
318 | 0 | } |
319 | | |
320 | 0 | state.tokens.advance(); |
321 | 0 | let right = parse_expr_with_precedence_recursive(state, prec + 1)?; |
322 | 0 | Ok(Some(Expr { |
323 | 0 | kind: ExprKind::Binary { |
324 | 0 | left: Box::new(left), |
325 | 0 | op: bin_op, |
326 | 0 | right: Box::new(right), |
327 | 0 | }, |
328 | 0 | span: Span { start: 0, end: 0 }, |
329 | 0 | attributes: Vec::new(), |
330 | 0 | })) |
331 | | } else { |
332 | 0 | Ok(None) |
333 | | } |
334 | 0 | } |
335 | | |
336 | | /// Try to parse type cast operator (as) - complexity: 5 |
337 | 0 | fn try_type_cast_operator( |
338 | 0 | state: &mut ParserState, |
339 | 0 | left: Expr, |
340 | 0 | token: &Token, |
341 | 0 | _min_prec: i32, |
342 | 0 | ) -> Result<Option<Expr>> { |
343 | 0 | if !matches!(token, Token::As) { |
344 | 0 | return Ok(None); |
345 | 0 | } |
346 | | |
347 | 0 | state.tokens.advance(); // consume 'as' |
348 | | |
349 | | // Get the target type |
350 | 0 | let target_type = match state.tokens.peek() { |
351 | 0 | Some((Token::Identifier(t), _)) => { |
352 | 0 | let type_name = t.clone(); |
353 | 0 | state.tokens.advance(); |
354 | 0 | type_name |
355 | | } |
356 | 0 | _ => bail!("Expected type name after 'as'"), |
357 | | }; |
358 | | |
359 | 0 | Ok(Some(Expr { |
360 | 0 | kind: ExprKind::TypeCast { |
361 | 0 | expr: Box::new(left), |
362 | 0 | target_type, |
363 | 0 | }, |
364 | 0 | span: Span { start: 0, end: 0 }, |
365 | 0 | attributes: Vec::new(), |
366 | 0 | })) |
367 | 0 | } |
368 | | |
369 | | /// Try to parse new actor operations (<- and <?) |
370 | 0 | fn try_new_actor_operators( |
371 | 0 | state: &mut ParserState, |
372 | 0 | left: Expr, |
373 | 0 | token: &Token, |
374 | 0 | min_prec: i32, |
375 | 0 | ) -> Result<Option<Expr>> { |
376 | 0 | let (expr_kind, _prec) = match token { |
377 | | Token::LeftArrow => { |
378 | | // Parse actor <- message |
379 | 0 | let prec = 1; // Same as assignment |
380 | 0 | if prec < min_prec { |
381 | 0 | return Ok(None); |
382 | 0 | } |
383 | 0 | state.tokens.advance(); |
384 | 0 | let message = parse_expr_with_precedence_recursive(state, prec)?; |
385 | 0 | (ExprKind::ActorSend { |
386 | 0 | actor: Box::new(left), |
387 | 0 | message: Box::new(message), |
388 | 0 | }, prec) |
389 | | } |
390 | | Token::ActorQuery => { |
391 | | // Parse actor <? message |
392 | 0 | let prec = 1; // Same as assignment |
393 | 0 | if prec < min_prec { |
394 | 0 | return Ok(None); |
395 | 0 | } |
396 | 0 | state.tokens.advance(); |
397 | 0 | let message = parse_expr_with_precedence_recursive(state, prec)?; |
398 | 0 | (ExprKind::ActorQuery { |
399 | 0 | actor: Box::new(left), |
400 | 0 | message: Box::new(message), |
401 | 0 | }, prec) |
402 | | } |
403 | 0 | _ => return Ok(None), |
404 | | }; |
405 | | |
406 | 0 | Ok(Some(Expr { |
407 | 0 | kind: expr_kind, |
408 | 0 | span: Span { start: 0, end: 0 }, |
409 | 0 | attributes: Vec::new(), |
410 | 0 | })) |
411 | 0 | } |
412 | | |
413 | | /// Try to parse assignment operators |
414 | 0 | fn try_assignment_operators( |
415 | 0 | state: &mut ParserState, |
416 | 0 | left: Expr, |
417 | 0 | token: &Token, |
418 | 0 | min_prec: i32, |
419 | 0 | ) -> Result<Option<Expr>> { |
420 | 0 | if !token.is_assignment_op() { |
421 | 0 | return Ok(None); |
422 | 0 | } |
423 | | |
424 | 0 | let prec = 1; |
425 | 0 | if prec < min_prec { |
426 | 0 | return Ok(None); |
427 | 0 | } |
428 | | |
429 | 0 | state.tokens.advance(); |
430 | 0 | let value = parse_expr_with_precedence_recursive(state, prec)?; |
431 | | |
432 | 0 | let expr = if *token == Token::Equal { |
433 | 0 | Expr { |
434 | 0 | kind: ExprKind::Assign { |
435 | 0 | target: Box::new(left), |
436 | 0 | value: Box::new(value), |
437 | 0 | }, |
438 | 0 | span: Span { start: 0, end: 0 }, |
439 | 0 | attributes: Vec::new(), |
440 | 0 | } |
441 | | } else { |
442 | 0 | let bin_op = get_compound_assignment_op(token); |
443 | 0 | Expr { |
444 | 0 | kind: ExprKind::CompoundAssign { |
445 | 0 | target: Box::new(left), |
446 | 0 | op: bin_op, |
447 | 0 | value: Box::new(value), |
448 | 0 | }, |
449 | 0 | span: Span { start: 0, end: 0 }, |
450 | 0 | attributes: Vec::new(), |
451 | 0 | } |
452 | | }; |
453 | 0 | Ok(Some(expr)) |
454 | 0 | } |
455 | | |
456 | | /// Get binary operator for compound assignment |
457 | 0 | fn get_compound_assignment_op(token: &Token) -> BinaryOp { |
458 | 0 | match token { |
459 | 0 | Token::PlusEqual => BinaryOp::Add, |
460 | 0 | Token::MinusEqual => BinaryOp::Subtract, |
461 | 0 | Token::StarEqual => BinaryOp::Multiply, |
462 | 0 | Token::SlashEqual => BinaryOp::Divide, |
463 | 0 | Token::PercentEqual => BinaryOp::Modulo, |
464 | 0 | Token::PowerEqual => BinaryOp::Power, |
465 | 0 | Token::AmpersandEqual => BinaryOp::BitwiseAnd, |
466 | 0 | Token::PipeEqual => BinaryOp::BitwiseOr, |
467 | 0 | Token::CaretEqual => BinaryOp::BitwiseXor, |
468 | 0 | Token::LeftShiftEqual => BinaryOp::LeftShift, |
469 | 0 | _ => unreachable!("Already checked is_assignment_op"), |
470 | | } |
471 | 0 | } |
472 | | |
473 | | /// Try to parse pipeline operators (>>) |
474 | 0 | fn try_pipeline_operators( |
475 | 0 | state: &mut ParserState, |
476 | 0 | left: Expr, |
477 | 0 | token: &Token, |
478 | 0 | min_prec: i32, |
479 | 0 | ) -> Result<Option<Expr>> { |
480 | 0 | if !matches!(token, Token::Pipeline) { |
481 | 0 | return Ok(None); |
482 | 0 | } |
483 | | |
484 | 0 | let prec = 3; |
485 | 0 | if prec < min_prec { |
486 | 0 | return Ok(None); |
487 | 0 | } |
488 | | |
489 | 0 | state.tokens.advance(); |
490 | 0 | let stage_expr = parse_expr_with_precedence_recursive(state, prec + 1)?; |
491 | | |
492 | 0 | let expr = if let ExprKind::Pipeline { expr, mut stages } = left.kind { |
493 | 0 | stages.push(PipelineStage { |
494 | 0 | op: Box::new(stage_expr), |
495 | 0 | span: Span { start: 0, end: 0 }, |
496 | 0 | }); |
497 | 0 | Expr { |
498 | 0 | kind: ExprKind::Pipeline { expr, stages }, |
499 | 0 | span: Span { start: 0, end: 0 }, |
500 | 0 | attributes: Vec::new(), |
501 | 0 | } |
502 | | } else { |
503 | 0 | Expr { |
504 | 0 | kind: ExprKind::Pipeline { |
505 | 0 | expr: Box::new(left), |
506 | 0 | stages: vec![PipelineStage { |
507 | 0 | op: Box::new(stage_expr), |
508 | 0 | span: Span { start: 0, end: 0 }, |
509 | 0 | }], |
510 | 0 | }, |
511 | 0 | span: Span { start: 0, end: 0 }, |
512 | 0 | attributes: Vec::new(), |
513 | 0 | } |
514 | | }; |
515 | 0 | Ok(Some(expr)) |
516 | 0 | } |
517 | | |
518 | | /// Try to parse range operators (.., ..=) |
519 | 0 | fn try_range_operators( |
520 | 0 | state: &mut ParserState, |
521 | 0 | left: Expr, |
522 | 0 | token: &Token, |
523 | 0 | min_prec: i32, |
524 | 0 | ) -> Result<Option<Expr>> { |
525 | 0 | if !matches!(token, Token::DotDot | Token::DotDotEqual) { |
526 | 0 | return Ok(None); |
527 | 0 | } |
528 | | |
529 | 0 | let prec = 5; |
530 | 0 | if prec < min_prec { |
531 | 0 | return Ok(None); |
532 | 0 | } |
533 | | |
534 | 0 | let inclusive = matches!(token, Token::DotDotEqual); |
535 | 0 | state.tokens.advance(); |
536 | 0 | let end = parse_expr_with_precedence_recursive(state, prec + 1)?; |
537 | 0 | Ok(Some(Expr { |
538 | 0 | kind: ExprKind::Range { |
539 | 0 | start: Box::new(left), |
540 | 0 | end: Box::new(end), |
541 | 0 | inclusive, |
542 | 0 | }, |
543 | 0 | span: Span { start: 0, end: 0 }, |
544 | 0 | attributes: Vec::new(), |
545 | 0 | })) |
546 | 0 | } |