use crate::ParseError;
use crate::block::qualifiers;
use crate::block::reaches_a_draw_arrow;
use crate::expr::atom;
use crate::expr::binary_op;
use crate::expr::parse_expr;
use crate::item::retired_spelling;
use crate::parser::Parser;
use crate::pattern::pattern_alt;
use crate::signature::TypeStop;
use crate::signature::capture_type;
use crate::signature::rust_generic;
use crate::support::above_bitor;
use crate::token::expr_from_tokens;
use crate::token::rust_string_body;
use crate::token::token_text;
use voltaic_ast::BinaryOp;
use voltaic_ast::Block;
use voltaic_ast::Expr;
use voltaic_ast::FieldInit;
use voltaic_ast::Ident;
use voltaic_ast::Param;
use voltaic_ast::Pattern;
use voltaic_ast::Section;
use voltaic_ast::Signature;
use voltaic_ast::StrInterpPart;
use voltaic_diag::Code;
use voltaic_lexer::Keyword;
use voltaic_lexer::Span;
use voltaic_lexer::StrPart;
use voltaic_lexer::TokenKind;
pub(crate) fn assign_tail(p: &mut Parser, target: Expr, start: usize) -> Result<Expr, ParseError> {
let op = if p.check(&TokenKind::ColonEq) {
p.advance();
None
} else {
let Some(chosen) = compound_assign(p) else {
return Ok(target);
};
Some(chosen)
};
let value = Box::new(parse_expr(p, 0)?);
Ok(Expr::Assign {
target: Box::new(target),
op,
value,
span: (start, p.prev_span().1),
})
}
pub(crate) fn bracketed(p: &mut Parser) -> Result<Expr, ParseError> {
let start = p.peek_span().0;
p.expect(&TokenKind::LBracket, "expected `[`")?;
p.skip_layout();
if p.check(&TokenKind::RBracket) {
p.advance();
return Ok(Expr::List {
items: Vec::new(),
span: (start, p.prev_span().1),
});
}
let first = parse_expr(p, above_bitor())?;
p.skip_layout();
if p.check(&TokenKind::Or) {
p.advance();
p.skip_layout();
let quals = qualifiers(p)?;
p.skip_layout();
p.expect(
&TokenKind::RBracket,
"expected `]` to close the comprehension",
)?;
return Ok(Expr::Comprehension {
element: Box::new(first),
qualifiers: quals,
span: (start, p.prev_span().1),
});
}
if p.check(&TokenKind::Semi) {
p.advance();
p.skip_layout();
let count = parse_expr(p, 0)?;
p.skip_layout();
p.expect(
&TokenKind::RBracket,
"expected `]` to close the array repeat",
)?;
return Ok(Expr::ArrayRepeat {
value: Box::new(first),
count: Box::new(count),
span: (start, p.prev_span().1),
});
}
let mut items = vec![first];
while p.check(&TokenKind::Comma) {
p.advance();
p.skip_layout();
if p.check(&TokenKind::RBracket) {
break;
}
items.push(parse_expr(p, 0)?);
p.skip_layout();
}
p.expect(&TokenKind::RBracket, "expected `]` to close the list")?;
Ok(Expr::List {
items,
span: (start, p.prev_span().1),
})
}
pub(crate) fn byte_literal(value: u8) -> String {
match value {
b'\\' => "b'\\\\'".to_string(),
b'\'' => "b'\\''".to_string(),
0x20..=0x7E => format!("b'{}'", char::from(value)),
_ => format!("b'\\x{:02X}'", value),
}
}
pub(crate) fn compound_assign(p: &mut Parser) -> Option<BinaryOp> {
let op = match p.peek().kind {
TokenKind::PlusEq => BinaryOp::Add,
TokenKind::MinusEq => BinaryOp::Sub,
TokenKind::StarEq => BinaryOp::Mul,
TokenKind::SlashEq => BinaryOp::Div,
TokenKind::PercentEq => BinaryOp::Rem,
_ => {
return None;
}
};
p.advance();
Some(op)
}
pub(crate) fn constructor_path(p: &mut Parser) -> Option<String> {
let TokenKind::Ident(first) = &p.peek().kind else {
return None;
};
if !first.chars().next().is_some_and(char::is_uppercase) {
return None;
}
let mut text = first.clone();
p.advance();
while p.check(&TokenKind::ColonColon) {
p.advance();
let TokenKind::Ident(next) = &p.peek().kind else {
break;
};
text.push_str("::");
text.push_str(next);
p.advance();
}
Some(text)
}
pub(crate) fn field_init(p: &mut Parser) -> Result<FieldInit, ParseError> {
let start = p.peek_span().0;
let name = p.ident("expected a field name")?;
let value = if p.check(&TokenKind::Eq) {
p.advance();
Some(parse_expr(p, 0)?)
} else {
None
};
Ok(FieldInit {
name,
value,
span: (start, p.prev_span().1),
})
}
pub(crate) fn interpolated_string(parts: &[StrPart], span: Span) -> Result<Expr, ParseError> {
let mut out = Vec::with_capacity(parts.len());
for part in parts {
match part {
StrPart::Text(text) => out.push(StrInterpPart::Text(rust_string_body(text))),
StrPart::Interp { tokens, spec } => out.push(StrInterpPart::Interp {
expr: expr_from_tokens(tokens, span)?,
spec: spec.clone(),
}),
};
}
Ok(Expr::StrInterp { parts: out, span })
}
pub(crate) fn lambda(p: &mut Parser) -> Result<Expr, ParseError> {
let start = p.peek_span().0;
let is_move = p.eat_keyword(Keyword::Move);
p.expect(&TokenKind::Backslash, "expected `\\` to open a lambda")?;
let mut params = Vec::new();
while !p.check(&TokenKind::Arrow) && !p.at_eof() {
let name = p.ident("expected a lambda parameter name")?;
let ty = if p.check(&TokenKind::Colon) {
p.advance();
Some(capture_type(
p,
"expected a type after `:`",
TypeStop::Greedy,
)?)
} else {
None
};
let at = name.span;
params.push(Param {
name,
ty,
is_mut: false,
span: at,
});
}
p.expect(
&TokenKind::Arrow,
"expected `->` after the lambda's parameters",
)?;
let body = parse_expr(p, 0)?;
let span = (start, body.span().1);
Ok(Expr::Lambda {
params,
body: Box::new(body),
is_move,
span,
})
}
pub(crate) fn macro_call(p: &mut Parser) -> Result<Expr, ParseError> {
let head = path_or_ident(p)?;
if !p.check(&TokenKind::Bang) || p.peek().ws_before {
if let Expr::Ident(name) = &head
&& let Some(retired) = retired_spelling(name)
{
return Err(retired);
}
return Ok(head);
}
p.advance();
let name = match &head {
Expr::Ident(i) => i.text.clone(),
Expr::Path { text, .. } => text.clone(),
other => unreachable!("a macro head is a name or a path, not {:?}", other),
};
let start = head.span().0;
let mut args = Vec::new();
while p.check(&TokenKind::Backslash) && p.peek_at(1).is_some_and(|t| t.kind == TokenKind::Arrow) {
p.advance();
p.advance();
args.push(parse_expr(p, 0)?);
}
Ok(Expr::MacroCall {
name,
args,
span: (start, p.prev_span().1),
})
}
pub(crate) fn method_call_syntax(name: &str, span: Span) -> ParseError {
let help = format!(
"a method is a function whose last argument is its receiver, so write `.{} args recv` or `recv |> .{} args`. `.` is field access and nothing else (plan 57 §1.1)",
name, name
);
ParseError::with_help(
Code::MethodCallSyntax,
"method-call syntax is retired",
help,
span,
)
}
pub(crate) struct MethodContext {
pub vars: Vec<(String, String)>,
pub recording: Option<String>,
pub implementing: Option<String>,
}
impl MethodContext {
pub(crate) fn for_class(params: &[Ident]) -> Self {
let vars = params
.iter()
.map(|param| (param.text.clone(), rust_generic(¶m.text)))
.collect();
MethodContext {
vars,
recording: None,
implementing: None,
}
}
pub(crate) fn for_instance(var: Option<&str>, subject: &str) -> Self {
let vars = var
.map(|found| (found.to_string(), subject.to_string()))
.into_iter()
.collect();
MethodContext {
vars,
recording: None,
implementing: None,
}
}
pub(crate) fn reserved(&self) -> Vec<String> {
self.vars.iter().map(|pair| pair.0.clone()).collect()
}
}
pub(crate) fn opens_a_section(kind: &TokenKind) -> bool {
match kind {
TokenKind::Minus | TokenKind::Amp | TokenKind::Star | TokenKind::Bang => false,
_ => true,
}
}
pub(crate) fn opens_an_over(p: &Parser) -> bool {
let word_is_over = match &p.peek().kind {
TokenKind::Ident(text) => text == "over",
_ => false,
};
if !word_is_over {
return false;
}
let opens = p.peek_at(1).is_some_and(|t| t.kind == TokenKind::LParen);
let selects = p.peek_at(2).is_some_and(|t| t.kind == TokenKind::Dot);
opens && selects
}
pub(crate) fn optional_chaining(span: Span) -> ParseError {
ParseError::with_help(
Code::OptionalChaining,
"`?.` is retired",
"write the `?` run its own lowering always was — `try` with `Some (&u.as_ref ()? .profile.as_ref ()? .name)` — and keep the `&`, because a field selector moves (plan 57 §11.4). `x? .name`, with a space, is `?` and then a field",
span,
)
}
pub(crate) fn over_update(p: &mut Parser, start: usize) -> Result<Expr, ParseError> {
p.advance();
let at_selector = p.peek_span();
p.expect(&TokenKind::LParen, "expected `(` to open the selector")?;
let Some(selected) = section(p, at_selector.0)? else {
return Err(ParseError::with_help(
Code::OverShape,
"expected a field selector after `over`",
"`over` takes a parenthesised path — `over (.addr.city) .to_uppercase u`",
at_selector,
));
};
let Expr::Section {
section: Section::Field(path),
..
} = selected
else {
return Err(ParseError::with_help(
Code::OverShape,
"expected a field selector after `over`",
"`over` takes a parenthesised path of field names — `over (.addr.city) .to_uppercase u`. `(.cmp y)` is a method application, not a path",
at_selector,
));
};
let function = atom(p)?;
let place = atom(p)?;
Ok(Expr::Over {
selector: path,
function: Box::new(function),
place: Box::new(place),
span: (start, p.prev_span().1),
})
}
pub(crate) fn path_or_ident(p: &mut Parser) -> Result<Expr, ParseError> {
let first = p.ident("expected a name")?;
if !p.check(&TokenKind::ColonColon) {
return Ok(Expr::Ident(first));
}
let start = first.span.0;
let mut text = first.text;
let mut tight_through = first.span.1;
while p.check(&TokenKind::ColonColon) {
let separator = p.peek_span();
if separator.0 > tight_through {
return Err(ParseError::r#with(
Code::SpacedPathSeparator,
"`::` joins a path, so it takes no space before it — write `:` to annotate a type, or close up the `::` to name a path",
separator,
));
}
p.advance();
let next = p.ident("expected a name after `::`")?;
text.push_str("::");
text.push_str(&next.text);
tight_through = next.span.1;
}
Ok(Expr::Path {
text,
span: (start, p.prev_span().1),
})
}
pub(crate) fn pipeline_section(name: &str, span: Span) -> ParseError {
let help = format!(
"write `.{}` — a method is a function awaiting its receiver, and it lowers to the closure this spelled. `(.{})` still selects a *field* (plan 57 §1.3)",
name, name
);
ParseError::with_help(
Code::PipelineSection,
"the `(|> name)` method reference is retired",
help,
span,
)
}
pub(crate) fn range_or_literal(p: &mut Parser, lo: String, span: Span) -> Pattern {
let inclusive = match &p.peek().kind {
TokenKind::DotDotEq => true,
TokenKind::DotDot => false,
_ => {
return Pattern::Literal { text: lo, span };
}
};
p.advance();
let hi = token_text(&p.peek().kind);
let end = p.peek_span().1;
p.advance();
Pattern::Range {
lo,
hi,
inclusive,
span: (span.0, end),
}
}
pub(crate) fn receiver(sig: &mut Signature) {
let Some(at) = sig.params.iter().position(|param| {
param
.ty
.as_ref()
.is_some_and(|ty| receiver_text(&ty.text).is_some())
}) else {
return ();
};
let text = sig.params[at]
.ty
.as_ref()
.and_then(|ty| receiver_text(&ty.text))
.expect("the position just matched");
let span = sig.params[at].span;
sig.params[at] = Param {
name: Ident { text, span },
ty: None,
is_mut: false,
span,
};
if at > 0 {
let n = sig.params.len();
let mut order: Vec<usize> = (0..n).collect();
order.remove(at);
order.insert(0, at);
for pair in order.into_iter().enumerate() {
let rust_at = pair.0;
let from = pair.1;
let name = &mut sig.params[from].name.text;
if name.starts_with("__rdx_a") {
*name = format!("__rdx_a{}", rust_at);
}
}
}
();
}
pub(crate) fn receiver_text(ty: &str) -> Option<String> {
let split = if let Some(rest) = ty.strip_prefix("&mut ") {
("&mut ", rest)
} else if let Some(rest) = ty.strip_prefix('&') {
("&", rest)
} else if let Some(rest) = ty.strip_prefix("mut ") {
("mut ", rest)
} else {
("", ty)
};
if split.1.trim() != "self" {
return None;
}
let prefix = split.0;
Some(format!("{}self", prefix))
}
pub(crate) fn record_literal(p: &mut Parser, head: Expr) -> Result<Expr, ParseError> {
let start = head.span().0;
p.expect(&TokenKind::LBrace, "expected `{` to open a record")?;
let mut fields = Vec::new();
loop {
p.skip_layout();
if p.check(&TokenKind::RBrace) {
break;
}
fields.push(field_init(p)?);
p.skip_layout();
if !p.check(&TokenKind::Comma) {
break;
}
p.advance();
}
p.skip_layout();
p.expect(&TokenKind::RBrace, "expected `}` to close the record")?;
Ok(Expr::Record {
head: Box::new(head),
fields,
span: (start, p.prev_span().1),
})
}
pub(crate) fn section(p: &mut Parser, start: usize) -> Result<Option<Expr>, ParseError> {
let mark = p.position();
let names_a_field = if let Some(next) = p.peek_at(1) {
match next.kind {
TokenKind::Ident(_) => true,
_ => false,
}
} else {
false
};
if p.check(&TokenKind::Dot) && names_a_field {
p.advance();
let mut path = vec![p.ident("expected a field name after `.`")?];
while p.check(&TokenKind::Dot)
&& (if let Some(next) = p.peek_at(1) {
match next.kind {
TokenKind::Ident(_) => true,
_ => false,
}
} else {
false
})
{
p.advance();
path.push(p.ident("expected a field name after `.`")?);
}
if p.check(&TokenKind::RParen) {
p.advance();
return Ok(Some(Expr::Section {
section: Section::Field(path),
span: (start, p.prev_span().1),
}));
}
p.rewind(mark);
return Ok(None);
}
if p.check(&TokenKind::Pipe)
&& let Some(token) = p.peek_at(1)
&& let TokenKind::Ident(named) = &token.kind
{
let end = token.span.1;
return Err(pipeline_section(named, (start, end)));
}
if opens_a_section(&p.peek().kind) {
if let Some(leading_op) = binary_op(p) {
let operand = parse_expr(p, 0)?;
p.expect(&TokenKind::RParen, "expected `)` to close the section")?;
return Ok(Some(Expr::Section {
section: Section::Right {
op: leading_op,
operand: Box::new(operand),
},
span: (start, p.prev_span().1),
}));
}
p.rewind(mark);
}
let Ok(operand) = atom(p) else {
p.rewind(mark);
return Ok(None);
};
if let Some(trailing_op) = binary_op(p)
&& p.check(&TokenKind::RParen)
{
p.advance();
return Ok(Some(Expr::Section {
section: Section::Left {
op: trailing_op,
operand: Box::new(operand),
},
span: (start, p.prev_span().1),
}));
}
p.rewind(mark);
Ok(None)
}
pub(crate) fn try_await(p: &mut Parser) -> Result<Option<Expr>, ParseError> {
let start = p.peek_span().0;
if p.check(&TokenKind::DrawArrow) {
p.advance();
let effect = Box::new(parse_expr(p, 0)?);
return Ok(Some(Expr::Await {
pattern: None,
value: effect,
span: (start, p.prev_span().1),
}));
}
if !reaches_a_draw_arrow(p) {
return Ok(None);
}
let pattern = pattern_alt(p)?;
p.expect(&TokenKind::DrawArrow, "expected `<-`")?;
let value = Box::new(parse_expr(p, 0)?);
Ok(Some(Expr::Await {
pattern: Some(pattern),
value,
span: (start, p.prev_span().1),
}))
}
pub(crate) fn ungrouped(text: &str) -> String {
let Some(inner) = text.strip_prefix('(').and_then(|t| t.strip_suffix(')')) else {
return text.to_string();
};
let chars: Vec<char> = inner.chars().collect();
let mut depth: i32 = 0;
let mut top_level_arrow = false;
let mut top_level_comma = false;
let mut i = 0;
let n = chars.len();
while i < n {
if chars[i] == '-' && chars.get(i + 1) == Some(&'>') {
top_level_arrow = top_level_arrow || depth == 0;
i += 2;
continue;
}
match chars[i] {
'(' | '[' | '<' => depth += 1,
')' | ']' | '>' => depth -= 1,
',' if depth == 0 => top_level_comma = true,
_ => (),
};
if depth < 0 {
return text.to_string();
}
i += 1;
}
if depth != 0 || !top_level_arrow || top_level_comma {
return text.to_string();
}
inner.to_string()
}
pub(crate) fn unit_tail(name: &Ident, block: &Block) -> Result<(), ParseError> {
let Some(last) = block.stmts.last() else {
return Ok(());
};
let spells_unit = match last.expr {
Expr::Unit { .. } | Expr::Return { .. } | Expr::Break { .. } | Expr::Continue { .. } => true,
_ => false,
};
if spells_unit {
return Ok(());
}
let message = format!(
"`{}` returns `()`, so its body must end with `()`",
name.text
);
Err(ParseError::with_help(
Code::UnitTail,
message,
"write `()` as the body's last line — plan 55 §1.6 spells a unit value rather than implying it, so a body ending in a loop, a binding or an assignment cannot become `()` by arriving last",
last.span,
))
}
#[cfg(test)]
mod __rdx_tests {
use super::*;
#[test]
fn a_function_types_grouping_goes() {
assert_eq!(
ungrouped("(impl Fn(char) -> bool)"),
"impl Fn(char) -> bool"
);
}
#[test]
fn a_tuple_a_unit_and_a_trait_object_keep_theirs() {
assert_eq!(ungrouped("(i32, u32)"), "(i32, u32)");
assert_eq!(ungrouped("()"), "()");
assert_eq!(ungrouped("(dyn Display)"), "(dyn Display)");
}
#[test]
fn a_tuple_holding_a_function_type_keeps_its_parentheses() {
assert_eq!(
ungrouped("(&str, fn(char) -> bool)"),
"(&str, fn(char) -> bool)"
);
}
#[test]
fn two_groups_are_not_one() {
assert_eq!(ungrouped("(a) -> (b)"), "(a) -> (b)");
}
#[test]
fn an_unparenthesised_type_is_untouched() {
assert_eq!(ungrouped("Vec<Vec<u8>>"), "Vec<Vec<u8>>");
assert_eq!(ungrouped("&'a str"), "&'a str");
}
}