{-
    BNF Converter: Abstract syntax
    Copyright (C) 2004  Author: Markus Forsberg, Aarne Ranta

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-}


module Language.LBNF.GetCF where

import Control.Monad ( when )

import Language.LBNF.CF
import Language.LBNF.Utils
-- import Language.LBNF.ParBNF
import Language.LBNF.Grammar(pGrammar, tokens)

import Data.List(nub,partition)
import qualified Language.LBNF.Grammar as Abs
import Language.LBNF.Runtime
import Data.Char
import Language.LBNF.TypeChecker

type TempRHS = Either [Either String String] Reg
type TempRule = (Fun,(Cat,TempRHS))

getCF :: String -> (CF, [String])
getCF = getCFofG . pGrammar . tokens

getCFofG :: ParseMonad Abs.Grammar -> (CF, [String])
getCFofG g = (cf,msgs ++ msgs1) where

  (cf,msgs1) = ((exts,ruls2),msgs2)
  (ruls2,msgs2) = untag $ map (checkRule cf0) $ rulesOfCF cf0
  untag :: ([Either Rule String]) -> ([Rule],[String])
  untag ls = ([c | Left c <- ls], [r| Right r <- ls])
  -- isRule = either (const True) (const False)
  cf0 :: CF
  (cf0@(exts,_),msgs) = (revs . srt . conv $ g)
  srt :: [Either (Either Pragma TempRule) String] -> (CF, [String])
  srt rs = let
       rules              = [fixRuleTokens n r | (n,Left (Right r)) <- zip [1..] rs]
       literals           = nub  [lit | Left xs <- map (snd . snd) rules,
                                   (Left lit) <- xs,
                                   elem lit specialCatsP]

       pragma             = [r | Left (Left r) <- rs]
       tokens             = [i | TokenReg i _ _ <- pragma]
       errors             = [s | Right s <- rs, not (null s)]
       (symbols,keywords) = partition notIdent reservedWords
       notIdent s         = null s || not (isIdentAlpha (head s)) || any (not . isIdentRest) s
       isIdentAlpha c     = isLatin1 c && isAlpha c
       isIdentRest c      = isIdentAlpha c || isDigit c || c == '_' || c == '\''
       reservedWords      = nub [t | (_,(_,Left its)) <- rules, Right t <- its] ++
         concatMap (reservedLiteralAQ [ (b,i,a) | AntiQuote b i a <- pragma ]) (literals ++ tokens)
       cats               = []
            in (((pragma,(literals,symbols,keywords,cats)),rules),errors)

  revs :: (CF, [String]) -> (CF, [String])
  revs (cf@((pragma,(literals,symbols,keywords,_)),rules),errors) =
    (((pragma,
       (literals,symbols,keywords,findAllReversibleCats (cf))),rules),errors)

fixRuleTokens :: Int -> TempRule -> Rule
fixRuleTokens n (f,(c,rhs)) =
  (f,(c,either Left (\r -> Right (r,"RTL_"++show n)) rhs))





conv :: ParseMonad Abs.Grammar -> [Either (Either Pragma TempRule) String]
conv (Bad s)                 = [Right s]
conv (Ok (Abs.Grammar defs)) = map Left $ concatMap (transDef defs) defs

reservedLiteralAQ []        l = []
reservedLiteralAQ [(b,i,a)] l = [b ++ l]
reservedLiteralAQ _         l = error "multiple antiquote pragmas"

isAqLabel x = case x of
  (Abs.Aq s)    -> True
--  Abs.LabP Abs.Aq _    -> True
--  Abs.LabPF Abs.Aq _ _ -> True
--  Abs.LabF Abs.Aq _    -> True
--  _                    -> False

transDef :: [Abs.Def] -> Abs.Def -> [Either Pragma TempRule]
transDef defs x = case x of
-- Abs.Rule label cat items | isAqLabel label -> []
 Abs.Rule label cat items ->
   [Right (transLabel label,(transCat cat, transRHS items))]
 Abs.Comment str               -> [Left $ CommentS str]
 Abs.Comments str0 str         -> [Left $ CommentM (str0,str)]
 Abs.Token ident reg           -> [Left $ TokenReg (transIdent ident) False reg]
 Abs.PosToken ident reg        -> [Left $ TokenReg (transIdent ident) True reg]
 Abs.Entryp idents             -> [Left $ EntryPoints (map transIdent idents)]
 Abs.Internal label cat items  ->
   [Right (transLabel label,(transCat cat,(Left $ Left "#":(map transItem items))))]
 Abs.Separator size ident str -> map  Right $ separatorRules size ident str
 Abs.Terminator size ident str -> map  Right $ terminatorRules size ident str
 Abs.Coercions ident int -> map  (Right) $ coercionRules ident int
 Abs.Rules ident strs -> map (Right) $ ebnfRules ident strs
 Abs.Layout ss      -> [Left $ Layout ss]
 Abs.LayoutStop ss  -> [Left $ LayoutStop ss]
 Abs.LayoutTop      -> [Left $ LayoutTop]
 Abs.Derive ss      -> [Left $ Derive [s|Abs.Ident s<-ss]]
-- Abs.Function f xs e -> [Left $ FunDef (transIdent f) (map transArg xs) (transExp e)]
 Abs.AntiQuote b i a ->
   [Left $ AntiQuote b i a]
   ++ [Left  $ TokenReg "AqToken" False $ aqToken i a]
   ++ aqRules (b,i,a) (getCats defs) where
   reg = aqToken a

aqToken :: String -> String -> Abs.Reg
aqToken i s@(c:cs) = Abs.RSeq (Abs.RSeqs i) $ Abs.RSeq (Abs.RStar $ foldr1 Abs.RAlt $ map clause prefixes) $ Abs.RSeqs s where
  prefixes = scanr (:) [c] . reverse $ cs
  clause (d:ds) = subclause (reverse ds) (Abs.RMinus Abs.RAny $ Abs.RChar d)
  subclause [] x = x
  subclause (e:es) x = Abs.RSeq (Abs.RChar e) (subclause es x)

getCats :: [Abs.Def] -> [Cat]
getCats = nub . concatMap (\x -> case x of
  Abs.Rule _ cat _     -> [transCat cat]
  Abs.Internal _ cat _ -> [transCat cat]
  _                    -> [])

aqRHS :: [Abs.Item] -> Cat
aqRHS xs = case filter filt xs of
  [Abs.NTerminal cat] -> transCat cat
  _ -> error "anti-quotation rules must have exactly one non-terminal"
  where
    filt x  =case x of
      Abs.Terminal str   -> False
      Abs.NTerminal cat  -> True


toks x = case x of
 Abs.Token (Abs.Ident ident) reg           -> [ident]
 Abs.PosToken (Abs.Ident ident) reg        -> [ident]
 _                                         -> []

aqRules :: (String,String,String) -> [String] -> [Either Pragma TempRule]
aqRules (b,i,a) = concatMap aqRule where
  aqRule cat = map Right [
      (aqFun,(cat, Left [Right b,Left "AqToken"])),
      (aqFun,(cat, Left [Right (b++normCat cat), Left "AqToken"]))
      ]

aqFun = "$global_aq"

-- addSpecials :: (String,String,String) -> [Either Pragma Rule] -> [Either Pragma Rule]
-- addSpecials (b,i,a) rs = rs ++ concatMap special literals where

--  special aqs@('A':'Q':'_':s) = map Right [(aqs,(aqs,[Left s])),
--    (renameAq s,(rename s, [Right b,Left "AqToken"])),
--    (renameAqt s,(rename s, [Right (b++s), Left "AqToken"]))
--    ]
--  rules              = [r | (Right r) <- rs]
--  literals           = nub  [lit | xs <- map (snd . snd) rules,
--    (Left lit) <- xs,
--    elem lit (map rename specialCatsP)]
-- \end{hack}



separatorRules :: Abs.MinimumSize -> Abs.Cat -> String -> [TempRule]
separatorRules size c s = if null s then terminatorRules size c s else ifEmpty [
  ("(:[])", (cs,Left [Left c'])),
  ("(:)",   (cs,Left [Left c', Right s, Left cs]))
  ]
 where
   c' = transCat c
   cs = "[" ++ c' ++ "]"
   ifEmpty rs = if (size == Abs.MNonempty) then rs else (("[]", (cs,Left [])) : rs)

terminatorRules :: Abs.MinimumSize -> Abs.Cat -> String -> [TempRule]
terminatorRules size c s = [
  ifEmpty,
  ("(:)",   (cs,Left $ Left c' : s' [Left cs]))
  ]
 where
   c' = transCat c
   cs = "[" ++ c' ++ "]"
   s' its = if null s then its else (Right s : its)
   ifEmpty = if (size == Abs.MNonempty)
                then ("(:[])",(cs,Left $ [Left c'] ++ if null s then [] else [Right s]))
                else ("[]",   (cs,Left []))

coercionRules :: Abs.Ident -> Integer -> [TempRule]
coercionRules (Abs.Ident c) n =
   ("_", (c,               Left [Left (c ++ "1")])) :
  [("_", (c ++ show (i-1), Left [Left (c ++ show i)])) | i <- [2..n]] ++
  [("_", (c ++ show n,     Left [Right "(", Left c, Right ")"]))]


ebnfRules :: Abs.Ident -> [Abs.RHS] -> [TempRule]
ebnfRules (Abs.Ident c) rhss =
  [(mkFun k c rhs, (c, transRHS rhs)) | (k, rhs) <- zip [1 :: Int ..] rhss]
 where
   mkFun :: Int -> String -> Abs.RHS -> String
   mkFun k c i = case i of
     (Abs.RHS [Abs.Terminal s])  -> c' ++ "_" ++ mkName k s
     (Abs.RHS [Abs.NTerminal n]) -> c' ++ identCat (transCat n)
     _ -> c' ++ "_" ++ show k
   c' = c --- normCat c
   mkName k s = if all (\c -> isAlphaNum c || elem c "_'") s
                   then s else show k


transRHS :: Abs.RHS -> TempRHS
transRHS (Abs.RHS its) = Left $ map transItem its
transRHS (Abs.TRHS r)  = Right r



transItem :: Abs.Item -> Either Cat String
transItem x = case x of
 Abs.Terminal str   -> Right str
 Abs.NTerminal cat  -> Left (transCat cat)

transCat :: Abs.Cat -> Cat
transCat x = case x of
 Abs.ListCat cat  -> "[" ++ (transCat cat) ++ "]"
 Abs.IdCat id     -> transIdent id

transLabel :: Abs.Label -> Fun
transLabel y = let g = transLabelId y in g
 where
   transLabelId x = case x of
     Abs.Id id     -> transIdent id
     Abs.Wild      -> "_"
     Abs.ListE     -> "[]"
     Abs.ListCons  -> "(:)"
     Abs.ListOne   -> "(:[])"
     Abs.Aq (Abs.JIdent i)     -> "$" ++ transIdent i
     Abs.Aq _     -> "$"
--   transProf (Abs.ProfIt bss as) = 
--     ([map fromInteger bs | Abs.Ints bs <- bss], map fromInteger as)

transIdent :: Abs.Ident -> String
transIdent x = case x of
 Abs.Ident str  -> str

transArg :: Abs.Arg -> String
transArg (Abs.Arg x) = transIdent x

transExp :: Abs.Exp -> Exp
transExp e = case e of
    Abs.App x es    -> App (transIdent x) (map transExp es)
    Abs.Var x       -> App (transIdent x) []
    Abs.Cons e1 e2  -> cons e1 (transExp e2)
    Abs.List es     -> foldr cons nil es
    Abs.LitInt x    -> LitInt x
    Abs.LitDouble x -> LitDouble x
    Abs.LitChar x   -> LitChar x
    Abs.LitString x -> LitString x
  where
    cons e1 e2 = App "(:)" [transExp e1, e2]
    nil        = App "[]" []