{-# OPTIONS --guardedness #-}

module A201602.STLCVarSupply where


module Stream where
  open import Data.Nat using (ℕ; zero; suc)
  open import Data.Product using (_,_; _×_)
  open import Data.Vec using (Vec; []; _∷_)

  record Stream (X : Set) : Set where
    coinductive
    field
      head : X
      tail : Stream X
  open Stream public

  take+ : {X : Set} → Stream X → X × Stream X
  take+ ∞ = head ∞ , tail ∞

  takeN+ : {X : Set} → (n : ℕ) → Stream X → Vec X n × Stream X
  takeN+ zero    ∞ = [] , ∞
  takeN+ (suc n) ∞ = let (x , ∞′) = take+ ∞
                         (xs , ∞″) = takeN+ n ∞′ in x ∷ xs , ∞″


module Var where
  open import Data.Nat using (ℕ)
  open import Data.Product using (_,_; proj₁; _×_)
  open import Data.String using (String)
  open import Data.Vec using (Vec; []; _∷_; zipWith)
  open Stream using (Stream)

  infixr 4 _,_

  VarSupply : Set
  VarSupply = Stream ℕ

  next : VarSupply → VarSupply
  next = Stream.tail

  record Var : Set where
    constructor _,_
    field
      name   : String
      serial : ℕ
  open Var public

  make+ : String → VarSupply → Var × VarSupply
  make+ n ∞ = let s , ∞′ = Stream.take+ ∞ in (n , s) , ∞′

  make2+ : String → String → VarSupply → (Var × Var) × VarSupply
  make2+ n₁ n₂ ∞ = let v₁ , ∞′ = make+ n₁ ∞
                       v₂ , ∞″ = make+ n₂ ∞′ in (v₁ , v₂) , ∞″

  make3+ : String → String → String → VarSupply → (Var × Var × Var) × VarSupply
  make3+ n₁ n₂ n₃ ∞ = let (v₁ , v₂) , ∞′ = make2+ n₁ n₂ ∞
                          v₃ , ∞″ = make+ n₃ ∞′ in (v₁ , v₂ , v₃) , ∞″

  make4+ : String → String → String → String → VarSupply → (Var × Var × Var × Var) × VarSupply
  make4+ n₁ n₂ n₃ n₄ ∞ = let (v₁ , v₂) , ∞′ = make2+ n₁ n₂ ∞
                             (v₃ , v₄) , ∞″ = make2+ n₃ n₄ ∞′ in (v₁ , v₂ , v₃ , v₄) , ∞″

  makeN+ : ∀{n} → Vec String n → VarSupply → Vec Var n × VarSupply
  makeN+ {n} ns ∞ = let ss , ∞′ = Stream.takeN+ n ∞ in zipWith _,_ ns ss , ∞′

  make : String → VarSupply → Var
  make n ∞ = proj₁ (make+ n ∞)

  make2 : String → String → VarSupply → Var × Var
  make2 n₁ n₂ ∞ = proj₁ (make2+ n₁ n₂ ∞)

  make3 : String → String → String → VarSupply → Var × Var × Var
  make3 n₁ n₂ n₃ ∞ = proj₁ (make3+ n₁ n₂ n₃ ∞)

  make4 : String → String → String → String → VarSupply → Var × Var × Var × Var
  make4 n₁ n₂ n₃ n₄ ∞ = proj₁ (make4+ n₁ n₂ n₃ n₄ ∞)

  makeN : ∀{n} → Vec String n → VarSupply → Vec Var n
  makeN ns ∞ = proj₁ (makeN+ ns ∞)

  check : Var → VarSupply → Var
  check x ∞ = make (name x) ∞


module STLC where
  open Var using (Var; VarSupply; check; next)

  infixr 4 _,_
  infixl 3 _$_
  infixr 2 𝜆_∙_
  infixr 1 _⊃_
  infixr 0 _∣_⊢_∷_
  infixr 0 _⊩_∷_

  data Ty : Set where
    ⊥   : Ty
    _⊃_ : Ty → Ty → Ty

  data Tm : Set where
    𝑣_   : Var → Tm
    𝜆_∙_ : Var → Tm → Tm
    _$_  : Tm → Tm → Tm

  record Hyp : Set where
    constructor _,_
    field
      term : Tm
      type : Ty
  open Hyp public

  data Cx : Set where
    ∅   : Cx
    _,_ : Cx → Hyp → Cx

  data _∈_ : Hyp → Cx → Set where
    Z  : ∀{Γ h}  → h ∈ (Γ , h)
    S_ : ∀{Γ h h′}  → h ∈ Γ  → h ∈ (Γ , h′)

  data _∣_⊢_∷_ (∞ : VarSupply) (Γ : Cx) : Tm → Ty → Set where
    M𝑣_ : ∀{A t}  → (t , A) ∈ Γ
                  → ∞ ∣ Γ ⊢ t ∷ A

    M𝜆  : ∀{A B x t}  → next ∞ ∣ Γ , (𝑣 (check x ∞) , A) ⊢ t ∷ B
                      → ∞ ∣ Γ ⊢ 𝜆 x ∙ t ∷ A ⊃ B

    M∘  : ∀{A B t s}  → ∞ ∣ Γ ⊢ t ∷ A ⊃ B  → ∞ ∣ Γ ⊢ s ∷ A
                      → ∞ ∣ Γ ⊢ t $ s ∷ B

  _⊩_∷_ : VarSupply → Tm → Ty → Set
  ∞ ⊩ t ∷ A = ∀{Γ} → ∞ ∣ Γ ⊢ t ∷ A


open import Data.Product using (_,_)
open Var using (make; make2; make3)
open STLC

eI : ∀{A ∞}
   → let x = make "x" ∞ in
     ∞ ⊩ 𝜆 x ∙ 𝑣 x ∷ A ⊃ A
eI = M𝜆 (M𝑣 Z)

eK : ∀{A B ∞}
   → let x , y = make2 "x" "y" ∞ in
     ∞ ⊩ 𝜆 x ∙ 𝜆 y ∙ 𝑣 x ∷ A ⊃ B ⊃ A
eK = M𝜆 (M𝜆 (M𝑣 S Z))

eS : ∀{A B C ∞}
   → let f , g , x = make3 "f" "g" "x" ∞ in
     ∞ ⊩ 𝜆 f ∙ 𝜆 g ∙ 𝜆 x ∙ (𝑣 f $ 𝑣 x) $ (𝑣 g $ 𝑣 x) ∷ (A ⊃ B ⊃ C) ⊃ (A ⊃ B) ⊃ A ⊃ C
eS = M𝜆 (M𝜆 (M𝜆 (M∘ (M∘ (M𝑣 S S Z)
                        (M𝑣 Z))
                    (M∘ (M𝑣 S Z)
                        (M𝑣 Z)))))