module Program where import Term ( Identifier ) import Module ( Module ) import qualified Term import qualified Module import qualified Log import qualified Exception import qualified ControlsBase as Controls import qualified Control.Monad.Exception.Synchronous as Exc import Control.Monad.Trans.Class ( lift ) import qualified Control.Exception as ExcBase import qualified System.IO.Strict as StrictIO import System.Directory ( doesFileExist ) import System.FilePath ( () ) import qualified System.IO.Error as Err import qualified Data.Foldable as Fold import qualified Data.Traversable as Trav import qualified Data.Map as M import qualified Data.Set as S import Control.Monad ( foldM, liftM4 ) data Program = Program { modules :: M.Map Module.Name Module , functions :: Module.FunctionDeclarations , constructors :: Module.ConstructorDeclarations , controls :: Controls.Assignments , controlValues :: Controls.Values } -- deriving (Show) empty :: Program empty = Program { modules = M.empty, functions = M.empty, constructors = S.empty, controls = M.empty, controlValues = Controls.emptyValues } singleton :: Module -> Program singleton m = Program { modules = M.singleton (Module.name m) m, functions = Module.functions m, constructors = Module.constructors m, controls = Module.controls m, controlValues = Controls.emptyValues } {- | add a module The module must not be present in the program, otherwise this function returns an invalid 'Program'. -} addModule :: Module -> Program -> Exc.Exceptional Exception.Message Program addModule m p = liftM4 ( Program ( M.insert ( Module.name m ) m ( modules p ) ) ) ( unionDecls ( Module.functions m ) ( functions p ) ) ( fmap M.keysSet $ unionDecls ( mapFromSet $ Module.constructors m ) ( mapFromSet $ constructors p ) ) ( Controls.union ( Controls.updateValues ( controlValues p ) ( Module.controls m ) ) ( controls p ) ) ( return $ controlValues p ) removeModule :: Module.Name -> Program -> Program removeModule nm p = case M.lookup nm $ modules p of Nothing -> p Just m -> Program { modules = M.delete nm $ modules p, functions = M.difference ( functions p ) ( Module.functions m ), constructors = S.difference ( constructors p ) ( Module.constructors m ), controls = M.difference ( controls p ) ( Module.controls m ), controlValues = controlValues p } replaceModule :: Module -> Program -> Exc.Exceptional Exception.Message Program replaceModule m p = addModule m $ removeModule (Module.name m) p mapFromSet :: Ord a => S.Set a -> M.Map a () mapFromSet = M.fromAscList . map (flip (,) ()) . S.toAscList unionDecls :: M.Map Identifier a -> M.Map Identifier a -> Exc.Exceptional Exception.Message ( M.Map Identifier a ) unionDecls 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 (Term.range n0) ("duplicate definition of " ++ show n0 ++ " in " ++ (Module.deconsName $ Module.nameFromIdentifier n0) ++ " and " ++ (Module.deconsName $ Module.nameFromIdentifier n1)))) (f m0) (f m1) minimize :: Module.Name -> Program -> (S.Set Module.Name, Program) minimize seed p = let trace modName ms = if S.member modName ms then foldl (flip trace) (S.delete modName ms) $ maybe [] (map Module.source . Module.imports) $ M.lookup modName (modules p) else ms removed = trace seed $ M.keysSet $ modules p in (removed, Fold.foldl (flip removeModule) p removed) -- | 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 -> do path <- chaseFile dirs ( Module.makeFileName n ) load dirs ( Module.deconsName n ) path p chaseMany :: [ FilePath ] -> [ Module.Name ] -> Program -> Exc.ExceptionalT Exception.Message IO Program chaseMany dirs names p = foldM ( chaser dirs ) p names chaseImports :: [ FilePath ] -> Module.Module -> Program -> Exc.ExceptionalT Exception.Message IO Program chaseImports dirs = chaseMany dirs . map Module.source . Module.imports load :: [ FilePath ] -> String -> FilePath -> Program -> Exc.ExceptionalT Exception.Message IO Program load dirs n ff p = do content <- Exc.mapExceptionT (\e -> Exception.Message Exception.InOut (Exception.dummyRange ff) (Err.ioeGetErrorString e)) $ Exc.fromEitherT $ ExcBase.try $ StrictIO.readFile ff m <- Exception.lift $ Module.parse n ff content lift $ Log.put $ show m chaseImports dirs m =<< Exception.lift ( addModule m p ) -- | 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 (Exception.dummyRange f) (unwords [ "module", "not", "found:", f ])) dirs