Files
Doughlang/src/parser/pat.rs

182 lines
6.8 KiB
Rust

use super::{PResult, PResultExt, Parse, ParseError, Parser};
use crate::{
ast::*,
token::{TKind, Token},
};
/// Precedence levels of value and type pattern expressions.
///
/// Lower (toward [Prec::Min]) precedence levels can contain
/// all higher (toward [Prec::Max]) precedence levels.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Prec {
/// The lowest precedence
Min,
/// "Alternate" pattern: `Pat | Pat`
Alt,
/// Tuple pattern: `Pat,+`
Tuple,
/// Type annotation: `Pat : Pat`
Typed,
/// Function pattern: `Pat -> Pat`
Fn,
/// Range pattern: `Pat .. Pat`, `Pat ..= Pat`
Range,
/// The highest precedence
Max,
}
macro_rules! intify {
($enum:ident($value:path) = $min:ident, $max: ident, $($variant:ident),*$(,)?) => {
#[expect(non_upper_case_globals)] {
const $min: u32 = $enum::$min as _;
const $max: u32 = $enum::$max as _;
$(const $variant: u32 = $enum::$variant as _;)*
match $value {
..=$min => $enum::$min,
$($variant => $enum::$variant,)*
$max.. => $enum::$max,
}
}};
}
impl Prec {
const fn from_int(value: u32) -> Self {
intify! {Prec(value) = Min, Max, Alt, Tuple, Typed, Fn, Range}
}
/// Returns the level of precedence higher than this one
const fn next(self) -> Self {
Self::from_int(self as u32 + 1)
}
}
/// Tries to map the incoming Token to a [pattern operator](PatOp)
/// and its [precedence level](Prec)
fn from_infix(token: &Token) -> Option<(PatOp, Prec)> {
Some(match token.kind {
TKind::Arrow => (PatOp::Fn, Prec::Fn),
TKind::Bar => (PatOp::Alt, Prec::Alt),
TKind::Colon => (PatOp::Typed, Prec::Typed),
TKind::Comma => (PatOp::Tuple, Prec::Tuple),
TKind::DotDot => (PatOp::RangeEx, Prec::Range),
TKind::DotDotEq => (PatOp::RangeIn, Prec::Range),
_ => None?,
})
}
impl<'t> Parse<'t> for Pat {
type Prec = Prec;
fn parse(p: &mut Parser<'t>, level: Prec) -> PResult<Self> {
let tok = p.peek()?;
// Prefix
let mut head = match tok.kind {
TKind::Fn => return p.consume().parse(Prec::Fn),
TKind::True | TKind::False | TKind::Character | TKind::Integer | TKind::String => {
Pat::Lit(p.parse(())?)
}
TKind::Bar => p.consume().parse(level)?,
TKind::Bang => p.consume().then(Pat::Never),
TKind::Amp => Pat::Op(PatOp::Ref, vec![p.consume().parse(Prec::Max)?]),
TKind::Star => Pat::Op(PatOp::Ptr, vec![p.consume().parse(Prec::Max)?]),
TKind::Mut => Pat::Op(PatOp::Mut, vec![p.consume().parse(Prec::Max)?]),
TKind::Pub => Pat::Op(PatOp::Pub, vec![p.consume().parse(Prec::Max)?]),
TKind::AmpAmp => Pat::Op(
PatOp::Ref,
vec![Pat::Op(PatOp::Ref, vec![p.consume().parse(Prec::Max)?])],
),
TKind::Identifier => match tok.lexeme.str() {
Some("_") => p.consume().then(Pat::Ignore),
_ => {
let mut path: Path = p.parse(())?;
// TODO: make these postfix.
match p.peek().map(Token::kind) {
Ok(TKind::LParen) => Pat::NamedTuple(path, p.parse(Prec::Typed)?),
Ok(TKind::LCurly) if level <= Prec::Tuple.next() => Pat::NamedStruct(
path,
p.consume()
.opt(Prec::Tuple, TKind::RCurly)?
.unwrap_or_else(|| Box::new(Pat::Op(PatOp::Tuple, vec![]))),
),
Ok(_) | Err(ParseError::EOF(_)) => match path.parts.len() {
1 => Self::Name(path.parts.pop().expect("name has 1 part")),
_ => Self::Path(path),
},
Err(e) => Err(e)?,
}
}
},
TKind::Grave => Pat::MetId(p.consume().next()?.lexeme.to_string()),
TKind::DotDot => Pat::Op(
PatOp::Rest,
// Identifier in Rest position always becomes binder
match p.consume().peek().allow_eof()?.map(Token::kind) {
Some(TKind::Identifier) => vec![Pat::Name(
p.take_lexeme()
.expect("should have lexeme")
.string()
.expect("should be string"),
)],
Some(TKind::Grave | TKind::Integer | TKind::Character) => vec![p.parse(level)?],
_ => vec![],
},
),
TKind::DotDotEq => Pat::Op(
PatOp::RangeIn,
match p.consume().peek().allow_eof()?.map(Token::kind) {
Some(TKind::Grave | TKind::Integer | TKind::Character) => vec![p.parse(level)?],
_ => vec![],
},
),
TKind::LParen => Pat::Op(
PatOp::Tuple,
p.consume()
.list(vec![], Prec::Typed, TKind::Comma, TKind::RParen)?,
),
TKind::LBrack => parse_array_pat(p)?,
_ => Err(ParseError::NotPattern(tok.kind, tok.span))?,
};
while let Ok(Some(tok)) = p.peek().allow_eof()
&& let Some((op, prec)) = from_infix(tok)
&& level <= prec
{
let kind = tok.kind;
head = match op {
PatOp::Typed => Pat::Op(PatOp::Typed, vec![head, p.consume().parse(prec.next())?]),
PatOp::Fn => Pat::Op(PatOp::Fn, vec![head, p.consume().parse(Prec::Fn.next())?]),
op @ (PatOp::RangeEx | PatOp::RangeIn) => Pat::Op(
op,
match p.consume().peek().map(Token::kind) {
Ok(TKind::Integer | TKind::Character | TKind::Identifier) => {
vec![head, p.parse(prec.next())?]
}
_ => vec![head],
},
),
op => Pat::Op(op, p.consume().list_bare(vec![head], prec.next(), kind)?),
}
}
Ok(head)
}
}
fn parse_array_pat(p: &mut Parser<'_>) -> PResult<Pat> {
if p.consume().peek()?.kind == TKind::RBrack {
p.consume();
return Ok(Pat::Op(PatOp::Slice, vec![]));
}
let item = p.parse(Prec::Tuple)?;
let repeat = p.opt_if(Prec::Tuple, TKind::Semi)?;
p.expect(TKind::RBrack)?;
Ok(match (repeat, item) {
(Some(repeat), item) => Pat::Op(PatOp::ArRep, vec![item, repeat]),
(None, Pat::Op(PatOp::Tuple, items)) => Pat::Op(PatOp::Slice, items),
(None, item) => Pat::Op(PatOp::Slice, vec![item]),
})
}