use std::fmt;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Literal {
Int(i64),
Float(f64),
Text(String),
Duration(i64),
Date(i32),
Moment(i64),
BigInt(
#[cfg_attr(feature = "serde", serde(with = "bigint_dec"))] logicaffeine_base::BigInt,
),
Nat(#[cfg_attr(feature = "serde", serde(with = "bigint_dec"))] logicaffeine_base::BigInt),
}
pub fn int_lit(n: logicaffeine_base::BigInt) -> Literal {
match n.to_i64() {
Some(x) => Literal::Int(x),
None => Literal::BigInt(n),
}
}
pub fn lit_bigint(lit: &Literal) -> Option<logicaffeine_base::BigInt> {
match lit {
Literal::Int(x) => Some(logicaffeine_base::BigInt::from_i64(*x)),
Literal::BigInt(n) => Some(n.clone()),
_ => None,
}
}
#[cfg(feature = "serde")]
mod bigint_dec {
use logicaffeine_base::BigInt;
pub fn serialize<S: serde::Serializer>(v: &BigInt, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&v.to_string())
}
pub fn deserialize<'de, D: serde::Deserializer<'de>>(d: D) -> Result<BigInt, D::Error> {
let s = <String as serde::Deserialize>::deserialize(d)?;
BigInt::parse_decimal(&s).ok_or_else(|| serde::de::Error::custom("invalid BigInt literal"))
}
}
impl Eq for Literal {}
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Literal::Int(n) => write!(f, "{}", n),
Literal::Float(x) => write!(f, "{}", x),
Literal::Text(s) => write!(f, "{:?}", s),
Literal::Duration(nanos) => {
let abs = nanos.unsigned_abs();
let sign = if *nanos < 0 { "-" } else { "" };
if abs >= 3_600_000_000_000 {
write!(f, "{}{}h", sign, abs / 3_600_000_000_000)
} else if abs >= 60_000_000_000 {
write!(f, "{}{}min", sign, abs / 60_000_000_000)
} else if abs >= 1_000_000_000 {
write!(f, "{}{}s", sign, abs / 1_000_000_000)
} else if abs >= 1_000_000 {
write!(f, "{}{}ms", sign, abs / 1_000_000)
} else if abs >= 1_000 {
write!(f, "{}{}μs", sign, abs / 1_000)
} else {
write!(f, "{}{}ns", sign, abs)
}
}
Literal::Date(days) => {
let days = *days as i64;
let (year, month, day) = days_to_ymd(days);
write!(f, "{:04}-{:02}-{:02}", year, month, day)
}
Literal::Moment(nanos) => {
let secs = nanos / 1_000_000_000;
let days = secs / 86400;
let time_secs = secs % 86400;
let hours = time_secs / 3600;
let mins = (time_secs % 3600) / 60;
let secs_rem = time_secs % 60;
let (year, month, day) = days_to_ymd(days);
write!(f, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
year, month, day, hours, mins, secs_rem)
}
Literal::BigInt(n) => write!(f, "{}", n),
Literal::Nat(n) => write!(f, "{}", n),
}
}
}
fn days_to_ymd(days: i64) -> (i64, u8, u8) {
let z = days + 719468;
let era = if z >= 0 { z / 146097 } else { (z - 146096) / 146097 };
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if m <= 2 { y + 1 } else { y };
(year, m as u8, d as u8)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Universe {
SProp,
Prop,
Type(u32),
Var(String),
Succ(Box<Universe>),
Max(Box<Universe>, Box<Universe>),
IMax(Box<Universe>, Box<Universe>),
}
impl Universe {
pub fn succ(&self) -> Universe {
match self {
Universe::SProp | Universe::Prop => Universe::Type(1),
Universe::Type(n) => Universe::Type(n + 1),
other => Universe::Succ(Box::new(other.clone())),
}
}
pub fn max(&self, other: &Universe) -> Universe {
match (self, other) {
(Universe::SProp, u) | (u, Universe::SProp) => u.clone(),
(Universe::Prop, u) | (u, Universe::Prop) => u.clone(),
(Universe::Type(a), Universe::Type(b)) => Universe::Type((*a).max(*b)),
_ => Universe::Max(Box::new(self.clone()), Box::new(other.clone())),
}
}
pub fn imax(&self, other: &Universe) -> Universe {
match other {
Universe::SProp => Universe::SProp,
Universe::Prop => Universe::Prop,
Universe::Type(_) | Universe::Succ(_) => self.max(other),
_ => {
if self.equiv(other) {
self.clone()
} else {
Universe::IMax(Box::new(self.clone()), Box::new(other.clone()))
}
}
}
}
pub fn is_subtype_of(&self, other: &Universe) -> bool {
level_leq(self, other)
}
pub fn equiv(&self, other: &Universe) -> bool {
level_leq(self, other) && level_leq(other, self)
}
pub fn substitute(&self, subst: &std::collections::HashMap<String, Universe>) -> Universe {
match self {
Universe::SProp | Universe::Prop | Universe::Type(_) => self.clone(),
Universe::Var(v) => subst.get(v).cloned().unwrap_or_else(|| self.clone()),
Universe::Succ(l) => l.substitute(subst).succ(),
Universe::Max(a, b) => a.substitute(subst).max(&b.substitute(subst)),
Universe::IMax(a, b) => a.substitute(subst).imax(&b.substitute(subst)),
}
}
}
#[derive(Clone, Debug)]
enum LNat {
Const(u64),
Var(String),
Succ(Box<LNat>),
Max(Box<LNat>, Box<LNat>),
IMax(Box<LNat>, Box<LNat>),
}
fn to_lnat(u: &Universe) -> LNat {
match u {
Universe::SProp => LNat::Const(0),
Universe::Prop => LNat::Const(0),
Universe::Type(n) => LNat::Const(*n as u64 + 1),
Universe::Var(v) => LNat::Var(v.clone()),
Universe::Succ(l) => LNat::Succ(Box::new(to_lnat(l))),
Universe::Max(a, b) => LNat::Max(Box::new(to_lnat(a)), Box::new(to_lnat(b))),
Universe::IMax(a, b) => LNat::IMax(Box::new(to_lnat(a)), Box::new(to_lnat(b))),
}
}
fn simp_lnat(t: &LNat) -> LNat {
match t {
LNat::Const(_) | LNat::Var(_) => t.clone(),
LNat::Succ(l) => LNat::Succ(Box::new(simp_lnat(l))),
LNat::Max(a, b) => LNat::Max(Box::new(simp_lnat(a)), Box::new(simp_lnat(b))),
LNat::IMax(a, b) => {
let a = simp_lnat(a);
let b = simp_lnat(b);
match &b {
LNat::Const(0) => LNat::Const(0),
LNat::Const(_) | LNat::Succ(_) => simp_lnat(&LNat::Max(Box::new(a), Box::new(b))),
LNat::Max(b1, b2) => simp_lnat(&LNat::Max(
Box::new(LNat::IMax(Box::new(a.clone()), b1.clone())),
Box::new(LNat::IMax(Box::new(a), b2.clone())),
)),
LNat::IMax(b1, b2) => simp_lnat(&LNat::IMax(
Box::new(LNat::Max(Box::new(a), b1.clone())),
b2.clone(),
)),
LNat::Var(_) => LNat::IMax(Box::new(a), Box::new(b)),
}
}
}
}
fn imax_pivot(t: &LNat) -> Option<String> {
match t {
LNat::Const(_) | LNat::Var(_) => None,
LNat::Succ(l) => imax_pivot(l),
LNat::Max(a, b) => imax_pivot(a).or_else(|| imax_pivot(b)),
LNat::IMax(a, b) => match &**b {
LNat::Var(v) => Some(v.clone()),
_ => imax_pivot(a).or_else(|| imax_pivot(b)),
},
}
}
fn subst_lnat(t: &LNat, v: &str, repl: &LNat) -> LNat {
match t {
LNat::Const(_) => t.clone(),
LNat::Var(x) => {
if x == v {
repl.clone()
} else {
t.clone()
}
}
LNat::Succ(l) => LNat::Succ(Box::new(subst_lnat(l, v, repl))),
LNat::Max(a, b) => {
LNat::Max(Box::new(subst_lnat(a, v, repl)), Box::new(subst_lnat(b, v, repl)))
}
LNat::IMax(a, b) => {
LNat::IMax(Box::new(subst_lnat(a, v, repl)), Box::new(subst_lnat(b, v, repl)))
}
}
}
fn lnat_atoms(t: &LNat, off: u64, out: &mut Vec<(Option<String>, u64)>) {
match t {
LNat::Const(c) => out.push((None, c + off)),
LNat::Var(v) => out.push((Some(v.clone()), off)),
LNat::Succ(l) => lnat_atoms(l, off + 1, out),
LNat::Max(a, b) => {
lnat_atoms(a, off, out);
lnat_atoms(b, off, out);
}
LNat::IMax(..) => unreachable!("lnat_atoms on an unresolved imax"),
}
}
fn leq_linear(a: &LNat, b: &LNat) -> bool {
let mut atoms_a = Vec::new();
lnat_atoms(a, 0, &mut atoms_a);
let mut atoms_b = Vec::new();
lnat_atoms(b, 0, &mut atoms_b);
let b_min = atoms_b.iter().map(|(_, off)| *off).max().unwrap_or(0);
atoms_a.iter().all(|(v, off)| match v {
None => *off <= b_min,
Some(name) => atoms_b
.iter()
.any(|(bv, boff)| bv.as_deref() == Some(name.as_str()) && *boff >= *off),
})
}
fn level_leq(a: &Universe, b: &Universe) -> bool {
if matches!(a, Universe::SProp) {
return true;
}
if matches!(b, Universe::SProp) {
return false;
}
lnat_leq(&simp_lnat(&to_lnat(a)), &simp_lnat(&to_lnat(b)))
}
fn lnat_leq(a: &LNat, b: &LNat) -> bool {
match imax_pivot(a).or_else(|| imax_pivot(b)) {
Some(v) => {
let zero = LNat::Const(0);
let a0 = simp_lnat(&subst_lnat(a, &v, &zero));
let b0 = simp_lnat(&subst_lnat(b, &v, &zero));
let vpos = LNat::Succ(Box::new(LNat::Var(format!("{v}✦"))));
let ap = simp_lnat(&subst_lnat(a, &v, &vpos));
let bp = simp_lnat(&subst_lnat(b, &v, &vpos));
lnat_leq(&a0, &b0) && lnat_leq(&ap, &bp)
}
None => leq_linear(a, b),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Term {
Sort(Universe),
Var(String),
Global(String),
Const { name: String, levels: Vec<Universe> },
Pi {
param: String,
param_type: Box<Term>,
body_type: Box<Term>,
},
Lambda {
param: String,
param_type: Box<Term>,
body: Box<Term>,
},
App(Box<Term>, Box<Term>),
Match {
discriminant: Box<Term>,
motive: Box<Term>,
cases: Vec<Term>,
},
Fix {
name: String,
body: Box<Term>,
},
MutualFix {
defs: Vec<(String, Term)>,
index: usize,
},
Let {
name: String,
ty: Box<Term>,
value: Box<Term>,
body: Box<Term>,
},
Lit(Literal),
Hole,
}
impl fmt::Display for Universe {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Universe::SProp => write!(f, "SProp"),
Universe::Prop => write!(f, "Prop"),
Universe::Type(n) => write!(f, "Type{}", n),
Universe::Var(v) => write!(f, "{}", v),
Universe::Succ(l) => write!(f, "({}+1)", l),
Universe::Max(a, b) => write!(f, "max({}, {})", a, b),
Universe::IMax(a, b) => write!(f, "imax({}, {})", a, b),
}
}
}
impl fmt::Display for Term {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Term::Sort(u) => write!(f, "{}", u),
Term::Var(name) => write!(f, "{}", name),
Term::Global(name) => write!(f, "{}", name),
Term::Const { name, levels } => {
write!(f, "{}.{{", name)?;
for (i, l) in levels.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", l)?;
}
write!(f, "}}")
}
Term::Pi {
param,
param_type,
body_type,
} => {
if param == "_" {
write!(f, "{} -> {}", param_type, body_type)
} else {
write!(f, "Π({}:{}). {}", param, param_type, body_type)
}
}
Term::Lambda {
param,
param_type,
body,
} => {
write!(f, "λ({}:{}). {}", param, param_type, body)
}
Term::App(func, arg) => {
let arg_needs_inner_parens =
matches!(arg.as_ref(), Term::Pi { param, .. } if param == "_");
if arg_needs_inner_parens {
write!(f, "({} ({}))", func, arg)
} else {
write!(f, "({} {})", func, arg)
}
}
Term::Match {
discriminant,
motive,
cases,
} => {
write!(f, "match {} return {} with ", discriminant, motive)?;
for (i, case) in cases.iter().enumerate() {
if i > 0 {
write!(f, " | ")?;
}
write!(f, "{}", case)?;
}
Ok(())
}
Term::Fix { name, body } => {
write!(f, "fix {}. {}", name, body)
}
Term::MutualFix { defs, index } => {
write!(f, "mutualfix {{ ")?;
for (i, (name, body)) in defs.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{} := {}", name, body)?;
}
write!(f, " }}.{}", index)
}
Term::Let { name, ty, value, body } => {
write!(f, "let {}:{} := {} in {}", name, ty, value, body)
}
Term::Lit(lit) => {
write!(f, "{}", lit)
}
Term::Hole => {
write!(f, "_")
}
}
}
}
pub fn instantiate_universes(
term: &Term,
subst: &std::collections::HashMap<String, Universe>,
) -> Term {
match term {
Term::Sort(u) => Term::Sort(u.substitute(subst)),
Term::Const { name, levels } => Term::Const {
name: name.clone(),
levels: levels.iter().map(|l| l.substitute(subst)).collect(),
},
Term::Var(_) | Term::Global(_) | Term::Lit(_) | Term::Hole => term.clone(),
Term::Pi { param, param_type, body_type } => Term::Pi {
param: param.clone(),
param_type: Box::new(instantiate_universes(param_type, subst)),
body_type: Box::new(instantiate_universes(body_type, subst)),
},
Term::Lambda { param, param_type, body } => Term::Lambda {
param: param.clone(),
param_type: Box::new(instantiate_universes(param_type, subst)),
body: Box::new(instantiate_universes(body, subst)),
},
Term::App(f, a) => Term::App(
Box::new(instantiate_universes(f, subst)),
Box::new(instantiate_universes(a, subst)),
),
Term::Match { discriminant, motive, cases } => Term::Match {
discriminant: Box::new(instantiate_universes(discriminant, subst)),
motive: Box::new(instantiate_universes(motive, subst)),
cases: cases.iter().map(|c| instantiate_universes(c, subst)).collect(),
},
Term::Fix { name, body } => Term::Fix {
name: name.clone(),
body: Box::new(instantiate_universes(body, subst)),
},
Term::MutualFix { defs, index } => Term::MutualFix {
defs: defs
.iter()
.map(|(n, b)| (n.clone(), instantiate_universes(b, subst)))
.collect(),
index: *index,
},
Term::Let { name, ty, value, body } => Term::Let {
name: name.clone(),
ty: Box::new(instantiate_universes(ty, subst)),
value: Box::new(instantiate_universes(value, subst)),
body: Box::new(instantiate_universes(body, subst)),
},
}
}