This should be just refactoring and comments.

Hopefully, does not change any functionality.
This commit is contained in:
Iavor Diatchki 2024-02-12 16:07:38 -08:00
parent 8d5db4eca1
commit dfc95f0da4
2 changed files with 299 additions and 166 deletions

View File

@ -153,7 +153,7 @@ repl cryrc projectConfig replMode callStacks stopOnError begin =
CommandOk -> do
begin
case projectConfig of
Just config -> CommandOk <$ Project.run config
Just config -> CommandOk <$ Project.loadProject config
Nothing -> crySession replMode stopOnError
_ -> return status

View File

@ -3,15 +3,16 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NamedFieldPuns #-}
module Cryptol.Project
( Config
, loadConfig
, run
, loadProject
) where
import Control.Monad (void,unless)
import Control.Monad.Reader
import Control.Monad.State
import Data.Bifunctor
import qualified Data.ByteString as BS.Strict
@ -41,15 +42,18 @@ import Cryptol.Utils.Ident
import Cryptol.Utils.Logger
import Cryptol.Utils.PP as PP
--------------------------------------------------------------------------------
-- Project Configuration
data Config = Config
{ root :: FilePath
, modules :: [FilePath] }
instance FromYAML Config where
parseYAML = withMap "Config" \m -> do
root <- Text.unpack <$> m .:? "root" .!= "."
modules <- map Text.unpack <$> m .:? "modules" .!= ["."]
pure Config {..}
parseYAML = withMap "Config" \m ->
do root <- Text.unpack <$> m .:? "root" .!= "."
modules <- map Text.unpack <$> m .:? "modules" .!= ["."]
pure Config { root, modules }
data ConfigLoadError = ConfigLoadError FilePath ConfigLoadErrorInfo
@ -69,6 +73,7 @@ instance PP ConfigLoadError where
where
topMsg = "Error loading project configuration" <+> text path PP.<.> ":"
-- | Parse project configuration.
loadConfig :: FilePath -> IO (Either ConfigLoadError Config)
loadConfig path = do
isDir <- doesDirectoryExist path
@ -77,16 +82,18 @@ loadConfig path = do
file <- BS.Lazy.fromStrict <$> BS.Strict.readFile filePath
first (ConfigLoadError filePath) <$>
case decode1 file of
Left (pos, err) -> pure $ Left $ ConfigParseError file (pos, err)
Right config -> first SetRootFailed <$> tryIOError do
setCurrentDirectory (takeDirectory filePath FP.</> root config)
pure config
Left (pos, err) -> pure (Left (ConfigParseError file (pos, err)))
Right config ->
first SetRootFailed <$>
tryIOError
do setCurrentDirectory (takeDirectory filePath FP.</> root config)
pure config
data FullFingerprint = FullFingerprint
{ moduleFingerprint :: Fingerprint
, includeFingerprints :: Map FilePath Fingerprint
, foreignFingerprints :: Set Fingerprint }
deriving (Eq, Show, Read)
--------------------------------------------------------------------------------
-- The Loading Monad
type LoadM = ReaderT LoadConfig (StateT LoadState ModuleM)
data ScanStatus
= LoadedChanged
@ -96,15 +103,60 @@ data ScanStatus
data LoadState = LoadState
{ findModuleCache :: Map (ModName, [FilePath]) ModulePath
, scanned :: Map ModulePath (FullFingerprint, ScanStatus) }
-- ^ Map (module name, search path) -> module path
type LoadM = StateT LoadState ModuleM
, scanned :: Map ModulePath (FullFingerprint, ScanStatus)
}
data FullFingerprint = FullFingerprint
{ moduleFingerprint :: Fingerprint
, includeFingerprints :: Map FilePath Fingerprint
, foreignFingerprints :: Set Fingerprint }
deriving (Eq, Show, Read)
data LoadConfig = LoadConfig
{ canonRoot :: FilePath
, loadCache :: LoadCache
}
doModule :: M.ModuleM a -> LoadM a
doModule m = lift (lift m)
doIO :: IO a -> LoadM a
doIO m = doModule (M.io m)
liftCallback :: (forall a. ModuleM a -> ModuleM a) -> LoadM b -> LoadM b
liftCallback f x = StateT (f . runStateT x)
liftCallback f x =
do r <- ask
s <- get
(a,s1) <- doModule (f (runStateT (runReaderT x r) s))
put s1
pure a
runLoadM :: Config -> LoadM () -> M.ModuleM LoadState
runLoadM cfg m =
do loadCfg <-
M.io
do path <- canonicalizePath (root cfg)
cache <- loadLoadCache
pure LoadConfig { canonRoot = path, loadCache = cache }
let loadState = LoadState { findModuleCache = Map.empty
, scanned = Map.empty
}
execStateT (runReaderT m loadCfg) loadState
insertScanned :: ModulePath -> FullFingerprint -> ScanStatus -> LoadM ()
insertScanned mpath fp status =
modify' \ls -> ls { scanned = Map.insert mpath (fp, status) (scanned ls) }
--------------------------------------------------------------------------------
-- The load cache. This is what persists across invocations.
newtype LoadCache = LoadCache
{ cacheFingerprints :: Map ModulePath FullFingerprint }
{ cacheFingerprints :: Map ModulePath FullFingerprint
}
deriving (Show, Read)
metaDir, loadCachePath :: FilePath
@ -114,150 +166,231 @@ loadCachePath = metaDir FP.</> "loadcache"
emptyLoadCache :: LoadCache
emptyLoadCache = LoadCache { cacheFingerprints = Map.empty }
run :: Config -> REPL ()
run Config {..} = do
canonRoot <- REPL.io $ canonicalizePath root
minp <- getModuleInput
(res, warnings) <- REPL.io $ runModuleM minp do
loadCache <- M.io $
(fromMaybe emptyLoadCache . readMaybe <$> readFile loadCachePath)
`catchIOError` \_ -> pure emptyLoadCache
let scanFromPath :: FilePath -> LoadM ()
scanFromPath fpath =
liftCallback (withPrependedSearchPath [takeDirectory fpath]) do
foundFPath <- lift $ M.findFile fpath
mpath <- lift $ InFile <$> M.io (canonicalizePath foundFPath)
void $ scan Nothing mpath
scan :: Maybe ImportSource -> ModulePath -> LoadM ScanStatus
scan mbIsrc mpath = do
ls <- get
case Map.lookup mpath (scanned ls) of
Just (_, status) -> pure status
Nothing -> liftCallback (errorInFile mpath) do
lift $ withLogger logPutStrLn $
"Scanning " ++ case mpath of
InFile p -> makeRelative canonRoot p
InMem l _ -> l
(fi, pmDeps) <- lift $ findDepsOf' mpath
foreignFps <- getForeignFps $ fiForeignDeps fi
let newFp = FullFingerprint
{ moduleFingerprint = fiFingerprint fi
, includeFingerprints = fiIncludeDeps fi
, foreignFingerprints = foreignFps }
loadChanged = LoadedChanged <$ load mbIsrc mpath
(fiFingerprint fi) (fiIncludeDeps fi) pmDeps
status <- case Map.lookup mpath (cacheFingerprints loadCache) of
Just oldFp
| oldFp == newFp -> do
let currentModNames = map (thing . mName . fst) pmDeps
depStatuses <- traverse scanFromImportSource $
filter ((`notElem` currentModNames) . importedModule) $
concatMap snd pmDeps
if LoadedChanged `elem` depStatuses
then loadChanged
else pure NotLoadedNotChanged
_ -> loadChanged
insertScanned mpath newFp status
pure status
scanFromImportSource :: ImportSource -> LoadM ScanStatus
scanFromImportSource isrc = do
mpath <- findModule' isrc
scan (Just isrc) mpath
parseAndLoad :: ImportSource -> ModulePath -> LoadM (Fingerprint, Map FilePath Fingerprint, [FileInfo])
parseAndLoad isrc mpath = do
(newModFp, newIncFps, pms) <- lift $ parseModule mpath
fis <- load (Just isrc) mpath newModFp newIncFps $
map (\pm -> let pm' = addPrelude pm in (pm', findDeps pm')) pms
pure (newModFp, newIncFps, fis)
load mbIsrc mpath newModFp newIncFps pmDeps =
for pmDeps \(pm, deps) -> do
let isrc = fromMaybe (FromModule (thing (mName pm))) mbIsrc
liftCallback (loading isrc) do
traverse_ loadFromImportSource deps
lift $ fmap snd $
doLoadModule True False isrc mpath newModFp newIncFps pm $
Set.fromList $ map importedModule deps
getForeignFps :: Map FilePath Bool -> LoadM (Set Fingerprint)
getForeignFps fsrcPaths = lift $
loadLoadCache :: IO LoadCache
loadLoadCache =
do txt <- readFile loadCachePath
pure (fromMaybe emptyLoadCache (readMaybe txt))
`catchIOError` \_ -> pure emptyLoadCache
saveLoadCache :: LoadCache -> IO ()
saveLoadCache cache =
do createDirectoryIfMissing False metaDir
writeFile loadCachePath (show cache)
--------------------------------------------------------------------------------
-- | Load a project.
-- Note that this does not update the Cryptol environment, it only updates
-- the project cache.
-- XXX: we probably want more functionality later on.
loadProject :: Config -> REPL ()
loadProject cfg =
do minp <- getModuleInput
(res, warnings) <-
REPL.io $ runModuleM minp
do ls <- runLoadM cfg (for_ (modules cfg) loadPath)
M.io $
saveLoadCache LoadCache { cacheFingerprints = fst <$> scanned ls }
printModuleWarnings warnings
case res of
Left err ->
do names <- mctxNameDisp <$> REPL.getFocusedEnv
rPrint (pp (ModuleSystemError names err))
Right _ -> rPutStrLn "all loaded!"
-- | Search for .cry files in the given path.
loadPath :: FilePath -> LoadM ()
loadPath path =
do isDir <- doIO (doesDirectoryExist path)
if isDir
then
doIO (tryIOError (getDirectoryContents path)) >>=
\case
Left err -> doModule (otherIOError path err)
Right entries -> for_ entries \entry -> loadPath (path FP.</> entry)
else
case takeExtension path of
".cry" -> scanFromPath path
_ -> pure ()
-- | Load a particular file path.
scanFromPath :: FilePath -> LoadM ()
scanFromPath fpath =
liftCallback (withPrependedSearchPath [takeDirectory fpath])
do foundFPath <- doModule (M.findFile fpath)
mpath <- doIO (InFile <$> canonicalizePath foundFPath)
void (scan Nothing mpath)
-- | Scan from a particular import source.
scanFromImportSource :: ImportSource -> LoadM ScanStatus
scanFromImportSource isrc =
do mpath <- findModule' isrc
scan (Just isrc) mpath
-- | This does the actual scanning work.
scan :: Maybe ImportSource -> ModulePath -> LoadM ScanStatus
scan mbIsrc mpath =
do ls <- get
case Map.lookup mpath (scanned ls) of
Just (_, status) -> pure status
Nothing ->
liftCallback (errorInFile mpath)
do root <- asks canonRoot
-- XXX: Use the logger from REPL?
doModule $ withLogger logPutStrLn $
"Scanning " ++ case mpath of
InFile p -> makeRelative root p
InMem l _ -> l
(fi, pmDeps) <- doModule (findDepsOf' mpath)
foreignFps <- doModule (getForeignFps (fiForeignDeps fi))
let newFp =
FullFingerprint
{ moduleFingerprint = fiFingerprint fi
, includeFingerprints = fiIncludeDeps fi
, foreignFingerprints = foreignFps
}
loadChanged =
LoadedChanged <$
load mbIsrc mpath
(fiFingerprint fi) (fiIncludeDeps fi) pmDeps
fps <- asks (cacheFingerprints . loadCache)
status <-
case Map.lookup mpath fps of
Just oldFp | oldFp == newFp ->
do let currentModNames = map (thing . mName . fst) pmDeps
depStatuses <-
traverse scanFromImportSource
[ src
| (_,srcs) <- pmDeps
, src <- srcs
, importedModule src `notElem` currentModNames
]
if LoadedChanged `elem` depStatuses
then loadChanged
else pure NotLoadedNotChanged
_ -> loadChanged
insertScanned mpath newFp status
pure status
parseAndLoad ::
ImportSource ->
ModulePath ->
LoadM (Fingerprint, Map FilePath Fingerprint, [FileInfo])
parseAndLoad isrc mpath =
do (newModFp, newIncFps, pms) <- doModule (parseModule mpath)
fis <- load (Just isrc) mpath newModFp newIncFps
[ (pm', findDeps pm')
| pm <- pms
, let pm' = addPrelude pm
]
pure (newModFp, newIncFps, fis)
{- | Process stuff we parsed from a file.
The reason we have a list of modules here, rather than just one,
is that a single file may contain multiple modules, due to to desugaring
of various things in the module system. -}
load ::
Maybe ImportSource ->
ModulePath ->
Fingerprint ->
Map FilePath Fingerprint ->
[(ModuleG ModName PName, [ImportSource])] ->
LoadM [FileInfo]
load mbIsrc mpath newModFp newIncFps pmDeps =
for pmDeps \(pm, deps) ->
do let isrc = fromMaybe (FromModule (thing (mName pm))) mbIsrc
liftCallback (loading isrc)
do traverse_ loadFromImportSource deps
doModule $ fmap snd $
doLoadModule True False isrc mpath newModFp newIncFps pm $
Set.fromList (map importedModule deps)
loadFromImportSource :: ImportSource -> LoadM ()
loadFromImportSource isrc =
do mpath <- findModule' isrc
let unlessLoaded k =
do loaded <- doModule (M.isLoadedStrict (importedModule isrc) mpath)
unless loaded k
liftCallback (errorInFile mpath)
do ls' <- get
case Map.lookup mpath (scanned ls') of
Just (fp, status) ->
case status of
LoadedChanged -> pure ()
LoadedNotChanged -> pure ()
NotLoadedNotChanged ->
unlessLoaded
do _ <- parseAndLoad isrc mpath
insertScanned mpath fp LoadedNotChanged
-- The file has not been fully loaded yet, but the individual
-- module within the file might
Nothing ->
unlessLoaded
do (newModFp, newIncFps, fis) <- parseAndLoad isrc mpath
foreignFps <- doModule $ getForeignFps $
Map.unionsWith (||) (map fiForeignDeps fis)
let newFp = FullFingerprint
{ moduleFingerprint = newModFp
, includeFingerprints = newIncFps
, foreignFingerprints = foreignFps
}
fps <- asks (cacheFingerprints . loadCache)
insertScanned mpath newFp
case Map.lookup mpath fps of
Just oldFp | oldFp == newFp -> LoadedNotChanged
_ -> LoadedChanged
-- | Module path for the given import
findModule' :: ImportSource -> LoadM ModulePath
findModule' isrc =
do let mname = modNameToNormalModName (importedModule isrc)
searchPath <- doModule M.getSearchPath
ls <- get
case Map.lookup (mname, searchPath) (findModuleCache ls) of
Just mpath -> pure mpath
Nothing ->
do modLoc <- doModule (findModule mname)
mpath <- case modLoc of
InFile path -> InFile <$> doIO (canonicalizePath path)
InMem l c -> pure (InMem l c)
put ls { findModuleCache = Map.insert (mname, searchPath)
mpath (findModuleCache ls) }
pure mpath
-- | Fingerprints of foreign declarations
getForeignFps :: Map FilePath Bool -> M.ModuleM (Set Fingerprint)
getForeignFps fsrcPaths =
Set.fromList <$>
for (Map.keys fsrcPaths) \fsrcPath ->
M.io (fingerprintFile fsrcPath) >>=
\case
Left ioe -> otherIOError fsrcPath ioe
Right fp -> pure fp
-- XXX: exists?
Set.fromList <$> for (Map.keys fsrcPaths) \fsrcPath ->
M.io (fingerprintFile fsrcPath) >>= \case
Left ioe -> otherIOError fsrcPath ioe
Right fp -> pure fp
insertScanned mpath fp status = modify' \ls -> ls
{ scanned = Map.insert mpath (fp, status) (scanned ls) }
findModule' :: ImportSource -> LoadM ModulePath
findModule' isrc = do
let mname = modNameToNormalModName $ importedModule isrc
searchPath <- lift M.getSearchPath
ls <- get
case Map.lookup (mname, searchPath) (findModuleCache ls) of
Just mpath -> pure mpath
Nothing -> do
mpath <- lift $ findModule mname >>= \case
InFile path -> InFile <$> M.io (canonicalizePath path)
InMem l c -> pure $ InMem l c
put ls
{ findModuleCache =
Map.insert (mname, searchPath) mpath (findModuleCache ls) }
pure mpath
loadFromImportSource :: ImportSource -> LoadM ()
loadFromImportSource isrc = do
mpath <- findModule' isrc
liftCallback (errorInFile mpath) do
ls' <- get
case Map.lookup mpath (scanned ls') of
Just (fp, status) ->
case status of
LoadedChanged -> pure ()
LoadedNotChanged -> pure ()
NotLoadedNotChanged -> do
loaded <- lift $ M.isLoadedStrict (importedModule isrc) mpath
unless loaded do
_ <- parseAndLoad isrc mpath
insertScanned mpath fp LoadedNotChanged
Nothing -> do
-- The file has not been fully loaded yet, but the individual
-- module within the file might
loaded <- lift $ M.isLoadedStrict (importedModule isrc) mpath
unless loaded do
(newModFp, newIncFps, fis) <- parseAndLoad isrc mpath
foreignFps <- getForeignFps $
Map.unionsWith (||) (map fiForeignDeps fis)
let newFp = FullFingerprint
{ moduleFingerprint = newModFp
, includeFingerprints = newIncFps
, foreignFingerprints = foreignFps }
insertScanned mpath newFp
case Map.lookup mpath (cacheFingerprints loadCache) of
Just oldFp
| oldFp == newFp -> LoadedNotChanged
_ -> LoadedChanged
ls <- flip execStateT
LoadState { findModuleCache = Map.empty, scanned = Map.empty } $
for_ modules \p -> do
let loadPath path = do
isDir <- lift $ M.io $ doesDirectoryExist path
if isDir
then lift (M.io (tryIOError (listDirectory path))) >>= \case
Left err -> lift $ otherIOError path err
Right entries -> for_ entries \case
'.':_ -> pure ()
entry -> loadPath (path FP.</> entry)
else case takeExtension path of
".cry" -> scanFromPath path
_ -> pure ()
loadPath p
let newLoadCache = LoadCache
{ cacheFingerprints = Map.map fst (scanned ls) }
M.io do
createDirectoryIfMissing False metaDir
writeFile loadCachePath $ show newLoadCache
printModuleWarnings warnings
case res of
Left err -> do
names <- mctxNameDisp <$> REPL.getFocusedEnv
rPrint $ pp $ ModuleSystemError names err
Right _ -> do
rPutStrLn "all loaded!"