From 9c5ddbc18bef81d66a00d25d9960909532ddbf0a Mon Sep 17 00:00:00 2001 From: loutr <l.ta-ma@proton.me> Date: Sun, 13 Nov 2022 16:58:37 +0100 Subject: [PATCH] chore(file structure): reorganised the file structure of arena- or term-related functions --- kernel/src/command.rs | 3 +- kernel/src/error.rs | 6 +- kernel/src/lib.rs | 5 +- kernel/src/term.rs | 732 ------------------------------------ kernel/src/term/arena.rs | 237 ++++++++++++ kernel/src/term/builders.rs | 167 ++++++++ kernel/src/term/calculus.rs | 356 ++++++++++++++++++ kernel/src/term/mod.rs | 3 + kernel/src/type_checker.rs | 10 +- kernel/tests/and.rs | 8 +- 10 files changed, 778 insertions(+), 749 deletions(-) delete mode 100644 kernel/src/term.rs create mode 100644 kernel/src/term/arena.rs create mode 100644 kernel/src/term/builders.rs create mode 100644 kernel/src/term/calculus.rs create mode 100644 kernel/src/term/mod.rs diff --git a/kernel/src/command.rs b/kernel/src/command.rs index 23c71273..7a904d4e 100644 --- a/kernel/src/command.rs +++ b/kernel/src/command.rs @@ -1,6 +1,5 @@ use crate::error::Result; -use crate::term::Arena; -use crate::term::Term; +use crate::{Arena, Term}; #[derive(Debug, Eq, PartialEq)] pub enum Command<'arena> { diff --git a/kernel/src/error.rs b/kernel/src/error.rs index 5d3d2adb..055bf348 100644 --- a/kernel/src/error.rs +++ b/kernel/src/error.rs @@ -1,7 +1,9 @@ -use crate::term::{DefinitionError, Term}; -use crate::type_checker::TypeCheckerError; use derive_more::{Display, From}; +use crate::term::arena::Term; +use crate::term::builders::extern_::DefinitionError; +use crate::type_checker::TypeCheckerError; + /// Type representing kernel errors. #[derive(Clone, Debug, Display, Eq, PartialEq)] pub struct Error<'arena> { diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 4457e045..6433072e 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -6,9 +6,10 @@ mod command; mod error; mod location; -pub mod term; +mod term; mod type_checker; pub use self::command::Command; pub use self::location::{Location, Position}; -pub use self::term::Term; +pub use self::term::arena::{use_arena, Arena, Term}; +pub use self::term::builders::extern_ as builders; diff --git a/kernel/src/term.rs b/kernel/src/term.rs deleted file mode 100644 index 4148c7bc..00000000 --- a/kernel/src/term.rs +++ /dev/null @@ -1,732 +0,0 @@ -use std::cell::OnceCell; -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; -use std::hash::Hash; -use std::marker::PhantomData; -use std::ops::Deref; - -use bumpalo::Bump; -use derive_more::{Add, Display, From, Into, Sub}; -use im_rc::hashmap::HashMap as ImHashMap; -use num_bigint::BigUint; - -use crate::error::{Error, ResultTerm}; - -#[derive( - Add, Copy, Clone, Debug, Default, Display, Eq, PartialEq, From, Into, Sub, PartialOrd, Ord, Hash, -)] -pub struct DeBruijnIndex(usize); - -#[derive(Add, Clone, Debug, Default, Display, Eq, From, Sub, PartialEq, PartialOrd, Ord, Hash)] -pub struct UniverseLevel(BigUint); - -#[non_exhaustive] -#[derive(Clone, Debug, Display, Eq, PartialEq)] -pub enum DefinitionError<'arena> { - #[display(fmt = "unknown identifiant {}", _0)] - ConstNotFound(&'arena str), -} - -pub struct Arena<'arena> { - alloc: &'arena Bump, - _phantom: PhantomData<*mut &'arena ()>, - - hashcons: HashSet<&'arena Node<'arena>>, - named_terms: HashMap<&'arena str, Term<'arena>>, - - mem_subst: HashMap<(Term<'arena>, Term<'arena>, DeBruijnIndex), Term<'arena>>, - // a shift hashmap may also be added when the is_certainly_closed also is -} - -#[derive(Clone, Copy, Display, Eq)] -#[display(fmt = "{}", "_0.payload")] -// PhantomData is a marker to ensure invariance over the 'arena lifetime. -pub struct Term<'arena>(&'arena Node<'arena>, PhantomData<*mut &'arena ()>); - -// the rest of the struct is very verbose and useless for debugging -impl<'arena> Debug for Term<'arena> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - self.0.payload.fmt(f) - } -} - -// no name storage here: meaning consts are known and can be found, but no pretty printing is -// possible so far. -#[derive(Debug)] -struct Node<'arena> { - payload: Payload<'arena>, - - head_normal_form: OnceCell<Term<'arena>>, - type_: OnceCell<Term<'arena>>, - // - // is_certainly_closed: boolean underapproximation of whether a term is closed, which can - // greatly improve performance in shifting -} - -#[derive(Clone, Debug, Display, Eq, PartialEq, Hash)] -pub enum Payload<'arena> { - #[display(fmt = "{}", _0)] - Var(DeBruijnIndex, Term<'arena>), - - #[display(fmt = "Prop")] - Prop, - - #[display(fmt = "Type {}", _0)] - Type(UniverseLevel), - - #[display(fmt = "{} {}", _0, _1)] - App(Term<'arena>, Term<'arena>), - - #[display(fmt = "\u{003BB} {} \u{02192} {}", _0, _1)] - Abs(Term<'arena>, Term<'arena>), - - #[display(fmt = "\u{03A0} {} \u{02192} {}", _0, _1)] - Prod(Term<'arena>, Term<'arena>), -} - -use Payload::*; - -/// (TODO PRECISE DOCUMENTATION) make use of unicity invariant to speed up equality test -impl<'arena> PartialEq<Term<'arena>> for Term<'arena> { - fn eq(&self, x: &Term<'arena>) -> bool { - std::ptr::eq(self.0, x.0) - } -} - -/// (TODO PRECISE DOCUMENTATION) make use of unicity invariant to speed up equality test -impl<'arena> Hash for Term<'arena> { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - std::ptr::hash(self.0, state) - } -} - -impl<'arena> PartialEq<Node<'arena>> for Node<'arena> { - fn eq(&self, x: &Node<'arena>) -> bool { - self.payload == x.payload - } -} - -impl<'arena> Eq for Node<'arena> {} - -/// (TODO PRECISE DOCUMENTATION) Only the payload matters and caracterises the value. Changing -/// OnceCells is *guaranteed* to have no impact on that. -impl<'arena> Hash for Node<'arena> { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.payload.hash(state); - } -} - -pub fn use_arena<F, T>(f: F) -> T -where - F: for<'arena> FnOnce(&mut Arena<'arena>) -> T, -{ - let alloc = Bump::new(); - let mut arena = Arena::new(&alloc); - f(&mut arena) -} - -use extern_build::Generator as ExternGenerator; -use intern_build::Generator; - -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(), - } - } - - /// (TODO DOC.) there are concurrent designs here: either hashcons take a Node and functions - /// which use it immediatly intend to compute the type and/or WHNF of the term, or this is - /// postponed and computed lazily. This iteration chooses the second approach. - fn hashcons(&mut self, n: Payload<'arena>) -> Term<'arena> { - let nn = Node { - payload: n, - head_normal_form: OnceCell::new(), - type_: OnceCell::new(), - }; - match self.hashcons.get(&nn) { - Some(addr) => Term(addr, PhantomData), - None => { - let addr = self.alloc.alloc(nn); - self.hashcons.insert(addr); - Term(addr, PhantomData) - } - } - } - - pub(crate) fn var(&mut self, index: DeBruijnIndex, type_: Term<'arena>) -> Term<'arena> { - self.hashcons(Var(index, type_)) - } - - pub(crate) fn prop(&mut self) -> Term<'arena> { - self.hashcons(Prop) - } - - pub(crate) fn type_(&mut self, level: UniverseLevel) -> Term<'arena> { - self.hashcons(Type(level)) - } - - pub(crate) fn type_usize(&mut self, level: usize) -> Term<'arena> { - self.hashcons(Type(BigUint::from(level).into())) - } - - pub(crate) fn app(&mut self, u1: Term<'arena>, u2: Term<'arena>) -> Term<'arena> { - self.hashcons(App(u1, u2)) - } - - pub(crate) fn abs(&mut self, arg_type: Term<'arena>, u: Term<'arena>) -> Term<'arena> { - self.hashcons(Abs(arg_type, u)) - } - - pub(crate) fn prod(&mut self, arg_type: Term<'arena>, u: Term<'arena>) -> Term<'arena> { - self.hashcons(Prod(arg_type, u)) - } - - pub(crate) fn build<F: Generator<'arena>>(&mut self, f: F) -> Term<'arena> { - f(self) - } - - #[inline] - pub fn build_from_extern<F: ExternGenerator<'arena>>(&mut self, f: F) -> ResultTerm<'arena> { - f(self, &ImHashMap::new(), 0.into()) - } - - pub fn bind(&mut self, name: &'arena str, t: Term<'arena>) { - self.named_terms.insert(name, t); - } - - /// Apply one step of β-reduction, using the leftmost-outermost evaluation strategy. - pub fn beta_reduction(&mut self, t: Term<'arena>) -> Term<'arena> { - match *t { - App(t1, t2) => match *t1 { - Abs(_, t1) => self.substitute(t1, t2, 1), - _ => { - let t1_new = self.beta_reduction(t1); - if t1_new == t1 { - let t2_new = self.beta_reduction(t2); - self.app(t1, t2_new) - } else { - self.app(t1_new, t2) - } - } - }, - Abs(arg_type, body) => { - let body = self.beta_reduction(body); - self.abs(arg_type, body) - } - Prod(arg_type, body) => { - let body = self.beta_reduction(body); - self.prod(arg_type, body) - } - _ => t, - } - } - - pub(crate) fn shift(&mut self, t: Term<'arena>, offset: usize, depth: usize) -> Term<'arena> { - 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); - let t2 = self.shift(t2, offset, depth); - self.app(t1, t2) - } - Abs(arg_type, body) => { - let arg_type = self.shift(arg_type, offset, depth); - let body = self.shift(body, offset, depth + 1); - self.abs(arg_type, body) - } - Prod(arg_type, body) => { - let arg_type = self.shift(arg_type, offset, depth); - let body = self.shift(body, offset, depth + 1); - self.prod(arg_type, body) - } - _ => t, - } - } - - pub(crate) fn substitute( - &mut self, - lhs: Term<'arena>, - rhs: Term<'arena>, - depth: usize, - ) -> Term<'arena> { - match self.mem_subst.get(&(lhs, rhs, depth.into())) { - Some(res) => *res, - None => { - let res = 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) => { - let l = self.substitute(l, rhs, depth); - let r = self.substitute(r, rhs, depth); - self.app(l, r) - } - Abs(arg_type, body) => { - let arg_type = self.substitute(arg_type, rhs, depth); - let body = self.substitute(body, rhs, depth + 1); - self.abs(arg_type, body) - } - Prod(arg_type, body) => { - let arg_type = self.substitute(arg_type, rhs, depth); - let body = self.substitute(body, rhs, depth + 1); - self.prod(arg_type, body) - } - _ => lhs, - }; - self.mem_subst.insert((lhs, rhs, depth.into()), res); - res - } - } - } - - /// Returns the normal form of a term in a given environment. - /// - /// This function is computationally expensive and should only be used for Reduce/Eval commands, not when type-checking. - pub fn normal_form(&mut self, t: Term<'arena>) -> Term<'arena> { - let mut temp = t; - let mut res = self.beta_reduction(t); - - while res != temp { - temp = res; - res = self.beta_reduction(res); - } - res - } - - /// Returns the weak-head normal form of a term in a given environment. - pub fn whnf(&mut self, t: Term<'arena>) -> Term<'arena> { - t.get_whnf_or_init(|| match *t { - App(t1, t2) => { - let t1 = self.whnf(t1); - match *t1 { - Abs(_, _) => { - let t = self.app(t1, t2); - let t = self.beta_reduction(t); - self.whnf(t) - } - _ => t, - } - } - _ => t, - }) - } -} - -impl<'arena> Term<'arena> { - pub(crate) fn into(self) -> impl Generator<'arena> { - move |_: &mut Arena<'arena>| self - } - - pub(crate) fn get_whnf_or_init<F>(self, f: F) -> Term<'arena> - where - F: FnOnce() -> Term<'arena>, - { - *self.0.head_normal_form.get_or_init(f) - } - - pub(crate) fn get_type_or_try_init<F>(self, f: F) -> ResultTerm<'arena> - where - F: FnOnce() -> ResultTerm<'arena>, - { - self.0.type_.get_or_try_init(f).copied() - } -} - -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>; - - #[inline] - pub(crate) fn prop<'arena>() -> impl Generator<'arena> { - |env: &mut Arena<'arena>| env.prop() - } - - #[inline] - pub(crate) fn type_<'arena>(level: UniverseLevel) -> impl Generator<'arena> { - move |env: &mut Arena<'arena>| env.type_(level) - } - - pub(crate) fn var<'arena, F: Generator<'arena>>( - index: DeBruijnIndex, - type_: F, - ) -> impl Generator<'arena> { - move |env: &mut Arena<'arena>| { - let ty = type_(env); - env.var(index, ty) - } - } - - #[inline] - pub(crate) 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) - } - } - - #[inline] - pub(crate) 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) - } - } - - #[inline] - pub(crate) 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 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) -> ResultTerm<'arena>; - - #[inline] - pub fn var<'arena>(name: &'arena str) -> impl Generator<'arena> { - move |arena: &mut Arena<'arena>, env: &Environment<'arena>, depth| { - env.get(name) - .map(|(bind_depth, term)| { - // maybe find a way to make this call efficiently lazy - let var_type = arena.shift(*term, usize::from(depth - *bind_depth), 0); - arena.var(depth - *bind_depth, var_type) - }) - .or_else(|| arena.named_terms.get(name).copied()) - .ok_or(Error { - kind: DefinitionError::ConstNotFound(name).into(), - }) - } - } - - #[inline] - pub fn prop<'arena>() -> impl Generator<'arena> { - |arena: &mut Arena<'arena>, _: &Environment<'arena>, _| Ok(arena.prop()) - } - - #[inline] - pub fn type_<'arena>(level: UniverseLevel) -> impl Generator<'arena> { - move |arena: &mut Arena<'arena>, _: &Environment<'arena>, _| Ok(arena.type_(level)) - } - - #[inline] - pub fn app<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( - u1: F1, - u2: F2, - ) -> impl Generator<'arena> { - |arena: &mut Arena<'arena>, env: &Environment<'arena>, depth| { - let u1 = u1(arena, env, depth)?; - let u2 = u2(arena, env, depth)?; - Ok(arena.app(u1, u2)) - } - } - - #[inline] - pub fn abs<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( - name: &'arena str, - arg_type: F1, - body: F2, - ) -> impl Generator<'arena> { - move |arena: &mut Arena<'arena>, env: &Environment<'arena>, depth| { - let arg_type = arg_type(arena, env, depth)?; - let env = env.update(name, (depth, arg_type)); - let body = body(arena, &env, depth + 1.into())?; - Ok(arena.abs(arg_type, body)) - } - } - - #[inline] - pub fn prod<'arena, F1: Generator<'arena>, F2: Generator<'arena>>( - name: &'arena str, - arg_type: F1, - body: F2, - ) -> impl Generator<'arena> { - move |arena: &mut Arena<'arena>, env: &Environment<'arena>, depth| { - let arg_type = arg_type(arena, env, depth)?; - let env = env.update(name, (depth, arg_type)); - let body = body(arena, &env, depth + 1.into())?; - Ok(arena.prod(arg_type, body)) - } - } -} - -#[cfg(test)] -mod tests { - // /!\ most of these tests are on ill-typed terms and should not be used for further testings - use super::intern_build::*; - use super::*; - - #[test] - fn simple_subst() { - use_arena(|arena| { - // λx.(λy.x y) x - let term = arena.build(abs( - prop(), - app( - abs(prop(), app(var(2.into(), prop()), var(1.into(), prop()))), - var(1.into(), prop()), - ), - )); - // λx.x x - let reduced = arena.build(abs( - prop(), - app(var(1.into(), prop()), var(1.into(), prop())), - )); - - assert_eq!(arena.beta_reduction(term), reduced); - }) - } - - #[test] - fn complex_subst() { - use_arena(|arena| { - // (λa.λb.λc.a (λd.λe.e (d b)) (λ_.c) (λd.d)) (λa.λb.a b) - let term = arena.build(app( - abs( - prop(), - abs( - prop(), - abs( - prop(), - app( - app( - app( - var(3.into(), prop()), - abs( - prop(), - abs( - prop(), - app( - var(1.into(), prop()), - app( - var(2.into(), prop()), - var(4.into(), prop()), - ), - ), - ), - ), - ), - abs(prop(), var(2.into(), prop())), - ), - abs(prop(), var(1.into(), prop())), - ), - ), - ), - ), - abs( - prop(), - abs(prop(), app(var(2.into(), prop()), var(1.into(), prop()))), - ), - )); - - let term_step_1 = arena.build(abs( - prop(), - abs( - prop(), - app( - app( - app( - abs( - prop(), - abs(prop(), app(var(2.into(), prop()), var(1.into(), prop()))), - ), - abs( - prop(), - abs( - prop(), - app( - var(1.into(), prop()), - app(var(2.into(), prop()), var(4.into(), prop())), - ), - ), - ), - ), - abs(prop(), var(2.into(), prop())), - ), - abs(prop(), var(1.into(), prop())), - ), - ), - )); - - let term_step_2 = arena.build(abs( - prop(), - abs( - prop(), - app( - app( - abs( - prop(), - app( - abs( - prop(), - abs( - prop(), - app( - var(1.into(), prop()), - app(var(2.into(), prop()), var(5.into(), prop())), - ), - ), - ), - var(1.into(), prop()), - ), - ), - abs(prop(), var(2.into(), prop())), - ), - abs(prop(), var(1.into(), prop())), - ), - ), - )); - - let term_step_3 = arena.build(abs( - prop(), - abs( - prop(), - app( - app( - abs( - prop(), - abs( - prop(), - app( - var(1.into(), prop()), - app(var(2.into(), prop()), var(4.into(), prop())), - ), - ), - ), - abs(prop(), var(2.into(), prop())), - ), - abs(prop(), var(1.into(), prop())), - ), - ), - )); - - let term_step_4 = arena.build(abs( - prop(), - abs( - prop(), - app( - abs( - prop(), - app( - var(1.into(), prop()), - app(abs(prop(), var(3.into(), prop())), var(3.into(), prop())), - ), - ), - abs(prop(), var(1.into(), prop())), - ), - ), - )); - - let term_step_5 = arena.build(abs( - prop(), - abs( - prop(), - app( - abs(prop(), var(1.into(), prop())), - app(abs(prop(), var(2.into(), prop())), var(2.into(), prop())), - ), - ), - )); - - let term_step_6 = arena.build(abs( - prop(), - abs( - prop(), - app(abs(prop(), var(2.into(), prop())), var(2.into(), prop())), - ), - )); - - // λa.λb.b - let term_step_7 = arena.build(abs(prop(), abs(prop(), var(1.into(), prop())))); - - assert_eq!(arena.beta_reduction(term), term_step_1); - assert_eq!(arena.beta_reduction(term_step_1), term_step_2); - assert_eq!(arena.beta_reduction(term_step_2), term_step_3); - assert_eq!(arena.beta_reduction(term_step_3), term_step_4); - assert_eq!(arena.beta_reduction(term_step_4), term_step_5); - assert_eq!(arena.beta_reduction(term_step_5), term_step_6); - assert_eq!(arena.beta_reduction(term_step_6), term_step_7); - assert_eq!(arena.beta_reduction(term_step_7), term_step_7); - }) - } - - #[test] - fn shift_prod() { - use_arena(|arena| { - let reduced = arena.build(prod(prop(), var(1.into(), prop()))); - let term = arena.build(app(abs(prop(), reduced.into()), prop())); - - assert_eq!(arena.beta_reduction(term), reduced) - }) - } - - #[test] - fn prod_beta_red() { - use_arena(|arena| { - let term = arena.build(prod( - prop(), - app(abs(prop(), var(1.into(), prop())), var(1.into(), prop())), - )); - let reduced = arena.build(prod(prop(), var(1.into(), prop()))); - - assert_eq!(arena.beta_reduction(term), reduced); - }) - } - - #[test] - fn app_red_rhs() { - use_arena(|arena| { - let term = arena.build(abs( - prop(), - app( - var(1.into(), prop()), - app(abs(prop(), var(1.into(), prop())), var(1.into(), prop())), - ), - )); - let reduced = arena.build(abs( - prop(), - app(var(1.into(), prop()), var(1.into(), prop())), - )); - - assert_eq!(arena.beta_reduction(term), reduced); - }) - } -} diff --git a/kernel/src/term/arena.rs b/kernel/src/term/arena.rs new file mode 100644 index 00000000..f81c9a6a --- /dev/null +++ b/kernel/src/term/arena.rs @@ -0,0 +1,237 @@ +use std::cell::OnceCell; +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::PhantomData; +use std::ops::Deref; + +use bumpalo::Bump; +use derive_more::{Add, Display, From, Into, Sub}; +use im_rc::hashmap::HashMap as ImHashMap; +use num_bigint::BigUint; + +use crate::error::ResultTerm; + +#[derive( + Add, Copy, Clone, Debug, Default, Display, Eq, PartialEq, From, Into, Sub, PartialOrd, Ord, Hash, +)] +pub struct DeBruijnIndex(usize); + +#[derive(Add, Clone, Debug, Default, Display, Eq, From, Sub, PartialEq, PartialOrd, Ord, Hash)] +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>>, + + mem_subst: HashMap<(Term<'arena>, Term<'arena>, usize), Term<'arena>>, + // a shift hashmap may also be added when the is_certainly_closed also is +} + +#[derive(Clone, Copy, Display, Eq)] +#[display(fmt = "{}", "_0.payload")] +// PhantomData is a marker to ensure invariance over the 'arena lifetime. +pub struct Term<'arena>(&'arena Node<'arena>, PhantomData<*mut &'arena ()>); + +// the rest of the struct is very verbose and useless for debugging +impl<'arena> Debug for Term<'arena> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0.payload.fmt(f) + } +} + +// no name storage here: meaning consts are known and can be found, but no pretty printing is +// possible so far. +#[derive(Debug)] +struct Node<'arena> { + payload: Payload<'arena>, + + head_normal_form: OnceCell<Term<'arena>>, + type_: OnceCell<Term<'arena>>, + // + // is_certainly_closed: boolean underapproximation of whether a term is closed, which can + // greatly improve performance in shifting +} + +#[derive(Clone, Debug, Display, Eq, PartialEq, Hash)] +pub enum Payload<'arena> { + #[display(fmt = "{}", _0)] + Var(DeBruijnIndex, Term<'arena>), + + #[display(fmt = "Prop")] + Prop, + + #[display(fmt = "Type {}", _0)] + Type(UniverseLevel), + + #[display(fmt = "{} {}", _0, _1)] + App(Term<'arena>, Term<'arena>), + + #[display(fmt = "\u{003BB} {} \u{02192} {}", _0, _1)] + Abs(Term<'arena>, Term<'arena>), + + #[display(fmt = "\u{03A0} {} \u{02192} {}", _0, _1)] + Prod(Term<'arena>, Term<'arena>), +} + +/// (TODO PRECISE DOCUMENTATION) make use of unicity invariant to speed up equality test +impl<'arena> PartialEq<Term<'arena>> for Term<'arena> { + fn eq(&self, x: &Term<'arena>) -> bool { + std::ptr::eq(self.0, x.0) + } +} + +/// (TODO PRECISE DOCUMENTATION) make use of unicity invariant to speed up equality test +impl<'arena> Hash for Term<'arena> { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + std::ptr::hash(self.0, state) + } +} + +impl<'arena> PartialEq<Node<'arena>> for Node<'arena> { + fn eq(&self, x: &Node<'arena>) -> bool { + self.payload == x.payload + } +} + +impl<'arena> Eq for Node<'arena> {} + +/// (TODO PRECISE DOCUMENTATION) Only the payload matters and caracterises the value. Changing +/// OnceCells is *guaranteed* to have no impact on that. +impl<'arena> Hash for Node<'arena> { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.payload.hash(state); + } +} + +pub fn use_arena<F, T>(f: F) -> T +where + F: for<'arena> FnOnce(&mut Arena<'arena>) -> T, +{ + let alloc = Bump::new(); + let mut arena = Arena::new(&alloc); + f(&mut arena) +} + +use super::builders::extern_::Builder; +use Payload::*; + +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(), + } + } + + pub fn bind(&mut self, name: &'arena str, t: Term<'arena>) { + self.named_terms.insert(name, t); + } + + pub fn get_binding(&self, name: &str) -> Option<Term<'arena>> { + self.named_terms.get(name).copied() + } + + /// (TODO DOC.) there are concurrent designs here: either hashcons take a Node and functions + /// which use it immediatly intend to compute the type and/or WHNF of the term, or this is + /// postponed and computed lazily. This iteration chooses the second approach. + fn hashcons(&mut self, n: Payload<'arena>) -> Term<'arena> { + let nn = Node { + payload: n, + head_normal_form: OnceCell::new(), + type_: OnceCell::new(), + }; + match self.hashcons.get(&nn) { + Some(addr) => Term(addr, PhantomData), + None => { + let addr = self.alloc.alloc(nn); + self.hashcons.insert(addr); + Term(addr, PhantomData) + } + } + } + + pub(crate) fn var(&mut self, index: DeBruijnIndex, type_: Term<'arena>) -> Term<'arena> { + self.hashcons(Var(index, type_)) + } + + pub(crate) fn prop(&mut self) -> Term<'arena> { + self.hashcons(Prop) + } + + pub(crate) fn type_(&mut self, level: UniverseLevel) -> Term<'arena> { + self.hashcons(Type(level)) + } + + pub(crate) fn type_usize(&mut self, level: usize) -> Term<'arena> { + self.hashcons(Type(BigUint::from(level).into())) + } + + pub(crate) fn app(&mut self, u1: Term<'arena>, u2: Term<'arena>) -> Term<'arena> { + self.hashcons(App(u1, u2)) + } + + pub(crate) fn abs(&mut self, arg_type: Term<'arena>, u: Term<'arena>) -> Term<'arena> { + self.hashcons(Abs(arg_type, u)) + } + + pub(crate) fn prod(&mut self, arg_type: Term<'arena>, u: Term<'arena>) -> Term<'arena> { + self.hashcons(Prod(arg_type, u)) + } + + #[inline] + pub fn build_from_extern<F: Builder<'arena>>(&mut self, f: F) -> ResultTerm<'arena> { + f(self, &ImHashMap::new(), 0.into()) + } + + pub(crate) fn get_subst_or_init<F>( + &mut self, + key: &(Term<'arena>, Term<'arena>, usize), + f: F, + ) -> Term<'arena> + where + F: FnOnce(&mut Self) -> Term<'arena>, + { + match self.mem_subst.get(key) { + Some(res) => *res, + None => { + let res = f(self); + self.mem_subst.insert(*key, res); + res + } + } + } +} + +impl<'arena> Term<'arena> { + pub(crate) fn get_whnf_or_init<F>(self, f: F) -> Term<'arena> + where + F: FnOnce() -> Term<'arena>, + { + *self.0.head_normal_form.get_or_init(f) + } + + pub(crate) fn get_type_or_try_init<F>(self, f: F) -> ResultTerm<'arena> + where + F: FnOnce() -> ResultTerm<'arena>, + { + self.0.type_.get_or_try_init(f).copied() + } +} + +impl<'arena> Deref for Term<'arena> { + type Target = Payload<'arena>; + + fn deref(&self) -> &Self::Target { + &self.0.payload + } +} diff --git a/kernel/src/term/builders.rs b/kernel/src/term/builders.rs new file mode 100644 index 00000000..e6c42ee9 --- /dev/null +++ b/kernel/src/term/builders.rs @@ -0,0 +1,167 @@ +use super::arena::{Arena, DeBruijnIndex, Term, UniverseLevel}; + +#[cfg(test)] +pub(crate) mod intern { + use super::*; + + impl<'arena> Arena<'arena> { + pub(crate) fn build<F: Builder<'arena>>(&mut self, f: F) -> Term<'arena> { + f(self) + } + } + + impl<'arena> Term<'arena> { + pub(crate) fn into(self) -> impl Builder<'arena> { + move |_: &mut Arena<'arena>| self + } + } + + pub(crate) trait Builder<'arena> = FnOnce(&mut Arena<'arena>) -> Term<'arena>; + + #[inline] + pub(crate) fn prop<'arena>() -> impl Builder<'arena> { + |env: &mut Arena<'arena>| env.prop() + } + + #[inline] + pub(crate) fn type_<'arena>(level: UniverseLevel) -> impl Builder<'arena> { + move |env: &mut Arena<'arena>| env.type_(level) + } + + #[inline] + pub(crate) fn var<'arena, F: Builder<'arena>>( + index: DeBruijnIndex, + type_: F, + ) -> impl Builder<'arena> { + move |env: &mut Arena<'arena>| { + let ty = type_(env); + env.var(index, ty) + } + } + + #[inline] + pub(crate) fn app<'arena, F1: Builder<'arena>, F2: Builder<'arena>>( + u1: F1, + u2: F2, + ) -> impl Builder<'arena> { + |env: &mut Arena<'arena>| { + let u1 = u1(env); + let u2 = u2(env); + env.app(u1, u2) + } + } + + #[inline] + pub(crate) fn abs<'arena, F1: Builder<'arena>, F2: Builder<'arena>>( + u1: F1, + u2: F2, + ) -> impl Builder<'arena> { + |env: &mut Arena<'arena>| { + let u1 = u1(env); + let u2 = u2(env); + env.abs(u1, u2) + } + } + + #[inline] + pub(crate) fn prod<'arena, F1: Builder<'arena>, F2: Builder<'arena>>( + u1: F1, + u2: F2, + ) -> impl Builder<'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 extern_ { + use derive_more::Display; + use im_rc::hashmap::HashMap as ImHashMap; + + use super::*; + use crate::error::{Error, ResultTerm}; + + #[non_exhaustive] + #[derive(Clone, Debug, Display, Eq, PartialEq)] + pub enum DefinitionError<'arena> { + #[display(fmt = "unknown identifiant {}", _0)] + ConstNotFound(&'arena str), + } + + pub type Environment<'arena> = ImHashMap<&'arena str, (DeBruijnIndex, Term<'arena>)>; + pub trait Builder<'arena> = + FnOnce(&mut Arena<'arena>, &Environment<'arena>, DeBruijnIndex) -> ResultTerm<'arena>; + + #[inline] + pub fn var<'arena>(name: &'arena str) -> impl Builder<'arena> { + move |arena: &mut Arena<'arena>, env: &Environment<'arena>, depth| { + env.get(name) + .map(|(bind_depth, term)| { + // maybe find a way to make this call efficiently lazy + let var_type = arena.shift(*term, usize::from(depth - *bind_depth), 0); + arena.var(depth - *bind_depth, var_type) + }) + .or_else(|| arena.get_binding(name)) + .ok_or(Error { + kind: DefinitionError::ConstNotFound(name).into(), + }) + } + } + + #[inline] + pub fn prop<'arena>() -> impl Builder<'arena> { + |arena: &mut Arena<'arena>, _: &Environment<'arena>, _| Ok(arena.prop()) + } + + #[inline] + pub fn type_<'arena>(level: UniverseLevel) -> impl Builder<'arena> { + move |arena: &mut Arena<'arena>, _: &Environment<'arena>, _| Ok(arena.type_(level)) + } + + #[inline] + pub fn app<'arena, F1: Builder<'arena>, F2: Builder<'arena>>( + u1: F1, + u2: F2, + ) -> impl Builder<'arena> { + |arena: &mut Arena<'arena>, env: &Environment<'arena>, depth| { + let u1 = u1(arena, env, depth)?; + let u2 = u2(arena, env, depth)?; + Ok(arena.app(u1, u2)) + } + } + + #[inline] + pub fn abs<'arena, F1: Builder<'arena>, F2: Builder<'arena>>( + name: &'arena str, + arg_type: F1, + body: F2, + ) -> impl Builder<'arena> { + move |arena: &mut Arena<'arena>, env: &Environment<'arena>, depth| { + let arg_type = arg_type(arena, env, depth)?; + let env = env.update(name, (depth, arg_type)); + let body = body(arena, &env, depth + 1.into())?; + Ok(arena.abs(arg_type, body)) + } + } + + #[inline] + pub fn prod<'arena, F1: Builder<'arena>, F2: Builder<'arena>>( + name: &'arena str, + arg_type: F1, + body: F2, + ) -> impl Builder<'arena> { + move |arena: &mut Arena<'arena>, env: &Environment<'arena>, depth| { + let arg_type = arg_type(arena, env, depth)?; + let env = env.update(name, (depth, arg_type)); + let body = body(arena, &env, depth + 1.into())?; + Ok(arena.prod(arg_type, body)) + } + } +} diff --git a/kernel/src/term/calculus.rs b/kernel/src/term/calculus.rs new file mode 100644 index 00000000..06b820cc --- /dev/null +++ b/kernel/src/term/calculus.rs @@ -0,0 +1,356 @@ +use super::arena::{Arena, Payload, Term}; +use Payload::*; + +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 { + App(t1, t2) => match *t1 { + Abs(_, t1) => self.substitute(t1, t2, 1), + _ => { + let t1_new = self.beta_reduction(t1); + if t1_new == t1 { + let t2_new = self.beta_reduction(t2); + self.app(t1, t2_new) + } else { + self.app(t1_new, t2) + } + } + }, + Abs(arg_type, body) => { + let body = self.beta_reduction(body); + self.abs(arg_type, body) + } + Prod(arg_type, body) => { + let body = self.beta_reduction(body); + self.prod(arg_type, body) + } + _ => t, + } + } + + pub(crate) fn shift(&mut self, t: Term<'arena>, offset: usize, depth: usize) -> Term<'arena> { + 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); + let t2 = self.shift(t2, offset, depth); + self.app(t1, t2) + } + Abs(arg_type, body) => { + let arg_type = self.shift(arg_type, offset, depth); + let body = self.shift(body, offset, depth + 1); + self.abs(arg_type, body) + } + Prod(arg_type, body) => { + let arg_type = self.shift(arg_type, offset, depth); + let body = self.shift(body, offset, depth + 1); + self.prod(arg_type, body) + } + _ => t, + } + } + + pub(crate) fn substitute( + &mut self, + lhs: Term<'arena>, + rhs: Term<'arena>, + depth: usize, + ) -> Term<'arena> { + self.get_subst_or_init(&(lhs, rhs, depth), |arena| match *lhs { + Var(i, _) if i == depth.into() => arena.shift(rhs, depth - 1, 0), + Var(i, type_) if i > depth.into() => arena.var(i - 1.into(), type_), + App(l, r) => { + let l = arena.substitute(l, rhs, depth); + let r = arena.substitute(r, rhs, depth); + arena.app(l, r) + } + Abs(arg_type, body) => { + let arg_type = arena.substitute(arg_type, rhs, depth); + let body = arena.substitute(body, rhs, depth + 1); + arena.abs(arg_type, body) + } + Prod(arg_type, body) => { + let arg_type = arena.substitute(arg_type, rhs, depth); + let body = arena.substitute(body, rhs, depth + 1); + arena.prod(arg_type, body) + } + _ => lhs, + }) + } + + /// Returns the normal form of a term in a given environment. + /// + /// This function is computationally expensive and should only be used for Reduce/Eval commands, not when type-checking. + pub fn normal_form(&mut self, t: Term<'arena>) -> Term<'arena> { + let mut temp = t; + let mut res = self.beta_reduction(t); + + while res != temp { + temp = res; + res = self.beta_reduction(res); + } + res + } + + /// Returns the weak-head normal form of a term in a given environment. + pub fn whnf(&mut self, t: Term<'arena>) -> Term<'arena> { + t.get_whnf_or_init(|| match *t { + App(t1, t2) => { + let t1 = self.whnf(t1); + match *t1 { + Abs(_, _) => { + let t = self.app(t1, t2); + let t = self.beta_reduction(t); + self.whnf(t) + } + _ => t, + } + } + _ => t, + }) + } +} + +#[cfg(test)] +mod tests { + // /!\ most of these tests are on ill-typed terms and should not be used for further testings + use super::super::arena::use_arena; + use super::super::builders::intern::*; + + #[test] + fn simple_subst() { + use_arena(|arena| { + // λx.(λy.x y) x + let term = arena.build(abs( + prop(), + app( + abs(prop(), app(var(2.into(), prop()), var(1.into(), prop()))), + var(1.into(), prop()), + ), + )); + // λx.x x + let reduced = arena.build(abs( + prop(), + app(var(1.into(), prop()), var(1.into(), prop())), + )); + + assert_eq!(arena.beta_reduction(term), reduced); + }) + } + + #[test] + fn complex_subst() { + use_arena(|arena| { + // (λa.λb.λc.a (λd.λe.e (d b)) (λ_.c) (λd.d)) (λa.λb.a b) + let term = arena.build(app( + abs( + prop(), + abs( + prop(), + abs( + prop(), + app( + app( + app( + var(3.into(), prop()), + abs( + prop(), + abs( + prop(), + app( + var(1.into(), prop()), + app( + var(2.into(), prop()), + var(4.into(), prop()), + ), + ), + ), + ), + ), + abs(prop(), var(2.into(), prop())), + ), + abs(prop(), var(1.into(), prop())), + ), + ), + ), + ), + abs( + prop(), + abs(prop(), app(var(2.into(), prop()), var(1.into(), prop()))), + ), + )); + + let term_step_1 = arena.build(abs( + prop(), + abs( + prop(), + app( + app( + app( + abs( + prop(), + abs(prop(), app(var(2.into(), prop()), var(1.into(), prop()))), + ), + abs( + prop(), + abs( + prop(), + app( + var(1.into(), prop()), + app(var(2.into(), prop()), var(4.into(), prop())), + ), + ), + ), + ), + abs(prop(), var(2.into(), prop())), + ), + abs(prop(), var(1.into(), prop())), + ), + ), + )); + + let term_step_2 = arena.build(abs( + prop(), + abs( + prop(), + app( + app( + abs( + prop(), + app( + abs( + prop(), + abs( + prop(), + app( + var(1.into(), prop()), + app(var(2.into(), prop()), var(5.into(), prop())), + ), + ), + ), + var(1.into(), prop()), + ), + ), + abs(prop(), var(2.into(), prop())), + ), + abs(prop(), var(1.into(), prop())), + ), + ), + )); + + let term_step_3 = arena.build(abs( + prop(), + abs( + prop(), + app( + app( + abs( + prop(), + abs( + prop(), + app( + var(1.into(), prop()), + app(var(2.into(), prop()), var(4.into(), prop())), + ), + ), + ), + abs(prop(), var(2.into(), prop())), + ), + abs(prop(), var(1.into(), prop())), + ), + ), + )); + + let term_step_4 = arena.build(abs( + prop(), + abs( + prop(), + app( + abs( + prop(), + app( + var(1.into(), prop()), + app(abs(prop(), var(3.into(), prop())), var(3.into(), prop())), + ), + ), + abs(prop(), var(1.into(), prop())), + ), + ), + )); + + let term_step_5 = arena.build(abs( + prop(), + abs( + prop(), + app( + abs(prop(), var(1.into(), prop())), + app(abs(prop(), var(2.into(), prop())), var(2.into(), prop())), + ), + ), + )); + + let term_step_6 = arena.build(abs( + prop(), + abs( + prop(), + app(abs(prop(), var(2.into(), prop())), var(2.into(), prop())), + ), + )); + + // λa.λb.b + let term_step_7 = arena.build(abs(prop(), abs(prop(), var(1.into(), prop())))); + + assert_eq!(arena.beta_reduction(term), term_step_1); + assert_eq!(arena.beta_reduction(term_step_1), term_step_2); + assert_eq!(arena.beta_reduction(term_step_2), term_step_3); + assert_eq!(arena.beta_reduction(term_step_3), term_step_4); + assert_eq!(arena.beta_reduction(term_step_4), term_step_5); + assert_eq!(arena.beta_reduction(term_step_5), term_step_6); + assert_eq!(arena.beta_reduction(term_step_6), term_step_7); + assert_eq!(arena.beta_reduction(term_step_7), term_step_7); + }) + } + + #[test] + fn shift_prod() { + use_arena(|arena| { + let reduced = arena.build(prod(prop(), var(1.into(), prop()))); + let term = arena.build(app(abs(prop(), reduced.into()), prop())); + + assert_eq!(arena.beta_reduction(term), reduced) + }) + } + + #[test] + fn prod_beta_red() { + use_arena(|arena| { + let term = arena.build(prod( + prop(), + app(abs(prop(), var(1.into(), prop())), var(1.into(), prop())), + )); + let reduced = arena.build(prod(prop(), var(1.into(), prop()))); + + assert_eq!(arena.beta_reduction(term), reduced); + }) + } + + #[test] + fn app_red_rhs() { + use_arena(|arena| { + let term = arena.build(abs( + prop(), + app( + var(1.into(), prop()), + app(abs(prop(), var(1.into(), prop())), var(1.into(), prop())), + ), + )); + let reduced = arena.build(abs( + prop(), + app(var(1.into(), prop()), var(1.into(), prop())), + )); + + assert_eq!(arena.beta_reduction(term), reduced); + }) + } +} diff --git a/kernel/src/term/mod.rs b/kernel/src/term/mod.rs new file mode 100644 index 00000000..1bfae37f --- /dev/null +++ b/kernel/src/term/mod.rs @@ -0,0 +1,3 @@ +pub mod arena; +pub mod builders; +pub mod calculus; diff --git a/kernel/src/type_checker.rs b/kernel/src/type_checker.rs index 65048a60..9e26ebbc 100644 --- a/kernel/src/type_checker.rs +++ b/kernel/src/type_checker.rs @@ -4,7 +4,7 @@ use derive_more::Display; use num_bigint::BigUint; use crate::error::{Error, Result, ResultTerm}; -use crate::term::{Arena, Payload, Term}; +use crate::term::arena::{Arena, Payload, Term}; use Payload::*; #[derive(Clone, Debug, Display, Eq, PartialEq)] @@ -174,10 +174,10 @@ impl<'arena> Arena<'arena> { #[cfg(test)] mod tests { use super::*; - use crate::term::intern_build::*; - use crate::term::use_arena; + use crate::term::builders::intern::*; + use crate::use_arena; - fn id<'arena>() -> impl Generator<'arena> { + fn id<'arena>() -> impl Builder<'arena> { abs(prop(), var(1.into(), prop())) } @@ -297,7 +297,7 @@ mod tests { // this test uses more intricate terms. In order to preserve some readibility, // switching to extern_build, which is clearer. fn typed_reduction_app_2() { - use crate::term::extern_build::*; + use crate::term::builders::extern_::*; use_arena(|arena| { // (λa.λb.λc.a (λd.λe.e (d b)) (λ_.c) (λd.d)) (λf.λg.f g) let term = arena diff --git a/kernel/tests/and.rs b/kernel/tests/and.rs index d323aee2..e7630382 100644 --- a/kernel/tests/and.rs +++ b/kernel/tests/and.rs @@ -1,10 +1,6 @@ -#![feature(box_syntax)] - +use kernel::builders::*; use kernel::Command::*; - -use kernel::term::extern_build::*; -use kernel::term::use_arena; -use kernel::term::Arena; +use kernel::{use_arena, Arena}; fn use_and_arena<F, T>(f: F) -> T where -- GitLab