lc-to-iota/src/lib.rs
2024-11-17 16:23:22 -05:00

128 lines
3.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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}"),
}
}
}