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;
/// 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);
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.
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 {}
//
// TODO: Identifier interning
//
/// A literal value (boolean, character, integer, string)
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal {
@@ -18,7 +30,7 @@ pub enum Literal {
/// A character literal: 'a', '\u{1f988}'
Char(char),
/// An integer literal: 0, 123, 0x10
Int(i128),
Int(u128),
/// A string literal:
Str(String),
}
@@ -66,13 +78,18 @@ pub enum Ty {
/// `[..Args, Rety]`
Fn(Vec<Ty>),
}
impl Default for Ty {
fn default() -> Self {
Self::Tuple(vec![])
}
}
/// A `let` binding
/// ```ignore
/// let Pat (= Expr)?
/// ``````
#[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)
/// ```ignore
@@ -86,7 +103,7 @@ pub struct Const<A: Annotation = Span>(pub Pat, pub Anno<Expr<A>, A>);
/// fn Ident? (Pat) Expr
/// ```
#[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
/// ```ignore
@@ -107,7 +124,7 @@ pub struct MatchArm<A: Annotation = Span>(pub Pat, pub Anno<Expr<A>, A>);
/// Expr { (Ident (: Expr)?),* }
/// ```
#[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
/// ```ignore
@@ -154,11 +171,29 @@ pub enum Expr<A: Annotation = Span> {
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> {
pub fn anno(self, annotation: A) -> Anno<Expr<A>, A> {
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 {
matches!(
self,
@@ -271,17 +306,20 @@ impl<A: Annotation> Display for Const<A> {
impl<A: Annotation> Display for Fn<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self(Some(name), pat, expr) => write!(f, "fn {name} {pat} {expr}"),
Self(None, pat, expr) => write!(f, "|{pat}| {expr}"),
Self(Some(name), pat, rety, expr) => write!(f, "fn {name} {pat} -> {rety} {expr}"),
Self(None, pat, rety, expr) => write!(f, "|{pat}| -> {rety} {expr}"),
}
}
}
impl<A: Annotation> Display for Let<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self(pat, Some(expr)) => write!(f, "let {pat} = {expr}"),
Self(pat, None) => write!(f, "let ({pat})"),
let Self(pat, exprs) = self;
match exprs.as_slice() {
[] => 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 {
Pat::Struct(name, bind) => match bind.as_ref() {
Pat::Tuple(parts) => f
.delimit_indented(fmt!("{name} {{"), "}")
.delimit_indented(fmt!("struct {name} {{"), "}")
.list_wrap("\n", parts, ",\n", ",\n"),
other => write!(f, "{name} {{ {other} }}"),
},
@@ -338,6 +376,9 @@ impl<A: Annotation> Display for Expr<A> {
Self::Fn(v) => v.fmt(f),
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}"),
other => f.delimit(fmt!("({op}, "), ")").list(other, ", "),
},