From c32fbef3cd3050e36b3176fc7a3c4128c9fcd6ef Mon Sep 17 00:00:00 2001 From: Lucas Tabary-Maujean <l.ta-ma@proton.me> Date: Sat, 5 Nov 2022 15:01:32 +0100 Subject: [PATCH] fix(terms and type checker): use deref trait, further temporary changes in type checker, removing previous environment definition; early: won't compile --- Cargo.lock | 22 +++--- Cargo.toml | 6 +- kernel/Cargo.toml | 4 +- kernel/src/environment.rs | 48 ------------ kernel/src/term.rs | 152 ++++++++++++++++++++++++++++--------- kernel/src/type_checker.rs | 78 +++---------------- 6 files changed, 144 insertions(+), 166 deletions(-) delete mode 100644 kernel/src/environment.rs diff --git a/Cargo.lock b/Cargo.lock index e79ad515..fe2c0da3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,9 +69,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.0.18" +version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b" +checksum = "8e67816e006b17427c9b4386915109b494fec2d929c63e3bd3561234cbf1bf1e" dependencies = [ "atty", "bitflags", @@ -305,8 +305,8 @@ version = "0.1.0" dependencies = [ "bumpalo", "derive_more", - "lazy_static", "im-rc", + "lazy_static", "num-bigint", ] @@ -418,9 +418,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" +checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8" dependencies = [ "thiserror", "ucd-trie", @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b75706b9642ebcb34dab3bc7750f811609a0eb1dd8b88c2d15bf628c1c65b2" +checksum = "d5fd9bc6500181952d34bd0b2b0163a54d794227b498be0b7afa7698d0a7b18f" dependencies = [ "pest", "pest_generator", @@ -438,9 +438,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f9272122f5979a6511a749af9db9bfc810393f63119970d7085fed1c4ea0db" +checksum = "d2610d5ac5156217b4ff8e46ddcef7cdf44b273da2ac5bca2ecbfa86a330e7c4" dependencies = [ "pest", "pest_meta", @@ -451,9 +451,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8717927f9b79515e565a64fe46c38b8cd0427e64c40680b14a7365ab09ac8d" +checksum = "824749bf7e21dd66b36fbe26b3f45c713879cccd4a009a917ab8e045ca8246fe" dependencies = [ "once_cell", "pest", diff --git a/Cargo.toml b/Cargo.toml index de3a1a7c..e8ad1806 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,15 +7,17 @@ members = [ [workspace.dependencies] anyhow = "1.0" +bumpalo = "3.11.1" clap = { version = "4.0.10", features = ["derive"] } derive_more = "0.99.17" +im-rc = "15.1.0" num-bigint = "0.4" [workspace.package] authors = [ "Arthur Adjedj", "Augustin Albert", - "Lucas Tabary-Maujean", - "Vincent Lafeychine" + "Vincent Lafeychine", + "Lucas Tabary-Maujean" ] license = "GPL-3.0-or-later" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 4ca21cd8..ce4e0d22 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -11,5 +11,5 @@ num-bigint.workspace = true [dev-dependencies] lazy_static = "1.4.0" -bumpalo.workspace = true; -im-rc.workspace = true; +bumpalo.workspace = true +im-rc.workspace = true diff --git a/kernel/src/environment.rs b/kernel/src/environment.rs deleted file mode 100644 index 5d9ada90..00000000 --- a/kernel/src/environment.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::error::{Error, Result}; -use crate::term::Term; -use derive_more::{Deref, DerefMut, Display}; -use std::collections::{hash_map, HashMap}; - -/// Global Environment, contains the term and type of every definitions, denoted by their strings. -#[derive(Clone, Default, Debug, Deref, DerefMut, Eq, PartialEq)] -pub struct Environment(HashMap<String, (Term, Term)>); - -/// Errors that can occur, at runtime, during environment manipulation. -#[non_exhaustive] -#[derive(Clone, Debug, Display, Eq, PartialEq)] -pub enum EnvironmentError { - #[display(fmt = "{} is already defined", _0)] - AlreadyDefined(String), - - #[display(fmt = "variable {} is not found", _0)] - VariableNotFound(String), -} - -impl Environment { - /// Creates an empty environment. - pub fn new() -> Self { - Self::default() - } - - /// Creates a new environment binding s with (t1,t2) - pub(crate) fn insert(&mut self, s: String, t1: Term, t2: Term) -> Result<&Self> { - if let hash_map::Entry::Vacant(e) = self.entry(s.clone()) { - e.insert((t1, t2)); - Ok(self) - } else { - Err(Error { - kind: EnvironmentError::AlreadyDefined(s).into(), - }) - } - } - - /// Returns the term linked to a definition in a given environment. - pub(crate) fn get_term(&self, s: &String) -> Option<Term> { - self.get(s).map(|(t, _)| t.clone()) - } - - /// Returns the type linked to a definition in a given environment. - pub(crate) fn get_type(&self, s: &String) -> Option<Term> { - self.get(s).map(|(_, t)| t.clone()) - } -} diff --git a/kernel/src/term.rs b/kernel/src/term.rs index 65024ff7..9cb2344e 100644 --- a/kernel/src/term.rs +++ b/kernel/src/term.rs @@ -8,6 +8,8 @@ use im_rc::hashmap::HashMap as ImHashMap; use std::cell::OnceCell; use std::collections::{HashMap, HashSet}; use std::hash::Hash; +use std::marker::PhantomData; +use std::ops::Deref; #[derive( Add, Copy, Clone, Debug, Default, Display, Eq, PartialEq, From, Into, Sub, PartialOrd, Ord, Hash, @@ -19,6 +21,7 @@ pub struct UniverseLevel(BigUint); pub struct Arena<'arena> { alloc: &'arena Bump, + _phantom: PhantomData<*mut &'arena ()>, hashcons: HashSet<&'arena Node<'arena>>, named_terms: HashMap<&'arena str, Term<'arena>>, @@ -26,16 +29,10 @@ pub struct Arena<'arena> { mem_subst: HashMap<(Term<'arena>, Term<'arena>, DeBruijnIndex), Term<'arena>>, } -type Environment<'arena> = ImHashMap<&'arena str, (DeBruijnIndex, Term<'arena>)>; -pub trait ExternTermGenerator<'arena> = FnOnce( - &mut Arena<'arena>, - &Environment<'arena>, - DeBruijnIndex, -) -> Result<Term<'arena>, KernelError<'arena>>; - #[derive(Clone, Copy, Display, Eq, Debug)] #[display(fmt = "{}", "_0.payload")] -pub struct Term<'arena>(&'arena Node<'arena>); +// PhantomData is a marker to ensure invariance over the 'arena lifetime. +pub struct Term<'arena>(&'arena Node<'arena>, PhantomData<*mut &'arena ()>); // no name storage here: meaning consts are known and can be found, but no pretty printing is // possible so far. @@ -55,7 +52,7 @@ type BinTermHashMap<'arena> = HashMap<(Term<'arena>, Term<'arena>), Term<'arena> // // We also need a substitution hasmap, (body, level_of_substitution, Term_to_incorporate) -#[derive(Clone, Copy, Debug, Display, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Display, Eq, PartialEq, Hash)] pub enum Payload<'arena> { #[display(fmt = "{}", _0)] Var(DeBruijnIndex, Term<'arena>), @@ -64,7 +61,7 @@ pub enum Payload<'arena> { Prop, #[display(fmt = "Type {}", _0)] - Type(&'arena UniverseLevel), + Type(UniverseLevel), #[display(fmt = "{} {}", _0, _1)] App(Term<'arena>, Term<'arena>), @@ -100,12 +97,6 @@ impl<'arena> Hash for Node<'arena> { } } -impl<'arena> From<Term<'arena>> for Payload<'arena> { - fn from(t: Term<'arena>) -> Self { - t.0.payload - } -} - pub fn use_arena<F, T>(f: F) -> T where F: for<'arena> FnOnce(Arena<'arena>) -> T, @@ -115,16 +106,21 @@ where f(arena) } +use intern_build::Generator; +use extern_build::Generator as ExternGenerator; + impl<'arena> Arena<'arena> { /// (TODO DOC.) Allocate a new memory arena. fn new(alloc: &'arena Bump) -> Self { Arena { alloc, + _phantom: PhantomData, hashcons: HashSet::new(), named_terms: HashMap::new(), mem_subst: HashMap::new(), + } } @@ -138,11 +134,11 @@ impl<'arena> Arena<'arena> { type_: OnceCell::new(), }; match self.hashcons.get(&nn) { - Some(addr) => Term(addr), + Some(addr) => Term(addr, PhantomData), None => { let addr = self.alloc.alloc(nn); self.hashcons.insert(addr); - Term(addr) + Term(addr, PhantomData) } } } @@ -155,7 +151,7 @@ impl<'arena> Arena<'arena> { self.hashcons(Prop) } - pub(crate) fn type_(&mut self, level: &'arena UniverseLevel) -> Term<'arena> { + pub(crate) fn type_(&mut self, level: UniverseLevel) -> Term<'arena> { self.hashcons(Type(level)) } @@ -171,7 +167,11 @@ impl<'arena> Arena<'arena> { self.hashcons(Prod(arg_type, u)) } - pub(crate) fn extern_gen<F: ExternTermGenerator<'arena>>( + pub(crate) fn build<F: Generator<'arena>>(&mut self, f: F) -> Term<'arena> { + f(self) + } + + pub fn build_from_extern<F: ExternGenerator<'arena>>( &mut self, f: F, ) -> Result<Term<'arena>, KernelError<'arena>> { @@ -180,8 +180,8 @@ impl<'arena> Arena<'arena> { /// Apply one step of β-reduction, using the leftmost-outermost evaluation strategy. pub fn beta_reduction(&mut self, t: Term<'arena>) -> Term<'arena> { - match t.into() { - App(t1, t2) => match t1.into() { + match *t { + App(t1, t2) => match *t1 { Abs(_, t1) => self.substitute(t1, t2, 1), _ => { let t1 = self.beta_reduction(t1); @@ -197,7 +197,7 @@ impl<'arena> Arena<'arena> { } pub(crate) fn shift(&mut self, t: Term<'arena>, offset: usize, depth: usize) -> Term<'arena> { - match t.into() { + match *t { Var(i, type_) if i > depth.into() => self.var(i + offset.into(), type_), App(t1, t2) => { let t1 = self.shift(t1, offset, depth); @@ -224,7 +224,7 @@ impl<'arena> Arena<'arena> { rhs: Term<'arena>, depth: usize, ) -> Term<'arena> { - match lhs.into() { + match *lhs { Var(i, _) if i == depth.into() => self.shift(rhs, depth - 1, 0), Var(i, type_) if i > depth.into() => self.var(i - 1.into(), type_), App(l, r) => { @@ -262,10 +262,10 @@ impl<'arena> Arena<'arena> { /// Returns the weak-head normal form of a term in a given environment. pub fn whnf(&mut self, t: Term<'arena>) -> Term<'arena> { - match t.into() { + match *t { App(t1, t2) => { let t1 = self.whnf(t1); - match t1.into() { + match *t1 { Abs(_, _) => { let t = self.app(t1, t2); let t = self.beta_reduction(t); @@ -279,15 +279,93 @@ impl<'arena> Arena<'arena> { } } +impl<'arena> Term<'arena> { + pub(crate) fn into(self) -> impl Generator<'arena> + { move |_: &mut Arena<'arena>| self } + + pub fn is_redex(self) -> bool { + match *self { + App(t, _) => match *t { + Abs(_, _) => true, + _ => false + } + _ => false + } + } +} + +impl<'arena> Deref for Term<'arena> { + type Target = Payload<'arena>; + + fn deref(&self) -> &Self::Target { + &self.0.payload + } +} + +pub(crate) mod intern_build { + use super::*; + + pub(crate) trait Generator<'arena> = FnOnce(&mut Arena<'arena>) -> Term<'arena>; + + pub fn prop<'arena>() -> impl Generator<'arena> { + |env: &mut Arena<'arena>| env.prop() + } + + pub fn type_<'arena>(level: UniverseLevel) -> impl Generator<'arena> { + move |env: &mut Arena<'arena>| env.type_(level) + } + + pub fn app<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( + u1: F1, + u2: F2, + ) -> impl Generator<'arena> { + |env: &mut Arena<'arena>| { + let u1 = u1(env); + let u2 = u2(env); + env.app(u1, u2) + } + } + + pub fn abs<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( + u1: F1, + u2: F2, + ) -> impl Generator<'arena> { + |env: &mut Arena<'arena>| { + let u1 = u1(env); + let u2 = u2(env); + env.abs(u1, u2) + } + } + + pub fn prod<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( + u1: F1, + u2: F2, + ) -> impl Generator<'arena> { + |env: &mut Arena<'arena>| { + let u1 = u1(env); + let u2 = u2(env); + env.prod(u1, u2) + } + } +} + /// These functions are available publicly, to the attention of the parser. They manipulate /// objects with a type morally equal to (Env -> Term), where Env is a working environment used /// in term construction from the parser. /// This is done as a way to elengantly keep the logic encapsulated in the kernel, but let the /// parser itself explore the term. -pub mod from_parser { - use crate::term::*; +pub mod extern_build { + use super::*; + + pub type Environment<'arena> = ImHashMap<&'arena str, (DeBruijnIndex, Term<'arena>)>; +pub trait Generator<'arena> = FnOnce( + &mut Arena<'arena>, + &Environment<'arena>, + DeBruijnIndex, +) -> Result<Term<'arena>, KernelError<'arena>>; + - pub fn var<'arena>(name: &'arena str) -> impl ExternTermGenerator<'arena> { + pub fn var<'arena>(name: &'arena str) -> impl Generator<'arena> { move |context: &mut Arena<'arena>, env: &Environment<'arena>, depth| { env.get(name) .map(|(bind_depth, term)| context.var(depth - *bind_depth, *term)) @@ -296,18 +374,18 @@ pub mod from_parser { } } - pub fn prop<'arena>() -> impl ExternTermGenerator<'arena> { + pub fn prop<'arena>() -> impl Generator<'arena> { |context: &mut Arena<'arena>, _: &Environment<'arena>, _| Ok(context.prop()) } - pub fn type_<'arena>(level: &'arena UniverseLevel) -> impl ExternTermGenerator<'arena> { + pub fn type_<'arena>(level: UniverseLevel) -> impl Generator<'arena> { move |context: &mut Arena<'arena>, _: &Environment<'arena>, _| Ok(context.type_(level)) } - pub fn app<'arena, F1: ExternTermGenerator<'arena>, F2: ExternTermGenerator<'arena>>( + pub fn app<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( u1: F1, u2: F2, - ) -> impl ExternTermGenerator<'arena> { + ) -> impl Generator<'arena> { |context: &mut Arena<'arena>, env: &Environment<'arena>, depth| { let u1 = u1(context, env, depth)?; let u2 = u2(context, env, depth)?; @@ -315,11 +393,11 @@ pub mod from_parser { } } - pub fn abs<'arena, F1: ExternTermGenerator<'arena>, F2: ExternTermGenerator<'arena>>( + pub fn abs<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( name: &'arena str, arg_type: F1, body: F2, - ) -> impl ExternTermGenerator<'arena> { + ) -> impl Generator<'arena> { move |context: &mut Arena<'arena>, env: &Environment<'arena>, depth| { let arg_type = arg_type(context, env, depth)?; let env = env.update(name, (depth + DeBruijnIndex(1), arg_type)); @@ -328,11 +406,11 @@ pub mod from_parser { } } - pub fn prod<'arena, F1: ExternTermGenerator<'arena>, F2: ExternTermGenerator<'arena>>( + pub fn prod<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( name: &'arena str, arg_type: F1, body: F2, - ) -> impl ExternTermGenerator<'arena> { + ) -> impl Generator<'arena> { move |context: &mut Arena<'arena>, env: &Environment<'arena>, depth| { let arg_type = arg_type(context, env, depth)?; let env = env.update(name, (depth + DeBruijnIndex(1), arg_type)); diff --git a/kernel/src/type_checker.rs b/kernel/src/type_checker.rs index beefbc09..684d92db 100644 --- a/kernel/src/type_checker.rs +++ b/kernel/src/type_checker.rs @@ -1,11 +1,10 @@ use crate::environment::{Environment, EnvironmentError}; use crate::error::{Error, Result}; -use crate::term::{DeBruijnIndex, Term}; +use crate::term::{Arena, Payload, Term}; use derive_more::Display; use num_bigint::BigUint; use std::cmp::max; -use std::ops::Index; -use Term::*; +use Payload::*; #[derive(Clone, Debug, Display, Eq, PartialEq)] #[display(fmt = "{} : {}", _0, _1)] @@ -36,72 +35,19 @@ pub enum TypeCheckerError { TypeMismatch(Term, Term), } -/// Type of lists of tuples representing the respective types of each variables -type Types = Vec<Term>; - -impl Index<DeBruijnIndex> for Types { - type Output = Term; - - fn index(&self, idx: DeBruijnIndex) -> &Self::Output { - &self[usize::from(idx)] - } -} - -/// Structure containing a context used for typechecking. -/// -/// It serves to store the types of variables in the following way: -/// In a given context {types, lvl}, the type of `Var(i)` is in `types[lvl - i]`. -#[derive(Clone, Debug, Default)] -struct Context { - types: Types, - lvl: DeBruijnIndex, -} - -impl Context { - /// Creates a new empty `Context`. - fn new() -> Self { - Self::default() - } - - /// Extends the actual context with a bound variable of type `ty`. - fn bind(&mut self, ty: &Term) -> &mut Self { - // if x : Var(i), and x appears in a redex, then i needs to be shifted in the context, it would lead to incoherences otherwise. - self.types = self.types.iter().map(|t| t.shift(1, 0)).collect(); - self.types.push(ty.shift(1, 0)); - self.lvl = self.lvl + 1.into(); - self - } -} - -impl Term { +impl<'arena> Arena<'arena> { /// Conversion function, checks whether two terms are definitionally equal. /// /// The conversion is untyped, meaning that it should **only** be called during type-checking when the two `Term`s are already known to be of the same type and in the same context. - fn conversion(&self, rhs: &Term, env: &Environment) -> bool { - match (self.whnf(env), rhs.whnf(env)) { - (Prop, Prop) => true, - - (Type(i), Type(j)) => i == j, - - (Var(i), Var(j)) => i == j, - - (Prod(t1, u1), Prod(box t2, u2)) => t1.conversion(&t2, env) && u1.conversion(&u2, env), - - // Since we assume that both vals already have the same type, - // checking conversion over the argument type is useless. - // However, this doesn't mean we can simply remove the arg type - // from the type constructor in the enum, it is needed to quote back to terms. - (Abs(_, t), Abs(_, u)) => t.conversion(&u, env), - - (App(box t1, box u1), App(box t2, box u2)) => { - t1.conversion(&t2, env) && u1.conversion(&u2, env) - } - // TODO: Unused code (#34) - // (app @ App(box Abs(_, _), box _), u) | (u, app @ App(box Abs(_, _), box _)) => { - // app.beta_reduction(env).conversion(&u, env) - // } - _ => false, - } + fn conversion(&mut self, lhs: Term<'arena>, rhs: Term<'arena>) -> bool { + let lhs = self.whnf(lhs); + let rhs = self.whnf(rhs); + lhs == rhs + + // TODO: Unused code (#34) + // (app @ App(box Abs(_, _), box _), u) | (u, app @ App(box Abs(_, _), box _)) => { + // app.beta_reduction(env).conversion(&u, env) + // } } /// Checks whether two terms are definitionally equal. -- GitLab