use crate::ParseError;
use crate::operand::constructor_path;
use crate::operand::range_or_literal;
use crate::parser::Parser;
use crate::token::token_text;
use voltaic_ast::Ident;
use voltaic_ast::Pattern;
use voltaic_ast::RecordPatternField;
use voltaic_lexer::Keyword;
use voltaic_lexer::TokenKind;
pub(crate) fn pattern(p: &mut Parser) -> Result<Pattern, ParseError> {
let span = p.peek_span();
match &p.peek().kind {
TokenKind::Ident(text) if text == "_" => {
p.advance();
Ok(Pattern::Wildcard(span))
}
TokenKind::Ident(_) => {
let name = p.ident("expected a pattern")?;
if p.check(&TokenKind::At) {
p.advance();
if p.check(&TokenKind::DotDot) {
p.advance();
return Ok(Pattern::NamedRest {
name,
span: (span.0, p.prev_span().1),
});
}
let inner = pattern_app(p)?;
return Ok(Pattern::As {
name,
inner: Box::new(inner),
span: (span.0, p.prev_span().1),
});
}
if p.check(&TokenKind::LBrace) {
return record_pattern(p, name.text, span.0);
}
Ok(Pattern::Binding(name))
}
TokenKind::DotDot => {
p.advance();
Ok(Pattern::Rest(span))
}
TokenKind::LBracket => {
p.advance();
let items = pattern_series(p, &TokenKind::RBracket)?;
p.expect(
&TokenKind::RBracket,
"expected `]` to close the slice pattern",
)?;
Ok(Pattern::Slice {
items,
span: (span.0, p.prev_span().1),
})
}
TokenKind::DotDotDot => Err(ParseError::r#with(
voltaic_diag::Code::EllipsisRestPattern,
"`...` left the language with doc 18 §7 — write a rest pattern Rust's way: `..` to discard, or `rest @ ..` to bind",
span,
)),
TokenKind::LParen => {
p.advance();
if p.check(&TokenKind::RParen) {
p.advance();
return Ok(Pattern::Tuple {
items: Vec::new(),
span: (span.0, p.prev_span().1),
});
}
let inner = pattern_alt(p)?;
if p.check(&TokenKind::Comma) {
p.advance();
let mut items = vec![inner];
items.extend(pattern_series(p, &TokenKind::RParen)?);
p.expect(
&TokenKind::RParen,
"expected `)` to close the tuple pattern",
)?;
return Ok(Pattern::Tuple {
items,
span: (span.0, p.prev_span().1),
});
}
p.expect(
&TokenKind::RParen,
"expected `)` to close a parenthesised pattern",
)?;
Ok(inner)
}
TokenKind::Kw(Keyword::SelfValue) => {
p.advance();
Ok(Pattern::Binding(Ident {
text: "self".to_string(),
span,
}))
}
TokenKind::Int(_)
| TokenKind::Float(_)
| TokenKind::Char(_)
| TokenKind::Byte(_)
| TokenKind::Kw(Keyword::True | Keyword::False) => {
let text = token_text(&p.peek().kind);
p.advance();
Ok(range_or_literal(p, text, span))
}
TokenKind::Str { raw, .. } => {
let text = raw.clone();
p.advance();
Ok(Pattern::Literal { text, span })
}
_ => Err(ParseError::r#with(
voltaic_diag::Code::ExpectedName,
"expected a parameter name, a literal pattern, a range, `_`, `(`, `[`, or `=`",
span,
)),
}
}
pub(crate) fn pattern_alt(p: &mut Parser) -> Result<Pattern, ParseError> {
let first = pattern_app(p)?;
if !p.check(&TokenKind::Kw(Keyword::Or)) {
return Ok(first);
}
let mut alts = vec![first];
while p.eat_keyword(Keyword::Or) {
alts.push(pattern_app(p)?);
}
Ok(Pattern::Or(alts))
}
pub(crate) fn pattern_app(p: &mut Parser) -> Result<Pattern, ParseError> {
let start = p.peek_span().0;
let Some(path) = constructor_path(p) else {
return pattern(p);
};
if p.check(&TokenKind::LBrace) {
return record_pattern(p, path, start);
}
let mut args = Vec::new();
while starts_a_pattern_argument(p) {
args.push(pattern(p)?);
}
if args.is_empty() {
return Ok(Pattern::Binding(Ident {
text: path,
span: (start, p.prev_span().1),
}));
}
Ok(Pattern::Apply {
path,
args,
span: (start, p.prev_span().1),
})
}
pub(crate) fn pattern_series(
p: &mut Parser,
close: &TokenKind,
) -> Result<Vec<Pattern>, ParseError> {
let mut items = Vec::new();
p.skip_layout();
while !p.check(close) {
items.push(pattern_alt(p)?);
p.skip_layout();
if !p.check(&TokenKind::Comma) {
break;
}
p.advance();
p.skip_layout();
}
Ok(items)
}
pub(crate) fn patterns(p: &mut Parser) -> Result<Vec<Pattern>, ParseError> {
let mut found = Vec::new();
while !p.at_eof() && !p.check(&TokenKind::Eq) && !p.check(&TokenKind::Newline) {
found.push(pattern(p)?);
}
Ok(found)
}
pub(crate) fn record_pattern(
p: &mut Parser,
head: String,
start: usize,
) -> Result<Pattern, ParseError> {
let mut path = head;
while p.check(&TokenKind::ColonColon) {
p.advance();
let segment = p.ident("expected a name after `::`")?;
path.push_str("::");
path.push_str(&segment.text);
}
p.expect(&TokenKind::LBrace, "expected `{` to open a record pattern")?;
let mut fields = Vec::new();
let mut rest = false;
p.skip_layout();
while !p.check(&TokenKind::RBrace) {
if p.check(&TokenKind::DotDot) {
p.advance();
rest = true;
break;
}
let name = p.ident("expected a field name in a record pattern")?;
let bound = if p.check(&TokenKind::Eq) {
p.advance();
Some(pattern_alt(p)?)
} else {
None
};
fields.push(RecordPatternField {
name,
pattern: bound,
});
p.skip_layout();
if !p.check(&TokenKind::Comma) {
break;
}
p.advance();
p.skip_layout();
}
p.expect(
&TokenKind::RBrace,
"expected `}` to close the record pattern",
)?;
Ok(Pattern::Record {
path,
fields,
rest,
span: (start, p.prev_span().1),
})
}
pub(crate) fn starts_a_pattern_argument(p: &Parser) -> bool {
match &p.peek().kind {
TokenKind::Ident(_)
| TokenKind::Int(_)
| TokenKind::Float(_)
| TokenKind::Char(_)
| TokenKind::Byte(_)
| TokenKind::Str { .. }
| TokenKind::LParen => true,
TokenKind::Kw(Keyword::True)
| TokenKind::Kw(Keyword::False)
| TokenKind::Kw(Keyword::SelfValue) => true,
_ => false,
}
}