{-# LANGUAGE CPP #-}
module Curry.CondCompile.Parser where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>), (<*>), (*>), (<*))
#endif
import Text.Parsec
import Curry.CondCompile.Type
type Parser a = Parsec String () a
program :: Parser Program
program = statement `sepBy` eol <* eof
statement :: Parser Stmt
statement = ifElse "if" condition If
<|> ifElse "ifdef" identifier IfDef
<|> ifElse "ifndef" identifier IfNDef
<|> define
<|> undef
<|> line
ifElse :: String -> Parser a -> (a -> [Stmt] -> [Elif] -> Else -> Stmt)
-> Parser Stmt
ifElse k p c = c <$> (try (many sp *> keyword k *> many1 sp) *> p <* many sp <* eol)
<*> many (statement <* eol)
<*> many (Elif <$> ((,) <$> (try (many sp *> keyword "elif" *> many1 sp) *> condition <* many sp <* eol)
<*> many (statement <* eol)))
<*> (Else <$> optionMaybe
(try (many sp *> keyword "else" *> many sp) *> eol *> many (statement <* eol)))
<* try (many sp <* keyword "endif" <* many sp)
define :: Parser Stmt
define = Define <$> (try (many sp *> keyword "define" *> many1 sp) *> identifier <* many1 sp)
<*> value <* many sp
undef :: Parser Stmt
undef = Undef <$> (try (many sp *> keyword "undef" *> many1 sp) *> identifier <* many sp)
line :: Parser Stmt
line = do
sps <- many sp
try $ ((char '#' <?> "") *> fail "unknown directive")
<|> ((Line . (sps ++)) <$> manyTill anyChar (try (lookAhead (eol <|> eof))))
keyword :: String -> Parser String
keyword = string . ('#' :)
condition :: Parser Cond
condition = (Defined <$> (try (string "defined(") *> many sp *> identifier <* many sp <* char ')'))
<|> (NDefined <$> (try (string "!defined(") *> many sp *> identifier <* many sp <* char ')'))
<|> (Comp <$> (identifier <* many sp) <*> operator <*> (many sp *> value) <?> "condition")
identifier :: Parser String
identifier = (:) <$> firstChar <*> many (firstChar <|> digit) <?> "identifier"
where firstChar = letter <|> char '_'
operator :: Parser Op
operator = choice [ Leq <$ try (string "<=")
, Lt <$ try (string "<")
, Geq <$ try (string ">=")
, Gt <$ try (string ">")
, Neq <$ try (string "!=")
, Eq <$ string "=="
] <?> "operator"
value :: Parser Int
value = fmap read (many1 digit)
eol :: Parser ()
eol = endOfLine *> return ()
sp :: Parser Char
sp = try $ lookAhead (eol *> unexpected "end of line" <?> "")
<|> space