{-# OPTIONS_GHC -W #-}
module Parse.Parse (program, dependencies) where

import Control.Applicative ((<$>), (<*>))
import qualified Data.List as List
import qualified Data.Map as Map
import Text.Parsec hiding (newline,spaces)
import qualified Text.PrettyPrint as P

import qualified AST.Declaration as D
import qualified AST.Module as M
import qualified AST.Variable as Var
import Parse.Helpers
import Parse.Declaration (infixDecl)
import Parse.Module
import qualified Parse.Declaration as Decl
import Transform.Declaration (combineAnnotations)

freshDef = commitIf (freshLine >> (letter <|> char '_')) $ do
             freshLine
             Decl.declaration <?> "another datatype or variable definition"

decls = do d <- Decl.declaration <?> "at least one datatype or variable definition"
           (d:) <$> many freshDef

program :: OpTable -> String -> Either [P.Doc] M.ValidModule
program table src =
    do (M.Module names filePath exs ims parseDecls) <-
           setupParserWithTable table programParser src
       decls <-
           either (\err -> Left [P.text err]) Right (combineAnnotations parseDecls)
       return $ M.Module names filePath exs ims decls

programParser :: IParser M.SourceModule
programParser =
    do optional freshLine
       (names,exports) <-
           option (["Main"], Var.openListing) (moduleDef `followedBy` freshLine)
       is <- (do try (lookAhead $ reserved "import")
                 imports `followedBy` freshLine) <|> return []
       declarations <- decls
       optional freshLine ; optional spaces ; eof
       return $ M.Module names "" exports is declarations

dependencies :: String -> Either [P.Doc] (String, [String])
dependencies =
    let getName = List.intercalate "." . fst in
    setupParser $ do
      optional freshLine
      (,) <$> option "Main" (getName <$> moduleDef `followedBy` freshLine)
          <*> option [] (map fst <$> imports `followedBy` freshLine)

setupParserWithTable :: OpTable -> IParser a -> String -> Either [P.Doc] a
setupParserWithTable table p source =
    do localTable <- setupParser parseFixities source
       case Map.intersection table localTable of
         overlap | not (Map.null overlap) -> Left [ msg overlap ]
                 | otherwise -> 
                     flip setupParser source $ do
                       putState (Map.union table localTable)
                       p
    where
      msg overlap =
          P.vcat [ P.text "Parse error:"
                 , P.text $ "Overlapping definitions for infix operators: " ++
                          List.intercalate " " (Map.keys overlap)
                 ]

parseFixities = do
  decls <- onFreshLines (:) [] infixDecl
  return $ Map.fromList [ (op,(lvl,assoc)) | D.Fixity assoc lvl op <- decls ]
              
setupParser :: IParser a -> String -> Either [P.Doc] a
setupParser p source =
    case iParse p source of
      Right result -> Right result
      Left err -> Left [ P.text $ "Parse error at " ++ show err ]