-- Bestimmung der look-ahead Mengen 
-- fuer Kontextfreie Grammatiken

{-----------------------------------------
   Definition kontextfreier Grammatiken
------------------------------------------}


terminals :: [Char]
terminals = ['a'..'z'] ++ ['0'..'9'] ++ "()+-*"

nonterminals :: [Char]
nonterminals = ['A'..'Z']

-- CFG = (Nonterminals,Terminals,StartSymbol,Produktionen)
type CFG = (String, String, Char, Rules)
type Rules = [(Char,String)]
type Nonterminal = Char
type Terminal = Char

isCFG (ns,as,start,ps) = subseteq ns nonterminals && subseteq as terminals
                         && elem start ns && valid ps 
    where valid ps = subseteq (lhs ps) ns && subseteq (rhs ps) (ns++as)

-- lhs bildet Liste aller linken Regelseiten
lhs :: Rules -> String
lhs =  foldr ((:).fst) [] 
            
-- rhs bildet Liste aller rechten Regelseiten
rhs :: Rules -> String
rhs = foldr ((++).snd) []

-- rules bestimmt Regeln zu Grammatik
rules :: CFG -> Rules
rules (ns,cs,s,ps) = ps


-- Bestimmung des Startsymbols
startsymbol :: CFG -> Nonterminal
startsymbol (ns,cs,s,ps) = s

-- Bestimmung der Liste der Nonterminale
nt :: CFG -> [Nonterminal]
nt (ns,cs,s,ps) = ns



-- Ausgabe einer CFG
showCFG :: CFG -> IO()
showCFG (ns,as,start,ps) 
 = putStr ("Nonterminale:  " ++ ns ++
      '\n':"Terminale:     " ++ as  ++
      '\n':"Startsymbol:   " ++ start :
      '\n':"Produktionen:  " ++ (showRules ps)) 

showRules        :: Rules -> String
showRules []     =  ['\n']
showRules (p:ps) = let (lhs,rhs) = p in
                   ('\n':lhs: " -> " ++ rhs) ++ showRules ps

-- Beispiel CFG
gae  = ("ETFHG","a()+*",'E',rgae)
rgae = [('E',"TH"),('H',"+TH"),('H',""),('T',"FG"),('G',"*FG"),('G',""),
        ('F',"(E)"),('F',"a")] 

gae2 = ("ETF","a()+*",'E',rgae2)
rgae2 = [('E',"E+T"),('E',"T"),('T',"F*T"),('T',"F"),('F',"(E)"),('F',"a")]


{----------------------------------------------
  Bestimmung der look-ahead Mengen
-----------------------------------------------}

fi :: CFG -> String -> [String] -- first Menge zu Satzform
fo :: CFG -> Char   -> [String] -- follow Menge zu Nonterminal

{- [String] zur Darstellung von epsilon als "", alle einzelnen Buchstaben
   werden entsprechend als einelementiger String dargestellt. -}
{- alte direkte Berechnung von first Mengen -}

fi g cs = removedups (fisave g cs [])
-- Um Linksrekursion abzufangen, wird im zusaetzlichen Argument alc 
-- (initialisiert mit []) protokolliert, von welchen Nonterminalen die
-- first-Symbole schon eingegangen sind.

fisave :: CFG -> String -> String -> [String]
fisave g "" alc = [""]
fisave g@(ns,as,start,ps)  [a] alc
    | elem a as = [[a]]
    | elem a ns = derivefi g a alc
    | otherwise = error "symbol not allowed in fi appl."
fisave g (c:cs) alc = checkForEps (fisave g [c] alc) g cs (c:alc) 

checkForEps :: [String] -> CFG -> String -> String -> [String]
checkForEps fs g "" alc    = fs
checkForEps fs g (c:cs) alc 
          | elem ""  fs = (remove "" fs) ++ 
                          checkForEps (fisave g [c] alc) g cs (c:alc)
          | otherwise   = fs

derivefi :: CFG -> Char -> String -> [String]
derivefi g@(ns,as,s,ps) c alc 
   | elem c alc = []
   | otherwise  = concat [fisave g alpha (c:alc)| (n,alpha) <- ps, n==c]


-- Bestimmung von Follow Mengen fuer Nonterminale

fo g@(ns,cs,start,ps) n  | n==start  = "" : derivefo g ps start [] [n]
                           | elem n ns = derivefo g ps n  [] [n]
                           | otherwise = error "nonterminal required in fo appl."

-- alc : already controlled symbols, zur Behandlung von Rekursion
derivefo :: CFG -> Rules -> Char -> [String] -> String -> [String]
derivefo g []     n fs alc = removedups fs
derivefo g (p:ps) n fs alc
    | elem n (snd p) = derivefo g ps n (fs ++ follows) newalc
    | otherwise      = derivefo g ps n fs alc
    where (follows,newalc) = collectfo g n (snd p) (fst p) alc

collectfo :: CFG -> Char -> String -> Char -> String -> ([String],String)
collectfo g n []     a alc = ([],alc)
collectfo g n (c:cs) a alc
     | n == c    = (fos, newalc) 
     | otherwise = collectfo g n cs a alc
     where fos = (if elem "" fics && not (elem a alc) 
                  then (remove "" fics) ++ focs 
                  else fics) ++ restlist
           (restlist,newalc) = collectfo g n cs a nextalc
           fics = fi g cs
           focs = derivefo g (rules g) a [] (a:alc) 
           nextalc = if elem "" fics && not (elem a alc) then a:alc else alc

-- Bestimmung der look-ahead Mengen
la :: CFG -> (Char,String) -> [String]
la g (a,alpha) = removedups (concat [ fi g (alpha ++ foA) | foA <- fo g a])


-- LL(1) Test fuer kontextfreie Grammatiken
ll1 :: CFG -> Bool
ll1 g = checkAlternatives (map fst ps)  (map (la g) ps)
      where ps = rules g

checkAlternatives :: String -> [[String]] -> Bool
checkAlternatives []     []       = True
checkAlternatives (n:ns) (la:las) = (check n la ns las) 
                                    && checkAlternatives ns las 

check :: Char -> [String] -> String -> [[String]] -> Bool
check n la [] [] = True
check n la (n':ns) (la':las) 
    | n == n' = (disjoint la la') && check n la ns las
    | otherwise = check n la ns las


-- Anzeige der La-Mengen

showLa :: CFG -> IO ()
showLa g = putStr (showRulesLa (rules g))
  where 
    showRulesLa        :: Rules -> String
    showRulesLa []     =  ['\n']
    showRulesLa (p:ps) = let (lhs,rhs) = p 
                             n = 10 - length rhs
                             blanks = replicate n ' '
                         in
                           ('\n':lhs: " -> " ++ rhs ++ blanks ++ 
                             concatMap (\ cs -> if null cs then " Eps " else cs) 
                                       (la g p)) 
                             ++ showRulesLa ps

----------------------------------------------
-- TD-Analyseautomat fuer LL(1)-Grammatik
----------------------------------------------}

action :: CFG -> String -> String -> Action
data Action = Rule String Int | Pop | Error | Accept
                deriving Show
action g@(ns,as,start,ps) [x]  [a] 
    | elem a ns           = findRule g a ps [x] 1
    | x == a && elem x as = Pop
    | otherwise           = Error
action g@(ns,as,start,ps) "" [a] = findRule g a ps "" 1 
action g "" ""  = Accept

findRule :: CFG -> Char -> Rules -> String -> Int -> Action
findRule g a (p:ps) x i 
    | fst p == a  && elem x (la g p) = Rule (snd p) i
    | otherwise                      = findRule g a ps x (i+1)
findRule g a [] x i = Error


type TDA = (String, String, [Int]) 

parseTDA :: CFG -> String -> [Int]
parseTDA g@(ns,as,start,ps) w = run g (w,[start],[])
    
run :: CFG -> TDA -> [Int]
run g (a:w,n:alpha,is) = case (action g [a] [n]) of  
                            Pop            -> run g (w,alpha,is)
                            (Rule beta i)  -> run g (a:w,beta++alpha,i:is)
                            x -> []
run g ("",n:alpha,is)  = case (action g "" [n]) of  
                            (Rule beta i) -> run g ("",beta++alpha,i:is)
                            x -> []
run g ("","",is) = reverse is
run g tda = []


step :: CFG -> TDA -> TDA
step g tda@(a:w,n:alpha,is) = case (action g [a] [n]) of  
                            Pop            -> (w,alpha,is)
                            (Rule beta i)  -> (a:w,beta++alpha,i:is)
                            x -> tda
step g tda@("",n:alpha,is)  = case (action g "" [n]) of  
                            (Rule beta i) ->  ("",beta++alpha,i:is)
                            x -> tda
step g ("","",is) =  ("","",is) 
step g tda = tda


stateSeq g w = iterate (step g) (w,[s],[])
               where s = startsymbol g

showSeq :: [TDA] -> String
showSeq (st:sts) = show st ++ '\n':showSeq sts
showSeq []       = []

showTDASeq g w = putStr (showSeq (stopSeq (stateSeq g w)))

-- cuts a sequence when a fixpoint is reached
stopSeq :: Eq a => [a] -> [a] 
stopSeq (x1:x2:xs) | x1==x2    = [x1]
                   | otherwise = x1:stopSeq (x2:xs)



main = showTDASeq gae "(a*a)"

{--------------------
-- Hilfsfunktionen --
--------------------}

-- entferne Duplikate aus Liste
removedups :: Eq a => [a] -> [a]
removedups []     = []
removedups (x:xs) = x:removedups(remove x xs)


-- entferne Element aus Liste
remove :: Eq a => a -> [a] -> [a]
remove x [] = []
remove x (y:ys) | x==y      = remove x ys
                | otherwise = y: remove x ys




-- subseteq testet, ob jedes Element der ersten Liste 
-- auch in der zweiten Liste vorkommt

subseteq           :: Eq a => [a] -> [a] -> Bool
subseteq [] ys     = True
subseteq (x:xs) [] = False
subseteq (x:xs) ys = elem x ys && subseteq xs ys



-- disjoint :: Eq a => [a] -> [a] -> Bool
disjoint [] ys = True
disjoint xs [] = True
disjoint (x:xs) ys = not (elem x ys) && disjoint xs ys
--
