doughlang: let else, fn rety

ast:
- `let Pat (= Expr (else Expr)?)?`,
- `fn (args) -> Ty`, coagulating `do`, `if/while` formatting fixes

parser:
- int cleanup,
- fn rety parsing,
- experimental `for` desugar,
- experimental semicolon elision (TODO: it sucks),
- let-else parsing
- `do` coagulation impl (still not 100% there)

TODO:
- Fix `do` coagulation
- codify `do` elision rules
- `for` needs lib support
  - this is fine because we have no codegen yet
- Ty matching in macro_matcher
  - Or rip out macro_matcher? Who knows what the future holds.
This commit is contained in:
2025-10-16 01:51:14 -04:00
parent 95abb81f4a
commit 03d9682409
3 changed files with 227 additions and 52 deletions

View File

@@ -3,13 +3,25 @@
pub mod macro_matcher; pub mod macro_matcher;
/// A value with an annotation. /// A value with an annotation.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Anno<T: Annotation, A: Annotation = Span>(pub T, pub A); pub struct Anno<T: Annotation, A: Annotation = Span>(pub T, pub A);
impl<T: Annotation, A: Annotation> std::fmt::Debug for Anno<T, A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<A as std::fmt::Debug>::fmt(&self.1, f)?;
f.write_str(": ")?;
<T as std::fmt::Debug>::fmt(&self.0, f)
}
}
/// An annotation: extra data added on to important AST nodes. /// An annotation: extra data added on to important AST nodes.
pub trait Annotation: Clone + std::fmt::Display + std::fmt::Debug + PartialEq + Eq {} pub trait Annotation: Clone + std::fmt::Display + std::fmt::Debug + PartialEq + Eq {}
impl<T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + Eq> Annotation for T {} impl<T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + Eq> Annotation for T {}
//
// TODO: Identifier interning
//
/// A literal value (boolean, character, integer, string) /// A literal value (boolean, character, integer, string)
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal { pub enum Literal {
@@ -18,7 +30,7 @@ pub enum Literal {
/// A character literal: 'a', '\u{1f988}' /// A character literal: 'a', '\u{1f988}'
Char(char), Char(char),
/// An integer literal: 0, 123, 0x10 /// An integer literal: 0, 123, 0x10
Int(i128), Int(u128),
/// A string literal: /// A string literal:
Str(String), Str(String),
} }
@@ -66,13 +78,18 @@ pub enum Ty {
/// `[..Args, Rety]` /// `[..Args, Rety]`
Fn(Vec<Ty>), Fn(Vec<Ty>),
} }
impl Default for Ty {
fn default() -> Self {
Self::Tuple(vec![])
}
}
/// A `let` binding /// A `let` binding
/// ```ignore /// ```ignore
/// let Pat (= Expr)? /// let Pat (= Expr)?
/// `````` /// ``````
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Let<A: Annotation = Span>(pub Pat, pub Option<Anno<Expr<A>, A>>); pub struct Let<A: Annotation = Span>(pub Pat, pub Vec<Anno<Expr<A>, A>>);
/// A `const` binding (which defines its name before executing) /// A `const` binding (which defines its name before executing)
/// ```ignore /// ```ignore
@@ -86,7 +103,7 @@ pub struct Const<A: Annotation = Span>(pub Pat, pub Anno<Expr<A>, A>);
/// fn Ident? (Pat) Expr /// fn Ident? (Pat) Expr
/// ``` /// ```
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Fn<A: Annotation = Span>(pub Option<String>, pub Pat, pub Anno<Expr<A>, A>); pub struct Fn<A: Annotation = Span>(pub Option<String>, pub Pat, pub Ty, pub Anno<Expr<A>, A>);
/// A match expression /// A match expression
/// ```ignore /// ```ignore
@@ -107,7 +124,7 @@ pub struct MatchArm<A: Annotation = Span>(pub Pat, pub Anno<Expr<A>, A>);
/// Expr { (Ident (: Expr)?),* } /// Expr { (Ident (: Expr)?),* }
/// ``` /// ```
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Make<A: Annotation = Span>(pub Box<Anno<Expr<A>, A>>, pub Vec<MakeArm<A>>); pub struct Make<A: Annotation = Span>(pub Anno<Expr<A>, A>, pub Vec<MakeArm<A>>);
/// A single "arm" of a make expression /// A single "arm" of a make expression
/// ```ignore /// ```ignore
@@ -154,11 +171,29 @@ pub enum Expr<A: Annotation = Span> {
Op(Op, Vec<Anno<Self, A>>), Op(Op, Vec<Anno<Self, A>>),
} }
impl<A: Annotation> Default for Expr<A> {
fn default() -> Self {
Self::Op(Op::Tuple, vec![])
}
}
impl<A: Annotation> Expr<A> { impl<A: Annotation> Expr<A> {
pub fn anno(self, annotation: A) -> Anno<Expr<A>, A> { pub fn anno(self, annotation: A) -> Anno<Expr<A>, A> {
Anno(self, annotation) Anno(self, annotation)
} }
pub fn and_do(self, annotation: A, other: Anno<Expr<A>, A>) -> Self {
let Self::Op(Op::Do, mut exprs) = self else {
return Self::Op(Op::Do, vec![self.anno(annotation), other]);
};
let Anno(Self::Op(Op::Do, mut other), _) = other else {
exprs.push(other);
return Self::Op(Op::Do, exprs);
};
exprs.append(&mut other);
Self::Op(Op::Do, exprs)
}
pub fn is_place(&self) -> bool { pub fn is_place(&self) -> bool {
matches!( matches!(
self, self,
@@ -271,17 +306,20 @@ impl<A: Annotation> Display for Const<A> {
impl<A: Annotation> Display for Fn<A> { impl<A: Annotation> Display for Fn<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self(Some(name), pat, expr) => write!(f, "fn {name} {pat} {expr}"), Self(Some(name), pat, rety, expr) => write!(f, "fn {name} {pat} -> {rety} {expr}"),
Self(None, pat, expr) => write!(f, "|{pat}| {expr}"), Self(None, pat, rety, expr) => write!(f, "|{pat}| -> {rety} {expr}"),
} }
} }
} }
impl<A: Annotation> Display for Let<A> { impl<A: Annotation> Display for Let<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { let Self(pat, exprs) = self;
Self(pat, Some(expr)) => write!(f, "let {pat} = {expr}"), match exprs.as_slice() {
Self(pat, None) => write!(f, "let ({pat})"), [] => write!(f, "let {pat}"),
[value] => write!(f, "let {pat} = {value}"),
[value, fail] => write!(f, "let {pat} = {value} else {fail}"),
other => f.delimit(fmt!("let! {pat} ("), ")").list(other, ", "),
} }
} }
} }
@@ -314,7 +352,7 @@ impl Display for Struct {
match pat { match pat {
Pat::Struct(name, bind) => match bind.as_ref() { Pat::Struct(name, bind) => match bind.as_ref() {
Pat::Tuple(parts) => f Pat::Tuple(parts) => f
.delimit_indented(fmt!("{name} {{"), "}") .delimit_indented(fmt!("struct {name} {{"), "}")
.list_wrap("\n", parts, ",\n", ",\n"), .list_wrap("\n", parts, ",\n", ",\n"),
other => write!(f, "{name} {{ {other} }}"), other => write!(f, "{name} {{ {other} }}"),
}, },
@@ -338,6 +376,9 @@ impl<A: Annotation> Display for Expr<A> {
Self::Fn(v) => v.fmt(f), Self::Fn(v) => v.fmt(f),
Self::Op(op @ (Op::If | Op::While), exprs) => match exprs.as_slice() { Self::Op(op @ (Op::If | Op::While), exprs) => match exprs.as_slice() {
[cond, pass, Anno(Expr::Op(Op::Tuple, e), _)] if e.is_empty() => {
write!(f, "{op}{cond} {pass}")
}
[cond, pass, fail] => write!(f, "{op}{cond} {pass} else {fail}"), [cond, pass, fail] => write!(f, "{op}{cond} {pass} else {fail}"),
other => f.delimit(fmt!("({op}, "), ")").list(other, ", "), other => f.delimit(fmt!("({op}, "), ")").list(other, ", "),
}, },

View File

@@ -93,14 +93,17 @@ impl<A: Annotation> Match<A> for Const<A> {
impl<A: Annotation> Match<A> for Fn<A> { impl<A: Annotation> Match<A> for Fn<A> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool { fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
let (Self(pat_id, pat_arg, pat_body), Self(expr_id, expr_arg, expr_body)) = (pat, expr); let (
Self(pat_id, pat_arg, _pat_rety, pat_body),
Self(expr_id, expr_arg, _expr_rety, expr_body),
) = (pat, expr);
pat_id == expr_id pat_id == expr_id
&& Match::recurse(sub, pat_arg, expr_arg) && Match::recurse(sub, pat_arg, expr_arg)
&& Match::recurse(sub, pat_body, expr_body) && Match::recurse(sub, pat_body, expr_body)
} }
fn apply(&mut self, sub: &Subst<A>) { fn apply(&mut self, sub: &Subst<A>) {
let Self(_, pat, body) = self; let Self(_, pat, _rety, body) = self;
pat.apply(sub); pat.apply(sub);
body.apply(sub); body.apply(sub);
} }

View File

@@ -200,15 +200,14 @@ impl<'t> Parse<'t> for Literal {
.expect("should have one char in char literal"), .expect("should have one char in char literal"),
), ),
TKind::Integer => { TKind::Integer => {
let Token { lexeme, kind: _, span } = p.take().expect("should have Token"); let Token { lexeme, span, .. } = p.take().expect("should have Token");
// TODO: more complex int parsing let Lexeme::Integer(int, _) = lexeme else {
let int = lexeme Err(ParseError::Expected(TKind::Integer, span))?
.int() };
.ok_or(ParseError::Expected(TKind::Integer, span))?; Literal::Int(int)
Literal::Int(int as _)
} }
TKind::String => Literal::Str({ TKind::String => Literal::Str({
let Token { lexeme, kind: _, span } = p.take().expect("should have Token"); let Token { lexeme, span, .. } = p.take().expect("should have Token");
lexeme lexeme
.string() .string()
.ok_or(ParseError::Expected(TKind::String, span))? .ok_or(ParseError::Expected(TKind::String, span))?
@@ -368,7 +367,10 @@ impl<'t> Parse<'t> for Ty {
_ => Err(ParseError::NotType(tok.kind, tok.span))?, _ => Err(ParseError::NotType(tok.kind, tok.span))?,
}; };
Ok(head) Ok(match p.next_if(TKind::Arrow) {
Ok(_) => Ty::Fn(vec![head, p.parse(())?]),
_ => head,
})
} }
} }
@@ -441,6 +443,7 @@ pub enum Ps {
Let, // let Pat = Expr Let, // let Pat = Expr
Const, // const Pat = Expr Const, // const Pat = Expr
Struct, // struct { Pat } | struct ( Pat ) Struct, // struct { Pat } | struct ( Pat )
For, // for Pat in Expr Expr else Expr
Fn, // fn ( Pat,* ) Expr Fn, // fn ( Pat,* ) Expr
Lambda0, // || Expr Lambda0, // || Expr
Lambda, // | Pat,* | Expr Lambda, // | Pat,* | Expr
@@ -448,6 +451,7 @@ pub enum Ps {
Make, // Expr{ Expr,* } Make, // Expr{ Expr,* }
Match, // match Expr { MatchArm,* } Match, // match Expr { MatchArm,* }
Mod, // mod Ty Expr Mod, // mod Ty Expr
ImplicitDo, // An implicit semicolon
End, // Produces an empty value. End, // Produces an empty value.
Op(Op), // A normal [ast::Op] Op(Op), // A normal [ast::Op]
} }
@@ -464,6 +468,7 @@ fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
} }
TKind::Public => (Ps::Op(Op::Pub), Prec::Body), TKind::Public => (Ps::Op(Op::Pub), Prec::Body),
TKind::For => (Ps::For, Prec::Body),
TKind::Fn => (Ps::Fn, Prec::Body), TKind::Fn => (Ps::Fn, Prec::Body),
TKind::Match => (Ps::Match, Prec::Body), TKind::Match => (Ps::Match, Prec::Body),
TKind::Macro => (Ps::Op(Op::Macro), Prec::Assign), TKind::Macro => (Ps::Op(Op::Macro), Prec::Assign),
@@ -533,6 +538,22 @@ fn from_infix(token: &Token) -> PResult<(Ps, Prec)> {
TKind::Star => (Ps::Op(Op::Mul), Prec::Term), TKind::Star => (Ps::Op(Op::Mul), Prec::Term),
TKind::Slash => (Ps::Op(Op::Div), Prec::Term), TKind::Slash => (Ps::Op(Op::Div), Prec::Term),
TKind::Rem => (Ps::Op(Op::Rem), Prec::Term), TKind::Rem => (Ps::Op(Op::Rem), Prec::Term),
TKind::True
| TKind::False
| TKind::Character
| TKind::Integer
| TKind::String
| TKind::Identifier
| TKind::Public
| TKind::Module
| TKind::Fn
| TKind::Do
| TKind::While
| TKind::If
| TKind::For
| TKind::Break
| TKind::Return => (Ps::ImplicitDo, Prec::Do),
kind => Err(ParseError::NotInfix(kind, token.span))?, kind => Err(ParseError::NotInfix(kind, token.span))?,
}) })
} }
@@ -565,6 +586,7 @@ impl<'t> Parse<'t> for Fn {
Ok(Token { lexeme, .. }) => Ok(Self( Ok(Token { lexeme, .. }) => Ok(Self(
lexeme.string(), lexeme.string(),
p.parse(PPrec::Typed)?, p.parse(PPrec::Typed)?,
p.opt_if((), TKind::Arrow)?.unwrap_or_default(),
p.parse(Prec::Body.next())?, p.parse(Prec::Body.next())?,
)), )),
_ => Ok(Self( _ => Ok(Self(
@@ -575,6 +597,7 @@ impl<'t> Parse<'t> for Fn {
TKind::Comma, TKind::Comma,
TKind::RParen, TKind::RParen,
)?), )?),
p.opt_if((), TKind::Arrow)?.unwrap_or_default(),
p.parse(Prec::Body.next())?, p.parse(Prec::Body.next())?,
)), )),
} }
@@ -585,10 +608,17 @@ impl<'t> Parse<'t> for Let {
type Prec = (); type Prec = ();
fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self> { fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self> {
Ok(Self( let pat = p.consume().parse(PPrec::Tuple)?;
p.consume().parse(PPrec::Alt)?, if p.next_if(TKind::Eq).is_err() {
p.opt_if(Prec::Tuple.value(), TKind::Eq)?, return Ok(Self(pat, vec![]));
)) }
let body = p.parse(Prec::Tuple.value())?;
if p.next_if(TKind::Else).is_err() {
return Ok(Self(pat, vec![body]));
}
Ok(Self(pat, vec![body, p.parse(Prec::Body.next())?]))
} }
} }
@@ -634,10 +664,106 @@ impl<'t> Parse<'t> for MakeArm {
impl<'t> Parse<'t> for Mod { impl<'t> Parse<'t> for Mod {
type Prec = (); type Prec = ();
fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self> { fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self> {
Ok(Mod(p.consume().parse(())?, p.parse(Prec::Body.value())?)) let ty = p.consume().parse(())?;
let body = p.parse(Prec::Body.value())?;
Ok(Mod(ty, body))
} }
} }
fn parse_for<'t>(p: &mut Parser<'t>, _level: ()) -> PResult<Expr> {
// for Pat
let pat = p.consume().parse(PPrec::Tuple)?;
// in Expr
let iter: Anno<Expr> = p.consume_if(TKind::In)?.parse(Prec::Logical.next())?;
let cspan = iter.1;
// Expr
let pass: Anno<Expr> = p.parse(Prec::Body.next())?;
let pspan = pass.1;
// else Expr?
let fail = match p.next_if(TKind::Else) {
Ok(_) => p.parse(Prec::Body.next())?,
_ => Expr::Op(Op::Tuple, vec![]).anno(pspan),
};
let fspan = fail.1;
/*
for `pat in `iter `pass else `fail
==>
match (`iter).into_iter() {
#iter => loop match #iter.next() {
None => break `fail,
Some(`pat) => `pass,
},
}
*/
// let mut tmp_p = Parser::new(Lexer::new(
// "match `iter.into_iter() {
// `iterator => loop match `iterator.next() {
// None => break `fail,
// Some(`pat) => `pass,
// },
// }",
// ));
// let mut template: Expr = tmp_p.parse(Prec::MIN)?;
// let mut subst = Subst::<Span> { exp: Default::default(), pat: Default::default() };
// subst.exp.extend([
// ("iterator".into(), Expr::Id("#iter".into())),
// ("iter".into(), iter.0),
// ("fail".into(), fail.0),
// ("pass".into(), pass.0),
// ]);
// subst.pat.extend([
// ("iterator".into(), Pat::Name("#iter".into())),
// ("pat".into(), pat),
// ]);
// template.apply(&subst);
// Ok(template)
Ok(Expr::Match(Box::new(Match(
Expr::Op(
Op::Dot,
vec![
iter,
Expr::Op(Op::Call, vec![Expr::Id("into_iter".into()).anno(cspan)]).anno(cspan),
],
)
.anno(cspan),
vec![MatchArm(
Pat::Name("#iter".into()),
Expr::Op(
Op::Loop,
vec![
Expr::Match(Box::new(Match(
Expr::Op(
Op::Dot,
vec![
Expr::Id("#iter".into()).anno(cspan),
Expr::Op(Op::Call, vec![Expr::Id("next".into()).anno(cspan)])
.anno(cspan),
],
)
.anno(cspan),
vec![
MatchArm(
Pat::Name("None".into()),
Expr::Op(Op::Break, vec![fail]).anno(fspan),
),
MatchArm(
Pat::TupStruct("Some".into(), Box::new(Pat::Tuple(vec![pat]))),
pass,
),
],
)))
.anno(pspan),
],
)
.anno(pspan),
)],
))))
}
impl<'t> Parse<'t> for Expr { impl<'t> Parse<'t> for Expr {
type Prec = usize; type Prec = usize;
@@ -662,6 +788,7 @@ impl<'t> Parse<'t> for Expr {
Ps::Mid => Expr::MetId(p.consume().next()?.lexeme.to_string()), Ps::Mid => Expr::MetId(p.consume().next()?.lexeme.to_string()),
Ps::Lit => Expr::Lit(p.parse(())?), Ps::Lit => Expr::Lit(p.parse(())?),
Ps::Let => Expr::Let(p.parse(())?), Ps::Let => Expr::Let(p.parse(())?),
Ps::For => parse_for(p, ())?,
Ps::Const => Expr::Const(p.parse(())?), Ps::Const => Expr::Const(p.parse(())?),
Ps::Struct => Expr::Struct(p.parse(())?), Ps::Struct => Expr::Struct(p.parse(())?),
Ps::Match => Expr::Match(p.parse(())?), Ps::Match => Expr::Match(p.parse(())?),
@@ -694,11 +821,15 @@ impl<'t> Parse<'t> for Expr {
p.consume() p.consume()
.opt(PPrec::Tuple, TKind::Bar)? .opt(PPrec::Tuple, TKind::Bar)?
.unwrap_or(Pat::Tuple(vec![])), .unwrap_or(Pat::Tuple(vec![])),
p.opt_if((), TKind::Arrow)?.unwrap_or_default(),
p.parse(Prec::Body.next())?,
))),
Ps::Lambda0 => Expr::Fn(Box::new(Fn(
None,
Pat::Tuple(vec![]),
p.consume().opt_if((), TKind::Arrow)?.unwrap_or_default(),
p.parse(Prec::Body.next())?, p.parse(Prec::Body.next())?,
))), ))),
Ps::Lambda0 => Expr::Fn(Box::new(Fn(None, Pat::Tuple(vec![]), {
p.consume().parse(Prec::Body.next())?
}))),
Ps::DoubleRef => p.consume().parse(prec.next()).map(|Anno(expr, span)| { Ps::DoubleRef => p.consume().parse(prec.next()).map(|Anno(expr, span)| {
Expr::Op( Expr::Op(
Op::Refer, Op::Refer,
@@ -724,12 +855,14 @@ impl<'t> Parse<'t> for Expr {
Ps::Make => match &head { Ps::Make => match &head {
Expr::Op(Op::Path, _) | Expr::Id(_) | Expr::MetId(_) => { Expr::Op(Op::Path, _) | Expr::Id(_) | Expr::MetId(_) => {
Expr::Make(Box::new(Make( Expr::Make(Box::new(Make(
head.anno(span).into(), head.anno(span),
p.consume().list(vec![], (), TKind::Comma, TKind::RCurly)?, p.consume().list(vec![], (), TKind::Comma, TKind::RCurly)?,
))) )))
} }
_ => break, _ => break,
}, },
Ps::Op(Op::Do) => head.and_do(span, p.consume().parse(prec.next())?),
Ps::ImplicitDo => head.and_do(span, p.parse(prec.next())?),
Ps::Op(Op::Index) => Expr::Op( Ps::Op(Op::Index) => Expr::Op(
Op::Index, Op::Index,
p.consume() p.consume()
@@ -740,13 +873,11 @@ impl<'t> Parse<'t> for Expr {
p.consume() p.consume()
.list(vec![head.anno(span)], 0, TKind::Comma, TKind::RParen)?, .list(vec![head.anno(span)], 0, TKind::Comma, TKind::RParen)?,
), ),
Ps::Op(op @ (Op::Do | Op::Tuple | Op::Dot | Op::Path | Op::LogAnd | Op::LogOr)) => { Ps::Op(op @ (Op::Tuple | Op::Dot | Op::Path | Op::LogAnd | Op::LogOr)) => Expr::Op(
Expr::Op(
op, op,
p.consume() p.consume()
.list_bare(vec![head.anno(span)], prec.next(), kind)?, .list_bare(vec![head.anno(span)], prec.next(), kind)?,
) ),
}
Ps::Op(op @ Op::Try) => { Ps::Op(op @ Op::Try) => {
p.consume(); p.consume();
Expr::Op(op, vec![head.anno(span)]) Expr::Op(op, vec![head.anno(span)])