{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Module : Disco.Module -- Copyright : (c) 2019 disco team (see LICENSE) -- Maintainer : byorgey@gmail.com -- -- SPDX-License-Identifier: BSD-3-Clause -- -- The 'ModuleInfo' record representing a disco module, and functions -- to resolve the location of a module on disk. ----------------------------------------------------------------------------- module Disco.Module where import Data.Data (Data) import GHC.Generics (Generic) import Control.Lens (Getting, foldOf, makeLenses, view) import Control.Monad (filterM) import Control.Monad.IO.Class (MonadIO (..)) import Data.Bifunctor (first) import Data.Map (Map) import qualified Data.Map as M import Data.Maybe (listToMaybe) import qualified Data.Set as S import System.Directory (doesFileExist) import System.FilePath (replaceExtension, ()) import Unbound.Generics.LocallyNameless (Alpha, Bind, Name, Subst, bind) import Unbound.Generics.LocallyNameless.Unsafe (unsafeUnbind) import Polysemy import Disco.AST.Surface import Disco.AST.Typed import Disco.Context import Disco.Extensions import Disco.Names import Disco.Pretty hiding ((<>)) import Disco.Typecheck.Erase (erase, erasePattern) import Disco.Typecheck.Util (TyCtx) import Disco.Types import Paths_disco ------------------------------------------------------------ -- ModuleInfo and related types ------------------------------------------------------------ -- | When loading a module, we could be loading it from code entered -- at the REPL, or from a standalone file. The two modes have -- slightly different behavior. data LoadingMode = REPL | Standalone -- | A definition consists of a name being defined, the types of any -- pattern arguments (each clause must have the same number of -- patterns), the type of the body of each clause, and a list of -- clauses. For example, -- -- @ -- f x (0,z) = 3*x + z > 5 -- f x (y,z) = z == 9 -- @ -- -- might look like @Defn f [Z, Z*Z] B [clause 1 ..., clause 2 ...]@ data Defn = Defn (Name ATerm) [Type] Type [Clause] deriving (Show, Generic, Alpha, Data, Subst Type) instance Pretty Defn where pretty (Defn x patTys ty clauses) = vcat $ prettyTyDecl x (foldr (:->:) ty patTys) : map (pretty . (x,) . eraseClause) clauses -- | A clause in a definition consists of a list of patterns (the LHS -- of the =) and a term (the RHS). For example, given the concrete -- syntax @f n (x,y) = n*x + y@, the corresponding 'Clause' would be -- something like @[n, (x,y)] (n*x + y)@. type Clause = Bind [APattern] ATerm eraseClause :: Clause -> Bind [Pattern] Term eraseClause b = bind (map erasePattern ps) (erase t) where (ps, t) = unsafeUnbind b -- | Type checking a module yields a value of type ModuleInfo which contains -- mapping from terms to their relavent documenation, a mapping from terms to -- properties, and a mapping from terms to their types. data ModuleInfo = ModuleInfo { _miName :: ModuleName , _miImports :: Map ModuleName ModuleInfo -- List of names declared by the module, in the order they occur , _miNames :: [QName Term] , _miDocs :: Ctx Term Docs , _miProps :: Ctx ATerm [AProperty] , _miTys :: TyCtx , _miTydefs :: TyDefCtx , _miTermdefs :: Ctx ATerm Defn , _miTerms :: [(ATerm, PolyType)] , _miExts :: ExtSet } deriving (Show) makeLenses ''ModuleInfo instance Semigroup ModuleInfo where -- | Two ModuleInfos -- are merged by joining their doc, type, type definition, and term -- contexts. The property context of the new module is the one -- obtained from the second module. The name of the new module is -- taken from the first. Definitions from later modules override -- earlier ones. Note that this function should really only be used -- for the special top-level REPL module. ModuleInfo n1 is1 ns1 d1 _ ty1 tyd1 tm1 tms1 es1 <> ModuleInfo _ is2 ns2 d2 p2 ty2 tyd2 tm2 tms2 es2 = ModuleInfo n1 (is1 <> is2) (ns1 <> ns2) (d2 <> d1) p2 (ty2 <> ty1) (tyd2 <> tyd1) (tm2 <> tm1) (tms1 <> tms2) (es1 <> es2) instance Monoid ModuleInfo where mempty = emptyModuleInfo mappend = (<>) -- | Get something from a module and its direct imports. withImports :: Monoid a => Getting a ModuleInfo a -> ModuleInfo -> a withImports l = view l <> foldOf (miImports . traverse . l) -- | Get the types of all names bound in a module and its direct imports. allTys :: ModuleInfo -> TyCtx allTys = withImports miTys -- | Get all type definitions from a module and its direct imports. allTydefs :: ModuleInfo -> TyDefCtx allTydefs = withImports miTydefs -- | The empty module info record. emptyModuleInfo :: ModuleInfo emptyModuleInfo = ModuleInfo REPLModule M.empty [] emptyCtx emptyCtx emptyCtx M.empty emptyCtx [] S.empty ------------------------------------------------------------ -- Module resolution ------------------------------------------------------------ -- | A data type indicating where we should look for Disco modules to -- be loaded. data Resolver = -- | Load only from the stdlib (standard lib modules) FromStdlib | -- | Load only from a specific directory (:load) FromDir FilePath | -- | Load from current working dir or stdlib (import at REPL) FromCwdOrStdlib | -- | Load from specific dir or stdlib (import in file) FromDirOrStdlib FilePath -- | Add the possibility of loading imports from the stdlib. For -- example, this is what we want to do after a user loads a specific -- file using `:load` (for which we will NOT look in the stdlib), -- but then we need to recursively load modules which it imports -- (which may either be in the stdlib, or the same directory as the -- `:load`ed module). withStdlib :: Resolver -> Resolver withStdlib (FromDir fp) = FromDirOrStdlib fp withStdlib r = r -- | Given a module resolution mode and a raw module name, relavent -- directories are searched for the file containing the provided -- module name. Returns Nothing if no module with the given name -- could be found. resolveModule :: Member (Embed IO) r => Resolver -> String -> Sem r (Maybe (FilePath, ModuleProvenance)) resolveModule resolver modname = do datadir <- liftIO getDataDir let searchPath = case resolver of FromStdlib -> [(datadir, Stdlib)] FromDir dir -> [(dir, Dir dir)] FromCwdOrStdlib -> [(datadir, Stdlib), (".", Dir ".")] FromDirOrStdlib dir -> [(datadir, Stdlib), (dir, Dir dir)] let fps = map (first ( replaceExtension modname "disco")) searchPath fexists <- liftIO $ filterM (doesFileExist . fst) fps return $ listToMaybe fexists