use crate::ParseError;
use crate::expr::ABOVE_AND_AND;
use crate::expr::parse_expr;
use crate::item::local_test;
use crate::item::starts_a_declaration;
use crate::item::walk_item_for_tails;
use crate::operand::assign_tail;
use crate::operand::opens_an_over;
use crate::operand::over_update;
use crate::operand::try_await;
use crate::parser::Parser;
use crate::pattern::pattern_alt;
use crate::signature::TypeStop;
use crate::signature::capture_type;
use crate::token::reserved_word;
use crate::token::scan_run;
use voltaic_ast::Block;
use voltaic_ast::CaseArm;
use voltaic_ast::Condition;
use voltaic_ast::ConditionOperand;
use voltaic_ast::Expr;
use voltaic_ast::Program;
use voltaic_ast::Qualifier;
use voltaic_ast::Stmt;
use voltaic_diag::Code;
use voltaic_lexer::Keyword;
use voltaic_lexer::Span;
use voltaic_lexer::TokenKind;
pub(crate) fn arm_body(p: &mut Parser) -> Result<Expr, ParseError> {
let start = p.peek_span().0;
let value = parse_expr(p, 0)?;
assign_tail(p, value, start)
}
pub(crate) fn bind_block(p: &mut Parser, start: usize) -> Result<Expr, ParseError> {
let names_a_method = p.peek_at(1).is_some_and(|t| t.kind == TokenKind::Dot);
if !names_a_method {
return Err(reserved_word(p));
}
p.advance();
p.advance();
let method = p.ident("expected a method name after `do .`")?;
let body = p.statement_block("expected an indented `do` body")?;
Ok(Expr::BindBlock {
method,
body,
span: (start, p.prev_span().1),
})
}
pub(crate) fn block_expr(
p: &mut Parser,
label: Option<String>,
start: usize,
) -> Result<Expr, ParseError> {
let body = p.statement_block("expected an indented block")?;
Ok(Expr::Block {
label,
body,
span: (start, p.prev_span().1),
})
}
pub(crate) fn case_arm(p: &mut Parser) -> Result<CaseArm, ParseError> {
let start = p.peek_span().0;
let pattern = pattern_alt(p)?;
let ty = if p.check(&TokenKind::Colon) {
p.advance();
Some(capture_type(
p,
"expected a type after `:` in a `case` pattern",
TypeStop::Greedy,
)?)
} else {
None
};
let arm_guard = if p.check(&TokenKind::Or) {
p.advance();
Some(condition(p)?)
} else {
None
};
p.expect(&TokenKind::Arrow, "expected `->` after a `case` pattern")?;
let body = arm_body(p)?;
p.newline_with_trailing("expected a line break after a `case` arm")?;
Ok(CaseArm {
pattern,
ty,
r#guard: arm_guard,
body,
span: (start, p.prev_span().1),
})
}
pub(crate) fn case_of(p: &mut Parser) -> Result<Expr, ParseError> {
let start = p.peek_span().0;
p.expect(&TokenKind::Kw(Keyword::Case), "expected `case`")?;
let scrutinee = parse_expr(p, 0)?;
p.expect(
&TokenKind::Kw(Keyword::Of),
"expected `of` after the scrutinee",
)?;
p.expect(&TokenKind::Newline, "expected a line break after `of`")?;
p.expect_with(
Code::EmptyBody,
&TokenKind::Indent,
"expected indented `case` arms",
)?;
let mut arms = Vec::new();
loop {
p.skip_separators();
if p.check(&TokenKind::Dedent) || p.at_eof() {
break;
}
arms.push(case_arm(p)?);
}
p.expect(&TokenKind::Dedent, "expected the `case` arms to end")?;
if arms.is_empty() {
return Err(ParseError::r#with(
Code::EmptyBody,
"a `case` must have at least one arm",
(start, p.prev_span().1),
));
}
Ok(Expr::Case {
scrutinee: Box::new(scrutinee),
arms,
span: (start, p.prev_span().1),
})
}
pub(crate) fn check_callback_tails(program: &Program) -> Vec<ParseError> {
let mut found = Vec::new();
for item in &program.items {
walk_item_for_tails(item, &mut found);
}
found
}
pub(crate) fn condition(p: &mut Parser) -> Result<Condition, ParseError> {
if !holds_a_pattern_test(p, false, &TokenKind::Kw(Keyword::Is)) {
if holds_a_pattern_test(p, false, &TokenKind::Eq) {
return Err(ParseError::r#with(Code::ExpectedToken, "a pattern test is written `scrutinee is pattern` — plan 60 moved it off `=`, which now means definition and nothing else".to_string(), p.peek_span()));
}
return Ok(Condition::plain(parse_expr(p, 0)?));
}
let mut operands = vec![condition_operand(p)?];
while p.check(&TokenKind::AndAnd) {
p.advance();
operands.push(condition_operand(p)?);
}
if p.check(&TokenKind::OrOr) {
return Err(ParseError::r#with(
Code::ExpectedToken,
"a condition holding a pattern test joins with `&&` only: Rust's let-chains do not admit `||`. Split the condition, or use a `case`",
p.peek_span(),
));
}
Ok(Condition { operands })
}
pub(crate) fn condition_operand(p: &mut Parser) -> Result<ConditionOperand, ParseError> {
if !holds_a_pattern_test(p, true, &TokenKind::Kw(Keyword::Is)) {
return Ok(ConditionOperand::Expr(parse_expr(p, ABOVE_AND_AND)?));
}
let source = parse_expr(p, ABOVE_AND_AND)?;
p.expect(
&TokenKind::Kw(Keyword::Is),
"expected `is` before the pattern",
)?;
Ok(ConditionOperand::Pattern {
pattern: pattern_alt(p)?,
source,
})
}
pub(crate) fn control_flow(p: &mut Parser) -> Result<Option<Expr>, ParseError> {
let start = p.peek_span().0;
let label = match &p.peek().kind {
TokenKind::Label(text) => {
let owned = text.clone();
p.advance();
Some(owned)
}
_ => None,
};
if p.check(&TokenKind::Kw(Keyword::While)) || p.check(&TokenKind::Kw(Keyword::Until)) {
let negated = p.check(&TokenKind::Kw(Keyword::Until));
let keyword_span = p.peek_span();
p.advance();
let cond = condition(p)?;
let reject = negated && cond.has_pattern();
let body = p.statement_block("expected an indented loop body")?;
if reject {
return Err(ParseError::r#with(
Code::PatternUntil,
"`until` cannot take a pattern test: it lowers to `while !(…)`, and a pattern test cannot be negated. Write `while` with the opposite pattern, or a `loop` with a `break`",
keyword_span,
));
}
return Ok(Some(Expr::While {
condition: cond,
negated,
body,
label,
span: (start, p.prev_span().1),
}));
}
if p.check(&TokenKind::Kw(Keyword::For)) {
p.advance();
let binder = p.ident("expected a name after `for`")?;
p.expect(
&TokenKind::Kw(Keyword::In),
"expected `in` after the binder — doc 30 §3.2 writes a `for` with Rust's own word",
)?;
let source = Box::new(parse_expr(p, 0)?);
let body = p.statement_block("expected an indented loop body")?;
return Ok(Some(Expr::For {
binder,
source,
body,
label,
span: (start, p.prev_span().1),
}));
}
if p.check(&TokenKind::Kw(Keyword::Loop)) {
p.advance();
let body = p.statement_block("expected an indented loop body")?;
return Ok(Some(Expr::Loop {
body,
label,
span: (start, p.prev_span().1),
}));
}
if label.is_some() && p.check(&TokenKind::Newline) {
return block_expr(p, label, start).map(Some);
}
if let Some(named) = label {
let message = format!(
"expected `while`, `until`, `for`, `loop` or an indented block after the label `'{}`",
named
);
return Err(ParseError::r#with(
Code::ExpectedToken,
message,
p.peek_span(),
));
}
if p.check(&TokenKind::Kw(Keyword::Break)) {
p.advance();
let break_label = match &p.peek().kind {
TokenKind::Label(text) => {
let owned = text.clone();
p.advance();
Some(owned)
}
_ => None,
};
let value = if p.at_statement_end() {
None
} else {
Some(Box::new(parse_expr(p, 0)?))
};
return Ok(Some(Expr::Break {
label: break_label,
value,
span: (start, p.prev_span().1),
}));
}
if p.check(&TokenKind::Kw(Keyword::Continue)) {
p.advance();
let continue_label = match &p.peek().kind {
TokenKind::Label(text) => {
let owned = text.clone();
p.advance();
Some(owned)
}
_ => None,
};
return Ok(Some(Expr::Continue {
label: continue_label,
span: (start, p.prev_span().1),
}));
}
if p.check(&TokenKind::Kw(Keyword::If)) {
return p.if_statement().map(Some);
}
if p.check(&TokenKind::Kw(Keyword::Guard)) {
p.advance();
let source = Box::new(parse_expr(p, 0)?);
p.expect(
&TokenKind::Kw(Keyword::Is),
"expected `is` before a `guard` pattern",
)?;
let pattern = pattern_alt(p)?;
p.expect(
&TokenKind::Kw(Keyword::Else),
"expected `else` — a `guard` without one is a binding, and let-else requires it",
)?;
let fallback = p.statement_block("expected an indented `else` body after `guard`")?;
return Ok(Some(Expr::Guard {
pattern,
source,
r#otherwise: fallback,
span: (start, p.prev_span().1),
}));
}
Ok(None)
}
pub(crate) fn holds_a_pattern_test(p: &Parser, one_operand: bool, marker: &TokenKind) -> bool {
let mut depth: i32 = 0;
let mut i = 0;
while let Some(tok) = p.peek_at(i) {
match &tok.kind {
kind if depth == 0 && kind == marker => {
return true;
}
TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
depth -= 1;
if depth < 0 {
return false;
}
}
TokenKind::AndAnd | TokenKind::OrOr if depth == 0 && one_operand => {
return false;
}
TokenKind::Kw(Keyword::Else) if depth == 0 => {
return false;
}
TokenKind::Arrow if depth == 0 => {
return false;
}
TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => {
return false;
}
_ => (),
};
i += 1;
}
false
}
pub(crate) fn modified_block(
p: &mut Parser,
kw: Keyword,
start: usize,
) -> Result<Expr, ParseError> {
p.advance();
let word = kw.text();
let body = p.statement_block(&format!("expected an indented `{}` body", word))?;
let span = (start, p.prev_span().1);
match kw {
Keyword::Async => Ok(Expr::AsyncBlock { body, span }),
Keyword::Unsafe => Ok(Expr::UnsafeBlock { body, span }),
Keyword::Try => Ok(Expr::TryBlock { body, span }),
_ => unreachable!("only `try`, `async` and `unsafe` modify a block"),
}
}
pub(crate) fn qualifiers(p: &mut Parser) -> Result<Vec<Qualifier>, ParseError> {
let mut found = Vec::new();
loop {
let named = match p.peek().kind {
TokenKind::Ident(_) => true,
_ => false,
};
let is_draw = named
&& p
.peek_at(1)
.is_some_and(|t| t.kind == TokenKind::Kw(Keyword::In));
if is_draw {
let name = p.ident("expected a name before `in`")?;
p.advance();
let source = parse_expr(p, 0)?;
found.push(Qualifier::Draw { name, source });
} else {
found.push(Qualifier::Guard(parse_expr(p, 0)?));
}
p.skip_layout();
if !p.check(&TokenKind::Comma) {
break;
}
p.advance();
p.skip_layout();
}
Ok(found)
}
pub(crate) fn reaches_a_draw_arrow(p: &Parser) -> bool {
let mut depth: i32 = 0;
let mut i = 0;
while let Some(tok) = p.peek_at(i) {
match &tok.kind {
TokenKind::DrawArrow if depth == 0 => {
return true;
}
TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
depth -= 1;
if depth < 0 {
return false;
}
}
TokenKind::Eq if depth == 0 => {
return false;
}
TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => {
return false;
}
_ => (),
};
i += 1;
}
false
}
pub(crate) fn reaches_a_fat_arrow(p: &Parser) -> Option<Span> {
let mut depth: i32 = 0;
let mut i = 0;
while let Some(tok) = p.peek_at(i) {
match &tok.kind {
TokenKind::FatArrow if depth == 0 => {
return Some(tok.span);
}
TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
depth -= 1;
if depth < 0 {
return None;
}
}
TokenKind::Eq if depth == 0 => {
return None;
}
TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => {
return None;
}
_ => (),
};
i += 1;
}
None
}
pub(crate) fn return_in_tail(block: &Block) -> Vec<ParseError> {
let mut found = Vec::new();
scan_run(&block.stmts, false, &mut found);
found
}
pub(super) fn stmt(p: &mut Parser) -> Result<Stmt, ParseError> {
let leading = p.peek_leading();
let start = p.peek_span().0;
local_test(p)?;
if starts_a_declaration(p) {
let declared = p.item()?;
let end = p.prev_span().1;
let trailing = if p.check(&TokenKind::Newline) {
let taken = p.peek().trailing.clone();
p.advance();
taken
} else {
None
};
let wrapped = Expr::Item {
item: Box::new(declared),
span: (start, end),
};
return Ok(Stmt {
expr: wrapped,
span: (start, end),
leading: Vec::new(),
trailing,
});
}
if opens_an_over(p) {
let found = over_update(p, start)?;
let end = p.prev_span().1;
let trailing = p.newline_with_trailing("expected a line break after the statement")?;
return Ok(Stmt {
expr: found,
span: (start, end),
leading,
trailing,
});
}
let flow = control_flow(p)?;
if let Some(found) = flow {
let end = p.prev_span().1;
let trailing = p.newline_with_trailing("expected a line break after the statement")?;
return Ok(Stmt {
expr: found,
span: (start, end),
leading,
trailing,
});
}
let expr = if p.check(&TokenKind::Kw(Keyword::Return)) {
p.advance();
let value = if p.at_statement_end() {
None
} else {
Some(Box::new(parse_expr(p, 0)?))
};
Expr::Return {
value,
span: (start, p.prev_span().1),
}
} else if let Some(drawn) = p.try_draw()? {
drawn
} else if let Some(awaited) = try_await(p)? {
awaited
} else if let Some(bound) = p.try_binding()? {
Expr::Binding(Box::new(bound))
} else {
let value = parse_expr(p, 0)?;
assign_tail(p, value, start)?
};
let end = p.prev_span().1;
let trailing = p.newline_with_trailing("expected a line break after the statement")?;
Ok(Stmt {
expr,
span: (start, end),
leading,
trailing,
})
}