diff --git a/Cargo.lock b/Cargo.lock index 2ec63d1..392f5c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index bc94a92..100507e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2024" [dependencies] repline = "*" +cl-arena = "*" unicode-ident = "1.0.12" diff --git a/examples/7fold.rs b/examples/7fold.rs index fbb2872..58fda04 100644 --- a/examples/7fold.rs +++ b/examples/7fold.rs @@ -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> { #[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(&mut self, expr: &'a Expr) -> 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) -> Result<(), Self::Error> { + self.uses += 1; + item.children(self) + } + fn visit_expr(&mut self, expr: &'a Expr) -> 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(&mut self, item: &'a Pat) -> Result<(), Self::Error> { + fn visit_pat(&mut self, item: &'a Pat) -> Result<(), Self::Error> { self.patterns += 1; item.children(self) } - fn visit_bind(&mut self, item: &'a Bind) -> Result<(), Self::Error> { + fn visit_bind(&mut self, item: &'a Bind) -> Result<(), Self::Error> { self.binds += 1; item.children(self) } - fn visit_make(&mut self, item: &'a Make) -> Result<(), Self::Error> { + fn visit_make(&mut self, item: &'a Make) -> Result<(), Self::Error> { self.makes += 1; item.children(self) } - fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { + fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { self.makearms += 1; item.children(self) } @@ -153,10 +160,26 @@ impl<'a> Visit<'a> for CountNodes { struct Sevenfold; -impl Fold for Sevenfold { +impl Fold for Sevenfold { type Error = (); + fn fold_annotation(&mut self, anno: Span) -> Result { + Ok(anno) + } + fn fold_literal(&mut self, _lit: Literal) -> Result { Ok(Literal::Int(7, 10)) } + + fn fold_macro_id(&mut self, name: Symbol) -> Result { + Ok(name) + } + + fn fold_symbol(&mut self, name: Symbol) -> Result { + Ok(name) + } + + fn fold_path(&mut self, path: Path) -> Result { + Ok(path) + } } diff --git a/examples/to-lisp.rs b/examples/to-lisp.rs index 95fa4fe..a2a662f 100644 --- a/examples/to-lisp.rs +++ b/examples/to-lisp.rs @@ -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(&mut self, expr: &'a Expr) -> 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(&mut self, item: &'a Pat) -> 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(&mut self, item: &'a Bind) -> 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(&mut self, item: &'a Make) -> Result<(), Self::Error> { + fn visit_make(&mut self, item: &'a Make) -> Result<(), Self::Error> { item.children(self) } - fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { + fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { item.children(self) } } diff --git a/examples/weight.rs b/examples/weight.rs index f75fba8..1898efe 100644 --- a/examples/weight.rs +++ b/examples/weight.rs @@ -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(&mut self, item: &'a Expr) -> Result<(), Self::Error> { + fn visit_expr(&mut self, item: &'a Expr) -> 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) -> 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) -> 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) -> Result<(), Self::Error> { self.size += size_of_val(item); item.children(self) } - fn visit_pat(&mut self, item: &'a Pat) -> Result<(), Self::Error> { + fn visit_make(&mut self, item: &'a Make) -> Result<(), Self::Error> { self.size += size_of_val(item); item.children(self) } - fn visit_bind(&mut self, item: &'a Bind) -> Result<(), Self::Error> { - self.size += size_of_val(item); - item.children(self) - } - - fn visit_make(&mut self, item: &'a Make) -> Result<(), Self::Error> { - self.size += size_of_val(item); - item.children(self) - } - - fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { + fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { self.size += size_of_val(item); item.children(self) } diff --git a/src/ast.rs b/src/ast.rs index d0b90a0..6832a1d 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -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 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] + type MacroId: Annotation + Hash + AsRef; + + /// 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(pub T, pub A); +pub struct Anno(pub T, pub A::Annotation); + +impl std::fmt::Debug for Anno { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(&self.1, f)?; + f.write_str(": ")?; + ::fmt(&self.0, f) + } +} /// Expressions: The beating heart of Dough. /// @@ -31,17 +64,17 @@ pub struct Anno(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 { +pub enum Expr { /// 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), /// `let Pat::NoTopAlt (= expr (else expr)?)?` | /// `(fn | mod | impl) Pat::Fn Expr` Bind(Box>), @@ -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, - // TODO: generic parameters -} +impl Expr { + pub const fn anno(self, annotation: A::Annotation) -> Anno, 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, 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, * } - Tree(Vec), + 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, 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( +pub struct Bind( pub BindOp, - pub Vec, + pub Vec, pub Pat, pub Vec, 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(pub Anno, A>, pub Vec>); - -/// A single "arm" of a make expression -/// ```text -/// Identifier (':' Expr)? -/// ``` -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MakeArm(pub String, pub Option, 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 { +pub enum Pat { /// 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, A>>), /// Matches a compound pattern @@ -348,64 +372,7 @@ pub enum PatOp { Alt, } -impl std::fmt::Debug for Anno { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(&self.1, f)?; - f.write_str(": ")?; - ::fmt(&self.0, f) - } -} -impl Default for Expr { - fn default() -> Self { - Self::Op(Op::Tuple, vec![]) - } -} - -impl Expr { - pub const fn anno(self, annotation: A) -> Anno, A> { - Anno(self, annotation) - } - - pub fn and_do(self, annotation: A, other: Anno, 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, A>])> { - match self { - Expr::Op(op, args) => Some((*op, args.as_slice())), - _ => None, - } - } -} - -impl Pat { +impl Pat { pub fn to_tuple(self) -> Self { match self { Self::Op(PatOp::Tuple, _) => self, @@ -414,8 +381,31 @@ impl Pat { } } -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 { + /// "*" + Glob, + /// Identifier + Name(A::Symbol), + /// Identifier as Identifier + Alias(A::Symbol, A::Symbol), + /// Identifier :: Use + Path(A::Symbol, Box>), + /// { Use, * } + Tree(Vec>), } + +/// A make (constructor) expression +/// ```ignore +/// Expr { (Ident (: Expr)?),* } +/// ``` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Make(pub Anno, A>, pub Vec>); + +/// A single "arm" of a make expression +/// ```text +/// Identifier (':' Expr)? +/// ``` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MakeArm(pub A::Symbol, pub Option, A>>); diff --git a/src/ast/display.rs b/src/ast/display.rs index 37e9b63..97bd2a5 100644 --- a/src/ast/display.rs +++ b/src/ast/display.rs @@ -2,13 +2,13 @@ use super::*; use crate::fmt::FmtAdapter; use std::{fmt::Display, format_args as fmt}; -impl Display for Anno { +impl Display for Anno { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } -impl Display for Expr { +impl Display for Expr { 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 Display for Use { 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 Display for Bind { +impl Display for Bind { 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 Display for Make { +impl Display for Make { 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 Display for MakeArm { +impl Display for MakeArm { 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 Display for MakeArm { } } -impl Display for Pat { +impl Display for Pat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Ignore => "_".fmt(f), diff --git a/src/ast/fold.rs b/src/ast/fold.rs index 6486dd9..6d6ea40 100644 --- a/src/ast/fold.rs +++ b/src/ast/fold.rs @@ -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 { +pub trait Fold { type Error; - fn fold_annotation(&mut self, anno: A) -> Result { - 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; - /// Consumes an [`Expr`], possibly transforms it, and produces a replacement [`Expr`] - fn fold_expr(&mut self, expr: Expr) -> Result, 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; + + /// Consumes a `Symbol` in A, possibly transforms it, and produces a replacement `Symbol` in B + fn fold_symbol(&mut self, name: From::Symbol) -> Result; + + /// Consumes a `Path` in A, possibly transforms it, and produces a replacement `Path` in B + fn fold_path(&mut self, path: From::Path) -> Result; + + /// Consumes a `Literal` in A, possibly transforms it, and produces a replacement `Literal` in B + fn fold_literal(&mut self, lit: From::Literal) -> Result; + + /// Folds an annotated expression, so the expression and annotation can be seen at once. + fn fold_anno_expr( + &mut self, + expr: Anno, From>, + ) -> Result, 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 { - 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 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 [`Use`], possibly transforms it, and produces a replacement [`Use`] - fn fold_use(&mut self, item: Use) -> Result { + fn fold_use(&mut self, item: Use) -> Result, Self::Error> { item.children(self) } /// Consumes a [`Pat`], possibly transforms it, and produces a replacement [`Pat`] - fn fold_pat(&mut self, pat: Pat) -> Result, Self::Error> { + fn fold_pat(&mut self, pat: Pat) -> Result, Self::Error> { pat.children(self) } /// Consumes a [`Bind`], possibly transforms it, and produces a replacement [`Bind`] - fn fold_bind(&mut self, bind: Bind) -> Result, Self::Error> { + 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> { + 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> { + fn fold_makearm(&mut self, arm: MakeArm) -> Result, Self::Error> { arm.children(self) } } -pub trait Foldable: Sized { +pub trait Foldable: Sized { + /// The return type of the associated [Fold] function + type Out; + /// Calls `Self`'s appropriate [Folder] function(s) - fn fold_in + ?Sized>(self, folder: &mut F) -> Result; + 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) - } + fn children + ?Sized>(self, folder: &mut F) -> Result; } -impl Foldable for Expr { - fn fold_in + ?Sized>(self, folder: &mut F) -> Result { +impl Foldable for Expr { + type Out = Expr; + + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { folder.fold_expr(self) } - fn children + ?Sized>(self, folder: &mut F) -> Result { + 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)?), + 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 Foldable for String { - fn fold_in + ?Sized>(self, folder: &mut F) -> Result { - folder.fold_ident(self) - } -} +impl Foldable for Use { + type Out = Use; -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 { + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { folder.fold_use(self) } - fn children + ?Sized>(self, folder: &mut F) -> Result { + fn children + ?Sized>(self, folder: &mut F) -> Result { 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 Foldable for Pat { - fn fold_in + ?Sized>(self, folder: &mut F) -> Result { +impl Foldable for Pat { + type Out = Pat; + + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { folder.fold_pat(self) } - fn children + ?Sized>(self, folder: &mut F) -> Result { + 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::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 Foldable for Bind { - fn fold_in + ?Sized>(self, folder: &mut F) -> Result { +impl Foldable for Bind { + type Out = Bind; + + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { folder.fold_bind(self) } - fn children + ?Sized>(self, folder: &mut F) -> Result { + fn children + ?Sized>(self, folder: &mut F) -> Result { 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::>()?, pat.fold_in(folder)?, exprs.fold_in(folder)?, )) } } -impl Foldable for Make { - fn fold_in + ?Sized>(self, folder: &mut F) -> Result { +impl Foldable for Make { + type Out = Make; + + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { folder.fold_make(self) } - fn children + ?Sized>(self, folder: &mut F) -> Result { + fn children + ?Sized>(self, folder: &mut F) -> Result { 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 Foldable for MakeArm { - fn fold_in + ?Sized>(self, folder: &mut F) -> Result { +impl Foldable for MakeArm { + type Out = MakeArm; + + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { folder.fold_makearm(self) } - fn children + ?Sized>(self, folder: &mut F) -> Result { + fn children + ?Sized>(self, folder: &mut F) -> Result { let Self(name, expr) = self; - Ok(Self(name, expr.fold_in(folder)?)) + Ok(MakeArm(folder.fold_symbol(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)) +impl Foldable for Anno, A> { + type Out = Anno, B>; + + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + folder.fold_anno_expr(self) } - fn children + ?Sized>(self, folder: &mut F) -> Result { + fn children + ?Sized>(self, folder: &mut F) -> Result { 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, A: Annotation> Foldable for 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); +// 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::>()) - }); +// // 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)?)) -} +// // 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)) +impl Foldable for Box +where + T: Foldable, + A: AstTypes, + B: AstTypes, +{ + type Out = Box; + + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + let value = *self; + Ok(Box::new(value.fold_in(folder)?)) } - fn children + ?Sized>(self, folder: &mut F) -> Result { - box_try_map(self, |t| t.children(folder)) + fn children + ?Sized>(self, folder: &mut F) -> Result { + let value = *self; + Ok(Box::new(value.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() +impl, A: AstTypes, B: AstTypes> Foldable for Vec { + type Out = Vec; + + fn fold_in + ?Sized>(self, folder: &mut F) -> Result { + self.children(folder) } - fn children + ?Sized>(self, folder: &mut F) -> Result { + 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 { +impl, A: AstTypes, B: AstTypes> Foldable for Option { + type Out = 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 { + 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/macro_matcher.rs b/src/ast/macro_matcher.rs index 66bf5d1..13ca154 100644 --- a/src/ast/macro_matcher.rs +++ b/src/ast/macro_matcher.rs @@ -5,18 +5,18 @@ use std::collections::HashMap; /// Stores a substitution from meta-identifiers to values #[derive(Clone, Debug)] -pub struct Subst { - pub exp: HashMap>, - pub pat: HashMap>, +pub struct Subst { + pub exp: HashMap>, + pub pat: HashMap>, } -impl Default for Subst { +impl Default for Subst { fn default() -> Self { Self { exp: Default::default(), pat: Default::default() } } } -impl Subst { - fn add_pat(&mut self, name: String, pat: &Pat) -> bool { +impl Subst { + fn add_pat(&mut self, name: A::MacroId, pat: &Pat) -> bool { if self.exp.contains_key(&name) { return false; } @@ -25,7 +25,7 @@ impl Subst { } self.pat.insert(name, pat.clone()).is_none() } - fn add_expr(&mut self, name: String, exp: &Expr) -> bool { + fn add_expr(&mut self, name: A::MacroId, exp: &Expr) -> bool { if self.pat.contains_key(&name) { return false; } @@ -36,11 +36,11 @@ impl Subst { } } -pub trait Match { +pub trait Match { /// 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 { /// Implements recursive Subst-building for Self fn recurse(sub: &mut Subst, pat: &Self, expr: &Self) -> bool; - /// Constructs a Subst - fn construct(&self, pat: &Self) -> Option> { + /// Matches self against the provided pattern + fn match_with(&self, pat: &Self) -> Option> { 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) -> bool { - Match::recurse(sub, pat, self) - } } -impl + Annotation, A: Annotation> Match for Anno { +impl + Annotation, A: AstTypes> Match for Anno { fn recurse(sub: &mut Subst, pat: &Self, expr: &Self) -> bool { Match::recurse(sub, &pat.0, &expr.0) } @@ -78,7 +73,7 @@ impl + Annotation, A: Annotation> Match for Anno { } } -impl Match for Bind { +impl Match for Bind { fn recurse(sub: &mut Subst, 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 Match for Bind { } } -impl Match for crate::ast::Make { +impl Match for crate::ast::Make { fn recurse(sub: &mut Subst, 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 Match for crate::ast::Make { } } -impl Match for Expr { +impl Match for Expr { fn recurse(sub: &mut Subst, 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 Match for Expr { } } -impl Match for MakeArm { +impl Match for MakeArm { // TODO: order-independent matching for MakeArm specifically. fn recurse(sub: &mut Subst, pat: &Self, expr: &Self) -> bool { pat.0 == expr.0 && Match::recurse(sub, &pat.1, &expr.1) @@ -161,10 +156,10 @@ impl Match for MakeArm { } } -impl Match for Pat { +impl Match for Pat { fn recurse(sub: &mut Subst, 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 Match for Pat { } } -impl Match for Op { +impl Match for Op { fn recurse(_: &mut Subst, pat: &Self, expr: &Self) -> bool { pat == expr } @@ -201,7 +196,7 @@ impl Match for Op { fn apply(&mut self, _sub: &Subst) {} } -impl> Match for [T] { +impl> Match for [T] { fn recurse(sub: &mut Subst, pat: &Self, expr: &Self) -> bool { if pat.len() != expr.len() { return false; @@ -221,7 +216,7 @@ impl> Match for [T] { } } -impl> Match for Box { +impl> Match for Box { fn recurse(sub: &mut Subst, pat: &Self, expr: &Self) -> bool { Match::recurse(sub, pat.as_ref(), expr.as_ref()) } @@ -231,7 +226,7 @@ impl> Match for Box { } } -impl> Match for Vec { +impl> Match for Vec { fn recurse(sub: &mut Subst, pat: &Self, expr: &Self) -> bool { Match::recurse(sub, pat.as_slice(), expr.as_slice()) } @@ -241,7 +236,7 @@ impl> Match for Vec { } } -impl> Match for Option { +impl> Match for Option { fn recurse(sub: &mut Subst, pat: &Self, expr: &Self) -> bool { match (pat, expr) { (Some(pat), Some(expr)) => Match::recurse(sub, pat, expr), diff --git a/src/ast/types.rs b/src/ast/types.rs new file mode 100644 index 0000000..13d2b57 --- /dev/null +++ b/src/ast/types.rs @@ -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, + // TODO: generic parameters +} + +impl From<&str> for Path { + fn from(value: &str) -> Self { + Self { parts: vec![value.into()] } + } +} + +impl From 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()), + } + } +} diff --git a/src/ast/visit.rs b/src/ast/visit.rs index 9bac91e..cc445f9 100644 --- a/src/ast/visit.rs +++ b/src/ast/visit.rs @@ -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 + ?Sized>(&mut self, walk: &'a W) -> 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) - } - 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(&mut self, item: &'a Pat) -> Result<(), Self::Error> { - item.children(self) - } - fn visit_bind(&mut self, item: &'a Bind) -> Result<(), Self::Error> { - item.children(self) - } - fn visit_make(&mut self, item: &'a Make) -> Result<(), Self::Error> { - item.children(self) - } - fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { - item.children(self) - } -} - -pub trait Walk<'a> { - #[inline] - fn children + ?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 + ?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) -> Result<(), Self::Error> { + expr.children(self) + } + fn visit_use(&mut self, item: &'a Use) -> Result<(), Self::Error> { + item.children(self) + } + fn visit_pat(&mut self, item: &'a Pat) -> Result<(), Self::Error> { + item.children(self) + } + fn visit_bind(&mut self, item: &'a Bind) -> Result<(), Self::Error> { + item.children(self) + } + fn visit_make(&mut self, item: &'a Make) -> Result<(), Self::Error> { + item.children(self) + } + fn visit_makearm(&mut self, item: &'a MakeArm) -> Result<(), Self::Error> { + item.children(self) + } } -impl<'a, A: Annotation> Walk<'a> for Expr { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +pub trait Walk<'a, A: AstTypes> { + #[inline] + fn children + ?Sized>(&'a self, _v: &mut V) -> Result<(), V::Error> { + Ok(()) + } + fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error>; +} + +impl<'a, A: AstTypes> Walk<'a, 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::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 { } #[inline] - fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + 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) - } -} - -impl<'a> Walk<'a> for Path { - fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { - v.visit_path(self) - } -} - -impl<'a> Walk<'a> for Literal { - fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { - v.visit_literal(self) - } -} - -impl<'a> Walk<'a> for Use { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +impl<'a, A: AstTypes> Walk<'a, A> for Use { + fn children + ?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 + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { v.visit_use(self) } } -impl<'a, A: Annotation> Walk<'a> for Pat { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +impl<'a, A: AstTypes> Walk<'a, A> for Pat { + fn children + ?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 + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { v.visit_pat(self) } } -impl<'a, A: Annotation> Walk<'a> for Bind { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +impl<'a, A: AstTypes> Walk<'a, A> for Bind { + fn children + ?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 + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { v.visit_bind(self) } } -impl<'a, A: Annotation> Walk<'a> for Make { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +impl<'a, A: AstTypes> Walk<'a, A> for Make { + fn children + ?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 + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { v.visit_make(self) } } -impl<'a, A: Annotation> Walk<'a> for MakeArm { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +impl<'a, A: AstTypes> Walk<'a, A> for MakeArm { + fn children + ?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 + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?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 { +impl<'a, T: Annotation + Walk<'a, A>, A: AstTypes> Walk<'a, A> for Anno { #[inline] - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { self.0.children(v) } #[inline] - fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?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 { // GENERIC IMPLEMENTATIONS ON COLLECTIONS // ////////////////////////////////////////////// -impl<'a, T: Walk<'a>> Walk<'a> for [T] { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +impl<'a, T: Walk<'a, A>, A: AstTypes> Walk<'a, A> for [T] { + fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { for item in self { item.visit_in(v)?; } Ok(()) } - fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?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 { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +impl<'a, T: Walk<'a, A>, A: AstTypes> Walk<'a, A> for Vec { + fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { self.as_slice().children(v) } - fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { self.as_slice().visit_in(v) } } -impl<'a, T: Walk<'a>> Walk<'a> for Option { - fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { +impl<'a, T: Walk<'a, A>, A: AstTypes> Walk<'a, A> for Option { + fn children + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { match self { Some(t) => t.children(v), _ => Ok(()), } } - fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { + fn visit_in + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { match self { Some(t) => t.visit_in(v), _ => Ok(()), diff --git a/src/main.rs b/src/bin/doughlang.rs similarity index 92% rename from src/main.rs rename to src/bin/doughlang.rs index f1db5b4..f17700e 100644 --- a/src/main.rs +++ b/src/bin/doughlang.rs @@ -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> { "" => 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> { } 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> { }; 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::>(T::Prec::default()), verbose) { + match (parser.parse::>(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", diff --git a/src/intern.rs b/src/intern.rs new file mode 100644 index 0000000..e89e19e --- /dev/null +++ b/src/intern.rs @@ -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 Copy for Interned<'_, T> {} + + impl Clone for Interned<'_, T> { + fn clone(&self) -> Self { + *self + } + } + + impl Debug for Interned<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("~")?; + self.value.fmt(f) + } + } + + impl Deref for Interned<'_, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + self.value + } + } + + impl PartialEq for Interned<'_, T> { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.value, other.value) + } + } + + impl Eq for Interned<'_, T> {} + + impl Hash for Interned<'_, T> { + fn hash(&self, state: &mut H) { + Self::as_ptr(self).hash(state) + } + } + + impl Display for Interned<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.value.fmt(f) + } + } + + impl<'a> AsRef for Interned<'a, str> { + fn as_ref(&self) -> &str { + todo!() + } + } + + impl From<&str> for Interned<'static, str> { + /// Types which implement [`AsRef`] 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>, + } + + 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> = 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> { + 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>, + } + + 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::() == 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> { + 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 Sync for DroplessInterner<'_, T> {} +} diff --git a/src/lib.rs b/src/lib.rs index 69fefab..f24beb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ pub mod fmt; +pub mod intern; + pub mod span; pub mod token; diff --git a/src/parser.rs b/src/parser.rs index c119211..9a3a920 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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), } diff --git a/src/parser/expr.rs b/src/parser/expr.rs index a785db8..88f5937 100644 --- a/src/parser/expr.rs +++ b/src/parser/expr.rs @@ -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)?, )) } diff --git a/src/parser/pat.rs b/src/parser/pat.rs index 28bd515..d788b26 100644 --- a/src/parser/pat.rs +++ b/src/parser/pat.rs @@ -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 => {