ast: Break compatibility

- Turned `static` and `const` into scoped modifiers (like `pub`
- Added anonymous record patterns
This commit is contained in:
2025-12-20 05:22:14 -05:00
parent 64ce18d576
commit f551e3aef3
8 changed files with 43 additions and 36 deletions

View File

@@ -30,8 +30,6 @@ impl ToLisp {
fn bind_op(&self, op: BindOp) -> &'static str { fn bind_op(&self, op: BindOp) -> &'static str {
match op { match op {
BindOp::Let => "let", BindOp::Let => "let",
BindOp::Const => "const",
BindOp::Static => "static",
BindOp::Type => "type", BindOp::Type => "type",
BindOp::Fn => "fn", BindOp::Fn => "fn",
BindOp::Mod => "mod", BindOp::Mod => "mod",
@@ -51,6 +49,7 @@ impl ToLisp {
PatOp::Rest => "rest", PatOp::Rest => "rest",
PatOp::RangeEx => "range-ex", PatOp::RangeEx => "range-ex",
PatOp::RangeIn => "range-in", PatOp::RangeIn => "range-in",
PatOp::Record => "record",
PatOp::Tuple => "tuple", PatOp::Tuple => "tuple",
PatOp::Slice => "slice", PatOp::Slice => "slice",
PatOp::ArRep => "ar-rep", PatOp::ArRep => "ar-rep",
@@ -75,6 +74,8 @@ impl ToLisp {
Op::Call => "call", Op::Call => "call",
Op::Pub => "pub", Op::Pub => "pub",
Op::Loop => "loop", Op::Loop => "loop",
Op::Const => "const",
Op::Static => "static",
Op::Match => "match", Op::Match => "match",
Op::If => "if", Op::If => "if",
Op::While => "while", Op::While => "while",
@@ -201,7 +202,7 @@ impl<'a> Visit<'a> for ToLisp {
Pat::MetId(id) => print!("(replace {id})"), Pat::MetId(id) => print!("(replace {id})"),
Pat::Name(name) => print!("{name}"), Pat::Name(name) => print!("{name}"),
Pat::Path(path) => path.visit_in(self)?, Pat::Path(path) => path.visit_in(self)?,
Pat::NamedStruct(path, pat) => { Pat::NamedRecord(path, pat) => {
print!("(struct-pat "); print!("(struct-pat ");
path.visit_in(self)?; path.visit_in(self)?;
print!(" "); print!(" ");

View File

@@ -81,8 +81,10 @@ pub enum Op {
Call, // Expr ( Expr,* ) Call, // Expr ( Expr,* )
Pub, // pub Expr Pub, // pub Expr
Const, // const Expr
Static, // static Expr
Loop, // loop Expr Loop, // loop Expr
Match, // match Expr { <Let(Match, ..)>,* } Match, // match Expr { <Bind(Match, ..)>,* }
If, // if Expr Expr (else Expr)? If, // if Expr Expr (else Expr)?
While, // while Expr Expr (else Expr)? While, // while Expr Expr (else Expr)?
Break, // break Expr Break, // break Expr
@@ -176,8 +178,6 @@ pub enum Use {
/// A pattern binding /// A pattern binding
/// ```ignore /// ```ignore
/// let Pat (= Expr (else Expr)?)? /// let Pat (= Expr (else Expr)?)?
/// const Pat (= Expr (else Expr)?)?
/// static Pat (= Expr (else Expr)?)?
/// type Pat (= Expr)? /// type Pat (= Expr)?
/// struct Pat /// struct Pat
/// enum Pat /// enum Pat
@@ -199,10 +199,6 @@ pub struct Bind<A: Annotation = Span>(
pub enum BindOp { pub enum BindOp {
/// A `let Pat (= Expr (else Expr)?)?` binding /// A `let Pat (= Expr (else Expr)?)?` binding
Let, Let,
/// A `const Pat = Expr` binding
Const,
/// A `static Pat = Expr` binding
Static,
/// A type-alias binding /// A type-alias binding
Type, Type,
/// A `fn Pat Expr` binding /// A `fn Pat Expr` binding
@@ -251,7 +247,7 @@ pub enum Pat {
/// Matches against a named const value /// Matches against a named const value
Path(Path), Path(Path),
/// Matches a Struct Expression `Ident { Pat }` /// Matches a Struct Expression `Ident { Pat }`
NamedStruct(Path, Box<Pat>), NamedRecord(Path, Box<Pat>),
/// Matches a Tuple Struct Expression `Ident ( Pat )` /// Matches a Tuple Struct Expression `Ident ( Pat )`
NamedTuple(Path, Box<Pat>), NamedTuple(Path, Box<Pat>),
/// Matches a literal value by equality comparison /// Matches a literal value by equality comparison
@@ -277,15 +273,17 @@ pub enum PatOp {
RangeEx, RangeEx,
/// Matches an inclusive bounded range (`0..=100`) /// Matches an inclusive bounded range (`0..=100`)
RangeIn, RangeIn,
/// Matches the elements of a tuple /// Matches the elements of a record or struct { a, b, c }
Record,
/// Matches the elements of a tuple ( a, b, c )
Tuple, Tuple,
/// Matches the elements of a slice or array /// Matches the elements of a slice or array [ a, b, c ]
Slice, Slice,
/// Matches a constant-size slice with repeating elements /// Matches a constant-size slice with repeating elements
ArRep, ArRep,
/// Matches a type annotation or struct member /// Matches a type annotation or struct member
Typed, Typed,
/// Matches a function signature /// Changes the binding mode to "function-body"
Fn, Fn,
/// Matches one of a list of alternatives /// Matches one of a list of alternatives
Alt, Alt,

View File

@@ -81,6 +81,8 @@ impl Display for Op {
Op::Index => "", Op::Index => "",
Op::Call => "", Op::Call => "",
Op::Pub => "pub ", Op::Pub => "pub ",
Op::Const => "const ",
Op::Static => "static ",
Op::Loop => "loop ", Op::Loop => "loop ",
Op::Match => "match ", Op::Match => "match ",
Op::If => "if ", Op::If => "if ",
@@ -182,7 +184,7 @@ impl<A: Annotation> Display for Bind<A> {
f.delimit(fmt!("{pat} "), "").list(exprs, ",!? ") f.delimit(fmt!("{pat} "), "").list(exprs, ",!? ")
} }
BindOp::Struct | BindOp::Enum => match pat { BindOp::Struct | BindOp::Enum => match pat {
Pat::NamedStruct(name, bind) => match bind.as_ref() { Pat::NamedRecord(name, bind) => match bind.as_ref() {
Pat::Op(PatOp::Tuple, parts) => f Pat::Op(PatOp::Tuple, parts) => f
.delimit_indented(fmt!("{name} {{"), "}") .delimit_indented(fmt!("{name} {{"), "}")
.list_wrap("\n", parts, ",\n", ",\n"), .list_wrap("\n", parts, ",\n", ",\n"),
@@ -211,8 +213,6 @@ impl Display for BindOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self { f.write_str(match self {
Self::Let => "let ", Self::Let => "let ",
Self::Const => "const ",
Self::Static => "static ",
Self::Type => "type ", Self::Type => "type ",
Self::Struct => "struct ", Self::Struct => "struct ",
Self::Enum => "enum ", Self::Enum => "enum ",
@@ -250,18 +250,22 @@ impl Display for Pat {
Self::MetId(name) => write!(f, "`{name}"), Self::MetId(name) => write!(f, "`{name}"),
Self::Name(name) => name.fmt(f), Self::Name(name) => name.fmt(f),
Self::Path(path) => path.fmt(f), Self::Path(path) => path.fmt(f),
Self::NamedStruct(name, bind) => match bind.as_ref() { Self::NamedRecord(name, bind) => match bind.as_ref() {
Pat::Op(PatOp::Tuple, parts) => { Pat::Op(PatOp::Tuple, parts) => {
f.delimit(fmt!("{name} {{ "), " }").list(parts, ", ") f.delimit(fmt!("{name} {{ "), " }").list(parts, ", ")
} }
other => write!(f, "{name} {{ {other} }}"), other => write!(f, "{name} {{ {other} }}"),
}, },
Self::NamedTuple(name, bind) => write!(f, "{name} {bind}"), Self::NamedTuple(name, bind) => write!(f, "{name} {bind}"),
Self::Op(PatOp::Record, pats) => f.delimit("{ ", " }").list(pats, ", "),
Self::Op(PatOp::Tuple, pats) => f.delimit("(", ")").list(pats, ", "), Self::Op(PatOp::Tuple, pats) => f.delimit("(", ")").list(pats, ", "),
Self::Op(PatOp::Slice, pats) => f.delimit("[", "]").list(pats, ", "), Self::Op(PatOp::Slice, pats) => f.delimit("[", "]").list(pats, ", "),
Self::Op(op @ PatOp::ArRep, pats) => f.delimit("[", "]").list(pats, op), Self::Op(op @ PatOp::ArRep, pats) => f.delimit("[", "]").list(pats, op),
Self::Op(op @ (PatOp::Typed | PatOp::Fn), pats) => f.list(pats, op), Self::Op(op @ (PatOp::Typed | PatOp::Fn), pats) => match pats.as_slice() {
Self::Op(op @ PatOp::Alt, pats) => f.delimit("<", ">").list(pats, op), [fun] => write!(f, "fn {fun}"), // TODO: reconsider this
pats => f.list(pats, op),
},
Self::Op(op @ PatOp::Alt, pats) => f.list(pats, op),
Self::Op(op, pats) => match pats.as_slice() { Self::Op(op, pats) => match pats.as_slice() {
[] => op.fmt(f), [] => op.fmt(f),
[rest] => write!(f, "{op}{rest}"), [rest] => write!(f, "{op}{rest}"),
@@ -281,6 +285,7 @@ impl Display for PatOp {
Self::Rest => "..", Self::Rest => "..",
Self::RangeEx => "..", Self::RangeEx => "..",
Self::RangeIn => "..=", Self::RangeIn => "..=",
Self::Record => ", ",
Self::Tuple => ", ", Self::Tuple => ", ",
Self::Slice => ", ", Self::Slice => ", ",
Self::ArRep => "[;]", Self::ArRep => "[;]",

View File

@@ -149,8 +149,8 @@ impl<A: Annotation> Foldable<A> for Pat {
Self::MetId(name) => Self::MetId(name.fold_in(folder)?), Self::MetId(name) => Self::MetId(name.fold_in(folder)?),
Self::Name(name) => Self::Name(name.fold_in(folder)?), Self::Name(name) => Self::Name(name.fold_in(folder)?),
Self::Path(path) => Self::Path(path.fold_in(folder)?), Self::Path(path) => Self::Path(path.fold_in(folder)?),
Self::NamedStruct(path, pat) => { Self::NamedRecord(path, pat) => {
Self::NamedStruct(path.fold_in(folder)?, pat.fold_in(folder)?) Self::NamedRecord(path.fold_in(folder)?, pat.fold_in(folder)?)
} }
Self::NamedTuple(path, pat) => { Self::NamedTuple(path, pat) => {
Self::NamedTuple(path.fold_in(folder)?, pat.fold_in(folder)?) Self::NamedTuple(path.fold_in(folder)?, pat.fold_in(folder)?)

View File

@@ -174,8 +174,8 @@ impl<A: Annotation> Match<A> for Pat {
(Pat::Name(_), _) => false, (Pat::Name(_), _) => false,
(Pat::Path(_), Pat::Path(_)) => true, (Pat::Path(_), Pat::Path(_)) => true,
(Pat::Path(_), _) => false, (Pat::Path(_), _) => false,
(Pat::NamedStruct(_, pat), Pat::NamedStruct(_, expr)) => Match::recurse(sub, pat, expr), (Pat::NamedRecord(_, pat), Pat::NamedRecord(_, expr)) => Match::recurse(sub, pat, expr),
(Pat::NamedStruct(..), _) => false, (Pat::NamedRecord(..), _) => false,
(Pat::NamedTuple(_, pat), Pat::NamedTuple(_, expr)) => Match::recurse(sub, pat, expr), (Pat::NamedTuple(_, pat), Pat::NamedTuple(_, expr)) => Match::recurse(sub, pat, expr),
(Pat::NamedTuple(..), _) => false, (Pat::NamedTuple(..), _) => false,
(Pat::Lit(pat), Pat::Lit(expr)) => pat == expr, (Pat::Lit(pat), Pat::Lit(expr)) => pat == expr,
@@ -193,7 +193,7 @@ impl<A: Annotation> Match<A> for Pat {
*self = expr.clone(); *self = expr.clone();
} }
} }
Pat::NamedStruct(_, expr) => expr.apply(sub), Pat::NamedRecord(_, expr) => expr.apply(sub),
Pat::NamedTuple(_, expr) => expr.apply(sub), Pat::NamedTuple(_, expr) => expr.apply(sub),
Pat::Op(_, pats) => pats.apply(sub), Pat::Op(_, pats) => pats.apply(sub),
} }

View File

@@ -110,7 +110,7 @@ impl<'a> Walk<'a> for Pat {
Self::MetId(id) => id.visit_in(v), Self::MetId(id) => id.visit_in(v),
Self::Name(name) => name.visit_in(v), Self::Name(name) => name.visit_in(v),
Self::Path(path) => path.visit_in(v), Self::Path(path) => path.visit_in(v),
Self::NamedStruct(path, pat) | Self::NamedTuple(path, pat) => { Self::NamedRecord(path, pat) | Self::NamedTuple(path, pat) => {
path.visit_in(v)?; path.visit_in(v)?;
pat.visit_in(v) pat.visit_in(v)
} }

View File

@@ -101,6 +101,8 @@ fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
TKind::Use => (Ps::Use, Prec::Max), TKind::Use => (Ps::Use, Prec::Max),
TKind::Pub => (Ps::Op(Op::Pub), Prec::Body), TKind::Pub => (Ps::Op(Op::Pub), Prec::Body),
TKind::Const => (Ps::Op(Op::Const), Prec::Body),
TKind::Static => (Ps::Op(Op::Static), Prec::Body),
TKind::For => (Ps::For, Prec::Body), TKind::For => (Ps::For, Prec::Body),
TKind::Match => (Ps::Op(Op::Match), Prec::Body), TKind::Match => (Ps::Op(Op::Match), Prec::Body),
TKind::Macro => (Ps::Op(Op::Macro), Prec::Assign), TKind::Macro => (Ps::Op(Op::Macro), Prec::Assign),
@@ -109,8 +111,6 @@ fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
| TKind::Mod | TKind::Mod
| TKind::Impl | TKind::Impl
| TKind::Let | TKind::Let
| TKind::Const
| TKind::Static
| TKind::Type | TKind::Type
| TKind::Struct | TKind::Struct
| TKind::Enum => (Ps::Def, Prec::Body), | TKind::Enum => (Ps::Def, Prec::Body),
@@ -440,8 +440,6 @@ fn from_bind(p: &mut Parser<'_>) -> PResult<(BindOp, PPrec, Option<TKind>, Optio
let bk = match p.peek()?.kind { let bk = match p.peek()?.kind {
// Token Operator Pat prec Body Token Body prec Else prec // Token Operator Pat prec Body Token Body prec Else prec
TKind::Let => (BindOp::Let, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Tuple), Some(Prec::Body)), TKind::Let => (BindOp::Let, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Tuple), Some(Prec::Body)),
TKind::Const => (BindOp::Const, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Tuple), None),
TKind::Static => (BindOp::Static, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Tuple), None),
TKind::Type => (BindOp::Type, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Extend), None), TKind::Type => (BindOp::Type, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Extend), None),
TKind::Struct => (BindOp::Struct, PPrec::Tuple, None, None, None), TKind::Struct => (BindOp::Struct, PPrec::Tuple, None, None, None),
TKind::Enum => (BindOp::Enum, PPrec::Tuple, None, None, None), TKind::Enum => (BindOp::Enum, PPrec::Tuple, None, None, None),

View File

@@ -73,12 +73,12 @@ impl<'t> Parse<'t> for Pat {
// Prefix // Prefix
let mut head = match tok.kind { let mut head = match tok.kind {
TKind::Fn => return p.consume().parse(Prec::Fn),
TKind::True | TKind::False | TKind::Character | TKind::Integer | TKind::String => { TKind::True | TKind::False | TKind::Character | TKind::Integer | TKind::String => {
Pat::Lit(p.parse(())?) Pat::Lit(p.parse(())?)
} }
TKind::Bar => p.consume().parse(level)?, TKind::Bar => p.consume().parse(level)?,
TKind::Bang => p.consume().then(Pat::Never), TKind::Bang => p.consume().then(Pat::Never),
TKind::Fn => Pat::Op(PatOp::Fn, vec![p.consume().parse(Prec::Typed)?]),
TKind::Amp => Pat::Op(PatOp::Ref, vec![p.consume().parse(Prec::Max)?]), TKind::Amp => Pat::Op(PatOp::Ref, vec![p.consume().parse(Prec::Max)?]),
TKind::Star => Pat::Op(PatOp::Ptr, vec![p.consume().parse(Prec::Max)?]), TKind::Star => Pat::Op(PatOp::Ptr, vec![p.consume().parse(Prec::Max)?]),
TKind::Mut => Pat::Op(PatOp::Mut, vec![p.consume().parse(Prec::Max)?]), TKind::Mut => Pat::Op(PatOp::Mut, vec![p.consume().parse(Prec::Max)?]),
@@ -94,7 +94,7 @@ impl<'t> Parse<'t> for Pat {
// TODO: make these postfix. // TODO: make these postfix.
match p.peek().map(Token::kind) { match p.peek().map(Token::kind) {
Ok(TKind::LParen) => Pat::NamedTuple(path, p.parse(Prec::Typed)?), Ok(TKind::LParen) => Pat::NamedTuple(path, p.parse(Prec::Typed)?),
Ok(TKind::LCurly) if level <= Prec::Tuple.next() => Pat::NamedStruct( Ok(TKind::LCurly) if level <= Prec::Tuple.next() => Pat::NamedRecord(
path, path,
p.consume() p.consume()
.opt(Prec::Tuple, TKind::RCurly)? .opt(Prec::Tuple, TKind::RCurly)?
@@ -130,6 +130,11 @@ impl<'t> Parse<'t> for Pat {
_ => vec![], _ => vec![],
}, },
), ),
TKind::LCurly => Pat::Op(
PatOp::Record,
p.consume()
.list(vec![], Prec::Typed, TKind::Comma, TKind::RCurly)?,
),
TKind::LParen => Pat::Op( TKind::LParen => Pat::Op(
PatOp::Tuple, PatOp::Tuple,
p.consume() p.consume()
@@ -145,9 +150,9 @@ impl<'t> Parse<'t> for Pat {
{ {
let kind = tok.kind; let kind = tok.kind;
head = match op { head = match op {
PatOp::Typed => Pat::Op(PatOp::Typed, vec![head, p.consume().parse(prec.next())?]), PatOp::Typed => Pat::Op(op, vec![head, p.consume().parse(prec.next())?]),
PatOp::Fn => Pat::Op(PatOp::Fn, vec![head, p.consume().parse(Prec::Fn.next())?]), PatOp::Fn => Pat::Op(op, vec![head, p.consume().parse(Prec::Fn.next())?]),
op @ (PatOp::RangeEx | PatOp::RangeIn) => Pat::Op( PatOp::RangeEx | PatOp::RangeIn => Pat::Op(
op, op,
match p.consume().peek().map(Token::kind) { match p.consume().peek().map(Token::kind) {
Ok(TKind::Integer | TKind::Character | TKind::Identifier) => { Ok(TKind::Integer | TKind::Character | TKind::Identifier) => {
@@ -156,7 +161,7 @@ impl<'t> Parse<'t> for Pat {
_ => vec![head], _ => vec![head],
}, },
), ),
op => Pat::Op(op, p.consume().list_bare(vec![head], prec.next(), kind)?), _ => Pat::Op(op, p.consume().list_bare(vec![head], prec.next(), kind)?),
} }
} }
Ok(head) Ok(head)