logicaffeine_kernel/term.rs
1//! Unified term representation for the Calculus of Constructions.
2//!
3//! In CoC, there is no distinction between terms and types.
4//! Everything is a Term in an infinite hierarchy of universes.
5
6use std::fmt;
7
8/// Primitive literal values.
9///
10/// These are opaque values that compute via hardware ALU, not recursion.
11#[derive(Debug, Clone, PartialEq)]
12pub enum Literal {
13 /// 64-bit signed integer
14 Int(i64),
15 /// 64-bit floating point
16 Float(f64),
17 /// UTF-8 string
18 Text(String),
19 /// Duration in nanoseconds (signed for negative offsets like "5 min early")
20 Duration(i64),
21 /// Calendar date as days since Unix epoch (i32 gives ±5.8 million year range)
22 Date(i32),
23 /// Instant in time as nanoseconds since Unix epoch (UTC)
24 Moment(i64),
25}
26
27impl Eq for Literal {}
28
29impl fmt::Display for Literal {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Literal::Int(n) => write!(f, "{}", n),
33 Literal::Float(x) => write!(f, "{}", x),
34 Literal::Text(s) => write!(f, "{:?}", s),
35 Literal::Duration(nanos) => {
36 // Display in most human-readable unit
37 let abs = nanos.unsigned_abs();
38 let sign = if *nanos < 0 { "-" } else { "" };
39 if abs >= 3_600_000_000_000 {
40 write!(f, "{}{}h", sign, abs / 3_600_000_000_000)
41 } else if abs >= 60_000_000_000 {
42 write!(f, "{}{}min", sign, abs / 60_000_000_000)
43 } else if abs >= 1_000_000_000 {
44 write!(f, "{}{}s", sign, abs / 1_000_000_000)
45 } else if abs >= 1_000_000 {
46 write!(f, "{}{}ms", sign, abs / 1_000_000)
47 } else if abs >= 1_000 {
48 write!(f, "{}{}μs", sign, abs / 1_000)
49 } else {
50 write!(f, "{}{}ns", sign, abs)
51 }
52 }
53 Literal::Date(days) => {
54 // Convert days since epoch to ISO-8601 date
55 // Unix epoch is 1970-01-01 (day 0)
56 // We use a simple algorithm for display purposes
57 let days = *days as i64;
58 let (year, month, day) = days_to_ymd(days);
59 write!(f, "{:04}-{:02}-{:02}", year, month, day)
60 }
61 Literal::Moment(nanos) => {
62 // Convert to ISO-8601 datetime
63 let secs = nanos / 1_000_000_000;
64 let days = secs / 86400;
65 let time_secs = secs % 86400;
66 let hours = time_secs / 3600;
67 let mins = (time_secs % 3600) / 60;
68 let secs_rem = time_secs % 60;
69 let (year, month, day) = days_to_ymd(days);
70 write!(f, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
71 year, month, day, hours, mins, secs_rem)
72 }
73 }
74 }
75}
76
77/// Convert days since Unix epoch to (year, month, day).
78fn days_to_ymd(days: i64) -> (i64, u8, u8) {
79 // Civil date from days since epoch using the algorithm from Howard Hinnant
80 // https://howardhinnant.github.io/date_algorithms.html
81 let z = days + 719468;
82 let era = if z >= 0 { z / 146097 } else { (z - 146096) / 146097 };
83 let doe = (z - era * 146097) as u32;
84 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
85 let y = yoe as i64 + era * 400;
86 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
87 let mp = (5 * doy + 2) / 153;
88 let d = doy - (153 * mp + 2) / 5 + 1;
89 let m = if mp < 10 { mp + 3 } else { mp - 9 };
90 let year = if m <= 2 { y + 1 } else { y };
91 (year, m as u8, d as u8)
92}
93
94/// Universe levels in the type hierarchy.
95///
96/// The hierarchy is: Prop : Type 1 : Type 2 : Type 3 : ...
97///
98/// - `Prop` is the universe of propositions (proof-irrelevant in full CIC)
99/// - `Type(n)` is the universe of types at level n
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum Universe {
102 /// Prop - the universe of propositions
103 Prop,
104 /// Type n - the universe of types at level n
105 Type(u32),
106}
107
108impl Universe {
109 /// Get the successor universe: Type n → Type (n+1)
110 pub fn succ(&self) -> Universe {
111 match self {
112 Universe::Prop => Universe::Type(1),
113 Universe::Type(n) => Universe::Type(n + 1),
114 }
115 }
116
117 /// Get the maximum of two universes (for Pi type formation)
118 pub fn max(&self, other: &Universe) -> Universe {
119 match (self, other) {
120 (Universe::Prop, u) | (u, Universe::Prop) => u.clone(),
121 (Universe::Type(a), Universe::Type(b)) => Universe::Type((*a).max(*b)),
122 }
123 }
124
125 /// Check if this universe is a subtype of another (cumulativity).
126 ///
127 /// Subtyping rules:
128 /// - Prop ≤ Type(i) for all i
129 /// - Type(i) ≤ Type(j) if i ≤ j
130 /// - Type(i) is NOT ≤ Prop
131 pub fn is_subtype_of(&self, other: &Universe) -> bool {
132 match (self, other) {
133 // Prop ≤ anything (Prop ≤ Prop, Prop ≤ Type(i))
134 (Universe::Prop, _) => true,
135 // Type(i) ≤ Type(j) if i ≤ j
136 (Universe::Type(i), Universe::Type(j)) => i <= j,
137 // Type(i) is NOT ≤ Prop
138 (Universe::Type(_), Universe::Prop) => false,
139 }
140 }
141}
142
143/// Unified term representation.
144///
145/// Every expression in CoC is a Term:
146/// - `Sort(u)` - universes (Type 0, Type 1, Prop)
147/// - `Var(x)` - variables
148/// - `Pi` - dependent function types: Π(x:A). B
149/// - `Lambda` - functions: λ(x:A). t
150/// - `App` - application: f x
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum Term {
153 /// Universe: Type n or Prop
154 Sort(Universe),
155
156 /// Local variable reference (bound by λ or Π)
157 Var(String),
158
159 /// Global definition (inductive type or constructor)
160 Global(String),
161
162 /// Dependent function type: Π(x:A). B
163 ///
164 /// When B doesn't mention x, this is just A → B.
165 /// When B mentions x, this is a dependent type.
166 Pi {
167 param: String,
168 param_type: Box<Term>,
169 body_type: Box<Term>,
170 },
171
172 /// Lambda abstraction: λ(x:A). t
173 Lambda {
174 param: String,
175 param_type: Box<Term>,
176 body: Box<Term>,
177 },
178
179 /// Application: f x
180 App(Box<Term>, Box<Term>),
181
182 /// Pattern matching on inductive types.
183 ///
184 /// `match discriminant return motive with cases`
185 ///
186 /// - discriminant: the term being matched (must have inductive type)
187 /// - motive: λx:I. T — describes the return type
188 /// - cases: one case per constructor, in definition order
189 Match {
190 discriminant: Box<Term>,
191 motive: Box<Term>,
192 cases: Vec<Term>,
193 },
194
195 /// Fixpoint (recursive function).
196 ///
197 /// `fix name. body` binds `name` to itself within `body`.
198 /// Used for recursive definitions like addition.
199 Fix {
200 /// Name for self-reference within the body
201 name: String,
202 /// The body of the fixpoint (typically a lambda)
203 body: Box<Term>,
204 },
205
206 /// Primitive literal value.
207 ///
208 /// Hardware-native values like i64, f64, String.
209 /// These compute via CPU ALU, not recursion.
210 Lit(Literal),
211
212 /// Hole (implicit argument).
213 ///
214 /// Represents an argument that should be inferred by the type checker.
215 /// Used in Literate syntax like `X equals Y` where the type of X/Y is implicit.
216 Hole,
217}
218
219impl fmt::Display for Universe {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 match self {
222 Universe::Prop => write!(f, "Prop"),
223 Universe::Type(n) => write!(f, "Type{}", n),
224 }
225 }
226}
227
228impl fmt::Display for Term {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 Term::Sort(u) => write!(f, "{}", u),
232 Term::Var(name) => write!(f, "{}", name),
233 Term::Global(name) => write!(f, "{}", name),
234 Term::Pi {
235 param,
236 param_type,
237 body_type,
238 } => {
239 // Use arrow notation for non-dependent functions (param = "_")
240 if param == "_" {
241 write!(f, "{} -> {}", param_type, body_type)
242 } else {
243 write!(f, "Π({}:{}). {}", param, param_type, body_type)
244 }
245 }
246 Term::Lambda {
247 param,
248 param_type,
249 body,
250 } => {
251 write!(f, "λ({}:{}). {}", param, param_type, body)
252 }
253 Term::App(func, arg) => {
254 // Arrow types (Pi with _) need inner parens when used as args
255 let arg_needs_inner_parens =
256 matches!(arg.as_ref(), Term::Pi { param, .. } if param == "_");
257
258 if arg_needs_inner_parens {
259 write!(f, "({} ({}))", func, arg)
260 } else {
261 write!(f, "({} {})", func, arg)
262 }
263 }
264 Term::Match {
265 discriminant,
266 motive,
267 cases,
268 } => {
269 write!(f, "match {} return {} with ", discriminant, motive)?;
270 for (i, case) in cases.iter().enumerate() {
271 if i > 0 {
272 write!(f, " | ")?;
273 }
274 write!(f, "{}", case)?;
275 }
276 Ok(())
277 }
278 Term::Fix { name, body } => {
279 write!(f, "fix {}. {}", name, body)
280 }
281 Term::Lit(lit) => {
282 write!(f, "{}", lit)
283 }
284 Term::Hole => {
285 write!(f, "_")
286 }
287 }
288 }
289}