swarm-0.4: src/Swarm/Game/World/Load.hs
{-# LANGUAGE DataKinds #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Loading world descriptions from `worlds/*.world`.
module Swarm.Game.World.Load where
import Control.Algebra (Has)
import Control.Arrow (left)
import Control.Carrier.Accum.FixedStrict (Accum)
import Control.Carrier.Lift (Lift, sendIO)
import Control.Carrier.Reader (runReader)
import Control.Effect.Throw (Throw, liftEither)
import Data.Map qualified as M
import Data.Maybe (catMaybes)
import Data.Sequence (Seq)
import Data.Text (Text)
import Swarm.Game.Entity (EntityMap)
import Swarm.Game.Failure (Asset (..), AssetData (..), LoadingFailure (..), SystemFailure (..))
import Swarm.Game.ResourceLoading (getDataDirSafe)
import Swarm.Game.World.Parse (parseWExp, runParser)
import Swarm.Game.World.Typecheck
import Swarm.Language.Pretty (prettyText)
import Swarm.Util (acquireAllWithExt)
import Swarm.Util.Effect (throwToWarning, withThrow)
import System.FilePath (dropExtension, joinPath, splitPath)
import Witch (into)
-- | Load and typecheck all world descriptions from `worlds/*.world`.
-- Emit a warning for each one which fails to parse or typecheck.
loadWorlds ::
(Has (Accum (Seq SystemFailure)) sig m, Has (Lift IO) sig m) =>
EntityMap ->
m WorldMap
loadWorlds em = do
res <- throwToWarning @SystemFailure $ getDataDirSafe Worlds "worlds"
case res of
Nothing -> return M.empty
Just dir -> do
worldFiles <- sendIO $ acquireAllWithExt dir "world"
ws <- mapM (throwToWarning @SystemFailure . loadWorld dir em) worldFiles
return . M.fromList . catMaybes $ ws
-- | Load a file containing a world DSL term, throwing an exception if
-- it fails to parse or typecheck.
loadWorld ::
(Has (Throw SystemFailure) sig m) =>
FilePath ->
EntityMap ->
(FilePath, String) ->
m (Text, Some (TTerm '[]))
loadWorld dir em (fp, src) = do
wexp <-
liftEither . left (AssetNotLoaded (Data Worlds) fp . CanNotParseMegaparsec) $
runParser parseWExp (into @Text src)
t <-
withThrow (AssetNotLoaded (Data Worlds) fp . DoesNotTypecheck . prettyText @CheckErr) $
runReader em . runReader @WorldMap M.empty $
infer CNil wexp
return (into @Text (dropExtension (stripDir dir fp)), t)
-- | Strip a leading directory from a 'FilePath'.
stripDir :: FilePath -> FilePath -> FilePath
stripDir dir fp = joinPath (drop (length (splitPath dir)) (splitPath fp))