Skip to main content

shape_ast/parser/expressions/control_flow/
loops.rs

1//! Loop and flow control expression parsing
2//!
3//! This module handles parsing of loop constructs and flow control:
4//! - While loops
5//! - For loops
6//! - Infinite loops
7//! - Let expressions
8//! - Break expressions
9//! - Return expressions
10//! - Block expressions
11
12use crate::ast::{
13    Assignment, AsyncLetExpr, BlockExpr, BlockItem, Expr, ForExpr, IfStatement, LetExpr, LoopExpr,
14    Span, Statement, WhileExpr,
15};
16use crate::error::{Result, ShapeError};
17use crate::parser::Rule;
18use pest::iterators::Pair;
19
20use super::super::super::pair_span;
21use super::pattern_matching::parse_pattern;
22use crate::parser::pair_location;
23
24/// Parse while expression
25pub fn parse_while_expr(pair: Pair<Rule>) -> Result<Expr> {
26    let span = pair_span(&pair);
27    let pair_loc = pair_location(&pair);
28    let mut inner = pair.into_inner();
29
30    let condition_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
31        message: "expected condition in while expression".to_string(),
32        location: Some(pair_loc.clone()),
33    })?;
34    let condition = super::super::parse_expression(condition_pair)?;
35    let body_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
36        message: "expected body in while expression".to_string(),
37        location: Some(pair_loc),
38    })?;
39    let body = parse_block_expr(body_pair)?;
40
41    Ok(Expr::While(
42        Box::new(WhileExpr {
43            condition: Box::new(condition),
44            body: Box::new(body),
45        }),
46        span,
47    ))
48}
49
50/// Parse for expression (including `for await x in stream { }`)
51pub fn parse_for_expr(pair: Pair<Rule>) -> Result<Expr> {
52    let span = pair_span(&pair);
53    let pair_loc = pair_location(&pair);
54    // Detect `for await` by checking the raw text
55    let is_async = pair.as_str().trim_start().starts_with("for") && pair.as_str().contains("await");
56    let mut inner = pair.into_inner();
57
58    let for_clause = inner.next().ok_or_else(|| ShapeError::ParseError {
59        message: "expected for clause".to_string(),
60        location: Some(pair_loc.clone()),
61    })?;
62    let clause_loc = pair_location(&for_clause);
63    let mut clause_inner = for_clause.into_inner();
64    let pattern_pair = clause_inner.next().ok_or_else(|| ShapeError::ParseError {
65        message: "expected pattern in for loop".to_string(),
66        location: Some(clause_loc.clone()),
67    })?;
68    let pattern = parse_pattern(pattern_pair)?;
69    let iterable_pair = clause_inner.next().ok_or_else(|| ShapeError::ParseError {
70        message: "expected iterable expression in for loop".to_string(),
71        location: Some(clause_loc),
72    })?;
73    let iterable = super::super::parse_expression(iterable_pair)?;
74
75    let body_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
76        message: "expected body in for expression".to_string(),
77        location: Some(pair_loc),
78    })?;
79    let body = parse_block_expr(body_pair)?;
80
81    Ok(Expr::For(
82        Box::new(ForExpr {
83            pattern,
84            iterable: Box::new(iterable),
85            body: Box::new(body),
86            is_async,
87        }),
88        span,
89    ))
90}
91
92/// Parse loop expression
93pub fn parse_loop_expr(pair: Pair<Rule>) -> Result<Expr> {
94    let span = pair_span(&pair);
95    let pair_loc = pair_location(&pair);
96    let mut inner = pair.into_inner();
97    let body_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
98        message: "expected body in loop expression".to_string(),
99        location: Some(pair_loc),
100    })?;
101    let body = parse_block_expr(body_pair)?;
102
103    Ok(Expr::Loop(
104        Box::new(LoopExpr {
105            body: Box::new(body),
106        }),
107        span,
108    ))
109}
110
111/// Parse let expression
112pub fn parse_let_expr(pair: Pair<Rule>) -> Result<Expr> {
113    let span = pair_span(&pair);
114    let pair_loc = pair_location(&pair);
115    let mut inner = pair.into_inner();
116
117    let pattern_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
118        message: "expected pattern in let expression".to_string(),
119        location: Some(pair_loc.clone()),
120    })?;
121    let pattern = parse_pattern(pattern_pair)?;
122
123    // Check if there's an initializer
124    let mut value = None;
125    let mut body_expr = None;
126
127    for next_pair in inner {
128        if next_pair.as_rule() == Rule::expression {
129            if value.is_none() {
130                value = Some(Box::new(super::super::parse_expression(next_pair)?));
131            } else {
132                body_expr = Some(super::super::parse_expression(next_pair)?);
133            }
134        }
135    }
136
137    let body = body_expr.ok_or_else(|| ShapeError::ParseError {
138        message: "let expression missing body".to_string(),
139        location: Some(pair_loc),
140    })?;
141
142    Ok(Expr::Let(
143        Box::new(LetExpr {
144            pattern,
145            type_annotation: None, // Add missing field
146            value,
147            body: Box::new(body),
148        }),
149        span,
150    ))
151}
152
153/// Parse break expression
154pub fn parse_break_expr(pair: Pair<Rule>) -> Result<Expr> {
155    let span = pair_span(&pair);
156    // Skip the break_keyword child pair — only look for an optional expression.
157    let mut inner = pair
158        .into_inner()
159        .filter(|p| p.as_rule() != Rule::break_keyword);
160    let value = if let Some(expr) = inner.next() {
161        Some(Box::new(super::super::parse_expression(expr)?))
162    } else {
163        None
164    };
165    Ok(Expr::Break(value, span))
166}
167
168/// Parse return expression
169pub fn parse_return_expr(pair: Pair<Rule>) -> Result<Expr> {
170    let span = pair_span(&pair);
171    // The "return" keyword starts at the beginning of this pair.
172    let keyword_line = pair.as_span().start_pos().line_col().0;
173    // Skip the return_keyword child pair — only look for an optional expression.
174    let mut inner = pair
175        .into_inner()
176        .filter(|p| p.as_rule() != Rule::return_keyword);
177    let value = if let Some(expr) = inner.next() {
178        // Only treat as `return <expr>` if the expression starts on the same
179        // line as `return`. The grammar greedily consumes the next expression
180        // even across newlines; bare `return` on its own line should be a
181        // void return, not `return <next-line-expr>`.
182        let expr_line = expr.as_span().start_pos().line_col().0;
183        if expr_line > keyword_line {
184            None
185        } else {
186            Some(Box::new(super::super::parse_expression(expr)?))
187        }
188    } else {
189        None
190    };
191    Ok(Expr::Return(value, span))
192}
193
194/// Parse block expression
195///
196/// The PEG grammar uses `(block_statement ~ ";"?)* ~ block_item?` where `";"?`
197/// is silent/optional. To implement semicolon-suppresses-return semantics
198/// (`{ 1; }` yields `()` while `{ 1 }` yields `1`), we inspect the raw source
199/// text after each `block_statement` span to detect whether a semicolon was
200/// actually present.
201pub fn parse_block_expr(pair: Pair<Rule>) -> Result<Expr> {
202    let span = pair_span(&pair);
203    let mut items = Vec::new();
204    let mut had_semi = Vec::new();
205
206    if let Some(block_items) = pair.into_inner().next() {
207        let source = block_items.as_str();
208        let block_start = block_items.as_span().start();
209        // Collect all inner pairs to analyze them
210        let inner_pairs: Vec<_> = block_items.into_inner().collect();
211
212        // Process each item, tracking whether it's a statement (with semicolon) or final expression
213        for item_pair in inner_pairs {
214            match item_pair.as_rule() {
215                Rule::block_statement => {
216                    // Detect if a semicolon follows this block_statement in the source
217                    let stmt_end = item_pair.as_span().end();
218                    let offset = stmt_end - block_start;
219                    let has_semicolon = source[offset..].starts_with(';')
220                        || source[offset..].trim_start().starts_with(';');
221
222                    let inner = item_pair.into_inner().next().unwrap();
223                    let inner_span = pair_span(&inner);
224                    let block_item = parse_block_entry(inner)?;
225                    // If a semicolon follows, ensure expressions become statements
226                    // so they don't produce a value on the stack
227                    let block_item = if has_semicolon {
228                        expr_to_statement(block_item, inner_span)
229                    } else {
230                        block_item
231                    };
232                    had_semi.push(has_semicolon);
233                    items.push(block_item);
234                }
235                Rule::block_item => {
236                    // This is the final expression without semicolon - the block's value
237                    let inner = item_pair.into_inner().next().unwrap();
238                    let block_item = parse_block_entry(inner)?;
239                    // Convert tail-position if-statement to a conditional expression
240                    // so the block evaluates to the if's value.
241                    let block_item = if_stmt_to_tail_expr(block_item);
242                    had_semi.push(false);
243                    items.push(block_item);
244                }
245                _ => {} // Skip other tokens
246            }
247        }
248    }
249
250    // Empty blocks evaluate to Unit
251    if items.is_empty() {
252        return Ok(Expr::Unit(span));
253    }
254
255    // Only promote the last item to a tail expression if it did NOT have a
256    // trailing semicolon. When it did, the expression was already wrapped as
257    // a Statement by expr_to_statement above, and the compiler's
258    // compile_expr_block will emit unit for the missing tail value.
259    if let Some(&last_had_semi) = had_semi.last() {
260        if !last_had_semi {
261            if let Some(last) = items.pop() {
262                items.push(if_stmt_to_tail_expr(last));
263            }
264        }
265    }
266
267    Ok(Expr::Block(BlockExpr { items }, span))
268}
269
270/// Convert a `BlockItem::Expression` to a `BlockItem::Statement` so the
271/// compiler treats it as a side-effect (pops the value) rather than keeping
272/// it as the block's return value.
273fn expr_to_statement(item: BlockItem, span: Span) -> BlockItem {
274    match item {
275        BlockItem::Expression(expr) => BlockItem::Statement(Statement::Expression(expr, span)),
276        other => other,
277    }
278}
279
280/// Convert a tail-position `if` statement into a conditional expression so the
281/// block evaluates to the value of the `if` rather than discarding it.
282///
283/// Only `BlockItem::Statement(Statement::If(..))` is converted; every other
284/// variant is returned unchanged.
285fn if_stmt_to_tail_expr(item: BlockItem) -> BlockItem {
286    match item {
287        BlockItem::Statement(Statement::If(if_stmt, span)) => {
288            BlockItem::Expression(if_stmt_to_conditional(if_stmt, span))
289        }
290        other => other,
291    }
292}
293
294/// Recursively convert an `IfStatement` (statement form) into an
295/// `Expr::Conditional` (expression form) by wrapping each branch's
296/// `Vec<Statement>` in a `Block` expression.
297fn if_stmt_to_conditional(if_stmt: IfStatement, span: Span) -> Expr {
298    let then_expr = stmts_to_block_expr(if_stmt.then_body, span);
299
300    let else_expr = if_stmt.else_body.map(|stmts| {
301        // An `else if` is represented as a single Statement::If inside the vec.
302        if stmts.len() == 1 && matches!(stmts.first(), Some(Statement::If(..))) {
303            let mut iter = stmts.into_iter();
304            if let Some(Statement::If(nested_if, nested_span)) = iter.next() {
305                return Box::new(if_stmt_to_conditional(nested_if, nested_span));
306            }
307            // The matches! guard above ensures we have Statement::If, so this
308            // path is not reachable. Fall through to stmts_to_block_expr with
309            // an empty vec if it ever were.
310            return Box::new(stmts_to_block_expr(Vec::new(), span));
311        }
312        Box::new(stmts_to_block_expr(stmts, span))
313    });
314
315    Expr::Conditional {
316        condition: Box::new(if_stmt.condition),
317        then_expr: Box::new(then_expr),
318        else_expr,
319        span,
320    }
321}
322
323/// Wrap a `Vec<Statement>` as a block expression whose last entry
324/// is promoted to `BlockItem::Expression` when possible, so it
325/// produces a value on the stack.
326fn stmts_to_block_expr(stmts: Vec<Statement>, span: Span) -> Expr {
327    if stmts.is_empty() {
328        return Expr::Unit(span);
329    }
330    let len = stmts.len();
331    let mut items: Vec<BlockItem> = Vec::with_capacity(len);
332    for (i, s) in stmts.into_iter().enumerate() {
333        let is_last = i == len - 1;
334        if is_last {
335            match s {
336                // Promote trailing expression-statement to a block expression item
337                Statement::Expression(expr, _) => {
338                    items.push(BlockItem::Expression(expr));
339                }
340                // Promote trailing nested if to a conditional expression (recursively)
341                Statement::If(nested_if, nested_span) => {
342                    items.push(BlockItem::Expression(if_stmt_to_conditional(
343                        nested_if,
344                        nested_span,
345                    )));
346                }
347                other => {
348                    items.push(BlockItem::Statement(other));
349                }
350            }
351        } else {
352            items.push(BlockItem::Statement(s));
353        }
354    }
355    Expr::Block(BlockExpr { items }, span)
356}
357
358fn parse_block_entry(inner: Pair<Rule>) -> Result<BlockItem> {
359    match inner.as_rule() {
360        Rule::return_stmt => {
361            let return_span = pair_span(&inner);
362            let value = inner
363                .into_inner()
364                .filter(|p| p.as_rule() != Rule::return_keyword)
365                .next()
366                .map(|expr_pair| super::super::parse_expression(expr_pair))
367                .transpose()?
368                .map(Box::new);
369            Ok(BlockItem::Expression(Expr::Return(value, return_span)))
370        }
371        Rule::variable_decl => {
372            let decl = crate::parser::parse_variable_decl(inner)?;
373            Ok(BlockItem::VariableDecl(decl))
374        }
375        Rule::assignment => {
376            let mut inner = inner.into_inner();
377            let pattern = crate::parser::parse_pattern(inner.next().unwrap())?;
378            let value = super::super::parse_expression(inner.next().unwrap())?;
379            Ok(BlockItem::Assignment(Assignment { pattern, value }))
380        }
381        Rule::expression => {
382            let expr = super::super::parse_expression(inner)?;
383            Ok(BlockItem::Expression(expr))
384        }
385        Rule::if_stmt => {
386            let stmt = crate::parser::statements::parse_if_stmt(inner)?;
387            Ok(BlockItem::Statement(stmt))
388        }
389        Rule::for_loop => {
390            let stmt = crate::parser::statements::parse_for_loop(inner)?;
391            Ok(BlockItem::Statement(stmt))
392        }
393        Rule::while_loop => {
394            let stmt = crate::parser::statements::parse_while_loop(inner)?;
395            Ok(BlockItem::Statement(stmt))
396        }
397        Rule::extend_statement => {
398            let span = pair_span(&inner);
399            let ext = crate::parser::extensions::parse_extend_statement(inner)?;
400            Ok(BlockItem::Statement(crate::ast::Statement::Extend(
401                ext, span,
402            )))
403        }
404        Rule::remove_target_stmt => Ok(BlockItem::Statement(crate::ast::Statement::RemoveTarget(
405            pair_span(&inner),
406        ))),
407        Rule::set_param_value_stmt => {
408            let span = pair_span(&inner);
409            let mut parts = inner.into_inner();
410            let param_pair = parts.next().ok_or_else(|| ShapeError::ParseError {
411                message: "expected parameter name in `set param` value directive".to_string(),
412                location: None,
413            })?;
414            let expr_pair = parts.next().ok_or_else(|| ShapeError::ParseError {
415                message: "expected expression in `set param` value directive".to_string(),
416                location: None,
417            })?;
418            let expression = super::super::parse_expression(expr_pair)?;
419            Ok(BlockItem::Statement(crate::ast::Statement::SetParamValue {
420                param_name: param_pair.as_str().to_string(),
421                expression,
422                span,
423            }))
424        }
425        Rule::set_param_type_stmt => {
426            let span = pair_span(&inner);
427            let mut parts = inner.into_inner();
428            let param_pair = parts.next().ok_or_else(|| ShapeError::ParseError {
429                message: "expected parameter name in `set param` directive".to_string(),
430                location: None,
431            })?;
432            let type_pair = parts.next().ok_or_else(|| ShapeError::ParseError {
433                message: "expected type annotation in `set param` directive".to_string(),
434                location: None,
435            })?;
436            let type_annotation = crate::parser::types::parse_type_annotation(type_pair)?;
437            Ok(BlockItem::Statement(crate::ast::Statement::SetParamType {
438                param_name: param_pair.as_str().to_string(),
439                type_annotation,
440                span,
441            }))
442        }
443        Rule::set_return_stmt => {
444            let span = pair_span(&inner);
445            let mut parts = inner.into_inner();
446            let payload_pair = parts.next().ok_or_else(|| ShapeError::ParseError {
447                message: "expected type annotation or expression in `set return` directive"
448                    .to_string(),
449                location: None,
450            })?;
451            match payload_pair.as_rule() {
452                Rule::type_annotation => {
453                    let type_annotation =
454                        crate::parser::types::parse_type_annotation(payload_pair)?;
455                    Ok(BlockItem::Statement(crate::ast::Statement::SetReturnType {
456                        type_annotation,
457                        span,
458                    }))
459                }
460                Rule::set_return_expr_payload => {
461                    let expr_pair =
462                        payload_pair
463                            .into_inner()
464                            .next()
465                            .ok_or_else(|| ShapeError::ParseError {
466                                message:
467                                    "expected expression in parenthesized `set return` directive"
468                                        .to_string(),
469                                location: None,
470                            })?;
471                    let expression = super::super::parse_expression(expr_pair)?;
472                    Ok(BlockItem::Statement(crate::ast::Statement::SetReturnExpr {
473                        expression,
474                        span,
475                    }))
476                }
477                _ => Err(ShapeError::ParseError {
478                    message: "expected type annotation or expression in `set return` directive"
479                        .to_string(),
480                    location: None,
481                }),
482            }
483        }
484        Rule::replace_body_stmt => {
485            let span = pair_span(&inner);
486            let mut parts = inner.into_inner();
487            let Some(payload) = parts.next() else {
488                return Ok(BlockItem::Statement(crate::ast::Statement::ReplaceBody {
489                    body: Vec::new(),
490                    span,
491                }));
492            };
493            match payload.as_rule() {
494                Rule::replace_body_expr_payload => {
495                    let expr_pair =
496                        payload
497                            .into_inner()
498                            .next()
499                            .ok_or_else(|| ShapeError::ParseError {
500                                message:
501                                    "expected expression in parenthesized `replace body` directive"
502                                        .to_string(),
503                                location: None,
504                            })?;
505                    let expression = super::super::parse_expression(expr_pair)?;
506                    Ok(BlockItem::Statement(
507                        crate::ast::Statement::ReplaceBodyExpr { expression, span },
508                    ))
509                }
510                Rule::statement => {
511                    let mut body = Vec::new();
512                    body.push(crate::parser::statements::parse_statement(payload)?);
513                    body.extend(crate::parser::statements::parse_statements(parts)?);
514                    Ok(BlockItem::Statement(crate::ast::Statement::ReplaceBody {
515                        body,
516                        span,
517                    }))
518                }
519                _ => Err(ShapeError::ParseError {
520                    message: "expected body block or expression in `replace body` directive"
521                        .to_string(),
522                    location: None,
523                }),
524            }
525        }
526        Rule::replace_module_stmt => {
527            let span = pair_span(&inner);
528            let mut parts = inner.into_inner();
529            let payload = parts.next().ok_or_else(|| ShapeError::ParseError {
530                message: "expected expression payload in `replace module` directive".to_string(),
531                location: None,
532            })?;
533            if payload.as_rule() != Rule::replace_module_expr_payload {
534                return Err(ShapeError::ParseError {
535                    message: "expected parenthesized expression in `replace module` directive"
536                        .to_string(),
537                    location: None,
538                });
539            }
540            let expr_pair = payload
541                .into_inner()
542                .next()
543                .ok_or_else(|| ShapeError::ParseError {
544                    message: "expected expression in parenthesized `replace module` directive"
545                        .to_string(),
546                    location: None,
547                })?;
548            let expression = super::super::parse_expression(expr_pair)?;
549            Ok(BlockItem::Statement(
550                crate::ast::Statement::ReplaceModuleExpr { expression, span },
551            ))
552        }
553        // Nested function definition: desugar `fn name(params) { body }` inside a block
554        // to `let name = fn(params) { body }` (a VariableDecl with a FunctionExpr value).
555        Rule::function_def => {
556            let span = pair_span(&inner);
557            let func_def = crate::parser::functions::parse_function_def(inner)?;
558            let func_expr = Expr::FunctionExpr {
559                params: func_def.params,
560                return_type: func_def.return_type,
561                body: func_def.body,
562                span,
563            };
564            Ok(BlockItem::VariableDecl(crate::ast::VariableDecl {
565                kind: crate::ast::VarKind::Let,
566                is_mut: false,
567                pattern: crate::ast::DestructurePattern::Identifier(func_def.name, span),
568                type_annotation: None,
569                value: Some(func_expr),
570                ownership: Default::default(),
571            }))
572        }
573        _ => Err(ShapeError::ParseError {
574            message: format!("Unexpected block entry: {:?}", inner.as_rule()),
575            location: None,
576        }),
577    }
578}
579
580/// Parse async let expression: `async let name = expr`
581/// Spawns a task and binds a future handle to a local variable.
582pub fn parse_async_let_expr(pair: Pair<Rule>) -> Result<Expr> {
583    let span = pair_span(&pair);
584    let pair_loc = pair_location(&pair);
585    let mut inner = pair.into_inner();
586
587    let name_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
588        message: "expected variable name in async let".to_string(),
589        location: Some(pair_loc.clone()),
590    })?;
591    let name = name_pair.as_str().to_string();
592
593    let expr_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
594        message: "expected expression in async let".to_string(),
595        location: Some(pair_loc),
596    })?;
597    let expr = super::super::parse_expression(expr_pair)?;
598
599    Ok(Expr::AsyncLet(
600        Box::new(AsyncLetExpr {
601            name,
602            expr: Box::new(expr),
603            span,
604        }),
605        span,
606    ))
607}
608
609/// Parse async scope expression: `async scope { ... }`
610/// Cancellation boundary -- on scope exit, all pending tasks are cancelled in reverse order.
611pub fn parse_async_scope_expr(pair: Pair<Rule>) -> Result<Expr> {
612    let span = pair_span(&pair);
613    let pair_loc = pair_location(&pair);
614    let mut inner = pair.into_inner();
615
616    let body_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
617        message: "expected body in async scope".to_string(),
618        location: Some(pair_loc),
619    })?;
620    let body = parse_block_expr(body_pair)?;
621
622    Ok(Expr::AsyncScope(Box::new(body), span))
623}