module Interpreter where 
{-@ LIQUID "--totality" @-}
{-@ LIQUID "--real" @-}

data BinOp a = Plus | Times

data Exp a  = EConst Int | EBinOp (BinOp a) (Exp a) (Exp a)

data Instr a = IConst Int | IBinOp (BinOp a)

data List a = Nil | Cons a (List a)


{-@ autosize Prog @-}
data Prog a = Emp | PInst (Instr a) (Prog a)
type Stack = List Int

data SMaybe a = SNothing a | SJust a

{-@ measure binOpDenote @-}
binOpDenote :: Int -> Int -> BinOp a -> Int 
binOpDenote x y Plus  = (x+y)
binOpDenote x y Times = (x*y)

{-@ measure expDenote @-}
expDenote :: Exp a -> Int 
expDenote (EConst n)       = n
expDenote (EBinOp b e1 e2) = binOpDenote (expDenote e1) (expDenote e2) b

{-@foo :: s:Stack -> {v:SMaybe Stack| v = SNothing s || v = SJust s } @-}
foo :: Stack -> SMaybe Stack
foo = undefined 

{-@ measure instrDenote @-}
instrDenote :: Stack -> Instr a -> SMaybe Stack
{-@ instrDenote :: s:Stack -> i:{v:Instr a | isIBinOp v => lenL s >= 2} 
                -> {v:SMaybe {st:Stack | if (isIBinOp i) then (lenL st = lenL s - 1) else (lenL st = lenL s + 1)} | v = instrDenote s i} @-}
instrDenote s (IConst n) = SJust (Cons n s)
instrDenote s (IBinOp b) = 
	let x  = headL s in 
	let s1 = tailL s in 
	let y  = headL s1 in 
	let s2 = tailL s1 in 
	SJust (Cons (binOpDenote x y b) s2)


{-@ invariant {v:Prog a | binOps v >= 0} @-}
{-@ measure binOps @-}
binOps :: Prog a -> Int 
binOps Emp = 0 
binOps (PInst x xs) = if isIBinOp x then 1 + (binOps xs) else binOps xs 



{- measure progDenote :: Stack -> Prog -> SMaybe Stack @-}
{-@ measure progDenote @-}
{-@ Decrease progDenote 2 @-}
progDenote :: Stack -> Prog a -> SMaybe Stack
{-@ progDenote :: s:Stack -> p:{v:Prog a | lenL s >= 2 * binOps v }  -> {v: SMaybe Stack | v = progDenote s p} @-}
progDenote s Emp = SJust s
progDenote s (PInst x xs) = SNothing s 
{-
progDenote s (PInst x xs) | SJust s' <- instrDenote s x = progDenote s' xs
                          | otherwise                   = SNothing s
-}

{-
{- compile :: e:Exp -> {v:Prog | (progDenote Nil v) == Nothing } @-}
{- compile :: e:Exp -> {v:Prog | (progDenote Nil v) == Just (Cons (expDenote e) Nil)} @-}
compile :: Exp -> Prog
compile (EConst n)       = single (IConst n)  
compile (EBinOp b e1 e2) = compile e2 `append` compile e1 `append` (single $ IBinOp b) 

-}



-- Hard Wire the measures so that I can provide exact types for Nil and Cons
{-@ SNothing :: s:s -> {v:SMaybe s | v = SNothing s && not (isSJust v)} @-}
{-@ SJust    :: s:s -> {v:SMaybe s | v = SJust s && (isSJust v) && (fromSJust v = s)} @-}
{-@ Cons     :: x:a -> xs:List a -> {v:List a | v = Cons x xs && headL v = x && tailL v = xs && lenL v = 1 + lenL xs} @-}
{-@ Nil      :: {v:List a | v = Nil  && lenL v = 0} @-}
iconst = IConst


{-@ measure isSJust :: SMaybe a -> Prop @-}
{-@ isSJust :: x:SMaybe a -> {v:Bool | Prop v <=> isSJust x}  @-}
isSJust (SJust _) = True 
isSJust (SNothing _ ) = False

{-@ measure fromSJust :: SMaybe a -> a @-}
{-@ fromSJust :: x:{v:SMaybe a | isSJust v} -> {v:a | v = fromSJust x}  @-}
fromSJust (SJust x) = x

{-@ measure isIBinOp @-}
isIBinOp (IBinOp _) = True
isIBinOp _          = False

{-@ measure headL :: List a -> a @-}
{-@ headL :: xs:{v:List a | lenL v > 0} -> {v:a | v = headL xs} @-}
headL :: List a -> a
headL (Cons x _) = x

{-@ measure tailL :: List a -> List a @-}
{-@ tailL :: xs:{v:List a | lenL v > 0} -> {v: List a | v = tailL xs && lenL v = lenL xs - 1} @-}
tailL :: List a -> List a
tailL (Cons _ xs) = xs


{-@ measure lenL :: List a -> Int @-}
{-@ invariant {v:List a | lenL v >= 0 } @-}
lenL :: List a -> Int 
lenL (Cons _ xs) = 1 + lenL xs 
lenL Nil         = 0 



-- List operations
{-@ autosize Exp @-}
{-@ data List [lenL] @-}

(Cons x xs) `append` ys = cons x $ append xs ys
Nil         `append` ys = ys 

emp      = Nil
single x = Cons x Nil
cons x xs = Cons x xs

