From 8b20ac8090549dc6574b366f078e192beceefe61 Mon Sep 17 00:00:00 2001 From: belazy <aarthuur01@gmail.com> Date: Sat, 24 Dec 2022 15:24:21 +0100 Subject: [PATCH] =?UTF-8?q?Resolve=20"Add=20Universe=20Polymorphism"=20?= =?UTF-8?q?=E2=9C=A8=EF=B8=8F=20Closes=20#28=20=F0=9F=90=94=EF=B8=8F?= =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F=20Approved-by:=20belazy=20<aarthuur01@gma?= =?UTF-8?q?il.com>=20Approved-by:=20aalbert=20<augustin.albert@bleu-azure.?= =?UTF-8?q?fr>=20Approved-by:=20v-lafeychine=20<vincent.lafeychine@proton.?= =?UTF-8?q?me>=20Approved-by:=20loutr=20<loutr@crans.org>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🦀ï¸ðŸ°ðŸ¦€ï¸ðŸ°ðŸ¦€ï¸ðŸ° * fix(proost): added Define command back (need to distinguish between the two in order to bind and reuse correctly later * chore(kernel, calculus/level): adapt documentation to new type * chore(level): Add specific enum to handle comparison state * chore(kernel): c-o-v-e-r-a-g-e * chore(parser): remove TODO * chore(parser): more coverage * fix(kernel): trailing comma when printing `InstantiatedDeclaration`s * fix(kernel): chiale + aboie, coverage * chore(kernel): mention #14 * chore(kernel): yet another test * chore(kernel): always more coverage * chore(parser): add issue nb to todo * fix(kernel): avoid reentrant entry bug when infering type of decls with 0 universe arguments * fix(parser): revert comit, wtf clippy ? * chore(parser): remove unused branch * chore(kernel): another test * Apply 1 suggestion(s) to 1 file(s) * fix(kernel): remove dangerous function * chore(kernel): always more coverage * feat(parser): remove `Define` command * fix(kernel): interface checking for decls * Apply 1 suggestion(s) to 1 file(s) * chore(kernel): add coverage * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * chore(kernel): coverage * Apply 1 suggestion(s) to 1 file(s) * fix(kernel): over-agressive normalization * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * fix(parser): avoid spacing befor univ decls/vars * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * Apply 1 suggestion(s) to 1 file(s) * fix(proost): check existing identifiers for al definitions * Apply 1 suggestion(s) to 1 file(s) * feat(parser): add `DeclarationCheckType` * chore(kernel): remove unused test * fix(proost): type-check terms before `Eval`uating * docs: update * fix: further adjustments to parser * chore: formatting + some adjustments for kernel tests * fix(parser, proost): modify command generation for declarations * chore(parser): add tests todo * chore(parser): tests on sort parsing * fix(parser): tests * feat(parser): parse universe bindings * feat(parser): parse universe declarations in define_type * fix(level): comments * chore: remove biging as a dependency * chore(kernel): remove todo * chore(kernel): remove unused file * chore(kernel): more tests * chore(kernel): documentation of `level_builder.rs` * feat(kernel): tests coverage * fix(kernel): tests * feat(kernel/term/builder): add binding to (instantiated) declaration builders * feat(kernel): add builders for declaration * chore(kernel): refactor builder file hierarchy and simplify these files * fix(proost): some import errors * feat(parser): add parsing for univ parameters of definitions * chore(kernel): add todo * fix: remove lifetime dependency on LevelEnvironment * feat : add inference for declarations. * feat(parser): add new parsing rules for levels * feat(parser): new syntax for universe polymorphism * feat(kernel): add type-checking for Decls * feat: incorporate LevelBuilder in TermBuilder * fix(kernel): refactor impl organisation: unload most of arena's * WOOOHOOOO * fix(kernel, level): fix incorrect assignment in hashcons function * fix(kernel): more errors * feat(kernel): fixed some errors * feat(kernel): finish rudimentary bases for modifying parser and type checker * feat(kernel): further work on Levels * WIP feat(kernel): modify declaration.rs structure * fix(kernel): removing Dweller struct, using a macro instead * fix(kernel): refactor file structure to add new core types. first attempt for levels. * feat(kernel): add generic Dweller type * WIP fix(kernel): refactor file structure to add new core types * um * feat:something * AH * first commit --- Cargo.lock | 38 -- Cargo.toml | 1 - kernel/Cargo.toml | 1 - kernel/src/calculus/level.rs | 178 +++++++++ kernel/src/calculus/mod.rs | 5 + kernel/src/calculus/term.rs | 412 ++++++++++++++++++++ kernel/src/error.rs | 12 +- kernel/src/lib.rs | 10 +- kernel/src/memory/arena.rs | 243 ++++++++++++ kernel/src/memory/declaration/builder.rs | 185 +++++++++ kernel/src/memory/declaration/mod.rs | 105 ++++++ kernel/src/memory/level/builder.rs | 199 ++++++++++ kernel/src/memory/level/mod.rs | 238 ++++++++++++ kernel/src/memory/mod.rs | 11 + kernel/src/memory/term/builder.rs | 284 ++++++++++++++ kernel/src/memory/term/mod.rs | 242 ++++++++++++ kernel/src/term/arena.rs | 345 ----------------- kernel/src/term/builders.rs | 254 ------------- kernel/src/term/calculus.rs | 302 --------------- kernel/src/term/mod.rs | 8 - kernel/src/type_checker.rs | 462 +++++++++++++---------- kernel/tests/and.rs | 14 +- parser/Cargo.toml | 1 - parser/src/command.rs | 26 +- parser/src/parser.rs | 235 ++++++++++-- parser/src/term.pest | 36 +- proost/src/evaluator.rs | 56 ++- proost/src/main.rs | 2 +- 28 files changed, 2671 insertions(+), 1234 deletions(-) create mode 100644 kernel/src/calculus/level.rs create mode 100644 kernel/src/calculus/mod.rs create mode 100644 kernel/src/calculus/term.rs create mode 100644 kernel/src/memory/arena.rs create mode 100644 kernel/src/memory/declaration/builder.rs create mode 100644 kernel/src/memory/declaration/mod.rs create mode 100644 kernel/src/memory/level/builder.rs create mode 100644 kernel/src/memory/level/mod.rs create mode 100644 kernel/src/memory/mod.rs create mode 100644 kernel/src/memory/term/builder.rs create mode 100644 kernel/src/memory/term/mod.rs delete mode 100644 kernel/src/term/arena.rs delete mode 100644 kernel/src/term/builders.rs delete mode 100644 kernel/src/term/calculus.rs delete mode 100644 kernel/src/term/mod.rs diff --git a/Cargo.lock b/Cargo.lock index de4781df..bb6450bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,12 +13,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "bitflags" version = "1.3.2" @@ -326,7 +320,6 @@ dependencies = [ "derive_more", "im-rc", "lazy_static", - "num-bigint", ] [[package]] @@ -382,36 +375,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - [[package]] name = "once_cell" version = "1.16.0" @@ -430,7 +393,6 @@ version = "0.1.0" dependencies = [ "derive_more", "kernel", - "num-bigint", "pest", "pest_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 4cf76d27..b1013a65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ clap = { version = "4.0.10", features = ["derive"] } colored = "2" derive_more = "0.99.17" im-rc = "15.1.0" -num-bigint = "0.4" path-absolutize = "3.0.14" pest = "2.5" pest_derive = "2.5" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 95c167d4..f67b0a78 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -9,7 +9,6 @@ license.workspace = true bumpalo.workspace = true derive_more.workspace = true im-rc.workspace = true -num-bigint.workspace = true [dev-dependencies] lazy_static = "1.4.0" diff --git a/kernel/src/calculus/level.rs b/kernel/src/calculus/level.rs new file mode 100644 index 00000000..561664d8 --- /dev/null +++ b/kernel/src/calculus/level.rs @@ -0,0 +1,178 @@ +//! A set of useful functions to operate on [`Level`]s. + +use Payload::*; + +use crate::memory::arena::Arena; +use crate::memory::level::{Level, Payload}; + +/// State during computation for level comparison. +enum State { + /// Comparison failed. + False, + + /// Comparison succeed. + True, + + /// Induction is needed to complete the comparaison. + Stuck(usize), +} + +impl State { + /// Checks if the comparison succeeded. + fn is_true(&self) -> bool { + matches!(self, State::True) + } +} + +impl<'arena> Level<'arena> { + /// Helper function for equality checking, used to substitute `Var(i)` with `Z` and `S(Var(i))`. + fn substitute_single(self, var: usize, u: Self, arena: &mut Arena<'arena>) -> Self { + match *self { + Succ(n) => n.substitute_single(var, u, arena).succ(arena), + Max(n, m) => Level::max(n.substitute_single(var, u, arena), m.substitute_single(var, u, arena), arena), + IMax(n, m) => Level::imax(n.substitute_single(var, u, arena), m.substitute_single(var, u, arena), arena), + Var(n) if n == var => u, + _ => self, + } + } + + /// Substitutes all level variables in `self` according to `univs`. + /// + /// This function makes no verification that `univs` has an appropriate size, which is way it + /// cannot be made public. + pub(crate) fn substitute(self, univs: &[Self], arena: &mut Arena<'arena>) -> Self { + match *self { + Zero => self, + Succ(n) => n.substitute(univs, arena).succ(arena), + Max(n, m) => Level::max(n.substitute(univs, arena), m.substitute(univs, arena), arena), + IMax(n, m) => Level::imax(n.substitute(univs, arena), m.substitute(univs, arena), arena), + Var(var) => *univs.get(var).unwrap_or_else(|| unreachable!()), + } + } + + /// Checks whether `self <= rhs + n`, without applying substitution. + /// + /// Precisely, it returns: + /// - `Stuck(i + 1)` if `Var(i)` needs to be substituted to unstuck the comparison. + /// - `True` if `self <= rhs + n`, + /// - `False` else. + fn geq_no_subst(self, rhs: Self, n: i64) -> State { + match (&*self, &*rhs) { + (Zero, _) if n >= 0 => State::True, + + (_, _) if self == rhs && n >= 0 => State::True, + + (Succ(l), _) if l.geq_no_subst(rhs, n - 1).is_true() => State::True, + (_, Succ(l)) if self.geq_no_subst(*l, n + 1).is_true() => State::True, + + (_, Max(l1, l2)) + if self.geq_no_subst(*l1, n).is_true() || self.geq_no_subst(*l2, n).is_true() => + { + State::True + } + (Max(l1, l2), _) if l1.geq_no_subst(rhs, n).is_true() && l2.geq_no_subst(rhs, n).is_true() => { + State::True + } + + (_, IMax(_, v)) | (IMax(_, v), _) if let Var(i) = **v => State::Stuck(i), + + _ => State::False, + } + } + + /// Checks whether `self <= rhs + n`. + /// + /// In a case where comparison is stuck because of a variable `Var(i)`, it checks whether the test is correct when `Var(i)` + /// is substituted for `0` and `S(Var(i))`. + pub fn geq(self, rhs: Self, n: i64, arena: &mut Arena<'arena>) -> bool { + match self.geq_no_subst(rhs, n) { + State::True => true, + State::False => false, + State::Stuck(i) => { + let zero = Level::zero(arena); + let vv = Level::var(i, arena).succ(arena); + self.substitute_single(i, zero, arena).geq(rhs.substitute_single(i, zero, arena), n, arena) + && self.substitute_single(i, vv, arena).geq(rhs.substitute_single(i, vv, arena), n, arena) + }, + } + } + + /// Checks whether `self = rhs`. + /// + /// This is a "conversion" equality test, not the equality function used by [`PartialEq`]. + pub fn is_eq(self, rhs: Self, arena: &mut Arena<'arena>) -> bool { + self.geq(rhs, 0, arena) && rhs.geq(self, 0, arena) + } +} + +#[cfg(test)] +mod tests { + use crate::memory::arena::use_arena; + use crate::memory::level::builder::raw::*; + use crate::memory::level::Level; + + #[test] + fn univ_eq() { + use_arena(|arena| { + let two = arena.build_level_raw(succ(succ(zero()))); + let one = arena.build_level_raw(succ(zero())); + let zero_ = arena.build_level_raw(zero()); + let var0 = Level::var(0, arena); + let var1 = Level::var(1, arena); + let succ_var0 = Level::succ(var0, arena); + let max1_var0 = Level::max(one, var0, arena); + let max0_var0 = Level::max(zero_, var0, arena); + let imax_0_var0 = Level::imax(zero_, var0, arena); + let max_var0_var1 = Level::max(var0, var1, arena); + let max_var1_var0 = Level::max(var1, var0, arena); + let max_var1_var1 = Level::max(var1, var1, arena); + let succ_max_var0_var1 = Level::succ(max_var0_var1, arena); + let max_succ_var0_succ_var1 = arena.build_level_raw(max(succ(var(0)), succ(var(1)))); + + assert!(!two.geq_no_subst(succ_var0, 0).is_true()); + assert!(imax_0_var0.is_eq(var0, arena)); + assert!(max1_var0.geq(succ_var0, 0, arena)); + assert!(!zero_.is_eq(one, arena)); + assert!(!one.is_eq(zero_, arena)); + assert!(var0.is_eq(max0_var0, arena)); + assert!(max0_var0.is_eq(var0, arena)); + assert!(max_var0_var1.is_eq(max_var1_var0, arena)); + assert!(!max_var1_var1.is_eq(max_var1_var0, arena)); + assert!(succ_max_var0_var1.is_eq(max_succ_var0_succ_var1, arena)); + assert!(var0.is_eq(Level::imax(var0, var0, arena), arena)); + assert!(arena.build_level_raw(imax(succ(zero()), max(zero(), zero()))).is_eq(zero_, arena)); + assert!(!Level::imax(var0, var1, arena).is_eq(Level::imax(var1, var0, arena), arena)); + + assert!( + (arena.build_level_raw(max(zero(), imax(zero(), max(succ(zero()), zero()))))) + .is_eq(arena.build_level_raw(imax(succ(zero()), imax(succ(zero()), succ(zero())))), arena) + ) + }); + } + + #[test] + fn subst() { + use_arena(|arena| { + let lvl = Level::imax( + Level::succ(Level::zero(arena), arena), + arena.build_level_raw(max(succ(zero()), max(var(0), var(1)))), + arena, + ); + let subst = vec![arena.build_level_raw(succ(zero())), arena.build_level_raw(zero())]; + assert_eq!( + lvl.substitute(&subst, arena), + arena.build_level_raw(imax(zero(), max(succ(zero()), max(succ(zero()), zero())))) + ) + }) + } + #[test] + fn single_subst() { + use_arena(|arena| { + let lvl = arena.build_level_raw(imax(max(succ(zero()), var(0)), var(0))); + assert_eq!( + lvl.substitute_single(0, Level::zero(arena), arena), + arena.build_level_raw(imax(max(succ(zero()), zero()), zero())) + ) + }) + } +} diff --git a/kernel/src/calculus/mod.rs b/kernel/src/calculus/mod.rs new file mode 100644 index 00000000..850844f0 --- /dev/null +++ b/kernel/src/calculus/mod.rs @@ -0,0 +1,5 @@ +//! A collection of computational functions over different types of +//! [dwellers](crate::memory::arena::Arena) + +pub mod level; +pub mod term; diff --git a/kernel/src/calculus/term.rs b/kernel/src/calculus/term.rs new file mode 100644 index 00000000..e1d5325e --- /dev/null +++ b/kernel/src/calculus/term.rs @@ -0,0 +1,412 @@ +//! A set of lambda-calculus quasi-primitives. +//! +//! This module consists of internal utility functions used by the type checker, and correspond to +//! usual functions over lambda-terms. These functions interact appropriately with a given arena. + +use Payload::*; + +use crate::memory::arena::Arena; +use crate::memory::declaration::InstantiatedDeclaration; +use crate::memory::level::Level; +use crate::memory::term::{Payload, Term}; + +impl<'arena> Term<'arena> { + /// Apply one step of β-reduction, using the leftmost-outermost evaluation strategy. + pub fn beta_reduction(self, arena: &mut Arena<'arena>) -> Self { + match *self { + App(t1, t2) => match *t1 { + Abs(_, t1) => t1.substitute(t2, 1, arena), + _ => { + let t1_new = t1.beta_reduction(arena); + if t1_new == t1 { + let t2_new = t2.beta_reduction(arena); + t1.app(t2_new, arena) + } else { + t1_new.app(t2, arena) + } + }, + }, + Abs(arg_type, body) => { + let body = body.beta_reduction(arena); + arg_type.abs(body, arena) + }, + Prod(arg_type, body) => { + let body = body.beta_reduction(arena); + arg_type.prod(body, arena) + }, + Decl(decl) => decl.get_term(arena), + _ => self, + } + } + + /// Returns the term `self` where all variables with de Bruijn index larger than `depth` are offset + /// by `offset`. + pub(crate) fn shift(self, offset: usize, depth: usize, arena: &mut Arena<'arena>) -> Self { + match *self { + Var(i, type_) if i > depth.into() => Term::var(i + offset.into(), type_, arena), + App(t1, t2) => { + let t1 = t1.shift(offset, depth, arena); + let t2 = t2.shift(offset, depth, arena); + t1.app(t2, arena) + }, + Abs(arg_type, body) => { + let arg_type = arg_type.shift(offset, depth, arena); + let body = body.shift(offset, depth + 1, arena); + arg_type.abs(body, arena) + }, + Prod(arg_type, body) => { + let arg_type = arg_type.shift(offset, depth, arena); + let body = body.shift(offset, depth + 1, arena); + arg_type.prod(body, arena) + }, + _ => self, + } + } + + /// Returns the term `self` where all instances of the variable tracked by `depth` are substituted + /// with `sub`. + pub(crate) fn substitute(self, sub: Self, depth: usize, arena: &mut Arena<'arena>) -> Self { + arena.get_subst_or_init(&(self, sub, depth), |arena| match *self { + Var(i, _) if i == depth.into() => sub.shift(depth - 1, 0, arena), + Var(i, type_) if i > depth.into() => Term::var(i - 1.into(), type_, arena), + App(l, r) => { + let l = l.substitute(sub, depth, arena); + let r = r.substitute(sub, depth, arena); + l.app(r, arena) + }, + Abs(arg_type, body) => { + let arg_type = arg_type.substitute(sub, depth, arena); + let body = body.substitute(sub, depth + 1, arena); + arg_type.abs(body, arena) + }, + Prod(arg_type, body) => { + let arg_type = arg_type.substitute(sub, depth, arena); + let body = body.substitute(sub, depth + 1, arena); + arg_type.prod(body, arena) + }, + _ => self, + }) + } + + /// Substitutes all level variables in `self` according to the correspondence given by + /// `univs`. + /// + /// This function would be safe to use from outside the kernel, but would serve no purpose as + /// level variables there can only appear behind a Declaration, which prevents the access to + /// the underlying Term. + pub(crate) fn substitute_univs(self, univs: &[Level<'arena>], arena: &mut Arena<'arena>) -> Self { + match *self { + Var(i, ty) => { + let ty = ty.substitute_univs(univs, arena); + Term::var(i, ty, arena) + }, + + Sort(level) => { + let subst = level.substitute(univs, arena); + Term::sort(subst, arena) + }, + App(u1, u2) => { + let u1 = u1.substitute_univs(univs, arena); + let u2 = u2.substitute_univs(univs, arena); + u1.app(u2, arena) + }, + Abs(u1, u2) => { + let u1 = u1.substitute_univs(univs, arena); + let u2 = u2.substitute_univs(univs, arena); + u1.abs(u2, arena) + }, + + Prod(u1, u2) => { + let u1 = u1.substitute_univs(univs, arena); + let u2 = u2.substitute_univs(univs, arena); + u1.prod(u2, arena) + }, + + Decl(decl) => { + // TODO (#14) this can be slightly optimised in space. Certainly the substitution mapping can be + // performed in place while allocating the slice in the arena with store_level_slice. This + // function thus has to be made with templates. + let params = &*decl.params.iter().map(|level| level.substitute(univs, arena)).collect::<Vec<Level>>(); + let params = arena.store_level_slice(params); + let inst = InstantiatedDeclaration::instantiate(decl.decl, params, arena); + Term::decl(inst, arena) + }, + } + } + + /// Returns the normal form of a term. + /// + /// This function is computationally expensive and should only be used for reduce/eval commands, not when type-checking. + pub fn normal_form(self, arena: &mut Arena<'arena>) -> Self { + let mut temp = self; + let mut res = self.beta_reduction(arena); + + while res != temp { + temp = res; + res = res.beta_reduction(arena); + } + res + } + + /// Returns the weak-head normal form of a term. + pub fn whnf(self, arena: &mut Arena<'arena>) -> Self { + self.get_whnf_or_init(|| match *self { + App(t1, t2) => match *t1.whnf(arena) { + Abs(_, t1) => { + let subst = t1.substitute(t2, 1, arena); + subst.whnf(arena) + }, + _ => self, + }, + _ => self, + }) + } +} + +#[cfg(test)] +mod tests { + // /!\ most terms used in these tests are ill-typed; they should not be used elsewhere + use crate::memory::arena::use_arena; + use crate::memory::declaration; + use crate::memory::term::builder::raw::*; + + #[test] + fn simple_subst() { + use_arena(|arena| { + // λx.(λy.x y) x + let term = arena.build_term_raw(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_term_raw(abs(prop(), app(var(1.into(), prop()), var(1.into(), prop())))); + + assert_eq!(term.beta_reduction(arena), 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_term_raw(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_term_raw(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_term_raw(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_term_raw(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_term_raw(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_term_raw(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_term_raw(abs(prop(), abs(prop(), app(abs(prop(), var(2.into(), prop())), var(2.into(), prop()))))); + + // λa.λb.b + let term_step_7 = arena.build_term_raw(abs(prop(), abs(prop(), var(1.into(), prop())))); + + assert_eq!(term.beta_reduction(arena), term_step_1); + assert_eq!(term_step_1.beta_reduction(arena), term_step_2); + assert_eq!(term_step_2.beta_reduction(arena), term_step_3); + assert_eq!(term_step_3.beta_reduction(arena), term_step_4); + assert_eq!(term_step_4.beta_reduction(arena), term_step_5); + assert_eq!(term_step_5.beta_reduction(arena), term_step_6); + assert_eq!(term_step_6.beta_reduction(arena), term_step_7); + assert_eq!(term_step_7.beta_reduction(arena), term_step_7); + }) + } + + #[test] + fn decl_subst() { + use_arena(|arena| { + let decl_ = crate::memory::declaration::InstantiatedDeclaration::instantiate( + crate::memory::declaration::builder::Builder::Decl(crate::memory::term::builder::Builder::Prop.into(), Vec::new()) + .realise(arena) + .unwrap(), + &Vec::new(), + arena, + ); + let decl = crate::memory::term::Term::decl(decl_, arena); + let reduced = arena.build_term_raw(prop()); + + assert_eq!(decl.beta_reduction(arena), reduced); + }) + } + + #[test] + fn shift_prod() { + use_arena(|arena| { + let reduced = prod(prop(), var(1.into(), prop())); + let term = arena.build_term_raw(app(abs(prop(), reduced), prop())); + + let reduced = arena.build_term_raw(prod(prop(), var(1.into(), prop()))); + assert_eq!(term.beta_reduction(arena), reduced) + }) + } + + #[test] + fn prod_beta_red() { + use_arena(|arena| { + let term = arena.build_term_raw(prod(prop(), app(abs(prop(), var(1.into(), prop())), var(1.into(), prop())))); + let reduced = arena.build_term_raw(prod(prop(), var(1.into(), prop()))); + + assert_eq!(term.beta_reduction(arena), reduced); + }) + } + + #[test] + fn app_red_rhs() { + use_arena(|arena| { + let term = arena.build_term_raw(abs( + prop(), + app(var(1.into(), prop()), app(abs(prop(), var(1.into(), prop())), var(1.into(), prop()))), + )); + let reduced = arena.build_term_raw(abs(prop(), app(var(1.into(), prop()), var(1.into(), prop())))); + + assert_eq!(term.beta_reduction(arena), reduced); + }) + } + + #[test] + fn normal_form() { + use_arena(|arena| { + let term = arena.build_term_raw(app( + app(app(abs(prop(), abs(prop(), abs(prop(), var(1.into(), prop())))), prop()), prop()), + prop(), + )); + let normal_form = arena.build_term_raw(prop()); + + assert_eq!(term.normal_form(arena), normal_form); + }) + } + + #[test] + fn subst_univs() { + use crate::memory::level::builder::raw::*; + + use_arena(|arena| { + let decl_ = crate::memory::declaration::InstantiatedDeclaration::instantiate( + declaration::builder::Builder::Decl(crate::memory::term::builder::Builder::Prop.into(), ["u", "v"].to_vec()) + .realise(arena) + .unwrap(), + &[arena.build_level_raw(zero()), arena.build_level_raw(zero())], + arena, + ); + + let prop_ = crate::memory::term::Term::decl(decl_, arena); + + assert_eq!(prop_.substitute_univs(&[arena.build_level_raw(zero()), arena.build_level_raw(zero())], arena), prop_); + + let vart = crate::memory::term::builder::raw::var; + + let lvl = max(succ(zero()), succ(zero())); + let term = arena.build_term_raw(abs( + sort_(lvl), + abs( + type_usize(0), + abs( + type_usize(1), + prod(vart(1.into(), type_usize(1)), app(vart(1.into(), type_usize(1)), vart(2.into(), type_usize(0)))), + ), + ), + )); + + assert_eq!(term.substitute_univs(&[arena.build_level_raw(zero()), arena.build_level_raw(zero())], arena), term); + }) + } +} diff --git a/kernel/src/error.rs b/kernel/src/error.rs index 5713f329..bf063b57 100644 --- a/kernel/src/error.rs +++ b/kernel/src/error.rs @@ -2,8 +2,7 @@ use derive_more::{Display, From}; -use crate::term::arena::Term; -use crate::term::builders::DefinitionError; +use crate::memory::*; use crate::type_checker::TypeCheckerError; /// Type representing kernel errors. @@ -20,10 +19,15 @@ pub struct Error<'arena> { #[derive(Clone, Debug, Display, Eq, PartialEq, From)] pub enum ErrorKind<'arena> { TypeChecker(TypeCheckerError<'arena>), - Definition(DefinitionError<'arena>), + Term(term::builder::TermError<'arena>), + Level(level::builder::LevelError<'arena>), + Declaration(declaration::builder::DeclarationError<'arena>), } impl<'arena> std::error::Error for Error<'arena> {} pub type Result<'arena, T> = std::result::Result<T, Error<'arena>>; -pub type ResultTerm<'arena> = Result<'arena, Term<'arena>>; +pub type ResultTerm<'arena> = Result<'arena, term::Term<'arena>>; +pub type ResultLevel<'arena> = Result<'arena, level::Level<'arena>>; +pub type ResultDecl<'arena> = Result<'arena, declaration::Declaration<'arena>>; +pub type ResultInstantiatedDecl<'arena> = Result<'arena, declaration::InstantiatedDeclaration<'arena>>; diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index fae36aaa..b795a4b3 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -2,15 +2,17 @@ //! A kernel for the calculus of constructions. //! -//! Terms can be built with functions from the [`term`] module. This module also provides essential -//! manipulation functions from lambda-calculus, while the [`type_checker`] module provides typed -//! interactions. +//! Terms can be built with functions from the [`memory::term`] module. The [`calculus`] module +//! provides essential manipulation functions from lambda-calculus, while the [`type_checker`] +//! module provides typed interactions. #![feature(no_coverage)] #![feature(once_cell)] #![feature(trait_alias)] +#![feature(if_let_guard)] +pub mod calculus; pub mod error; pub mod location; -pub mod term; +pub mod memory; pub mod type_checker; diff --git a/kernel/src/memory/arena.rs b/kernel/src/memory/arena.rs new file mode 100644 index 00000000..35f762e2 --- /dev/null +++ b/kernel/src/memory/arena.rs @@ -0,0 +1,243 @@ +//! A comprehensive memory management unit for terms. +//! +//! This module defines the core functions used to manipulate an arena and its dwellers. + +use std::collections::{HashMap, HashSet}; +use std::marker::PhantomData; + +use bumpalo::Bump; + +use super::declaration::Declaration; +use super::level::Level; +use super::term::Term; + +/// A comprehensive memory management unit for terms. +/// +/// An arena is a location in memory where a group of terms with several properties is stored. Most +/// importantly, it ensures that all terms living in the arena are syntactically unique, which +/// accelerates many algorithms. In particular, this property allows for *memoizing* easily +/// operations on terms like substitution, shifting, type checking, etc. It also facilitates the +/// [building of terms](super::term::builder) which are named or use named terms. +/// +/// While [`Term`] is the most important type, it is not the only one. The arena has a general +/// concept of *dwellers*, which corresponds to types which have the same, desirable properties as +/// terms. +/// +/// This paradigm of memory management is akin to what is usually lectured for Binary Decision +/// Diagrams (BDD) management. Additionally, it makes use of Rust features to provide a clean +/// interface: the arena type is invariant over its lifetime argument (usually called `'arena`), +/// which together with the [`use_arena`] function, enforces strong guarantees on how the arena can +/// be used, particularly if several of them are used simultaneously. +/// +/// Early versions of this system are freely inspired by an assignment designed by +/// [Jacques-Henri Jourdan](<https://jhjourdan.mketjh.fr>). +pub struct Arena<'arena> { + pub(super) alloc: &'arena Bump, + + /// enforces invariances over lifetime parameter + _phantom: PhantomData<*mut &'arena ()>, + + // Hashconsing of the various dwellers, at the heart of the uniqueness property + // Please note that [`Level`] behave differently because it has an additional *reduced form* + // invariant. + pub(super) hashcons_terms: HashSet<&'arena super::term::Node<'arena>>, + pub(super) hashcons_decls: HashSet<&'arena super::declaration::Node<'arena>>, + pub(super) hashcons_levels: HashMap<&'arena super::level::Node<'arena>, super::level::Level<'arena>>, + + named_decls: HashMap<&'arena str, Declaration<'arena>>, + named_terms: HashMap<&'arena str, Term<'arena>>, + + /// Hash maps used to speed up certain algorithms. See also `OnceCell`s in [`Term`] + pub(super) mem_subst: HashMap<(Term<'arena>, Term<'arena>, usize), Term<'arena>>, + // TODO shift hashmap (see #45) + // requires the design of an additional is_certainly_closed predicate in terms. +} + +/// This function is the main function that the kernel exports. Most importantly, it is the only +/// one to provide an entry point for Arena objects, by means of a closure provided by the end +/// user. +/// +/// Such an interface is the most elegant way to ensure the one-to-one correspondence between +/// lifetime parameters and [`Arena`] objects. +/// +/// To generate the `alloc` object in this function is necessary, as this is the main way to +/// "create" a lifetime variable which makes sense. That way, `'arena` is valid exactly during +/// the execution of the function `f`. +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) +} + +impl<'arena> Arena<'arena> { + /// Allocates a new memory arena. As detailed in the [`use_arena`] function, it is necessary to + /// externalise the generation of the [`bumpalo::Bump`] object. + fn new(alloc: &'arena Bump) -> Self { + Arena { + alloc, + _phantom: PhantomData, + + hashcons_terms: HashSet::new(), + hashcons_decls: HashSet::new(), + hashcons_levels: HashMap::new(), + + named_decls: HashMap::new(), + named_terms: HashMap::new(), + + mem_subst: HashMap::new(), + } + } + + /// Stores a slice of levels in the arena. + /// + /// This is most importantly used by [instantiated + /// declarations](`super::declaration::instantiatedDeclaration`). + pub(crate) fn store_level_slice(&mut self, slice: &[Level<'arena>]) -> &'arena [Level<'arena>] { + self.alloc.alloc_slice_copy(slice) + } + + /// Stores a string in the arena. + /// + /// This is typically done to ensure strings live long enough when manipulating them. + pub(crate) fn store_name(&mut self, name: &str) -> &'arena str { + self.alloc.alloc_str(name) + } + + /// Binds a term to a given name. + pub fn bind(&mut self, name: &str, t: Term<'arena>) { + let name = self.store_name(name); + self.named_terms.insert(name, t); + } + + /// Binds a declaration to a given name. + pub fn bind_decl(&mut self, name: &str, decl: Declaration<'arena>) { + let name = self.store_name(name); + self.named_decls.insert(name, decl); + } + + /// Retrieves the binding of a given name, if one exists. + pub fn get_binding(&self, name: &str) -> Option<Term<'arena>> { + self.named_terms.get(name).copied() + } + + /// Retrieves the declaration binding of a given name, if one exists. + pub fn get_binding_decl(&self, name: &str) -> Option<Declaration<'arena>> { + self.named_decls.get(name).copied() + } +} + +/// This macro generates two types, $dweller and Node, parametrised by a lifetime. These types are +/// associated to a set of traits that they are expected to have by living in an arena. +macro_rules! new_dweller { + ($dweller: ident, $header: ident, $payload: ident) => { + #[doc = concat!("A ", stringify!($dweller), ". + +This type is a dweller of an [arena](crate::memory::arena::Arena). For detailed +information about the content of this type, please refer to the documentation of its +[payload](", stringify!($payload), "). + +This type is associated, through its lifetime argument, to an [`Arena`], where it +lives. There, it is guaranteed to be unique, which helps to accelerate a variety of +algorithms. It is fundamentally a pointer to an internal structure, called a Node, +which itself contains the [core content](", stringify!($payload), "). + +Additionally, the Node contains lazy structures which typically further helps +accelerating specific algorithms.")] + #[derive(Clone, Copy)] + pub struct $dweller<'arena>(&'arena Node<'arena>, std::marker::PhantomData<*mut &'arena ()>); + + pub(super) struct Node<'arena> { + header: $header<'arena>, + payload: $payload<'arena>, + } + + impl<'arena> $dweller<'arena> { + fn new(node: &'arena Node<'arena>) -> Self { + $dweller(node, std::marker::PhantomData) + } + } + + #[doc = concat!(stringify!($dweller), "s are arguably smart pointers, and as such, can be +directly dereferenced to their associated payload. + +This is done for convenience, as it allows to manipulate the terms relatively seamlessly. + +# Example + +For a [`Term`](crate::memory::term::Term), it is possible to write: +``` +# use kernel::memory::arena::use_arena; +# use kernel::memory::term::Payload::*; +# use kernel::memory::term::builder::prop; +# use_arena(|arena| { +# let t = arena.build(prop()).unwrap(); +match *t { + Abs(_, t2) => t2.beta_reduction(arena), + App(t1, _) => t1, + _ => t, +} +# ;}) +``` +Please note that this trait has some limits. For instance, the notations used to match against +a *pair* of", stringify!($dweller), "s still requires some convolution.")] + impl<'arena> std::ops::Deref for $dweller<'arena> { + type Target = $payload<'arena>; + + fn deref(&self) -> &Self::Target { + &self.0.payload + } + } + + #[doc = concat!("Debug mode only prints the payload of a ", stringify!($dweller), ". + +Apart from enhancing debug readability, this reimplementation is surprisingly necessary: in +the case of terms for instance, and because they may refer to themselves in the payload, the +default debug implementation recursively calls itself until the stack overflows.")] + impl<'arena> std::fmt::Debug for $dweller<'arena> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0.payload.fmt(f) + } + } + + #[doc = concat!("Because ", stringify!($dweller), "s are unique in the arena, it is +sufficient to compare their locations in memory to test equality.")] + impl<'arena> PartialEq<Self> for $dweller<'arena> { + fn eq(&self, rhs: &Self) -> bool { + std::ptr::eq(self.0, rhs.0) + } + } + + impl<'arena> Eq for $dweller<'arena> {} + + #[doc = concat!("Because ", stringify!($dweller), "s are unique in the arena, it is +sufficient to compare their locations in memory to test equality. In particular, hash can +also be computed from the location")] + impl<'arena> std::hash::Hash for $dweller<'arena> { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + std::ptr::hash(self.0, state) + } + } + + impl<'arena> PartialEq<Self> for Node<'arena> { + fn eq(&self, x: &Self) -> bool { + self.payload == x.payload + } + } + + impl<'arena> Eq for Node<'arena> {} + + /// Nodes are not guaranteed to be unique. Nonetheless, only the payload matters and characterises + /// the value. Which means computing the hash for nodes can be restricted to hashing their + /// payloads. + impl<'arena> std::hash::Hash for Node<'arena> { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.payload.hash(state); + } + } + }; +} + +pub(super) use new_dweller; diff --git a/kernel/src/memory/declaration/builder.rs b/kernel/src/memory/declaration/builder.rs new file mode 100644 index 00000000..00b4b719 --- /dev/null +++ b/kernel/src/memory/declaration/builder.rs @@ -0,0 +1,185 @@ +//! A collection of safe functions to build [`Declaration`]s. and [`InstantiatedDeclaration`]s. +//! +//! This module provides two main ways of building these. The first one is via closures: users can +//! manipulate closures and create bigger ones which, when [built](Arena::build), provide the expected +//! term. +//! +//! The overall syntax remains transparent to the user. This means the user focuses on the +//! structure of the term they want to build, while the [closures](`BuilderTrait`) internally build an appropriate +//! logic; namely verifying the correct amount of variables have been supplied or bound. + +use derive_more::Display; + +use super::{Declaration, InstantiatedDeclaration}; +use crate::error::{Error, Result, ResultDecl, ResultInstantiatedDecl}; +use crate::memory::arena::Arena; +use crate::memory::level::builder as level; +use crate::memory::term::builder as term; + +#[non_exhaustive] +#[derive(Clone, Debug, Display, Eq, PartialEq)] +pub enum DeclarationError<'arena> { + #[display(fmt = "expected {_0} universe variables, got {_1}")] + IncorrectVariableNumber(usize, usize), + + #[display(fmt = "unknown declaration {_0}")] + UnknownDeclaration(&'arena str), +} + +/// The trait of builders producing declarations. +/// +/// Note that, unlike the other building traits, this one only takes an arena as an argument. +/// This is because declarations cannot be declared locally (that is, within a context where some +/// extra local variables are bound by lambda-abstraction). +pub trait BuilderTrait<'build> = for<'arena> FnOnce(&mut Arena<'arena>) -> ResultDecl<'arena>; + +pub trait InstantiatedBuilderTrait<'build> = + for<'arena> FnOnce(&mut Arena<'arena>, &level::Environment<'build>) -> ResultInstantiatedDecl<'arena>; + +impl<'arena> Arena<'arena> { + /// Returns the declaration built from the given closure, provided with an empty context, at + /// depth 0. + #[inline] + pub fn build_declaration<'build, F: BuilderTrait<'build>>(&mut self, f: F) -> ResultDecl<'arena> { + f(self) + } + + /// Returns the instantiated declaration built from the given closure, provided with an empty + /// context, at depth 0. + #[inline] + pub fn build_instantiated_declaration<'build, F: InstantiatedBuilderTrait<'build>>( + &mut self, + f: F, + ) -> ResultInstantiatedDecl<'arena> { + f(self, &level::Environment::new()) + } +} + +/// Returns a builder creating `term`, where universe variables are described by `vars`. +pub fn declaration<'build, F: term::BuilderTrait<'build>>(term: F, vars: &[&'build str]) -> impl BuilderTrait<'build> { + let len = vars.len(); + let lvl_env = vars.iter().enumerate().map(|(n, name)| (*name, n)).collect(); + move |arena| Ok(Declaration::new(term(arena, &term::Environment::new(), &lvl_env, 0.into())?, len)) +} + +/// Template of declarations. +#[derive(Clone, Debug, Display, PartialEq, Eq)] +pub enum Builder<'build> { + #[display(fmt = "{_0}")] + Decl(Box<term::Builder<'build>>, Vec<&'build str>), +} + +impl<'build> Builder<'build> { + /// Realise a builder into a [`Declaration`]. This internally uses functions described in + /// the [builder](`crate::memory::declaration::builder`) module. + pub fn realise<'arena>(&self, arena: &mut Arena<'arena>) -> ResultDecl<'arena> { + arena.build_declaration(self.partial_application()) + } + + fn partial_application(&self) -> impl BuilderTrait<'build> + '_ { + |arena| self.realise_in_context(arena) + } + + fn realise_in_context<'arena>(&self, arena: &mut Arena<'arena>) -> ResultDecl<'arena> { + match self { + Builder::Decl(term, vars) => declaration(term.partial_application(), vars.as_slice())(arena), + } + } +} + +fn try_build_instance<'arena, 'build>( + decl: Declaration<'arena>, + levels: &'build [level::Builder<'build>], + arena: &mut Arena<'arena>, + env: &level::Environment<'build>, +) -> ResultInstantiatedDecl<'arena> { + let levels = levels.iter().map(|level_builder| level_builder.realise_in_context(arena, env)).collect::<Result<Vec<_>>>()?; + + if decl.1 == levels.len() { + Ok(InstantiatedDeclaration::instantiate(decl, levels.as_slice(), arena)) + } else { + Err(Error { + kind: DeclarationError::IncorrectVariableNumber(decl.1, levels.len()).into(), + }) + } +} + +/// Returns a builder creating the declaration built by `decl` instantiated with the universe +/// levels `levels`. +/// +/// Please note that this is the only function from the closure API requiring the use of Builders. +/// This is by choice, as opposed to the other possibility, where `levels` would be a slice over +/// `Box<dyn level::BuilderTrait>`, which is not interesting performance-wise. +pub fn instance<'build, F: BuilderTrait<'build>>( + decl: F, + levels: &'build [level::Builder<'build>], +) -> impl InstantiatedBuilderTrait<'build> { + move |arena, env| try_build_instance(decl(arena)?, levels, arena, env) +} + +/// Returns a builder creating the declaration bound by `name`, instantiated with the universe +/// levels `levels`. +pub fn var<'build>(name: &'build str, levels: &'build [level::Builder<'build>]) -> impl InstantiatedBuilderTrait<'build> { + move |arena, env| { + let decl = arena.get_binding_decl(name).ok_or(Error { + kind: DeclarationError::UnknownDeclaration(arena.store_name(name)).into(), + })?; + try_build_instance(decl, levels, arena, env) + } +} + +/// A builder for instantiated declarations. +/// +/// While there is fundamentally only one way to instantiate a declaration, this builder exposes +/// two variants: the [first](`InstantiatedBuilder::Instance`) is to be used typically through the +/// API, in a context where, for instance, the associated declaration is not bound in the arena. +/// +/// On the other hand, the [second variant](`InstantiatedBuilder::Var`) is typically used by the +/// parser, as it corresponds to the only possible scenario in a file. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum InstantiatedBuilder<'build> { + Instance(Box<Builder<'build>>, Vec<level::Builder<'build>>), + + Var(&'build str, Vec<level::Builder<'build>>), +} + +impl<'arena> std::fmt::Display for InstantiatedBuilder<'arena> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use InstantiatedBuilder::*; + match self { + Instance(decl, params) => { + write!(f, "{decl}.{{")?; + params.iter().try_for_each(|level| write!(f, "{level}, "))?; + write!(f, "}}") + }, + Var(decl, params) => { + write!(f, "{decl}.{{")?; + params.iter().try_for_each(|level| write!(f, "{level}, "))?; + write!(f, "}}") + }, + } + } +} +impl<'build> InstantiatedBuilder<'build> { + /// Realise a builder into an [`InstantiatedDeclaration`]. This internally uses functions described in + /// the [builder](`crate::memory::declaration::builder`) module. + pub fn realise<'arena>(&self, arena: &mut Arena<'arena>) -> ResultInstantiatedDecl<'arena> { + arena.build_instantiated_declaration(self.partial_application()) + } + + pub(in crate::memory) fn partial_application(&'build self) -> impl InstantiatedBuilderTrait<'build> { + |arena, lvl_env| self.realise_in_context(arena, lvl_env) + } + + fn realise_in_context<'arena>( + &'build self, + arena: &mut Arena<'arena>, + lvl_env: &level::Environment<'build>, + ) -> ResultInstantiatedDecl<'arena> { + use InstantiatedBuilder::*; + match self { + Instance(decl, levels) => instance(decl.partial_application(), levels)(arena, lvl_env), + Var(name, levels) => var(name, levels)(arena, lvl_env), + } + } +} diff --git a/kernel/src/memory/declaration/mod.rs b/kernel/src/memory/declaration/mod.rs new file mode 100644 index 00000000..a2ef8888 --- /dev/null +++ b/kernel/src/memory/declaration/mod.rs @@ -0,0 +1,105 @@ +//! universe-polymorphic declarations. + +use std::cell::OnceCell; +use std::fmt; + +use derive_more::Display; + +use super::arena::Arena; +use super::level::Level; +use super::term::Term; +use crate::error::ResultTerm; + +pub mod builder; + +/// A declaration is a term where some of its constituting universe levels may contain +/// universe-polymorphic variables. +/// +/// None of these variables may be "free". +/// +/// Declarations can be instantiated to create [`InstantiatedDeclaration`]s, which can in turn be +/// incorporated into [`Term`]s. +#[derive(Copy, Clone, Debug, Display, Eq, PartialEq, Hash)] +#[display(fmt = "{_0}")] +pub struct Declaration<'arena>(pub(crate) Term<'arena>, pub(crate) usize); + +super::arena::new_dweller!(InstantiatedDeclaration, Header, Payload); + +/// An instantiated declaration. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct Payload<'arena> { + /// The declaration being instantiated + pub decl: Declaration<'arena>, + + /// The parameters used to instantiate it + pub params: &'arena [Level<'arena>], +} + +struct Header<'arena> { + /// The corresponding term, where levels have been substituted. + term: OnceCell<Term<'arena>>, +} + +impl<'arena> fmt::Display for InstantiatedDeclaration<'arena> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0.header.term.get() { + Some(term) => write!(f, "{term}"), + None => { + write!(f, "({}).{{", self.0.payload.decl)?; + + let mut iter = self.0.payload.params.iter(); + + iter.next().map(|level| write!(f, "{level}")).unwrap_or(Ok(()))?; + iter.try_for_each(|level| write!(f, ", {level}"))?; + + write!(f, "}}") + }, + } + } +} + +impl<'arena> Declaration<'arena> { + pub(crate) fn new(term: Term<'arena>, vars: usize) -> Self { + Self(term, vars) + } +} + +impl<'arena> InstantiatedDeclaration<'arena> { + /// Creates a new instantiated declaration from its base components. It is not verified that + /// the provided slice matches in length the number of expected Levels. + pub(crate) fn instantiate(decl: Declaration<'arena>, params: &[Level<'arena>], arena: &mut Arena<'arena>) -> Self { + let new_node = Node { + header: Header { + term: OnceCell::new(), + }, + payload: Payload { + decl, + params: arena.store_level_slice(params), + }, + }; + + match arena.hashcons_decls.get(&new_node) { + Some(addr) => Self::new(addr), + None => { + let addr = arena.alloc.alloc(new_node); + arena.hashcons_decls.insert(addr); + Self::new(addr) + }, + } + } + + /// Returns the term linked to a definition in a given environment. + pub fn get_term(self, arena: &mut Arena<'arena>) -> Term<'arena> { + *self.0.header.term.get_or_init(|| self.0.payload.decl.0.substitute_univs(self.0.payload.params, arena)) + } + + /// Tries to type the generic underlying declaration. If it works, returns the type + /// corresponding to the instantiated declaration, via a universe-variable substitution. + pub(crate) fn get_type_or_try_init<F>(self, f: F, arena: &mut Arena<'arena>) -> ResultTerm<'arena> + where + F: FnOnce(Term<'arena>, &mut Arena<'arena>) -> ResultTerm<'arena>, + { + let term = self.0.payload.decl.0; + term.get_type_or_try_init(|| f(term, arena)).map(|type_| type_.substitute_univs(self.0.payload.params, arena)) + } +} diff --git a/kernel/src/memory/level/builder.rs b/kernel/src/memory/level/builder.rs new file mode 100644 index 00000000..eb702a2e --- /dev/null +++ b/kernel/src/memory/level/builder.rs @@ -0,0 +1,199 @@ +//! A collection of safe functions to build [`Level`]s. +//! +//! This module provides two main ways of building terms. The first one is via closures: users can +//! manipulate closures and create bigger ones which, when [built](Arena::build_level), provide the expected +//! level. +//! +//! The overall syntax remains transparent to the user. This means the user focuses on the +//! structure of the term they want to build, while the [closures](`BuilderTrait`) internally build an appropriate +//! logic: converting regular universe variables names into their corresponding variable numbers. +//! +//! The other way to proceed is built on top of the latter. Users can also manipulate a sort of +//! *high-level level* or *template*, described by the public enumeration [`Builder`], and at any +//! moment, [realise](Builder::realise) it. + +use std::collections::HashMap; + +use derive_more::Display; + +use super::Level; +use crate::error::{Error, ResultLevel}; +use crate::memory::arena::Arena; + +#[non_exhaustive] +#[derive(Clone, Debug, Display, Eq, PartialEq)] +pub enum LevelError<'arena> { + #[display(fmt = "unknown universe variable {}", _0)] + VarNotFound(&'arena str), +} + +/// Local environment used to store correspondence between locally-bound variables and the pair +/// (depth at which they were bound, their type) +pub type Environment<'build> = HashMap<&'build str, usize>; + +/// The trait of closures which build levels with an adequate logic. +/// +/// A call with a couple of arguments `(arena, env)` of a closure with this trait should +/// build a definite level in the [`Arena`] `arena`. +pub trait BuilderTrait<'build> = for<'arena> FnOnce(&mut Arena<'arena>, &Environment<'build>) -> ResultLevel<'arena>; + +impl<'arena> Arena<'arena> { + /// Returns the level built from the given closure, provided with a Level environment, which binds names to `usize`s + #[inline] + pub fn build_level<'build, F: BuilderTrait<'build>>(&mut self, f: F) -> ResultLevel<'arena> { + f(self, &Environment::new()) + } +} + +/// Returns a closure building a universe variable associated to `name` +#[inline] +pub const fn var(name: &str) -> impl BuilderTrait<'_> { + move |arena, env| { + env.get(name).map(|lvl| Level::var(*lvl, arena)).ok_or(Error { + kind: LevelError::VarNotFound(arena.store_name(name)).into(), + }) + } +} + +/// Returns a closure building the 0 level. +#[inline] +pub const fn zero<'build>() -> impl BuilderTrait<'build> { + |arena, _| Ok(Level::zero(arena)) +} + +/// Returns a closure building a constant level. +#[inline] +pub const fn const_<'build>(n: usize) -> impl BuilderTrait<'build> { + move |arena, _| Ok(Level::from(n, arena)) +} + +/// Returns a closure building the sum of `u` and a constant `n`. +#[inline] +#[no_coverage] +pub const fn plus<'build, F: BuilderTrait<'build>>(u: F, n: usize) -> impl BuilderTrait<'build> { + move |arena, env| Ok(u(arena, env)?.add(n, arena)) +} + +/// Returns a closure building the successor of a level built from the given closure `u1` +#[inline] +#[no_coverage] +pub const fn succ<'build, F1: BuilderTrait<'build>>(u1: F1) -> impl BuilderTrait<'build> { + |arena, env| Ok(u1(arena, env)?.succ(arena)) +} + +/// Returns a closure building the max of two levels built from the given closures `u1` and +/// `u2`. +#[inline] +#[no_coverage] +pub const fn max<'build, F1: BuilderTrait<'build>, F2: BuilderTrait<'build>>(u1: F1, u2: F2) -> impl BuilderTrait<'build> { + |arena, env| Ok(u1(arena, env)?.max(u2(arena, env)?, arena)) +} + +/// Returns a closure building the imax of two levels built from the given closures `u1` and +/// `u2`. +#[inline] +#[no_coverage] +pub const fn imax<'build, F1: BuilderTrait<'build>, F2: BuilderTrait<'build>>(u1: F1, u2: F2) -> impl BuilderTrait<'build> { + |arena, env| Ok(u1(arena, env)?.imax(u2(arena, env)?, arena)) +} + +/// Template of levels. +/// +/// A [`Builder`] describes a level in a naive but easy to build manner. It strongly resembles the +/// [`Level`] type, except that the `Var` constructor include a name, as in the syntactic way of +/// writing levels. Because its purpose is to provide an easy way to build terms, even through the +/// API, it offers different ways to build some terms, for convenience. +#[derive(Clone, Debug, Display, PartialEq, Eq)] +pub enum Builder<'builder> { + #[display(fmt = "0")] + Zero, + + Const(usize), + + #[display(fmt = "({_0}) + {_1}")] + Plus(Box<Builder<'builder>>, usize), + + #[display(fmt = "S({_0})")] + Succ(Box<Builder<'builder>>), + + #[display(fmt = "max({_0}, {_1})")] + Max(Box<Builder<'builder>>, Box<Builder<'builder>>), + + #[display(fmt = "imax({_0}, {_1})")] + IMax(Box<Builder<'builder>>, Box<Builder<'builder>>), + + Var(&'builder str), +} + +impl<'build> Builder<'build> { + /// Realise a builder into a [`Level`]. This internally uses functions described in + /// the [builder](`crate::memory::level::builder`) module. + pub fn realise<'arena>(&self, arena: &mut Arena<'arena>) -> ResultLevel<'arena> { + arena.build_level(self.partial_application()) + } + + pub(in crate::memory) fn partial_application(&self) -> impl BuilderTrait<'build> + '_ { + |arena, env| self.realise_in_context(arena, env) + } + + pub(in crate::memory) fn realise_in_context<'arena>( + &self, + arena: &mut Arena<'arena>, + env: &Environment<'build>, + ) -> ResultLevel<'arena> { + use Builder::*; + match *self { + Zero => zero()(arena, env), + Const(c) => const_(c)(arena, env), + Plus(ref u, n) => plus(u.partial_application(), n)(arena, env), + Succ(ref l) => succ(l.partial_application())(arena, env), + Max(ref l, ref r) => max(l.partial_application(), r.partial_application())(arena, env), + IMax(ref l, ref r) => imax(l.partial_application(), r.partial_application())(arena, env), + Var(s) => var(s)(arena, env), + } + } +} + +#[cfg(test)] +pub(crate) mod raw { + use super::*; + + pub trait BuilderTrait = for<'arena> FnOnce(&mut Arena<'arena>) -> Level<'arena>; + + impl<'arena> Arena<'arena> { + pub(crate) fn build_level_raw<F: BuilderTrait>(&mut self, f: F) -> Level<'arena> { + f(self) + } + } + + pub const fn var(id: usize) -> impl BuilderTrait { + move |arena| Level::var(id, arena) + } + + pub const fn zero() -> impl BuilderTrait { + |arena| Level::zero(arena) + } + + pub const fn succ<F1: BuilderTrait>(u1: F1) -> impl BuilderTrait { + |arena| { + let u1 = u1(arena); + u1.succ(arena) + } + } + + pub const fn max<F1: BuilderTrait, F2: BuilderTrait>(u1: F1, u2: F2) -> impl BuilderTrait { + |arena| { + let u1 = u1(arena); + let u2 = u2(arena); + u1.max(u2, arena) + } + } + + pub const fn imax<F1: BuilderTrait, F2: BuilderTrait>(u1: F1, u2: F2) -> impl BuilderTrait { + |arena| { + let u1 = u1(arena); + let u2 = u2(arena); + u1.imax(u2, arena) + } + } +} diff --git a/kernel/src/memory/level/mod.rs b/kernel/src/memory/level/mod.rs new file mode 100644 index 00000000..fdfb7418 --- /dev/null +++ b/kernel/src/memory/level/mod.rs @@ -0,0 +1,238 @@ +//! Universe levels. + +use std::cell::OnceCell; +use std::fmt::{Display, Formatter}; +use std::hash::Hash; + +use super::arena::Arena; + +super::arena::new_dweller!(Level, Header, Payload); + +pub mod builder; + +struct Header<'arena> { + /// The plus-form of a level + plus_form: OnceCell<(Level<'arena>, usize)>, +} + +/// A universe level. +/// +/// While types in the usual calculus of constructions live in types fully described with integers, +/// more is needed when manipulating universe-polymorphic descriptions: precisely, the right amount +/// of formal computation has to be introduced in order to account for universe-polymorphic +/// variables. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Payload<'arena> { + /// The zero level (associated to Prop) + Zero, + + /// The successor of a level + Succ(Level<'arena>), + + /// The maximum of two levels + Max(Level<'arena>, Level<'arena>), + + /// The impredicative maximum of two levels + IMax(Level<'arena>, Level<'arena>), + + /// A universe-polymorphic variable + Var(usize), +} + +impl Display for Level<'_> { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self.to_numeral() { + Some(n) => write!(f, "{n}"), + None => match **self { + Zero => unreachable!("Zero is a numeral"), + Succ(_) => { + let (u, n) = self.plus(); + write!(f, "{u} + {n}") + }, + Max(n, m) => write!(f, "max ({n}) ({m})"), + IMax(n, m) => write!(f, "imax ({n}) ({m})"), + Var(n) => write!(f, "u{n}"), + }, + } + } +} + +use Payload::*; + +impl<'arena> Level<'arena> { + /// This function is the base low-level function for creating levels. + /// + /// It enforces the uniqueness property of levels in the arena, as well as the reduced-form + /// invariant. + fn hashcons(payload: Payload<'arena>, arena: &mut Arena<'arena>) -> Self { + let new_node = Node { + payload, + header: Header { + plus_form: OnceCell::new(), + }, + }; + + match arena.hashcons_levels.get(&new_node) { + Some(level) => *level, + None => { + // add the unreduced node to the arena + let node_unreduced = &*arena.alloc.alloc(new_node); + let level_unreduced = Level::new(node_unreduced); + arena.hashcons_levels.insert(node_unreduced, level_unreduced); + + // compute its reduced form + let reduced = level_unreduced.normalize(arena); + + // supersede the previous correspondence + arena.hashcons_levels.insert(node_unreduced, reduced); + arena.hashcons_levels.insert(reduced.0, reduced); + + reduced + }, + } + } + + /// Returns the 0-level + pub(crate) fn zero(arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Zero, arena) + } + + /// Returns the successor of a level + pub(crate) fn succ(self, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Succ(self), arena) + } + + /// Returns the level corresponding to the maximum of two levels + pub(crate) fn max(self, right: Self, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Max(self, right), arena) + } + + /// Returns the level corresponding to the impredicative maximum of two levels + pub(crate) fn imax(self, right: Self, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(IMax(self, right), arena) + } + + /// Returns the level associated to a given variable. + pub(crate) fn var(id: usize, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Var(id), arena) + } + + /// The addition of a level and an integer + pub fn add(self, n: usize, arena: &mut Arena<'arena>) -> Self { + if n == 0 { + self + } else { + let s = self.add(n - 1, arena); + s.succ(arena) + } + } + + /// Builds a level from an integer + pub fn from(n: usize, arena: &mut Arena<'arena>) -> Self { + Level::zero(arena).add(n, arena) + } + + /// Converts a level to an integer, if possible + pub fn to_numeral(self) -> Option<usize> { + let (u, n) = self.plus(); + (*u == Zero).then_some(n) + } + + /// Decomposes a level `l` in the best pair `(u, n)` s.t. `l = u + n` + pub fn plus(self) -> (Self, usize) { + *self.0.header.plus_form.get_or_init(|| match *self { + Succ(u) => { + let (u, n) = u.plus(); + (u, n + 1) + }, + _ => (self, 0), + }) + } + + /// Helper function for universe comparison. normalizes imax(es) as follows: + /// - `imax(0, u) = u` + /// - `imax(u, 0) = u` + /// - `imax(u, S(v)) = max(u, S(v))` + /// - `imax(u, imax(v, w)) = max(imax(u, w), imax(v, w))` + /// - `imax(u, max(v, w)) = max(imax(u, v), imax(u, w))` + /// + /// The function also reduces max. This is further helpful when trying to print the type. + /// + /// Here, the imax normalization pushes imaxes to all have a `Var(i)` as the second argument. To solve this last case, one needs + /// to substitute `Var(i)` with `0` and `S(Var(i))`. This gives us a consistent way to unstuck the geq-checking. + fn normalize(self, arena: &mut Arena<'arena>) -> Self { + match *self { + IMax(u, v) => { + if u == v { + u + } else { + match &*v { + Zero => v, + Succ(_) => u.max(v, arena), + IMax(_, vw) => Level::max(u.imax(*vw, arena), v, arena), + Max(vv, vw) => Level::max(u.imax(*vv, arena), u.imax(*vw, arena), arena), + _ => self, + } + } + }, + + Max(u, v) => { + if u == v { + u + } else { + match (&*u, &*v) { + (Zero, _) => v, + (_, Zero) => u, + (Succ(uu), Succ(vv)) => Level::max(*uu, *vv, arena).succ(arena), + _ => self, + } + } + }, + _ => self, + } + } +} + +#[cfg(test)] +mod pretty_printing { + + use crate::memory::arena::use_arena; + use crate::memory::level::builder::raw::*; + use crate::memory::level::Level; + + #[test] + fn to_num() { + use_arena(|arena| { + assert_eq!(arena.build_level_raw(max(succ(zero()), zero())).to_numeral(), Some(1)); + assert_eq!(arena.build_level_raw(max(succ(zero()), succ(var(0)))).to_numeral(), None); + assert_eq!(arena.build_level_raw(imax(var(0), zero())).to_numeral(), Some(0)); + assert_eq!(arena.build_level_raw(imax(zero(), succ(zero()))).to_numeral(), Some(1)) + }) + } + + #[test] + fn to_plus() { + use_arena(|arena| { + assert_eq!(arena.build_level_raw(succ(zero())).plus(), (Level::zero(arena), 1)); + }) + } + + #[test] + fn to_pretty_print() { + use_arena(|arena| { + assert_eq!( + format!("{}", arena.build_level_raw(max(succ(zero()), imax(max(zero(), var(0)), succ(var(0)))))), + "max (1) (max (u0) (u0 + 1))" + ) + }) + } + + #[test] + fn normalize() { + use_arena(|arena| { + let lvl = arena.build_level_raw(imax(zero(), imax(zero(), imax(succ(zero()), var(0))))); + + assert_eq!(format!("{lvl}"), "max (imax (0) (u0)) (max (imax (0) (u0)) (imax (1) (u0)))") + }) + } +} diff --git a/kernel/src/memory/mod.rs b/kernel/src/memory/mod.rs new file mode 100644 index 00000000..8cf67bcb --- /dev/null +++ b/kernel/src/memory/mod.rs @@ -0,0 +1,11 @@ +//! Abstracted memory manipulation primitives. +//! +//! This module provides a paradigm for building and manipulating [terms](term::Term) in the +//! calculus of construction, centered around the notion of [arena](`arena::Arena`). Terms also +//! rely on other structures like [declarations](declaration::Declaration) and [universe +//! levels](level::Level). + +pub mod arena; +pub mod declaration; +pub mod level; +pub mod term; diff --git a/kernel/src/memory/term/builder.rs b/kernel/src/memory/term/builder.rs new file mode 100644 index 00000000..3cb24e68 --- /dev/null +++ b/kernel/src/memory/term/builder.rs @@ -0,0 +1,284 @@ +//! A collection of safe functions to build [`Term`]s. +//! +//! This module provides two main ways of building terms. The first one is via closures: users can +//! manipulate closures and create bigger ones which, when [built](Arena::build), provide the expected +//! term. +//! +//! The overall syntax remains transparent to the user. This means the user focuses on the +//! structure of the term they want to build, while the [closures](`BuilderTrait`) internally build an appropriate +//! logic: converting regular terms into de Bruijn-compatible ones, assigning types to variables, +//! etc. +//! +//! The other way to proceed is built on top of the latter. Users can also manipulate a sort of +//! *high-level term* or *template*, described by the public enumeration [`Builder`], and at any +//! moment, [realise](Builder::realise) it. + +use derive_more::Display; +use im_rc::hashmap::HashMap as ImHashMap; + +use super::{DeBruijnIndex, Term}; +use crate::error::{Error, ResultTerm}; +use crate::memory::arena::Arena; +use crate::memory::declaration::builder as declaration; +use crate::memory::level::builder as level; + +#[non_exhaustive] +#[derive(Clone, Debug, Display, Eq, PartialEq)] +pub enum TermError<'arena> { + #[display(fmt = "unknown identifier {}", _0)] + ConstNotFound(&'arena str), +} + +/// Local environment used to store correspondence between locally-bound variables and the pair +/// (depth at which they were bound, their type) +pub type Environment<'build, 'arena> = ImHashMap<&'build str, (DeBruijnIndex, Term<'arena>)>; + +/// The trait of closures which build terms with an adequate logic. +/// +/// A call with a quadruplet of arguments `(arena, env, lvl_env, index)` of a closure with this +/// trait should build a definite term in the [`Arena`] `arena`, knowing the bindings declared in +/// `env` and `lvl_env`, provided that the term is built at a current depth `index`. +/// +/// Please note that this is just a trait alias, meaning it enforces few constraints: while +/// functions in this module returning a closure with this trait are guaranteed to be sound, end +/// users can also create their own closures satisfying `BuilderTrait`; this should be avoided. +pub trait BuilderTrait<'build> = for<'arena> FnOnce( + &mut Arena<'arena>, + &Environment<'build, 'arena>, + &level::Environment<'build>, + DeBruijnIndex, +) -> ResultTerm<'arena>; + +impl<'arena> Arena<'arena> { + /// Returns the term built from the given closure, provided with an empty context, at depth 0. + #[inline] + pub fn build<'build, F: BuilderTrait<'build>>(&mut self, f: F) -> ResultTerm<'arena> { + f(self, &Environment::new(), &level::Environment::new(), 0.into()) + } +} + +/// Returns a closure building a variable associated to the name `name` +#[inline] +pub const fn var(name: &str) -> impl BuilderTrait<'_> { + move |arena, env, _, depth| { + env.get(name) + .map(|(bind_depth, term)| { + // This is arguably an eager computation, it could be worth making it lazy, + // or at least memoizing it so as to not compute it again + let var_type = term.shift(usize::from(depth - *bind_depth), 0, arena); + Term::var(depth - *bind_depth, var_type, arena) + }) + .or_else(|| arena.get_binding(name)) + .ok_or(Error { + kind: TermError::ConstNotFound(arena.store_name(name)).into(), + }) + } +} + +/// Returns a closure building the Prop term. +#[inline] +pub const fn prop<'build>() -> impl BuilderTrait<'build> { + |arena, _, _, _| Ok(Term::prop(arena)) +} + +/// Returns a closure building the Type `level` term. +#[inline] +#[no_coverage] +pub const fn type_<'build, F: level::BuilderTrait<'build>>(level: F) -> impl BuilderTrait<'build> { + move |arena, _, lvl_env, _| Ok(Term::sort(level(arena, lvl_env)?.succ(arena), arena)) +} + +/// Returns a closure building the Type `level` term (indirection from `usize`). +#[inline] +pub const fn type_usize<'build>(level: usize) -> impl BuilderTrait<'build> { + move |arena, _, _, _| Ok(Term::type_usize(level, arena)) +} + +/// Returns a closure building the Sort `level` term. +#[inline] +#[no_coverage] +pub const fn sort<'build, F: level::BuilderTrait<'build>>(level: F) -> impl BuilderTrait<'build> { + move |arena, _, lvl_env, _| Ok(Term::sort(level(arena, lvl_env)?, arena)) +} + +/// Returns a closure building the Sort `level` term (indirection from `usize`). +#[inline] +pub const fn sort_usize<'build>(level: usize) -> impl BuilderTrait<'build> { + move |arena, _, _, _| Ok(Term::sort_usize(level, arena)) +} + +/// Returns a closure building the application of two terms built from the given closures `u1` and +/// `u2`. +#[inline] +#[no_coverage] +pub const fn app<'build, F1: BuilderTrait<'build>, F2: BuilderTrait<'build>>(u1: F1, u2: F2) -> impl BuilderTrait<'build> { + |arena, env, lvl_env, depth| { + let u1 = u1(arena, env, lvl_env, depth)?; + let u2 = u2(arena, env, lvl_env, depth)?; + Ok(u1.app(u2, arena)) + } +} + +/// Returns a closure building the lambda-abstraction with a body built from `body` and an argument +/// type from `arg_type`. +#[inline] +#[no_coverage] +pub const fn abs<'build, F1: BuilderTrait<'build>, F2: BuilderTrait<'build>>( + name: &'build str, + arg_type: F1, + body: F2, +) -> impl BuilderTrait<'build> { + move |arena, env, lvl_env, depth| { + let arg_type = arg_type(arena, env, lvl_env, depth)?; + let env = env.update(name, (depth, arg_type)); + let body = body(arena, &env, lvl_env, depth + 1.into())?; + Ok(arg_type.abs(body, arena)) + } +} + +/// Returns a closure building the dependant product of a term built from `body` over all elements +/// of the type built from `arg_type`. +#[inline] +#[no_coverage] +pub const fn prod<'build, F1: BuilderTrait<'build>, F2: BuilderTrait<'build>>( + name: &'build str, + arg_type: F1, + body: F2, +) -> impl BuilderTrait<'build> { + move |arena, env, lvl_env, depth| { + let arg_type = arg_type(arena, env, lvl_env, depth)?; + let env = env.update(name, (depth, arg_type)); + let body = body(arena, &env, lvl_env, depth + 1.into())?; + Ok(arg_type.prod(body, arena)) + } +} + +/// Returns a closure building the term associated to the instantiated declaration `decl`. +#[inline] +#[no_coverage] +pub const fn decl<'build, F: declaration::InstantiatedBuilderTrait<'build>>(decl: F) -> impl BuilderTrait<'build> { + move |arena, _, lvl_env, _| Ok(Term::decl(decl(arena, lvl_env)?, arena)) +} + +/// Template of terms. +/// +/// A Builder describes a term in a naive but easy to build manner. It strongly resembles the +/// [payload](`crate::memory::term::Payload`) type, except that `Var`, `Abs` and `Prod` constructors +/// include a name, as in the classic way of writing lambda-terms (i.e. no de Bruijn indices +/// involved). Because its purpose is to provide an easy way to build terms, even through the API, +/// it offers different ways to build some terms, for convenience. +#[derive(Clone, Debug, Display, PartialEq, Eq)] +pub enum Builder<'build> { + #[display(fmt = "{_0}")] + Var(&'build str), + + #[display(fmt = "Prop")] + Prop, + + #[display(fmt = "Type {_0}")] + Type(Box<level::Builder<'build>>), + + #[display(fmt = "Sort {_0}")] + Sort(Box<level::Builder<'build>>), + + #[display(fmt = "{_0} {_1}")] + App(Box<Builder<'build>>, Box<Builder<'build>>), + + #[display(fmt = "\u{003BB} {_0}: {_1} \u{02192} {_2}")] + Abs(&'build str, Box<Builder<'build>>, Box<Builder<'build>>), + + #[display(fmt = "\u{03A0} {_0}: {_1} \u{02192} {_2}")] + Prod(&'build str, Box<Builder<'build>>, Box<Builder<'build>>), + + Decl(Box<declaration::InstantiatedBuilder<'build>>), +} + +impl<'build> Builder<'build> { + /// Realise a builder into a [`Term`]. This internally uses functions described in + /// the [builder](`crate::memory::term::builder`) module. + pub fn realise<'arena>(&self, arena: &mut Arena<'arena>) -> ResultTerm<'arena> { + arena.build(self.partial_application()) + } + + pub(in crate::memory) fn partial_application(&'build self) -> impl BuilderTrait<'build> { + |arena, env, lvl_env, depth| self.realise_in_context(arena, env, lvl_env, depth) + } + + fn realise_in_context<'arena>( + &'build self, + arena: &mut Arena<'arena>, + env: &Environment<'build, 'arena>, + lvl_env: &level::Environment<'build>, + depth: DeBruijnIndex, + ) -> ResultTerm<'arena> { + use Builder::*; + match self { + Var(s) => var(s)(arena, env, lvl_env, depth), + Prop => prop()(arena, env, lvl_env, depth), + Type(ref level) => type_(level.partial_application())(arena, env, lvl_env, depth), + Sort(ref level) => sort(level.partial_application())(arena, env, lvl_env, depth), + App(ref l, ref r) => app(l.partial_application(), r.partial_application())(arena, env, lvl_env, depth), + Abs(s, ref arg, ref body) => abs(s, arg.partial_application(), body.partial_application())(arena, env, lvl_env, depth), + Prod(s, ref arg, ref body) => { + prod(s, arg.partial_application(), body.partial_application())(arena, env, lvl_env, depth) + }, + Decl(ref decl_builder) => decl(decl_builder.partial_application())(arena, env, lvl_env, depth), + } + } +} + +#[cfg(test)] +pub(crate) mod raw { + use super::*; + + pub trait BuilderTrait = for<'arena> FnOnce(&mut Arena<'arena>) -> Term<'arena>; + + impl<'arena> Arena<'arena> { + pub(crate) fn build_term_raw<F: BuilderTrait>(&mut self, f: F) -> Term<'arena> { + f(self) + } + } + + pub const fn var<F: BuilderTrait>(index: DeBruijnIndex, type_: F) -> impl BuilderTrait { + move |arena| { + let ty = type_(arena); + Term::var(index, ty, arena) + } + } + + pub const fn prop() -> impl BuilderTrait { + |arena| Term::prop(arena) + } + + pub const fn type_usize(level: usize) -> impl BuilderTrait { + move |arena| Term::type_usize(level, arena) + } + + pub const fn sort_<F: level::raw::BuilderTrait>(level: F) -> impl BuilderTrait { + move |arena| Term::sort(level(arena), arena) + } + + pub const fn app<F1: BuilderTrait, F2: BuilderTrait>(u1: F1, u2: F2) -> impl BuilderTrait { + |arena| { + let u1 = u1(arena); + let u2 = u2(arena); + u1.app(u2, arena) + } + } + + pub const fn abs<F1: BuilderTrait, F2: BuilderTrait>(u1: F1, u2: F2) -> impl BuilderTrait { + |arena| { + let u1 = u1(arena); + let u2 = u2(arena); + u1.abs(u2, arena) + } + } + + pub const fn prod<F1: BuilderTrait, F2: BuilderTrait>(u1: F1, u2: F2) -> impl BuilderTrait { + |arena| { + let u1 = u1(arena); + let u2 = u2(arena); + u1.prod(u2, arena) + } + } +} diff --git a/kernel/src/memory/term/mod.rs b/kernel/src/memory/term/mod.rs new file mode 100644 index 00000000..a9a5780d --- /dev/null +++ b/kernel/src/memory/term/mod.rs @@ -0,0 +1,242 @@ +//! Terms in the calculus of construction. +//! +//! This module defines the core functions used to create and manipulate terms. + +use core::fmt; +use std::cell::OnceCell; +use std::fmt::Debug; + +use derive_more::{Add, Display, From, Into, Sub}; + +use super::declaration::InstantiatedDeclaration; +use super::level::Level; +use crate::error::ResultTerm; +use crate::memory::arena::Arena; + +pub mod builder; + +/// An index used to designate bound variables. +#[derive(Add, Copy, Clone, Debug, Default, Display, Eq, PartialEq, From, Into, Sub, PartialOrd, Ord, Hash)] +pub struct DeBruijnIndex(usize); + +super::arena::new_dweller!(Term, Header, Payload); + +struct Header<'arena> { + /// lazy structure to store the weak-head normal form of a term + head_normal_form: OnceCell<Term<'arena>>, + + /// lazy structure to store the type of a term + type_: OnceCell<Term<'arena>>, + // TODO(#45) is_certainly_closed: boolean underapproximation of whether a term is closed. This + // may greatly improve performance in shifting, along with a mem_shift hash map. +} + +/// A term. +/// +/// This enumeration has almost the same shape as the algebraic type of terms in the calculus of +/// constructions. +/// +/// One exception is the [variable](`Payload::Var`). Along with its de Bruijn index, the +/// variable also stores its type, which is unique, and also ensures two variables with a different +/// type do not share the same term in memory. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum Payload<'arena> { + /// A variable, with its de Bruijn index and its type + //#[display(fmt = "{_0}")] + Var(DeBruijnIndex, Term<'arena>), + + /// Sort i, the encoding of Prop and Type i type + Sort(Level<'arena>), + + /// The application of two terms + //#[display(fmt = "{_0} {_1}")] + App(Term<'arena>, Term<'arena>), + + /// The lambda-abstraction of a term: the argument type is on the left, the body on the right. + Abs(Term<'arena>, Term<'arena>), + + /// The dependant product of the term on the right over all elements of the type on the left. + //#[display(fmt = "\u{03A0} {_0} \u{02192} {_1}")] + Prod(Term<'arena>, Term<'arena>), + + /// An instance of a universe-polymorphic declaration + Decl(InstantiatedDeclaration<'arena>), +} + +impl<'arena> fmt::Display for Payload<'arena> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Var(index, _) => write!(f, "{index}"), + Sort(level) => match level.to_numeral() { + Some(n) => match n { + 0 => write!(f, "Prop"), + 1 => write!(f, "Type"), + _ => write!(f, "Type {}", n - 1), + }, + None => write!(f, "Sort {level}"), + }, + App(fun, arg) => write!(f, "({fun}) ({arg})"), + Abs(argtype, body) => write!(f, "\u{003BB} {argtype} \u{02192} {body}"), + Prod(argtype, body) => write!(f, "\u{003A0} {argtype} \u{02192} {body}"), + Decl(decl) => write!(f, "{decl}"), + } + } +} + +impl<'arena> fmt::Display for Term<'arena> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.payload) + } +} + +use Payload::*; + +impl<'arena> Term<'arena> { + /// This function is the base low-level function for creating terms. + /// + /// It enforces the uniqueness property of terms in the arena. + fn hashcons(payload: Payload<'arena>, arena: &mut Arena<'arena>) -> Self { + // There are concurrent designs here. hashcons could also take a node, which gives + // subsequent function some liberty in providing the other objects of the header: WHNF, + // type_ (unlikely, because not always desirable), is_certainly_closed. + let new_node = Node { + payload, + header: Header { + head_normal_form: OnceCell::new(), + type_: OnceCell::new(), + }, + }; + + match arena.hashcons_terms.get(&new_node) { + Some(addr) => Term::new(addr), + None => { + let addr = arena.alloc.alloc(new_node); + arena.hashcons_terms.insert(addr); + Term::new(addr) + }, + } + } + + /// Returns a variable term with the given index and type + pub(crate) fn var(index: DeBruijnIndex, type_: Term<'arena>, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Var(index, type_), arena) + } + + /// Returns the term corresponding to a proposition + pub(crate) fn prop(arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Sort(Level::zero(arena)), arena) + } + + /// Returns the term associated to the sort of the given level + pub(crate) fn sort(level: Level<'arena>, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Sort(level), arena) + } + + /// Returns the term corresponding to Type(level), casting level appropriately first + pub(crate) fn type_usize(level: usize, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Sort(Level::from(level + 1, arena)), arena) + } + + /// Returns the term corresponding to Sort(level), casting level appropriately first + pub(crate) fn sort_usize(level: usize, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Sort(Level::from(level, arena)), arena) + } + + /// Returns the application of one term to the other + pub(crate) fn app(self, arg: Self, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(App(self, arg), arena) + } + + /// Returns the lambda-abstraction of the term `body`, with an argument of type `arg_type`. + /// + /// Please note that no verification is done that occurrences of this variable in `body` have + /// the same type. + pub(crate) fn abs(self, body: Self, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Abs(self, body), arena) + } + + /// Returns the dependant product of the term `body`, over elements of `arg_type`. + /// + /// Please note that no verification is done that occurrences of this variable in `body` have + /// the same type. + pub(crate) fn prod(self, body: Self, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Prod(self, body), arena) + } + + /// Returns the term associated to the given instantiated declaration. + pub(crate) fn decl(decl: InstantiatedDeclaration<'arena>, arena: &mut Arena<'arena>) -> Self { + Self::hashcons(Decl(decl), arena) + } + + /// Returns the weak head normal form of the term, lazily computing the closure `f`. + pub(crate) fn get_whnf_or_init<F>(self, f: F) -> Self + where + F: FnOnce() -> Self, + { + *self.0.header.head_normal_form.get_or_init(f) + } + + /// Returns the type of the term, lazily computing the closure `f`. + pub(crate) fn get_type_or_try_init<F>(self, f: F) -> ResultTerm<'arena> + where + F: FnOnce() -> ResultTerm<'arena>, + { + self.0.header.type_.get_or_try_init(f).copied() + } +} + +impl<'arena> Arena<'arena> { + /// Returns the result of the substitution described by the key, lazily computing the closure `f`. + 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 + }, + } + } +} + +#[cfg(test)] +mod tests { + use crate::memory::arena::use_arena; + use crate::memory::level::builder::raw::{var, *}; + use crate::memory::term::builder::raw::*; + + #[test] + fn display() { + use_arena(|arena| { + let decl_ = crate::memory::declaration::InstantiatedDeclaration::instantiate( + crate::memory::declaration::builder::Builder::Decl(crate::memory::term::builder::Builder::Prop.into(), Vec::new()) + .realise(arena) + .unwrap(), + &Vec::new(), + arena, + ); + + let prop_ = crate::memory::term::Term::decl(decl_, arena); + + assert_eq!(format!("{}", prop_), "(Prop).{}"); + let vart = crate::memory::term::builder::raw::var; + + let lvl = max(succ(var(0)), succ(var(1))); + let term = arena.build_term_raw(abs( + sort_(lvl), + abs( + type_usize(0), + abs( + type_usize(1), + prod(vart(1.into(), type_usize(1)), app(vart(1.into(), type_usize(1)), vart(2.into(), type_usize(0)))), + ), + ), + )); + + assert_eq!(format!("{}", term), "λ Sort max (u0) (u1) + 1 → λ Type → λ Type 1 → Î 1 → (1) (2)") + }) + } +} diff --git a/kernel/src/term/arena.rs b/kernel/src/term/arena.rs deleted file mode 100644 index 620eaed7..00000000 --- a/kernel/src/term/arena.rs +++ /dev/null @@ -1,345 +0,0 @@ -//! A comprehensive memory management unit for terms. -//! -//! This module defines the core functions used to manipulate an arena and its terms. - -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 num_bigint::BigUint; - -use crate::error::ResultTerm; - -/// An index used to designate bound variables. -#[derive(Add, Copy, Clone, Debug, Default, Display, PartialEq, Eq, Hash, From, Into, PartialOrd, Ord, Sub)] -pub struct DeBruijnIndex(usize); - -/// A level of universe, used to build terms of the form `Type i`. -/// -/// In type theory, this corresponds to the construction of universes "à la Russell", the purpose -/// of which is to give a hierarchy to these types, so as to preserve soundness against paradoxes -/// akin to Russell's. Universe levels can be arbitrarily large, and, with good faith, they are -/// represented with *big unsigned integers*, limited only by the memory of the operating computer. -#[derive(Add, Clone, Debug, Default, Display, PartialEq, Eq, From, Hash, PartialOrd, Ord, Sub)] -pub struct UniverseLevel(BigUint); - -/// A comprehensive memory management unit for terms. -/// -/// An arena is a location in memory where a group of terms with several properties is stored. Most -/// importantly, it ensures that all terms living in the arena are syntactically unique, which -/// accelerates many algorithms. In particular, this property allows for *memoizing* easily -/// operations on terms like substitution, shifting, type checking, etc. It also facilitates the -/// [building of terms](super::builders) which are named or use named terms. -/// -/// This paradigm of memory management is akin to what is usually lectured for Binary Decision -/// Diagrams (BDD) management. Additionally, it makes use of Rust features to provide a clean -/// interface: the arena type is invariant over its lifetime argument (usually called `'arena`), -/// which together with the [`use_arena`] function, enforces strong guarantees on how the arena can -/// be used, particularly if several of them are used simultaneously. -/// -/// Early versions of this system are freely inspired by an assignment designed by -/// [Jacques-Henri Jourdan](<https://jhjourdan.mketjh.fr>). -pub struct Arena<'arena> { - alloc: &'arena Bump, - - // enforces invariances over lifetime parameter - _phantom: PhantomData<*mut &'arena ()>, - - // Hashconsing of terms, at the heart of the uniqueness property - hashcons: HashSet<&'arena Node<'arena>>, - named_terms: HashMap<&'arena str, Term<'arena>>, - - // Hash maps used to speed up certain algorithms. See also `OnceCell`s in [`Term`] - mem_subst: HashMap<(Term<'arena>, Term<'arena>, usize), Term<'arena>>, - // TODO shift hashmap (see #45) - // requires the design of an additional is_certainly_closed predicate in terms. -} - -/// A term of the calculus of constructions. -/// -/// This type is associated, through its lifetime argument, to an [`Arena`], where it lives. There, -/// it is guaranteed to be unique, which accelerates many algorithms. It is fundamentally a pointer -/// to an internal term structure, called a Node, which itself contains the core term, [`Payload`], -/// which is what can be expected of a term. -/// -/// Additionally, the Node contains lazy structures which indicate the result of certain -/// transformation on the term, namely type checking and term reduction. Storing it directly here -/// is both faster and takes overall less space than storing the result in a separate hash table. -#[derive(Clone, Copy, Display, Eq)] -#[display(fmt = "{}", "_0.payload")] -pub struct Term<'arena>( - &'arena Node<'arena>, - // This marker ensures invariance over the 'arena lifetime. - PhantomData<*mut &'arena ()>, -); - -#[derive(Debug, Eq)] -struct Node<'arena> { - payload: Payload<'arena>, - - // Lazy and aliasing-compatible structures for memoizing - head_normal_form: OnceCell<Term<'arena>>, - type_: OnceCell<Term<'arena>>, - // TODO is_certainly_closed: boolean underapproximation of whether a term is closed. - // This may greatly improve performance in shifting, along with a mem_shift hash map. -} - -/// The essence of a term. -/// -/// This enumeration has the same shape as the algebraic type of terms in the calculus of -/// constructions. -/// -/// There is one true exception, which is the variable (Var)[`Payload::Var`]. Along with its de -/// Bruijn index, the variable also stores its type, which is unique, and also ensures two -/// variables with a different type do not share the same term in memory. -#[derive(Clone, Debug, Display, Eq, PartialEq, Hash)] -pub enum Payload<'arena> { - /// A variable, with its de Bruijn index and its type - #[display(fmt = "{}", _0)] - Var(DeBruijnIndex, Term<'arena>), - - /// The type of propositions - #[display(fmt = "Prop")] - Prop, - - /// Type i, as described in [`UniverseLevel`] - #[display(fmt = "Type {}", _0)] - Type(UniverseLevel), - - /// The application of two terms - #[display(fmt = "{} {}", _0, _1)] - App(Term<'arena>, Term<'arena>), - - /// The lambda-abstraction of a term: the argument type is on the left, the body on the right. - #[display(fmt = "\u{003BB} {} \u{02192} {}", _0, _1)] - Abs(Term<'arena>, Term<'arena>), - - /// The dependant product of the term on the right over all elements of the type on the left. - #[display(fmt = "\u{03A0} {} \u{02192} {}", _0, _1)] - Prod(Term<'arena>, Term<'arena>), -} - -/// This function is the main function that the kernel exports. Most importantly, it is the only -/// one to provide an entry point for Arena objects, by means of a closure provided by the end -/// user. -/// -/// Such an interface is the most elegant way to ensure the one-to-one correspondence between -/// lifetime parameters and [`Arena`] objects. -/// -/// To generate the `alloc` object in this function is necessary, as this is the main way to -/// "create" a lifetime variable which makes sense. That way, `'arena` is valid exactly during -/// the execution of the function `f`. -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 Payload::*; - -impl<'arena> Arena<'arena> { - /// Allocates a new memory arena. As detailed in the [`use_arena`] function, it is necessary to - /// externalise the generation of the [`bumpalo::Bump`] object. - fn new(alloc: &'arena Bump) -> Self { - Arena { - alloc, - _phantom: PhantomData, - - hashcons: HashSet::new(), - named_terms: HashMap::new(), - - mem_subst: HashMap::new(), - } - } - - /// Stores a string in the arena. - /// - /// This is typically done to ensure strings live long enough when manipulating them. - pub(crate) fn store_name(&mut self, name: &str) -> &'arena str { - self.alloc.alloc_str(name) - } - - /// Binds a term to a certain name. - pub fn bind(&mut self, name: &str, t: Term<'arena>) { - let name = self.store_name(name); - self.named_terms.insert(name, t); - } - - /// Retrieves the binding of a certain name, if one exists. - pub fn get_binding(&self, name: &str) -> Option<Term<'arena>> { - self.named_terms.get(name).copied() - } - - /// This function is the base low-level function for creating terms. - /// - /// It enforces the uniqueness property of terms in the arena. - fn hashcons(&mut self, n: Payload<'arena>) -> Term<'arena> { - // There are concurrent designs here. hashcons could also take a node, which gives - // subsequent function some liberty in providing the other objects of the header: WHNF, - // type_ (unlikely, because not always desirable), is_certainly_closed. - let new_node = Node { - payload: n, - head_normal_form: OnceCell::new(), - type_: OnceCell::new(), - }; - - match self.hashcons.get(&new_node) { - Some(addr) => Term(addr, PhantomData), - None => { - let addr = self.alloc.alloc(new_node); - self.hashcons.insert(addr); - Term(addr, PhantomData) - }, - } - } - - /// Returns a variable term with the given index and type - pub(crate) fn var(&mut self, index: DeBruijnIndex, type_: Term<'arena>) -> Term<'arena> { - self.hashcons(Var(index, type_)) - } - - /// Returns the term corresponding to a proposition - pub(crate) fn prop(&mut self) -> Term<'arena> { - self.hashcons(Prop) - } - - /// Returns the term corresponding to Type(level) - pub(crate) fn type_(&mut self, level: UniverseLevel) -> Term<'arena> { - self.hashcons(Type(level)) - } - - /// Returns the term corresponding to Type(level), casting level appropriately first - pub(crate) fn type_usize(&mut self, level: usize) -> Term<'arena> { - self.hashcons(Type(BigUint::from(level).into())) - } - - /// Returns the application of one term to the other - pub(crate) fn app(&mut self, func: Term<'arena>, arg: Term<'arena>) -> Term<'arena> { - self.hashcons(App(func, arg)) - } - - /// Returns the lambda-abstraction of the term `body`, with an argument of type `arg_type`. - /// - /// Please note that no verification is done that occurrences of this variable in `body` have - /// the same type. - pub(crate) fn abs(&mut self, arg_type: Term<'arena>, body: Term<'arena>) -> Term<'arena> { - self.hashcons(Abs(arg_type, body)) - } - - /// Returns the dependant product of the term `body`, over elements of `arg_type`. - /// - /// Please note that no verification is done that occurrences of this variable in `body` have - /// the same type. - pub(crate) fn prod(&mut self, arg_type: Term<'arena>, body: Term<'arena>) -> Term<'arena> { - self.hashcons(Prod(arg_type, body)) - } - - /// Returns the result of the substitution described by the key, lazily computing the closure `f`. - 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> { - /// Returns the weak head normal form of the term, lazily computing the closure `f`. - 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) - } - - /// Returns the type of the term, lazily computing the closure `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() - } -} - -/// A Term is arguably a smart pointer, and as such, can be directly dereferenced to its associated -/// payload. -/// -/// This is done for convenience, as it allows to manipulate the terms relatively seamlessly. -/// ``` -/// # use kernel::term::arena::{use_arena, Payload::*}; -/// # use kernel::term::builders::prop; -/// # use_arena(|arena| { -/// # let t = arena.build(prop()).unwrap(); -/// match *t { -/// Abs(_, t2) => arena.beta_reduction(t2), -/// App(t1, _) => t1, -/// _ => t, -/// } -/// # ;}) -/// ``` -/// Please note that this trait has some limits. For instance, the notations used to match against -/// a *pair* of terms still requires some convolution. -impl<'arena> Deref for Term<'arena> { - type Target = Payload<'arena>; - - fn deref(&self) -> &Self::Target { - &self.0.payload - } -} - -/// Debug mode only prints the payload of a term. -/// -/// Apart from enhancing the debug readability, this reimplementation is surprisingly necessary: -/// because terms may refer to themselves in the payload, the default debug implementation -/// recursively calls itself and provokes a stack overflow. -impl<'arena> Debug for Term<'arena> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - self.0.payload.fmt(f) - } -} - -/// Because terms are unique in the arena, it is sufficient to compare their locations in memory to -/// test equality. -impl<'arena> PartialEq<Term<'arena>> for Term<'arena> { - fn eq(&self, x: &Term<'arena>) -> bool { - std::ptr::eq(self.0, x.0) - } -} - -/// Because terms are unique in the arena, it is sufficient to compare their locations in memory to -/// test equality. In particular, hash can also be computed from the location. -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 - } -} - -/// Nodes are not guaranteed to be unique. Nonetheless, only the payload matters and characterises -/// the value. Which means computing the hash for nodes can be restricted to hashing their -/// payloads. -impl<'arena> Hash for Node<'arena> { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.payload.hash(state); - } -} diff --git a/kernel/src/term/builders.rs b/kernel/src/term/builders.rs deleted file mode 100644 index ffa91ff5..00000000 --- a/kernel/src/term/builders.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! Collection of safe functions to build Terms -//! -//! This module provides two main ways of building terms. The first one is via closures: users can -//! manipulate closures and create bigger ones which, when [built](Arena::build), provide the expected -//! term. -//! -//! The overall syntax remains transparent to the user. This means the user focuses on the -//! structure of the term they want to build, while the [closures](`BuilderTrait`) internally build an appropriate -//! logic: converting regular terms into de Bruijn-compatible ones, assigning types to variables, -//! etc. -//! -//! The other way to proceed is built on top of the latter. Users can also manipulate a sort of -//! *high-level term* or *template*, described by the public enumeration [`Builder`], and at any -//! moment, [realise](Builder::realise) it. - -use derive_more::Display; -use im_rc::hashmap::HashMap as ImHashMap; - -use super::arena::{Arena, DeBruijnIndex, Term, UniverseLevel}; -use crate::error::{Error, ResultTerm}; - -#[non_exhaustive] -#[derive(Clone, Debug, Display, Eq, PartialEq)] -pub enum DefinitionError<'arena> { - #[display(fmt = "unknown identifier {}", _0)] - ConstNotFound(&'arena str), -} - -/// Local environment used to store correspondence between locally-bound variables and the pair -/// (depth at which they were bound, their type) -pub type Environment<'build, 'arena> = ImHashMap<&'build str, (DeBruijnIndex, Term<'arena>)>; - -/// The trait of closures which build terms with an adequate logic. -/// -/// A call with a triplet of arguments `(arena, env, index)` of a closure with this trait should -/// build a definite term in the [`Arena`] `arena`, knowing the bindings declared in `environment`, -/// provided that the term is built at a current depth `index`. -/// -/// Please note that this is just a trait alias, meaning it enforces very little constraints: while -/// functions in this module returning a closure with this trait are guaranteed to be sound, end -/// users can also create their own closures satisfying `BuilderTrait`; this should be avoided. -pub trait BuilderTrait<'build, 'arena> = - FnOnce(&mut Arena<'arena>, &Environment<'build, 'arena>, DeBruijnIndex) -> ResultTerm<'arena>; - -impl<'arena> Arena<'arena> { - /// Returns the term built from the given closure, provided with an empty context, at depth 0. - #[inline] - pub fn build<'build, F: BuilderTrait<'build, 'arena>>(&mut self, f: F) -> ResultTerm<'arena> - where - 'arena: 'build, - { - f(self, &Environment::new(), 0.into()) - } -} - -/// Returns a closure building a variable associated to the name `name` -#[inline] -pub const fn var<'build, 'arena>(name: &'build str) -> impl BuilderTrait<'build, 'arena> { - move |arena: &mut Arena<'arena>, env: &Environment<'build, 'arena>, depth| { - env.get(name) - .map(|(bind_depth, term)| { - // This is arguably an eager computation, it could be worth making it lazy, - // or at least memoizing it so as to not compute it again - 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(arena.store_name(name)).into(), - }) - } -} - -/// Returns a closure building the Prop term. -#[inline] -pub const fn prop<'build, 'arena>() -> impl BuilderTrait<'build, 'arena> { - |arena: &mut Arena<'arena>, _: &Environment<'build, 'arena>, _| Ok(arena.prop()) -} - -/// Returns a closure building the Type `level` term. -#[inline] -pub const fn type_<'build, 'arena>(level: UniverseLevel) -> impl BuilderTrait<'build, 'arena> { - move |arena: &mut Arena<'arena>, _: &Environment<'build, 'arena>, _| Ok(arena.type_(level)) -} - -/// Returns a closure building the Type `level` term (indirection from `usize`). -#[inline] -pub const fn type_usize<'build, 'arena>(level: usize) -> impl BuilderTrait<'build, 'arena> { - use num_bigint::BigUint; - move |arena: &mut Arena<'arena>, _: &Environment<'build, 'arena>, _| Ok(arena.type_(BigUint::from(level).into())) -} - -/// Returns a closure building the application of two terms built from the given closures `u1` and -/// `u2`. -#[inline] -#[no_coverage] -pub const fn app<'build, 'arena, F1: BuilderTrait<'build, 'arena>, F2: BuilderTrait<'build, 'arena>>( - u1: F1, - u2: F2, -) -> impl BuilderTrait<'build, 'arena> { - |arena: &mut Arena<'arena>, env: &Environment<'build, 'arena>, depth| { - let u1 = u1(arena, env, depth)?; - let u2 = u2(arena, env, depth)?; - Ok(arena.app(u1, u2)) - } -} - -/// Returns a closure building the lambda-abstraction with a body built from `body` and an argument -/// type from `arg_type`. -#[inline] -#[no_coverage] -pub const fn abs<'build, 'arena, F1: BuilderTrait<'build, 'arena>, F2: BuilderTrait<'build, 'arena>>( - name: &'build str, - arg_type: F1, - body: F2, -) -> impl BuilderTrait<'build, 'arena> { - move |arena: &mut Arena<'arena>, env: &Environment<'build, '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)) - } -} - -/// Returns a closure building the dependant product of a term built from `body` over all elements -/// of the type built from `arg_type`. -#[inline] -#[no_coverage] -pub const fn prod<'build, 'arena, F1: BuilderTrait<'build, 'arena>, F2: BuilderTrait<'build, 'arena>>( - name: &'build str, - arg_type: F1, - body: F2, -) -> impl BuilderTrait<'build, 'arena> { - move |arena: &mut Arena<'arena>, env: &Environment<'build, '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)) - } -} - -/// Template of terms. -/// -/// A Builder describes a term in a naive but easy to build manner. It strongly resembles the -/// [payload](`crate::term::arena::Payload`) type, except that `Var`, `Abs` and `Prod` constructors -/// include a name, as in the classic way of writing lambda-terms (i.e. no de Bruijn indices -/// involved). -#[derive(Clone, Debug, Display, PartialEq, Eq)] -pub enum Builder<'r> { - #[display(fmt = "{}", _0)] - Var(&'r str), - - #[display(fmt = "Prop")] - Prop, - - #[display(fmt = "Type {}", _0)] - Type(usize), - - #[display(fmt = "{} {}", _0, _1)] - App(Box<Builder<'r>>, Box<Builder<'r>>), - - #[display(fmt = "\u{003BB} {}: {} \u{02192} {}", _0, _1, _2)] - Abs(&'r str, Box<Builder<'r>>, Box<Builder<'r>>), - - #[display(fmt = "\u{03A0} {}: {} \u{02192} {}", _0, _1, _2)] - Prod(&'r str, Box<Builder<'r>>, Box<Builder<'r>>), -} - -impl<'build> Builder<'build> { - /// Build a terms from a [`Builder`]. This internally uses functions described in the - /// [builders](`crate::term::builders`) module. - pub fn realise<'arena>(&self, arena: &mut Arena<'arena>) -> ResultTerm<'arena> { - arena.build(self.partial_application()) - } - - fn partial_application<'arena>(&self) -> impl BuilderTrait<'build, 'arena> + '_ { - |arena: &mut Arena<'arena>, env: &Environment<'build, 'arena>, depth| self.realise_in_context(arena, env, depth) - } - - fn realise_in_context<'arena>( - &self, - arena: &mut Arena<'arena>, - env: &Environment<'build, 'arena>, - depth: DeBruijnIndex, - ) -> ResultTerm<'arena> { - use Builder::*; - match *self { - Var(s) => var(s)(arena, env, depth), - Type(level) => type_usize(level)(arena, env, depth), - Prop => prop()(arena, env, depth), - App(ref l, ref r) => app(l.partial_application(), r.partial_application())(arena, env, depth), - Abs(s, ref arg, ref body) => abs(s, arg.partial_application(), body.partial_application())(arena, env, depth), - Prod(s, ref arg, ref body) => prod(s, arg.partial_application(), body.partial_application())(arena, env, depth), - } - } -} - -#[cfg(test)] -pub(crate) mod raw { - use super::*; - - pub trait BuilderTrait<'arena> = FnOnce(&mut Arena<'arena>) -> Term<'arena>; - - impl<'arena> Arena<'arena> { - pub(crate) fn build_raw<F: BuilderTrait<'arena>>(&mut self, f: F) -> Term<'arena> { - f(self) - } - } - - impl<'arena> Term<'arena> { - pub(crate) const fn into(self) -> impl BuilderTrait<'arena> { - move |_: &mut Arena<'arena>| self - } - } - - pub const fn var<'arena, F: BuilderTrait<'arena>>(index: DeBruijnIndex, type_: F) -> impl BuilderTrait<'arena> { - move |env: &mut Arena<'arena>| { - let ty = type_(env); - env.var(index, ty) - } - } - - pub const fn prop<'arena>() -> impl BuilderTrait<'arena> { - |env: &mut Arena<'arena>| env.prop() - } - - pub const fn type_<'arena>(level: UniverseLevel) -> impl BuilderTrait<'arena> { - move |env: &mut Arena<'arena>| env.type_(level) - } - - pub const fn app<'arena, F1: BuilderTrait<'arena>, F2: BuilderTrait<'arena>>(u1: F1, u2: F2) -> impl BuilderTrait<'arena> { - |env: &mut Arena<'arena>| { - let u1 = u1(env); - let u2 = u2(env); - env.app(u1, u2) - } - } - - pub const fn abs<'arena, F1: BuilderTrait<'arena>, F2: BuilderTrait<'arena>>(u1: F1, u2: F2) -> impl BuilderTrait<'arena> { - |env: &mut Arena<'arena>| { - let u1 = u1(env); - let u2 = u2(env); - env.abs(u1, u2) - } - } - - pub const fn prod<'arena, F1: BuilderTrait<'arena>, F2: BuilderTrait<'arena>>(u1: F1, u2: F2) -> impl BuilderTrait<'arena> { - |env: &mut Arena<'arena>| { - let u1 = u1(env); - let u2 = u2(env); - env.prod(u1, u2) - } - } -} diff --git a/kernel/src/term/calculus.rs b/kernel/src/term/calculus.rs deleted file mode 100644 index da57c2fb..00000000 --- a/kernel/src/term/calculus.rs +++ /dev/null @@ -1,302 +0,0 @@ -//! A set of lambda-calculus quasi-primitives. -//! -//! This module consists of internal utility functions used by the type checker, and correspond to -//! usual functions over lambda-terms. These functions interact appropriately with a given arena. - -use Payload::*; - -use super::arena::{Arena, Payload, Term}; - -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, - } - } - - /// Returns the term `t` where all variables with de Bruijn index larger than `depth` are offset - /// by `offset`. - 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, - } - } - - /// Returns the term `t` where all instances of the variable tracked by `depth` are substituted - /// with `sub`. - pub(crate) fn substitute(&mut self, t: Term<'arena>, sub: Term<'arena>, depth: usize) -> Term<'arena> { - self.get_subst_or_init(&(t, sub, depth), |arena| match *t { - Var(i, _) if i == depth.into() => arena.shift(sub, depth - 1, 0), - Var(i, type_) if i > depth.into() => arena.var(i - 1.into(), type_), - App(l, r) => { - let l = arena.substitute(l, sub, depth); - let r = arena.substitute(r, sub, depth); - arena.app(l, r) - }, - Abs(arg_type, body) => { - let arg_type = arena.substitute(arg_type, sub, depth); - let body = arena.substitute(body, sub, depth + 1); - arena.abs(arg_type, body) - }, - Prod(arg_type, body) => { - let arg_type = arena.substitute(arg_type, sub, depth); - let body = arena.substitute(body, sub, depth + 1); - arena.prod(arg_type, body) - }, - _ => t, - }) - } - - /// Returns the normal form of a term. - /// - /// 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. - pub fn whnf(&mut self, t: Term<'arena>) -> Term<'arena> { - t.get_whnf_or_init(|| match *t { - App(t1, t2) => match *self.whnf(t1) { - Abs(_, t1) => { - let subst = self.substitute(t1, t2, 1); - self.whnf(subst) - }, - _ => t, - }, - _ => t, - }) - } -} - -#[cfg(test)] -mod tests { - // /!\ most terms used in these tests are ill-typed; they should not be used elsewhere - use super::super::arena::use_arena; - use super::super::builders::raw::*; - - #[test] - fn simple_subst() { - use_arena(|arena| { - // λx.(λy.x y) x - let term = arena - .build_raw(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_raw(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_raw(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_raw(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_raw(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_raw(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_raw(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_raw(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_raw(abs(prop(), abs(prop(), app(abs(prop(), var(2.into(), prop())), var(2.into(), prop()))))); - - // λa.λb.b - let term_step_7 = arena.build_raw(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_raw(prod(prop(), var(1.into(), prop()))); - let term = arena.build_raw(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_raw(prod(prop(), app(abs(prop(), var(1.into(), prop())), var(1.into(), prop())))); - let reduced = arena.build_raw(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_raw(abs(prop(), app(var(1.into(), prop()), app(abs(prop(), var(1.into(), prop())), var(1.into(), prop()))))); - let reduced = arena.build_raw(abs(prop(), app(var(1.into(), prop()), var(1.into(), prop())))); - - assert_eq!(arena.beta_reduction(term), reduced); - }) - } - - #[test] - fn normal_form() { - use_arena(|arena| { - let term = arena - .build_raw(app(app(app(abs(prop(), abs(prop(), abs(prop(), var(1.into(), prop())))), prop()), prop()), prop())); - let normal_form = arena.build_raw(prop()); - - assert_eq!(arena.normal_form(term), normal_form); - }) - } -} diff --git a/kernel/src/term/mod.rs b/kernel/src/term/mod.rs deleted file mode 100644 index 42fe9d28..00000000 --- a/kernel/src/term/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Terms in the Calculus of Construction -//! -//! This module provides a paradigm for building and manipulating terms of the calculus of -//! construction, centered around the notion of [arena](`arena::Arena`). - -pub mod arena; -pub mod builders; -pub mod calculus; diff --git a/kernel/src/type_checker.rs b/kernel/src/type_checker.rs index fbadb941..2ef2b54f 100644 --- a/kernel/src/type_checker.rs +++ b/kernel/src/type_checker.rs @@ -2,14 +2,13 @@ //! //! The logical core of the kernel. -use std::cmp::max; - use derive_more::Display; -use num_bigint::BigUint; use Payload::*; use crate::error::{Error, Result, ResultTerm}; -use crate::term::arena::{Arena, Payload, Term}; +use crate::memory::arena::Arena; +use crate::memory::declaration::Declaration; +use crate::memory::term::{Payload, Term}; #[derive(Clone, Debug, Display, Eq, PartialEq)] #[display(fmt = "{}: {}", _0, _1)] @@ -19,117 +18,112 @@ pub struct TypedTerm<'arena>(Term<'arena>, Term<'arena>); #[non_exhaustive] #[derive(Clone, Debug, Display, Eq, PartialEq)] pub enum TypeCheckerError<'arena> { - /// `t` is not a universe - #[display(fmt = "{} is not a universe", _0)] + #[display(fmt = "{_0} is not a universe")] NotUniverse(Term<'arena>), - /// `t1` and `t2` are not definitionally equal - #[display(fmt = "{} and {} are not definitionally equal", _0, _1)] + #[display(fmt = "{_0} and {_1} are not definitionally equal")] NotDefEq(Term<'arena>, Term<'arena>), - /// function `f` expected a `t`, received `x: t`' - #[display(fmt = "function {} expects a term of type {}, received {}", _0, _1, _2)] + #[display(fmt = "function {_0} expects a term of type {_1}, received {_2}")] WrongArgumentType(Term<'arena>, Term<'arena>, TypedTerm<'arena>), - /// `t1` of type `ty` is not a function so cannot be applied to `t2` - #[display(fmt = "{} is not a function, it cannot be applied to {}", _0, _1)] + #[display(fmt = "{_0} is not a function, it cannot be applied to {_1}")] NotAFunction(TypedTerm<'arena>, Term<'arena>), - /// Expected `ty1`, found `ty2` - #[display(fmt = "expected {}, got {}", _0, _1)] + #[display(fmt = "expected {_0}, got {_1}")] TypeMismatch(Term<'arena>, Term<'arena>), } -impl<'arena> Arena<'arena> { +impl<'arena> Term<'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(&mut self, lhs: Term<'arena>, rhs: Term<'arena>) -> bool { - if lhs == rhs { + /// when the two [`Term`]s are already known to be of the same type and in the same context. + fn conversion(self, rhs: Self, arena: &mut Arena<'arena>) -> bool { + if self == rhs { return true; } - let lhs = self.whnf(lhs); - let rhs = self.whnf(rhs); + let lhs = self.whnf(arena); + let rhs = rhs.whnf(arena); if lhs == rhs { return true; } match (&*lhs, &*rhs) { - // For universe conversion (Prop/Type), the equality of conversion is syntactical over the whnfs, as such, - // they live in the same place in memory. This means we don't need to check for these cases here. - // /!\ Once we have universe polymorphism, this won't be the case anymore and these cases will need to be added - // again. + (Sort(l1), Sort(l2)) => l1.is_eq(*l2, arena), + (Var(i, _), Var(j, _)) => i == j, - (&Prod(t1, u1), &Prod(t2, u2)) => self.conversion(t1, t2) && self.conversion(u1, u2), + (&Prod(t1, u1), &Prod(t2, u2)) => t1.conversion(t2, arena) && u1.conversion(u2, arena), // Since we assume that both values 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)) => self.conversion(t, u), + (&Abs(_, t), &Abs(_, u)) => t.conversion(u, arena), + + (&App(t1, u1), &App(t2, u2)) => t1.conversion(t2, arena) && u1.conversion(u2, arena), - (&App(t1, u1), &App(t2, u2)) => self.conversion(t1, t2) && self.conversion(u1, u2), + // We do not automatically unfold definitions during normalisation because of how costly it is. + // Instead, when the same declaration is met on both terms, they're also equal in memory. + // Otherwise, either one of them is not a decl, or they are two different decls. In both case, we unfold decls to check + // equality. + (&Decl(decl), _) => decl.get_term(arena).conversion(rhs, arena), + + (_, &Decl(decl)) => decl.get_term(arena).conversion(lhs, arena), _ => false, } } /// Checks whether two terms are definitionally equal. - pub fn is_def_eq(&mut self, lhs: Term<'arena>, rhs: Term<'arena>) -> Result<'arena, ()> { - self.conversion(lhs, rhs).then_some(()).ok_or(Error { - kind: TypeCheckerError::NotDefEq(lhs, rhs).into(), + pub fn is_def_eq(self, rhs: Self, arena: &mut Arena<'arena>) -> Result<'arena, ()> { + self.conversion(rhs, arena).then_some(()).ok_or(Error { + kind: TypeCheckerError::NotDefEq(self, rhs).into(), }) } /// Computes the universe in which `(x: A) -> B` lives when `A: lhs` and `B: rhs`. - fn imax(&mut self, lhs: Term<'arena>, rhs: Term<'arena>) -> ResultTerm<'arena> { - match *rhs { - // Because Prop is impredicative, if B : Prop, then (x : A) -> B : Prop - Prop => Ok(self.prop()), - - Type(ref i) => match *lhs { - Prop => Ok(self.type_(i.clone())), - - // else if u1 = Type(i) and u2 = Type(j), then (x : A) -> B : Type(max(i,j)) - Type(ref j) => Ok(self.type_(max(i, j).clone())), - + fn imax(self, rhs: Self, arena: &mut Arena<'arena>) -> ResultTerm<'arena> { + match *self { + Sort(l1) => match *rhs { + Sort(l2) => { + let lvl = l1.imax(l2, arena); + Ok(Term::sort(lvl, arena)) + }, _ => Err(Error { - kind: TypeCheckerError::NotUniverse(lhs).into(), + kind: TypeCheckerError::NotUniverse(rhs).into(), }), }, - _ => Err(Error { - kind: TypeCheckerError::NotUniverse(rhs).into(), + kind: TypeCheckerError::NotUniverse(self).into(), }), } } - /// Infers the type of the term `t`, living in arena `self`. - pub fn infer(&mut self, t: Term<'arena>) -> ResultTerm<'arena> { - t.get_type_or_try_init(|| match *t { - Prop => Ok(self.type_usize(0)), - Type(ref i) => Ok(self.type_(i.clone() + BigUint::from(1_u64).into())), + /// Infers the type of the term `t`, living in arena `arena`. + pub fn infer_raw(self, arena: &mut Arena<'arena>) -> ResultTerm<'arena> { + match *self { + Sort(lvl) => Ok(Term::sort(lvl.succ(arena), arena)), Var(_, type_) => Ok(type_), Prod(t, u) => { - let univ_t = self.infer(t)?; - let univ_u = self.infer(u)?; + let univ_t = t.infer(arena)?; + let univ_u = u.infer(arena)?; - let univ_t = self.whnf(univ_t); - let univ_u = self.whnf(univ_u); - self.imax(univ_t, univ_u) + let univ_t = univ_t.whnf(arena); + let univ_u = univ_u.whnf(arena); + univ_t.imax(univ_u, arena) }, Abs(t, u) => { - let type_t = self.infer(t)?; + let type_t = t.infer(arena)?; match *type_t { - Type(_) | Prop => { - let type_u = self.infer(u)?; - Ok(self.prod(t, type_u)) + Sort(_) => { + let type_u = u.infer(arena)?; + Ok(t.prod(type_u, arena)) }, _ => Err(Error { kind: TypeCheckerError::NotUniverse(type_t).into(), @@ -138,14 +132,14 @@ impl<'arena> Arena<'arena> { }, App(t, u) => { - let type_t = self.infer(t)?; - let type_t = self.whnf(type_t); + let type_t = t.infer(arena)?; + let type_t = type_t.whnf(arena); match *type_t { Prod(arg_type, cls) => { - let type_u = self.infer(u)?; + let type_u = u.infer(arena)?; - if self.conversion(type_u, arg_type) { - Ok(self.substitute(cls, u, 1)) + if type_u.conversion(arg_type, arena) { + Ok(cls.substitute(u, 1, arena)) } else { Err(Error { kind: TypeCheckerError::WrongArgumentType(t, arg_type, TypedTerm(u, type_u)).into(), @@ -158,70 +152,129 @@ impl<'arena> Arena<'arena> { }), } }, - }) + + Decl(decl) => decl.get_type_or_try_init(Term::infer_raw, arena), + } + } + + /// Infers the type of the term `t`, living in arena `arena`, checks for memoization. + pub fn infer(self, arena: &mut Arena<'arena>) -> ResultTerm<'arena> { + self.get_type_or_try_init(|| self.infer_raw(arena)) } - /// Checks whether the term `t` living in `self` is of type `ty`. - pub fn check(&mut self, t: Term<'arena>, ty: Term<'arena>) -> Result<'arena, ()> { - let tty = self.infer(t)?; + /// Checks whether the term `t` living in `arena` is of type `ty`. + pub fn check(self, ty: Self, arena: &mut Arena<'arena>) -> Result<'arena, ()> { + let tty = self.infer(arena)?; - self.conversion(tty, ty).then_some(()).ok_or(Error { + tty.conversion(ty, arena).then_some(()).ok_or(Error { kind: TypeCheckerError::TypeMismatch(tty, ty).into(), }) } } +impl<'arena> Declaration<'arena> { + /// Infer the type of a declaration. + /// + /// Because it is not allowed to access the underlying term of a declaration, this function + /// does not return anything, and only serves as a way to ensure the declaration is + /// well-formed. + pub fn infer(self, arena: &mut Arena<'arena>) -> Result<'arena, ()> { + self.0.infer(arena)?; + Ok(()) + } + + pub fn check(self, ty: Self, arena: &mut Arena<'arena>) -> Result<'arena, ()> { + self.0.check(ty.0, arena) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::term::arena::use_arena; - use crate::term::builders::raw::*; + use crate::memory::arena::use_arena; + use crate::memory::term::builder::raw::*; - fn id<'arena>() -> impl BuilderTrait<'arena> { + fn id() -> impl BuilderTrait { abs(prop(), var(1.into(), prop())) } #[test] fn def_eq_1() { use_arena(|arena| { - let id = arena.build_raw(id()); - let term = arena.build_raw(app(abs(prop(), id.into()), id.into())); - let normal_form = arena.build_raw(abs(prop(), var(1.into(), prop()))); + let term = arena.build_term_raw(app(abs(prop(), id()), id())); + let normal_form = arena.build_term_raw(abs(prop(), var(1.into(), prop()))); + + assert!(term.is_def_eq(normal_form, arena).is_ok()) + }) + } + + #[test] + fn conv_decl() { + use_arena(|arena| { + let decl_ = crate::memory::declaration::InstantiatedDeclaration::instantiate( + crate::memory::declaration::builder::Builder::Decl(crate::memory::term::builder::Builder::Prop.into(), Vec::new()) + .realise(arena) + .unwrap(), + &Vec::new(), + arena, + ); + let decl = crate::memory::term::Term::decl(decl_, arena); + + let prop = arena.build_term_raw(prop()); + + assert!(decl.is_def_eq(prop, arena).is_ok()); + assert!(prop.is_def_eq(decl, arena).is_ok()); + }) + } + + #[test] + fn infer_decl() { + //use std::env; + //env::set_var("RUST_BACKTRACE", "1"); + use_arena(|arena| { + let decl_ = crate::memory::declaration::InstantiatedDeclaration::instantiate( + crate::memory::declaration::builder::Builder::Decl(crate::memory::term::builder::Builder::Prop.into(), Vec::new()) + .realise(arena) + .unwrap(), + &Vec::new(), + arena, + ); + + let term = crate::memory::term::Term::decl(decl_, arena); + let ty = arena.build_term_raw(type_usize(0)); - assert!(arena.is_def_eq(term, normal_form).is_ok()) + assert!(term.check(ty, arena).is_ok()); }) } #[test] fn def_eq_2() { use_arena(|arena| { - let id = arena.build_raw(id()); - let term = arena.build_raw(app(abs(prop(), abs(prop(), var(2.into(), prop()))), id.into())); - let normal_form = arena.build_raw(abs(prop(), id.into())); + let term = arena.build_term_raw(app(abs(prop(), abs(prop(), var(2.into(), prop()))), id())); + let normal_form = arena.build_term_raw(abs(prop(), id())); - assert!(arena.is_def_eq(term, normal_form).is_ok()) + assert!(term.is_def_eq(normal_form, arena).is_ok()) }) } #[test] fn def_eq_self() { use_arena(|arena| { - let id = arena.build_raw(id()); // λa.a (λx.x) (λx.x) - let term = arena.build_raw(abs(prop(), app(app(var(2.into(), prop()), id.into()), id.into()))); + let term = arena.build_term_raw(abs(prop(), app(app(var(2.into(), prop()), id()), id()))); - assert!(arena.is_def_eq(term, term).is_ok()); + assert!(term.is_def_eq(term, arena).is_ok()); }) } #[test] fn failed_def_equal() { use_arena(|arena| { - let term_lhs = arena.prop(); - let term_rhs = arena.type_usize(0); + let term_lhs = Term::prop(arena); + let term_rhs = Term::type_usize(0, arena); assert_eq!( - arena.is_def_eq(term_lhs, term_rhs), + term_lhs.is_def_eq(term_rhs, arena), Err(Error { kind: TypeCheckerError::NotDefEq(term_lhs, term_rhs).into() }) @@ -232,13 +285,11 @@ mod tests { #[test] fn failed_prod_binder_conversion() { use_arena(|arena| { - let type_0 = arena.type_usize(0); - - let term_lhs = arena.build_raw(prod(prop(), prop())); - let term_rhs = arena.build_raw(prod(type_0.into(), prop())); + let term_lhs = arena.build_term_raw(prod(prop(), prop())); + let term_rhs = arena.build_term_raw(prod(type_usize(0), prop())); assert_eq!( - arena.is_def_eq(term_lhs, term_rhs), + term_lhs.is_def_eq(term_rhs, arena), Err(Error { kind: TypeCheckerError::NotDefEq(term_lhs, term_rhs).into() }) @@ -249,13 +300,11 @@ mod tests { #[test] fn failed_app_head_conversion() { use_arena(|arena| { - let type_0 = arena.type_usize(0); - let term_lhs = arena.build_raw(abs(type_0.into(), abs(type_0.into(), app(var(1.into(), prop()), prop())))); - - let term_rhs = arena.build_raw(abs(type_0.into(), abs(type_0.into(), app(var(2.into(), prop()), prop())))); + let term_lhs = arena.build_term_raw(abs(type_usize(0), abs(type_usize(0), app(var(1.into(), prop()), prop())))); + let term_rhs = arena.build_term_raw(abs(type_usize(0), abs(type_usize(0), app(var(2.into(), prop()), prop())))); assert_eq!( - arena.is_def_eq(term_lhs, term_rhs), + term_lhs.is_def_eq(term_rhs, arena), Err(Error { kind: TypeCheckerError::NotDefEq(term_lhs, term_rhs).into() }) @@ -266,15 +315,15 @@ mod tests { #[test] fn typed_reduction_app_1() { use_arena(|arena| { - let type_0 = arena.type_usize(0); - let term = arena.build_raw(app(abs(type_0.into(), var(1.into(), type_0.into())), prop())); + let type_0 = Term::type_usize(0, arena); + let term = arena.build_term_raw(app(abs(type_usize(0), var(1.into(), type_usize(0))), prop())); - let reduced = arena.build_raw(prop()); - assert!(arena.is_def_eq(term, reduced).is_ok()); + let reduced = arena.build_term_raw(prop()); + assert!(term.is_def_eq(reduced, arena).is_ok()); - let term_type = arena.infer(term).unwrap(); + let term_type = term.infer(arena).unwrap(); assert_eq!(term_type, type_0); - assert!(arena.check(term, term_type).is_ok()) + assert!(term.check(term_type, arena).is_ok()) }) } @@ -282,7 +331,7 @@ mod tests { // this test uses more intricate terms. In order to preserve some readability, // switching to extern_build, which is clearer. fn typed_reduction_app_2() { - use crate::term::builders::*; + use crate::memory::term::builder::*; use_arena(|arena| { // (λa.λb.λc.a (λd.λe.e (d b)) (λ_.c) (λd.d)) (λf.λg.f g) let term = arena @@ -350,42 +399,40 @@ mod tests { // λa: P. λb: P. b let reduced = arena.build(abs("_", prop(), abs("x", prop(), var("x")))).unwrap(); - assert!(arena.is_def_eq(term, reduced).is_ok()); + assert!(term.is_def_eq(reduced, arena).is_ok()); - let term_type = arena.infer(term).unwrap(); + let term_type = term.infer(arena).unwrap(); let expected_type = arena.build(prod("_", prop(), prod("_", prop(), prop()))).unwrap(); assert_eq!(term_type, expected_type); - assert!(arena.check(term, term_type).is_ok()) + assert!(term.check(term_type, arena).is_ok()) }) } #[test] fn typed_reduction_universe() { use_arena(|arena| { - let type_0 = arena.type_usize(0); - let type_1 = arena.type_usize(1); + let type_0 = Term::type_usize(0, arena); + let type_1 = Term::type_usize(1, arena); - let term = arena.build_raw(app(abs(prop(), type_0.into()), prod(prop(), var(1.into(), prop())))); + let term = arena.build_term_raw(app(abs(prop(), type_usize(0)), prod(prop(), var(1.into(), prop())))); - assert!(arena.is_def_eq(term, type_0).is_ok()); + assert!(term.is_def_eq(type_0, arena).is_ok()); - let term_type = arena.infer(term).unwrap(); + let term_type = term.infer(arena).unwrap(); assert_eq!(term_type, type_1); - assert!(arena.check(term, term_type).is_ok()) + assert!(term.check(term_type, arena).is_ok()) }) } #[test] fn escape_from_prop() { use_arena(|arena| { - let type_0 = arena.type_usize(0); - let type_1 = arena.type_usize(1); - let term = arena.build_raw(abs(prop(), prod(var(1.into(), prop()), type_0.into()))); + let term = arena.build_term_raw(abs(prop(), prod(var(1.into(), prop()), type_usize(0)))); - let term_type = arena.infer(term).unwrap(); - let expected_type = arena.build_raw(prod(prop(), type_1.into())); + let term_type = term.infer(arena).unwrap(); + let expected_type = arena.build_term_raw(prod(prop(), type_usize(1))); assert_eq!(term_type, expected_type); - assert!(arena.check(term, term_type).is_ok()) + assert!(term.check(term_type, arena).is_ok()) }) } @@ -394,7 +441,7 @@ mod tests { use_arena(|arena| { // λ(x: P -> P).(λ(y: P).x y) x // λP -> P.(λP.2 1) 1 - let term = arena.build_raw(abs( + let term = arena.build_term_raw(abs( prod(prop(), prop()), app( abs(prop(), app(var(2.into(), prod(prop(), prop())), var(1.into(), prop()))), @@ -403,68 +450,66 @@ mod tests { )); // λx.x x - let reduced = arena.build_raw(abs(prop(), app(var(1.into(), prop()), var(1.into(), prop())))); + let reduced = arena.build_term_raw(abs(prop(), app(var(1.into(), prop()), var(1.into(), prop())))); - assert!(arena.is_def_eq(term, reduced).is_ok()) + assert!(term.is_def_eq(reduced, arena).is_ok()) }) } #[test] fn typed_prod_1() { use_arena(|arena| { - let type_0 = arena.type_usize(0); - let term = arena.build_raw(prod(prop(), prop())); - let term_type = arena.infer(term).unwrap(); + let type_0 = Term::type_usize(0, arena); + let term = arena.build_term_raw(prod(prop(), prop())); + let term_type = term.infer(arena).unwrap(); assert_eq!(term_type, type_0); - assert!(arena.check(term, term_type).is_ok()) + assert!(term.check(term_type, arena).is_ok()) }) } #[test] fn typed_prod_2() { use_arena(|arena| { - let term = arena.build_raw(prod(prop(), var(1.into(), prop()))); - let term_type = arena.infer(term).unwrap(); + let term = arena.build_term_raw(prod(prop(), var(1.into(), prop()))); + let term_type = term.infer(arena).unwrap(); - assert_eq!(term_type, arena.prop()); - assert!(arena.check(term, term_type).is_ok()); + assert_eq!(term_type, Term::prop(arena)); + assert!(term.check(term_type, arena).is_ok()); }) } #[test] fn typed_prod_3() { use_arena(|arena| { - let term = arena.build_raw(abs(prop(), abs(var(1.into(), prop()), var(1.into(), var(2.into(), prop()))))); - let term_type = arena.infer(term).unwrap(); - let expected_type = arena.build_raw(prod(prop(), prod(var(1.into(), prop()), var(2.into(), prop())))); + let term = arena.build_term_raw(abs(prop(), abs(var(1.into(), prop()), var(1.into(), var(2.into(), prop()))))); + let term_type = term.infer(arena).unwrap(); + let expected_type = arena.build_term_raw(prod(prop(), prod(var(1.into(), prop()), var(2.into(), prop())))); assert_eq!(term_type, expected_type); - assert!(arena.check(term, term_type).is_ok()); + assert!(term.check(term_type, arena).is_ok()); }) } #[test] fn typed_polymorphism() { use_arena(|arena| { - let type_0 = arena.type_usize(0); + let identity = arena + .build_term_raw(abs(type_usize(0), abs(var(1.into(), type_usize(0)), var(1.into(), var(2.into(), type_usize(0)))))); - let identity = - arena.build_raw(abs(type_0.into(), abs(var(1.into(), type_0.into()), var(1.into(), var(2.into(), type_0.into()))))); - - let identity_type = arena.infer(identity).unwrap(); + let identity_type = identity.infer(arena).unwrap(); let expected_type = - arena.build_raw(prod(type_0.into(), prod(var(1.into(), type_0.into()), var(2.into(), type_0.into())))); + arena.build_term_raw(prod(type_usize(0), prod(var(1.into(), type_usize(0)), var(2.into(), type_usize(0))))); assert_eq!(identity_type, expected_type); - assert!(arena.check(identity, identity_type).is_ok()); + assert!(identity.check(identity_type, arena).is_ok()); }) } #[test] fn typed_polymorphism_2() { use_arena(|arena| { - let term = arena.build_raw(abs( + let term = arena.build_term_raw(abs( prop(), abs( prop(), @@ -474,32 +519,35 @@ mod tests { ), ), )); - let term_type = arena.infer(term).unwrap(); + let term_type = term.infer(arena).unwrap(); - assert_eq!(term_type, arena.build_raw(prod(prop(), prod(prop(), prod(prod(prop(), prod(prop(), prop())), prop()))))); - assert!(arena.check(term, term_type).is_ok()); + assert_eq!( + term_type, + arena.build_term_raw(prod(prop(), prod(prop(), prod(prod(prop(), prod(prop(), prop())), prop())))) + ); + assert!(term.check(term_type, arena).is_ok()); }) } #[test] fn type_hierarchy_prop() { use_arena(|arena| { - let term = arena.prop(); - let term_type = arena.infer(term).unwrap(); + let term = Term::prop(arena); + let term_type = term.infer(arena).unwrap(); - assert_eq!(term_type, arena.type_usize(0)); - assert!(arena.check(term, term_type).is_ok()); + assert_eq!(term_type, Term::type_usize(0, arena)); + assert!(term.check(term_type, arena).is_ok()); }) } #[test] fn type_hierarchy_type() { use_arena(|arena| { - let term = arena.type_usize(0); - let term_type = arena.infer(term).unwrap(); + let term = Term::type_usize(0, arena); + let term_type = term.infer(arena).unwrap(); - assert_eq!(term_type, arena.type_usize(1)); - assert!(arena.check(term, term_type).is_ok()); + assert_eq!(term_type, Term::type_usize(1, arena)); + assert!(term.check(term_type, arena).is_ok()); }) } @@ -509,12 +557,16 @@ mod tests { #[test] fn not_function_abs() { use_arena(|arena| { - let term = arena.build_raw(abs(app(prop(), prop()), prop())); + let term = arena.build_term_raw(abs(app(prop(), prop()), prop())); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotAFunction(TypedTerm(arena.prop(), arena.type_usize(0)), arena.prop()).into() + kind: TypeCheckerError::NotAFunction( + TypedTerm(Term::prop(arena), Term::type_usize(0, arena)), + Term::prop(arena) + ) + .into() }) ); }) @@ -523,12 +575,16 @@ mod tests { #[test] fn not_function_prod_1() { use_arena(|arena| { - let term = arena.build_raw(prod(prop(), app(prop(), prop()))); + let term = arena.build_term_raw(prod(prop(), app(prop(), prop()))); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotAFunction(TypedTerm(arena.prop(), arena.type_usize(0)), arena.prop()).into() + kind: TypeCheckerError::NotAFunction( + TypedTerm(Term::prop(arena), Term::type_usize(0, arena)), + Term::prop(arena) + ) + .into() }) ); }) @@ -537,12 +593,16 @@ mod tests { #[test] fn not_function_prod_2() { use_arena(|arena| { - let term = arena.build_raw(prod(app(prop(), prop()), prop())); + let term = arena.build_term_raw(prod(app(prop(), prop()), prop())); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotAFunction(TypedTerm(arena.prop(), arena.type_usize(0)), arena.prop()).into() + kind: TypeCheckerError::NotAFunction( + TypedTerm(Term::prop(arena), Term::type_usize(0, arena)), + Term::prop(arena) + ) + .into() }) ); }) @@ -551,12 +611,16 @@ mod tests { #[test] fn not_function_app_1() { use_arena(|arena| { - let term = arena.build_raw(app(prop(), prop())); + let term = arena.build_term_raw(app(prop(), prop())); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotAFunction(TypedTerm(arena.prop(), arena.type_usize(0)), arena.prop()).into() + kind: TypeCheckerError::NotAFunction( + TypedTerm(Term::prop(arena), Term::type_usize(0, arena)), + Term::prop(arena) + ) + .into() }) ); }) @@ -565,12 +629,16 @@ mod tests { #[test] fn not_function_app_2() { use_arena(|arena| { - let term = arena.build_raw(app(app(prop(), prop()), prop())); + let term = arena.build_term_raw(app(app(prop(), prop()), prop())); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotAFunction(TypedTerm(arena.prop(), arena.type_usize(0)), arena.prop()).into() + kind: TypeCheckerError::NotAFunction( + TypedTerm(Term::prop(arena), Term::type_usize(0, arena)), + Term::prop(arena) + ) + .into() }) ); }) @@ -579,12 +647,16 @@ mod tests { #[test] fn not_function_app_3() { use_arena(|arena| { - let term = arena.build_raw(app(abs(prop(), prop()), app(prop(), prop()))); + let term = arena.build_term_raw(app(abs(prop(), prop()), app(prop(), prop()))); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotAFunction(TypedTerm(arena.prop(), arena.type_usize(0)), arena.prop()).into() + kind: TypeCheckerError::NotAFunction( + TypedTerm(Term::prop(arena), Term::type_usize(0, arena)), + Term::prop(arena) + ) + .into() }) ); }) @@ -595,7 +667,7 @@ mod tests { use_arena(|arena| { // λ(x: P -> P).(λ(y: P).x y) x // λP -> P.(λP.2 1) 1 - let term = arena.build_raw(abs( + let term = arena.build_term_raw(abs( prod(prop(), prop()), app( abs(prop(), app(var(2.into(), prod(prop(), prop())), var(1.into(), prop()))), @@ -604,12 +676,15 @@ mod tests { )); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { kind: TypeCheckerError::WrongArgumentType( - arena.build_raw(abs(prop(), app(var(2.into(), prod(prop(), prop())), var(1.into(), prop())))), - arena.prop(), - TypedTerm(arena.build_raw(var(1.into(), prod(prop(), prop()))), arena.build_raw(prod(prop(), prop()))) + arena.build_term_raw(abs(prop(), app(var(2.into(), prod(prop(), prop())), var(1.into(), prop())))), + Term::prop(arena), + TypedTerm( + arena.build_term_raw(var(1.into(), prod(prop(), prop()))), + arena.build_term_raw(prod(prop(), prop())) + ) ) .into() }) @@ -620,15 +695,15 @@ mod tests { #[test] fn not_universe_abs() { use_arena(|arena| { - let term = arena.build_raw(abs( + let term = arena.build_term_raw(abs( prop(), abs(var(1.into(), prop()), abs(var(1.into(), var(2.into(), prop())), var(1.into(), var(2.into(), prop())))), )); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotUniverse(arena.build_raw(var(2.into(), prop()))).into() + kind: TypeCheckerError::NotUniverse(arena.build_term_raw(var(2.into(), prop()))).into() }) ); }) @@ -637,12 +712,12 @@ mod tests { #[test] fn not_universe_prod_1() { use_arena(|arena| { - let term = arena.build_raw(prod(id(), prop())); + let term = arena.build_term_raw(prod(id(), prop())); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotUniverse(arena.build_raw(prod(prop(), prop()))).into() + kind: TypeCheckerError::NotUniverse(arena.build_term_raw(prod(prop(), prop()))).into() }) ); }) @@ -651,13 +726,14 @@ mod tests { #[test] fn not_universe_prod_2() { use_arena(|arena| { - let term = arena.build_raw(prod(prop(), abs(prop(), prop()))); + let term = arena.build_term_raw(prod(prop(), abs(prop(), prop()))); + let prop = Term::prop(arena); + let type_ = Term::type_usize(0, arena); assert_eq!( - arena.infer(term), + term.infer(arena), Err(Error { - kind: TypeCheckerError::NotUniverse(arena.build_raw(prod(prop(), type_(BigUint::from(0_u64).into())))) - .into() + kind: TypeCheckerError::NotUniverse(prop.prod(type_, arena)).into() }) ); }) @@ -666,13 +742,17 @@ mod tests { #[test] fn check_fail_1() { use_arena(|arena| { - let term = arena.build_raw(app(prop(), prop())); - let expected_type = arena.prop(); + let term = arena.build_term_raw(app(prop(), prop())); + let expected_type = Term::prop(arena); assert_eq!( - arena.check(term, expected_type), + term.check(expected_type, arena), Err(Error { - kind: TypeCheckerError::NotAFunction(TypedTerm(arena.prop(), arena.type_usize(0)), arena.prop()).into(), + kind: TypeCheckerError::NotAFunction( + TypedTerm(Term::prop(arena), Term::type_usize(0, arena)), + Term::prop(arena) + ) + .into(), }) ); }) @@ -681,11 +761,11 @@ mod tests { #[test] fn check_fail_2() { use_arena(|arena| { - let prop = arena.prop(); + let prop = Term::prop(arena); assert_eq!( - arena.check(prop, prop), + prop.check(prop, arena), Err(Error { - kind: TypeCheckerError::TypeMismatch(arena.type_usize(0), prop).into(), + kind: TypeCheckerError::TypeMismatch(Term::type_usize(0, arena), prop).into(), }) ); }) diff --git a/kernel/tests/and.rs b/kernel/tests/and.rs index c5013a39..ac4b8394 100644 --- a/kernel/tests/and.rs +++ b/kernel/tests/and.rs @@ -1,5 +1,5 @@ -use kernel::term::arena::{use_arena, Arena}; -use kernel::term::builders::*; +use kernel::memory::arena::{use_arena, Arena}; +use kernel::memory::term::builder::*; fn use_and_arena<F, T>(f: F) -> T where @@ -36,7 +36,7 @@ fn and_true_true() { let hypothesis = arena.build(abs("x", var("False"), var("x"))).unwrap(); let true_ = arena.build(var("True")).unwrap(); - assert!(arena.check(hypothesis, true_).is_ok()); + assert!(hypothesis.check(true_, arena).is_ok()); arena.bind("hyp", hypothesis); let proof = arena @@ -47,7 +47,7 @@ fn and_true_true() { )) .unwrap(); - assert!(arena.check(proof, goal).is_ok()); + assert!(proof.check(goal, arena).is_ok()); }) } @@ -101,7 +101,7 @@ fn and_intro() { )) .unwrap(); - assert!(arena.check(proof, goal).is_ok()); + assert!(proof.check(goal, arena).is_ok()); }) } @@ -154,7 +154,7 @@ fn and_elim_1() { )) .unwrap(); - assert!(arena.check(proof, goal).is_ok()); + assert!(proof.check(goal, arena).is_ok()); }) } @@ -212,6 +212,6 @@ fn and_elim_2() { )) .unwrap(); - assert!(arena.check(proof, goal).is_ok()); + assert!(proof.check(goal, arena).is_ok()); }) } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 02b56ba1..d8817526 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -9,6 +9,5 @@ license.workspace = true kernel.path = "../kernel" derive_more.workspace = true -num-bigint.workspace = true pest.workspace = true pest_derive.workspace = true diff --git a/parser/src/command.rs b/parser/src/command.rs index 2daa4448..3f3c1fc1 100644 --- a/parser/src/command.rs +++ b/parser/src/command.rs @@ -4,15 +4,19 @@ use core::fmt; -use kernel::term::builders::Builder; +use kernel::memory::declaration::builder as declaration; +use kernel::memory::term::builder::Builder; /// The type of commands that can be received by the kernel. #[derive(Debug, Eq, PartialEq)] pub enum Command<'build> { - /// Define a new term and optionally check that it's type match the given one. + /// Define the given term Define(&'build str, Option<Builder<'build>>, Builder<'build>), - /// Infer the type of a term and check that it match the given one. + /// Define the given declaration + Declaration(&'build str, Option<declaration::Builder<'build>>, declaration::Builder<'build>), + + /// Infer the type of a term and check that it matches the given one. CheckType(Builder<'build>, Builder<'build>), /// Infer the type of a term. @@ -33,22 +37,26 @@ impl<'build> fmt::Display for Command<'build> { use Command::*; match self { - Define(name, None, t) => write!(f, "def {} := {}", name, t), + Define(name, None, t) => write!(f, "def {name} := {t}"), + + Define(name, Some(ty), t) => write!(f, "def {name}: {ty} := {t}"), + + Declaration(name, None, t) => write!(f, "def {name} := {t}"), - Define(name, Some(ty), t) => write!(f, "def {}: {} := {}", name, ty, t), + Declaration(name, Some(ty), t) => write!(f, "def {name}: {ty} := {t}"), - CheckType(t, ty) => write!(f, "check {}: {}", t, ty), + CheckType(t, ty) => write!(f, "check {t}: {ty}"), - GetType(t) => write!(f, "check {}", t), + GetType(t) => write!(f, "check {t}"), - Eval(t) => write!(f, "eval {}", t), + Eval(t) => write!(f, "eval {t}"), Import(files) => { write!(f, "imports")?; files.iter().try_for_each(|file| write!(f, " {file}")) }, - Search(name) => write!(f, "search {}", name), + Search(name) => write!(f, "search {name}"), } } } diff --git a/parser/src/parser.rs b/parser/src/parser.rs index 8084d02e..a997617f 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -1,11 +1,13 @@ use kernel::location::Location; -use kernel::term::builders::Builder; +use kernel::memory::declaration::builder as declaration; +use kernel::memory::level::builder as level; +use kernel::memory::term::builder as term; use pest::error::LineColLocation; use pest::iterators::Pair; use pest::{Parser, Span}; use crate::command::Command; -use crate::error::{Error, ErrorKind}; +use crate::error; #[derive(Parser)] #[grammar = "term.pest"] @@ -18,9 +20,52 @@ fn convert_span(span: Span) -> Location { ((x1, y1), (x2, y2)).into() } +/// build universe level from errorless pest's output +fn parse_level(pair: Pair<Rule>) -> level::Builder { + use level::Builder::*; + + // location to be used in a future version + let _loc = convert_span(pair.as_span()); + + match pair.as_rule() { + Rule::Num => { + let n = pair.into_inner().as_str().parse().unwrap(); + Const(n) + }, + + Rule::Max => { + let mut iter = pair.into_inner(); + let univ1 = parse_level(iter.next().unwrap()); + let univ2 = parse_level(iter.next().unwrap()); + Max(box univ1, box univ2) + }, + + Rule::IMax => { + let mut iter = pair.into_inner(); + let univ1 = parse_level(iter.next().unwrap()); + let univ2 = parse_level(iter.next().unwrap()); + IMax(box univ1, box univ2) + }, + + Rule::Plus => { + let mut iter = pair.into_inner(); + let univ = parse_level(iter.next().unwrap()); + let n = iter.map(|x| x.as_str().parse::<usize>().unwrap()).sum(); + Plus(box univ, n) + }, + + Rule::string => { + let name = pair.as_str(); + Var(name) + }, + + univ => unreachable!("unexpected universe level: {univ:?}"), + } +} + /// Returns a kernel term builder from pest output -fn parse_term(pair: Pair<Rule>) -> Builder { - use Builder::*; +fn parse_term(pair: Pair<Rule>) -> term::Builder { + use term::Builder::*; // location to be used in a future version let _loc = convert_span(pair.as_span()); @@ -30,12 +75,33 @@ fn parse_term(pair: Pair<Rule>) -> Builder { Rule::Var => Var(pair.into_inner().as_str()), - Rule::Type => Type(pair.into_inner().fold(0, |_, x| x.as_str().parse::<usize>().unwrap())), + Rule::VarDecl => { + let mut iter = pair.into_inner(); + let name = iter.next().unwrap().as_str(); + let levels = iter.next().unwrap().into_inner().map(parse_level).collect(); + Decl(box declaration::InstantiatedBuilder::Var(name, levels)) + }, + + Rule::Type => { + if let Some(next) = pair.into_inner().next_back() { + Type(box parse_level(next)) + } else { + Type(box level::Builder::Const(0)) + } + }, + + Rule::Sort => { + if let Some(next) = pair.into_inner().next_back() { + Sort(box parse_level(next)) + } else { + Sort(box level::Builder::Const(0)) + } + }, Rule::App => { let mut iter = pair.into_inner().map(parse_term); let t = iter.next().unwrap(); - iter.fold(t, |acc, x| App(Box::new(acc), Box::new(x))) + iter.fold(t, |acc, x| App(box acc, box x)) }, Rule::Abs => { @@ -43,11 +109,11 @@ fn parse_term(pair: Pair<Rule>) -> Builder { let body = parse_term(iter.next_back().unwrap()); iter.flat_map(|pair| { let mut pair = pair.into_inner(); - let type_ = Box::new(parse_term(pair.next_back().unwrap())); + let type_ = box parse_term(pair.next_back().unwrap()); pair.map(move |var| (var.as_str(), type_.clone())) }) .rev() - .fold(body, |acc, (var, type_)| Abs(var, type_, Box::new(acc))) + .fold(body, |acc, (var, type_)| Abs(var, type_, box acc)) }, Rule::dProd => { @@ -55,25 +121,25 @@ fn parse_term(pair: Pair<Rule>) -> Builder { let body = parse_term(iter.next_back().unwrap()); iter.flat_map(|pair| { let mut pair = pair.into_inner(); - let type_ = Box::new(parse_term(pair.next_back().unwrap())); + let type_ = box parse_term(pair.next_back().unwrap()); pair.map(move |var| (var.as_str(), type_.clone())) }) .rev() - .fold(body, |acc, (var, type_)| Prod(var, type_, Box::new(acc))) + .fold(body, |acc, (var, type_)| Prod(var, type_, box acc)) }, Rule::Prod => { let mut iter = pair.into_inner(); let ret = parse_term(iter.next_back().unwrap()); - iter.map(parse_term).rev().fold(ret, |acc, argtype| Prod("_", Box::new(argtype), Box::new(acc))) + iter.map(parse_term).rev().fold(ret, |acc, argtype| Prod("_", box argtype, box acc)) }, - term => unreachable!("Unexpected term: {:?}", term), + term => unreachable!("unexpected term: {term:?}"), } } /// build commands from errorless pest's output -fn parse_expr<'build>(pair: Pair<'build, Rule>) -> Command<'build> { +fn parse_expr(pair: Pair<Rule>) -> Command { // location to be used in a future version let _loc = convert_span(pair.as_span()); @@ -93,17 +159,42 @@ fn parse_expr<'build>(pair: Pair<'build, Rule>) -> Command<'build> { Rule::Define => { let mut iter = pair.into_inner(); - let s: &'build str = iter.next().unwrap().as_str(); + let s = iter.next().unwrap().as_str(); let term = parse_term(iter.next().unwrap()); Command::Define(s, None, term) }, Rule::DefineCheckType => { let mut iter = pair.into_inner(); - let s: &'build str = iter.next().unwrap().as_str(); - let t = parse_term(iter.next().unwrap()); + let s = iter.next().unwrap().as_str(); + let ty = parse_term(iter.next().unwrap()); let term = parse_term(iter.next().unwrap()); - Command::Define(s, Some(t), term) + + Command::Define(s, Some(ty), term) + }, + + Rule::Declaration => { + let mut iter = pair.into_inner(); + let mut string_decl = iter.next().unwrap().into_inner(); + let s = string_decl.next().unwrap().as_str(); + let vars: Vec<&str> = string_decl.next().unwrap().into_inner().map(|name| name.as_str()).collect(); + let body = iter.next().map(parse_term).unwrap(); + + Command::Declaration(s, None, declaration::Builder::Decl(box body, vars)) + }, + + Rule::DeclarationCheckType => { + let mut iter = pair.into_inner(); + let mut string_decl = iter.next().unwrap().into_inner(); + let s = string_decl.next().unwrap().as_str(); + let vars: Vec<&str> = string_decl.next().unwrap().into_inner().map(|name| name.as_str()).collect(); + + let ty = parse_term(iter.next().unwrap()); + let decl = iter.next().map(parse_term).unwrap(); + + let ty = declaration::Builder::Decl(box ty, vars.clone()); + let decl = declaration::Builder::Decl(box decl, vars); + Command::Declaration(s, Some(ty), decl) }, Rule::Eval => { @@ -126,12 +217,14 @@ fn parse_expr<'build>(pair: Pair<'build, Rule>) -> Command<'build> { } /// convert pest error to kernel error -fn convert_error(err: pest::error::Error<Rule>) -> Error { +fn convert_error(err: pest::error::Error<Rule>) -> error::Error { // renaming error messages let err = err.renamed_rules(|rule| match *rule { Rule::string | Rule::Var => "variable".to_owned(), - Rule::number => "universe level".to_owned(), + Rule::number => "number".to_owned(), Rule::Define => "def var := term".to_owned(), + Rule::Declaration => "def decl.{ vars, ... } := term".to_owned(), + Rule::DeclarationCheckType => "def decl.{ vars, ... } : term := term".to_owned(), Rule::CheckType => "check term : term".to_owned(), Rule::GetType => "check term".to_owned(), Rule::DefineCheckType => "def var : term := term".to_owned(), @@ -141,11 +234,19 @@ fn convert_error(err: pest::error::Error<Rule>) -> Error { Rule::App => "application".to_owned(), Rule::Prop => "Prop".to_owned(), Rule::Type => "Type".to_owned(), + Rule::Sort => "Sort".to_owned(), Rule::Eval => "eval term".to_owned(), Rule::filename => "path_to_file".to_owned(), Rule::ImportFile => "import path_to_file".to_owned(), Rule::Search => "search var".to_owned(), - _ => unreachable!("low level rules cannot appear in error messages"), + Rule::Max => "max".to_owned(), + Rule::Plus => "plus".to_owned(), + Rule::IMax => "imax".to_owned(), + Rule::arg_univ => "universe argument".to_owned(), + Rule::univ_decl => "universe declaration".to_owned(), + _ => { + unreachable!("low level rules cannot appear in error messages") + }, }); // extracting the location from the pest output @@ -183,8 +284,8 @@ fn convert_error(err: pest::error::Error<Rule>) -> Error { for _ in 0..4 { chars.next(); } - Error { - kind: ErrorKind::CannotParse(chars.as_str().to_string()), + error::Error { + kind: error::ErrorKind::CannotParse(chars.as_str().to_string()), location: loc, } } @@ -192,30 +293,32 @@ fn convert_error(err: pest::error::Error<Rule>) -> Error { /// Parse a text input and try to convert it into a command. /// /// if unsuccessful, a box containing the first error that was encountered is returned. -pub fn parse_line(line: &str) -> crate::error::Result<Command> { +pub fn parse_line(line: &str) -> error::Result<Command> { CommandParser::parse(Rule::command, line).map_err(convert_error).map(|mut pairs| parse_expr(pairs.next().unwrap())) } /// Parse a text input and try to convert it into a vector of commands. /// /// if unsuccessful, a box containing the first error that was encountered is returned. -pub fn parse_file(file: &str) -> crate::error::Result<Vec<Command>> { +pub fn parse_file(file: &str) -> error::Result<Vec<Command>> { CommandParser::parse(Rule::file, file).map_err(convert_error).map(|pairs| pairs.into_iter().map(parse_expr).collect()) } #[cfg(test)] mod tests { - use kernel::term::builders::*; - use Builder::*; + + use error::{Error, ErrorKind}; + use kernel::memory::term::builder as term; + use term::Builder::*; use super::Command::*; use super::*; /// Error messages - const COMMAND_ERR: &str = "expected def var := term, def var : term := term, check term : term, check term, eval term, import path_to_file, or search var"; - const TERM_ERR: &str = "expected variable, abstraction, dependent product, application, product, Prop, or Type"; - const SIMPLE_TERM_ERR: &str = "expected variable, abstraction, Prop, or Type"; - const UNIVERSE_ERR: &str = "expected universe level, variable, abstraction, Prop, or Type"; + const COMMAND_ERR: &str = "expected def var := term, def var : term := term, def decl.{ vars, ... } := term, def decl.{ vars, ... } : term := term, check term : term, check term, eval term, import path_to_file, or search var"; + const TERM_ERR: &str = "expected variable, abstraction, dependent product, application, product, Prop, Type, or Sort"; + const SIMPLE_TERM_ERR: &str = "expected variable, abstraction, Prop, Type, Sort, or universe argument"; + const UNIVERSE_ERR: &str = "expected number, variable, abstraction, Prop, Type, Sort, plus, max, or imax"; #[test] fn failure_universe_level() { @@ -230,7 +333,25 @@ mod tests { #[test] fn successful_define_with_type_annotation() { - assert_eq!(parse_line("def x : Type := Prop"), Ok(Define("x", Some(Type(0)), Prop))); + assert_eq!(parse_line("def x : Type := Prop"), Ok(Define("x", Some(Type(box level::Builder::Const(0))), Prop))); + } + + #[test] + fn successful_declare_with_type_annotation() { + assert_eq!( + parse_line("def x.{u} : Type u := foo.{u}"), + Ok(Declaration( + "x", + Some(declaration::Builder::Decl(box Type(box level::Builder::Var("u")), ["u"].to_vec())), + declaration::Builder::Decl( + box kernel::memory::term::builder::Builder::Decl(box declaration::InstantiatedBuilder::Var( + "foo", + [level::Builder::Var("u")].to_vec() + )), + ["u"].to_vec() + ) + )) + ); } #[test] @@ -254,9 +375,14 @@ mod tests { assert_eq!(parse_line("def x := Prop"), Ok(Define("x", None, Prop))); } + #[test] + fn successful_declare() { + assert_eq!(parse_line("def x.{} := Prop"), Ok(Declaration("x", None, declaration::Builder::Decl(box Prop, Vec::new())))); + } + #[test] fn successful_checktype() { - assert_eq!(parse_line("check Prop : Type"), Ok(CheckType(Prop, Type(0)))); + assert_eq!(parse_line("check Prop : Type"), Ok(CheckType(Prop, Type(box level::Builder::Const(0))))); } #[test] @@ -264,6 +390,11 @@ mod tests { assert_eq!(parse_line("check Prop"), Ok(GetType(Prop))); } + #[test] + fn successful_gettype_sort() { + assert_eq!(parse_line("check Sort"), Ok(GetType(Sort(box level::Builder::Const(0))))); + } + #[test] fn successful_var() { assert_eq!(parse_line("check fun A: Prop => A"), Ok(GetType(Abs("A", Box::new(Prop), Box::new(Var("A")))))); @@ -271,9 +402,25 @@ mod tests { #[test] fn successful_type() { - assert_eq!(parse_line("check Type"), Ok(GetType(Type(0)))); - assert_eq!(parse_line("check Type 0"), Ok(GetType(Type(0)))); - assert_eq!(parse_line("check Type 1"), Ok(GetType(Type(1)))); + assert_eq!(parse_line("check Type"), Ok(GetType(Type(box level::Builder::Const(0))))); + assert_eq!(parse_line("check Type 0"), Ok(GetType(Type(box level::Builder::Const(0))))); + assert_eq!(parse_line("check Type 1"), Ok(GetType(Type(box level::Builder::Const(1))))); + } + + #[test] + fn successful_sort() { + assert_eq!(parse_line("check Sort"), Ok(GetType(Sort(box level::Builder::Const(0))))); + assert_eq!(parse_line("check Sort 0"), Ok(GetType(Sort(box level::Builder::Const(0))))); + assert_eq!(parse_line("check Sort 1"), Ok(GetType(Sort(box level::Builder::Const(1))))); + assert_eq!(parse_line("check Sort (0 + 1)"), Ok(GetType(Sort(box level::Builder::Plus(box level::Builder::Const(0), 1))))); + assert_eq!( + parse_line("check Sort max 0 0"), + Ok(GetType(Sort(box level::Builder::Max(box level::Builder::Const(0), box level::Builder::Const(0))))) + ); + assert_eq!( + parse_line("check Sort imax 0 0"), + Ok(GetType(Sort(box level::Builder::IMax(box level::Builder::Const(0), box level::Builder::Const(0))))) + ); } #[test] @@ -296,7 +443,11 @@ mod tests { #[test] fn successful_dprod() { - let res = Ok(GetType(Prod("x", Box::new(Type(0)), Box::new(Prod("y", Box::new(Type(1)), Box::new(Var("x"))))))); + let res = Ok(GetType(Prod( + "x", + Box::new(Type(box level::Builder::Const(0))), + Box::new(Prod("y", Box::new(Type(box level::Builder::Const(1))), Box::new(Var("x")))), + ))); assert_eq!(parse_line("check (x:Type) -> (y:Type 1) -> x"), res); assert_eq!(parse_line("check (x:Type) -> ((y:Type 1) -> x)"), res); } @@ -383,13 +534,21 @@ mod tests { #[test] fn parenthesis_in_prod() { - let res = Prod("_", Box::new(Type(0)), Box::new(Prod("_", Box::new(Type(1)), Box::new(Type(2))))); + let res = Prod( + "_", + Box::new(Type(box level::Builder::Const(0))), + Box::new(Prod("_", Box::new(Type(box level::Builder::Const(1))), Box::new(Type(box level::Builder::Const(2))))), + ); assert_eq!(parse_line("check (((Type))) -> (((Type 1 -> Type 2)))"), Ok(GetType(res))); } #[test] fn parenthesis_in_dprod() { - let res = Prod("x", Box::new(Type(0)), Box::new(Prod("y", Box::new(Type(1)), Box::new(Var("x"))))); + let res = Prod( + "x", + Box::new(Type(box level::Builder::Const(0))), + Box::new(Prod("y", Box::new(Type(box level::Builder::Const(1))), Box::new(Var("x")))), + ); assert_eq!(parse_line("check (((x:Type))) -> ((((y:Type 1) -> x)))"), Ok(GetType(res))); } diff --git a/parser/src/term.pest b/parser/src/term.pest index fc78bc41..c0250907 100644 --- a/parser/src/term.pest +++ b/parser/src/term.pest @@ -3,14 +3,16 @@ COMMENT = _{ "//" ~ (!"\n" ~ ANY)* } number = @{ ASCII_DIGIT+ } filename = @{ ( ASCII_ALPHANUMERIC | PUNCTUATION )+ } string = @{ !keywords ~ ASCII_ALPHA ~ ( "_" | ASCII_ALPHANUMERIC )* } -keywords = @{ ( "fun" | "def" | "check" | "Prop" | "Type" ) ~ !ASCII_ALPHANUMERIC } +keywords = @{ ( "fun" | "def" | "check" | "Prop" | "Type" | "Sort" ) ~ !ASCII_ALPHANUMERIC } eoi = _{ !ANY } -Term = _{ Abs | dProd | Prod | App | Var | Prop | Type | "(" ~ Term ~ ")" } -term_prod = _{ App | Abs | dProd | Var | Prop | Type | "(" ~ Prod ~ ")" | "(" ~ term_prod ~ ")" } -term_app = _{ Abs | Var | Prop | Type | "(" ~ App ~ ")" | "(" ~ Prod ~ ")" | "(" ~ dProd ~ ")" | "(" ~ term_app ~ ")" } -term_dprod = _{ App | Abs | Prod | dProd | Var | Prop | Type | "(" ~ term_dprod ~ ")" } -term_abs = _{ App | Abs | Prod | dProd | Var | Prop | Type | "(" ~ term_abs ~ ")" } + + +Term = _{ Abs | dProd | Prod | App | VarDecl | Var | Prop | Type | Sort | "(" ~ Term ~ ")" } +term_prod = _{ App | Abs | dProd | VarDecl | Var | Prop | Type | Sort | "(" ~ Prod ~ ")" | "(" ~ term_prod ~ ")" } +term_app = _{ Abs | VarDecl | Var | Prop | Type | Sort | "(" ~ App ~ ")" | "(" ~ Prod ~ ")" | "(" ~ dProd ~ ")" | "(" ~ term_app ~ ")" } +term_dprod = _{ App | Abs | Prod | dProd | VarDecl | Var | Prop | Type | Sort | "(" ~ term_dprod ~ ")" } +term_abs = _{ App | Abs | Prod | dProd | VarDecl | Var | Prop | Type | Sort | "(" ~ term_abs ~ ")" } Abs = { ( "fun" ~ ( arg_abs_par ~ ( "," ~ arg_abs_par )* ) ~ "=>" ~ Term ) } arg_abs_par = _{ arg_abs | "(" ~ arg_abs_par ~ ")" } @@ -22,19 +24,33 @@ arg_dprod = { string+ ~ ":" ~ term_dprod } App = { term_app ~ term_app+ } Prod = { term_prod ~ ( "->" ~ term_prod )+ } - Prop = { "Prop" } -Type = { "Type(" ~ number ~ ")" | "Type" ~ number? } + +Type = { "Type" ~ univ? } +Sort = { "Sort" ~ univ? } +univ = _{ Plus | Max | IMax | Num | string | "(" ~ univ ~ ")" } +univ_plus = _{ Max | IMax | Num | string | "(" ~ univ ~ ")" } +Num = { number } +Plus = { univ_plus ~ ( "+" ~ number )+ } +Max = { ( "max" ~ "(" ~ univ ~ "," ~ univ ~ ")" ) | ( "max" ~ univ ~ univ ) } +IMax = { ( "imax" ~ "(" ~ univ ~ "," ~ univ ~ ")" ) | ( "imax" ~ univ ~ univ ) } + Var = { string } +VarDecl = ${ string ~ arg_univ } +stringDecl = ${ string ~ univ_decl } + +arg_univ = {".{" ~ (univ ~ ("," ~ univ)* )? ~ "}"} +univ_decl = {".{" ~ (string ~ ("," ~ string)* )? ~ "}"} -Command = _{ Define | CheckType | GetType | DefineCheckType | Eval | ImportFile | Search} +Command = _{ Define | Declaration | DeclarationCheckType | CheckType | GetType | DefineCheckType | Eval | ImportFile | Search} Define = { "def" ~ string ~ ":=" ~ Term } DefineCheckType = { "def" ~ string ~ ":" ~ Term ~ ":=" ~ Term } +Declaration = { "def" ~ stringDecl ~ ":=" ~ Term } +DeclarationCheckType = { "def" ~ stringDecl ~ ":" ~ Term ~ ":=" ~ Term } CheckType = { "check" ~ Term ~ ":" ~ Term } GetType = { "check" ~ Term } Eval = { "eval" ~ Term } ImportFile = { "import" ~ filename* } Search = { "search" ~ string } - command = _{SOI ~ Command ~ eoi } file = _{ SOI ~ Command* ~ eoi } diff --git a/proost/src/evaluator.rs b/proost/src/evaluator.rs index eba1b916..89a00c19 100644 --- a/proost/src/evaluator.rs +++ b/proost/src/evaluator.rs @@ -5,7 +5,8 @@ use std::path::PathBuf; use colored::Colorize; use derive_more::Display; use kernel::location::Location; -use kernel::term::arena::{Arena, Term}; +use kernel::memory::arena::Arena; +use kernel::memory::term::Term; use parser::command::Command; use parser::{parse_file, parse_line}; use path_absolutize::Absolutize; @@ -131,50 +132,65 @@ impl<'arena> Evaluator { .map(|_| None) } - fn process<'build>( + fn process( &mut self, arena: &mut Arena<'arena>, - command: &Command<'build>, + command: &Command, importing: &mut Vec<PathBuf>, ) -> Result<'arena, Option<Term<'arena>>> { match command { - Command::Define(s, None, term) => { - let term = term.realise(arena)?; - if arena.get_binding(s).is_none() { - arena.infer(term)?; - arena.bind(s, term); - Ok(None) - } else { - Err(Toplevel(Error { + Command::Define(s, ty, term) => { + if arena.get_binding(s).is_some() { + return Err(Toplevel(Error { kind: ErrorKind::BoundVariable(s.to_string()), location: Location::default(), // TODO (see #38) - })) + })); } - }, - - Command::Define(s, Some(t), term) => { let term = term.realise(arena)?; - let t = t.realise(arena)?; - arena.check(term, t)?; + match ty { + None => { + term.infer(arena)?; + }, + Some(ty) => term.check(ty.realise(arena)?, arena)?, + } arena.bind(s, term); Ok(None) }, + Command::Declaration(s, ty, decl) => { + if arena.get_binding_decl(s).is_some() { + return Err(Toplevel(Error { + kind: ErrorKind::BoundVariable(s.to_string()), + location: Location::default(), // TODO (see #38) + })); + } + let decl = decl.realise(arena)?; + match ty { + None => { + decl.infer(arena)?; + }, + Some(ty) => decl.check(ty.realise(arena)?, arena)?, + } + arena.bind_decl(s, decl); + Ok(None) + }, + Command::CheckType(t1, t2) => { let t1 = t1.realise(arena)?; let t2 = t2.realise(arena)?; - arena.check(t1, t2)?; + t1.check(t2, arena)?; Ok(None) }, Command::GetType(t) => { let t = t.realise(arena)?; - Ok(arena.infer(t).map(Some)?) + Ok(t.infer(arena).map(Some)?) }, Command::Eval(t) => { let t = t.realise(arena)?; - Ok(Some(arena.normal_form(t))) + let _ = t.infer(arena)?; + Ok(Some(t.normal_form(arena))) }, Command::Search(s) => Ok(arena.get_binding(s)), // TODO (see #49) diff --git a/proost/src/main.rs b/proost/src/main.rs index 08d25afb..cd141e10 100644 --- a/proost/src/main.rs +++ b/proost/src/main.rs @@ -53,7 +53,7 @@ fn main() -> Result<'static, ()> { rl.bind_sequence(KeyEvent::from('\t'), EventHandler::Conditional(Box::new(TabEventHandler))); rl.bind_sequence(KeyEvent(KeyCode::Enter, Modifiers::ALT), EventHandler::Simple(Cmd::Newline)); - kernel::term::arena::use_arena(|arena| { + kernel::memory::arena::use_arena(|arena| { let current_path = current_dir()?; let mut evaluator = Evaluator::new(current_path, args.verbose); -- GitLab