From c1d32f3efc6fd8c8e19ee4f66104781bbe1e6c14 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 29 Oct 2025 04:39:41 -0400 Subject: [PATCH] ast: Add `Fold`, `Folder` traits, reorganize visit parser: fix precedence of `BindOp::Let` --- src/ast.rs | 3 + src/ast/fold.rs | 271 +++++++++++++++++++++++++++++++++++++++++++++ src/ast/visit.rs | 46 ++++---- src/parser/expr.rs | 2 +- 4 files changed, 298 insertions(+), 24 deletions(-) create mode 100644 src/ast/fold.rs diff --git a/src/ast.rs b/src/ast.rs index d1d89f4..3adfbb3 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -4,6 +4,8 @@ pub mod macro_matcher; pub mod visit; +pub mod fold; + /// An annotation: extra data added on to important AST nodes. pub trait Annotation: Clone + std::fmt::Display + std::fmt::Debug + PartialEq + Eq {} impl Annotation for T {} @@ -130,6 +132,7 @@ pub enum Op { XorSet, // Expr ^= Expr OrSet, // Expr |= Expr } + /// A qualified identifier /// /// TODO: qualify identifier diff --git a/src/ast/fold.rs b/src/ast/fold.rs new file mode 100644 index 0000000..e61e28d --- /dev/null +++ b/src/ast/fold.rs @@ -0,0 +1,271 @@ +//! A folder (implementer of the [`Fold`] trait) maps ASTs to ASTs +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::wildcard_imports, clippy::missing_errors_doc)] + +use std::mem::MaybeUninit; + +use super::*; + +/// Deconstructs an entire AST, and reconstructs it from parts. +/// +/// Each function acts as a customization point. +/// +/// Aside from [Annotation]s, each node in the AST implements the [`Foldable`] trait, +/// which provides double dispatch. +pub trait Fold { + type Error; + + fn fold_annotation(&mut self, anno: A) -> Result { + Ok(anno) + } + + /// Consumes an [`Expr`], possibly transforms it, and produces a replacement [`Expr`] + fn fold_expr(&mut self, expr: Expr) -> Result, Self::Error> { + expr.children(self) + } + + /// Consumes a Symbol, possibly transforms it, and produces a replacement Symbol + fn fold_ident(&mut self, name: String) -> Result { + name.children(self) // TODO: ^ this should be a symbol + } + + /// Consumes a [`Path`], possibly transforms it, and produces a replacement [`Path`] + fn fold_path(&mut self, path: Path) -> Result { + path.children(self) + } + + /// Consumes a [`Literal`], possibly transforms it, and produces a replacement [`Literal`] + fn fold_literal(&mut self, lit: Literal) -> Result { + lit.children(self) + } + + /// Consumes a [`Use`], possibly transforms it, and produces a replacement [`Use`] + fn fold_use(&mut self, item: Use) -> Result { + item.children(self) + } + + /// Consumes a [`Pat`], possibly transforms it, and produces a replacement [`Pat`] + fn fold_pat(&mut self, pat: Pat) -> Result { + pat.children(self) + } + + /// Consumes a [`Bind`], possibly transforms it, and produces a replacement [`Bind`] + fn fold_bind(&mut self, bind: Bind) -> Result, Self::Error> { + bind.children(self) + } + + /// Consumes a [`Make`], possibly transforms it, and produces a replacement [`Make`] + fn fold_make(&mut self, make: Make) -> Result, Self::Error> { + make.children(self) + } + + /// Consumes a [`MakeArm`], possibly transforms it, and produces a replacement [`MakeArm`] + fn fold_makearm(&mut self, arm: MakeArm) -> Result, Self::Error> { + arm.children(self) + } +} + +pub trait Foldable: Sized { + /// Calls `Self`'s appropriate [Folder] function(s) + fn fold_in + ?Sized>(self, folder: &mut F) -> Result; + + /// Destructures `self`, calling [`Foldable::fold_in`] on all foldable members, + /// and rebuilds a `Self` out of the results. + #[allow(unused_variables)] + fn children + ?Sized>(self, folder: &mut F) -> Result { + Ok(self) + } +} + +impl Foldable for Expr { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_expr(self) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + Ok(match self { + Self::Omitted => Self::Omitted, + Self::Id(path) => Self::Id(path.fold_in(folder)?), + Self::MetId(id) => Self::MetId(id.fold_in(folder)?), + Self::Lit(literal) => Self::Lit(literal.fold_in(folder)?), + Self::Use(item) => Self::Use(item.fold_in(folder)?), + Self::Bind(bind) => Self::Bind(bind.fold_in(folder)?), + Self::Make(make) => Self::Make(make.fold_in(folder)?), + Self::Op(op, annos) => Self::Op(op, annos.fold_in(folder)?), + }) + } +} + +impl Foldable for String { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_ident(self) + } +} + +impl Foldable for Path { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_path(self) + } + + fn children + ?Sized>(self, _folder: &mut F) -> Result { + Ok(self) + } +} + +impl Foldable for Literal { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_literal(self) + } + + fn children + ?Sized>(self, _folder: &mut F) -> Result { + Ok(self) + } +} + +impl Foldable for Use { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_use(self) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + Ok(match self { + Self::Glob => Self::Glob, + Self::Name(name) => Self::Name(name), + Self::Path(name, rest) => Self::Path(name, rest.fold_in(folder)?), + Self::Tree(items) => Self::Tree(items.fold_in(folder)?), + }) + } +} + +impl Foldable for Pat { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_pat(self) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + Ok(match self { + Self::Ignore => Self::Ignore, + Self::Never => Self::Never, + Self::MetId(name) => Self::MetId(name.fold_in(folder)?), + Self::Name(name) => Self::Name(name.fold_in(folder)?), + Self::Path(path) => Self::Path(path.fold_in(folder)?), + Self::NamedStruct(path, pat) => { + Self::NamedStruct(path.fold_in(folder)?, pat.fold_in(folder)?) + } + Self::NamedTuple(path, pat) => { + Self::NamedTuple(path.fold_in(folder)?, pat.fold_in(folder)?) + } + Self::Lit(literal) => Self::Lit(literal.fold_in(folder)?), + Self::Op(op, pats) => Self::Op(op, pats.fold_in(folder)?), + }) + } +} + +impl Foldable for Bind { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_bind(self) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + let Self(op, gens, pat, exprs) = self; + Ok(Self( + op, + gens.fold_in(folder)?, + pat.fold_in(folder)?, + exprs.fold_in(folder)?, + )) + } +} + +impl Foldable for Make { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_make(self) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + let Self(expr, arms) = self; + Ok(Self(expr.fold_in(folder)?, arms.fold_in(folder)?)) + } +} + +impl Foldable for MakeArm { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_makearm(self) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + let Self(name, expr) = self; + Ok(Self(name, expr.fold_in(folder)?)) + } +} + +impl, A: Annotation> Foldable for Anno { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + let Self(expr, anno) = self; + let anno = folder.fold_annotation(anno)?; + Ok(Self(expr.fold_in(folder)?, anno)) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + let Self(expr, anno) = self; + Ok(Self(expr.children(folder)?, anno)) + } +} + +////////////////////////////////////////////// +// GENERIC IMPLEMENTATIONS ON COLLECTIONS // +////////////////////////////////////////////// + +/// Maps the value in the box across `f()` without deallocating +fn box_try_map(boxed: Box, f: impl FnOnce(T) -> Result) -> Result, E> { + // TODO: replace with Box::take when it stabilizes. + let rawbox = Box::into_raw(boxed); + + // Safety: `rawbox` came from a Box, so it is aligned and initialized. + // To prevent further reuse and deallocate on failure, rawbox is + // shadowed by a Box>. + // Safety: MaybeUninit has the same size and alignment as T. + let (value, rawbox) = (unsafe { rawbox.read() }, unsafe { + Box::from_raw(rawbox.cast::>()) + }); + + // rawbox is reinitialized with f(value) + Ok(Box::write(rawbox, f(value)?)) +} + +impl, A: Annotation> Foldable for Box { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + box_try_map(self, |t| t.fold_in(folder)) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + box_try_map(self, |t| t.children(folder)) + } +} + +impl, A: Annotation> Foldable for Vec { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + self.into_iter().map(|e| e.fold_in(folder)).collect() + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + // TODO: is this correct for generic data structures? + self.into_iter().map(|e| e.fold_in(folder)).collect() + } +} + +impl, A: Annotation> Foldable for Option { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + Ok(match self { + Self::Some(value) => Some(value.fold_in(folder)?), + Self::None => None, + }) + } + + fn children + ?Sized>(self, folder: &mut F) -> Result { + Ok(match self { + Self::Some(value) => Some(value.children(folder)?), + Self::None => None, + }) + } +} diff --git a/src/ast/visit.rs b/src/ast/visit.rs index ebdb91a..f1d5f75 100644 --- a/src/ast/visit.rs +++ b/src/ast/visit.rs @@ -9,6 +9,9 @@ pub trait Visit<'a> { fn visit(&mut self, walk: &'a impl Walk<'a>) -> Result<(), Self::Error> { walk.visit_in(self) } + fn visit_expr(&mut self, expr: &'a Expr) -> Result<(), Self::Error> { + expr.children(self) + } fn visit_ident(&mut self, name: &'a str) -> Result<(), Self::Error> { name.children(self) } @@ -33,9 +36,6 @@ pub trait Visit<'a> { fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { item.children(self) } - fn visit_expr(&mut self, expr: &'a Expr) -> Result<(), Self::Error> { - expr.children(self) - } } pub trait Walk<'a> { @@ -46,6 +46,26 @@ pub trait Walk<'a> { fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error>; } +impl<'a, A: Annotation> Walk<'a> for Expr { + fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + match self { + Self::Omitted => Ok(()), + Self::Id(path) => path.visit_in(v), + Self::MetId(id) => id.visit_in(v), + Self::Lit(literal) => literal.visit_in(v), + Self::Use(u) => u.visit_in(v), + Self::Bind(bind) => bind.visit_in(v), + Self::Make(make) => make.visit_in(v), + Self::Op(_op, annos) => annos.visit_in(v), + } + } + + #[inline] + fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + v.visit_expr(self) + } +} + impl<'a> Walk<'a> for str { fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { v.visit_ident(self) @@ -145,26 +165,6 @@ impl<'a, A: Annotation> Walk<'a> for MakeArm { } } -impl<'a, A: Annotation> Walk<'a> for Expr { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { - match self { - Self::Omitted => Ok(()), - Self::Id(path) => path.visit_in(v), - Self::MetId(id) => id.visit_in(v), - Self::Lit(literal) => literal.visit_in(v), - Self::Use(u) => u.visit_in(v), - Self::Bind(bind) => bind.visit_in(v), - Self::Make(make) => make.visit_in(v), - Self::Op(_op, annos) => annos.visit_in(v), - } - } - - #[inline] - fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { - v.visit_expr(self) - } -} - impl<'a, T: Annotation + Walk<'a>, A: Annotation> Walk<'a> for Anno { #[inline] fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { diff --git a/src/parser/expr.rs b/src/parser/expr.rs index cc4103c..71d4c7b 100644 --- a/src/parser/expr.rs +++ b/src/parser/expr.rs @@ -507,7 +507,7 @@ fn parse_for(p: &mut Parser<'_>, _level: ()) -> PResult { fn from_bind(p: &mut Parser<'_>) -> PResult<(BindOp, PPrec, Option, Option, Option)> { let bk = match p.peek()?.kind { // Token Operator Pat prec Body Token Body prec Else prec - TKind::Let => (BindOp::Let, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Compare), 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::Assign), None), TKind::Static => (BindOp::Static, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Assign), None), TKind::Type => (BindOp::Type, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Project), None),