pub use self::Notation::*;
pub use self::Term::*;
use self::TermError::*;
use std::error::Error;
use std::fmt::{self, Write as _};
#[cfg(feature = "backslash_lambda")]
pub const LAMBDA: char = '\\';
#[cfg(not(feature = "backslash_lambda"))]
pub const LAMBDA: char = 'λ';
pub const UD: Term = Var(0);
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Notation {
Classic,
DeBruijn,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Context(Vec<String>);
impl Context {
pub fn new<S: AsRef<str>>(namings: &[S]) -> Self {
let owned = namings.iter().map(|s| s.as_ref().to_string()).collect();
Context(owned)
}
pub fn empty() -> Self {
vec![].into()
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &str> {
self.0.iter().map(|s| s.as_str())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn contains<S: AsRef<str>>(&self, name: S) -> bool {
self.iter().any(|item| item == name.as_ref())
}
pub fn resolve_free_var(&self, idx: usize) -> Option<&str> {
if idx == 0 {
None
} else {
self.0.get(idx - 1).map(|s| s.as_str())
}
}
}
impl<S: AsRef<str>> From<&[S]> for Context {
fn from(namings: &[S]) -> Self {
Self::new(namings)
}
}
impl From<Vec<String>> for Context {
fn from(namings: Vec<String>) -> Self {
Context(namings)
}
}
#[derive(PartialEq, Clone, Hash, Eq)]
pub enum Term {
Var(usize),
Abs(Box<Term>),
App(Box<(Term, Term)>),
}
#[derive(Debug, PartialEq, Eq)]
pub enum TermError {
NotVar,
NotAbs,
NotApp,
}
impl fmt::Display for TermError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TermError::NotVar => write!(f, "the term is not a variable",),
TermError::NotAbs => write!(f, "the term is not an abstraction"),
TermError::NotApp => write!(f, "the term is not an application"),
}
}
}
impl Error for TermError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
impl Term {
pub fn unvar(self) -> Result<usize, TermError> {
if let Var(n) = self {
Ok(n)
} else {
Err(NotVar)
}
}
pub fn unvar_ref(&self) -> Result<&usize, TermError> {
if let Var(ref n) = *self {
Ok(n)
} else {
Err(NotVar)
}
}
pub fn unvar_mut(&mut self) -> Result<&mut usize, TermError> {
if let Var(ref mut n) = *self {
Ok(n)
} else {
Err(NotVar)
}
}
pub fn unabs(self) -> Result<Term, TermError> {
if let Abs(x) = self {
Ok(*x)
} else {
Err(NotAbs)
}
}
pub fn unabs_ref(&self) -> Result<&Term, TermError> {
if let Abs(ref x) = *self {
Ok(x)
} else {
Err(NotAbs)
}
}
pub fn unabs_mut(&mut self) -> Result<&mut Term, TermError> {
if let Abs(ref mut x) = *self {
Ok(x)
} else {
Err(NotAbs)
}
}
pub fn unapp(self) -> Result<(Term, Term), TermError> {
if let App(boxed) = self {
let (lhs, rhs) = *boxed;
Ok((lhs, rhs))
} else {
Err(NotApp)
}
}
pub fn unapp_ref(&self) -> Result<(&Term, &Term), TermError> {
if let App(boxed) = self {
let (ref lhs, ref rhs) = **boxed;
Ok((lhs, rhs))
} else {
Err(NotApp)
}
}
pub fn unapp_mut(&mut self) -> Result<(&mut Term, &mut Term), TermError> {
if let App(boxed) = self {
let (ref mut lhs, ref mut rhs) = **boxed;
Ok((lhs, rhs))
} else {
Err(NotApp)
}
}
pub fn lhs(self) -> Result<Term, TermError> {
if let Ok((lhs, _)) = self.unapp() {
Ok(lhs)
} else {
Err(NotApp)
}
}
pub fn lhs_ref(&self) -> Result<&Term, TermError> {
if let Ok((lhs, _)) = self.unapp_ref() {
Ok(lhs)
} else {
Err(NotApp)
}
}
pub fn lhs_mut(&mut self) -> Result<&mut Term, TermError> {
if let Ok((lhs, _)) = self.unapp_mut() {
Ok(lhs)
} else {
Err(NotApp)
}
}
pub fn rhs(self) -> Result<Term, TermError> {
if let Ok((_, rhs)) = self.unapp() {
Ok(rhs)
} else {
Err(NotApp)
}
}
pub fn rhs_ref(&self) -> Result<&Term, TermError> {
if let Ok((_, rhs)) = self.unapp_ref() {
Ok(rhs)
} else {
Err(NotApp)
}
}
pub fn rhs_mut(&mut self) -> Result<&mut Term, TermError> {
if let Ok((_, rhs)) = self.unapp_mut() {
Ok(rhs)
} else {
Err(NotApp)
}
}
pub fn is_supercombinator(&self) -> bool {
let mut stack = vec![(0usize, self)];
while let Some((depth, term)) = stack.pop() {
match term {
Var(i) => {
if *i > depth || *i == 0 {
return false;
}
}
Abs(t) => stack.push((depth + 1, t)),
App(boxed) => {
let (ref f, ref a) = **boxed;
stack.push((depth, f));
stack.push((depth, a))
}
}
}
true
}
pub fn max_depth(&self) -> u32 {
match self {
Var(_) => 0,
Abs(t) => t.max_depth() + 1,
App(boxed) => {
let d0 = boxed.0.max_depth();
let d1 = boxed.1.max_depth();
d0.max(d1)
}
}
}
pub fn is_isomorphic_to(&self, other: &Term) -> bool {
match (self, other) {
(Var(x), Var(y)) => x == y,
(Abs(p), Abs(q)) => p.is_isomorphic_to(q),
(App(p), App(q)) => p.0.is_isomorphic_to(&q.0) && p.1.is_isomorphic_to(&q.1),
_ => false,
}
}
pub fn has_free_variables(&self) -> bool {
self.has_free_variables_helper(0)
}
fn has_free_variables_helper(&self, depth: usize) -> bool {
match self {
Var(x) => *x > depth || *x == 0,
Abs(p) => p.has_free_variables_helper(depth + 1),
App(p) => p.0.has_free_variables_helper(depth) || p.1.has_free_variables_helper(depth),
}
}
pub fn max_free_index(&self) -> usize {
self.max_free_index_helper(0)
}
fn max_free_index_helper(&self, depth: usize) -> usize {
match self {
Var(x) => x.saturating_sub(depth),
Abs(p) => p.max_free_index_helper(depth + 1),
App(p) => {
p.0.max_free_index_helper(depth)
.max(p.1.max_free_index_helper(depth))
}
}
}
pub fn with_context<'a>(&'a self, ctx: &'a Context) -> impl fmt::Display + 'a {
DisplayWithContext { term: self, ctx }
}
}
pub fn abs(term: Term) -> Term {
Abs(Box::new(term))
}
pub fn app(lhs: Term, rhs: Term) -> Term {
App(Box::new((lhs, rhs)))
}
impl fmt::Display for Term {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let naming = Naming::Auto {
max_depth: self.max_depth() as usize,
};
show_precedence_cla(&naming, self, f, 0, 0)
}
}
struct DisplayWithContext<'a> {
term: &'a Term,
ctx: &'a Context,
}
impl<'a> fmt::Display for DisplayWithContext<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let binder_names = generate_binder_names(self.ctx, self.term.max_depth());
let naming = Naming::Provided {
ctx: self.ctx,
binder_names: &binder_names,
};
show_precedence_cla(&naming, self.term, f, 0, 0)
}
}
enum Naming<'a> {
Auto { max_depth: usize },
Provided {
ctx: &'a Context,
binder_names: &'a [String],
},
}
impl Naming<'_> {
fn write_binder(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
match self {
Self::Auto { .. } => {
let mut buf = [0; MAX_BASE26_LEN];
f.write_str(base26_into(depth, &mut buf))
}
Self::Provided { binder_names, .. } => f.write_str(
binder_names
.get(depth)
.expect("[BUG] binder_names are insufficient"),
),
}
}
fn write_free(&self, f: &mut fmt::Formatter, idx: usize) -> fmt::Result {
match self {
Self::Auto { max_depth } => {
let mut buf = [0; MAX_BASE26_LEN];
f.write_str(base26_into(max_depth.saturating_add(idx) - 1, &mut buf))
}
Self::Provided { ctx, .. } => match ctx.resolve_free_var(idx) {
Some(name) => f.write_str(name),
None => write!(f, "<unknown{idx}>"),
},
}
}
}
fn generate_binder_names(ctx: &Context, number: u32) -> Vec<String> {
(0..)
.map(base26_encode)
.filter(|name| !ctx.contains(name))
.take(number as usize)
.collect()
}
const MAX_BASE26_LEN: usize = 16;
fn base26_into(mut n: usize, buf: &mut [u8; MAX_BASE26_LEN]) -> &str {
let mut len = 0;
loop {
buf[len] = b'a' + (n % 26) as u8;
len += 1;
if n < 26 {
break;
}
n = n / 26 - 1;
}
buf[..len].reverse();
std::str::from_utf8(&buf[..len]).expect("[BUG] base26 produced non-UTF-8")
}
fn base26_encode(n: usize) -> String {
let mut buf = [0; MAX_BASE26_LEN];
base26_into(n, &mut buf).to_owned()
}
fn show_precedence_cla(
naming: &Naming,
term: &Term,
f: &mut fmt::Formatter,
context_precedence: usize,
depth: usize,
) -> fmt::Result {
match term {
Var(0) => f.write_str("undefined"),
Var(i) if *i <= depth => naming.write_binder(f, depth - *i),
Var(i) => naming.write_free(f, *i - depth),
Abs(t) => {
let parenthesize = context_precedence > 1;
if parenthesize {
f.write_char('(')?;
}
f.write_char(LAMBDA)?;
naming.write_binder(f, depth)?;
f.write_char('.')?;
show_precedence_cla(naming, t, f, 0, depth + 1)?;
if parenthesize {
f.write_char(')')?;
}
Ok(())
}
App(boxed) => {
let (ref t1, ref t2) = **boxed;
let parenthesize = context_precedence == 3;
if parenthesize {
f.write_char('(')?;
}
show_precedence_cla(naming, t1, f, 2, depth)?;
f.write_char(' ')?;
show_precedence_cla(naming, t2, f, 3, depth)?;
if parenthesize {
f.write_char(')')?;
}
Ok(())
}
}
}
impl fmt::Debug for Term {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
show_precedence_dbr(self, f, 0)
}
}
fn show_precedence_dbr(
term: &Term,
f: &mut fmt::Formatter,
context_precedence: usize,
) -> fmt::Result {
match term {
Var(i) if *i <= 0xF && *i != 0 => write!(f, "{i:X}"),
Var(i) => write!(f, "[{i:X}]"),
Abs(t) => {
let parenthesize = context_precedence > 1;
if parenthesize {
f.write_char('(')?;
}
f.write_char(LAMBDA)?;
show_precedence_dbr(t, f, 0)?;
if parenthesize {
f.write_char(')')?;
}
Ok(())
}
App(boxed) => {
let (ref t1, ref t2) = **boxed;
let parenthesize = context_precedence == 3;
if parenthesize {
f.write_char('(')?;
}
show_precedence_dbr(t1, f, 2)?;
show_precedence_dbr(t2, f, 3)?;
if parenthesize {
f.write_char(')')?;
}
Ok(())
}
}
}
#[macro_export]
macro_rules! app {
($term1:expr, $($term2:expr),+) => {
{
let mut term = $term1;
$(term = app(term, $term2);)*
term
}
};
}
#[macro_export]
macro_rules! abs {
($n:expr, $term:expr) => {{
let mut term = $term;
for _ in 0..$n {
term = abs(term);
}
term
}};
}
#[cfg(test)]
mod tests {
use super::*;
fn lam(expected: &str) -> String {
expected.replace('λ', &LAMBDA.to_string())
}
#[test]
fn app_macro() {
assert_eq!(
app!(Var(4), app!(Var(1), Var(2), Var(3))),
app(Var(4), app(app(Var(1), Var(2)), Var(3)))
);
}
#[test]
fn context_methods() {
let ctx = Context::new(&["a", "b", "c"]);
let empty_ctx = Context::empty();
assert_eq!(ctx.len(), 3);
assert!(!ctx.is_empty());
assert_eq!(empty_ctx.len(), 0);
assert!(empty_ctx.is_empty());
assert!(ctx.contains("b"));
assert!(!ctx.contains("d"));
let names: Vec<&str> = ctx.iter().collect();
assert_eq!(names, vec!["a", "b", "c"]);
}
#[test]
fn context_resolve_free_var() {
let ctx = Context::new(&["a", "b", "c"]);
assert_eq!(ctx.resolve_free_var(1), Some("a"));
assert_eq!(ctx.resolve_free_var(3), Some("c"));
assert_eq!(ctx.resolve_free_var(0), None); assert_eq!(ctx.resolve_free_var(4), None); }
#[test]
fn abs_macro() {
assert_eq!(abs!(4, Var(1)), abs(abs(abs(abs(Var(1))))));
assert_eq!(abs!(2, app(Var(1), Var(2))), abs(abs(app(Var(1), Var(2)))));
}
#[test]
fn open_term_display() {
assert_eq!(abs(Var(2)).to_string(), lam("λa.b"));
assert_eq!(abs(Var(3)).to_string(), lam("λa.c"));
assert_eq!(abs!(2, Var(3)).to_string(), lam("λa.λb.c"));
assert_eq!(abs!(2, Var(4)).to_string(), lam("λa.λb.d"));
assert_eq!(
app!(
Var(3),
Var(4),
abs(app(Var(4), Var(5))),
abs!(2, app(Var(5), Var(6)))
)
.to_string(),
lam("e f (λa.e f) (λa.λb.e f)")
);
assert_eq!(
app!(
abs!(2, app(Var(3), Var(4))),
Var(1),
Var(2),
abs(app(Var(2), Var(3)))
)
.to_string(),
lam("(λa.λb.c d) c d (λa.c d)")
);
assert_eq!(
app(abs(Var(1)), app(abs(app(Var(10), Var(1))), Var(10))).to_string(),
lam("(λa.a) ((λa.j a) k)")
);
assert_eq!(
abs!(
27,
app!(Var(28), Var(29), Var(30), Var(50), Var(702), Var(703))
)
.to_string(),
lam(
"λa.λb.λc.λd.λe.λf.λg.λh.λi.λj.λk.λl.λm.λn.λo.λp.λq.λr.λs.λt.λu.λv.λw.λx.λy.λz.λaa.ab ac ad ax zz aaa"
)
);
assert_eq!(
abs!(3, app!(Var(2), Var(3), Var(4))).to_string(),
lam("λa.λb.λc.b a d")
);
assert_eq!(Var(26).to_string(), "z");
assert_eq!(Var(27).to_string(), "aa");
}
#[test]
fn display_modes() {
let zero = abs!(2, Var(1));
let succ = abs!(3, app(Var(2), app!(Var(3), Var(2), Var(1))));
let pred = abs!(
3,
app!(
Var(3),
abs!(2, app(Var(1), app(Var(2), Var(4)))),
abs(Var(2)),
abs(Var(1))
)
);
assert_eq!(zero.to_string(), lam("λa.λb.b"));
assert_eq!(succ.to_string(), lam("λa.λb.λc.b (a b c)"));
assert_eq!(
pred.to_string(),
lam("λa.λb.λc.a (λd.λe.e (d b)) (λd.c) (λd.d)")
);
assert_eq!(format!("{:?}", zero), lam("λλ1"));
assert_eq!(format!("{:?}", succ), lam("λλλ2(321)"));
assert_eq!(format!("{:?}", pred), lam("λλλ3(λλ1(24))(λ2)(λ1)"));
}
#[test]
fn term_display_with_context() {
let ctx = Context::new(&["x", "y"]);
let term1 = app(Var(1), Var(2));
assert_eq!(term1.with_context(&ctx).to_string(), "x y");
let term2 = abs(app(Var(1), Var(3)));
assert_eq!(term2.with_context(&ctx).to_string(), lam("λa.a y"));
let term3 = abs(Var(2));
assert_eq!(term3.with_context(&ctx).to_string(), lam("λa.x"));
}
#[test]
fn term_display_with_clashing_context() {
let ctx = Context::new(&["a", "c"]);
let term1 = app(Var(1), Var(2));
assert_eq!(term1.with_context(&ctx).to_string(), "a c");
let term2 = abs(app(Var(1), Var(3)));
assert_eq!(term2.with_context(&ctx).to_string(), lam("λb.b c"));
let term3 = abs(Var(2));
assert_eq!(term3.with_context(&ctx).to_string(), lam("λb.a"));
}
#[test]
fn term_display_without_context() {
let term1 = app(Var(1), Var(2));
assert_eq!(term1.to_string(), "a b");
assert_eq!(
term1.with_context(&Context::empty()).to_string(),
"<unknown1> <unknown2>"
);
let term2 = abs(app(Var(1), Var(3)));
assert_eq!(term2.to_string(), lam("λa.a c"));
assert_eq!(
term2.with_context(&Context::empty()).to_string(),
lam("λa.a <unknown2>")
);
let term3 = abs(Var(2));
assert_eq!(term3.to_string(), lam("λa.b"));
assert_eq!(
term3.with_context(&Context::empty()).to_string(),
lam("λa.<unknown1>")
);
}
#[test]
fn is_supercombinator() {
assert!(abs(Var(1)).is_supercombinator());
assert!(app(abs(Var(1)), abs(Var(1))).is_supercombinator());
assert!(abs!(10, Var(10)).is_supercombinator());
assert!(abs!(10, app(Var(10), Var(10))).is_supercombinator());
assert!(!Var(0).is_supercombinator());
assert!(!Var(1).is_supercombinator());
assert!(!abs(Var(2)).is_supercombinator());
assert!(!app(abs(Var(1)), Var(1)).is_supercombinator());
assert!(!abs!(10, Var(11)).is_supercombinator());
assert!(!abs!(10, app(Var(10), Var(11))).is_supercombinator());
}
#[test]
fn max_depth() {
assert_eq!(Var(1).max_depth(), 0);
assert_eq!(abs(Var(1)).max_depth(), 1);
assert_eq!(abs!(10, Var(5)).max_depth(), 10);
assert_eq!(
app!(abs!(5, Var(2)), abs!(9, Var(4)), abs!(7, Var(6))).max_depth(),
9
);
}
#[test]
fn is_isomorphic_to() {
assert!(abs(Var(1)).is_isomorphic_to(&abs(Var(1))));
assert!(!abs(Var(1)).is_isomorphic_to(&abs(Var(2))));
assert!(!app(abs(Var(1)), Var(1)).is_isomorphic_to(&app(abs(Var(1)), Var(2))));
assert!(app(abs(Var(1)), Var(1)).is_isomorphic_to(&app(abs(Var(1)), Var(1))));
assert!(!app(abs(Var(1)), Var(1)).is_isomorphic_to(&app(Var(2), abs(Var(1)))));
}
#[test]
fn has_free_variables() {
assert!(!(abs(Var(1)).has_free_variables()));
assert!(abs(Var(2)).has_free_variables());
assert!(app(abs(Var(2)), Var(1)).has_free_variables());
assert!(app(abs(Var(2)), abs(Var(1))).has_free_variables());
assert!(app(abs(Var(1)), abs(Var(2))).has_free_variables());
assert!(!app(abs(Var(1)), abs(Var(1))).has_free_variables());
assert!(
!(abs(app(
abs(app(Var(2), app(Var(1), Var(1)))),
abs(app(Var(2), app(Var(1), Var(1)))),
)))
.has_free_variables()
);
assert!((Var(0)).has_free_variables());
}
}