module Program where import Term ( Range (Range, start), Identifier (..) ) import Module ( Module ) import qualified Module import qualified Log import qualified Exception import qualified Control.Monad.Exception.Synchronous as Exc import Control.Monad.Trans.Class ( lift ) import qualified Control.Exception as ExcBase import Text.ParserCombinators.Parsec ( parse ) import qualified Text.ParserCombinators.Parsec.Pos as Pos import qualified Text.ParserCombinators.Parsec.Error as PErr import System.Directory ( doesFileExist ) import System.FilePath ( () ) import qualified System.IO.Error as Err import qualified System.FilePath as FP import qualified Data.Traversable as Trav import qualified Data.Map as M import Control.Monad ( foldM ) import Data.List.HT ( chop ) data Program = Program { modules :: M.Map Module.Name Module , functions :: Module.FunctionDeclarations } -- deriving (Show) empty :: Program empty = Program { modules = M.empty, functions = M.empty } add_module :: Module -> Program -> Exc.Exceptional Exception.Message Program add_module m p = fmap (\newFuncs -> p { modules = M.insert ( Module.name m ) m $ modules p, functions = newFuncs }) $ union_functions ( Module.functions m ) $ M.difference (functions p) (M.findWithDefault M.empty ( Module.name m ) ( fmap Module.functions $ modules p )) union_functions :: Module.FunctionDeclarations -> Module.FunctionDeclarations -> Exc.Exceptional Exception.Message Module.FunctionDeclarations union_functions m0 m1 = let f = M.mapWithKey (\nm rs -> (nm, Exc.Success rs)) in Trav.sequenceA . fmap snd $ M.unionWith (\(n0,_) (n1,_) -> (n0, Exc.Exception $ Exception.Message Exception.Parse (range n0) ("duplicate definition of " ++ show n0 ++ " in " ++ (show $ Pos.sourceName $ start $ range n0) ++ " and " ++ (show $ Pos.sourceName $ start $ range n1)))) (f m0) (f m1) -- | load from disk, with import chasing chase :: [ FilePath ] -> Module.Name -> Exc.ExceptionalT Exception.Message IO Program chase dirs n = chaser dirs empty n chaser :: [ FilePath ] -> Program -> Module.Name -> Exc.ExceptionalT Exception.Message IO Program chaser dirs p n = do lift $ Log.put $ "chasing " ++ Module.tellName n case M.lookup n ( modules p ) of Just _ -> lift $ do Log.put $ "module is already loaded" return p Nothing -> let nn = Module.deconsName n in load dirs p nn =<< chaseFile dirs ( FP.addExtension (FP.joinPath $ chop ('.'==) nn) "hs" ) load :: [ FilePath ] -> Program -> String -> FilePath -> Exc.ExceptionalT Exception.Message IO Program load dirs p n ff = do parseResult <- Exc.mapExceptionT (\e -> Exception.Message Exception.InOut (dummyRange ff) (Err.ioeGetErrorString e)) $ Exc.fromEitherT $ ExcBase.try $ fmap (\s -> parse (Module.parseUntilEOF ff s) n s) $ readFile ff case parseResult of Left err -> Exc.throwT (messageFromParserError err) Right m -> do lift $ Log.put $ show m pNew <- Exc.ExceptionalT $ return $ add_module m p foldM ( chaser dirs ) pNew $ map Module.source $ Module.imports m -- | look for file, trying to append its name to the directories in the path, -- in turn. Will fail if file is not found. chaseFile :: [FilePath] -> FilePath -> Exc.ExceptionalT Exception.Message IO FilePath chaseFile dirs f = foldr (\dir go -> do let ff = dir f e <- lift $ doesFileExist ff if e then lift $ do Log.put $ unwords [ "found at location", ff ] return ff else go) (Exc.throwT $ Exception.Message Exception.InOut (dummyRange f) (unwords [ "module", "not", "found:", f ])) dirs dummyRange :: String -> Range dummyRange f = let pos = Pos.initialPos f in Range pos pos messageFromParserError :: PErr.ParseError -> Exception.Message messageFromParserError err = Exception.Message Exception.Parse (let p = PErr.errorPos err in Range p (Pos.updatePosChar p ' ')) (removeLeadingNewline $ PErr.showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input" $ PErr.errorMessages err) removeLeadingNewline :: String -> String removeLeadingNewline ('\n':str) = str removeLeadingNewline str = str