doughlang: symbol interning and AST reparameterization

- intern: Add interners from cl-intern

- ast:
- Break AST into ternimals (AstTypes) and nonterminals (Expr, Pat, Bind, Make, Use)
- Make AST generic over terminals (except operators, for now)
This commit is contained in:
2026-01-06 04:57:15 -05:00
parent e4c008bd4b
commit 2be73d2660
17 changed files with 952 additions and 592 deletions

116
Cargo.lock generated
View File

@@ -2,23 +2,23 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "2.9.1"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cfg-if"
version = "1.0.1"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cl-arena"
version = "0.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b1af9ddfb0f2e11bd7f73f26ae78571e59767b2109acc2d62ef5fad6a23cf9d"
[[package]]
name = "crossterm"
@@ -45,6 +45,7 @@ dependencies = [
name = "doughlang"
version = "0.1.0"
dependencies = [
"cl-arena",
"repline",
"unicode-ident",
]
@@ -61,9 +62,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.179"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
[[package]]
name = "linux-raw-sys"
@@ -79,19 +80,18 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lock_api"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "parking_lot"
version = "0.12.4"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -99,22 +99,22 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.11"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
"windows-link",
]
[[package]]
name = "redox_syscall"
version = "0.5.17"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
@@ -130,9 +130,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags",
"errno",
@@ -155,9 +155,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "unicode-ident"
version = "1.0.18"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "windows-link"
@@ -173,67 +173,3 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@@ -5,4 +5,5 @@ edition = "2024"
[dependencies]
repline = "*"
cl-arena = "*"
unicode-ident = "1.0.12"

View File

@@ -5,11 +5,13 @@ use std::{convert::Infallible, error::Error};
use doughlang::{
ast::{
fold::{Fold, Foldable},
types::{Literal, Path, Symbol},
visit::{Visit, Walk},
*,
},
lexer::Lexer,
parser::{Parser, expr::Prec},
span::Span,
};
const CODE: &str = r#"
@@ -86,6 +88,7 @@ fn main() -> Result<(), Box<dyn Error>> {
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct CountNodes {
exprs: usize,
macro_ids: usize,
idents: usize,
paths: usize,
literals: usize,
@@ -102,50 +105,54 @@ impl CountNodes {
}
}
impl<'a> Visit<'a> for CountNodes {
impl<'a, A: AstTypes> Visit<'a, A> for CountNodes {
type Error = Infallible;
fn visit_expr<A: Annotation>(&mut self, expr: &'a Expr<A>) -> Result<(), Self::Error> {
fn visit_macro_id(&mut self, _name: &'a A::MacroId) -> Result<(), Self::Error> {
self.macro_ids += 1;
Ok(())
}
fn visit_symbol(&mut self, _name: &'a A::Symbol) -> Result<(), Self::Error> {
self.idents += 1;
Ok(())
}
fn visit_path(&mut self, _path: &'a A::Path) -> Result<(), Self::Error> {
self.paths += 1;
Ok(())
}
fn visit_literal(&mut self, _lit: &'a A::Literal) -> Result<(), Self::Error> {
self.literals += 1;
Ok(())
}
fn visit_use(&mut self, item: &'a Use<A>) -> Result<(), Self::Error> {
self.uses += 1;
item.children(self)
}
fn visit_expr(&mut self, expr: &'a Expr<A>) -> Result<(), Self::Error> {
self.exprs += 1;
expr.children(self)
}
fn visit_ident(&mut self, name: &'a str) -> Result<(), Self::Error> {
self.idents += 1;
name.children(self)
}
fn visit_path(&mut self, path: &'a Path) -> Result<(), Self::Error> {
self.paths += 1;
path.children(self)
}
fn visit_literal(&mut self, lit: &'a Literal) -> Result<(), Self::Error> {
self.literals += 1;
lit.children(self)
}
fn visit_use(&mut self, item: &'a Use) -> Result<(), Self::Error> {
self.uses += 1;
item.children(self)
}
fn visit_pat<A: Annotation>(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
fn visit_pat(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
self.patterns += 1;
item.children(self)
}
fn visit_bind<A: Annotation>(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
fn visit_bind(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
self.binds += 1;
item.children(self)
}
fn visit_make<A: Annotation>(&mut self, item: &'a Make<A>) -> Result<(), Self::Error> {
fn visit_make(&mut self, item: &'a Make<A>) -> Result<(), Self::Error> {
self.makes += 1;
item.children(self)
}
fn visit_makearm<A: Annotation>(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
fn visit_makearm(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
self.makearms += 1;
item.children(self)
}
@@ -153,10 +160,26 @@ impl<'a> Visit<'a> for CountNodes {
struct Sevenfold;
impl<A: Annotation> Fold<A> for Sevenfold {
impl Fold<DefaultTypes> for Sevenfold {
type Error = ();
fn fold_annotation(&mut self, anno: Span) -> Result<Span, Self::Error> {
Ok(anno)
}
fn fold_literal(&mut self, _lit: Literal) -> Result<Literal, Self::Error> {
Ok(Literal::Int(7, 10))
}
fn fold_macro_id(&mut self, name: Symbol) -> Result<Symbol, Self::Error> {
Ok(name)
}
fn fold_symbol(&mut self, name: Symbol) -> Result<Symbol, Self::Error> {
Ok(name)
}
fn fold_path(&mut self, path: Path) -> Result<Path, Self::Error> {
Ok(path)
}
}

View File

@@ -2,9 +2,9 @@ use std::error::Error;
use doughlang::{
ast::{
visit::{Visit, Walk},
*,
types::{Literal, Path}, visit::{Visit, Walk}, *
},
intern::interned::Interned,
lexer::Lexer,
parser::{Parser, expr::Prec},
};
@@ -126,19 +126,15 @@ impl ToLisp {
}
}
impl<'a> Visit<'a> for ToLisp {
impl<'a> Visit<'a, DefaultTypes> for ToLisp {
type Error = ();
fn visit(&mut self, walk: &'a impl Walk<'a>) -> Result<(), Self::Error> {
walk.visit_in(self)
}
fn visit_expr<A: Annotation>(&mut self, expr: &'a Expr<A>) -> Result<(), Self::Error> {
fn visit_expr(&mut self, expr: &'a Expr) -> Result<(), Self::Error> {
match expr {
Expr::Omitted => print!("()"),
Expr::Id(path) => path.visit_in(self)?,
Expr::Id(path) => self.visit_path(path)?,
Expr::MetId(id) => print!("`{id}"),
Expr::Lit(literal) => literal.visit_in(self)?,
Expr::Lit(literal) => self.visit_literal(literal)?,
Expr::Use(import) => import.visit_in(self)?,
Expr::Bind(bind) => bind.visit_in(self)?,
Expr::Make(make) => make.visit_in(self)?,
@@ -155,7 +151,12 @@ impl<'a> Visit<'a> for ToLisp {
Ok(())
}
fn visit_ident(&mut self, name: &'a str) -> Result<(), Self::Error> {
fn visit_macro_id(&mut self, name: &'a Interned<'static, str>) -> Result<(), Self::Error> {
print!("{name}");
Ok(())
}
fn visit_symbol(&mut self, name: &'a Interned<'static, str>) -> Result<(), Self::Error> {
print!("{name}");
Ok(())
}
@@ -165,7 +166,7 @@ impl<'a> Visit<'a> for ToLisp {
print!("(at");
for part in parts {
print!(" ");
part.visit_in(self)?;
self.visit_symbol(part)?;
}
print!(")");
Ok(())
@@ -179,7 +180,7 @@ impl<'a> Visit<'a> for ToLisp {
fn visit_use(&mut self, item: &'a Use) -> Result<(), Self::Error> {
match item {
Use::Glob => print!("(use-glob)"),
Use::Name(name) => name.visit_in(self)?,
Use::Name(name) => self.visit_symbol(name)?,
Use::Alias(name, alias) => print!("(use-alias {name} {alias})"),
Use::Path(name, tree) => {
print!("(use-path {name} ");
@@ -198,7 +199,7 @@ impl<'a> Visit<'a> for ToLisp {
Ok(())
}
fn visit_pat<A: Annotation>(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
fn visit_pat(&mut self, item: &'a Pat) -> Result<(), Self::Error> {
match item {
Pat::Ignore => print!("(ignore)"),
Pat::Never => print!("(never)"),
@@ -217,7 +218,7 @@ impl<'a> Visit<'a> for ToLisp {
Ok(())
}
fn visit_bind<A: Annotation>(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
fn visit_bind(&mut self, item: &'a Bind) -> Result<(), Self::Error> {
let Bind(op, generics, pattern, exprs) = item;
print!("({} ", self.bind_op(*op));
if !generics.is_empty() {
@@ -236,11 +237,11 @@ impl<'a> Visit<'a> for ToLisp {
Ok(())
}
fn visit_make<A: Annotation>(&mut self, item: &'a Make<A>) -> Result<(), Self::Error> {
fn visit_make(&mut self, item: &'a Make) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_makearm<A: Annotation>(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> {
item.children(self)
}
}

View File

@@ -90,50 +90,50 @@ impl Weight {
}
}
impl<'a> Visit<'a> for Weight {
impl<'a, A: AstTypes> Visit<'a, A> for Weight {
type Error = Infallible;
fn visit_expr<A: Annotation>(&mut self, item: &'a Expr<A>) -> Result<(), Self::Error> {
fn visit_expr(&mut self, item: &'a Expr<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_ident(&mut self, item: &'a str) -> Result<(), Self::Error> {
fn visit_symbol(&mut self, item: &'a A::Symbol) -> Result<(), Self::Error> {
self.size += size_of_val(item);
Ok(())
}
fn visit_path(&mut self, item: &'a A::Path) -> Result<(), Self::Error> {
self.size += size_of_val(item);
Ok(())
}
fn visit_literal(&mut self, item: &'a A::Literal) -> Result<(), Self::Error> {
self.size += size_of_val(item);
Ok(())
}
fn visit_use(&mut self, item: &'a Use<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_path(&mut self, item: &'a Path) -> Result<(), Self::Error> {
self.size += size_of_val(item) + size_of_val(item.parts.as_slice());
item.children(self)
}
fn visit_literal(&mut self, item: &'a Literal) -> Result<(), Self::Error> {
fn visit_pat(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_use(&mut self, item: &'a Use) -> Result<(), Self::Error> {
fn visit_bind(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_pat<A: Annotation>(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
fn visit_make(&mut self, item: &'a Make<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_bind<A: Annotation>(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_make<A: Annotation>(&mut self, item: &'a Make<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_makearm<A: Annotation>(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
fn visit_makearm(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}

View File

@@ -1,22 +1,55 @@
//! The Abstract Syntax Tree defines an interface between the parser and type checker
use crate::span::Span;
pub mod macro_matcher;
pub mod visit;
pub mod fold;
use std::hash::Hash;
mod display;
/// An annotation: extra data added on to important AST nodes.
pub mod fold;
pub mod macro_matcher;
pub mod types;
pub mod visit;
pub use types::DefaultTypes;
/// An annotation: bounds on AST parameters
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 {}
pub trait AstTypes: Annotation {
/// An annotation on an arbitrary [Expr]
type Annotation: Annotation;
/// A literal value
type Literal: Annotation;
/// A (possibly interned) symbol or index which implements [AsRef<str>]
type MacroId: Annotation + Hash + AsRef<str>;
/// A (possibly interned) symbol or index
type Symbol: Annotation + Copy + Hash;
/// A (possibly compound) symbol or index
type Path: Annotation;
// /// An Operator in an [Expression](Expr)
// type ExprOp: Annotation + Copy;
// /// An Operator within a [Pattern](Pat)
// type PatOp: Annotation + Copy;
}
/// A value with an annotation.
#[derive(Clone, PartialEq, Eq)]
pub struct Anno<T: Annotation, A: Annotation = Span>(pub T, pub A);
pub struct Anno<T: Annotation, A: AstTypes = DefaultTypes>(pub T, pub A::Annotation);
impl<T: Annotation, A: AstTypes> std::fmt::Debug for Anno<T, A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<A::Annotation as std::fmt::Debug>::fmt(&self.1, f)?;
f.write_str(": ")?;
<T as std::fmt::Debug>::fmt(&self.0, f)
}
}
/// Expressions: The beating heart of Dough.
///
@@ -31,17 +64,17 @@ pub struct Anno<T: Annotation, A: Annotation = Span>(pub T, pub A);
/// performing import resolution, as imports typically depend on the order
/// in which names are bound.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Expr<A: Annotation = Span> {
pub enum Expr<A: AstTypes = DefaultTypes> {
/// Omitted by semicolon insertion-elision rules
Omitted,
/// An identifier
Id(Path),
Id(A::Path),
/// An escaped token for macro binding
MetId(String),
MetId(A::MacroId),
/// A literal bool, string, char, or int
Lit(Literal),
Lit(A::Literal),
/// use Use
Use(Use),
Use(Use<A>),
/// `let Pat::NoTopAlt (= expr (else expr)?)?` |
/// `(fn | mod | impl) Pat::Fn Expr`
Bind(Box<Bind<A>>),
@@ -198,42 +231,48 @@ pub enum Op {
OrSet,
}
/// A qualified identifier
///
/// TODO: qualify identifier
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Path {
// TODO: Identifier interning
pub parts: Vec<String>,
// TODO: generic parameters
}
impl<A: AstTypes> Expr<A> {
pub const fn anno(self, annotation: A::Annotation) -> Anno<Expr<A>, A> {
Anno(self, annotation)
}
/// A literal value (boolean, character, integer, string)
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal {
/// A boolean literal: true | false
Bool(bool),
/// A character literal: 'a', '\u{1f988}'
Char(char),
/// An integer literal: 0, 123, 0x10
Int(u128, u32),
/// A string literal:
Str(String),
}
pub fn and_do(self, annotation: A::Annotation, 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)
}
/// A compound import declaration
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Use {
/// "*"
Glob,
/// Identifier
Name(String),
/// Identifier as Identifier
Alias(String, String),
/// Identifier :: Use
Path(String, Box<Use>),
/// { Use, * }
Tree(Vec<Use>),
pub fn to_tuple(self, annotation: A::Annotation) -> Self {
match self {
Self::Op(Op::Tuple, _) => self,
_ => Self::Op(Op::Tuple, vec![self.anno(annotation)]),
}
}
pub const fn is_place(&self) -> bool {
matches!(
self,
Self::Id(_) | Self::Op(Op::Index | Op::Dot | Op::Deref, _)
)
}
pub const fn is_value(&self) -> bool {
!self.is_place()
}
#[allow(clippy::type_complexity)]
pub const fn as_slice(&self) -> Option<(Op, &[Anno<Expr<A>, A>])> {
match self {
Expr::Op(op, args) => Some((*op, args.as_slice())),
_ => None,
}
}
}
/// A pattern binding
@@ -249,9 +288,9 @@ pub enum Use {
/// Pat => Expr // in match
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Bind<A: Annotation = Span>(
pub struct Bind<A: AstTypes = DefaultTypes>(
pub BindOp,
pub Vec<Path>,
pub Vec<A::Path>,
pub Pat<A>,
pub Vec<Anno<Expr<A>, A>>,
);
@@ -277,34 +316,19 @@ pub enum BindOp {
/// A `Pat => Expr` binding
Match,
}
/// A make (constructor) expression
/// ```ignore
/// Expr { (Ident (: Expr)?),* }
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Make<A: Annotation = Span>(pub Anno<Expr<A>, A>, pub Vec<MakeArm<A>>);
/// A single "arm" of a make expression
/// ```text
/// Identifier (':' Expr)?
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MakeArm<A: Annotation = Span>(pub String, pub Option<Anno<Expr<A>, A>>);
/// Binding patterns for each kind of matchable value.
///
/// This covers both patterns in Match expressions, and type annotations.
/// This covers both bindings and type annotations in [Bind] expressions.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Pat<A: Annotation = Span> {
pub enum Pat<A: AstTypes = DefaultTypes> {
/// Matches anything without binding
Ignore,
/// Matches nothing, ever
Never,
/// Matches nothing; used for macro substitution
MetId(String),
MetId(A::MacroId),
/// Matches anything, and binds it to a name
Name(String),
Name(A::Symbol),
/// Matches a value by equality comparison
Value(Box<Anno<Expr<A>, A>>),
/// Matches a compound pattern
@@ -348,64 +372,7 @@ pub enum PatOp {
Alt,
}
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)
}
}
impl<A: Annotation> Default for Expr<A> {
fn default() -> Self {
Self::Op(Op::Tuple, vec![])
}
}
impl<A: Annotation> Expr<A> {
pub const 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 to_tuple(self, annotation: A) -> Self {
match self {
Self::Op(Op::Tuple, _) => self,
_ => Self::Op(Op::Tuple, vec![self.anno(annotation)]),
}
}
pub const fn is_place(&self) -> bool {
matches!(
self,
Self::Id(_) | Self::Op(Op::Index | Op::Dot | Op::Deref, _)
)
}
pub const fn is_value(&self) -> bool {
!self.is_place()
}
#[allow(clippy::type_complexity)]
pub const fn as_slice(&self) -> Option<(Op, &[Anno<Expr<A>, A>])> {
match self {
Expr::Op(op, args) => Some((*op, args.as_slice())),
_ => None,
}
}
}
impl<A: Annotation> Pat<A> {
impl<A: AstTypes> Pat<A> {
pub fn to_tuple(self) -> Self {
match self {
Self::Op(PatOp::Tuple, _) => self,
@@ -414,8 +381,31 @@ impl<A: Annotation> Pat<A> {
}
}
impl From<&str> for Path {
fn from(value: &str) -> Self {
Self { parts: vec![value.to_owned()] }
}
/// A compound import declaration
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Use<A: AstTypes = DefaultTypes> {
/// "*"
Glob,
/// Identifier
Name(A::Symbol),
/// Identifier as Identifier
Alias(A::Symbol, A::Symbol),
/// Identifier :: Use
Path(A::Symbol, Box<Use<A>>),
/// { Use, * }
Tree(Vec<Use<A>>),
}
/// A make (constructor) expression
/// ```ignore
/// Expr { (Ident (: Expr)?),* }
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Make<A: AstTypes = DefaultTypes>(pub Anno<Expr<A>, A>, pub Vec<MakeArm<A>>);
/// A single "arm" of a make expression
/// ```text
/// Identifier (':' Expr)?
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MakeArm<A: AstTypes = DefaultTypes>(pub A::Symbol, pub Option<Anno<Expr<A>, A>>);

View File

@@ -2,13 +2,13 @@ use super::*;
use crate::fmt::FmtAdapter;
use std::{fmt::Display, format_args as fmt};
impl<T: Display + Annotation, A: Annotation> Display for Anno<T, A> {
impl<T: Display + Annotation, A: AstTypes> Display for Anno<T, A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<A: Annotation> Display for Expr<A> {
impl<A: AstTypes> Display for Expr<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Omitted => "/* omitted */".fmt(f),
@@ -135,28 +135,7 @@ impl Display for Op {
}
}
impl Display for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { parts } = self;
f.list(parts, "::")
}
}
impl Display for Literal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bool(v) => v.fmt(f),
Self::Char(c) => write!(f, "'{}'", c.escape_debug()),
Self::Int(i, 2) => write!(f, "0b{i:b}"),
Self::Int(i, 8) => write!(f, "0o{i:o}"),
Self::Int(i, 16) => write!(f, "0x{i:x}"),
Self::Int(i, _) => i.fmt(f),
Self::Str(s) => write!(f, "\"{}\"", s.escape_debug()),
}
}
}
impl Display for Use {
impl<A: AstTypes> Display for Use<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Glob => "*".fmt(f),
@@ -174,7 +153,7 @@ impl Display for Use {
}
}
impl<A: Annotation> Display for Bind<A> {
impl<A: AstTypes> Display for Bind<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(op, gens, pat, exprs) = self;
op.fmt(f)?;
@@ -238,14 +217,14 @@ impl Display for BindOp {
}
}
impl<A: Annotation> Display for Make<A> {
impl<A: AstTypes> Display for Make<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(expr, make_arms) = self;
f.delimit(fmt!("({expr} {{"), "})").list(make_arms, ", ")
}
}
impl<A: Annotation> Display for MakeArm<A> {
impl<A: AstTypes> Display for MakeArm<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self(name, Some(body)) => write!(f, "{name}: {body}"),
@@ -254,7 +233,7 @@ impl<A: Annotation> Display for MakeArm<A> {
}
}
impl<A: Annotation> Display for Pat<A> {
impl<A: AstTypes> Display for Pat<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ignore => "_".fmt(f),

View File

@@ -2,207 +2,201 @@
#![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,
/// Aside from [`AstTypes`], each node in the AST implements the [`Foldable`] trait,
/// which provides double dispatch.
pub trait Fold<A: Annotation> {
pub trait Fold<From: AstTypes, To: AstTypes = From> {
type Error;
fn fold_annotation(&mut self, anno: A) -> Result<A, Self::Error> {
Ok(anno)
}
/// Consumes an Annotation in A, possibly transforms it, and produces a replacement Annotation
/// in B
fn fold_annotation(&mut self, anno: From::Annotation) -> Result<To::Annotation, Self::Error>;
/// Consumes an [`Expr`], possibly transforms it, and produces a replacement [`Expr`]
fn fold_expr(&mut self, expr: Expr<A>) -> Result<Expr<A>, Self::Error> {
/// Consumes a `MacroId` in A, possibly transforms it, and produces a replacement `MacroId` in B
fn fold_macro_id(&mut self, name: From::MacroId) -> Result<To::MacroId, Self::Error>;
/// Consumes a `Symbol` in A, possibly transforms it, and produces a replacement `Symbol` in B
fn fold_symbol(&mut self, name: From::Symbol) -> Result<To::Symbol, Self::Error>;
/// Consumes a `Path` in A, possibly transforms it, and produces a replacement `Path` in B
fn fold_path(&mut self, path: From::Path) -> Result<To::Path, Self::Error>;
/// Consumes a `Literal` in A, possibly transforms it, and produces a replacement `Literal` in B
fn fold_literal(&mut self, lit: From::Literal) -> Result<To::Literal, Self::Error>;
/// Folds an annotated expression, so the expression and annotation can be seen at once.
fn fold_anno_expr(
&mut self,
expr: Anno<Expr<From>, From>,
) -> Result<Anno<Expr<To>, To>, Self::Error> {
expr.children(self)
}
/// Consumes a Symbol, possibly transforms it, and produces a replacement Symbol
fn fold_ident(&mut self, name: String) -> Result<String, Self::Error> {
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, Self::Error> {
path.children(self)
}
/// Consumes a [`Literal`], possibly transforms it, and produces a replacement [`Literal`]
fn fold_literal(&mut self, lit: Literal) -> Result<Literal, Self::Error> {
lit.children(self)
/// Consumes an [`Expr`], possibly transforms it, and produces a replacement [`Expr`]
fn fold_expr(&mut self, expr: Expr<From>) -> Result<Expr<To>, Self::Error> {
expr.children(self)
}
/// Consumes a [`Use`], possibly transforms it, and produces a replacement [`Use`]
fn fold_use(&mut self, item: Use) -> Result<Use, Self::Error> {
fn fold_use(&mut self, item: Use<From>) -> Result<Use<To>, Self::Error> {
item.children(self)
}
/// Consumes a [`Pat`], possibly transforms it, and produces a replacement [`Pat`]
fn fold_pat(&mut self, pat: Pat<A>) -> Result<Pat<A>, Self::Error> {
fn fold_pat(&mut self, pat: Pat<From>) -> Result<Pat<To>, Self::Error> {
pat.children(self)
}
/// Consumes a [`Bind`], possibly transforms it, and produces a replacement [`Bind`]
fn fold_bind(&mut self, bind: Bind<A>) -> Result<Bind<A>, Self::Error> {
fn fold_bind(&mut self, bind: Bind<From>) -> Result<Bind<To>, Self::Error> {
bind.children(self)
}
/// Consumes a [`Make`], possibly transforms it, and produces a replacement [`Make`]
fn fold_make(&mut self, make: Make<A>) -> Result<Make<A>, Self::Error> {
fn fold_make(&mut self, make: Make<From>) -> Result<Make<To>, Self::Error> {
make.children(self)
}
/// Consumes a [`MakeArm`], possibly transforms it, and produces a replacement [`MakeArm`]
fn fold_makearm(&mut self, arm: MakeArm<A>) -> Result<MakeArm<A>, Self::Error> {
fn fold_makearm(&mut self, arm: MakeArm<From>) -> Result<MakeArm<To>, Self::Error> {
arm.children(self)
}
}
pub trait Foldable<A: Annotation>: Sized {
pub trait Foldable<A: AstTypes, B: AstTypes>: Sized {
/// The return type of the associated [Fold] function
type Out;
/// Calls `Self`'s appropriate [Folder] function(s)
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error>;
/// Destructures `self`, calling [`Foldable::fold_in`] on all foldable members,
/// and rebuilds a `Self` out of the results.
#[allow(unused_variables)]
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
Ok(self)
}
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error>;
}
impl<A: Annotation> Foldable<A> for Expr<A> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
impl<A: AstTypes, B: AstTypes> Foldable<A, B> for Expr<A> {
type Out = Expr<B>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
folder.fold_expr(self)
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
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)?),
Self::Omitted => Expr::Omitted,
Self::Id(path) => Expr::Id(folder.fold_path(path)?),
Self::MetId(id) => Expr::MetId(folder.fold_macro_id(id)?),
Self::Lit(lit) => Expr::Lit(folder.fold_literal(lit)?),
Self::Use(item) => Expr::Use(item.fold_in(folder)?),
Self::Bind(bind) => Expr::Bind(bind.fold_in(folder)?),
Self::Make(make) => Expr::Make(make.fold_in(folder)?),
Self::Op(op, annos) => Expr::Op(op, annos.fold_in(folder)?),
})
}
}
impl<A: Annotation> Foldable<A> for String {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
folder.fold_ident(self)
}
}
impl<A: AstTypes, B: AstTypes> Foldable<A, B> for Use<A> {
type Out = Use<B>;
impl<A: Annotation> Foldable<A> for Path {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
folder.fold_path(self)
}
fn children<F: Fold<A> + ?Sized>(self, _folder: &mut F) -> Result<Self, F::Error> {
Ok(self)
}
}
impl<A: Annotation> Foldable<A> for Literal {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
folder.fold_literal(self)
}
fn children<F: Fold<A> + ?Sized>(self, _folder: &mut F) -> Result<Self, F::Error> {
Ok(self)
}
}
impl<A: Annotation> Foldable<A> for Use {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
folder.fold_use(self)
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
Ok(match self {
Self::Glob => Self::Glob,
Self::Name(name) => Self::Name(name),
Self::Alias(name, alias) => Self::Alias(name, alias),
Self::Path(name, rest) => Self::Path(name, rest.fold_in(folder)?),
Self::Tree(items) => Self::Tree(items.fold_in(folder)?),
Self::Glob => Use::Glob,
Self::Name(name) => Use::Name(folder.fold_symbol(name)?),
Self::Alias(name, alias) => {
Use::Alias(folder.fold_symbol(name)?, folder.fold_symbol(alias)?)
}
Self::Path(name, rest) => Use::Path(folder.fold_symbol(name)?, rest.fold_in(folder)?),
Self::Tree(items) => Use::Tree(items.fold_in(folder)?),
})
}
}
impl<A: Annotation> Foldable<A> for Pat<A> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
impl<A: AstTypes, B: AstTypes> Foldable<A, B> for Pat<A> {
type Out = Pat<B>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
folder.fold_pat(self)
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
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::Value(expr) => Self::Value(expr.fold_in(folder)?),
Self::Op(op, pats) => Self::Op(op, pats.fold_in(folder)?),
Self::Ignore => Pat::Ignore,
Self::Never => Pat::Never,
Self::MetId(name) => Pat::MetId(folder.fold_macro_id(name)?),
Self::Name(name) => Pat::Name(folder.fold_symbol(name)?),
Self::Value(expr) => Pat::Value(expr.fold_in(folder)?),
Self::Op(op, pats) => Pat::Op(op, pats.fold_in(folder)?),
})
}
}
impl<A: Annotation> Foldable<A> for Bind<A> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
impl<A: AstTypes, B: AstTypes> Foldable<A, B> for Bind<A> {
type Out = Bind<B>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
folder.fold_bind(self)
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
let Self(op, gens, pat, exprs) = self;
Ok(Self(
Ok(Bind(
op,
gens.fold_in(folder)?,
gens.into_iter()
.map(|g| folder.fold_path(g))
.collect::<Result<_, _>>()?,
pat.fold_in(folder)?,
exprs.fold_in(folder)?,
))
}
}
impl<A: Annotation> Foldable<A> for Make<A> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
impl<A: AstTypes, B: AstTypes> Foldable<A, B> for Make<A> {
type Out = Make<B>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
folder.fold_make(self)
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
let Self(expr, arms) = self;
Ok(Self(expr.fold_in(folder)?, arms.fold_in(folder)?))
Ok(Make(expr.fold_in(folder)?, arms.fold_in(folder)?))
}
}
impl<A: Annotation> Foldable<A> for MakeArm<A> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
impl<A: AstTypes, B: AstTypes> Foldable<A, B> for MakeArm<A> {
type Out = MakeArm<B>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
folder.fold_makearm(self)
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
let Self(name, expr) = self;
Ok(Self(name, expr.fold_in(folder)?))
Ok(MakeArm(folder.fold_symbol(name)?, expr.fold_in(folder)?))
}
}
impl<T: Annotation + Foldable<A>, A: Annotation> Foldable<A> for Anno<T, A> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
let Self(expr, anno) = self;
let anno = folder.fold_annotation(anno)?;
Ok(Self(expr.fold_in(folder)?, anno))
impl<A: AstTypes, B: AstTypes> Foldable<A, B> for Anno<Expr<A>, A> {
type Out = Anno<Expr<B>, B>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
folder.fold_anno_expr(self)
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
let Self(expr, anno) = self;
Ok(Self(expr.children(folder)?, anno))
Ok(Anno(expr.children(folder)?, folder.fold_annotation(anno)?))
}
}
@@ -210,53 +204,66 @@ impl<T: Annotation + Foldable<A>, A: Annotation> Foldable<A> for Anno<T, A> {
// GENERIC IMPLEMENTATIONS ON COLLECTIONS //
//////////////////////////////////////////////
/// Maps the value in the box across `f()` without deallocating
fn box_try_map<T, E>(boxed: Box<T>, f: impl FnOnce(T) -> Result<T, E>) -> Result<Box<T>, E> {
// TODO: replace with Box::take when it stabilizes.
let rawbox = Box::into_raw(boxed);
// Maps the value in the box across `f()` without deallocating
// fn box_try_map<T, E>(boxed: Box<T>, f: impl FnOnce(T) -> Result<T, E>) -> Result<Box<T>, 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<MaybeUninit<T>>.
// Safety: MaybeUninit<T> has the same size and alignment as T.
let (value, rawbox) = (unsafe { rawbox.read() }, unsafe {
Box::from_raw(rawbox.cast::<MaybeUninit<T>>())
});
// // 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<MaybeUninit<T>>.
// // Safety: MaybeUninit<T> has the same size and alignment as T.
// let (value, rawbox) = (unsafe { rawbox.read() }, unsafe {
// Box::from_raw(rawbox.cast::<MaybeUninit<T>>())
// });
// rawbox is reinitialized with f(value)
Ok(Box::write(rawbox, f(value)?))
}
// // rawbox is reinitialized with f(value)
// Ok(Box::write(rawbox, f(value)?))
// }
impl<T: Foldable<A>, A: Annotation> Foldable<A> for Box<T> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
box_try_map(self, |t| t.fold_in(folder))
impl<T, A, B> Foldable<A, B> for Box<T>
where
T: Foldable<A, B>,
A: AstTypes,
B: AstTypes,
{
type Out = Box<T::Out>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
let value = *self;
Ok(Box::new(value.fold_in(folder)?))
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
box_try_map(self, |t| t.children(folder))
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
let value = *self;
Ok(Box::new(value.children(folder)?))
}
}
impl<T: Foldable<A>, A: Annotation> Foldable<A> for Vec<T> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
self.into_iter().map(|e| e.fold_in(folder)).collect()
impl<T: Foldable<A, B>, A: AstTypes, B: AstTypes> Foldable<A, B> for Vec<T> {
type Out = Vec<T::Out>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
self.children(folder)
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
// TODO: is this correct for generic data structures?
self.into_iter().map(|e| e.fold_in(folder)).collect()
}
}
impl<T: Foldable<A>, A: Annotation> Foldable<A> for Option<T> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
impl<T: Foldable<A, B>, A: AstTypes, B: AstTypes> Foldable<A, B> for Option<T> {
type Out = Option<T::Out>;
fn fold_in<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
Ok(match self {
Self::Some(value) => Some(value.fold_in(folder)?),
Self::None => None,
})
}
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
fn children<F: Fold<A, B> + ?Sized>(self, folder: &mut F) -> Result<Self::Out, F::Error> {
Ok(match self {
Self::Some(value) => Some(value.children(folder)?),
Self::None => None,

View File

@@ -5,18 +5,18 @@ use std::collections::HashMap;
/// Stores a substitution from meta-identifiers to values
#[derive(Clone, Debug)]
pub struct Subst<A: Annotation> {
pub exp: HashMap<String, Expr<A>>,
pub pat: HashMap<String, Pat<A>>,
pub struct Subst<A: AstTypes> {
pub exp: HashMap<A::MacroId, Expr<A>>,
pub pat: HashMap<A::MacroId, Pat<A>>,
}
impl<A: Annotation> Default for Subst<A> {
impl<A: AstTypes> Default for Subst<A> {
fn default() -> Self {
Self { exp: Default::default(), pat: Default::default() }
}
}
impl<A: Annotation> Subst<A> {
fn add_pat(&mut self, name: String, pat: &Pat<A>) -> bool {
impl<A: AstTypes> Subst<A> {
fn add_pat(&mut self, name: A::MacroId, pat: &Pat<A>) -> bool {
if self.exp.contains_key(&name) {
return false;
}
@@ -25,7 +25,7 @@ impl<A: Annotation> Subst<A> {
}
self.pat.insert(name, pat.clone()).is_none()
}
fn add_expr(&mut self, name: String, exp: &Expr<A>) -> bool {
fn add_expr(&mut self, name: A::MacroId, exp: &Expr<A>) -> bool {
if self.pat.contains_key(&name) {
return false;
}
@@ -36,11 +36,11 @@ impl<A: Annotation> Subst<A> {
}
}
pub trait Match<A: Annotation> {
pub trait Match<A: AstTypes> {
/// Applies a substitution rule from `pat` to `template` on `self`
fn apply_rule(&mut self, pat: &Self, template: &Self) -> bool
where Self: Sized + Clone {
let Some(sub) = self.construct(pat) else {
let Some(sub) = self.match_with(pat) else {
return false;
};
@@ -56,19 +56,14 @@ pub trait Match<A: Annotation> {
/// Implements recursive Subst-building for Self
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool;
/// Constructs a Subst
fn construct(&self, pat: &Self) -> Option<Subst<A>> {
/// Matches self against the provided pattern
fn match_with(&self, pat: &Self) -> Option<Subst<A>> {
let mut sub = Subst::default();
Match::recurse(&mut sub, pat, self).then_some(sub)
}
/// Matches self against the provided pattern
fn match_with(&self, pat: &Self, sub: &mut Subst<A>) -> bool {
Match::recurse(sub, pat, self)
}
}
impl<M: Match<A> + Annotation, A: Annotation> Match<A> for Anno<M, A> {
impl<M: Match<A> + Annotation, A: AstTypes> Match<A> for Anno<M, A> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
Match::recurse(sub, &pat.0, &expr.0)
}
@@ -78,7 +73,7 @@ impl<M: Match<A> + Annotation, A: Annotation> Match<A> for Anno<M, A> {
}
}
impl<A: Annotation> Match<A> for Bind<A> {
impl<A: AstTypes> Match<A> for Bind<A> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
let (Self(pat_kind, _, pat_pat, pat_expr), Self(expr_kind, _, expr_pat, expr_expr)) =
(pat, expr);
@@ -94,7 +89,7 @@ impl<A: Annotation> Match<A> for Bind<A> {
}
}
impl<A: Annotation> Match<A> for crate::ast::Make<A> {
impl<A: AstTypes> Match<A> for crate::ast::Make<A> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
let (Make(pat, pat_arms), Make(expr, expr_arms)) = (pat, expr);
Match::recurse(sub, pat, expr) && Match::recurse(sub, pat_arms, expr_arms)
@@ -107,12 +102,12 @@ impl<A: Annotation> Match<A> for crate::ast::Make<A> {
}
}
impl<A: Annotation> Match<A> for Expr<A> {
impl<A: AstTypes> Match<A> for Expr<A> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
match (pat, expr) {
(Expr::Omitted, Expr::Omitted) => true,
(Expr::Omitted, _) => false,
(Expr::MetId(name), _) if name == "_" => true,
(Expr::MetId(name), _) if name.as_ref() == "_" => true,
(Expr::MetId(name), _) => sub.add_expr(name.clone(), expr),
(Expr::Id(pat), Expr::Id(expr)) => pat == expr,
(Expr::Id(_), _) => false,
@@ -149,7 +144,7 @@ impl<A: Annotation> Match<A> for Expr<A> {
}
}
impl<A: Annotation> Match<A> for MakeArm<A> {
impl<A: AstTypes> Match<A> for MakeArm<A> {
// TODO: order-independent matching for MakeArm specifically.
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
pat.0 == expr.0 && Match::recurse(sub, &pat.1, &expr.1)
@@ -161,10 +156,10 @@ impl<A: Annotation> Match<A> for MakeArm<A> {
}
}
impl<A: Annotation> Match<A> for Pat<A> {
impl<A: AstTypes> Match<A> for Pat<A> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
match (pat, expr) {
(Pat::MetId(name), _) if name == "_" => true,
(Pat::MetId(name), _) if name.as_ref() == "_" => true,
(Pat::MetId(name), _) => sub.add_pat(name.clone(), expr),
(Pat::Ignore, Pat::Ignore) => true,
(Pat::Ignore, _) => false,
@@ -193,7 +188,7 @@ impl<A: Annotation> Match<A> for Pat<A> {
}
}
impl<A: Annotation> Match<A> for Op {
impl<A: AstTypes> Match<A> for Op {
fn recurse(_: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
pat == expr
}
@@ -201,7 +196,7 @@ impl<A: Annotation> Match<A> for Op {
fn apply(&mut self, _sub: &Subst<A>) {}
}
impl<A: Annotation, T: Match<A>> Match<A> for [T] {
impl<A: AstTypes, T: Match<A>> Match<A> for [T] {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
if pat.len() != expr.len() {
return false;
@@ -221,7 +216,7 @@ impl<A: Annotation, T: Match<A>> Match<A> for [T] {
}
}
impl<A: Annotation, T: Match<A>> Match<A> for Box<T> {
impl<A: AstTypes, T: Match<A>> Match<A> for Box<T> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
Match::recurse(sub, pat.as_ref(), expr.as_ref())
}
@@ -231,7 +226,7 @@ impl<A: Annotation, T: Match<A>> Match<A> for Box<T> {
}
}
impl<A: Annotation, T: Match<A>> Match<A> for Vec<T> {
impl<A: AstTypes, T: Match<A>> Match<A> for Vec<T> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
Match::recurse(sub, pat.as_slice(), expr.as_slice())
}
@@ -241,7 +236,7 @@ impl<A: Annotation, T: Match<A>> Match<A> for Vec<T> {
}
}
impl<A: Annotation, T: Match<A>> Match<A> for Option<T> {
impl<A: AstTypes, T: Match<A>> Match<A> for Option<T> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
match (pat, expr) {
(Some(pat), Some(expr)) => Match::recurse(sub, pat, expr),

80
src/ast/types.rs Normal file
View File

@@ -0,0 +1,80 @@
use std::fmt::Display;
use crate::{ast::AstTypes, fmt::FmtAdapter, intern::interned::Interned, span::Span};
/// The types emitted by the parser
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DefaultTypes;
impl AstTypes for DefaultTypes {
type Annotation = Span;
type Literal = Literal;
type MacroId = Symbol;
type Symbol = Symbol;
type Path = Path;
}
impl std::fmt::Display for DefaultTypes {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
/// An interned symbol (i.e. a name)
pub type Symbol = Interned<'static, str>;
/// A qualified identifier
///
/// TODO: qualify identifier
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Path {
// TODO: Identifier interning
pub parts: Vec<Symbol>,
// TODO: generic parameters
}
impl From<&str> for Path {
fn from(value: &str) -> Self {
Self { parts: vec![value.into()] }
}
}
impl From<Symbol> for Path {
fn from(value: Symbol) -> Self {
Self { parts: vec![value] }
}
}
impl std::fmt::Display for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { parts } = self;
f.list(parts, "::")
}
}
/// A literal value (boolean, character, integer, string)
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal {
/// A boolean literal: true | false
Bool(bool),
/// A character literal: 'a', '\u{1f988}'
Char(char),
/// An integer literal: 0, 123, 0x10
Int(u128, u32),
/// A string literal:
Str(String),
}
impl Display for Literal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bool(v) => v.fmt(f),
Self::Char(c) => write!(f, "'{}'", c.escape_debug()),
Self::Int(i, 2) => write!(f, "0b{i:b}"),
Self::Int(i, 8) => write!(f, "0o{i:o}"),
Self::Int(i, 16) => write!(f, "0x{i:x}"),
Self::Int(i, _) => i.fmt(f),
Self::Str(s) => write!(f, "\"{}\"", s.escape_debug()),
}
}
}

View File

@@ -3,56 +3,63 @@
#![allow(clippy::wildcard_imports, clippy::missing_errors_doc)]
use super::*;
pub trait Visit<'a> {
pub trait Visit<'a, A: AstTypes> {
type Error;
fn visit(&mut self, walk: &'a impl Walk<'a>) -> Result<(), Self::Error> {
fn visit<W: Walk<'a, A> + ?Sized>(&mut self, walk: &'a W) -> Result<(), Self::Error> {
walk.visit_in(self)
}
fn visit_expr<A: Annotation>(&mut self, expr: &'a Expr<A>) -> Result<(), Self::Error> {
expr.children(self)
}
fn visit_ident(&mut self, name: &'a str) -> Result<(), Self::Error> {
name.children(self)
}
fn visit_path(&mut self, path: &'a Path) -> Result<(), Self::Error> {
path.children(self)
}
fn visit_literal(&mut self, lit: &'a Literal) -> Result<(), Self::Error> {
lit.children(self)
}
fn visit_use(&mut self, item: &'a Use) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_pat<A: Annotation>(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_bind<A: Annotation>(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_make<A: Annotation>(&mut self, item: &'a Make<A>) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_makearm<A: Annotation>(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
item.children(self)
}
}
pub trait Walk<'a> {
#[inline]
fn children<V: Visit<'a> + ?Sized>(&'a self, _v: &mut V) -> Result<(), V::Error> {
fn visit_literal(&mut self, lit: &'a A::Literal) -> Result<(), Self::Error> {
let _ = lit;
Ok(())
}
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error>;
fn visit_macro_id(&mut self, name: &'a A::MacroId) -> Result<(), Self::Error> {
let _ = name;
Ok(())
}
fn visit_symbol(&mut self, name: &'a A::Symbol) -> Result<(), Self::Error> {
let _ = name;
Ok(())
}
fn visit_path(&mut self, path: &'a A::Path) -> Result<(), Self::Error> {
let _ = path;
Ok(())
}
fn visit_expr(&mut self, expr: &'a Expr<A>) -> Result<(), Self::Error> {
expr.children(self)
}
fn visit_use(&mut self, item: &'a Use<A>) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_pat(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_bind(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_make(&mut self, item: &'a Make<A>) -> Result<(), Self::Error> {
item.children(self)
}
fn visit_makearm(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
item.children(self)
}
}
impl<'a, A: Annotation> Walk<'a> for Expr<A> {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
pub trait Walk<'a, A: AstTypes> {
#[inline]
fn children<V: Visit<'a, A> + ?Sized>(&'a self, _v: &mut V) -> Result<(), V::Error> {
Ok(())
}
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error>;
}
impl<'a, A: AstTypes> Walk<'a, A> for Expr<A> {
fn children<V: Visit<'a, A> + ?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::Id(path) => v.visit_path(path),
Self::MetId(id) => v.visit_macro_id(id),
Self::Lit(lit) => v.visit_literal(lit),
Self::Use(u) => u.visit_in(v),
Self::Bind(bind) => bind.visit_in(v),
Self::Make(make) => make.visit_in(v),
@@ -61,40 +68,22 @@ impl<'a, A: Annotation> Walk<'a> for Expr<A> {
}
#[inline]
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_expr(self)
}
}
impl<'a> Walk<'a> for str {
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_ident(self)
}
}
impl<'a> Walk<'a> for Path {
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_path(self)
}
}
impl<'a> Walk<'a> for Literal {
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_literal(self)
}
}
impl<'a> Walk<'a> for Use {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
impl<'a, A: AstTypes> Walk<'a, A> for Use<A> {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
match self {
Self::Glob => Ok(()),
Self::Name(name) => name.visit_in(v),
Self::Name(name) => v.visit_symbol(name),
Self::Alias(name, alias) => {
name.visit_in(v)?;
alias.visit_in(v)
v.visit_symbol(name)?;
v.visit_symbol(alias)
}
Self::Path(name, rest) => {
name.visit_in(v)?;
v.visit_symbol(name)?;
rest.visit_in(v)
}
Self::Tree(items) => items.visit_in(v),
@@ -102,76 +91,76 @@ impl<'a> Walk<'a> for Use {
}
#[inline]
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_use(self)
}
}
impl<'a, A: Annotation> Walk<'a> for Pat<A> {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
impl<'a, A: AstTypes> Walk<'a, A> for Pat<A> {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
match self {
Self::Ignore | Self::Never => Ok(()),
Self::MetId(id) => id.visit_in(v),
Self::Name(name) => name.visit_in(v),
Self::MetId(id) => v.visit_macro_id(id),
Self::Name(name) => v.visit_symbol(name),
Self::Value(literal) => literal.visit_in(v),
Self::Op(_, pats) => pats.visit_in(v),
}
}
#[inline]
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_pat(self)
}
}
impl<'a, A: Annotation> Walk<'a> for Bind<A> {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
impl<'a, A: AstTypes> Walk<'a, A> for Bind<A> {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
let Self(_kind, gens, pat, exprs) = self;
gens.visit_in(v)?;
gens.iter().try_for_each(|g| v.visit_path(g))?;
pat.visit_in(v)?;
exprs.visit_in(v)
}
#[inline]
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_bind(self)
}
}
impl<'a, A: Annotation> Walk<'a> for Make<A> {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
impl<'a, A: AstTypes> Walk<'a, A> for Make<A> {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
let Self(expr, arms) = self;
expr.visit_in(v)?;
arms.visit_in(v)
}
#[inline]
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_make(self)
}
}
impl<'a, A: Annotation> Walk<'a> for MakeArm<A> {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
impl<'a, A: AstTypes> Walk<'a, A> for MakeArm<A> {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
let Self(name, expr) = self;
name.visit_in(v)?;
v.visit_symbol(name)?;
expr.visit_in(v)
}
#[inline]
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
v.visit_makearm(self)
}
}
impl<'a, T: Annotation + Walk<'a>, A: Annotation> Walk<'a> for Anno<T, A> {
impl<'a, T: Annotation + Walk<'a, A>, A: AstTypes> Walk<'a, A> for Anno<T, A> {
#[inline]
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
self.0.children(v)
}
#[inline]
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
self.0.visit_in(v)
}
}
@@ -180,15 +169,15 @@ impl<'a, T: Annotation + Walk<'a>, A: Annotation> Walk<'a> for Anno<T, A> {
// GENERIC IMPLEMENTATIONS ON COLLECTIONS //
//////////////////////////////////////////////
impl<'a, T: Walk<'a>> Walk<'a> for [T] {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
impl<'a, T: Walk<'a, A>, A: AstTypes> Walk<'a, A> for [T] {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
for item in self {
item.visit_in(v)?;
}
Ok(())
}
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
for item in self {
item.visit_in(v)?;
}
@@ -196,24 +185,24 @@ impl<'a, T: Walk<'a>> Walk<'a> for [T] {
}
}
impl<'a, T: Walk<'a>> Walk<'a> for Vec<T> {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
impl<'a, T: Walk<'a, A>, A: AstTypes> Walk<'a, A> for Vec<T> {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
self.as_slice().children(v)
}
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
self.as_slice().visit_in(v)
}
}
impl<'a, T: Walk<'a>> Walk<'a> for Option<T> {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
impl<'a, T: Walk<'a, A>, A: AstTypes> Walk<'a, A> for Option<T> {
fn children<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
match self {
Some(t) => t.children(v),
_ => Ok(()),
}
}
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
fn visit_in<V: Visit<'a, A> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
match self {
Some(t) => t.visit_in(v),
_ => Ok(()),

View File

@@ -1,7 +1,7 @@
//! Tests the lexer\
use doughlang::{
ast::{
Anno, Annotation, Bind, Expr, Literal, Pat, Path, Use,
Anno, Annotation, Bind, Expr, Pat, Use,
macro_matcher::{Match, Subst},
},
lexer::{EOF, LexError, Lexer},
@@ -44,8 +44,6 @@ enum ParseMode {
Expr,
Pat,
Bind,
Path,
Literal,
Use,
Tokens,
}
@@ -55,8 +53,6 @@ impl From<&str> for ParseMode {
"expr" => Self::Expr,
"pat" => Self::Pat,
"bind" => Self::Bind,
"path" => Self::Path,
"literal" => Self::Literal,
"use" => Self::Use,
"tokens" => Self::Tokens,
_ => Default::default(),
@@ -69,8 +65,6 @@ impl ParseMode {
Self::Expr => parse::<'a, Expr>,
Self::Pat => parse::<'a, Pat>,
Self::Bind => parse::<'a, Bind>,
Self::Path => parse::<'a, Path>,
Self::Literal => parse::<'a, Literal>,
Self::Use => parse::<'a, Use>,
Self::Tokens => tokens::<'a, dyn Parse<'a, Prec = ()>>,
}
@@ -86,7 +80,7 @@ fn main() -> Result<(), Box<dyn Error>> {
"" => Ok(Response::Continue),
"exit" => Ok(Response::Break),
"help" => {
println!("Parsing: {parsing:?} (expr, pat, bind, path, literal, use, tokens)");
println!("Parsing: {parsing:?} (expr, pat, bind, use, tokens)");
println!("Verbose: {verbose:?} (pretty, debug, quiet)");
Ok(Response::Deny)
}
@@ -100,7 +94,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
Ok(Response::Accept)
}
line @ ("tokens" | "expr" | "pat" | "bind" | "path" | "literal" | "use") => {
line @ ("tokens" | "expr" | "pat" | "bind" | "use") => {
parsing = ParseMode::from(line);
println!("Parse mode set to '{parsing:?}'");
Ok(Response::Accept)
@@ -144,7 +138,7 @@ fn subst() -> Result<(), Box<dyn Error>> {
};
if p.next_if(TKind::Arrow).is_err() {
let Some(Subst { exp, pat }) = exp.construct(&pat) else {
let Some(Subst { exp, pat }) = exp.match_with(&pat) else {
println!("Match failed: {exp} <- {pat}");
continue;
};
@@ -203,7 +197,7 @@ fn tokens<'t, T: Parse<'t> + ?Sized>(document: &'t str, verbose: Verbosity) {
fn parse<'t, T: Parse<'t> + Annotation>(document: &'t str, verbose: Verbosity) {
let mut parser = Parser::new(Lexer::new(document));
for idx in 0.. {
match (parser.parse::<Anno<T, Span>>(T::Prec::default()), verbose) {
match (parser.parse::<Anno<T, _>>(T::Prec::default()), verbose) {
(Err(e @ ParseError::EOF(s)), _) if s.tail == document.len() as _ => {
println!(
"\x1b[92m{e} (total {} byte{}, {idx} expression{})\x1b[0m",

354
src/intern.rs Normal file
View File

@@ -0,0 +1,354 @@
//! Interners for [strings](string_interner) and non-[Drop] [types](dropless_interner).
//!
//! An object is [Interned][1] if it is allocated within one of the interners
//! in this module. [Interned][1] values have referential equality semantics, and
//! [Deref](std::ops::Deref) to the value within their respective intern pool.
//!
//! This means, of course, that the same value interned in two different pools will be
//! considered *not equal* by [Eq] and [Hash](std::hash::Hash).
//!
//! [1]: interned::Interned
pub mod interned {
//! An [Interned] reference asserts its wrapped value has referential equality.
use super::string_interner::StringInterner;
use std::{
fmt::{Debug, Display},
hash::Hash,
ops::Deref,
};
/// An [Interned] value is one that is *referentially comparable*.
/// That is, the interned value is unique in memory, simplifying
/// its equality and hashing implementation.
///
/// Comparing [Interned] values via [PartialOrd] or [Ord] will still
/// dereference to the wrapped pointers, and as such, may produce
/// results inconsistent with [PartialEq] or [Eq].
#[repr(transparent)]
pub struct Interned<'a, T: ?Sized> {
value: &'a T,
}
impl<'a, T: ?Sized> Interned<'a, T> {
/// Gets the internal value as a pointer
pub fn as_ptr(interned: &Self) -> *const T {
interned.value
}
/// Gets the internal value as a reference with the interner's lifetime
pub fn to_ref(&self) -> &'a T {
self.value
}
}
impl<'a, T: ?Sized> Interned<'a, T> {
pub(super) fn new(value: &'a T) -> Self {
Self { value }
}
}
impl<T: ?Sized> Copy for Interned<'_, T> {}
impl<T: ?Sized> Clone for Interned<'_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: ?Sized + Debug> Debug for Interned<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("~")?;
self.value.fmt(f)
}
}
impl<T: ?Sized> Deref for Interned<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<T: ?Sized> PartialEq for Interned<'_, T> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.value, other.value)
}
}
impl<T: ?Sized> Eq for Interned<'_, T> {}
impl<T: ?Sized> Hash for Interned<'_, T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Self::as_ptr(self).hash(state)
}
}
impl<T: ?Sized + Display> Display for Interned<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
impl<'a> AsRef<str> for Interned<'a, str> {
fn as_ref(&self) -> &str {
todo!()
}
}
impl From<&str> for Interned<'static, str> {
/// Types which implement [`AsRef<str>`] will be stored in the global [StringInterner]
fn from(value: &str) -> Self {
from_str(value)
}
}
fn from_str(value: &str) -> Interned<'static, str> {
let global_interner = StringInterner::global();
global_interner.get_or_insert(value)
}
}
pub mod string_interner {
//! A [StringInterner] hands out [Interned] copies of each unique string given to it.
use super::interned::Interned;
use cl_arena::dropless_arena::DroplessArena;
use std::{
collections::HashSet,
sync::{OnceLock, RwLock},
};
/// A string interner hands out [Interned] copies of each unique string given to it.
#[derive(Default)]
pub struct StringInterner<'a> {
arena: DroplessArena<'a>,
keys: RwLock<HashSet<&'a str>>,
}
impl StringInterner<'static> {
/// Gets a reference to a global string interner whose [Interned] strings are `'static`
pub fn global() -> &'static Self {
static GLOBAL_INTERNER: OnceLock<StringInterner<'static>> = OnceLock::new();
// SAFETY: The RwLock within the interner's `keys` protects the arena
// from being modified concurrently.
GLOBAL_INTERNER.get_or_init(|| StringInterner {
arena: DroplessArena::new(),
keys: Default::default(),
})
}
}
impl<'a> StringInterner<'a> {
/// Creates a new [StringInterner] backed by the provided [DroplessArena]
pub fn new(arena: DroplessArena<'a>) -> Self {
Self { arena, keys: RwLock::new(HashSet::new()) }
}
/// Returns an [Interned] copy of the given string,
/// allocating a new one if it doesn't already exist.
///
/// # Blocks
/// This function blocks when the interner is held by another thread.
pub fn get_or_insert(&'a self, value: &str) -> Interned<'a, str> {
let Self { arena, keys } = self;
// Safety: Holding this write guard for the entire duration of this
// function enforces a safety invariant. See StringInterner::global.
let mut keys = keys.write().expect("should not be poisoned");
Interned::new(match keys.get(value) {
Some(value) => value,
None => {
let value = match value {
"" => "", // Arena will panic if passed an empty string
_ => arena.alloc_str(value),
};
keys.insert(value);
value
}
})
}
/// Gets a reference to the interned copy of the given value, if it exists
/// # Blocks
/// This function blocks when the interner is held by another thread.
pub fn get(&'a self, value: &str) -> Option<Interned<'a, str>> {
let keys = self.keys.read().expect("should not be poisoned");
keys.get(value).copied().map(Interned::new)
}
}
impl std::fmt::Debug for StringInterner<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Interner")
.field("keys", &self.keys)
.finish()
}
}
impl std::fmt::Display for StringInterner<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Ok(keys) = self.keys.read() else {
return write!(f, "Could not lock StringInterner key map.");
};
let mut keys: Vec<_> = keys.iter().collect();
keys.sort();
writeln!(f, "Keys:")?;
for (idx, key) in keys.iter().enumerate() {
writeln!(f, "{idx}:\t\"{key}\"")?
}
writeln!(f, "Count: {}", keys.len())?;
Ok(())
}
}
// # Safety:
// This is fine because StringInterner::get_or_insert(v) holds a RwLock
// for its entire duration, and doesn't touch the non-(Send+Sync) arena
// unless the lock is held by a write guard.
unsafe impl Send for StringInterner<'_> {}
unsafe impl Sync for StringInterner<'_> {}
#[cfg(test)]
mod tests {
use super::StringInterner;
macro_rules! ptr_eq {
($a: expr, $b: expr $(, $($t:tt)*)?) => {
assert_eq!(std::ptr::addr_of!($a), std::ptr::addr_of!($b) $(, $($t)*)?)
};
}
macro_rules! ptr_ne {
($a: expr, $b: expr $(, $($t:tt)*)?) => {
assert_ne!(std::ptr::addr_of!($a), std::ptr::addr_of!($b) $(, $($t)*)?)
};
}
#[test]
fn empties_is_unique() {
let interner = StringInterner::global();
let empty = interner.get_or_insert("");
let empty2 = interner.get_or_insert("");
ptr_eq!(*empty, *empty2);
}
#[test]
fn non_empty_is_unique() {
let interner = StringInterner::global();
let nonempty1 = interner.get_or_insert("not empty!");
let nonempty2 = interner.get_or_insert("not empty!");
let different = interner.get_or_insert("different!");
ptr_eq!(*nonempty1, *nonempty2);
ptr_ne!(*nonempty1, *different);
}
}
}
pub mod dropless_interner {
//! A [DroplessInterner] hands out [Interned] references for arbitrary types.
//!
//! Note: It is a *logic error* to modify the returned reference via interior mutability
//! in a way that changes the values produced by [Eq] and [Hash].
//!
//! See the standard library [HashSet] for more details.
//!
//! ```rust
//! use doughlang::intern::dropless_interner::DroplessInterner;
//! use cl_arena::dropless_arena::DroplessArena;
//!
//! let da = DroplessArena::new();
//! let di: DroplessInterner<'_, ()> = DroplessInterner::new(da);
//! let unit1 = di.get_or_insert(());
//! let unit2 = di.get_or_insert(());
//! let unit3 = di.get(&()).unwrap();
//! assert_eq!(unit1, unit3);
//! assert_eq!(unit2, unit1);
//! assert_eq!(unit3, unit2);
//! ```
//!
//! ```rust
//! use doughlang::intern::dropless_interner::DroplessInterner;
//! use cl_arena::dropless_arena::DroplessArena;
//!
//! let da = DroplessArena::new();
//! let di: DroplessInterner<'_, [i32; 10]> = DroplessInterner::new(da);
//! let arr1 = di.get_or_insert([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
//! let arr2 = di.get_or_insert([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
//! let arr3 = di.get(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).unwrap();
//! assert_eq!(arr1, arr3);
//! assert_eq!(arr2, arr1);
//! assert_eq!(arr3, arr2);
//!
//! let arr4 = di.get_or_insert([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
//! assert_ne!(arr1, arr4);
//! ```
use super::interned::Interned;
use cl_arena::dropless_arena::DroplessArena;
use std::{collections::HashSet, hash::Hash, sync::RwLock};
/// A [DroplessInterner] hands out [Interned] references for arbitrary types.
///
/// See the [module-level documentation](self) for more information.
pub struct DroplessInterner<'a, T: Eq + Hash> {
arena: DroplessArena<'a>,
keys: RwLock<HashSet<&'a T>>,
}
impl<'a, T: Eq + Hash> Default for DroplessInterner<'a, T> {
fn default() -> Self {
Self { arena: Default::default(), keys: Default::default() }
}
}
impl<'a, T: Eq + Hash> DroplessInterner<'a, T> {
/// Creates a new [DroplessInterner] backed by the provided [TypedArena]
///
/// # Panics
/// Panics if T [needs drop](std::mem::needs_drop)
pub fn new(arena: DroplessArena<'a>) -> Self {
Self { arena, keys: RwLock::new(HashSet::new()) }
}
/// Converts the given value into an [Interned] value.
///
/// # Blocks
/// This function blocks when the interner is held by another thread.
pub fn get_or_insert(&'a self, value: T) -> Interned<'a, T> {
let Self { arena, keys } = self;
// Safety: Locking the keyset for the entire duration of this function
// enforces a safety invariant when the interner is stored in a global.
let mut keys = keys.write().expect("should not be poisoned");
Interned::new(match keys.get(&value) {
Some(value) => value,
None => {
let value = if std::mem::size_of::<T>() == 0 {
Box::leak(Box::new(value))
} else {
arena.alloc(value)
};
keys.insert(value);
value
}
})
}
/// Returns the [Interned] copy of the given value, if one already exists
///
/// # Blocks
/// This function blocks when the interner is being written to by another thread.
pub fn get(&self, value: &T) -> Option<Interned<'a, T>> {
let keys = self.keys.read().expect("should not be poisoned");
keys.get(value).copied().map(Interned::new)
}
}
/// # Safety
/// This should be safe because references yielded by
/// [get_or_insert](DroplessInterner::get_or_insert) are unique, and the function uses
/// the [RwLock] around the [HashSet] to ensure mutual exclusion
unsafe impl<'a, T: Eq + Hash + Send> Send for DroplessInterner<'a, T> where &'a T: Send {}
unsafe impl<T: Eq + Hash + Send + Sync> Sync for DroplessInterner<'_, T> {}
}

View File

@@ -4,6 +4,8 @@
pub mod fmt;
pub mod intern;
pub mod span;
pub mod token;

View File

@@ -1,7 +1,7 @@
//! The parser takes a stream of [`Token`]s from the [`Lexer`], and turns them into [`crate::ast`]
//! nodes.
use crate::{
ast::*,
ast::{types::{Literal, Path}, *},
lexer::{LexError, LexFailure, Lexer},
span::Span,
token::{Lexeme, TKind, Token},
@@ -217,7 +217,12 @@ impl<'t> Parse<'t> for Path {
parts.push("".into()); // the "root"
}
while let Ok(id) = p.next_if(TKind::Identifier)? {
parts.push(id.lexeme.string().expect("Identifier should have String"));
parts.push(
id.lexeme
.str()
.expect("Identifier should have String")
.into(),
);
if let None | Some(Err(_)) = p.next_if(TKind::ColonColon).allow_eof()? {
break;
}
@@ -254,7 +259,7 @@ impl<'t> Parse<'t> for Use {
Ok(match tok.kind {
TKind::Star => p.then(Use::Glob),
TKind::Identifier => {
let name = tok.lexeme.string().expect("should have String");
let name = tok.lexeme.str().expect("should have String").into();
match p.peek().map(Token::kind).allow_eof()? {
Some(TKind::ColonColon) => Use::Path(name, p.consume().parse(())?),
Some(TKind::As) => Use::Alias(
@@ -263,8 +268,9 @@ impl<'t> Parse<'t> for Use {
.next_if(TKind::Identifier)?
.map_err(|e| ParseError::Expected(TKind::Identifier, e, p.span()))?
.lexeme
.string()
.expect("Identifier should have string"),
.str()
.expect("Identifier should have string")
.into(),
),
_ => Use::Name(name),
}

View File

@@ -1,6 +1,6 @@
use super::{PResult, PResultExt, Parse, ParseError, Parser, no_eof, pat::Prec as PPrec};
use crate::{
ast::*,
ast::{types::Literal, *},
token::{TKind, Token},
};
use std::iter;
@@ -223,14 +223,14 @@ impl<'t> Parse<'t> for Expr {
Ps::End => Err(ParseError::NotPrefix(kind, span))?,
Ps::Id => Expr::Id(p.parse(())?),
Ps::Mid => Expr::MetId(p.consume().next()?.lexeme.to_string()),
Ps::Mid => Expr::MetId(p.consume().next()?.lexeme.to_string().as_str().into()),
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)?;
let next = p.parse(level.max(Prec::Do.next()))?;
Expr::Op(Op::Meta, vec![comment, next])
}
Ps::For => parse_for(p, ())?,
@@ -262,7 +262,7 @@ impl<'t> Parse<'t> for Expr {
.expect(TKind::LBrack)?
.opt(MIN, TKind::RBrack)?
.unwrap_or_else(|| Expr::Op(Op::Tuple, vec![]).anno(span)),
p.parse(level)?,
p.parse(level.max(Prec::Do.next()))?,
],
),
Ps::Op(Op::Block) => Expr::Op(
@@ -525,7 +525,10 @@ impl<'t> Parse<'t> for MakeArm {
.next_if(TKind::Identifier)?
.map_err(|tk| ParseError::Expected(TKind::Identifier, tk, p.span()))?;
Ok(MakeArm(
name.lexeme.string().expect("Identifier should have String"),
name.lexeme
.str()
.expect("Identifier should have String")
.into(),
p.opt_if(Prec::Body.value(), TKind::Colon)?,
))
}

View File

@@ -1,6 +1,6 @@
use super::{PResult, PResultExt, Parse, ParseError, Parser, expr::Prec as ExPrec};
use crate::{
ast::*,
ast::{types::Path, *},
token::{TKind, Token},
};
@@ -128,7 +128,7 @@ impl<'t> Parse<'t> for Pat {
Prefix::Consume => p.consume().parse(level)?,
Prefix::Underscore => p.consume().then(Pat::Ignore),
Prefix::Never => p.consume().then(Pat::Never),
Prefix::MetId => Pat::MetId(p.consume().next()?.lexeme.to_string()),
Prefix::MetId => Pat::MetId(p.consume().next()?.lexeme.to_string().as_str().into()),
Prefix::Constant => Pat::Value(p.parse(ExPrec::Unary.value())?),
Prefix::Array => parse_array_pat(p)?,
Prefix::Id => {