{-# LANGUAGE PatternGuards #-}

-- TODO, use Language.Haskell
-- Doesn't handle string literals?

module Lambdabot.Plugin.Haskell.Pl.Parser (parsePF) where

import Lambdabot.Plugin.Haskell.Pl.Common

import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Expr
import Text.ParserCombinators.Parsec.Language
import qualified Text.ParserCombinators.Parsec.Token as T
import Control.Applicative ((<*))
import Data.List

-- is that supposed to be done that way?
tp :: T.TokenParser st
tp = T.makeTokenParser $ haskellStyle {
  reservedNames = ["if","then","else","let","in"]
}

parens :: Parser a -> Parser a
parens = T.parens tp

brackets :: Parser a -> Parser a
brackets = T.brackets tp

symbol :: String -> Parser String
symbol = T.symbol tp

modName :: CharParser st String
modName = do
  c <- oneOf ['A'..'Z']
  cs <- many (alphaNum <|> oneOf "_'")
  return (c:cs)

qualified :: CharParser st String -> CharParser st String
qualified p = do
  qs <- many $ try $ modName <* char '.' <* lookAhead (letter <|> oneOf opchars)
  nm <- p
  return $ intercalate "." (qs ++ [nm])

atomic :: Parser String
atomic = try (string "()") <|> try (show `fmap` T.natural tp) <|> qualified (T.identifier tp)

reserved :: String -> Parser ()
reserved = T.reserved tp

charLiteral :: Parser Char
charLiteral = T.charLiteral tp

stringLiteral :: Parser String
stringLiteral = T.stringLiteral tp

table :: [[Operator Char st Expr]]
table = addToFirst def $ map (map inf) operators where
  addToFirst y (x:xs) = ((y:x):xs)
  addToFirst _ _ = assert False bt

  def :: Operator Char st Expr
  def = Infix (try $ do
      name <- parseOp
      guard $ not $ isJust $ lookupOp name
      spaces
      return $ \e1 e2 -> App (Var Inf name) e1 `App` e2
    ) AssocLeft

  inf :: (String, (Assoc, Int)) -> Operator Char st Expr
  inf (name, (assoc, _)) = Infix (try $ do
      _ <- string name
      notFollowedBy $ oneOf opchars
      spaces
      let name' = if head name == '`'
                  then tail . reverse . tail . reverse $ name
                  else name
      return $ \e1 e2 -> App (Var Inf name') e1 `App` e2
    ) assoc


parseOp :: CharParser st String
parseOp = (between (char '`') (char '`') $ qualified (T.identifier tp))
  <|> try (do
    op <- qualified $ many1 $ oneOf opchars
    guard $ not $ op `elem` reservedOps
    return op)

pattern :: Parser Pattern
pattern = buildExpressionParser ptable ((PVar `fmap`
                       (    atomic
                        <|> (symbol "_" >> return "")))
                        <|> parens pattern)
    <?> "pattern" where
  ptable = [[Infix (symbol ":" >> return PCons) AssocRight],
            [Infix (symbol "," >> return PTuple) AssocNone]]

lambda :: Parser Expr
lambda = do
    _  <- symbol "\\"
    vs <- many1 pattern
    _  <- symbol "->"
    e  <- myParser False
    return $ foldr Lambda e vs
  <?> "lambda abstraction"

var :: Parser Expr
var = try (makeVar `fmap` atomic <|>
           parens (try unaryNegation <|> try rightSection
                   <|> try (makeVar `fmap` many1 (char ','))
                   <|> tuple) <|> list <|> (Var Pref . show) `fmap` charLiteral
                   <|> stringVar `fmap` stringLiteral)
        <?> "variable" where
  makeVar v | Just _ <- lookupOp v = Var Inf v -- operators always want to
                                               -- be infixed
            | otherwise            = Var Pref v
  stringVar :: String -> Expr
  stringVar str = makeList $ (Var Pref . show) `map` str

list :: Parser Expr
list = msum (map (try . brackets) plist) <?> "list" where
  plist = [
    foldr (\e1 e2 -> cons `App` e1 `App` e2) nil `fmap`
      (myParser False `sepBy` symbol ","),
    do e <- myParser False
       _ <- symbol ".."
       return $ Var Pref "enumFrom" `App` e,
    do e  <- myParser False
       _  <- symbol ","
       e' <- myParser False
       _  <- symbol ".."
       return $ Var Pref "enumFromThen" `App` e `App` e',
    do e  <- myParser False
       _  <- symbol ".."
       e' <- myParser False
       return $ Var Pref "enumFromTo" `App` e `App` e',
    do e   <- myParser False
       _   <- symbol ","
       e'  <- myParser False
       _   <- symbol ".."
       e'' <- myParser False
       return $ Var Pref "enumFromThenTo" `App` e `App` e' `App` e''
    ]

tuple :: Parser Expr
tuple = do
    elts <- myParser False `sepBy` symbol ","
    guard $ length elts /= 1
    let name = Var Pref $ replicate (length elts - 1) ','
    return $ foldl App name elts
  <?> "tuple"

unaryNegation :: Parser Expr
unaryNegation = do
    _ <- symbol "-"
    e <- myParser False
    return $ Var Pref "negate" `App` e
  <?> "unary negation"

rightSection :: Parser Expr
rightSection = do
    v <- Var Inf `fmap` parseOp
    spaces
    let rs e = flip' `App` v `App` e
    option v (rs `fmap` myParser False)
  <?> "right section"


myParser :: Bool -> Parser Expr
myParser b = lambda <|> expr b

expr :: Bool -> Parser Expr
expr b = buildExpressionParser table (term b) <?> "expression"

decl :: Parser Decl
decl = do
  f <- atomic
  args <- pattern `endsIn` symbol "="
  e <- myParser False
  return $ Define f (foldr Lambda e args)

letbind :: Parser Expr
letbind = do
  reserved "let"
  ds <- decl `sepBy` symbol ";"
  reserved "in"
  e <- myParser False
  return $ Let ds e

ifexpr :: Parser Expr
ifexpr = do
  reserved "if"
  p <- myParser False
  reserved "then"
  e1 <- myParser False
  reserved "else"
  e2 <- myParser False
  return $ if' `App` p `App` e1 `App` e2

term :: Bool -> Parser Expr
term b = application <|> lambda <|> letbind <|> ifexpr <|>
    (guard b >> (notFollowedBy (noneOf ")") >> return (Var Pref "")))
  <?> "simple term"

application :: Parser Expr
application = do
    e:es <- many1 $ var <|> parens (myParser True)
    return $ foldl App e es
  <?> "application"

endsIn :: Parser a -> Parser b -> Parser [a]
endsIn p end = do
  xs <- many p
  _  <- end
  return $ xs

input :: Parser TopLevel
input = do
  spaces
  tl <- try (do
      f    <- atomic
      args <- pattern `endsIn` symbol "="
      e    <- myParser False
      return $ TLD True $ Define f (foldr Lambda e args)
    ) <|> TLE `fmap` myParser False
  eof
  return tl

parsePF :: String -> Either String TopLevel
parsePF inp = case runParser input () "" inp of
    Left err -> Left $ show err
    Right e  -> Right $ mapTopLevel postprocess e


postprocess :: Expr -> Expr
postprocess (Var f v) = (Var f v)
postprocess (App e1 (Var Pref "")) = postprocess e1
postprocess (App e1 e2) = App (postprocess e1) (postprocess e2)
postprocess (Lambda v e) = Lambda v (postprocess e)
postprocess (Let ds e) = Let (mapDecl postprocess `map` ds) $ postprocess e where
  mapDecl :: (Expr -> Expr) -> Decl -> Decl
  mapDecl f (Define foo e') = Define foo $ f e'

