doughlang: Expressions in patterns

ast:
- Document operators
- Parameterize Pat with Annotation
- Consolidate Path and Lit into "Value" (Expr)
- Consolidate NamedRecord/Namedtuple into PatOp::TypePrefixed
- Allow Pat<Pat,*> patterns
- Additional helper functions on Expr and Pat

lexer:
- Support inner-doc comment syntax `//!`
  - Cleans up `///` or `//!` prefix

parser:
- Make Parse::Prec `Default`
- Allow expression elision after `..`/`..=`
- Fix Parser::consume not updating elide_do state
- Add token splitting, to make `&&Expr` and `&&Pat` easier
- error: Embed Pat precedence in ParseError
This commit is contained in:
2026-01-05 15:17:22 -05:00
parent d6104b6a0b
commit e4c008bd4b
16 changed files with 700 additions and 322 deletions

View File

@@ -76,6 +76,7 @@ pub enum Ps {
Lit, // Literal
Use, // use Use
Def, // any definition (let, const, static, struct, enum, fn, ...)
Doc, // Documentation Comment
For, // for Pat in Expr Expr else Expr
Lambda0, // || Expr
Lambda, // | Pat,* | Expr
@@ -90,6 +91,8 @@ pub enum Ps {
/// and its [precedence level](Prec)
fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
Ok(match token.kind {
TKind::OutDoc => (Ps::Doc, Prec::Max),
TKind::InDoc => (Ps::Doc, Prec::Max),
TKind::Do => (Ps::Op(Op::Do), Prec::Do),
TKind::Semi => (Ps::End, Prec::Body),
@@ -207,12 +210,9 @@ impl<'t> Parse<'t> for Expr {
fn parse(p: &mut Parser<'t>, level: usize) -> PResult<Self> {
const MIN: usize = Prec::MIN;
// TODO: in-tree doc comments
while p.next_if(TKind::Doc)?.is_ok() {}
// Prefix
let tok @ &Token { kind, span, .. } = p.peek()?;
let ((op, prec), span) = (from_prefix(tok)?, span);
let (op, prec) = from_prefix(tok)?;
no_eof(move || {
let mut head = match op {
// "End" is produced when an "empty" expression is syntactically required.
@@ -227,11 +227,19 @@ impl<'t> Parse<'t> for Expr {
Ps::Lit => Expr::Lit(p.parse(())?),
Ps::Use => Expr::Use(p.consume().parse(())?),
Ps::Def => Expr::Bind(p.parse(None)?),
Ps::Doc => {
let comment = Literal::Str(p.take_lexeme()?.string().unwrap());
let comment = Expr::Lit(comment).anno(span);
let next = p.parse(level)?;
Expr::Op(Op::Meta, vec![comment, next])
}
Ps::For => parse_for(p, ())?,
Ps::Lambda | Ps::Lambda0 => {
p.consume();
let args = if op == Ps::Lambda {
p.opt(PPrec::Tuple, TKind::Bar)?
.map(Pat::to_tuple)
.unwrap_or(Pat::Op(PatOp::Tuple, vec![]))
} else {
Pat::Op(PatOp::Tuple, vec![])
@@ -246,7 +254,6 @@ impl<'t> Parse<'t> for Expr {
vec![p.parse(Prec::Body.next())?],
)))
}
Ps::For => parse_for(p, ())?,
Ps::Op(Op::Match) => parse_match(p)?,
Ps::Op(Op::Meta) => Expr::Op(
Op::Meta,
@@ -260,10 +267,10 @@ impl<'t> Parse<'t> for Expr {
),
Ps::Op(Op::Block) => Expr::Op(
Op::Block,
p.consume().opt(MIN, TKind::RCurly)?.into_iter().collect(),
p.consume().opt(MIN, kind.flip())?.into_iter().collect(),
),
Ps::Op(Op::Array) => parse_array(p)?,
Ps::Op(Op::Group) => match p.consume().opt(MIN, TKind::RParen)? {
Ps::Op(Op::Group) => match p.consume().opt(MIN, kind.flip())? {
Some(value) => Expr::Op(Op::Group, vec![value]),
None => Expr::Op(Op::Tuple, vec![]),
},
@@ -295,12 +302,11 @@ impl<'t> Parse<'t> for Expr {
};
// Infix and Postfix
while let Ok(Some(tok)) = p.peek().allow_eof()
while let Ok(Some(tok @ &Token { kind, .. })) = p.peek().allow_eof()
&& let Ok((op, prec)) = from_infix(tok)
&& level <= prec.prev()
&& op != Ps::End
{
let kind = tok.kind;
let span = span.merge(p.span());
head = match op {
@@ -320,7 +326,7 @@ impl<'t> Parse<'t> for Expr {
span,
match p.consume().peek().allow_eof()? {
Some(_) => p.parse(prec.next())?,
None => Anno(Default::default(), span),
None => Anno(Expr::Omitted, span),
},
),
Ps::Op(Op::Index) => Expr::Op(
@@ -328,15 +334,14 @@ impl<'t> Parse<'t> for Expr {
p.consume()
.list(vec![head.anno(span)], 0, TKind::Comma, TKind::RBrack)?,
),
Ps::Op(Op::Call) => Expr::Op(
Op::Call,
vec![
head.anno(span),
p.consume()
.opt(0, TKind::RParen)?
.unwrap_or_else(|| Expr::Op(Op::Tuple, vec![]).anno(span)),
],
),
Ps::Op(Op::Call) => {
let head = head.anno(span);
let args = match p.consume().opt::<Anno<Expr>>(0, TKind::RParen)? {
None => Expr::Op(Op::Tuple, vec![]).anno(span),
Some(Anno(expr, span)) => expr.to_tuple(span).anno(span),
};
Expr::Op(Op::Call, vec![head, args])
}
Ps::Op(op @ (Op::Tuple | Op::Dot | Op::LogAnd | Op::LogOr)) => Expr::Op(
op,
p.consume()
@@ -445,7 +450,7 @@ fn from_bind(p: &mut Parser<'_>) -> PResult<(BindOp, PPrec, Option<TKind>, Optio
TKind::Enum => (BindOp::Enum, PPrec::Tuple, None, None, None),
TKind::Fn => (BindOp::Fn, PPrec::Fn, None, Some(Prec::Body), None),
TKind::Mod => (BindOp::Mod, PPrec::Max, None, Some(Prec::Body), None),
TKind::Impl => (BindOp::Impl, PPrec::Max, None, Some(Prec::Body), None),
TKind::Impl => (BindOp::Impl, PPrec::Fn, None, Some(Prec::Body), None),
TKind::Bar => (BindOp::Match, PPrec::Alt, Some(TKind::FatArrow), Some(Prec::Body), None),
// no consume!
_ => return Ok((BindOp::Match, PPrec::Alt, Some(TKind::FatArrow), Some(Prec::Body), None)),
@@ -481,11 +486,14 @@ impl<'t> Parse<'t> for Bind {
return Ok(Self(level, generics, pat, vec![]));
};
// `=>` for match, `=`? for everything else
if let Some(arrow) = arrow
&& p.next_if(arrow).allow_eof()?.is_none_or(|v| v.is_err())
{
return Ok(Self(level, generics, pat, vec![]));
// `=>` for match, `=` for `let`, `type`
if let Some(arrow) = arrow {
if p.next_if(arrow).allow_eof()?.is_none_or(|v| v.is_err()) {
return Ok(Self(level, generics, pat, vec![]));
}
} else {
// Allow prefix `=`? for the rest of them
p.next_if(TKind::Eq).allow_eof()?;
}
// `=` Expr