SJW/src/Module.hs

78 lines
2.4 KiB
Haskell

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
module Module (
Environment
, Log
, Module(..)
, Modules(..)
, emptyEnvironment
, parse
, register
) where
import Context (CodePath(..), Context(..), Contextual, Path(..))
import Control.Monad.Except (MonadError(..))
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.RWS (MonadState, MonadWriter, asks, modify)
import Data.Attoparsec.Text (parseOnly)
import Data.Map (Map)
import qualified Data.Map as Map (empty, insert)
import Data.Set (Set)
import qualified Data.Set as Set (empty, insert)
import qualified Data.Text as Text (pack)
import Dependencies (Failable)
import Module.File (File(..))
import qualified Module.File (parser)
import Module.Imports (Reference(..), recurse)
import Prelude hiding (takeWhile)
import System.Directory (doesFileExist)
import System.FilePath ((</>), (<.>))
import Text.Printf (printf)
data Module = Module {
file :: Module.File.File
, dependencies :: Set Path
}
newtype Modules = Modules {
modules :: Map Path Module
}
emptyEnvironment :: Modules
emptyEnvironment = Modules {
modules = Map.empty
}
type Environment = MonadState Modules
type Log = MonadWriter [String]
register :: Environment m => Path -> Module -> m ()
register path module_ = modify $
\(Modules modules) -> Modules $ Map.insert path module_ modules
build :: File -> Module
build file = Module {file, dependencies}
where
dependencies = recurse pushDependency Set.empty $ imports file
pushDependency set _ ref = Set.insert (modulePath ref) set
parse :: (Contextual m, MonadIO m, Failable m) => Bool -> Path -> m Module
parse isMain path = do
searchPath <- asks codePaths
filePath <- find (CodePath [], searchPath) path
source <- Text.pack <$> liftIO (readFile filePath)
either throwError (return . build) $
parseOnly (Module.File.parser isMain) source
find :: (Failable m, MonadIO m) => (CodePath, CodePath) -> Path -> m FilePath
find (stack, CodePath []) path = throwError $
printf "Module %s not found in paths : %s" (show path) (show $ stack)
find (CodePath stackedDirs, CodePath (dir:otherDirs)) path@(Path components) = do
fileExists <- liftIO $ doesFileExist filePath
if fileExists
then return filePath
else find (CodePath (dir:stackedDirs), CodePath otherDirs) path
where
filePath = foldl (</>) dir components <.> "js"