679 lines
23 KiB
Haskell
679 lines
23 KiB
Haskell
module Main where
|
|
|
|
import Check (checkFile, checkFileWithStore, instrumentIOContinuations)
|
|
import ContentStore
|
|
import ContentStore.Bundle
|
|
import Module.Manifest
|
|
import System.Exit (die)
|
|
import Eval (evalTricu, mainResult, result)
|
|
import FileEval
|
|
( ContractMode(..)
|
|
, LoadedSource(..)
|
|
, defaultStorePath
|
|
, evaluateFileWithContextWithStoreAndMode
|
|
, evaluateFileWithStore
|
|
, loadFileWithStoreMode
|
|
, compileFileWithStore
|
|
)
|
|
import IODriver (IOPermissions(..), runIO)
|
|
import Parser (parseTricu)
|
|
import REPL (repl, replWithStore)
|
|
import Research (T, EvaluatedForm(..), Env, formatT, exportDag)
|
|
import Wire (encodeBundle, defaultExportNames, Bundle(..))
|
|
|
|
import Control.Monad (foldM, forM, unless, when)
|
|
import Data.Char (isAlphaNum)
|
|
import Data.List (sortOn)
|
|
import qualified Data.Text as T
|
|
import Data.Version (showVersion)
|
|
import Paths_tricu (version)
|
|
import Options.Applicative
|
|
|
|
import qualified Data.ByteString as BS
|
|
import qualified Data.ByteString.Lazy as BL
|
|
import qualified Data.Sequence as Seq
|
|
import qualified Data.Map as Map
|
|
import System.Directory (createDirectoryIfMissing, getHomeDirectory)
|
|
import System.FilePath (takeBaseName, (</>))
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- CLI argument types
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
data AppArgs = AppArgs
|
|
{ globalStore :: Maybe FilePath
|
|
, appCommand :: TricuArgs
|
|
} deriving (Show)
|
|
|
|
data TricuArgs
|
|
= Repl
|
|
| Check
|
|
{ checkInput :: FilePath
|
|
, checkStore :: Maybe FilePath
|
|
}
|
|
| Eval
|
|
{ evalFiles :: [FilePath]
|
|
, evalStore :: Maybe FilePath
|
|
, evalFormat :: EvaluatedForm
|
|
, evalOutput :: FilePath
|
|
, evalUnchecked :: Bool
|
|
, evalIo :: Bool
|
|
, evalAllowRead :: [FilePath]
|
|
, evalAllowWrite :: [FilePath]
|
|
, evalAllowReadAll :: Bool
|
|
, evalAllowWriteAll :: Bool
|
|
, evalUnsafeIo :: Bool
|
|
}
|
|
| ArboricxCompile
|
|
{ compileInput :: FilePath
|
|
, compileStore :: Maybe FilePath
|
|
, compileOutput :: FilePath
|
|
, compileNames :: [String]
|
|
}
|
|
| ArboricxImport
|
|
{ importFile :: FilePath
|
|
, importStore :: Maybe FilePath
|
|
, importModule :: Maybe String
|
|
}
|
|
| ArboricxExport
|
|
{ exportTargets :: [String]
|
|
, exportModules :: [String]
|
|
, exportOutput :: FilePath
|
|
, exportNames :: [String]
|
|
, exportStore :: Maybe FilePath
|
|
, exportAll :: Bool
|
|
, exportSplit :: Bool
|
|
, dag :: Bool
|
|
}
|
|
| StoreAliasList
|
|
{ storeAliasKind :: AliasKind
|
|
, storePathOpt :: Maybe FilePath
|
|
}
|
|
| StoreAliasGet
|
|
{ storeAliasKind :: AliasKind
|
|
, storeAliasName :: String
|
|
, storePathOpt :: Maybe FilePath
|
|
}
|
|
deriving (Show)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- optparse-applicative parsers
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
readEvaluatedForm :: ReadM EvaluatedForm
|
|
readEvaluatedForm = eitherReader $ \s -> case s of
|
|
"tree" -> Right Tree
|
|
"fsl" -> Right FSL
|
|
"ast" -> Right AST
|
|
"ternary" -> Right Ternary
|
|
"ascii" -> Right Ascii
|
|
"decode" -> Right Decode
|
|
"number" -> Right Number
|
|
"string" -> Right StringLit
|
|
_ -> Left $ "Unknown format: " ++ s ++ ". Expected: tree, fsl, ast, ternary, ascii, decode, number, string"
|
|
|
|
checkParser :: Parser TricuArgs
|
|
checkParser = Check
|
|
<$> argument str (metavar "FILE")
|
|
<*> optional (option str
|
|
( long "store"
|
|
<> short 's'
|
|
<> metavar "PATH"
|
|
<> help "Content-addressed store path for module import resolution"
|
|
))
|
|
|
|
evalParser :: Parser TricuArgs
|
|
evalParser = Eval
|
|
<$> many (argument str (metavar "FILE..."))
|
|
<*> optional (option str
|
|
( long "store"
|
|
<> short 's'
|
|
<> metavar "PATH"
|
|
<> help "Content-addressed store path for module import resolution"
|
|
))
|
|
<*> option readEvaluatedForm
|
|
( long "format"
|
|
<> short 'f'
|
|
<> metavar "FORM"
|
|
<> value Tree
|
|
<> help "Output format: tree, fsl, ast, ternary, ascii, decode, number, string"
|
|
)
|
|
<*> option str
|
|
( long "output"
|
|
<> short 'o'
|
|
<> metavar "FILE"
|
|
<> value ""
|
|
<> help "Write output to file instead of stdout"
|
|
)
|
|
<*> switch
|
|
( long "unchecked"
|
|
<> help "Evaluate as untyped code: ignore View Contract annotations and do not publish unchecked view refs"
|
|
)
|
|
<*> switch
|
|
( long "io"
|
|
<> help "Interpret the result as an IO action tree and execute it"
|
|
)
|
|
<*> many (option str
|
|
( long "allow-read"
|
|
<> metavar "PATH"
|
|
<> help "Allow reading from PATH prefix (repeatable)"
|
|
))
|
|
<*> many (option str
|
|
( long "allow-write"
|
|
<> metavar "PATH"
|
|
<> help "Allow writing to PATH prefix (repeatable)"
|
|
))
|
|
<*> switch
|
|
( long "allow-read-all"
|
|
<> help "Allow reading from any path"
|
|
)
|
|
<*> switch
|
|
( long "allow-write-all"
|
|
<> help "Allow writing to any path"
|
|
)
|
|
<*> switch
|
|
( long "unsafe-io"
|
|
<> help "Allow unrestricted read and write access"
|
|
)
|
|
|
|
compileParser :: Parser TricuArgs
|
|
compileParser = ArboricxCompile
|
|
<$> option str
|
|
( long "file"
|
|
<> short 'f'
|
|
<> metavar "FILE"
|
|
<> value ""
|
|
<> help "Input .tri source file"
|
|
)
|
|
<*> optional (option str
|
|
( long "store"
|
|
<> short 's'
|
|
<> metavar "PATH"
|
|
<> help "Content-addressed store path for module import resolution"
|
|
))
|
|
<*> option str
|
|
( long "output"
|
|
<> short 'o'
|
|
<> metavar "FILE"
|
|
<> value ""
|
|
<> help "Output bundle file path (required)"
|
|
)
|
|
<*> many (option str
|
|
( long "name"
|
|
<> short 'n'
|
|
<> metavar "NAME"
|
|
<> help "Definition name(s) to export as bundle roots (repeatable)"
|
|
))
|
|
|
|
importParser :: Parser TricuArgs
|
|
importParser = ArboricxImport
|
|
<$> option str
|
|
( long "file"
|
|
<> short 'f'
|
|
<> metavar "FILE"
|
|
<> value ""
|
|
<> help "Bundle file to import"
|
|
)
|
|
<*> optional (option str
|
|
( long "store"
|
|
<> short 's'
|
|
<> metavar "PATH"
|
|
<> help "Content-addressed store path"
|
|
))
|
|
<*> optional (option str
|
|
( long "module"
|
|
<> short 'm'
|
|
<> metavar "NAME"
|
|
<> help "Module alias to create for the imported bundle (defaults to bundle file basename)"
|
|
))
|
|
|
|
exportParser :: Parser TricuArgs
|
|
exportParser = ArboricxExport
|
|
<$> many (option str
|
|
( long "target"
|
|
<> short 't'
|
|
<> metavar "TARGET"
|
|
<> help "Target hash or name (repeatable)"
|
|
))
|
|
<*> many (option str
|
|
( long "module"
|
|
<> short 'm'
|
|
<> metavar "MODULE"
|
|
<> help "Module alias or manifest hash to export (repeatable; bundle export only)"
|
|
))
|
|
<*> option str
|
|
( long "output"
|
|
<> short 'o'
|
|
<> metavar "FILE"
|
|
<> value ""
|
|
<> help "Output file path (required for bundle export)"
|
|
)
|
|
<*> many (option str
|
|
( long "name"
|
|
<> short 'n'
|
|
<> metavar "NAME"
|
|
<> help "Export name(s) for the bundle manifest (repeatable)"
|
|
))
|
|
<*> optional (option str
|
|
( long "store"
|
|
<> short 's'
|
|
<> metavar "PATH"
|
|
<> help "Content-addressed store path"
|
|
))
|
|
<*> switch
|
|
( long "all"
|
|
<> help "Export all name aliases that point at tree-term objects"
|
|
)
|
|
<*> switch
|
|
( long "split"
|
|
<> help "Write one single-export bundle per export; --output is treated as a directory"
|
|
)
|
|
<*> switch
|
|
( long "dag"
|
|
<> help "Export as a topologically-sorted DAG node table instead of a bundle"
|
|
)
|
|
|
|
aliasKindReader :: ReadM AliasKind
|
|
aliasKindReader = eitherReader $ \s -> case s of
|
|
"names" -> Right NameAlias
|
|
"name" -> Right NameAlias
|
|
"modules" -> Right ModuleAlias
|
|
"module" -> Right ModuleAlias
|
|
"packages" -> Right PackageAlias
|
|
"package" -> Right PackageAlias
|
|
_ -> Left "alias kind must be one of: names, modules, packages"
|
|
|
|
storePathParser :: Parser (Maybe FilePath)
|
|
storePathParser = optional (option str
|
|
( long "store"
|
|
<> short 's'
|
|
<> metavar "PATH"
|
|
<> help "Content-addressed store path"
|
|
))
|
|
|
|
aliasKindParser :: Parser AliasKind
|
|
aliasKindParser = option aliasKindReader
|
|
( long "kind"
|
|
<> short 'k'
|
|
<> metavar "KIND"
|
|
<> value NameAlias
|
|
<> help "Alias kind: names, modules, packages (default: names)"
|
|
)
|
|
|
|
storeAliasListParser :: Parser TricuArgs
|
|
storeAliasListParser = StoreAliasList
|
|
<$> aliasKindParser
|
|
<*> storePathParser
|
|
|
|
storeAliasGetParser :: Parser TricuArgs
|
|
storeAliasGetParser = StoreAliasGet
|
|
<$> aliasKindParser
|
|
<*> argument str (metavar "NAME")
|
|
<*> storePathParser
|
|
|
|
versionStr :: String
|
|
versionStr = "tricu " ++ showVersion version
|
|
|
|
tricuParser :: Parser AppArgs
|
|
tricuParser = AppArgs
|
|
<$> optional (option str
|
|
( long "store"
|
|
<> metavar "PATH"
|
|
<> help "Global content-addressed store path used by commands and the REPL unless a subcommand overrides it"
|
|
))
|
|
<*> ((subparser topCommands <|> pure Repl)
|
|
<**> infoOption versionStr (long "version" <> help "Show version"))
|
|
where
|
|
topCommands = mconcat
|
|
[ command "check" (info (checkParser <**> helper)
|
|
(progDesc "Check View Contract annotations and report ok or diagnostics"))
|
|
, command "eval" (info (evalParser <**> helper)
|
|
(progDesc "Evaluate tricu source and print the result of the final expression"))
|
|
, command "arboricx" (info (arboricxParser <**> helper)
|
|
(progDesc "Arboricx bundle operations"))
|
|
, command "store" (info (storeParser <**> helper)
|
|
(progDesc "Inspect and manage the content-addressed store"))
|
|
]
|
|
|
|
arboricxParser :: Parser TricuArgs
|
|
arboricxParser = subparser $ mconcat
|
|
[ command "compile" (info (compileParser <**> helper)
|
|
(progDesc "Compile a .tri file into a standalone Arboricx bundle"))
|
|
, command "import" (info (importParser <**> helper)
|
|
(progDesc "Import an Arboricx bundle into the content store"))
|
|
, command "export" (info (exportParser <**> helper)
|
|
(progDesc "Export one or more terms from the content store"))
|
|
]
|
|
|
|
storeParser :: Parser TricuArgs
|
|
storeParser = subparser $ mconcat
|
|
[ command "alias" (info (storeAliasParser <**> helper)
|
|
(progDesc "Inspect workspace aliases"))
|
|
]
|
|
|
|
storeAliasParser :: Parser TricuArgs
|
|
storeAliasParser = subparser $ mconcat
|
|
[ command "list" (info (storeAliasListParser <**> helper)
|
|
(progDesc "List aliases by kind"))
|
|
, command "get" (info (storeAliasGetParser <**> helper)
|
|
(progDesc "Resolve an alias by kind and name"))
|
|
]
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Entry point
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
main :: IO ()
|
|
main = do
|
|
appArgs <- execParser $ info (tricuParser <**> helper)
|
|
( fullDesc
|
|
<> progDesc "Exploring Tree Calculus"
|
|
<> header versionStr
|
|
)
|
|
let mGlobalStore = globalStore appArgs
|
|
args = applyGlobalStore mGlobalStore (appCommand appArgs)
|
|
case args of
|
|
Repl -> runReplWithStore mGlobalStore
|
|
Check {} -> runCheck args
|
|
Eval {} -> runEval args
|
|
ArboricxCompile {} -> runCompile args
|
|
ArboricxImport {} -> runImport args
|
|
ArboricxExport {} -> runExport args
|
|
StoreAliasList {} -> runStoreAliasList args
|
|
StoreAliasGet {} -> runStoreAliasGet args
|
|
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Command runners
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
applyGlobalStore :: Maybe FilePath -> TricuArgs -> TricuArgs
|
|
applyGlobalStore mGlobal args = case args of
|
|
Repl -> Repl
|
|
Check {} -> args { checkStore = preferLocal (checkStore args) }
|
|
Eval {} -> args { evalStore = preferLocal (evalStore args) }
|
|
ArboricxCompile {} -> args { compileStore = preferLocal (compileStore args) }
|
|
ArboricxImport {} -> args { importStore = preferLocal (importStore args) }
|
|
ArboricxExport {} -> args { exportStore = preferLocal (exportStore args) }
|
|
StoreAliasList {} -> args { storePathOpt = preferLocal (storePathOpt args) }
|
|
StoreAliasGet {} -> args { storePathOpt = preferLocal (storePathOpt args) }
|
|
where
|
|
preferLocal local = case local of
|
|
Just _ -> local
|
|
Nothing -> mGlobal
|
|
|
|
runRepl :: IO ()
|
|
runRepl = runReplWithStore Nothing
|
|
|
|
runReplWithStore :: Maybe FilePath -> IO ()
|
|
runReplWithStore mStore = do
|
|
putStrLn "Welcome to the tricu REPL"
|
|
putStrLn "You may exit with `CTRL+D` or the `!exit` command."
|
|
case mStore of
|
|
Nothing -> repl
|
|
Just store -> replWithStore (StorePath store)
|
|
|
|
runCheck :: TricuArgs -> IO ()
|
|
runCheck opts = do
|
|
output <- case checkStore opts of
|
|
Nothing -> checkFile (checkInput opts)
|
|
Just storePath -> checkFileWithStore (StorePath storePath) (checkInput opts)
|
|
putStrLn output
|
|
|
|
evaluateCheckedIOFile :: StorePath -> ContractMode -> Env -> FilePath -> IO Env
|
|
evaluateCheckedIOFile store mode env filePath = do
|
|
loaded <- loadFileWithStoreMode mode store filePath
|
|
checkedAst <- case instrumentIOContinuations (loadedAst loaded) of
|
|
Left err -> die err
|
|
Right asts -> pure asts
|
|
viewEnv <- evaluateFileWithStore (Just store) "./lib/view.tri"
|
|
pure $ evalTricu (Map.unions [viewEnv, loadedImports loaded, env]) checkedAst
|
|
|
|
runEval :: TricuArgs -> IO ()
|
|
runEval opts = do
|
|
let files = evalFiles opts
|
|
form = evalFormat opts
|
|
out = evalOutput opts
|
|
resultT <- case files of
|
|
[] -> do
|
|
input <- getContents
|
|
let env = evalTricu Map.empty (parseTricu input)
|
|
return $ result env
|
|
_ -> do
|
|
mStoreOpt <- traverse (pure . StorePath) (evalStore opts)
|
|
let contractMode = if evalUnchecked opts then IgnoreContracts else EnforceContracts
|
|
finalEnv <- if evalIo opts && contractMode == EnforceContracts
|
|
then do
|
|
store <- maybe defaultStorePath pure mStoreOpt
|
|
foldM (evaluateCheckedIOFile store contractMode) Map.empty files
|
|
else foldM (evaluateFileWithContextWithStoreAndMode contractMode mStoreOpt) Map.empty files
|
|
return $ mainResult finalEnv
|
|
finalT <- if evalIo opts
|
|
then do
|
|
let perms = IOPermissions
|
|
{ allowRead = evalAllowRead opts
|
|
, allowWrite = evalAllowWrite opts
|
|
, allowReadAll = evalUnsafeIo opts || evalAllowReadAll opts
|
|
, allowWriteAll = evalUnsafeIo opts || evalAllowWriteAll opts
|
|
}
|
|
result <- runIO perms resultT
|
|
case result of
|
|
Left err -> die $ "IO error: " ++ err
|
|
Right val -> pure val
|
|
else return resultT
|
|
writeOutput out (formatT form finalT)
|
|
|
|
runCompile :: TricuArgs -> IO ()
|
|
runCompile opts = do
|
|
let input = compileInput opts
|
|
out = compileOutput opts
|
|
names = compileNames opts
|
|
mStore = StorePath <$> compileStore opts
|
|
when (null out) $ die "tricu arboricx compile: --output is required"
|
|
when (null input) $ die "tricu arboricx compile: input file is required"
|
|
let nameTexts = if null names then [] else map T.pack names
|
|
compileFileWithStore mStore input out nameTexts
|
|
|
|
runImport :: TricuArgs -> IO ()
|
|
runImport opts = do
|
|
let file = importFile opts
|
|
when (null file) $ die "tricu arboricx import: input file is required"
|
|
store <- resolveStorePath (importStore opts)
|
|
bundleData <- BL.readFile file
|
|
roots <- unpackBundleToStore store (BL.toStrict bundleData)
|
|
mapM_ (\(name, root) ->
|
|
writeAlias store NameAlias name (treeTermRef root)) roots
|
|
let manifest = ModuleManifest []
|
|
[ ModuleExport
|
|
name
|
|
(treeTermRef root)
|
|
"arboricx.abi.tree.v1"
|
|
Nothing
|
|
Nothing
|
|
| (name, root) <- roots
|
|
]
|
|
moduleName = T.pack $ maybe (takeBaseName file) id (importModule opts)
|
|
manifestHash <- putManifest store manifest
|
|
writeAlias store ModuleAlias moduleName (ObjectRef (unDomain manifestDomain) manifestHash)
|
|
putStrLn $ "Imported " ++ show (length roots) ++ " root(s):"
|
|
mapM_ (\(name, root) -> putStrLn $ " " ++ T.unpack name ++ " -> " ++ T.unpack root) roots
|
|
putStrLn $ "Created module alias " ++ T.unpack moduleName ++ " -> " ++ T.unpack manifestHash
|
|
|
|
runExport :: TricuArgs -> IO ()
|
|
runExport opts =
|
|
if dag opts
|
|
then runExportDag opts
|
|
else runExportBundle opts
|
|
|
|
runExportBundle :: TricuArgs -> IO ()
|
|
runExportBundle opts = do
|
|
let targets = exportTargets opts
|
|
modules = exportModules opts
|
|
out = exportOutput opts
|
|
names = exportNames opts
|
|
allFlag = exportAll opts
|
|
splitFlag = exportSplit opts
|
|
when (null out) $ die "tricu arboricx export: --output is required"
|
|
when (null targets && null modules && not allFlag) $
|
|
die "tricu arboricx export: at least one --target, --module, or --all is required"
|
|
when (splitFlag && not (null names)) $
|
|
die "tricu arboricx export --split: --name is not supported; split bundles use their export names"
|
|
store <- resolveStorePath (exportStore opts)
|
|
allEntries <- if allFlag then resolveAllNameExports store else pure []
|
|
targetRoots <- mapM (resolveStoreTarget store) targets
|
|
moduleRoots <- concat <$> mapM (resolveModuleExports store) modules
|
|
let targetEntries = zip (defaultExportNames (length targetRoots)) targetRoots
|
|
entries = allEntries ++ targetEntries ++ moduleRoots
|
|
expNames = if null names then map fst entries else map T.pack names
|
|
when (null entries) $
|
|
die "tricu arboricx export: no tree-term exports found"
|
|
when (length expNames /= length entries) $
|
|
die "tricu arboricx export: number of --name values must match number of exported roots"
|
|
if splitFlag
|
|
then runExportBundleSplit store out (zip expNames (map snd entries))
|
|
else do
|
|
bundle <- packBundleFromStore store (zip expNames (map snd entries))
|
|
let bundleData = encodeBundle bundle
|
|
BL.writeFile out (BL.fromStrict bundleData)
|
|
putStrLn $ "Exported bundle with " ++ show (length entries) ++ " export(s) to " ++ out
|
|
putStrLn $ " nodes: " ++ show (Seq.length (bundleNodes bundle))
|
|
putStrLn $ " size: " ++ show (BS.length bundleData) ++ " bytes"
|
|
|
|
runExportBundleSplit :: StorePath -> FilePath -> [(T.Text, ObjectHash)] -> IO ()
|
|
runExportBundleSplit store outDir entries = do
|
|
createDirectoryIfMissing True outDir
|
|
written <- forM (zip [0 :: Int ..] entries) $ \(i, (name, root)) -> do
|
|
bundle <- packBundleFromStore store [(name, root)]
|
|
let bundleData = encodeBundle bundle
|
|
path = outDir </> splitBundleFileName i name
|
|
BL.writeFile path (BL.fromStrict bundleData)
|
|
pure (path, Seq.length (bundleNodes bundle), BS.length bundleData)
|
|
putStrLn $ "Exported " ++ show (length written) ++ " split bundle(s) to " ++ outDir
|
|
mapM_ (\(path, nodeCount, byteCount) ->
|
|
putStrLn $ " " ++ path ++ " (nodes: " ++ show nodeCount ++ ", size: " ++ show byteCount ++ " bytes)") written
|
|
|
|
runStoreAliasList :: TricuArgs -> IO ()
|
|
runStoreAliasList opts = do
|
|
store <- resolveStorePath (storePathOpt opts)
|
|
aliases <- listAliases store (storeAliasKind opts)
|
|
mapM_ (\(name, ref) -> putStrLn $ T.unpack name ++ " -> " ++ formatObjectRef ref) aliases
|
|
|
|
runStoreAliasGet :: TricuArgs -> IO ()
|
|
runStoreAliasGet opts = do
|
|
store <- resolveStorePath (storePathOpt opts)
|
|
mRef <- readAlias store (storeAliasKind opts) (T.pack $ storeAliasName opts)
|
|
case mRef of
|
|
Nothing -> die $ "alias not found: " ++ storeAliasName opts
|
|
Just ref -> putStrLn $ storeAliasName opts ++ " -> " ++ formatObjectRef ref
|
|
|
|
runExportDag :: TricuArgs -> IO ()
|
|
runExportDag opts = do
|
|
let targets = exportTargets opts
|
|
modules = exportModules opts
|
|
out = exportOutput opts
|
|
unless (null modules) $
|
|
die "tricu arboricx export --dag: --module is only supported for bundle export"
|
|
case targets of
|
|
[target] -> do
|
|
store <- resolveStorePath (exportStore opts)
|
|
root <- resolveStoreTarget store target
|
|
maybeTerm <- getTreeTerm store root
|
|
case maybeTerm of
|
|
Nothing -> die $ "Term not found: " ++ target
|
|
Just term -> do
|
|
let (rootIdx, nodes) = Research.exportDag term
|
|
output = unlines $
|
|
show rootIdx :
|
|
map (\(tag, refs) -> unwords (tag : map show refs)) nodes
|
|
writeOutput out output
|
|
[] -> die "tricu arboricx export --dag: exactly one --target is required"
|
|
_ -> die "tricu arboricx export --dag: exactly one --target is required"
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Helpers
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
resolveStorePath :: Maybe FilePath -> IO StorePath
|
|
resolveStorePath (Just path) = return (StorePath path)
|
|
resolveStorePath Nothing = do
|
|
home <- getHomeDirectory
|
|
return (StorePath (home </> ".tricu" </> "store"))
|
|
|
|
treeTermRef :: ObjectHash -> ObjectRef
|
|
treeTermRef = ObjectRef (unDomain treeTermDomain)
|
|
|
|
resolveStoreTarget :: StorePath -> String -> IO ObjectHash
|
|
resolveStoreTarget store target = do
|
|
mAlias <- readAlias store NameAlias (T.pack target)
|
|
let root = maybe (T.pack target) objectRefHash mAlias
|
|
mTree <- getTreeTerm store root
|
|
case mTree of
|
|
Just _ -> return root
|
|
Nothing -> die $ "Term not found in store: " ++ target
|
|
|
|
resolveAllNameExports :: StorePath -> IO [(T.Text, ObjectHash)]
|
|
resolveAllNameExports store = do
|
|
aliases <- sortOn fst <$> listAliases store NameAlias
|
|
fmap concat $ mapM exportAlias aliases
|
|
where
|
|
exportAlias (name, ref)
|
|
| objectRefKind ref /= unDomain treeTermDomain = pure []
|
|
| otherwise = do
|
|
mTree <- getTreeTerm store (objectRefHash ref)
|
|
case mTree of
|
|
Nothing -> die $ "Name alias tree term not found: " ++ T.unpack name
|
|
Just _ -> pure [(name, objectRefHash ref)]
|
|
|
|
resolveModuleExports :: StorePath -> String -> IO [(T.Text, ObjectHash)]
|
|
resolveModuleExports store moduleTarget = do
|
|
manifestHash <- resolveModuleManifestHash store moduleTarget
|
|
mManifest <- getManifest store manifestHash
|
|
manifest <- case mManifest of
|
|
Nothing -> die $ "Module manifest not found in store: " ++ moduleTarget
|
|
Just value -> return value
|
|
mapM exportEntry (moduleManifestExports manifest)
|
|
where
|
|
exportEntry ex = do
|
|
let ref = moduleExportObject ex
|
|
unless (objectRefKind ref == unDomain treeTermDomain) $
|
|
die $ "Unsupported module export object kind for " ++ T.unpack (moduleExportName ex) ++ ": " ++ T.unpack (objectRefKind ref)
|
|
mTree <- getTreeTerm store (objectRefHash ref)
|
|
case mTree of
|
|
Nothing -> die $ "Module export tree term not found: " ++ T.unpack (moduleExportName ex)
|
|
Just _ -> return (moduleExportName ex, objectRefHash ref)
|
|
|
|
resolveModuleManifestHash :: StorePath -> String -> IO ObjectHash
|
|
resolveModuleManifestHash store moduleTarget = do
|
|
mAlias <- readAlias store ModuleAlias (T.pack moduleTarget)
|
|
case mAlias of
|
|
Just ref -> do
|
|
unless (objectRefKind ref == unDomain manifestDomain) $
|
|
die $ "Module alias does not point at a module manifest: " ++ moduleTarget
|
|
return (objectRefHash ref)
|
|
Nothing -> return (T.pack moduleTarget)
|
|
|
|
formatObjectRef :: ObjectRef -> String
|
|
formatObjectRef ref = T.unpack (objectRefKind ref) ++ " " ++ T.unpack (objectRefHash ref)
|
|
|
|
splitBundleFileName :: Int -> T.Text -> FilePath
|
|
splitBundleFileName i name = show i ++ "-" ++ sanitize (T.unpack name) ++ ".arboricx"
|
|
where
|
|
sanitize [] = "export"
|
|
sanitize xs = case map safeChar xs of
|
|
[] -> "export"
|
|
ys -> ys
|
|
safeChar c
|
|
| isAlphaNum c || c == '-' || c == '_' || c == '.' = c
|
|
| otherwise = '_'
|
|
|
|
writeOutput :: FilePath -> String -> IO ()
|
|
writeOutput path content
|
|
| null path = putStr content
|
|
| otherwise = writeFile path content
|
|
|
|
runTricuTEnv :: Env -> String -> T
|
|
runTricuTEnv env input =
|
|
let asts = parseTricu input
|
|
finalEnv = evalTricu env asts
|
|
in result finalEnv
|