use crisp_ast::expr::{ExprKind, Stmt};
use crisp_ast::item::Item;
use crisp_parser::Parser;
fn parse_fn_body(src: &str) -> crisp_ast::expr::Expr {
let file = format!("f() = {src}");
let mut p = Parser::new(&file).unwrap_or_else(|e| panic!("parser: {e}"));
let ast = p.parse_file().unwrap_or_else(|e| panic!("parse: {e}"));
let Item::Function(f) = &ast.items[0] else {
panic!("expected function");
};
f.body.clone()
}
#[test]
fn parse_while_no_struct_lit_in_cond() {
let e = parse_fn_body("{ while i < n { i = i + 1 } }");
let ExprKind::Block(b) = e.kind else {
panic!("{:?}", e.kind);
};
let w = match b.stmts.first() {
Some(Stmt::Expr(w)) => w,
_ => b.tail.as_ref().expect("while"),
};
assert!(matches!(w.kind, ExprKind::While { .. }));
}
#[test]
fn parse_for() {
let e = parse_fn_body("{ for x in xs { total = total + x } }");
let ExprKind::Block(b) = e.kind else {
panic!("{:?}", e.kind);
};
let f = match b.stmts.first() {
Some(Stmt::Expr(f)) => f,
_ => b.tail.as_ref().expect("for"),
};
assert!(matches!(f.kind, ExprKind::For { .. }));
}
#[test]
fn parse_loop_break_value() {
let e = parse_fn_body("{ loop { if done then break 42 } }");
let ExprKind::Block(b) = e.kind else {
panic!("{:?}", e.kind);
};
let l = match b.stmts.first() {
Some(Stmt::Expr(l)) => l,
_ => b.tail.as_ref().expect("loop"),
};
let ExprKind::Loop(body) = &l.kind else {
panic!("{:?}", l.kind);
};
let ExprKind::Block(lb) = &body.kind else {
panic!("{:?}", body.kind);
};
let iff = match lb.stmts.first() {
Some(Stmt::Expr(e)) => e,
_ => lb.tail.as_ref().expect("if"),
};
let ExprKind::If {
then_branch,
else_branch: None,
..
} = &iff.kind
else {
panic!("{:?}", iff.kind);
};
assert!(matches!(then_branch.kind, ExprKind::Break(Some(_))));
}
#[test]
fn parse_continue() {
let e = parse_fn_body("{ while true { continue } }");
let ExprKind::Block(b) = e.kind else {
panic!("{:?}", e.kind);
};
let w = match b.stmts.first() {
Some(Stmt::Expr(w)) => w,
_ => b.tail.as_ref().expect("while"),
};
let ExprKind::While { body, .. } = &w.kind else {
panic!("{:?}", w.kind);
};
let ExprKind::Block(wb) = &body.kind else {
panic!("{:?}", body.kind);
};
let c_kind = if let Some(t) = &wb.tail {
&t.kind
} else if let Some(Stmt::Expr(e)) = wb.stmts.first() {
&e.kind
} else {
panic!("expected continue in while body: {:?}", wb);
};
assert!(matches!(c_kind, ExprKind::Continue));
}
#[test]
fn issue_96_while_then_paren_tail_is_not_a_call() {
let e = parse_fn_body("{ while i < n { i = i + 1 } (lo + hi) / 2.0 }");
let ExprKind::Block(b) = e.kind else {
panic!("{:?}", e.kind);
};
let w = b.stmts.iter().find_map(|s| match s {
Stmt::Expr(e) if matches!(e.kind, ExprKind::While { .. }) => Some(e),
_ => None,
});
assert!(
w.is_some(),
"while must be a statement, not a callee (#96): {:?}",
b
);
let tail = b.tail.as_ref().expect("parenthesized tail");
assert!(
matches!(tail.kind, ExprKind::Binary { .. }),
"tail should be (lo + hi) / 2.0, got {:?}",
tail.kind
);
}
#[test]
fn issue_96_if_then_assign_parses_as_assign_expr() {
let e = parse_fn_body("{ if ignites(mid, 0.8) then hi = mid else lo = mid }");
let ExprKind::Block(b) = e.kind else {
panic!("{:?}", e.kind);
};
let iff = match b.stmts.first() {
Some(Stmt::Expr(e)) => e,
_ => b.tail.as_ref().expect("if"),
};
let ExprKind::If {
then_branch,
else_branch: Some(else_branch),
..
} = &iff.kind
else {
panic!("{:?}", iff.kind);
};
assert!(
matches!(then_branch.kind, ExprKind::Assign { .. }),
"then: {:?}",
then_branch.kind
);
assert!(
matches!(else_branch.kind, ExprKind::Assign { .. }),
"else: {:?}",
else_branch.kind
);
}