use crate::ParseError;
use crate::block::bind_block;
use crate::block::block_expr;
use crate::block::case_of;
use crate::block::modified_block;
use crate::operand::bracketed;
use crate::operand::byte_literal;
use crate::operand::interpolated_string;
use crate::operand::lambda;
use crate::operand::macro_call;
use crate::operand::method_call_syntax;
use crate::operand::optional_chaining;
use crate::operand::record_literal;
use crate::operand::section;
use crate::parser::Parser;
use crate::signature::TypeStop;
use crate::signature::capture_type;
use crate::support::PREFIX_POWER;
use crate::support::binary_power;
use crate::token::rust_hatch;
use crate::token::rust_string_body;
use voltaic_ast::BinaryOp;
use voltaic_ast::Expr;
use voltaic_ast::Fixity;
use voltaic_ast::Ident;
use voltaic_ast::TypeExpr;
use voltaic_ast::UnaryOp;
use voltaic_diag::Code;
use voltaic_lexer::Keyword;
use voltaic_lexer::StrPart;
use voltaic_lexer::TokenKind;
pub(crate) const ABOVE_AND_AND: u8 = 10;
pub(crate) fn application(p: &mut Parser) -> Result<Expr, ParseError> {
let mut head = atom(p)?;
while !p.just_dedented() && starts_argument(&p.peek().kind) {
let mut args = Vec::new();
while starts_argument(&p.peek().kind) {
args.push(atom(p)?);
}
let span = (head.span().0, p.prev_span().1);
let built = match head {
Expr::Field { recv, name, .. } => Expr::Dot {
recv,
name,
args,
span,
},
other => Expr::Call {
callee: Box::new(other),
args,
is_dollar: false,
span,
},
};
head = built;
loop {
match postfix(p, head)? {
Ok(grown) => head = grown,
Err(unchanged) => {
head = unchanged;
break;
}
};
}
}
Ok(head)
}
pub(crate) fn atom(p: &mut Parser) -> Result<Expr, ParseError> {
let mut expr = prefix(p)?;
loop {
match postfix(p, expr)? {
Ok(grown) => expr = grown,
Err(unchanged) => {
return Ok(unchanged);
}
};
}
}
pub(crate) fn binary_op(p: &mut Parser) -> Option<BinaryOp> {
let found = binary_power(&p.peek().kind)?;
p.advance();
Some(found.0)
}
pub(crate) fn digit_precedence(text: &str) -> Option<u8> {
match text {
"0" => Some(0),
"1" => Some(1),
"2" => Some(2),
"3" => Some(3),
"4" => Some(4),
"5" => Some(5),
"6" => Some(6),
"7" => Some(7),
"8" => Some(8),
"9" => Some(9),
_ => None,
}
}
pub(crate) fn operator_chains(p: &Parser, spelling: &Ident) -> bool {
match &p.peek().kind {
TokenKind::UserOp(next) => next == &spelling.text,
_ => false,
}
}
pub(crate) fn parse_expr(p: &mut Parser, min: u8) -> Result<Expr, ParseError> {
let mut lhs = application(p)?;
loop {
if min == 0 && p.check(&TokenKind::Colon) {
p.advance();
let ty = capture_type(p, "expected a type after `:`", TypeStop::Greedy)?;
let at = (lhs.span().0, ty.span.1);
lhs = Expr::Ascribe {
inner: Box::new(lhs),
ty,
span: at,
};
continue;
}
if min <= 1 && p.check(&TokenKind::Dollar) {
p.advance();
let arg = parse_expr(p, 1)?;
let at = (lhs.span().0, arg.span().1);
lhs = Expr::Call {
callee: Box::new(lhs),
args: vec![arg],
is_dollar: true,
span: at,
};
continue;
}
if let Some(declared) = user_rung(p) {
if declared.2 < min {
return Ok(lhs);
}
let spelling = declared.0;
p.advance();
let rhs = parse_expr(p, declared.3)?;
let span = (lhs.span().0, rhs.span().1);
if declared.1 == Fixity::NonAssoc && operator_chains(p, &spelling) {
return Err(ParseError::with_help(
Code::NonAssociativeOperator,
format!(
"`{}` was declared `infix`, so it does not chain",
spelling.text
),
format!(
"write the grouping — `(a {} b) {} c` — or declare it `infixl` or `infixr`",
spelling.text, spelling.text
),
p.peek_span(),
));
}
lhs = Expr::UserBinary {
spelling,
fixity: declared.1,
precedence: declared.5,
target: declared.4,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
span,
};
continue;
}
let Some(rung) = binary_power(&p.peek().kind) else {
if let TokenKind::UserOp(spelling) = &p.peek().kind {
return Err(ParseError::with_help(
Code::UndeclaredOperator,
format!("`{}` is not declared in this file", spelling),
format!(
"declare it above its first use — `infixl 6 {} = Set::union`. An operator's scope is the file, which is plan 14 decision 4's refusal of name resolution",
spelling
),
p.peek_span(),
));
}
return Ok(lhs);
};
if rung.1 < min {
return Ok(lhs);
}
p.advance();
let rhs = parse_expr(p, rung.2)?;
let span = (lhs.span().0, rhs.span().1);
lhs = Expr::Binary {
op: rung.0,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
span,
};
}
}
pub(crate) fn parse_spine(p: &mut Parser) -> Result<Vec<TypeExpr>, ParseError> {
let mut spine = vec![capture_type(
p,
"expected a type after `::`",
TypeStop::Greedy,
)?];
while p.check(&TokenKind::Arrow) {
p.advance();
spine.push(capture_type(
p,
"expected a type after `->`",
TypeStop::Greedy,
)?);
}
Ok(spine)
}
#[allow(clippy::result_large_err)]
pub(crate) fn postfix(p: &mut Parser, lhs: Expr) -> Result<Result<Expr, Expr>, ParseError> {
if p.just_dedented() {
return Ok(Err(lhs));
}
if let Expr::Unit { .. } = lhs {
return Ok(Err(lhs));
}
let start = lhs.span().0;
if p.check(&TokenKind::Question) {
let glued = p
.peek_at(1)
.is_some_and(|t| t.kind == TokenKind::Dot && !t.ws_before);
if glued {
let at = p.peek_span();
return Err(optional_chaining((at.0, at.1 + 1)));
}
p.advance();
return Ok(Ok(Expr::Try {
inner: Box::new(lhs),
span: (start, p.prev_span().1),
}));
}
if p.check(&TokenKind::LBracket) {
p.advance();
p.skip_layout();
let index = parse_expr(p, 0)?;
p.skip_layout();
p.expect(&TokenKind::RBracket, "expected `]` to close the index")?;
return Ok(Ok(Expr::Index {
recv: Box::new(lhs),
index: Box::new(index),
span: (start, p.prev_span().1),
}));
}
if p.check(&TokenKind::LBrace) {
return Ok(Ok(record_literal(p, lhs)?));
}
if p.check(&TokenKind::Dot) {
p.advance();
if let TokenKind::Int(digits) = &p.peek().kind {
let index_name = Ident {
text: digits.clone(),
span: p.peek_span(),
};
p.advance();
return Ok(Ok(Expr::Field {
recv: Box::new(lhs),
name: index_name,
span: (start, p.prev_span().1),
}));
}
let name = p.ident("expected a field name or a tuple index after `.`")?;
if p.check(&TokenKind::LParen) && !p.peek().ws_before {
return Err(method_call_syntax(&name.text, (start, name.span.1)));
}
return Ok(Ok(Expr::Field {
recv: Box::new(lhs),
name,
span: (start, p.prev_span().1),
}));
}
Ok(Err(lhs))
}
pub(crate) fn prefix(p: &mut Parser) -> Result<Expr, ParseError> {
let start = p.peek_span().0;
if let Some(op) = unary_op(p) {
let operand = parse_expr(p, PREFIX_POWER)?;
let span = (start, operand.span().1);
return Ok(Expr::Unary {
op,
operand: Box::new(operand),
span,
});
}
match p.peek().kind.clone() {
TokenKind::Int(text) | TokenKind::Float(text) => {
let span = p.peek_span();
p.advance();
Ok(Expr::Num { text, span })
}
TokenKind::Str { parts, raw } => {
let span = p.peek_span();
p.advance();
let mut interpolates = false;
for part in &parts {
match part {
StrPart::Interp { .. } => interpolates = true,
_ => (),
};
}
if interpolates {
return interpolated_string(&parts, span);
}
if raw.starts_with("\"\"\"") {
let mut joined = String::new();
for part in &parts {
match part {
StrPart::Text(text) => joined.push_str(text.as_str()),
StrPart::Interp { .. } => unreachable!("checked above"),
};
}
let body = rust_string_body(&joined);
return Ok(Expr::StrLit {
value: format!("\"{}\"", body),
span,
});
}
Ok(Expr::StrLit { value: raw, span })
}
TokenKind::RawStr { text, hashes } => {
let span = p.peek_span();
p.advance();
let pad = "#".repeat(hashes);
Ok(Expr::RawLit {
text: format!("r{}\"{}\"{}", pad, text, pad),
span,
})
}
TokenKind::ByteStr(text) => {
let span = p.peek_span();
p.advance();
let body = rust_string_body(&text);
Ok(Expr::RawLit {
text: format!("b\"{}\"", body),
span,
})
}
TokenKind::CStr(text) => {
let span = p.peek_span();
p.advance();
let body = rust_string_body(&text);
Ok(Expr::RawLit {
text: format!("c\"{}\"", body),
span,
})
}
TokenKind::Byte(value) => {
let span = p.peek_span();
p.advance();
Ok(Expr::RawLit {
text: byte_literal(value),
span,
})
}
TokenKind::Kw(Keyword::Async) => modified_block(p, Keyword::Async, start),
TokenKind::Kw(Keyword::Unsafe) => modified_block(p, Keyword::Unsafe, start),
TokenKind::Kw(Keyword::Try) => modified_block(p, Keyword::Try, start),
TokenKind::Char(value) => {
let span = p.peek_span();
p.advance();
Ok(Expr::Char { value, span })
}
TokenKind::Kw(Keyword::True) => {
let span = p.peek_span();
p.advance();
Ok(Expr::Bool { value: true, span })
}
TokenKind::Kw(Keyword::False) => {
let span = p.peek_span();
p.advance();
Ok(Expr::Bool { value: false, span })
}
TokenKind::At => {
p.advance();
let name = p.ident("expected a field name after `@`")?;
let span = (start, name.span.1);
Ok(Expr::SelfField { name, span })
}
TokenKind::AtAt => {
p.advance();
let name = p.ident("expected a name after `@@`")?;
let span = (start, name.span.1);
Ok(Expr::SelfType { name, span })
}
TokenKind::LBracket => bracketed(p),
TokenKind::LParen => {
p.advance();
p.skip_layout();
if p.check(&TokenKind::RParen) {
p.advance();
return Ok(Expr::Unit {
span: (start, p.prev_span().1),
});
}
let sectioned = section(p, start)?;
if let Some(found) = sectioned {
return Ok(found);
}
let inner = parse_expr(p, 0)?;
p.skip_layout();
if p.check(&TokenKind::Comma) {
let mut items = vec![inner];
while p.check(&TokenKind::Comma) {
p.advance();
p.skip_layout();
if p.check(&TokenKind::RParen) {
break;
}
items.push(parse_expr(p, 0)?);
p.skip_layout();
}
p.expect(&TokenKind::RParen, "expected `)` to close the tuple")?;
return Ok(Expr::Tuple {
items,
span: (start, p.prev_span().1),
});
}
p.expect(&TokenKind::RParen, "expected `)` to close the group")?;
Ok(Expr::Paren {
inner: Box::new(inner),
span: (start, p.prev_span().1),
})
}
TokenKind::Kw(Keyword::Rust) => {
p.advance();
let hatched = rust_hatch(p)?;
Ok(Expr::RustHatch {
code: hatched.0,
span: (start, p.prev_span().1),
})
}
TokenKind::Backslash => lambda(p),
TokenKind::Kw(Keyword::Move) => lambda(p),
TokenKind::Kw(Keyword::If) => p.if_statement(),
TokenKind::Kw(Keyword::Do) => bind_block(p, start),
TokenKind::Newline => block_expr(p, None, start),
TokenKind::Label(name) => {
p.advance();
block_expr(p, Some(name), start)
}
TokenKind::Kw(Keyword::Case) => case_of(p),
TokenKind::Kw(Keyword::SelfValue) => {
let span = p.peek_span();
p.advance();
Ok(Expr::Ident(Ident {
text: "self".to_string(),
span,
}))
}
TokenKind::Kw(Keyword::SelfType) => {
let span = p.peek_span();
p.advance();
Ok(Expr::Ident(Ident {
text: "Self".to_string(),
span,
}))
}
TokenKind::Dot => {
p.advance();
let name = p.ident("expected a method name after `.`")?;
let span = (start, name.span.1);
Ok(Expr::MethodRef { name, span })
}
TokenKind::Ident(_) => macro_call(p),
_ => Err(ParseError::r#with(
Code::ExpectedExpression,
"expected an expression",
p.peek_span(),
)),
}
}
pub(crate) fn starts_argument(kind: &TokenKind) -> bool {
match kind {
TokenKind::Int(_)
| TokenKind::Float(_)
| TokenKind::Str { .. }
| TokenKind::RawStr { .. }
| TokenKind::ByteStr(_)
| TokenKind::CStr(_)
| TokenKind::Char(_)
| TokenKind::Byte(_)
| TokenKind::Ident(_)
| TokenKind::LParen
| TokenKind::At
| TokenKind::AtAt => true,
TokenKind::Kw(Keyword::True | Keyword::False | Keyword::SelfValue | Keyword::SelfType) => true,
_ => false,
}
}
pub(crate) fn unary_op(p: &mut Parser) -> Option<UnaryOp> {
let op = match p.peek().kind {
TokenKind::Minus => UnaryOp::Neg,
TokenKind::Bang => UnaryOp::Not,
TokenKind::Star => UnaryOp::Deref,
TokenKind::Amp => {
p.advance();
if p.eat_keyword(Keyword::Mut) {
return Some(UnaryOp::RefMut);
}
return Some(UnaryOp::Ref);
}
_ => {
return None;
}
};
p.advance();
Some(op)
}
pub(crate) fn user_rung(p: &Parser) -> Option<(Ident, Fixity, u8, u8, String, u8)> {
let TokenKind::UserOp(text) = &p.peek().kind else {
return None;
};
let declared = p.fixities.get(text.as_str())?;
let fixity = declared.0;
let precedence = declared.1;
let powers = fixity.powers(precedence);
let spelling = Ident {
text: text.clone(),
span: p.peek_span(),
};
Some((
spelling,
fixity,
powers.0,
powers.1,
declared.2.clone(),
precedence,
))
}