module Simulator.CheckPrecision where

open import Data.Nat
open import Data.Bool renaming (Bool to 𝔹; _β‰Ÿ_ to _β‰Ÿα΅‡_)
open import Data.Unit using (⊀; tt)
open import Data.Maybe
open import Data.Product using (_Γ—_; βˆƒ; βˆƒ-syntax) renaming (_,_ to ⟨_,_⟩)
open import Data.List hiding ([_])
open import Function using (case_of_)
open import Relation.Nullary using (Β¬_; Dec; yes; no)
open import Relation.Nullary.Decidable using (isYes)
open import Relation.Nullary.Negation using (contradiction)
open import Relation.Binary.PropositionalEquality
  using (_≑_; _β‰’_; refl; trans; sym; subst; cong; congβ‚‚)

open import Common.Utils
open import Common.Types
open import Memory.Addr
open import CC.Errors
open import Simulator.AST


{- Each case of the `check` function below reflects
   its corresponding rule in `Precision` -}
check-βŠ‘? : (t tβ€² : AST) β†’ 𝔹
-- first get rid of all the `cast-pc`s
check-βŠ‘? (castpc _ t _) tβ€² = check-βŠ‘? t tβ€²
check-βŠ‘? t (castpc _ tβ€² _) = check-βŠ‘? t tβ€²
-- in most cases we expect the two sides to be syntactically equal
-- Var
check-βŠ‘? (var x _) (var y _) = isYes (x β‰Ÿ y)
-- Const
check-βŠ‘? (const {ΞΉ} k β„“ _) (const {ΞΉβ€²} kβ€² β„“β€² _) =
  case ` ΞΉ ≑ᡣ? ` ΞΉβ€² of Ξ» where
  (yes refl) β†’ (isYes (const-eq? k kβ€²)) ∧ (isYes (β„“ =? β„“β€²))
  (no  _)    β†’ false
-- Addr
check-βŠ‘? (addr a β„“ _) (addr aβ€² β„“β€² _) =
  (isYes (addr-eq? a aβ€²)) ∧ (isYes (β„“ =? β„“β€²))
-- Lam
check-βŠ‘? (lam β„“αΆœ A t β„“ _) (lam β„“αΆœβ€² Aβ€² tβ€² β„“β€² _) =
  (isYes (β„“αΆœ =? β„“αΆœβ€²)) ∧ (isYes (β„“ =? β„“β€²)) ∧
  (isYes (A βŠ‘? Aβ€²))   ∧ (check-βŠ‘? t tβ€²)
-- App
check-βŠ‘? (app t₁ tβ‚‚ _) (app t₁′ tβ‚‚β€² _) = (check-βŠ‘? t₁ t₁′) ∧ (check-βŠ‘? tβ‚‚ tβ‚‚β€²)
-- If
check-βŠ‘? (cond t₁ tβ‚‚ t₃ _) (cond t₁′ tβ‚‚β€² t₃′ _) =
  (check-βŠ‘? t₁ t₁′) ∧ (check-βŠ‘? tβ‚‚ tβ‚‚β€²) ∧ (check-βŠ‘? t₃ t₃′)
-- Let
check-βŠ‘? (let-bind t₁ tβ‚‚ _) (let-bind t₁′ tβ‚‚β€² _) =
  (check-βŠ‘? t₁ t₁′) ∧ (check-βŠ‘? tβ‚‚ tβ‚‚β€²)
-- Ref, Ref?, and Refβœ“
check-βŠ‘? (ref   β„“ t _) (ref   β„“β€² tβ€² _) = isYes (β„“ =? β„“β€²) ∧ (check-βŠ‘? t tβ€²)
check-βŠ‘? (ref?  β„“ t _) (ref?  β„“β€² tβ€² _) = isYes (β„“ =? β„“β€²) ∧ (check-βŠ‘? t tβ€²)
check-βŠ‘? (ref?  β„“ t _) (ref   β„“β€² tβ€² _) = isYes (β„“ =? β„“β€²) ∧ (check-βŠ‘? t tβ€²)
check-βŠ‘? (refβœ“ β„“ t _) (refβœ“ β„“β€² tβ€² _) = isYes (β„“ =? β„“β€²) ∧ (check-βŠ‘? t tβ€²)
-- Deref
check-βŠ‘? (deref t _) (deref tβ€² _) = check-βŠ‘? t tβ€²
-- Assign, Assign?, and Assignβœ“
check-βŠ‘? (assign   t₁ tβ‚‚ _) (assign   t₁′ tβ‚‚β€² _) = check-βŠ‘? t₁ t₁′ ∧ check-βŠ‘? tβ‚‚ tβ‚‚β€²
check-βŠ‘? (assign?  t₁ tβ‚‚ _) (assign?  t₁′ tβ‚‚β€² _) = check-βŠ‘? t₁ t₁′ ∧ check-βŠ‘? tβ‚‚ tβ‚‚β€²
check-βŠ‘? (assign?  t₁ tβ‚‚ _) (assign   t₁′ tβ‚‚β€² _) = check-βŠ‘? t₁ t₁′ ∧ check-βŠ‘? tβ‚‚ tβ‚‚β€²
check-βŠ‘? (assign?  t₁ tβ‚‚ _) (assignβœ“ t₁′ tβ‚‚β€² _) = check-βŠ‘? t₁ t₁′ ∧ check-βŠ‘? tβ‚‚ tβ‚‚β€²  {- example: `AssignRefProxy` -}
check-βŠ‘? (assignβœ“ t₁ tβ‚‚ _) (assignβœ“ t₁′ tβ‚‚β€² _) = check-βŠ‘? t₁ t₁′ ∧ check-βŠ‘? tβ‚‚ tβ‚‚β€²
-- Prot
check-βŠ‘? (protect β„“ t _) (protect β„“β€² tβ€² _) =
  isYes (β„“ =? β„“β€²) ∧ check-βŠ‘? t tβ€²
-- NSU error
check-βŠ‘? (err nsu-error _) (err nsu-error _) = true
-- Cast
check-βŠ‘? (cast t A B C) (cast tβ€² Aβ€² Bβ€² Cβ€²) =
  -- relate by Cast
  (isYes (A βŠ‘? Aβ€²) ∧ isYes (B βŠ‘? Bβ€²) ∧ check-βŠ‘? t tβ€²) ∨
  -- relate by CastL
  (isYes (A βŠ‘<:? Cβ€²) ∧ isYes (B βŠ‘<:? Cβ€²) ∧ check-βŠ‘? t (cast tβ€² Aβ€² Bβ€² Cβ€²)) ∨
  -- relate by CastR
  (isYes (C βŠ‘? Aβ€²) ∧ isYes (C βŠ‘? Bβ€²) ∧ check-βŠ‘? (cast t A B C) tβ€²)
-- Special case: cast on the left, blame on the right
check-βŠ‘? (cast t A B C) (err (blame p) Aβ€²) =
  {- relate by CastL -}
  (isYes (A βŠ‘? Aβ€²) ∧ isYes (B βŠ‘? Aβ€²) ∧ check-βŠ‘? t (err (blame p) Aβ€²)) ∨
  {- relate by Blame -}
  (isYes (C βŠ‘? Aβ€²))
-- CastL
check-βŠ‘? (cast t A B C) tβ€² =
  let Cβ€² = get-type tβ€² in
  {- intuition: more precise side can go up in subtyping -}
  isYes (A βŠ‘<:? Cβ€²) ∧ isYes (B βŠ‘<:? Cβ€²) ∧ check-βŠ‘? t tβ€²
-- CastR
check-βŠ‘? t (cast tβ€² Aβ€² Bβ€² Cβ€²) =
  let C = get-type t in
  isYes (C βŠ‘? Aβ€²) ∧ isYes (C βŠ‘? Bβ€²) ∧ check-βŠ‘? t tβ€²
-- Blame
check-βŠ‘? t (err (blame p) Aβ€²) =
  let A = get-type t in
  isYes (A βŠ‘? Aβ€²)
-- Otherwise
check-βŠ‘? _ _ = false