use crate::frontend::ast::{Expr, ExprKind, Pattern};
use crate::frontend::lexer::Token;
use crate::frontend::parser::{bail, parse_expr_recursive, ParserState, Result};
use super::super::{parse_list_pattern, parse_match_pattern, parse_tuple_pattern};
pub(in crate::frontend::parser) fn parse_loop_label(
state: &mut ParserState,
label_name: String,
) -> Result<Expr> {
state.tokens.expect(&Token::Colon)?;
match state.tokens.peek() {
Some((Token::For, _)) => parse_labeled_for_loop(state, Some(label_name)),
Some((Token::While, _)) => parse_labeled_while_loop(state, Some(label_name)),
Some((Token::Loop, _)) => parse_labeled_loop(state, Some(label_name)),
_ => bail!("Expected loop keyword after label"),
}
}
pub(in crate::frontend::parser) fn parse_while_loop(state: &mut ParserState) -> Result<Expr> {
parse_labeled_while_loop(state, None)
}
fn parse_labeled_while_loop(state: &mut ParserState, label: Option<String>) -> Result<Expr> {
let start_span = state.tokens.expect(&Token::While)?;
if matches!(state.tokens.peek(), Some((Token::Let, _))) {
state.tokens.advance(); let pattern = parse_match_pattern(state)
.map_err(|e| anyhow::anyhow!("Expected pattern after 'while let': {e}"))?;
state
.tokens
.expect(&Token::Equal)
.map_err(|e| anyhow::anyhow!("Expected '=' after pattern in while-let: {e}"))?;
let expr = Box::new(
parse_expr_recursive(state)
.map_err(|e| anyhow::anyhow!("Expected expression after '=' in while-let: {e}"))?,
);
let body = Box::new(
parse_expr_recursive(state)
.map_err(|e| anyhow::anyhow!("Expected body after while-let condition: {e}"))?,
);
Ok(Expr::new(
ExprKind::WhileLet {
label,
pattern,
expr,
body,
},
start_span,
))
} else {
let condition = Box::new(
parse_expr_recursive(state)
.map_err(|e| anyhow::anyhow!("Expected condition after 'while': {e}"))?,
);
let body = Box::new(
parse_expr_recursive(state)
.map_err(|e| anyhow::anyhow!("Expected body after while condition: {e}"))?,
);
Ok(Expr::new(
ExprKind::While {
label,
condition,
body,
},
start_span,
))
}
}
pub(in crate::frontend::parser) fn parse_for_loop(state: &mut ParserState) -> Result<Expr> {
parse_labeled_for_loop(state, None)
}
fn parse_labeled_for_loop(state: &mut ParserState, label: Option<String>) -> Result<Expr> {
let start_span = state.tokens.expect(&Token::For)?;
let pattern = parse_for_pattern(state)?;
state
.tokens
.expect(&Token::In)
.map_err(|_| anyhow::anyhow!("Expected 'in' after for pattern"))?;
let iterator = Box::new(
parse_expr_recursive(state)
.map_err(|e| anyhow::anyhow!("Expected iterator after 'in': {e}"))?,
);
let body = Box::new(
parse_expr_recursive(state)
.map_err(|e| anyhow::anyhow!("Expected body after for iterator: {e}"))?,
);
let var = pattern.primary_name();
Ok(Expr::new(
ExprKind::For {
label,
var,
pattern: Some(pattern),
iter: iterator,
body,
},
start_span,
))
}
fn parse_for_pattern(state: &mut ParserState) -> Result<Pattern> {
let Some((token, _)) = state.tokens.peek() else {
bail!("Expected pattern in for loop");
};
match token {
Token::Identifier(name) => {
let name = name.clone();
state.tokens.advance();
if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
let mut patterns = vec![Pattern::Identifier(name)];
while matches!(state.tokens.peek(), Some((Token::Comma, _))) {
state.tokens.advance(); if let Some((Token::Identifier(next_name), _)) = state.tokens.peek() {
let next_name = next_name.clone();
state.tokens.advance();
patterns.push(Pattern::Identifier(next_name));
} else {
bail!("Expected identifier after comma in tuple pattern");
}
}
Ok(Pattern::Tuple(patterns))
} else {
Ok(Pattern::Identifier(name))
}
}
Token::Underscore => {
state.tokens.advance();
Ok(Pattern::Wildcard)
}
Token::LeftParen => {
parse_tuple_pattern(state)
}
Token::LeftBracket => {
parse_list_pattern(state)
}
_ => bail!("Expected identifier, underscore, or destructuring pattern in for loop"),
}
}
pub(in crate::frontend::parser) fn parse_loop(state: &mut ParserState) -> Result<Expr> {
parse_labeled_loop(state, None)
}
fn parse_labeled_loop(state: &mut ParserState, label: Option<String>) -> Result<Expr> {
let start_span = state.tokens.expect(&Token::Loop)?;
let body = Box::new(parse_expr_recursive(state)?);
Ok(Expr::new(ExprKind::Loop { label, body }, start_span))
}
#[cfg(test)]
mod tests {
use crate::frontend::ast::ExprKind;
use crate::frontend::parser::Parser;
#[test]
fn test_for_loop_simple() {
let code = "for i in 0..10 { print(i) }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Simple for loop should parse");
}
#[test]
fn test_for_loop_tuple_destructuring() {
let code = "for key, value in map { print(key) }";
let result = Parser::new(code).parse();
assert!(
result.is_ok(),
"For loop with tuple destructuring should parse"
);
}
#[test]
fn test_for_loop_tuple_with_parens() {
let code = "for (x, y) in pairs { print(x) }";
let result = Parser::new(code).parse();
assert!(
result.is_ok(),
"For loop with tuple pattern (parens) should parse"
);
}
#[test]
fn test_for_loop_wildcard() {
let code = "for _ in 0..10 { side_effect() }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop with wildcard should parse");
}
#[test]
fn test_while_loop() {
let code = "while x < 10 { x = x + 1 }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While loop should parse");
}
#[test]
fn test_while_let_loop() {
let code = "while let Some(value) = optional { process(value) }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While-let loop should parse");
}
#[test]
fn test_infinite_loop() {
let code = "loop { if done { break } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Infinite loop should parse");
}
#[test]
fn test_labeled_for_loop() {
let code = "'outer: for i in 0..10 { break 'outer }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Labeled for loop should parse");
}
#[test]
fn test_labeled_while_loop() {
let code = "'outer: while true { break 'outer }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Labeled while loop should parse");
}
#[test]
fn test_labeled_infinite_loop() {
let code = "'outer: loop { break 'outer }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Labeled infinite loop should parse");
}
#[test]
fn test_for_loop_over_array() {
let code = "for item in [1, 2, 3] { print(item) }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop over array should parse");
}
#[test]
fn test_for_loop_over_function_call() {
let code = "for x in get_items() { print(x) }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop over function call should parse");
}
#[test]
fn test_for_loop_with_empty_body() {
let code = "for i in 0..10 { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop with empty body should parse");
}
#[test]
fn test_while_loop_with_true() {
let code = "while true { break }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While true loop should parse");
}
#[test]
fn test_while_loop_with_false() {
let code = "while false { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While false loop should parse");
}
#[test]
fn test_while_loop_compound_condition() {
let code = "while x > 0 && y < 100 { x = x - 1 }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While with compound condition should parse");
}
#[test]
fn test_while_let_with_tuple() {
let code = "while let (a, b) = get_pair() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While let with tuple should parse");
}
#[test]
fn test_while_let_with_none() {
let code = "while let None = maybe { break }";
let result = Parser::new(code).parse();
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_loop_with_empty_body() {
let code = "loop { break }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Loop with break should parse");
}
#[test]
fn test_for_loop_over_range_inclusive() {
let code = "for i in 0..=10 { print(i) }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop over inclusive range should parse");
}
#[test]
fn test_for_loop_three_element_tuple() {
let code = "for a, b, c in triples { print(a) }";
let result = Parser::new(code).parse();
assert!(
result.is_ok(),
"For loop with three element tuple should parse"
);
}
#[test]
fn test_nested_for_loops() {
let code = "for i in 0..10 { for j in 0..10 { } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Nested for loops should parse");
}
#[test]
fn test_for_inside_while() {
let code = "while running { for x in items { } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For inside while should parse");
}
#[test]
fn test_while_inside_for() {
let code = "for x in items { while processing { } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While inside for should parse");
}
#[test]
fn test_loop_inside_loop() {
let code = "loop { loop { break } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Nested loop should parse");
}
#[test]
fn test_for_loop_over_method_call() {
let code = "for x in items.iter() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop over method call should parse");
}
#[test]
fn test_for_loop_list_destructuring() {
let code = "for [a, b] in pairs { print(a) }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop with list pattern should parse");
}
#[test]
fn test_while_loop_with_parenthesized_condition() {
let code = "while (x > 0) { }";
let result = Parser::new(code).parse();
assert!(
result.is_ok(),
"While with parenthesized condition should parse"
);
}
#[test]
fn test_for_loop_with_underscore_var() {
let code = "for _unused in 0..10 { }";
let result = Parser::new(code).parse();
assert!(
result.is_ok(),
"For loop with underscore prefix should parse"
);
}
#[test]
fn test_for_loop_enumerate() {
let code = "for i, item in items.enumerate() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop with enumerate should parse");
}
#[test]
fn test_while_complex_condition() {
let code = "while x > 0 && y < 100 { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While with complex condition should parse");
}
#[test]
fn test_loop_with_return() {
let code = "fun f() { loop { return 42 } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Loop with return should parse");
}
#[test]
fn test_for_loop_filter_chain() {
let code = "for x in items.filter(|x| x > 0) { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop with filter should parse");
}
#[test]
fn test_while_let_tuple() {
let code = "while let (a, b) = get_pair() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While let with tuple should parse");
}
#[test]
fn test_for_in_function() {
let code = "fun process(items) { for x in items { print(x) } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop in function should parse");
}
#[test]
fn test_while_method_condition() {
let code = "while queue.is_not_empty() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While with method condition should parse");
}
#[test]
fn test_nested_while_loops() {
let code = "while a { while b { } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Nested while loops should parse");
}
#[test]
fn test_for_loop_map_chain() {
let code = "for x in items.map(|x| x * 2) { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop with map should parse");
}
#[test]
fn test_loop_labeled_break() {
let code = "'outer: loop { loop { break 'outer } }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Loop with labeled break should parse");
}
#[test]
fn test_labeled_for_loop_label_is_set() {
let code = "'outer: for i in [1, 2] { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Labeled for loop should parse");
let expr = result.unwrap();
let for_expr = match &expr.kind {
ExprKind::Block(exprs) if !exprs.is_empty() => &exprs[0],
_ => &expr,
};
match &for_expr.kind {
ExprKind::For { label, var, .. } => {
assert!(
label.is_some(),
"For loop label should be Some, got None. var={:?}",
var
);
let label_val = label.as_ref().unwrap();
assert!(
!label_val.starts_with('\''),
"Label should not start with quote, got: {:?}",
label_val
);
}
other => panic!("Expected For expression, got: {:?}", other),
}
}
#[test]
fn test_for_loop_array_literal() {
let code = "for x in [1, 2, 3] { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop over array literal should parse");
}
#[test]
fn test_while_assignment_block() {
let code = "while true { let x = 1 }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "While with assignment should parse");
}
#[test]
fn test_for_loop_indexed() {
let code = "for (i, v) in items.enumerate() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "For loop with tuple pattern should parse");
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
#[ignore = "Property tests run with --ignored flag"] fn prop_for_loops_never_panic(var in "[a-z]+", n in 0u32..100) {
let code = format!("for {var} in 0..{n} {{ }}");
let _ = Parser::new(&code).parse(); }
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_while_loops_never_panic(var in "[a-z]+", n in 0i32..100) {
let code = format!("while {var} < {n} {{ }}");
let _ = Parser::new(&code).parse(); }
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_infinite_loops_always_parse(_seed in any::<u32>()) {
let code = "loop { break }";
let result = Parser::new(code).parse();
prop_assert!(result.is_ok());
}
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_labeled_loops_parse(label in "[a-z]+") {
let code = format!("'{label}: loop {{ break '{label} }}");
let result = Parser::new(&code).parse();
prop_assert!(result.is_ok());
}
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_for_tuple_destructuring(var1 in "[a-z]+", var2 in "[a-z]+") {
let code = format!("for {var1}, {var2} in pairs {{ }}");
let result = Parser::new(&code).parse();
prop_assert!(result.is_ok());
}
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_while_let_always_has_pattern(var in "[a-z]+") {
let code = format!("while let Some({var}) = opt {{ }}");
let result = Parser::new(&code).parse();
prop_assert!(result.is_ok());
}
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_nested_loops_parse(depth in 1usize..5) {
let mut code = String::new();
for i in 0..depth {
code.push_str(&format!("for x{i} in 0..10 {{ "));
}
code.push_str("()");
for _ in 0..depth {
code.push_str(" }");
}
let result = Parser::new(&code).parse();
prop_assert!(result.is_ok());
}
}
}
}