Files
tricu/src/Module/Resolver.hs

156 lines
6.5 KiB
Haskell

module Module.Resolver
( ResolvedExport(..)
, ResolvedModule(..)
, resolveModuleImport
, resolveModuleImportSelecting
, resolveModuleImports
, resolvedModulesEnv
) where
import ContentStore.Alias
import ContentStore.Arboricx (decodeTreeTerm, treeTermDomain)
import ContentStore.ViewTree (decodeViewTree, viewTreeKind, viewTreeRootTerm)
import ContentStore.Object
import ContentStore.Resolver
import Module.Manifest
import Research
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text as T
-- | A manifest export resolved into the importing source's local lexical scope.
-- The executable term is loaded, while object/view refs remain available for
-- later checker and diagnostics phases.
data ResolvedExport = ResolvedExport
{ resolvedExportSourceName :: T.Text
, resolvedExportLocalName :: String
, resolvedExportObject :: ObjectRef
, resolvedExportAbi :: T.Text
, resolvedExportView :: Maybe ObjectRef
, resolvedExportProvenance :: Maybe ViewProvenance
, resolvedExportTerm :: T
} deriving (Show, Eq)
data ResolvedModule = ResolvedModule
{ resolvedModuleTarget :: String
, resolvedModuleNamespace :: String
, resolvedModuleManifest :: ObjectHash
, resolvedModuleExports :: [ResolvedExport]
} deriving (Show, Eq)
resolveModuleImports :: ObjectResolver -> [TricuAST] -> IO ([ResolvedModule], [TricuAST])
resolveModuleImports resolver asts = do
let (imports, nonImports) = foldr splitImport ([], []) asts
modules <- mapM (uncurry (resolveModuleImport resolver)) imports
return (modules, nonImports)
where
splitImport (SImport target namespace) (is, rest) = ((target, namespace) : is, rest)
splitImport ast (is, rest) = (is, ast : rest)
resolveModuleImport :: ObjectResolver -> String -> String -> IO ResolvedModule
resolveModuleImport resolver moduleTarget namespace =
resolveModuleImportSelecting resolver Nothing moduleTarget namespace
resolveModuleImportSelecting :: ObjectResolver -> Maybe (Set.Set T.Text) -> String -> String -> IO ResolvedModule
resolveModuleImportSelecting resolver selected moduleTarget namespace = do
manifestHash <- resolveModuleManifestHash resolver moduleTarget
mManifest <- resolveManifest resolver manifestHash
manifest <- case mManifest of
Nothing -> errorWithoutStackTrace $
"Module import failed for " ++ show moduleTarget
++ " as " ++ show namespace
++ ": manifest object not found (kind " ++ T.unpack (unDomain manifestDomain)
++ ", hash " ++ T.unpack manifestHash ++ ")"
Just value -> return value
let wantedExports = case selected of
Nothing -> moduleManifestExports manifest
Just names -> filter (\ex -> moduleExportName ex `Set.member` names) (moduleManifestExports manifest)
exports <- mapM (resolveModuleExport resolver localNamespace) wantedExports
return ResolvedModule
{ resolvedModuleTarget = moduleTarget
, resolvedModuleNamespace = namespace
, resolvedModuleManifest = manifestHash
, resolvedModuleExports = exports
}
where
localNamespace = if namespace == "!Local" then "" else namespace
resolveModuleExport :: ObjectResolver -> String -> ModuleExport -> IO ResolvedExport
resolveModuleExport resolver namespace ex = do
let ref = moduleExportObject ex
sourceName = moduleExportName ex
term <- resolveExportTerm resolver sourceName ref
return ResolvedExport
{ resolvedExportSourceName = sourceName
, resolvedExportLocalName = nsVariable namespace (T.unpack sourceName)
, resolvedExportObject = ref
, resolvedExportAbi = moduleExportAbi ex
, resolvedExportView = moduleExportView ex
, resolvedExportProvenance = moduleExportViewProvenance ex
, resolvedExportTerm = term
}
resolveExportTerm :: ObjectResolver -> T.Text -> ObjectRef -> IO T
resolveExportTerm resolver sourceName ref
| objectRefKind ref == viewTreeKind = do
bytes <- requireObject "view tree"
case decodeViewTree bytes >>= viewTreeRootTerm of
Left err -> errorWithoutStackTrace $
"Module export " ++ show (T.unpack sourceName)
++ " references invalid view tree " ++ T.unpack (objectRefHash ref)
++ ": " ++ err
Right term -> return term
| objectRefKind ref == unDomain treeTermDomain = do
bytes <- requireObject "tree term"
case decodeTreeTerm bytes of
Left err -> errorWithoutStackTrace $
"Module export " ++ show (T.unpack sourceName)
++ " references invalid tree term " ++ T.unpack (objectRefHash ref)
++ ": " ++ err
Right term -> return term
| otherwise = errorWithoutStackTrace $
"Module export " ++ show (T.unpack sourceName)
++ " has unsupported object kind " ++ show (T.unpack (objectRefKind ref))
++ "; expected " ++ show (T.unpack viewTreeKind)
++ " or " ++ show (T.unpack (unDomain treeTermDomain))
where
requireObject label = do
mBytes <- resolverObject resolver ref
case mBytes of
Just bytes -> return bytes
Nothing -> errorWithoutStackTrace $
"Module export " ++ show (T.unpack sourceName)
++ " references missing " ++ label ++ " " ++ T.unpack (objectRefHash ref)
++ " (kind " ++ T.unpack (objectRefKind ref) ++ ")"
resolvedModulesEnv :: [ResolvedModule] -> Env
resolvedModulesEnv modules = Map.fromList
[ (resolvedExportLocalName ex, resolvedExportTerm ex)
| m <- modules
, ex <- resolvedModuleExports m
]
resolveModuleManifestHash :: ObjectResolver -> String -> IO ObjectHash
resolveModuleManifestHash resolver moduleTarget = do
mAlias <- resolverAlias resolver ModuleAlias (T.pack moduleTarget)
case mAlias of
Just ref ->
if objectRefKind ref == unDomain manifestDomain
then return (objectRefHash ref)
else errorWithoutStackTrace $
"Module alias " ++ show moduleTarget
++ " points at unsupported object kind " ++ show (T.unpack (objectRefKind ref))
++ "; expected " ++ show (T.unpack (unDomain manifestDomain))
++ " (hash " ++ T.unpack (objectRefHash ref) ++ ")"
Nothing ->
case textToHashBytes (T.pack moduleTarget) of
Right _ -> return (T.pack moduleTarget)
Left _ -> errorWithoutStackTrace $
"Module alias not found: " ++ show moduleTarget
++ "; add it to tricu.workspace or write a ModuleAlias, or import by manifest hash"
nsVariable :: String -> String -> String
nsVariable "" name = name
nsVariable moduleName name = moduleName ++ "." ++ name