It's a type checker for a simple dependently typed language with an interval type, based on [1]. The type checking procedure is very similar to that of [4]: it's bidirectional and NbE-based, so we have raw terms and values, but since we're in a dependently typed setting, there are also typed terms indexed by values that represent types. E.g. typed Π-types are
data _⊢_ {n} (Γ : Con n) : Value n -> Set where
πᵗ : (σₜ : Γ ⊢ typeᵛ) -> Γ ▻ eval σₜ ⊢ typeᵛ -> Γ ⊢ typeᵛ
...
That example demonstrates that evaluation is defined on typed terms, which is a great source of confidence that type checking terminates, because there is no evaluation defined on raw terms.
Type checking returns either a (rudimentary) error or a typed term, hence the type system is Church-like and all typing rules are just constructors of _⊢_. Unlike in [4] binders in Value have Kripke semantics, e.g.
data Value n where
piᵛ : Value n -> Kripke n -> Value n
lamᵛ : Kripke n -> Value n
...
where Kripke n = ∀ {m} -> n ⊑ m -> Value m -> Value m.
The HoTT part consists of the interval type int, left and right points l and r, the 1- operator spelled as inv, the type of paths path, path abstraction δ (all variables are represented as de Bruijn indices), path application _#_ (Agda doesn't allow to use _@_) and coe. Everything is the same as in [1], except that path is the homogeneous Path from [2] and not PathOver (see [3]) and the order of arguments to coe is changed: it's coe A j x instead of coe A x j. No univalence and data types for now. All computational rules from [1] hold.
There is one another constructor of Term: pure. It's used to store typed terms in raw terms to prevent retypechecking.
To define terms we use higher-order syntax in the style of [5].
Functional extensionality is
funExt : Term⁺
funExt = (pi type λ A
→ pi type λ B
→ pi (A ⇒ B) λ f
→ pi (A ⇒ B) λ g
→ (pi A λ x → path B (f · x) (g · x))
⇒ path (A ⇒ B) f g)
∋ lam 5 λ A B f g p → dim λ i → lam 1 λ x → p · x # i
_∋_ returns either a String with an error or a typed term wrapped in pure.
The definition of J is a simplified version of the definition in [1]: psqueeze returns path A x (p # (squeeze · i · r)) rather than path A x (p # i) and we don't need squeeze · i · r ~> i to hold (which causes all the trouble) to define J.
idp : Term⁺
idp = (pi type λ A → pi A λ x → path A x x)
∋ lam 2 λ A x → dim λ i → x
li : Term⁺
li = (pi int λ i → path int l i)
∋ lam 1 λ i → coe (lam 1 λ i → path int l i) i (idp · int · l)
squeeze : Term⁺
squeeze = (int ⇒ int ⇒ int) ∋ lam 2 λ i j → li · j # i
psqueeze : Term⁺
psqueeze = (pi type λ A
→ pi A λ x
→ pi A λ y
→ pi int λ i
→ pi (path A x y) λ p
→ path A x (p # (squeeze · i · r)))
∋ lam 5 λ A x y i p → dim λ j → p # (squeeze · i · j)
J : Term⁺
J = (pi type λ A
→ pi A λ x
→ pi A λ y
→ pi (pi A λ y → path A x y ⇒ type) λ B
→ B · x · (idp · A · x)
⇒ pi (path A x y) λ p
→ B · y · p)
∋ lam 6 λ A x y B z p →
coe (lam 1 λ i → B · (p # (squeeze · i · r)) · (psqueeze · A · x · y · i · p)) r z
And J computes:
J-computes : Term⁺
J-computes = (pi type λ A
→ pi A λ x
→ pi (pi A λ y → path A x y ⇒ type) λ B
→ pi (B · x · (idp · A · x) ⇒ type) λ C
→ pi (B · x · (idp · A · x)) λ z
→ C · (J · A · x · x · B · z · (idp · A · x))
⇒ C · z)
∋ lam 6 λ A x B C z w → w
[1] "Homotopy Type Theory with an interval type", Valery Isaev.
[2] "Cubical Type Theory: a constructive interpretation of the univalence axiom", Cyril Cohen, Thierry Coquand, Simon Huber, and Anders Mörtberg.
[3] "A Cubical Approach to Synthetic Homotopy Theory", Daniel R. Licata, Guillaume Brunerie.
[4] "Simply Easy! An Implementation of a Dependently Typed Lambda Calculus", Andres Löh, Conor McBride, Wouter Swierstra.
[5] "A little hack to make de Bruijn syntax more readable", Conor McBride.