-----------------------------------------------------------------
--
-- Implementierung der SECD-Maschine 
-- mit Pretty-Print Ausgabe
-- 
-----------------------------------------------------------------
module SECD_PP where

data Expr = App Expr Expr | Var Int | Lambda Int Expr | Ap 
            deriving (Eq,Show)
 
data Closure = Closure Expr [(Expr,Closure)] 
               deriving (Eq,Show)
               
type S = [Closure]
type E = [(Expr,Closure)]
type C = [Expr]
type D = [(S,E,C)]

--- 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"

--- Transitionen der secd Maschine
transform :: (S,E,C,D) -> (S,E,C,D)
transform ((res:s),     e, [],          ((ds,de,dc):d)) 
        = (res:ds,     de, dc,          d)
transform (s,           e, ((Var i):c), d)              
        = ((val i e):s, e, c,           d)
transform (s,                         e, ((Lambda i b):c), d)              
        = ((Closure (Lambda i b) e):s,e, c,                d)
transform (s, e, ((App e1 e2):c), d)  
        = (s, e, e2:e1:Ap:c,      d)
transform (((Closure (Lambda n exp) env):cl:s), e,             (Ap:c), d)  
        = ([],                                 (add env n cl), [exp], (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))

---- 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 (Lambda i e) n list = (Lambda n newe,n1)
    where (newe,n1)  = new e (n+1) ((i,n):list)

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
showexp :: Expr -> String
showexp (App e1 e2) = "(" ++ showexp e1 ++ " " ++ showexp e2 ++ ")"
showexp (Var n)     = [varlist!!n]
showexp (Lambda n e) = "lam " ++ [varlist!!n] ++ ". " ++ showexp e 

varlist :: String
varlist = ['a'..'z'] ++ varlist


result :: (S,E,C,D) -> Closure
result (s,e,c,d) = (head s)

showres :: (S,E,C,D) -> String
showres = showexp . cl2e . result

cl2e :: Closure -> Expr 
cl2e (Closure e []) = e
cl2e cl = error "cl2e not defined" 

--- Beispiel Lambda Ausdruecke
i = (Lambda 1 (Var 1))
k = (Lambda 1 (Lambda 2 (Var 1)))
s = (Lambda 1 (Lambda 2 (Lambda 3 (App (App (Var 1) (Var 3)) 
                                       (App (Var 2) (Var 3))))))
suc = (Lambda 1 (Lambda 2 (Lambda 3 (App (App (Var 1) (Var 2))
                                         (App (Var 2) (Var 3))
      )         )         )         )
num0 = (Lambda 1 (Lambda 2 (Var 2)))
num1 = (Lambda 4 (Lambda 5 (App (Var 4) (Var 5))))

y = Lambda 1 (App (Lambda 2 (App (Var 1) (App (Var 2) (Var 2))))
                  (Lambda 3 (App (Var 1) (App (Var 3) (Var 3)))))
theta = (App (Lambda 1 (Lambda 2 (App (Var 2) (App (App (Var 1) (Var 1)) (Var 2)))))
             (Lambda 1 (Lambda 2 (App (Var 2) (App (App (Var 1) (Var 1)) (Var 2)))))
        )     
                  
tt = (Lambda 1 (Lambda 2 (Var 1)))
ff = (Lambda 1 (Lambda 2 (Var 2)))
iff = (Lambda 1 (Lambda 2 (Lambda 3 (App (App (Var 1) (Var 2)) (Var 3)))))

g = Lambda 1 (Lambda 2 (App (App (App iff tt) num0) (App (Var 1) num0)))


   