import GolangLean.Core.Syntax

namespace GolangLean.Core

/-! # Big-step operational semantics for Tiny Go Core.

State is `Heap × Env`. The heap is an `Array Value`; references are
indices. Env is a list of (name, value) bindings — innermost shadowing
outermost. Closures capture the env at the point of `lam`-evaluation
(lexical scope, as in Go).

The relation is

    BigStep h env e v h'

read "starting from heap `h` and env `env`, term `e` evaluates to value
`v` and produces heap `h'`". One inductive constructor per syntactic
form. Every premise either appeals to `BigStep` recursively or to a
total side-condition (lookup, binop application, bounds). -/

mutual

  inductive Value where
    | vUnit  : Value
    | vInt   : Int  → Value
    | vBool  : Bool → Value
    | vClos  : String → Term → EnvList → Value
    | vLoc   : Nat  → Value

  inductive EnvList where
    | nil    : EnvList
    | cons   : String → Value → EnvList → EnvList

end

mutual
  partial def Value.repr : Value → Std.Format
    | .vUnit       => "vUnit"
    | .vInt n      => f!"vInt {n}"
    | .vBool b     => f!"vBool {b}"
    | .vClos x _ _ => f!"<closure λ {x}. ...>"
    | .vLoc n      => f!"vLoc {n}"
  partial def EnvList.repr : EnvList → Std.Format
    | .nil         => "[]"
    | .cons k v r  => f!"{k}↦{Value.repr v} :: {EnvList.repr r}"
end

instance : Repr Value where
  reprPrec v _ := Value.repr v
instance : Repr EnvList where
  reprPrec env _ := EnvList.repr env

abbrev Env  := EnvList
abbrev Heap := Array Value

namespace EnvList

def lookup : EnvList → String → Option Value
  | .nil,        _ => none
  | .cons k v r, x => if k = x then some v else lookup r x

def extend (env : EnvList) (x : String) (v : Value) : EnvList :=
  .cons x v env

end EnvList

namespace BinOp

def apply : BinOp → Value → Value → Option Value
  | .add, .vInt a,  .vInt b  => some (.vInt (a + b))
  | .sub, .vInt a,  .vInt b  => some (.vInt (a - b))
  | .mul, .vInt a,  .vInt b  => some (.vInt (a * b))
  | .eq,  .vInt a,  .vInt b  => some (.vBool (a == b))
  | .eq,  .vBool a, .vBool b => some (.vBool (a == b))
  | .lt,  .vInt a,  .vInt b  => some (.vBool (a < b))
  | _,    _,        _        => none

end BinOp

inductive BigStep : Heap → Env → Term → Value → Heap → Prop where
  | unitR    {h env} :
      BigStep h env .unitT .vUnit h
  | intLitR  {h env} (n : Int) :
      BigStep h env (.intLit n) (.vInt n) h
  | boolLitR {h env} (b : Bool) :
      BigStep h env (.boolLit b) (.vBool b) h
  | varR     {h env x v} (hLook : env.lookup x = some v) :
      BigStep h env (.var x) v h
  | lamR     {h env} (x : String) (body : Term) :
      BigStep h env (.lam x body) (.vClos x body env) h
  | appR     {h env e1 e2 x body env' v_arg v h1 h2 h3}
             (D1 : BigStep h  env  e1 (.vClos x body env') h1)
             (D2 : BigStep h1 env  e2 v_arg                h2)
             (Db : BigStep h2 (env'.extend x v_arg) body v h3) :
      BigStep h env (.app e1 e2) v h3
  | letInR   {h env x e1 e2 v1 v2 h1 h2}
             (D1 : BigStep h  env e1 v1 h1)
             (D2 : BigStep h1 (env.extend x v1) e2 v2 h2) :
      BigStep h env (.letIn x e1 e2) v2 h2
  | ifTR     {h env e e1 e2 v h1 h2}
             (Dc : BigStep h  env e (.vBool true) h1)
             (Dt : BigStep h1 env e1 v h2) :
      BigStep h env (.ifte e e1 e2) v h2
  | ifFR     {h env e e1 e2 v h1 h2}
             (Dc : BigStep h  env e (.vBool false) h1)
             (Df : BigStep h1 env e2 v h2) :
      BigStep h env (.ifte e e1 e2) v h2
  | binopR   {h env op e1 e2 v1 v2 v h1 h2}
             (D1 : BigStep h  env e1 v1 h1)
             (D2 : BigStep h1 env e2 v2 h2)
             (Hop : op.apply v1 v2 = some v) :
      BigStep h env (.binop op e1 e2) v h2
  | refMkR   {h env e v h1}
             (D : BigStep h env e v h1) :
      BigStep h env (.refMk e) (.vLoc h1.size) (h1.push v)
  | derefR   {h env e loc v h1}
             (D : BigStep h env e (.vLoc loc) h1)
             (Hget : h1[loc]? = some v) :
      BigStep h env (.deref e) v h1
  | assignR  {h env e1 e2 loc v h1 h2}
             (D1 : BigStep h  env e1 (.vLoc loc) h1)
             (D2 : BigStep h1 env e2 v h2)
             (Hin : loc < h2.size) :
      BigStep h env (.assign e1 e2) .vUnit (h2.set! loc v)
  | seqR     {h env e1 e2 v1 v2 h1 h2}
             (D1 : BigStep h  env e1 v1 h1)
             (D2 : BigStep h1 env e2 v2 h2) :
      BigStep h env (.seq e1 e2) v2 h2

end GolangLean.Core
