tricu

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

FileEval.hs (9095B)


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