{-# LANGUAGE PatternGuards #-}

module Development.Make.Parse(parse) where

import Development.Make.Type
import Data.Char
import Data.List
import Data.Maybe


trim = dropWhile isSpace . reverse . dropWhile isSpace . reverse


parse :: FilePath -> IO Makefile
parse file = do
    src <- if file == "-" then getContents else readFile file
    return $ parseMakefile src


parseMakefile :: String -> Makefile
parseMakefile xs = Makefile $ rejoin $ concatMap parse $ map comments $ continuations $ lines xs
    where
        continuations (x:y:xs) | "\\" `isSuffixOf` x = continuations $ (init x ++ dropWhile isSpace y):xs
        continuations (x:xs) = x : continuations xs
        continuations [] = []

        comments = takeWhile (/= '#')

        parse x | all isSpace x = []
                | all isSpace $ take 1 x = [Right $ parseCommand $ trim x]
                | (a,b) <- break (== ';') x = Left (parseStmt a) : [Right $ parseCommand $ trim $ drop 1 b | b /= ""]

        rejoin (Left r@Rule{}:Right e:xs) = rejoin $ Left r{commands = commands r ++ [e]} : xs
        rejoin (Right e:xs) = error $ "Command must be under a rule: " ++ show e
        rejoin (Left r:xs) = r : rejoin xs
        rejoin [] = []


parseStmt :: String -> Stmt
parseStmt x
    | (a,'=':b) <- break (== '=') x, ':' `notElem` a =
        if "+" `isSuffixOf` a then Assign (trim $ init a) PlusEquals (parseExpr $ trim b)
        else if "?" `isSuffixOf` a then Assign (trim $ init a) QuestionEquals (parseExpr $ trim b)
        else Assign (trim a) Equals (parseExpr $ trim b)
    | (a,':':b) <- break (== ':') x = case b of
        '=':b -> Assign (trim a) ColonEquals (parseExpr $ trim b)
        ':':'=':b -> Assign (trim a) ColonEquals (parseExpr $ trim b)
        _ -> Rule (parseExpr $ trim a) (parseExpr $ trim $ fromMaybe b $ stripPrefix ":" b) []
    | otherwise = error $ "Invalid statement: " ++ x


parseExpr :: String -> Expr
parseExpr x = simplifyExpr $ Concat $ f x
    where
        f ('$':'$':x) = Lit "$" : f x
        f ('$':'(':xs) = case break (== ')') xs of
            (var,')':rest) -> parseVar var : f rest
            _ -> error $ "Couldn't find trailing `)' after " ++ xs
        f ('$':'{':xs) = case break (== '}') xs of
            (var,'}':rest) -> parseVar var : f rest
            _ -> error $ "Couldn't find trailing `}' after " ++ xs
        f ('$':x:xs) = Var [x] : f xs
        f (x:xs) = Lit [x] : f xs
        f [] = []


parseVar :: String -> Expr
parseVar x = Var x


parseCommand :: String -> Command
parseCommand = Expr . parseExpr
