module Data.Makefile.Parse.Internal where
import Control.Monad
import Control.Applicative
import Data.Attoparsec.Text
import Data.Makefile
import qualified Data.Attoparsec.Text as Atto
import qualified Data.Text as T
import qualified Data.Text.IO as T
parseMakefile :: IO (Either String Makefile)
parseMakefile = Atto.parseOnly makefile <$> T.readFile "Makefile"
parseAsMakefile :: FilePath -> IO (Either String Makefile)
parseAsMakefile f = Atto.parseOnly makefile <$> T.readFile f
parseMakefileContents :: T.Text -> Either String Makefile
parseMakefileContents = Atto.parseOnly makefile
makefile :: Parser Makefile
makefile = Makefile <$> many' entry
entry :: Parser Entry
entry = many' emptyLine *> (assignment <|> rule)
assignment :: Parser Entry
assignment = do
varName <- variableName
assType <- assignmentType
varVal <- toEscapedLineEnd
return (Assignment assType varName varVal)
takeWhileM :: (Char -> Parser Bool) -> Parser T.Text
takeWhileM a = (T.pack . reverse) <$> go []
where
go cs = do
c <- Atto.anyChar
True <- a c
go (c:cs) <|> pure (c:cs)
variableName :: Parser T.Text
variableName = stripped $ takeWhileM go
where
go '+' = Atto.peekChar' >>= \case
'=' -> return False
_c -> return True
go '?' = Atto.peekChar' >>= \case
'=' -> return False
_c -> return True
go '!' = Atto.peekChar' >>= \case
'=' -> return False
_c -> return True
go ':' = return False
go '#' = return False
go '=' = return False
go _c = return True
assignmentType :: Parser AssignmentType
assignmentType =
("=" *> pure RecursiveAssign)
<|> ("+=" *> pure AppendAssign)
<|> ("?=" *> pure ConditionalAssign)
<|> ("!=" *> pure ShellAssign)
<|> (":=" *> pure SimpleAssign)
<|> ("::=" *> pure SimplePosixAssign)
rule :: Parser Entry
rule =
Rule
<$> target
<*> many' dependency
<*> many' (many' emptyLine *> command)
command :: Parser Command
command = Command <$> (Atto.char '\t' *> toEscapedLineEnd)
target :: Parser Target
target = Target <$> stripped (Atto.takeWhile (/= ':') <* Atto.char ':')
dependency :: Parser Dependency
dependency = Dependency <$> (sameLine <|> newLine)
where
sameLine =
Atto.takeWhile (== ' ')
*> Atto.takeWhile1 (`notElem` [' ', '\n', '#', '\\'])
newLine =
Atto.takeWhile (== ' ')
*> Atto.char '\\'
*> Atto.char '\n'
*> (sameLine <|> newLine)
comment :: Parser T.Text
comment = Atto.char '#' *> Atto.takeWhile (/= '\n')
nextLine :: Parser ()
nextLine = Atto.takeWhile (/= '\n') *> Atto.char '\n' *> pure ()
emptyLine :: Parser ()
emptyLine = Atto.takeWhile (`elem` ['\t', ' ']) *>
many' comment *>
Atto.char '\n' *>
pure ()
toLineEnd :: Parser T.Text
toLineEnd = Atto.takeWhile (`notElem` ['\n', '#'])
toEscapedLineEnd :: Parser T.Text
toEscapedLineEnd = (T.unwords . filter (not . T.null)) <$> go
where
go = do
l <- toLineEnd <* (void (Atto.char '\n') <|> pure ())
case T.stripSuffix "\\" l of
Nothing -> return [T.strip l]
Just l' -> (T.strip l':) <$> go
stripped :: Parser T.Text -> Parser T.Text
stripped = fmap T.strip