-----------------------------------------------------------------
--
-- Implementierung der SECD-Maschine 
-- mit Pretty-Print Ausgabe
-- 
-- Outermost-Auswertung mit Update der Umgebung
-- 
-----------------------------------------------------------------
module SECD_PP_OUTERMOST2 where

data Expr = App Expr Expr | Var Int | Lam Int Expr | Ap
            | Con Constants | Update Int
            deriving Eq
data Closure = Closure Expr [(Expr,Closure)] 
               deriving (Eq,Show)
        
type S = [Closure]
type E = [(Expr,Closure)]
type C = [Expr]
type D = [(S,E,C)]

data Constants = N Int | O Operation | If deriving Eq
data Operation = Plus | Times | Leq       deriving Eq

--- Funktionen zur Umgebungsverwaltung
add :: [(Expr,a)] -> Int -> a -> [(Expr,a)]
add envlist n e = (Var n,e):envlist

val :: Int -> [(Expr,a)] -> a
val n ((Var m,e):rest) = if n==m then e else val n rest 
val n [] =   error "ungebundene Variable"

-- Update der Umgebung
update :: E -> Int -> Closure -> E
update ((Var j,clj):env) i cl 
       | i == j    = (Var i,cl) : env
       | otherwise = (Var j,clj): update env i cl
update [] _ _ = []


--- Transitionen der secd Maschine
transform :: (S,E,C,D) -> (S,E,C,D)
transform (s, e, ((Var i):c), d)              
--        = ((val i e):s, e, c, d)
        = ([], env, [a], (s,e,Update i:c):d) 
--        = ([], env, [a], (s,e,c):d) 
	  where Closure a env = val i e
--   Elemente der Umgebung sind potenziell unausgewertet,
--   Sprung+Update danach
transform (cl:s,           e, Update i:c, d)
        = (cl:s, update e i cl, c, d)        

transform (s, e, ((Lam i b):c), d)              
        = ((Closure (Lam i b) e):s,e, c, d)
transform (s, e, ((App e1 e2):c), d)  
--        = (s, e, e2:e1:Ap:c, d)
        = ((Closure e2 e):s, e, e1:Ap:c, d)
--   Argument als Closure auf den Stack legen, unausgewertet

transform (((Closure (Lam n exp) env):cl:s), e, (Ap:c), d)  
        = ([], (add env n cl), [exp], (s,e,c):d)
transform ((res:s),  e, [], ((ds,de,dc):d)) 
        = (res:ds, de, dc, d)
-- Konstantenauswertung        
transform (s, e, (Con x):c, d)
    = ((Closure (Con x) []):s, e, c, d)   
-- erzwungene Striktheit durch einen Marker auf dem Stack.
-- beide Argumente da, OK
transform (((Closure (Con (O op)) []):
            (Closure (Con (N n)) []):
            (Closure (Con (N m)) []):s), e, (Ap:Ap:c), d) 
    = ((Closure (Con (N (apply op n m))) []):s, e, c, d)
-- erstes Argument schon da:
transform (((Closure (Con (O op)) []):
            (Closure (Con (N n)) []):
            (Closure e2 env2):s), e, (Ap:Ap:c), d) 
    = ([],env2,[e2],(s,e,Con (N n):Con (O op):Ap:Ap:c):d)
-- kein Argument ausgewertet:
transform (((Closure (Con (O op)) []):
            (Closure e1 env1):s), e, (Ap:Ap:c), d) 
    = ([],env1,[e1], (s,e, Con (O op):Ap:Ap:c):d) 
    
-- If analog, Bedingung auswerten:
transform ((Closure (Con If) []):
           (Closure (Con (N x)) []):cl1:cl2:s, e, (Ap:Ap:Ap:c), d) 
    = if x == 1 then (cl1:s, e, c, d) else (cl2:s, e, c, d)
transform ((Closure (Con If) []):
           (Closure b envB ):cl1:cl2:s, e, (Ap:Ap:Ap:c), d) 
    = ([],envB,[b],(s, e, Con If:Ap:Ap:Ap:c):d)
transform (((Closure exp env):(Closure exp2 env2):s), e, (Ap:c), d)  
    = ( (Closure (App exp exp2) (env++env2)):s,   e, c,      d)
        
---- Startfunktion
evaluate :: Expr -> (S,E,C,D)
evaluate exp = run ([],[],[rename exp],[])

evalseq  :: Expr -> IO ()
evalseq  exp = putStr $ unlines 
                 (map myshow (stateseq ([],[],[rename exp],[])))

myshow :: (S,E,C,D) -> String
myshow (s,e,c,d) = "\nStack:   " ++ show s ++ 
                   "\nEnv:     " ++ show e ++
                   "\nControl: " ++ show c ++ 
                   "\nDump:    " ++ show d 

---- Einzelschrittfunktion
run :: (S,E,C,D) -> (S,E,C,D)
run (s,e,c,d)  = if c == [] && d == [] then (s,e,c,d)
                 else run (transform (s,e,c,d))

stateseq :: (S,E,C,D) -> [(S,E,C,D)]
stateseq (s,e,c,d)  = if c == [] && d == [] then [(s,e,c,d)]
                      else (s,e,c,d): stateseq (transform (s,e,c,d))

-- Konstantenauswertung 
apply :: Operation -> Int -> Int -> Int 
apply Plus  n m = n+m
apply Times n m = n*m
apply Leq   n m = if n <= m then 1 else 0
 
---- Umbenennung gebundener Variablen, 
---- damit keine Variablenkonflikte auftreten
rename :: Expr -> Expr
rename exp = e where (e,n) = new exp 0 []

new :: Expr -> Int -> [(Int,Int)] -> (Expr,Int)
new (Var i)     n list = (Var (get i list),n)
new (App e1 e2) n list = (App newe1 newe2,n2)
    where (newe1,n1) = new e1 n  list
          (newe2,n2) = new e2 n1 list
new (Lam i e) n list = (Lam n newe,n1)
    where (newe,n1)  = new e (n+1) ((i,n):list)
new (Con c)     n list = (Con c,n)

get :: Eq a => a -> [(a,b)] -> b 
get i []        = error "freie Variable bei Umbenennung entdeckt"
get i ((j,n):l) = if i==j then n else get i l
         
-- Pretty Printer
instance Show Expr where
    show (App e1 e2) = "(" ++ show e1 ++ " " ++ show e2 ++ ")"
    show (Var n)     = [varlist!!n]
    show (Lam n e) = "lam " ++ [varlist!!n] ++ ". " ++ show e 
    show (Con c)     = show c
    show (Update i)  = "Update " ++ show i
    show Ap          = "Ap" 

instance Show Constants 
 where show (N x)  = show x
       show (O op) = show op
       show If     = "If "
       
instance Show Operation 
 where show Plus  = "+"
       show Times = "*"
       show Leq   = "<=" 

varlist :: String
varlist = ['a'..'z'] ++ varlist

--- Einige Beispiele fuer Lam Ausdruecke
bsp = App fun (Lam 1 (App (App (Con (O Times)) (Var 1)) (Var 1)))
fun = Lam 1 (App (App (Con (O Plus)) (App (Var 1) (Con (N 3)))) 
                                     (App (Var 1) (Con (N 4))))
i = (Lam 1 (Var 1))
k = (Lam 1 (Lam 2 (Var 1)))
s = (Lam 1 (Lam 2 (Lam 3 (App (App (Var 1) (Var 3)) 
                                       (App (Var 2) (Var 3))))))
suc = (Lam 1 (Lam 2 (Lam 3 (App (App (Var 1) (Var 2))
                                         (App (Var 2) (Var 3))
      )         )         )         )
num0 = (Lam 1 (Lam 2 (Var 2)))
num1 = (Lam 4 (Lam 5 (App (Var 4) (Var 5))))
plusnum = Lam 1 (Lam 2 (Lam 3 (Lam 4 
	    (App (App (Var 1) (Var 3)) (App (App (Var 2)(Var 3)) (Var 4))))))

-- für Lesbarkeit und Auswertung:
showNum num = App (App num (Lam 1 (App (App (Con (O Plus)) (Con (N 1))) (Var 1)))) (Con (N 0))
     
delta = (App s s) where s = Lam 1 (App (Var 1) (Var 1)) 

plusC = Lam 1 (App (App (Con (O Plus)) (Var 1)) (Var 1))
malC  = Lam 1 (App (App (Con (O Times)) (Var 1)) (Var 1))
bspArgs = App malC (App plusC (Con (N 1)))

c2 = Con (N 2)
malmalC 
  = Lam 1 (App (App 
		(Con (O Times)) b)               -- b*b*b
		(App (App (Con (O Times)) b) b)) -- = b*b
				where b = Var 1
bsp2 =  (App malC (App malmalC c2))
