module Haskell.Parser where

import Control.Monad
import Data.Char
import Haskell.AST
import Text.Parsec
import Text.Parsec.String


parseAST :: String -> String -> Either ParseError AST
parseAST fname source = parse pAST fname source

pAST :: Parser AST
pAST = do
    whitespace
    tops <- many pToplevel
    eof
    return $ AST tops

pToplevel :: Parser Toplevel
pToplevel = TopDef <$> pDef

pDef :: Parser Def
pDef = do
    n <- pVariable
    args <- many pNameV
    symbolO "="
    ex <- pExpr
    symbolO ";"
    case args of
        [] -> return $ Def n ex
        _  -> return $ Def n (Lam args ex Nothing)

pExpr :: Parser Expr
pExpr = pLam <|> pCase <|> pApp
  where
    pSimpleExpr = choice [Num <$> pNum <*> return Nothing
                         ,Ref <$> pVariable <*> return Nothing
                         ,parens (pExpr `sepBy` symbolO ",") >>= \case
                            [ex] -> return ex
                            exs  -> return $ Tup exs Nothing]

    pLam = do
        symbolO "\\"
        args <- many1 pNameV
        symbolO "->"
        body <- pExpr
        return $ Lam args body Nothing

    pApp = many1 pSimpleExpr >>= \case
            []     -> undefined
            [e]    -> return e
            (e:es) -> return $ App e es Nothing

    pCase = do
        symbolW "case"
        e <- pExpr
        symbolW "of"
        arms <- braces (pCaseArm `sepBy` symbolO ";")
        return $ Case e arms Nothing

    pCaseArm = do
        pat <- pLargePat
        symbolO "->"
        ex <- pExpr
        return (pat, ex)

pSimplePat :: Parser Pat
pSimplePat = choice [symbolW "_" >> return PatAny
                    ,PatVar <$> pNameV
                    ,pNameT >>= \n -> return (PatCon n [])
                    ,parens (pLargePat `sepBy` symbolO ",") >>= \case
                        [pat] -> return pat
                        pats  -> return $ PatTup pats]

pLargePat :: Parser Pat
pLargePat = choice [PatCon <$> pNameT <*> many pSimplePat
                   ,pSimplePat]

pNum :: Parser Integer
pNum = (char '-' >> (negate <$> pPositive)) <|> pPositive
  where pPositive = read <$> many1 digit

pVariable :: Parser Name
pVariable = pName <|> try (parens pOperator)

pName :: Parser Name
pName = notReserved $ liftM2 (:) (satisfy isAlpha) pNameRest

pNameV :: Parser Name
pNameV = notReserved $ liftM2 (:) (satisfy isLower) pNameRest

pNameT :: Parser Name
pNameT = notReserved $ liftM2 (:) (satisfy isUpper) pNameRest

pNameRest :: Parser Name
pNameRest = many (satisfy $ \d -> isAlphaNum d || d `elem` "_'") <* aheadW

notReserved :: Parser Name -> Parser Name
notReserved p =
    try $ p >>= \n ->
        if n `elem` reservedWords then unexpected "reserved word" else return n

pOperator :: Parser String
pOperator = many1 (oneOf ":!#$%&*+./<=>?@\\^|-~") <* aheadO

parens :: Parser a -> Parser a
parens = between (symbolBare "(") (symbolBare ")")

braces :: Parser a -> Parser a
braces = between (symbolBare "{") (symbolBare "}")

symbolW :: String -> Parser ()
symbolW s = string s >> aheadW

symbolO :: String -> Parser ()
symbolO s = string s >> aheadO

symbolBare :: String -> Parser ()
symbolBare s = string s >> whitespace

aheadW :: Parser ()
aheadW = do
    void (lookAhead (space <|> satisfy (\d -> not (isAlphaNum d) && d `notElem` "_'"))) <|> eof
    whitespace

aheadO :: Parser ()
aheadO = do
    void (lookAhead (space <|> alphaNum <|> oneOf ")};")) <|> eof
    whitespace

whitespace :: Parser ()
whitespace = void $ many space

reservedWords :: [String]
reservedWords = ["case", "of", "class", "instance", "where", "let", "in"]