do: Elaborate on pattern syntax, add binary as operator

This commit is contained in:
2025-09-16 04:18:12 -04:00
parent b6949147c4
commit 7b05da1334
6 changed files with 150 additions and 78 deletions

View File

@@ -15,6 +15,7 @@ pub enum ParseError {
Expected(TKind, Span),
NotLiteral(TKind, Span),
NotPattern(TKind, Span),
NotType(TKind, Span),
NotPrefix(TKind, Span),
NotInfix(TKind, Span),
NotPostfix(TKind, Span),
@@ -27,6 +28,7 @@ impl Display for ParseError {
Self::Expected(tk, loc) => write!(f, "{loc}: Expected {tk:?}."),
Self::NotLiteral(tk, loc) => write!(f, "{loc}: {tk:?} is not valid in a literal."),
Self::NotPattern(tk, loc) => write!(f, "{loc}: {tk:?} is not valid in a pattern."),
Self::NotType(tk, loc) => write!(f, "{loc}: {tk:?} is not valid in a type."),
Self::NotPrefix(tk, loc) => write!(f, "{loc}: {tk:?} is not a prefix operator."),
Self::NotInfix(tk, loc) => write!(f, "{loc}: {tk:?} is not a infix operator."),
Self::NotPostfix(tk, loc) => write!(f, "{loc}: {tk:?} is not a postfix operator."),
@@ -155,7 +157,7 @@ impl<'t> Parser<'t> {
})
}
/// Parses an expression into a vec unless the next token is `end`
/// Parses a P unless the next token is `end`
pub fn opt<P: Parse<'t>>(&mut self, level: P::Prec, end: TKind) -> PResult<Option<P>> {
let out = match self.peek_if(end) {
None => Some(self.parse(level)?),
@@ -179,8 +181,8 @@ pub trait Parse<'t> {
}
impl<'t> Parse<'t> for Literal {
type Prec = usize;
fn parse(p: &mut Parser<'t>, _level: usize) -> PResult<Self> {
type Prec = ();
fn parse(p: &mut Parser<'t>, _level: ()) -> PResult<Self> {
let tok = p.peek()?;
Ok(match tok.kind {
TKind::True => p.consume().then(Literal::Bool(true)),
@@ -209,9 +211,10 @@ impl<'t> Parse<'t> for Literal {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum PPrec {
Min,
Typed,
Tuple,
Alt,
NoTopAlt,
Max,
}
impl<'t> Parse<'t> for Pat {
@@ -223,7 +226,7 @@ impl<'t> Parse<'t> for Pat {
// Prefix
let mut head = match tok.kind {
TKind::True | TKind::False | TKind::Character | TKind::Integer | TKind::String => {
Pat::Lit(p.parse(0)?)
Pat::Lit(p.parse(())?)
}
TKind::Identifier => match tok.lexeme.as_str() {
"_" => p.consume().then(Pat::Ignore),
@@ -254,12 +257,15 @@ impl<'t> Parse<'t> for Pat {
let kind = tok.kind;
head = match kind {
TKind::Bar if level < PPrec::Alt => {
Pat::Alt(p.consume().list_bare(vec![head], PPrec::Alt, kind)?)
TKind::Colon if level > PPrec::Typed => {
Pat::Typed(head.into(), p.consume().parse(())?)
}
TKind::Comma if level < PPrec::Tuple => {
TKind::Comma if level > PPrec::Tuple => {
Pat::Tuple(p.consume().list_bare(vec![head], PPrec::Tuple, kind)?)
}
TKind::Bar if level > PPrec::Alt => {
Pat::Alt(p.consume().list_bare(vec![head], PPrec::Alt, kind)?)
}
_ => break,
}
}
@@ -268,13 +274,44 @@ impl<'t> Parse<'t> for Pat {
}
}
impl<'t> Parse<'t> for Ty {
type Prec = ();
fn parse(p: &mut Parser<'t>, level: Self::Prec) -> PResult<Self>
where Self: Sized {
let tok = p.peek()?;
let head = match tok.kind {
TKind::Identifier => match tok.lexeme.as_str() {
"_" => p.consume().then(Ty::Infer),
_ => Ty::Named(p.take_lexeme().expect("should have Token")),
},
TKind::LBrack => {
let ty = p.consume().parse(level)?;
match p.next()? {
Token { kind: TKind::Semi, .. } => {
let ty = Ty::Array(ty, p.parse(Prec::Binary.next())?);
p.next_if(TKind::RBrack)?;
ty
}
Token { kind: TKind::RBrack, .. } => Ty::Slice(ty),
tok => Err(ParseError::NotType(tok.kind, tok.span))?,
}
}
_ => Err(ParseError::NotType(tok.kind, tok.span))?,
};
Ok(head)
}
}
impl<'t> Parse<'t> for MatchArm {
type Prec = usize;
fn parse(p: &mut Parser<'t>, _level: usize) -> PResult<Self> {
fn parse(p: &mut Parser<'t>, level: usize) -> PResult<Self> {
p.next_if(TKind::Bar).ok();
Ok(MatchArm(
p.list(vec![], PPrec::Min, TKind::Bar, TKind::FatArrow)?,
p.parse(0)?,
p.list(vec![], PPrec::Max, TKind::Bar, TKind::FatArrow)?,
p.parse(level)?,
))
}
}
@@ -301,11 +338,11 @@ enum Prec {
Assign,
/// Constructor for a tuple
Tuple,
/// Constructor for a struct
Make,
/// The body of a function, conditional, etc.
Body,
/// The short-circuiting logical operators [Prec::LogOr], [Prec::LogAnd]
/// Constructor for a struct
Make,
/// The conditional of an `if` or `while` (which is really an `if`)
Logical,
/// The short-circuiting "boolean or" operator
LogOr,
@@ -372,17 +409,18 @@ pub enum Ps {
fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
Ok(match token.kind {
TKind::Do => (Ps::Op(Op::Do), Prec::Do),
TKind::Identifier => (Ps::Id, Prec::Max),
TKind::Grave => (Ps::Mid, Prec::Max),
TKind::ColonColon => (Ps::Op(Op::Path), Prec::Max),
TKind::True | TKind::False | TKind::Character | TKind::Integer | TKind::String => {
(Ps::Lit, Prec::Max)
}
TKind::Identifier => (Ps::Id, Prec::Max),
TKind::Grave => (Ps::Mid, Prec::Max),
TKind::Fn => (Ps::Fn, Prec::Body),
TKind::Match => (Ps::Match, Prec::Body),
TKind::Macro => (Ps::Op(Op::Macro), Prec::Assign),
TKind::Let => (Ps::Let, Prec::Body),
TKind::Let => (Ps::Let, Prec::Tuple),
TKind::Const => (Ps::Const, Prec::Body),
TKind::Loop => (Ps::Op(Op::Loop), Prec::Body),
TKind::If => (Ps::Op(Op::If), Prec::Body),
@@ -414,6 +452,17 @@ fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
fn from_infix(token: &Token) -> PResult<(Ps, Prec)> {
Ok(match token.kind {
TKind::Semi => (Ps::Op(Op::Do), Prec::Do), // the inspiration
TKind::As => (Ps::Op(Op::As), Prec::Body),
TKind::Comma => (Ps::Op(Op::Tuple), Prec::Tuple),
TKind::Dot => (Ps::Op(Op::Dot), Prec::Project),
TKind::ColonColon => (Ps::Op(Op::Path), Prec::Max),
TKind::AmpAmp => (Ps::Op(Op::LogAnd), Prec::LogAnd),
TKind::BarBar => (Ps::Op(Op::LogOr), Prec::LogOr),
TKind::Question => (Ps::Op(Op::Try), Prec::Unary),
TKind::LParen => (Ps::Op(Op::Call), Prec::Extend),
TKind::LBrack => (Ps::Op(Op::Index), Prec::Extend),
// TKind::LCurly => (Ps::Make, Prec::Make),
TKind::RParen | TKind::RBrack | TKind::RCurly => (Ps::End, Prec::Max),
TKind::Eq => (Ps::Op(Op::Set), Prec::Assign),
TKind::XorXor => (Ps::Op(Op::LogXor), Prec::Logical),
@@ -435,45 +484,19 @@ fn from_infix(token: &Token) -> PResult<(Ps, Prec)> {
TKind::Star => (Ps::Op(Op::Mul), Prec::Term),
TKind::Slash => (Ps::Op(Op::Div), Prec::Term),
TKind::Rem => (Ps::Op(Op::Rem), Prec::Term),
TKind::ColonColon => (Ps::Op(Op::Path), Prec::Max),
TKind::Question => (Ps::End, Prec::Extend),
kind => Err(ParseError::NotInfix(kind, token.span))?,
})
}
#[allow(clippy::match_single_binding, unused)]
fn from_postfix(token: &Token) -> PResult<(Ps, Prec)> {
Ok(match token.kind {
TKind::Semi => (Ps::Op(Op::Do), Prec::Do), // the inspiration
TKind::Comma => (Ps::Op(Op::Tuple), Prec::Tuple),
TKind::Dot => (Ps::Op(Op::Dot), Prec::Project),
TKind::ColonColon => (Ps::Op(Op::Path), Prec::Max),
TKind::AmpAmp => (Ps::Op(Op::LogAnd), Prec::LogAnd),
TKind::BarBar => (Ps::Op(Op::LogOr), Prec::LogOr),
TKind::Question => (Ps::Op(Op::Try), Prec::Unary),
TKind::LParen => (Ps::Op(Op::Call), Prec::Extend),
TKind::LBrack => (Ps::Op(Op::Index), Prec::Extend),
TKind::LCurly => (Ps::Make, Prec::Make),
kind => Err(ParseError::NotPostfix(kind, token.span))?,
// _ => (Ps::End, Prec::Max),
})
}
#[rustfmt::skip]
fn should_coagulate(prev: Op, op: Op) -> bool {
prev == op && match prev {
Op::LogAnd => true,
Op::LogOr => true,
Op::Dot => false,
Op::Path => true,
Op::Lt => false,
Op::Leq => false,
Op::Eq => false,
Op::Neq => false,
Op::Geq => false,
Op::Gt => false,
_ => false,
}
}
impl<'t> Parse<'t> for Expr {
type Prec = usize;
@@ -496,12 +519,12 @@ impl<'t> Parse<'t> for Expr {
Ps::Id => Expr::Id(p.take_lexeme().expect("should have ident")),
Ps::Mid => Expr::MetId(p.consume().next_if(TKind::Identifier)?.lexeme),
Ps::Lit => Expr::Lit(p.parse(MIN)?),
Ps::Lit => Expr::Lit(p.parse(())?),
Ps::Let => Expr::Let(
p.consume().parse(PPrec::NoTopAlt)?,
p.opt_if(prec.next(), TKind::Eq)?,
p.consume().parse(PPrec::Alt)?,
p.opt_if(prec.value(), TKind::Eq)?,
),
Ps::Const => Expr::Const(p.consume().parse(PPrec::NoTopAlt)?, {
Ps::Const => Expr::Const(p.consume().parse(PPrec::Tuple)?, {
p.next_if(TKind::Eq)?;
p.parse(prec.next())?
}),
@@ -514,7 +537,7 @@ impl<'t> Parse<'t> for Expr {
),
Ps::Match => Expr::Match(p.consume().parse(Prec::Logical.value())?, {
p.next_if(TKind::LCurly)?;
p.list(vec![], 0, TKind::Comma, TKind::RCurly)?
p.list(vec![], prec.next(), TKind::Comma, TKind::RCurly)?
}),
Ps::Op(Op::Block) => Expr::Op(
Op::Block,
@@ -576,10 +599,30 @@ impl<'t> Parse<'t> for Expr {
&& let Ok((op, prec)) = from_postfix(tok)
&& level <= prec.prev()
&& op != Ps::End
{
// let kind = tok.kind;
let span = span.merge(p.span());
// p.consume();
head = match (op, &head) {
(Ps::Make, Expr::Op(Op::Path, _) | Expr::Id(_) | Expr::MetId(_)) => Expr::Make(
head.anno(span).into(),
p.consume().list(vec![], (), TKind::Comma, TKind::RCurly)?,
),
_ => break,
};
}
// Infix
while let Ok(tok) = p.peek()
&& let Ok((op, prec)) = from_infix(tok)
&& level <= prec.prev()
&& op != Ps::End
{
let kind = tok.kind;
let span = span.merge(p.span());
p.consume();
head = match op {
Ps::Make => Expr::Make(
head.anno(span).into(),
@@ -596,27 +639,8 @@ impl<'t> Parse<'t> for Expr {
Ps::Op(op @ (Op::Do | Op::Tuple | Op::Dot | Op::Path | Op::LogAnd | Op::LogOr)) => {
Expr::Op(op, p.list_bare(vec![head.anno(span)], prec.next(), kind)?)
}
Ps::Op(op) => Expr::Op(op, vec![head.anno(span)]),
_ => unimplemented!("postfix {op:?}"),
};
}
// Infix
while let Ok(tok) = p.peek()
&& let Ok((op, prec)) = from_infix(tok)
&& level <= prec.prev()
&& op != Ps::End
{
let span = span.merge(p.span());
p.consume();
head = match (op, head) {
// controls expression chaining vs coagulating
(Ps::Op(op), Expr::Op(prev, mut args)) if should_coagulate(prev, op) => {
args.push(p.parse(prec.next())?);
Expr::Op(op, args)
}
(Ps::Op(op), head) => Expr::Op(op, vec![head.anno(span), p.parse(prec.next())?]),
Ps::Op(op @ Op::Try) => Expr::Op(op, vec![head.anno(span)]),
Ps::Op(op) => Expr::Op(op, vec![head.anno(span), p.parse(prec.next())?]),
_ => unimplemented!("infix {op:?}"),
}
}