packages feed

Eq-1.0: EqManips/Preprocessor.hs

module EqManips.Preprocessor ( processFile
                             , LangDef( .. )
                             , kindAssociation
                             ) where

import System.FilePath
import Data.List
import Control.Applicative
import Text.Parsec.Error( ParseError )

import EqManips.Algorithm.Eval
import EqManips.Algorithm.Utils
import EqManips.InputParser.EqCode
import EqManips.Renderer.Ascii
import EqManips.Renderer.Cpp
import EqManips.EvaluationContext
import EqManips.Types
import EqManips.Renderer.RenderConf

data LangDef = LangDef {
          initComm :: String
        , languageName :: String
        , endLineComm :: String
        , formater :: Formula TreeForm -> [String]
    }


voidLang :: LangDef
voidLang = LangDef
    { initComm = ""
    , endLineComm = ""
    , languageName = ""
    , formater = formulaTextTable defaultRenderConf
    }

shellLang, cppLang, cLang, ocamlLang, haskellLang :: LangDef
cppLang = voidLang { initComm = "//"
                   , endLineComm = ""
                   , formater = (\f -> [convertToCpp f])
                   , languageName = "C++ like"
                   }

shellLang = voidLang { initComm = "#"
                     , endLineComm = ""
                     , languageName = "Shell like"
                     }

cLang = voidLang { initComm = "/*", endLineComm = "*/"
                 , languageName = "C like"}

haskellLang = voidLang { initComm = "--", endLineComm = ""
                       , languageName = "Haskell"
                       }

ocamlLang = voidLang { initComm = "(*", endLineComm = "*)"
                     , languageName = "OCaml" }

kindAssociation :: [(String, LangDef)]
kindAssociation =
    [ (".c", cLang)
    , ( ".C", cppLang)
    , ( ".cc", cppLang)
    , ( ".cpp", cppLang)
    , ( ".h", cLang)
    , ( ".hpp", cppLang)
    , ( ".java", cppLang)
    , ( ".cs", cppLang)

    , ( ".hs", haskellLang)
    , ( ".lhs", haskellLang)
    , ( ".ml", ocamlLang)
    , ( ".mli", ocamlLang)

    , ( ".py", shellLang)
    , ( ".rb", shellLang)
    , ( ".sh", shellLang)
    , ( ".ps1", shellLang)
    ]

beginResultMark, endResultMark :: String
beginResultMark = "<@<"
endResultMark = ">@>"

------------------------------------------------------
----    Choosing weapons for preprocessing
------------------------------------------------------
processFile :: FilePath -> IO String
processFile inFile =
    case langOfFileName inFile of
         Nothing -> do print "Error unrecognized file type"
                       return ""
         Just lang -> do
             file <- readFile inFile
             let rez = concat . obtainEqResult 
                              . processLines lang $ lines file
             return rez

-- temp to avoid nasty warning
langOfFileName :: FilePath -> Maybe LangDef
langOfFileName name = lookup (takeExtension name) kindAssociation

processLines :: LangDef -> [String] -> EqContext [String]
processLines lang lst = do
    fileLines' <- fileLines
    return . reverse . map (++ "\n") $ concat fileLines'
    where initVal = (PState (begin lang) (pure []), pure [])

          updater ((PState f _), acc) l = (rez , neoList)
                where rez = f l
                      (PState _ lst') = rez
                      neoList = do
                          a <- lst'
                          acc' <- acc
                          return $ a : acc'

          (_,fileLines) = foldl' updater initVal lst

------------------------------------------------------
----    Processing file's lines
------------------------------------------------------
eatSpaces :: String -> (String, String)
eatSpaces = eat []
    where eat acc (' ':xs) = eat (' ':acc) xs
          eat acc ('\t':xs) = eat ('\t':acc) xs
          eat acc xs = (acc, xs)

stripSuffix :: String -> String -> String
stripSuffix suffix text
    | isSuffixOf suffix text = take (length text - length suffix) text
    | otherwise = text
    
removeBeginComment :: LangDef -> String -> Maybe (String, String)
removeBeginComment langDef line = do
        let (iniSpace, restLine) = eatSpaces line
        rest <- stripPrefix (initComm langDef) restLine
        return ( iniSpace ++ initComm langDef
               , stripSuffix (endLineComm langDef) rest)

-- | Grab a word from a string, returning it and
-- the tail.
word :: String -> (String, String)
word = w []
    where w acc [] = (reverse acc, [])
          w acc (' ':xs) = (reverse acc, xs)
          w acc ('\t':xs) = (reverse acc, xs)
          w acc (c:xs) = w (c:acc) xs

data PreprocessState = PState (String -> PreprocessState) (EqContext [String])
    
begin :: LangDef -> String -> PreprocessState
begin lang line =
    maybe (PState (begin lang) $ pure [line])
          (\(initSpace, line') -> rez initSpace . snd $ eatSpaces line')
          $ removeBeginComment lang line
        where rez initSpace ('E':'q':':':xs) =
                  let (command, rest) = word xs
                  in PState (gatherInput lang (initSpace, command, [rest])) $ pure [line]
              rez _ _ = PState (begin lang) $ pure [line]

              
gatherInput :: LangDef -> (String, String, [String]) -> String -> PreprocessState
gatherInput lang info@(initSpace, command, eqInfo) line = 
    maybe (PState (begin lang) $ produce lang info >>= pure . (line:))
          markSearch
          $ removeBeginComment lang line
        where markSearch (_,line') = 
                maybe (PState (gatherInput lang (initSpace, command, eqInfo ++ [line'])) 
                              $ pure [line])
                      (const $ PState (skip lang info) $ pure [])
                      $ stripPrefix beginResultMark line'

-- Prelude const :: a -> b -> a
-- Prelude maybe :: b -> (a -> b) -> Maybe a -> b
-- Data.List stripPrefix :: Eq a => [a] -> [a] -> Maybe [a]
skip :: LangDef -> (String, String, [String]) -> String -> PreprocessState
skip lang info line =
    maybe (PState (skip lang info) (pure []))
          endSearch
          $ removeBeginComment lang line
        where endSearch (_,line') =
                  if stripPrefix endResultMark line' == Nothing
                      then PState (skip lang info) (pure [])
                      else PState (begin lang) $ produce lang info

produce :: LangDef -> (String, String, [String]) -> EqContext [String]
produce lang (initSpace, command, eqData) =
   return $ endLine : process command mayParsedFormla ++ [preLine]
    where emark = endLineComm lang
          preLine = initSpace ++ beginResultMark ++ emark
          endLine = initSpace ++ endResultMark ++ emark

          mayParsedFormla = parseFormula $ concat eqData

          commentLine = initSpace ++ " "
          commentEnd = ' ' : emark

          spaceCount acc ' ' = 1 + acc
          spaceCount acc '\t' = 4 + acc
          spaceCount acc _ = acc

          unCommentedLine = replicate (foldl' spaceCount 0 initSpace) ' '

          process :: String -> Either ParseError (Formula ListForm) -> [String]
          process _ (Left err) = map (commentLine++) . lines $ show err
          process "format" (Right f) = printResult (treeIfyFormula f)
          process "eval" (Right f) = 
            let rez = performTransformation $ reduce f
            in case (errorList rez) of
                    [] -> reverse . map (unCommentedLine ++) 
                                  . formater lang 
                                  . treeIfyFormula
                                  $ result rez
                    errs@(_:_) -> concat
                        [ (commentLine ++ txt ++ commentEnd) : printResult form
                                    | (form, txt) <- errs ]
          process _ (Right _) = ["Unknown command " ++ command]

          printResult =
              reverse . map (\l -> commentLine ++ l ++ commentEnd)
                      . formulaTextTable defaultRenderConf