conlang: PATTERN MATCHING AND DESTRUCTURED BINDINGS WOOOOO

- Integrate the Match and Pattern nodes into the AST
  - TODO: `let x: T` is ambiguous with `let x: {}`. Currently the latter takes precedence in the parser.

- Implement pattern matching through unification in the interpreter.
  - It's not fast, but it works!

- Refactor destructuring assignments to use the new pattern functionality
This commit is contained in:
2025-01-29 04:15:33 -06:00
parent 6e94b702c9
commit 6ee9bbd72e
14 changed files with 285 additions and 119 deletions

View File

@@ -119,6 +119,10 @@ pub enum Parsing {
Break,
Return,
Continue,
Pattern,
Match,
MatchArm,
}
impl Display for Error {
@@ -225,6 +229,10 @@ impl Display for Parsing {
Parsing::Break => "a break expression",
Parsing::Return => "a return expression",
Parsing::Continue => "a continue expression",
Parsing::Pattern => "a pattern",
Parsing::Match => "a match expression",
Parsing::MatchArm => "a match arm",
}
.fmt(f)
}

View File

@@ -917,7 +917,7 @@ impl Parse<'_> for Let {
p.consume_peeked();
Ok(Let {
mutable: Mutability::parse(p)?,
name: Sym::parse(p)?,
name: Pattern::parse(p)?,
ty: if p.match_type(TokenKind::Colon, Parsing::Let).is_ok() {
Some(Ty::parse(p)?.into())
} else {
@@ -1075,6 +1075,38 @@ impl Parse<'_> for Return {
}
}
impl Parse<'_> for Pattern {
fn parse(p: &mut Parser<'_>) -> PResult<Self> {
let value = prec::exprkind(p, prec::Precedence::Highest.level())?;
Pattern::try_from(value)
.map_err(|_| p.error(ExpectedParsing { want: Parsing::Pattern }, Parsing::Pattern))
}
}
impl Parse<'_> for Match {
/// [Match] = `match` [Expr] `{` [MatchArm],* `}`
fn parse(p: &mut Parser<'_>) -> PResult<Self> {
p.match_type(TokenKind::Match, Parsing::Match)?;
let scrutinee = Expr::parse(p)?.into();
let arms = delim(
sep(MatchArm::parse, TokenKind::Comma, CURLIES.1, Parsing::Match),
CURLIES,
Parsing::Match,
)(p)?;
Ok(Match { scrutinee, arms })
}
}
impl Parse<'_> for MatchArm {
/// [MatchArm] = [Pattern] `=>` [Expr]
fn parse(p: &mut Parser<'_>) -> PResult<Self> {
let pat = Pattern::parse(p)?;
p.match_type(TokenKind::FatArrow, Parsing::MatchArm)?;
let expr = Expr::parse(p)?;
Ok(MatchArm(pat, expr))
}
}
/// ret_body = (*unconsumed* `;` | [Expr])
fn ret_body(p: &mut Parser, while_parsing: Parsing) -> PResult<Option<Box<Expr>>> {
Ok(match p.peek_kind(while_parsing)? {

View File

@@ -21,6 +21,7 @@ pub fn exprkind(p: &mut Parser, power: u8) -> PResult<ExprKind> {
TokenKind::LBrack => exprkind_arraylike(p)?,
TokenKind::LParen => exprkind_tuplelike(p)?,
TokenKind::Let => Let::parse(p)?.into(),
TokenKind::Match => Match::parse(p)?.into(),
TokenKind::While => ExprKind::While(While::parse(p)?),
TokenKind::If => ExprKind::If(If::parse(p)?),
TokenKind::For => ExprKind::For(For::parse(p)?),
@@ -32,8 +33,7 @@ pub fn exprkind(p: &mut Parser, power: u8) -> PResult<ExprKind> {
}
op => {
let (kind, prec) =
from_prefix(op).ok_or_else(|| p.error(Unexpected(op), parsing))?;
let (kind, prec) = from_prefix(op).ok_or_else(|| p.error(Unexpected(op), parsing))?;
let ((), after) = prec.prefix().expect("should have a precedence");
p.consume_peeked();
Unary { kind, tail: exprkind(p, after)?.into() }.into()
@@ -65,14 +65,10 @@ pub fn exprkind(p: &mut Parser, power: u8) -> PResult<ExprKind> {
ExprKind::Index(Index { head: head.into(), indices })
}
TokenKind::LParen => {
let exprs =
sep(Expr::parse, TokenKind::Comma, TokenKind::RParen, parsing)(p)?;
let exprs = sep(Expr::parse, TokenKind::Comma, TokenKind::RParen, parsing)(p)?;
p.match_type(TokenKind::RParen, parsing)?;
Binary {
kind: BinaryKind::Call,
parts: (head, Tuple { exprs }.into()).into(),
}
.into()
Binary { kind: BinaryKind::Call, parts: (head, Tuple { exprs }.into()).into() }
.into()
}
TokenKind::Dot => {
let kind = MemberKind::parse(p)?;
@@ -260,6 +256,7 @@ pub enum Precedence {
Cast,
Member, // left-associative
Call,
Highest,
}
impl Precedence {
@@ -310,9 +307,7 @@ impl From<BinaryKind> for Precedence {
Op::BitAnd | Op::BitOr | Op::BitXor => Precedence::Bitwise,
Op::LogAnd | Op::LogOr | Op::LogXor => Precedence::Logic,
Op::RangeExc | Op::RangeInc => Precedence::Range,
Op::Lt | Op::LtEq | Op::Equal | Op::NotEq | Op::GtEq | Op::Gt => {
Precedence::Compare
}
Op::Lt | Op::LtEq | Op::Equal | Op::NotEq | Op::GtEq | Op::Gt => Precedence::Compare,
}
}
}