tools/PascalPreprocessor.hs
author sheepluva
Thu, 03 May 2012 12:33:08 +0200
changeset 7014 c0b32404ef74
parent 6964 6dde80ae7049
child 7038 d853e4385241
permissions -rw-r--r--
+ display lack of map in a nice way, don't generate bogus preview in the background * improve map selection behavior in online mode, so that players can load a missing map and have it correctly selected after pressing F5 without any rejoin, map-reselection. also doesn't cause "different maps generated" anymore if map is missing, instead the person lacking the map gets "invalid genmap" and drops out of game immediatly

module PascalPreprocessor where

import Text.Parsec
import Control.Monad.IO.Class
import Control.Monad
import System.IO
import qualified Data.Map as Map
import Data.Char


-- comments are removed
comment = choice [
        char '{' >> notFollowedBy (char '$') >> manyTill anyChar (try $ char '}') >> return ""
        , (try $ string "(*") >> manyTill anyChar (try $ string "*)") >> return ""
        , (try $ string "//") >> manyTill anyChar (try newline) >> return "\n"
        ]

initDefines = Map.fromList [("FPC", ""), ("PAS2C", "")]
        
preprocess :: String -> IO String
preprocess fn = do
    r <- runParserT (preprocessFile fn) (initDefines, [True]) "" ""
    case r of
         (Left a) -> do
             hPutStrLn stderr (show a)
             return ""
         (Right a) -> return a
    
    where
    preprocessFile fn = do
        f <- liftIO (readFile fn)
        setInput f
        preprocessor
        
    preprocessor, codeBlock, switch :: ParsecT String (Map.Map String String, [Bool]) IO String
    
    preprocessor = chainr codeBlock (return (++)) ""
    
    codeBlock = do
        s <- choice [
            switch
            , comment
            , char '\'' >> many (noneOf "'\n") >>= \s -> char '\'' >> return ('\'' : s ++ "'")
            , identifier >>= replace
            , noneOf "{" >>= \a -> return [a]
            ]
        (_, ok) <- getState
        return $ if and ok then s else ""

    --otherChar c = c `notElem` "{/('_" && not (isAlphaNum c)
    identifier = do
        c <- letter <|> oneOf "_"
        s <- many (alphaNum <|> oneOf "_")
        return $ c:s
            
    switch = do
        try $ string "{$"
        s <- choice [
            include
            , ifdef
            , if'
            , elseSwitch
            , endIf
            , define
            , unknown
            ]
        return s
        
    include = do
        try $ string "INCLUDE"
        spaces
        (char '"')
        fn <- many1 $ noneOf "\"\n"
        char '"'
        spaces
        char '}'
        f <- liftIO (readFile fn `catch` error ("File not found: " ++ fn))
        c <- getInput
        setInput $ f ++ c
        return ""

    ifdef = do
        s <- try (string "IFDEF") <|> try (string "IFNDEF")
        let f = if s == "IFNDEF" then not else id
        
        spaces
        d <- identifier
        spaces
        char '}'
        
        updateState $ \(m, b) ->
            (m, (f $ d `Map.member` m) : b)
      
        return ""

    if' = do
        s <- try (string "IF" >> notFollowedBy alphaNum)
        
        manyTill anyChar (char '}')
        --char '}'
        
        updateState $ \(m, b) ->
            (m, False : b)
      
        return ""

    elseSwitch = do
        try $ string "ELSE}"
        updateState $ \(m, b:bs) -> (m, (not b):bs)
        return ""
    endIf = do
        try $ string "ENDIF}"
        updateState $ \(m, b:bs) -> (m, bs)
        return ""
    define = do
        try $ string "DEFINE"
        spaces
        i <- identifier        
        d <- ((string ":=" >> return ())<|> spaces) >> many (noneOf "}")
        char '}'
        updateState $ \(m, b) -> (if and b then Map.insert i d m else m, b)
        return ""
    replace s = do
        (m, _) <- getState
        return $ Map.findWithDefault s s m
        
    unknown = do
        fn <- many1 $ noneOf "}\n"
        char '}'
        return $ "{$" ++ fn ++ "}"