use crate::frontend::ast::{Expr, ExprKind, Span};
use crate::frontend::lexer::Token;
use crate::frontend::parser::{parse_expr_recursive, ParserState, Result};
fn skip_comments(state: &mut ParserState) {
while matches!(
state.tokens.peek(),
Some((
Token::LineComment(_)
| Token::BlockComment(_)
| Token::DocComment(_)
| Token::HashComment(_),
_
))
) {
state.tokens.advance();
}
}
pub(in crate::frontend::parser) fn parse_break_token(
state: &mut ParserState,
span: Span,
) -> Result<Expr> {
state.tokens.advance();
let label = match state.tokens.peek() {
Some((Token::Lifetime(name), _)) => {
let stripped = name.strip_prefix('\'').unwrap_or(name).to_string();
state.tokens.advance();
Some(stripped)
}
Some((Token::Label(name), _)) => {
let label = Some(name.clone());
state.tokens.advance();
label
}
_ => None,
};
skip_comments(state);
let value = if matches!(
state.tokens.peek(),
Some((Token::Semicolon | Token::RightBrace | Token::RightParen, _))
) || state.tokens.peek().is_none()
{
None
} else {
Some(Box::new(parse_expr_recursive(state)?))
};
Ok(Expr::new(ExprKind::Break { label, value }, span))
}
pub(in crate::frontend::parser) fn parse_continue_token(
state: &mut ParserState,
span: Span,
) -> Result<Expr> {
state.tokens.advance();
let label = match state.tokens.peek() {
Some((Token::Lifetime(name), _)) => {
let stripped = name.strip_prefix('\'').unwrap_or(name).to_string();
state.tokens.advance();
Some(stripped)
}
Some((Token::Label(name), _)) => {
let label = Some(name.clone());
state.tokens.advance();
label
}
_ => None,
};
skip_comments(state);
Ok(Expr::new(ExprKind::Continue { label }, span))
}
pub(in crate::frontend::parser) fn parse_return_token(
state: &mut ParserState,
span: Span,
) -> Result<Expr> {
state.tokens.advance();
skip_comments(state);
let value = if matches!(
state.tokens.peek(),
Some((Token::Semicolon | Token::RightBrace, _))
) || state.tokens.peek().is_none()
{
None
} else {
Some(Box::new(parse_expr_recursive(state)?))
};
Ok(Expr::new(ExprKind::Return { value }, span))
}
pub(in crate::frontend::parser) fn parse_throw_token(
state: &mut ParserState,
span: Span,
) -> Result<Expr> {
state.tokens.advance();
let expr = Box::new(parse_expr_recursive(state)?);
Ok(Expr::new(ExprKind::Throw { expr }, span))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::parser::Parser;
fn parse(code: &str) -> Result<Expr> {
let mut parser = Parser::new(code);
parser.parse()
}
fn get_block_exprs(expr: &Expr) -> Option<&Vec<Expr>> {
match &expr.kind {
ExprKind::Block(exprs) => Some(exprs),
_ => None,
}
}
fn get_function_body(expr: &Expr) -> Option<&Expr> {
if let Some(exprs) = get_block_exprs(expr) {
if let Some(func) = exprs.first() {
if let ExprKind::Function { body, .. } = &func.kind {
return Some(body.as_ref());
}
}
}
None
}
#[test]
fn test_bare_break() {
let expr = parse("loop { break }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Loop { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
if let ExprKind::Break { label, value } = &inner[0].kind {
assert!(label.is_none());
assert!(value.is_none());
} else {
panic!("Expected Break expression");
}
}
}
}
}
#[test]
fn test_break_with_value() {
let expr = parse("loop { break 42 }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Loop { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
if let ExprKind::Break { value, .. } = &inner[0].kind {
assert!(value.is_some());
}
}
}
}
}
#[test]
fn test_break_with_string_value() {
let expr = parse("loop { break \"done\" }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Loop { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
if let ExprKind::Break { value, .. } = &inner[0].kind {
assert!(value.is_some());
}
}
}
}
}
#[test]
fn test_break_with_expression() {
let expr = parse("loop { break x + 1 }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Loop { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
if let ExprKind::Break { value, .. } = &inner[0].kind {
assert!(value.is_some());
}
}
}
}
}
#[test]
fn test_break_with_label_in_while() {
let expr = parse("'outer: while true { break 'outer }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::While { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
if let ExprKind::Break { label, .. } = &inner[0].kind {
assert!(label.is_some());
}
}
}
}
}
#[test]
fn test_break_followed_by_semicolon() {
let expr = parse("loop { break; x }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Loop { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
assert!(matches!(&inner[0].kind, ExprKind::Break { .. }));
}
}
}
}
#[test]
fn test_break_followed_by_right_brace() {
let result = parse("loop { if true { break } }");
assert!(result.is_ok(), "Break followed by }} should parse");
}
#[test]
fn test_bare_continue() {
let expr = parse("while true { continue }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::While { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
if let ExprKind::Continue { label } = &inner[0].kind {
assert!(label.is_none());
} else {
panic!("Expected Continue expression");
}
}
}
}
}
#[test]
fn test_continue_no_label() {
let code = "while true { continue }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Continue should parse successfully");
}
#[test]
fn test_continue_with_label() {
let expr = parse("'outer: while true { continue 'outer }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::While { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
if let ExprKind::Continue { label } = &inner[0].kind {
assert!(label.is_some());
}
}
}
}
}
#[test]
fn test_continue_in_for_loop() {
let expr = parse("for x in xs { continue }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::For { body, .. } = &exprs[0].kind {
if let ExprKind::Block(inner) = &body.kind {
assert!(matches!(&inner[0].kind, ExprKind::Continue { .. }));
}
}
}
}
#[test]
fn test_continue_followed_by_semicolon() {
let result = parse("loop { continue; x }");
assert!(
result.is_ok(),
"Continue followed by semicolon should parse"
);
}
#[test]
fn test_continue_in_nested_loop() {
let expr = parse("'outer: while true { while false { continue 'outer } }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::While { .. }));
}
}
#[test]
fn test_bare_return() {
let code = "fun f() { return }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Bare return should parse successfully");
}
#[test]
fn test_return_with_value() {
let code = "fun f() { return 42 }";
let result = Parser::new(code).parse();
assert!(
result.is_ok(),
"Return with value should parse successfully"
);
}
#[test]
fn test_return_value_none() {
let expr = parse("fun f() { return }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_none());
}
}
}
#[test]
fn test_return_value_some() {
let expr = parse("fun f() { return 42 }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_return_with_string() {
let expr = parse("fun f() { return \"hello\" }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_return_with_expression() {
let expr = parse("fun f(x) { return x * 2 }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_return_followed_by_semicolon() {
let expr = parse("fun f() { return; 42 }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::Function { .. }));
}
}
#[test]
fn test_return_followed_by_right_brace() {
let result = parse("fun f() { if true { return } }");
assert!(result.is_ok(), "Return followed by }} should parse");
}
#[test]
fn test_return_in_if_else() {
let expr = parse("fun f(x) { if x { return 1 } else { return 2 } }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::Function { .. }));
}
}
#[test]
fn test_early_return() {
let expr = parse("fun f(x) { if x < 0 { return } x * 2 }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::Function { .. }));
}
}
#[test]
fn test_throw_expression() {
let code = "fun f() { throw \"error\" }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Throw should parse successfully");
}
#[test]
fn test_throw_with_string() {
let expr = parse("fun f() { throw \"error\" }").unwrap();
if let Some(body) = get_function_body(&expr) {
assert!(matches!(&body.kind, ExprKind::Throw { .. }));
}
}
#[test]
fn test_throw_with_variable() {
let expr = parse("fun f(e) { throw e }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Throw { expr } = &body.kind {
assert!(matches!(&expr.kind, ExprKind::Identifier(_)));
}
}
}
#[test]
fn test_throw_with_call() {
let expr = parse("fun f() { throw Error(\"oops\") }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Throw { expr } = &body.kind {
assert!(matches!(&expr.kind, ExprKind::Call { .. }));
}
}
}
#[test]
fn test_throw_with_struct_literal() {
let expr = parse("fun f() { throw MyError { msg: \"oops\" } }").unwrap();
if let Some(body) = get_function_body(&expr) {
assert!(matches!(&body.kind, ExprKind::Throw { .. }));
}
}
#[test]
fn test_throw_in_conditional() {
let expr = parse("fun f(x) { if x < 0 { throw \"negative\" } }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::Function { .. }));
}
}
#[test]
#[ignore = "Property tests run with --ignored flag"] fn test_break_with_label() {
let code = "for x in xs { break 'outer; }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Break with label should parse successfully");
}
#[test]
fn test_nested_loops_with_control_flow() {
let result = parse("'a: while true { 'b: while false { break 'a } }");
assert!(result.is_ok(), "Nested loops with break should parse");
}
#[test]
fn test_control_flow_in_match_arm() {
let result = parse("fun f(x) { match x { 1 => return 1, _ => 0 } }");
assert!(result.is_ok(), "Control flow in match arm should parse");
}
#[test]
fn test_control_flow_in_lambda() {
let expr = parse("|x| { if x < 0 { return } x }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::Lambda { .. }));
}
}
#[test]
fn test_multiple_returns() {
let expr = parse("fun f(a, b) { if a { return 1 } if b { return 2 } 3 }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::Function { .. }));
}
}
#[test]
fn test_break_continue_in_same_loop() {
let expr = parse("while true { if x { break } else { continue } }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::While { .. }));
}
}
#[test]
fn test_return_tuple() {
let expr = parse("fun f() { return (1, 2, 3) }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_return_list() {
let expr = parse("fun f() { return [1, 2, 3] }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_throw_binary_expression() {
let expr = parse("fun f() { throw a + b }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Throw { expr } = &body.kind {
assert!(matches!(&expr.kind, ExprKind::Binary { .. }));
}
}
}
#[test]
fn test_break_with_label_and_value() {
let expr = parse("'outer: while true { break 'outer 42 }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::While { .. }));
}
}
#[test]
fn test_simple_break_in_while() {
let result = Parser::new("while true { break }").parse();
assert!(result.is_ok(), "Simple break should parse");
}
#[test]
fn test_simple_continue_in_while() {
let result = Parser::new("while true { continue }").parse();
assert!(result.is_ok(), "Simple continue should parse");
}
#[test]
fn test_return_method_call() {
let expr = parse("fun f(s) { return s.len() }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_return_if_expr() {
let expr = parse("fun f(x) { return if x > 0 { 1 } else { -1 } }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_break_followed_by_expr() {
let result = Parser::new("while true { break; 42 }").parse();
assert!(result.is_ok(), "Break followed by expr should parse");
}
#[test]
fn test_continue_followed_by_expr() {
let result = Parser::new("while true { continue; 42 }").parse();
assert!(result.is_ok(), "Continue followed by expr should parse");
}
#[test]
fn test_throw_new_expr() {
let result = Parser::new("fun f() { throw Error(\"fail\") }").parse();
assert!(result.is_ok(), "Throw with call should parse");
}
#[test]
fn test_nested_return_in_block() {
let expr = parse("fun f() { { return 1 } }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::Function { .. }));
}
}
#[test]
fn test_break_in_for_loop() {
let result = Parser::new("for i in range(10) { break }").parse();
assert!(result.is_ok(), "Break in for loop should parse");
}
#[test]
fn test_continue_in_for_loop_range() {
let result = Parser::new("for i in range(10) { continue }").parse();
assert!(result.is_ok(), "Continue in for loop should parse");
}
#[test]
fn test_return_unary_expr() {
let expr = parse("fun f(x) { return -x }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_throw_interpolated_string() {
let result = Parser::new(r#"fun f() { throw "error: {msg}" }"#).parse();
assert!(
result.is_ok(),
"Throw with interpolated string should parse"
);
}
#[test]
fn test_return_closure_call() {
let expr = parse("fun f() { return (|x| x + 1)(5) }").unwrap();
if let Some(body) = get_function_body(&expr) {
if let ExprKind::Return { value } = &body.kind {
assert!(value.is_some());
}
}
}
#[test]
fn test_multiple_control_flow() {
let result = Parser::new("fun f(x) { if x > 0 { return 1 } throw \"error\" }").parse();
assert!(result.is_ok(), "Multiple control flow should parse");
}
#[test]
fn test_break_with_line_comment() {
let result = parse("while true { break // comment\n 42 }");
assert!(result.is_ok(), "Break with line comment should parse");
}
#[test]
fn test_break_with_block_comment() {
let result = parse("while true { break /* block */ }");
assert!(result.is_ok(), "Break with block comment should parse");
}
#[test]
fn test_continue_with_line_comment() {
let result = parse("while true { continue // comment\n }");
assert!(result.is_ok(), "Continue with line comment should parse");
}
#[test]
fn test_continue_with_block_comment() {
let result = parse("while true { continue /* block */ }");
assert!(result.is_ok(), "Continue with block comment should parse");
}
#[test]
fn test_return_with_line_comment() {
let result = parse("fun f() { return // comment\n }");
assert!(result.is_ok(), "Return with line comment should parse");
}
#[test]
fn test_return_with_block_comment() {
let result = parse("fun f() { return /* block */ }");
assert!(result.is_ok(), "Return with block comment should parse");
}
#[test]
fn test_break_with_doc_comment() {
let result = parse("while true { break /// doc comment\n }");
assert!(result.is_ok(), "Break with doc comment should parse");
}
#[test]
fn test_continue_with_doc_comment() {
let result = parse("while true { continue /// doc comment\n }");
assert!(result.is_ok(), "Continue with doc comment should parse");
}
#[test]
fn test_return_with_doc_comment() {
let result = parse("fun f() { return /// doc comment\n }");
assert!(result.is_ok(), "Return with doc comment should parse");
}
#[test]
fn test_break_with_hash_comment() {
let result = parse("while true { break # hash comment\n }");
assert!(result.is_ok(), "Break with hash comment should parse");
}
#[test]
fn test_continue_with_hash_comment() {
let result = parse("while true { continue # hash comment\n }");
assert!(result.is_ok(), "Continue with hash comment should parse");
}
#[test]
fn test_return_with_hash_comment() {
let result = parse("fun f() { return # hash comment\n }");
assert!(result.is_ok(), "Return with hash comment should parse");
}
#[test]
fn test_break_with_multiple_comments() {
let result = parse("while true { break // first\n /* second */ }");
assert!(result.is_ok(), "Break with multiple comments should parse");
}
#[test]
fn test_break_with_at_label() {
let result = parse("@outer: while true { break @outer }");
assert!(result.is_ok(), "Break with @label should parse");
}
#[test]
fn test_continue_with_at_label() {
let result = parse("@outer: while true { continue @outer }");
assert!(result.is_ok(), "Continue with @label should parse");
}
#[test]
fn test_break_at_label_with_value() {
let result = parse("@loop1: while true { break @loop1 42 }");
assert!(result.is_ok(), "Break with @label and value should parse");
}
#[test]
fn test_break_at_eof() {
let result = parse("while true { break }");
assert!(result.is_ok(), "Break at block end should parse");
}
#[test]
fn test_return_at_eof() {
let result = parse("fun f() { return }");
assert!(result.is_ok(), "Bare return should parse");
}
#[test]
fn test_break_in_right_paren_context() {
let result = parse("while true { (break) }");
assert!(result.is_ok(), "Break in parens should parse");
}
#[test]
fn test_throw_with_complex_expr() {
let result = parse("fun f() { throw Error { msg: \"fail\", code: 1 } }");
assert!(result.is_ok(), "Throw with struct literal should parse");
}
#[test]
fn test_return_with_match() {
let result = parse("fun f(x) { return match x { 1 => \"one\", _ => \"other\" } }");
assert!(result.is_ok(), "Return with match should parse");
}
#[test]
fn test_break_with_if_value() {
let result = parse("while true { break if x { 1 } else { 2 } }");
assert!(result.is_ok(), "Break with if value should parse");
}
#[test]
fn test_nested_control_flow() {
let result = parse("fun f() { while true { for i in xs { if i > 5 { return i } } } }");
assert!(result.is_ok(), "Nested control flow should parse");
}
#[test]
fn test_control_flow_in_closure() {
let result = parse("let f = |x| { if x { return 1 } 2 }");
assert!(result.is_ok(), "Control flow in closure should parse");
}
#[test]
fn test_throw_in_try_block() {
let result = parse("try { throw \"error\" } catch e { e }");
assert!(result.is_ok(), "Throw in try block should parse");
}
#[test]
fn test_return_in_finally() {
let result = parse("fun f() { try { 1 } finally { return 2 } }");
assert!(result.is_ok(), "Return in finally should parse");
}
}