128 lines
3.5 KiB
Rust
128 lines
3.5 KiB
Rust
//! Compiles the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus),
|
||
//! represented by the [`LC`] type,
|
||
//! to a tree of [Iota (ι) combinators](https://en.wikipedia.org/wiki/Iota_and_Jot),
|
||
//! represented by the [`Iota`] type.
|
||
|
||
#[cfg(test)]
|
||
mod test;
|
||
|
||
use core::fmt;
|
||
use std::rc::Rc;
|
||
use thiserror::Error;
|
||
|
||
#[derive(Debug, PartialEq, Eq)]
|
||
pub enum Iota {
|
||
App(Rc<Iota>, Rc<Iota>),
|
||
Iota,
|
||
}
|
||
|
||
#[derive(Debug, Error, PartialEq, Eq)]
|
||
pub enum IotaCompileError {
|
||
#[error("LC term has a free variable")]
|
||
FreeVar,
|
||
}
|
||
|
||
impl TryFrom<LC> for Iota {
|
||
type Error = IotaCompileError;
|
||
|
||
fn try_from(value: LC) -> Result<Self, Self::Error> {
|
||
todo!()
|
||
}
|
||
}
|
||
|
||
/// A representation of the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus), with
|
||
/// [de Bruijn indices](https://en.wikipedia.org/wiki/De_Bruijn_index).
|
||
#[derive(Debug, PartialEq, Eq)]
|
||
pub enum LC {
|
||
/// A lambda abstraction; binds a new variable.
|
||
Abs(Rc<LC>),
|
||
/// An application of a function to an argument.
|
||
App(Rc<LC>, Rc<LC>),
|
||
/// A variable represented by a
|
||
/// [de Bruijn index](https://en.wikipedia.org/wiki/De_Bruijn_index).
|
||
///
|
||
/// Zero refers to the closest abstraction, and each successive number refers to a
|
||
/// nested abstraction, as shown in this diagram:
|
||
/// ```text
|
||
/// ┌─────┐
|
||
/// │ ┌─┐ │
|
||
/// λ λ λ 0 1 2
|
||
/// └─────────┘
|
||
/// ```
|
||
Var(usize),
|
||
}
|
||
|
||
impl LC {
|
||
/// Applies this term to the given term.
|
||
///
|
||
/// Often more ergonomic than [`LC::App`].
|
||
/// # examples
|
||
/// ```
|
||
/// # use lc_to_iota::LC::{Abs, App, Var};
|
||
/// assert_eq!(Var(0).app(1), App(Var(0).into(), Var(1).into()));
|
||
/// ```
|
||
#[must_use]
|
||
pub fn app(self, x: impl Into<Self>) -> Self {
|
||
Self::App(self.into(), x.into().into())
|
||
}
|
||
|
||
/// Adds an abstration around this term.
|
||
///
|
||
/// Often more ergonomic than [`LC::Abs`], but is somewhat backwards.
|
||
/// # examples
|
||
/// ```
|
||
/// # use lc_to_iota::LC::{Abs, App, Var};
|
||
/// assert_eq!(Var(0).abs(), Abs(Var(0).into()));
|
||
/// ```
|
||
#[must_use]
|
||
pub fn abs(self) -> Self {
|
||
Self::Abs(self.into())
|
||
}
|
||
|
||
/// Returns whether any variables are
|
||
/// [free](https://en.wikipedia.org/wiki/Free_variables_and_bound_variables) in self;
|
||
/// that is, whether any variables occur that are not bound by an abstraction.
|
||
///
|
||
/// # Examples
|
||
/// ```
|
||
/// # use lc_to_iota::LC::{Abs, App, Var};
|
||
/// // x
|
||
/// let x = Var(0);
|
||
/// assert!(x.has_free());
|
||
/// // λx.x
|
||
/// assert!(!x.abs().has_free());
|
||
/// // λfx.f x
|
||
/// let apply = Var(1).app(0).abs().abs();
|
||
/// assert!(!apply.has_free());
|
||
/// ```
|
||
#[must_use]
|
||
pub fn has_free(&self) -> bool {
|
||
self.has_free_past(0)
|
||
}
|
||
|
||
fn has_free_past(&self, bound: usize) -> bool {
|
||
match self {
|
||
LC::Abs(x) => x.has_free_past(bound + 1),
|
||
LC::App(f, x) => f.has_free_past(bound) || x.has_free_past(bound),
|
||
// if nothing is bound (bound = 0), x is always free
|
||
LC::Var(x) => *x >= bound,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<usize> for LC {
|
||
fn from(n: usize) -> Self {
|
||
Self::Var(n)
|
||
}
|
||
}
|
||
|
||
impl fmt::Display for LC {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
match self {
|
||
LC::Abs(x) => write!(f, "(λ {x})"),
|
||
LC::App(g, x) => write!(f, "({g} {x})"),
|
||
LC::Var(n) => write!(f, "{n}"),
|
||
}
|
||
}
|
||
}
|