tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

FileEval.hs (9900B)


      1 module FileEval
      2   ( LoadedSource(..)
      3   , preprocessFile
      4   , preprocessFileWithStore
      5   , preprocessFileWithResolver
      6   , evaluateFile
      7   , evaluateFileWithStore
      8   , evaluateFileWithContext
      9   , evaluateFileWithContextWithStore
     10   , evaluateFileResult
     11   , compileFile
     12   , compileFileWithStore
     13   , loadFileWithStore
     14   , loadFileWithResolver
     15   , loadWorkspaceExportWithStore
     16   , defaultStorePath
     17   ) where
     18 
     19 import ContentStore
     20 import Eval (evalTricu, freeVars, result, injectKernel)
     21 import Frontend.ContractDesugar (desugarContracts)
     22 import Lexer
     23 import Module.Manifest
     24 import Module.Resolver
     25 import Module.Workspace
     26 import Parser
     27 import Research
     28 import Wire (buildBundle, encodeBundle, decodeBundle, verifyBundle, Bundle(..))
     29 
     30 import Data.List       (partition, isPrefixOf)
     31 import Data.Maybe      (mapMaybe)
     32 import System.Directory (getHomeDirectory, getTemporaryDirectory)
     33 import System.FilePath ((</>))
     34 import System.Exit (die)
     35 
     36 import qualified Data.ByteString as BS
     37 import qualified Data.ByteString.Lazy as BL
     38 import qualified Data.Map as Map
     39 import qualified Data.Set as Set
     40 import qualified Data.Sequence as Seq
     41 import qualified Data.Text as T
     42 
     43 extractMain :: Env -> Either String T
     44 extractMain env =
     45   case Map.lookup "main" env of
     46     Just evalResult -> Right evalResult
     47     Nothing -> Left "No `main` function detected"
     48 
     49 data LoadedSource = LoadedSource
     50   { loadedImports :: Env
     51   , loadedAst     :: [TricuAST]
     52   , loadedModules :: [ResolvedModule]
     53   }
     54 
     55 data LoadContext = LoadContext
     56   { loadResolver  :: ObjectResolver
     57   , loadStore     :: Maybe StorePath
     58   , loadWorkspace :: Workspace
     59   }
     60 
     61 processImports :: [TricuAST] -> ([TricuAST], [(String, String)])
     62 processImports asts =
     63   let (imports, nonImports) = partition isImp asts
     64       importTargets = mapMaybe getImportInfo imports
     65   in (nonImports, importTargets)
     66   where
     67     isImp (SImport _ _) = True
     68     isImp _ = False
     69     getImportInfo (SImport p n) = Just (p, n)
     70     getImportInfo _ = Nothing
     71 
     72 evaluateFileResult :: FilePath -> IO T
     73 evaluateFileResult filePath = do
     74   env <- evaluateFile filePath
     75   case extractMain env of
     76     Right evalResult -> return evalResult
     77     Left err -> errorWithoutStackTrace err
     78 
     79 evaluateFile :: FilePath -> IO Env
     80 evaluateFile = evaluateFileWithStore Nothing
     81 
     82 evaluateFileWithStore :: Maybe StorePath -> FilePath -> IO Env
     83 evaluateFileWithStore mStore filePath = do
     84   loaded <- maybe loadFile loadFileWithStore mStore filePath
     85   pure $ evalTricu (injectKernel (loadedImports loaded)) (loadedAst loaded)
     86 
     87 evaluateFileWithContext :: Env -> FilePath -> IO Env
     88 evaluateFileWithContext = evaluateFileWithContextWithStore Nothing
     89 
     90 evaluateFileWithContextWithStore :: Maybe StorePath -> Env -> FilePath -> IO Env
     91 evaluateFileWithContextWithStore mStore env filePath = do
     92   loaded <- case mStore of
     93     Nothing    -> loadFile filePath
     94     Just store -> loadFileWithStore store filePath
     95   pure $ evalTricu (injectKernel (Map.union (loadedImports loaded) env)) (loadedAst loaded)
     96 
     97 preprocessFile :: FilePath -> IO [TricuAST]
     98 preprocessFile p = loadedAst <$> loadFile p
     99 
    100 preprocessFileWithStore :: StorePath -> FilePath -> IO [TricuAST]
    101 preprocessFileWithStore store p = loadedAst <$> loadFileWithStore store p
    102 
    103 preprocessFileWithResolver :: ObjectResolver -> FilePath -> IO [TricuAST]
    104 preprocessFileWithResolver resolver p = loadedAst <$> loadFileWithResolver resolver p
    105 
    106 loadFile :: FilePath -> IO LoadedSource
    107 loadFile p = do
    108   store <- defaultStorePath
    109   loadFileWithStore store p
    110 
    111 loadFileWithStore :: StorePath -> FilePath -> IO LoadedSource
    112 loadFileWithStore store p = do
    113   workspace <- findWorkspaceFor p
    114   resolver <- cachedFilesystemResolver store
    115   let ctx = LoadContext resolver (Just store) workspace
    116   loadFile' ctx p
    117 
    118 loadFileWithResolver :: ObjectResolver -> FilePath -> IO LoadedSource
    119 loadFileWithResolver resolver p = do
    120   let ctx = LoadContext resolver Nothing emptyWorkspace
    121   loadFile' ctx p
    122 
    123 -- Load a library entry through the same workspace/bootstrap boundary as an
    124 -- authored import, without a generated source file or source interpolation.
    125 loadWorkspaceExportWithStore :: StorePath -> FilePath -> String -> String -> IO T
    126 loadWorkspaceExportWithStore store contextPath target exportName = do
    127   workspace <- findWorkspaceFor contextPath
    128   resolver <- cachedFilesystemResolver store
    129   let ctx = LoadContext resolver (Just store) workspace
    130   ensureWorkspaceModule ctx target
    131   resolved <- resolveModuleImportSelecting resolver
    132     (Just $ Set.singleton $ T.pack exportName) target "!Local"
    133   case Map.lookup exportName (resolvedModulesEnv [resolved]) of
    134     Just term -> pure term
    135     Nothing -> die $ "Module export not found: " ++ target ++ "." ++ exportName
    136 
    137 loadFile' :: LoadContext -> FilePath -> IO LoadedSource
    138 loadFile' ctx currentPath = do
    139   contents <- readFile currentPath
    140   let tokens = lexTricu contents
    141   case parseProgram tokens of
    142     Left err -> errorWithoutStackTrace (handleParseError tokens err)
    143     Right ast ->
    144       let (nonImports, importTargets) = processImports ast
    145           desugaredNonImports = desugarContracts nonImports
    146       in do
    147         let reexportOnlyModule = null desugaredNonImports && not (null importTargets)
    148         resolvedModules <- mapM (\(target, name) -> do
    149             ensureWorkspaceModule ctx target
    150             resolveModuleImportSelecting (loadResolver ctx) (selectedExportsForImport reexportOnlyModule target name desugaredNonImports) target name) importTargets
    151         let moduleEnv = resolvedModulesEnv resolvedModules
    152         pure LoadedSource
    153           { loadedImports = moduleEnv
    154           , loadedAst = desugaredNonImports
    155           , loadedModules = resolvedModules
    156           }
    157 
    158 ensureWorkspaceModule :: LoadContext -> String -> IO ()
    159 ensureWorkspaceModule ctx moduleTarget = do
    160   existing <- resolverAlias (loadResolver ctx) ModuleAlias (T.pack moduleTarget)
    161   case existing of
    162     Just _ -> return ()
    163     Nothing -> do
    164       mSource <- resolveSourceModulePath ctx moduleTarget
    165       case (loadStore ctx, mSource) of
    166         (Just store, Just sourcePath) -> buildWorkspaceModule ctx store moduleTarget sourcePath
    167         _ -> return ()
    168 
    169 resolveSourceModulePath :: LoadContext -> String -> IO (Maybe FilePath)
    170 resolveSourceModulePath ctx moduleTarget =
    171   return (lookupWorkspaceModule (loadWorkspace ctx) (T.pack moduleTarget))
    172 
    173 buildWorkspaceModule :: LoadContext -> StorePath -> String -> FilePath -> IO ()
    174 buildWorkspaceModule ctx store moduleName sourcePath = do
    175   loaded <- loadFile' ctx sourcePath
    176   let asts = loadedAst loaded
    177       env = evalTricu (loadedImports loaded) asts
    178       localNames = topLevelDefinitions asts
    179       names = if null localNames
    180                 then filter (/= "!result") (Map.keys env)
    181                 else localNames
    182   exports <- mapM (buildExport env) names
    183   manifestHash <- putManifest store (ModuleManifest [] exports)
    184   writeAlias store ModuleAlias (T.pack moduleName) (ObjectRef (unDomain manifestDomain) manifestHash)
    185   where
    186     buildExport env name = case Map.lookup name env of
    187       Nothing -> errorWithoutStackTrace $
    188         "Workspace module export not found after evaluation: " ++ name
    189       Just term -> do
    190         rootRef <- putTreeTerm store term
    191         return ModuleExport
    192           { moduleExportName = T.pack name
    193           , moduleExportObject = ObjectRef (unDomain treeTermDomain) rootRef
    194           , moduleExportAbi = "arboricx.abi.tree.v1"
    195           }
    196 
    197 topLevelDefinitions :: [TricuAST] -> [String]
    198 topLevelDefinitions = mapMaybe go
    199   where
    200     go (SDef name _ _) = Just name
    201     go (SDefAnn name _ _ _) = Just name
    202     go _ = Nothing
    203 
    204 defaultStorePath :: IO StorePath
    205 defaultStorePath = do
    206   home <- getHomeDirectory
    207   if home == "/homeless-shelter"
    208     then do
    209       tmp <- getTemporaryDirectory
    210       return (StorePath (tmp </> "tricu" </> "store"))
    211     else return (StorePath (home </> ".tricu" </> "store"))
    212 
    213 selectedExportsForImport :: Bool -> String -> String -> [TricuAST] -> Maybe (Set.Set T.Text)
    214 selectedExportsForImport True _ _ _ = Nothing
    215 selectedExportsForImport False _moduleTarget importNamespace asts =
    216   Just $ Set.fromList directSelections
    217   where
    218     directSelections = mapMaybe select (Set.toList used)
    219     used = foldMap freeVars asts
    220     prefix = importNamespace ++ "."
    221     select name
    222       | importNamespace == "!Local" = Just (T.pack name)
    223       | prefix `isPrefixOf` name = Just (T.pack (drop (length prefix) name))
    224       | otherwise = Nothing
    225 
    226 -- | Compile a tricu source file to a standalone Arboricx bundle.
    227 -- Emits a canonical indexed bundle with no SHA-256 hashing.
    228 compileFile :: FilePath -> FilePath -> [T.Text] -> IO ()
    229 compileFile = compileFileWithStore Nothing
    230 
    231 compileFileWithStore :: Maybe StorePath -> FilePath -> FilePath -> [T.Text] -> IO ()
    232 compileFileWithStore mStore inputPath outputPath maybeNames = do
    233   env <- evaluateFileWithStore mStore inputPath
    234   let defaultNames = ["main"]
    235       wantedNames  = if null maybeNames then defaultNames else maybeNames
    236       wantedNamesUnpacked = map T.unpack wantedNames
    237   compiledTerms <- mapM (\n -> case Map.lookup n env of
    238     Nothing -> die $ "No definition '" ++ n ++ "' found in " ++ inputPath
    239     Just t  -> return (T.pack n, t)) wantedNamesUnpacked
    240   let bundle = buildBundle compiledTerms
    241       bundleData = encodeBundle bundle
    242       nodeCount = Seq.length (bundleNodes bundle)
    243       bundleSize = BS.length bundleData
    244   BL.writeFile outputPath (BL.fromStrict bundleData)
    245   putStrLn $ "Compiled " ++ inputPath ++ " -> " ++ outputPath
    246   putStrLn $ "  exports: " ++ T.unpack (T.intercalate ", " (map fst compiledTerms))
    247   putStrLn $ "  nodes: " ++ show nodeCount
    248   putStrLn $ "  size: " ++ show bundleSize ++ " bytes"
    249   case decodeBundle bundleData of
    250     Left err -> putStrLn $ "  round-trip decode failed: " ++ err
    251     Right decoded -> case verifyBundle decoded of
    252       Left err -> putStrLn $ "  round-trip verify failed: " ++ err
    253       Right () -> putStrLn $ "  round-trip: OK"