1
1
mirror of https://github.com/anoma/juvix.git synced 2025-01-01 12:06:57 +03:00
juvix/app/Commands/Repl.hs

662 lines
24 KiB
Haskell
Raw Normal View History

{-# LANGUAGE QuasiQuotes #-}
module Commands.Repl where
import Commands.Base hiding
( command,
)
import Commands.Repl.Base
import Commands.Repl.Options
import Control.Exception (throwIO)
import Control.Monad.Except qualified as Except
import Control.Monad.Reader qualified as Reader
import Control.Monad.State.Strict qualified as State
import Control.Monad.Trans.Class (lift)
import Data.String.Interpolate (i, __i)
import Evaluator
import Juvix.Compiler.Concrete.Data.InfoTable qualified as Scoped
import Juvix.Compiler.Concrete.Data.Scope (scopePath)
import Juvix.Compiler.Concrete.Data.ScopedName (absTopModulePath)
import Juvix.Compiler.Concrete.Data.ScopedName qualified as Scoped
import Juvix.Compiler.Concrete.Language qualified as Concrete
import Juvix.Compiler.Concrete.Pretty qualified as Concrete
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver (runPathResolver)
External package dependencies (#2272) This PR adds external git dependency support to the Juvix package format. ## New dependency Git item You can now add a `git` block to the dependencies list: ```yaml name: HelloWorld main: HelloWorld.juvix dependencies: - .juvix-build/stdlib - git: url: https://my.git.repo name: myGitRepo ref: main version: 0.1.0 ``` Git block required fields: * `url`: The URL of the git repository * `ref`: The git reference that should be checked out * `name`: The name for the dependency. This is used to name the directory of the clone, it is required. Perhaps we could come up with a way to automatically name the clone directory. Current ideas are to somehow encode the URL / ref combination or use a UUID. However there's some value in having the clone directory named in a friendly way. NB: * The values of the `name` fields must be unique among the git blocks in the dependencies list. ## Behaviour When dependencies for a package are registered, at the beginning of the compiler pipeline, all remote dependencies are processed: 1. If it doesn't already exist, the remote dependency is cloned to `.juvix-build/deps/$name` 2. `git fetch` is run in the clone 3. `git checkout` at the specified `ref` is run in the clone The clone is then processed by the PathResolver in the same way as path dependencies. NB: * Remote dependencies of transitive dependencies are also processed. * The `git fetch` step is required for the case where the remote is updated. In this case we want the user to be able to update the `ref` field. ## Errors 1. Missing fields in the Git dependency block are YAML parse errors 2. Duplicate `name` values in the dependencies list is an error thrown when the package file is processed 3. The `ref` doesn't exist in the clone or the clone directory is otherwise corrupt. An error with a suggestion to `juvix clean` is given. The package file path is used as the location in the error message. 4. Other `git` command errors (command not found, etc.), a more verbose error is given with the arguments that were passed to the git command. ## Future work 1. Add an offline mode 2. Add a lock file mechanism that resolves branch/tag git refs to commit hashes * closes https://github.com/anoma/juvix/issues/2083 --------- Co-authored-by: Jan Mas Rovira <janmasrovira@gmail.com>
2023-09-01 14:37:06 +03:00
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Error
import Juvix.Compiler.Core qualified as Core
import Juvix.Compiler.Core.Extra.Value
import Juvix.Compiler.Core.Info qualified as Info
import Juvix.Compiler.Core.Info.NoDisplayInfo qualified as Info
import Juvix.Compiler.Core.Pretty qualified as Core
import Juvix.Compiler.Core.Transformation qualified as Core
import Juvix.Compiler.Core.Transformation.DisambiguateNames (disambiguateNames)
import Juvix.Compiler.Internal.Language qualified as Internal
import Juvix.Compiler.Internal.Pretty qualified as Internal
Use a Juvix source file to define a package (#2434) Depends on: * ~~https://github.com/anoma/juvix/pull/2459~~ * https://github.com/anoma/juvix/pull/2462 This PR is part of a series implementing: * https://github.com/anoma/juvix/issues/2336 This PR adds the package file loading function, including a file evaluation effect. It integrates this with the existing `readPackage` function and adds tests / smoke tests. ## Package.juvix format Instead of `juvix.yaml` (which is still supported currently) users can now place a `Package.juvix` file in the root of their project. The simplest `Package.juvix` file you can write is: ``` module Package; import PackageDescription open; package : Package := defaultPackage; ``` The [PackageDescription](https://github.com/anoma/juvix/blob/35b2f618f093895f32929ac0f2c0affcdab8d627/include/package/PackageDescription.juvix) module defines the `Package` type. Users can use "go-to definition" in their IDE from the Package file to see the documentation and definitions. Users may also import `Stdlib.Prelude` in their Package file. This is loaded from the global project. No other module imports are supported. Notes: * If a directory contains both `Package.juvix` and `juvix.yaml` then `Package.juvix` is used in preference. ## Default stdlib dependency The `Dependency` type has a constructor called `defaultStdlib`. This means that any project can use the compiler builtin standard library dependency. With `juvix.yaml` this dependency is only available when the `dependencies` field is unspecified. ``` module Package; import PackageDescription open; package : Package := defaultPackage { dependencies := [defaultStdlib] }; ``` ## Validation As well as the standard type checking validation that the Juvix compiler provides additional validation is made on the file. * The Package module must contain the identifier `package` and it must have type `Package` that's obtained from the global `PackageDescription` module. * Every dependency specified in the Package.juvix must be unique. * Closes https://github.com/anoma/juvix/issues/2336 ## Examples ### Package with name and version ``` module Package; import PackageDescription open; package : Package := defaultPackage {name := "a-package"; version := mkVersion 0 1 0}; ``` ### Package with GitHub dependency ``` module Package; import PackageDescription open; package : Package := defaultPackage {name := "a-package"; version := mkVersion 0 1 0; dependencies := [defaultStdlib; github (org := "anoma"; repo := "juvix-containers"; ref := "v0.7.1")]}; ``` ## Package with main and buildDir fields ``` module Package; import Stdlib.Prelude open; import PackageDescription open; package : Package := defaultPackage {name := "a-package"; version := mkVersion 0 1 0; dependencies := [defaultStdlib; github (org := "anoma"; repo := "juvix-containers"; ref := "v0.7.1")]; buildDir := just "/tmp/build"; main := just "HelloWorld.juvix" }; ```
2023-10-27 14:35:20 +03:00
import Juvix.Compiler.Pipeline.Package.Loader.Error
import Juvix.Compiler.Pipeline.Package.Loader.EvalEff.IO
import Juvix.Compiler.Pipeline.Repl
import Juvix.Compiler.Pipeline.Run
import Juvix.Compiler.Pipeline.Setup (entrySetup)
External package dependencies (#2272) This PR adds external git dependency support to the Juvix package format. ## New dependency Git item You can now add a `git` block to the dependencies list: ```yaml name: HelloWorld main: HelloWorld.juvix dependencies: - .juvix-build/stdlib - git: url: https://my.git.repo name: myGitRepo ref: main version: 0.1.0 ``` Git block required fields: * `url`: The URL of the git repository * `ref`: The git reference that should be checked out * `name`: The name for the dependency. This is used to name the directory of the clone, it is required. Perhaps we could come up with a way to automatically name the clone directory. Current ideas are to somehow encode the URL / ref combination or use a UUID. However there's some value in having the clone directory named in a friendly way. NB: * The values of the `name` fields must be unique among the git blocks in the dependencies list. ## Behaviour When dependencies for a package are registered, at the beginning of the compiler pipeline, all remote dependencies are processed: 1. If it doesn't already exist, the remote dependency is cloned to `.juvix-build/deps/$name` 2. `git fetch` is run in the clone 3. `git checkout` at the specified `ref` is run in the clone The clone is then processed by the PathResolver in the same way as path dependencies. NB: * Remote dependencies of transitive dependencies are also processed. * The `git fetch` step is required for the case where the remote is updated. In this case we want the user to be able to update the `ref` field. ## Errors 1. Missing fields in the Git dependency block are YAML parse errors 2. Duplicate `name` values in the dependencies list is an error thrown when the package file is processed 3. The `ref` doesn't exist in the clone or the clone directory is otherwise corrupt. An error with a suggestion to `juvix clean` is given. The package file path is used as the location in the error message. 4. Other `git` command errors (command not found, etc.), a more verbose error is given with the arguments that were passed to the git command. ## Future work 1. Add an offline mode 2. Add a lock file mechanism that resolves branch/tag git refs to commit hashes * closes https://github.com/anoma/juvix/issues/2083 --------- Co-authored-by: Jan Mas Rovira <janmasrovira@gmail.com>
2023-09-01 14:37:06 +03:00
import Juvix.Data.Effect.Git
import Juvix.Data.Effect.Process
import Juvix.Data.Error.GenericError qualified as Error
import Juvix.Data.NameKind
import Juvix.Extra.Paths
import Juvix.Extra.Stdlib
import Juvix.Extra.Version
import Juvix.Prelude.Pretty
import Juvix.Prelude.Pretty qualified as P
import System.Console.ANSI qualified as Ansi
import System.Console.Haskeline
import System.Console.Repline
import System.Console.Repline qualified as Repline
printHelpTxt :: ReplOptions -> Repl ()
printHelpTxt opts = do
liftIO $ do
putStrLn normalCmds
let isDev = opts ^. replIsDev
when isDev (putStrLn devCmds)
where
normalCmds :: Text
normalCmds =
[__i|
EXPRESSION Evaluate an expression in the context of the currently loaded module
:help Print help text and describe options
:load FILE Load a file into the REPL
:reload Reload the currently loaded file
:type EXPRESSION Infer the type of an expression
:def IDENTIFIER Print the definition of the identifier
2023-05-30 19:19:39 +03:00
:doc IDENTIFIER Print the documentation of the identifier
:core EXPRESSION Translate the expression to JuvixCore
:multiline Start a multi-line input. Submit with <Ctrl-D>
:root Print the current project root
:version Display the Juvix version
:quit Exit the REPL
|]
devCmds :: Text
devCmds =
[__i|
:dev DEV CMD Command reserved for debugging
|]
replDefaultLoc :: Interval
replDefaultLoc = singletonInterval (mkInitialLoc replPath)
replFromJust :: Repl a -> Maybe a -> Repl a
replFromJust err = maybe err return
replFromEither :: Either JuvixError a -> Repl a
replFromEither = either (lift . Except.throwError) return
replGetContext :: Repl ReplContext
replGetContext = State.gets (^. replStateContext) >>= replFromJust noFileLoadedErr
replError :: AnsiText -> Repl a
replError msg =
lift
. Except.throwError
. JuvixError
$ GenericError
{ _genericErrorLoc = replDefaultLoc,
_genericErrorMessage = msg,
_genericErrorIntervals = [replDefaultLoc]
}
noFileLoadedErr :: Repl a
noFileLoadedErr = replError (mkAnsiText @Text "No file loaded. Load a file using the `:load FILE` command.")
welcomeMsg :: (MonadIO m) => m ()
welcomeMsg = liftIO (putStrLn [i|Juvix REPL version #{versionTag}: https://juvix.org. Run :help for help|])
multilineCmd :: String
multilineCmd = "multiline"
quit :: String -> Repl ()
quit _ = liftIO (throwIO Interrupt)
loadEntryPoint :: EntryPoint -> Repl ()
loadEntryPoint ep = do
artif <- liftIO (corePipelineIO' ep)
let newCtx =
ReplContext
{ _replContextArtifacts = artif,
_replContextEntryPoint = ep
}
State.modify (set replStateContext (Just newCtx))
let epPath :: Maybe (Path Abs File) = ep ^? entryPointModulePaths . _head
whenJust epPath $ \path -> liftIO (putStrLn [i|OK loaded: #{toFilePath path}|])
reloadFile :: String -> Repl ()
reloadFile _ = replGetContext >>= loadEntryPoint . (^. replContextEntryPoint)
pSomeFile :: String -> Prepath File
pSomeFile = mkPrepath
loadFile :: Prepath File -> Repl ()
loadFile f = do
entryPoint <- getReplEntryPointFromPrepath f
loadEntryPoint entryPoint
loadDefaultPrelude :: Repl ()
loadDefaultPrelude = whenJustM defaultPreludeEntryPoint $ \e -> do
root <- Reader.asks (^. replRoots . rootsRootDir)
2023-09-07 19:26:19 +03:00
let hasInternet = not (e ^. entryPointOffline)
-- The following is needed to ensure that the default location of the
-- standard library exists
void
. liftIO
. runM
2023-09-07 19:26:19 +03:00
. evalInternet hasInternet
. runFilesIO
. runError @JuvixError
. runReader e
External package dependencies (#2272) This PR adds external git dependency support to the Juvix package format. ## New dependency Git item You can now add a `git` block to the dependencies list: ```yaml name: HelloWorld main: HelloWorld.juvix dependencies: - .juvix-build/stdlib - git: url: https://my.git.repo name: myGitRepo ref: main version: 0.1.0 ``` Git block required fields: * `url`: The URL of the git repository * `ref`: The git reference that should be checked out * `name`: The name for the dependency. This is used to name the directory of the clone, it is required. Perhaps we could come up with a way to automatically name the clone directory. Current ideas are to somehow encode the URL / ref combination or use a UUID. However there's some value in having the clone directory named in a friendly way. NB: * The values of the `name` fields must be unique among the git blocks in the dependencies list. ## Behaviour When dependencies for a package are registered, at the beginning of the compiler pipeline, all remote dependencies are processed: 1. If it doesn't already exist, the remote dependency is cloned to `.juvix-build/deps/$name` 2. `git fetch` is run in the clone 3. `git checkout` at the specified `ref` is run in the clone The clone is then processed by the PathResolver in the same way as path dependencies. NB: * Remote dependencies of transitive dependencies are also processed. * The `git fetch` step is required for the case where the remote is updated. In this case we want the user to be able to update the `ref` field. ## Errors 1. Missing fields in the Git dependency block are YAML parse errors 2. Duplicate `name` values in the dependencies list is an error thrown when the package file is processed 3. The `ref` doesn't exist in the clone or the clone directory is otherwise corrupt. An error with a suggestion to `juvix clean` is given. The package file path is used as the location in the error message. 4. Other `git` command errors (command not found, etc.), a more verbose error is given with the arguments that were passed to the git command. ## Future work 1. Add an offline mode 2. Add a lock file mechanism that resolves branch/tag git refs to commit hashes * closes https://github.com/anoma/juvix/issues/2083 --------- Co-authored-by: Jan Mas Rovira <janmasrovira@gmail.com>
2023-09-01 14:37:06 +03:00
. runLogIO
. runProcessIO
. runError @GitProcessError
2023-09-07 19:26:19 +03:00
. runGitProcess
External package dependencies (#2272) This PR adds external git dependency support to the Juvix package format. ## New dependency Git item You can now add a `git` block to the dependencies list: ```yaml name: HelloWorld main: HelloWorld.juvix dependencies: - .juvix-build/stdlib - git: url: https://my.git.repo name: myGitRepo ref: main version: 0.1.0 ``` Git block required fields: * `url`: The URL of the git repository * `ref`: The git reference that should be checked out * `name`: The name for the dependency. This is used to name the directory of the clone, it is required. Perhaps we could come up with a way to automatically name the clone directory. Current ideas are to somehow encode the URL / ref combination or use a UUID. However there's some value in having the clone directory named in a friendly way. NB: * The values of the `name` fields must be unique among the git blocks in the dependencies list. ## Behaviour When dependencies for a package are registered, at the beginning of the compiler pipeline, all remote dependencies are processed: 1. If it doesn't already exist, the remote dependency is cloned to `.juvix-build/deps/$name` 2. `git fetch` is run in the clone 3. `git checkout` at the specified `ref` is run in the clone The clone is then processed by the PathResolver in the same way as path dependencies. NB: * Remote dependencies of transitive dependencies are also processed. * The `git fetch` step is required for the case where the remote is updated. In this case we want the user to be able to update the `ref` field. ## Errors 1. Missing fields in the Git dependency block are YAML parse errors 2. Duplicate `name` values in the dependencies list is an error thrown when the package file is processed 3. The `ref` doesn't exist in the clone or the clone directory is otherwise corrupt. An error with a suggestion to `juvix clean` is given. The package file path is used as the location in the error message. 4. Other `git` command errors (command not found, etc.), a more verbose error is given with the arguments that were passed to the git command. ## Future work 1. Add an offline mode 2. Add a lock file mechanism that resolves branch/tag git refs to commit hashes * closes https://github.com/anoma/juvix/issues/2083 --------- Co-authored-by: Jan Mas Rovira <janmasrovira@gmail.com>
2023-09-01 14:37:06 +03:00
. runError @DependencyError
Use a Juvix source file to define a package (#2434) Depends on: * ~~https://github.com/anoma/juvix/pull/2459~~ * https://github.com/anoma/juvix/pull/2462 This PR is part of a series implementing: * https://github.com/anoma/juvix/issues/2336 This PR adds the package file loading function, including a file evaluation effect. It integrates this with the existing `readPackage` function and adds tests / smoke tests. ## Package.juvix format Instead of `juvix.yaml` (which is still supported currently) users can now place a `Package.juvix` file in the root of their project. The simplest `Package.juvix` file you can write is: ``` module Package; import PackageDescription open; package : Package := defaultPackage; ``` The [PackageDescription](https://github.com/anoma/juvix/blob/35b2f618f093895f32929ac0f2c0affcdab8d627/include/package/PackageDescription.juvix) module defines the `Package` type. Users can use "go-to definition" in their IDE from the Package file to see the documentation and definitions. Users may also import `Stdlib.Prelude` in their Package file. This is loaded from the global project. No other module imports are supported. Notes: * If a directory contains both `Package.juvix` and `juvix.yaml` then `Package.juvix` is used in preference. ## Default stdlib dependency The `Dependency` type has a constructor called `defaultStdlib`. This means that any project can use the compiler builtin standard library dependency. With `juvix.yaml` this dependency is only available when the `dependencies` field is unspecified. ``` module Package; import PackageDescription open; package : Package := defaultPackage { dependencies := [defaultStdlib] }; ``` ## Validation As well as the standard type checking validation that the Juvix compiler provides additional validation is made on the file. * The Package module must contain the identifier `package` and it must have type `Package` that's obtained from the global `PackageDescription` module. * Every dependency specified in the Package.juvix must be unique. * Closes https://github.com/anoma/juvix/issues/2336 ## Examples ### Package with name and version ``` module Package; import PackageDescription open; package : Package := defaultPackage {name := "a-package"; version := mkVersion 0 1 0}; ``` ### Package with GitHub dependency ``` module Package; import PackageDescription open; package : Package := defaultPackage {name := "a-package"; version := mkVersion 0 1 0; dependencies := [defaultStdlib; github (org := "anoma"; repo := "juvix-containers"; ref := "v0.7.1")]}; ``` ## Package with main and buildDir fields ``` module Package; import Stdlib.Prelude open; import PackageDescription open; package : Package := defaultPackage {name := "a-package"; version := mkVersion 0 1 0; dependencies := [defaultStdlib; github (org := "anoma"; repo := "juvix-containers"; ref := "v0.7.1")]; buildDir := just "/tmp/build"; main := just "HelloWorld.juvix" }; ```
2023-10-27 14:35:20 +03:00
. runError @PackageLoaderError
. runEvalFileEffIO
. runPathResolver root
Add `juvix dependencies update` command (#2419) This PR adds a new command `juvix dependencies update` that fetches all dependencies in a project and updates the project lock file. Currently the only way to update the lock file is to delete it and generate a new one. ## CLI Docs ``` juvix dependencies --help Usage: juvix dependencies COMMAND Subcommands related to dependencies Available options: -h,--help Show this help text Available commands: update Fetch package dependencies and update the lock file ``` ## Example A project containing the following `juvix.yaml` ```yaml dependencies: - .juvix-build/stdlib/ - git: url: https://github.com/anoma/juvix-test ref: v0.6.0 name: test main: Example.juvix name: example version: 1.0.0 ``` compile to generate the lockfile: `juvix compile` ```yaml # This file was autogenerated by Juvix version 0.5.1. # Do not edit this file manually. dependencies: - path: .juvix-build/stdlib/ dependencies: [] - git: name: test ref: a94c61749678ff57556ee6e4cb1f8fbbddbc4ab1 url: https://github.com/anoma/juvix-test dependencies: - git: name: stdlib ref: 4facf14d9b2d06b81ce1be1882aa9050f768cb45 url: https://github.com/anoma/juvix-stdlib dependencies: [] ``` Now update the test dependency version: ```yaml - .juvix-build/stdlib/ - git: url: https://github.com/anoma/juvix-test ref: v0.6.1 name: test main: Example.juvix name: example version: 1.0.0 ``` And run `juvix dependencies update` Now the lockfile has updated to the hash of v0.6.1: ```yaml # This file was autogenerated by Juvix version 0.5.1. # Do not edit this file manually. dependencies: - path: .juvix-build/stdlib/ dependencies: [] - git: name: test ref: a7ac74cac0db92e0b5e349f279d797c3788cdfdd url: https://github.com/anoma/juvix-test dependencies: - git: name: stdlib ref: 4facf14d9b2d06b81ce1be1882aa9050f768cb45 url: https://github.com/anoma/juvix-stdlib dependencies: [] ``` --------- Co-authored-by: Jonathan Cubides <jonathan.cubides@uib.no>
2023-10-03 19:09:13 +03:00
$ entrySetup defaultDependenciesConfig
loadEntryPoint e
getReplEntryPoint :: (Roots -> a -> GlobalOptions -> IO EntryPoint) -> a -> Repl EntryPoint
getReplEntryPoint f inputFile = do
roots <- Reader.asks (^. replRoots)
gopts <- State.gets (^. replStateGlobalOptions)
liftIO (set entryPointSymbolPruningMode KeepAll <$> f roots inputFile gopts)
getReplEntryPointFromPrepath :: Prepath File -> Repl EntryPoint
getReplEntryPointFromPrepath = getReplEntryPoint entryPointFromGlobalOptionsPre
getReplEntryPointFromPath :: Path Abs File -> Repl EntryPoint
getReplEntryPointFromPath = getReplEntryPoint entryPointFromGlobalOptions
displayVersion :: String -> Repl ()
displayVersion _ = liftIO (putStrLn versionTag)
replCommand :: ReplOptions -> String -> Repl ()
replCommand opts input = catchAll $ do
ctx <- replGetContext
let tab = ctx ^. replContextArtifacts . artifactCoreTable
evalRes <- compileThenEval ctx input
whenJust evalRes $ \n ->
if
| Info.member Info.kNoDisplayInfo (Core.getInfo n) -> return ()
| opts ^. replPrintValues ->
renderOutLn (Core.ppOut opts (toValue tab n))
| otherwise -> renderOutLn (Core.ppOut opts n)
where
compileThenEval :: ReplContext -> String -> Repl (Maybe Core.Node)
compileThenEval ctx s = compileString >>= mapM eval
where
artif :: Artifacts
artif = ctx ^. replContextArtifacts
eval :: Core.Node -> Repl Core.Node
eval n = do
ep <- getReplEntryPointFromPrepath (mkPrepath (toFilePath replPath))
let shouldDisambiguate :: Bool
shouldDisambiguate = not (opts ^. replNoDisambiguate)
(artif', n') <-
replFromEither
. run
. runReader ep
. runError @JuvixError
. runState artif
. runTransformations shouldDisambiguate (opts ^. replTransformations)
$ n
liftIO (doEvalIO' artif' n') >>= replFromEither
doEvalIO' :: Artifacts -> Core.Node -> IO (Either JuvixError Core.Node)
doEvalIO' artif' n =
mapLeft (JuvixError @Core.CoreError)
<$> doEvalIO False replDefaultLoc (artif' ^. artifactCoreTable) n
compileString :: Repl (Maybe Core.Node)
compileString = do
(artifacts, res) <- liftIO $ compileReplInputIO' ctx (strip (pack s))
res' <- replFromEither res
State.modify (over (replStateContext . _Just) (set replContextArtifacts artifacts))
return res'
core :: String -> Repl ()
core input = do
ctx <- replGetContext
opts <- Reader.asks (^. replOptions)
compileRes <- liftIO (compileReplInputIO' ctx (strip (pack input))) >>= replFromEither . snd
whenJust compileRes (renderOutLn . Core.ppOut opts)
dev :: String -> Repl ()
dev input = do
ctx <- replGetContext
if
| input == scoperStateCmd -> do
renderOutLn (Concrete.ppTrace (ctx ^. replContextArtifacts . artifactScoperState))
| otherwise ->
renderOutLn
( "Unrecognized command "
<> input
<> "\nAvailable commands: "
<> unwords cmds
)
where
cmds :: [String]
cmds = [scoperStateCmd]
scoperStateCmd :: String
scoperStateCmd = "scoperState"
ppConcrete :: (Concrete.PrettyPrint a) => a -> Repl AnsiText
ppConcrete a = do
gopts <- State.gets (^. replStateGlobalOptions)
let popts :: GenericOptions = project' gopts
return (Concrete.ppOut popts a)
printConcrete :: (Concrete.PrettyPrint a) => a -> Repl ()
2023-05-30 19:19:39 +03:00
printConcrete = ppConcrete >=> renderOut
printConcreteLn :: (Concrete.PrettyPrint a) => a -> Repl ()
2023-05-30 19:19:39 +03:00
printConcreteLn = ppConcrete >=> renderOutLn
replParseIdentifiers :: String -> Repl (NonEmpty Concrete.ScopedIden)
replParseIdentifiers input =
replExpressionUpToScopedAtoms (strip (pack input))
>>= getIdentifiers
where
getIdentifiers :: Concrete.ExpressionAtoms 'Concrete.Scoped -> Repl (NonEmpty Concrete.ScopedIden)
getIdentifiers as = mapM getIdentifier (as ^. Concrete.expressionAtoms)
where
getIdentifier :: Concrete.ExpressionAtom 'Concrete.Scoped -> Repl (Concrete.ScopedIden)
getIdentifier = \case
Concrete.AtomIdentifier a -> return a
Concrete.AtomParens p
| Concrete.ExpressionIdentifier a <- p -> return a
| Concrete.ExpressionParensIdentifier a <- p -> return a
_ -> err
where
err :: Repl a
err = replError (mkAnsiText @Text ":def expects one or more identifiers")
2023-05-30 19:19:39 +03:00
printDocumentation :: String -> Repl ()
printDocumentation = replParseIdentifiers >=> printIdentifiers
where
printIdentifiers :: NonEmpty Concrete.ScopedIden -> Repl ()
printIdentifiers (d :| ds) = do
printIdentifier d
whenJust (nonEmpty ds) $ \ds' -> replNewline >> printIdentifiers ds'
where
getInfoTable :: Repl Scoped.InfoTable
getInfoTable = (^. replContextArtifacts . artifactScopeTable) <$> replGetContext
printIdentifier :: Concrete.ScopedIden -> Repl ()
printIdentifier s = do
let n = s ^. Concrete.scopedIdenFinal . Scoped.nameId
2023-07-26 10:59:50 +03:00
mdoc <- case getNameKind s of
KNameAxiom -> getDocAxiom n
KNameInductive -> getDocInductive n
KNameLocal -> return Nothing
KNameFunction -> getDocFunction n
KNameConstructor -> getDocConstructor n
KNameLocalModule -> impossible
KNameTopModule -> impossible
KNameAlias -> impossible
KNameFixity -> impossible
2023-05-30 19:19:39 +03:00
printDoc mdoc
where
printDoc :: Maybe (Concrete.Judoc 'Concrete.Scoped) -> Repl ()
printDoc = \case
Nothing -> do
s' <- ppConcrete s
renderOut (mkAnsiText @Text "No documentation available for ")
2023-05-30 19:19:39 +03:00
renderOutLn s'
Just ju -> printConcrete ju
getDocFunction :: Scoped.NameId -> Repl (Maybe (Concrete.Judoc 'Concrete.Scoped))
getDocFunction fun = do
tbl :: Scoped.InfoTable <- getInfoTable
let def :: Scoped.FunctionInfo = tbl ^?! Scoped.infoFunctions . at fun . _Just
return (def ^. Scoped.functionInfoDoc)
2023-05-30 19:19:39 +03:00
getDocInductive :: Scoped.NameId -> Repl (Maybe (Concrete.Judoc 'Concrete.Scoped))
getDocInductive ind = do
tbl :: Scoped.InfoTable <- (^. replContextArtifacts . artifactScopeTable) <$> replGetContext
let def :: Concrete.InductiveDef 'Concrete.Scoped = tbl ^?! Scoped.infoInductives . at ind . _Just . Scoped.inductiveInfoDef
return (def ^. Concrete.inductiveDoc)
getDocAxiom :: Scoped.NameId -> Repl (Maybe (Concrete.Judoc 'Concrete.Scoped))
getDocAxiom ax = do
tbl :: Scoped.InfoTable <- (^. replContextArtifacts . artifactScopeTable) <$> replGetContext
let def :: Concrete.AxiomDef 'Concrete.Scoped = tbl ^?! Scoped.infoAxioms . at ax . _Just . Scoped.axiomInfoDef
return (def ^. Concrete.axiomDoc)
getDocConstructor :: Scoped.NameId -> Repl (Maybe (Concrete.Judoc 'Concrete.Scoped))
getDocConstructor c = do
tbl :: Scoped.InfoTable <- (^. replContextArtifacts . artifactScopeTable) <$> replGetContext
let def :: Scoped.ConstructorInfo = tbl ^?! Scoped.infoConstructors . at c . _Just
return (def ^. Scoped.constructorInfoDef . Concrete.constructorDoc)
printDefinition :: String -> Repl ()
printDefinition = replParseIdentifiers >=> printIdentifiers
where
printIdentifiers :: NonEmpty Concrete.ScopedIden -> Repl ()
printIdentifiers (d :| ds) = do
printIdentifier d
whenJust (nonEmpty ds) $ \ds' -> replNewline >> printIdentifiers ds'
where
getInfoTable :: Repl Scoped.InfoTable
getInfoTable = (^. replContextArtifacts . artifactScopeTable) <$> replGetContext
printIdentifier :: Concrete.ScopedIden -> Repl ()
2023-07-26 10:59:50 +03:00
printIdentifier s =
let n = s ^. Concrete.scopedIdenFinal . Scoped.nameId
2023-07-26 10:59:50 +03:00
in case getNameKind s of
KNameAxiom -> printAxiom n
KNameInductive -> printInductive n
KNameLocal -> return ()
KNameFunction -> printFunction n
KNameConstructor -> printConstructor n
KNameLocalModule -> impossible
KNameTopModule -> impossible
KNameFixity -> impossible
KNameAlias -> impossible
where
printLocation :: (HasLoc s) => s -> Repl ()
printLocation def = do
s' <- ppConcrete s
let txt :: Text = " is " <> prettyText (nameKindWithArticle (getNameKind s)) <> " defined at " <> prettyText (getLoc def)
renderOutLn (s' <> mkAnsiText txt)
printFunction :: Scoped.NameId -> Repl ()
printFunction fun = do
tbl :: Scoped.InfoTable <- getInfoTable
case tbl ^. Scoped.infoFunctions . at fun of
Just def -> do
printLocation def
printConcreteLn def
Nothing -> return ()
printInductive :: Scoped.NameId -> Repl ()
printInductive ind = do
tbl :: Scoped.InfoTable <- (^. replContextArtifacts . artifactScopeTable) <$> replGetContext
let def :: Concrete.InductiveDef 'Concrete.Scoped = tbl ^?! Scoped.infoInductives . at ind . _Just . Scoped.inductiveInfoDef
printLocation def
2023-05-30 19:19:39 +03:00
printConcreteLn def
printAxiom :: Scoped.NameId -> Repl ()
printAxiom ax = do
tbl :: Scoped.InfoTable <- (^. replContextArtifacts . artifactScopeTable) <$> replGetContext
let def :: Concrete.AxiomDef 'Concrete.Scoped = tbl ^?! Scoped.infoAxioms . at ax . _Just . Scoped.axiomInfoDef
printLocation def
2023-05-30 19:19:39 +03:00
printConcreteLn def
printConstructor :: Scoped.NameId -> Repl ()
printConstructor c = do
tbl :: Scoped.InfoTable <- (^. replContextArtifacts . artifactScopeTable) <$> replGetContext
let ind :: Scoped.Symbol = tbl ^?! Scoped.infoConstructors . at c . _Just . Scoped.constructorInfoTypeName
printInductive (ind ^. Scoped.nameId)
inferType :: String -> Repl ()
inferType input = do
gopts <- State.gets (^. replStateGlobalOptions)
n <- replExpressionUpToTyped (strip (pack input))
renderOutLn (Internal.ppOut (project' @GenericOptions gopts) (n ^. Internal.typedType))
replCommands :: ReplOptions -> [(String, String -> Repl ())]
replCommands opts = catchable ++ nonCatchable
where
nonCatchable :: [(String, String -> Repl ())]
nonCatchable =
[ ("quit", quit)
]
catchable :: [(String, String -> Repl ())]
catchable =
map
(second (catchAll .))
[ ("help", const (printHelpTxt opts)),
-- `multiline` is included here for auto-completion purposes only.
-- `repline`'s `multilineCommand` logic overrides this no-op.
(multilineCmd, const (return ())),
("load", loadFile . pSomeFile),
("reload", reloadFile),
("root", printRoot),
("def", printDefinition),
2023-05-30 19:19:39 +03:00
("doc", printDocumentation),
("type", inferType),
("version", displayVersion),
("core", core),
("dev", dev)
]
catchAll :: Repl () -> Repl ()
catchAll = Repline.dontCrash . catchJuvixError
where
catchJuvixError :: Repl () -> Repl ()
catchJuvixError (HaskelineT m) = HaskelineT (mapInputT_ catchErrorS m)
where
catchErrorS :: ReplS () -> ReplS ()
catchErrorS = (`Except.catchError` printErrorS)
defaultMatcher :: [(String, CompletionFunc ReplS)]
defaultMatcher = [(":load", fileCompleter)]
optsCompleter :: WordCompleter ReplS
optsCompleter n = do
opts <- Reader.asks (^. replOptions)
let names = (":" <>) . fst <$> replCommands opts
return (filter (isPrefixOf n) names)
replBanner :: MultiLine -> Repl String
replBanner = \case
MultiLine -> return "... "
SingleLine -> do
mmodulePath <-
State.gets
( ^?
replStateContext
. _Just
. replContextArtifacts
. artifactMainModuleScope
. _Just
. scopePath
. absTopModulePath
)
return $ case mmodulePath of
Just path -> [i|#{unpack (P.prettyText path)}> |]
Nothing -> "juvix> "
replPrefix :: Maybe Char
replPrefix = Just ':'
replMultilineCommand :: Maybe String
replMultilineCommand = Just multilineCmd
replInitialiser :: Repl ()
replInitialiser = do
gopts <- State.gets (^. replStateGlobalOptions)
opts <- Reader.asks (^. replOptions)
welcomeMsg
unless
(opts ^. replNoPrelude || gopts ^. globalNoStdlib)
(maybe loadDefaultPrelude (loadFile . (^. pathPath)) (opts ^. replInputFile))
replFinaliser :: Repl ExitDecision
replFinaliser = return Exit
replTabComplete :: CompleterStyle ReplS
replTabComplete = Prefix (wordCompleter optsCompleter) defaultMatcher
printRoot :: String -> Repl ()
printRoot _ = do
r <- State.gets (^. replStateRoots . rootsRootDir)
liftIO $ putStrLn (pack (toFilePath r))
runCommand :: (Members '[Embed IO, App] r) => ReplOptions -> Sem r ()
runCommand opts = do
roots <- askRoots
let replAction :: ReplS ()
2022-12-20 15:05:40 +03:00
replAction = do
evalReplOpts
ReplOpts
{ prefix = replPrefix,
multilineCommand = replMultilineCommand,
initialiser = replInitialiser,
finaliser = replFinaliser,
tabComplete = replTabComplete,
command = replCommand opts,
options = replCommands opts,
banner = replBanner
2022-12-20 15:05:40 +03:00
}
globalOptions <- askGlobalOptions
let env =
ReplEnv
{ _replRoots = roots,
_replOptions = opts
}
iniState =
ReplState
{ _replStateRoots = roots,
_replStateContext = Nothing,
_replStateGlobalOptions = globalOptions
}
e <-
embed
. Except.runExceptT
. (`State.evalStateT` iniState)
. (`Reader.runReaderT` env)
$ replAction
case e of
Left {} -> error "impossible: uncaught exception"
Right () -> return ()
-- | If the package contains the stdlib as a dependency, loads the Prelude
defaultPreludeEntryPoint :: Repl (Maybe EntryPoint)
defaultPreludeEntryPoint = do
roots <- State.gets (^. replStateRoots)
let buildDir = roots ^. rootsBuildDir
root = roots ^. rootsRootDir
pkg = roots ^. rootsPackage
mstdlibPath <- liftIO (runM (runFilesIO (packageStdlib root buildDir (pkg ^. packageDependencies))))
case mstdlibPath of
Just stdlibPath ->
Just
. set entryPointResolverRoot stdlibPath
<$> getReplEntryPointFromPath (stdlibPath <//> preludePath)
Nothing -> return Nothing
2022-12-20 15:05:40 +03:00
replMakeAbsolute :: SomeBase b -> Repl (Path Abs b)
replMakeAbsolute = \case
Abs p -> return p
Rel r -> do
invokeDir <- State.gets (^. replStateRoots . rootsInvokeDir)
2022-12-20 15:05:40 +03:00
return (invokeDir <//> r)
replExpressionUpToScopedAtoms :: Text -> Repl (Concrete.ExpressionAtoms 'Concrete.Scoped)
replExpressionUpToScopedAtoms txt = do
ctx <- replGetContext
x <-
liftIO
. runM
. runError
. evalState (ctx ^. replContextArtifacts)
. runReader (ctx ^. replContextEntryPoint)
$ expressionUpToAtomsScoped replPath txt
replFromEither x
replExpressionUpToTyped :: Text -> Repl Internal.TypedExpression
replExpressionUpToTyped txt = do
ctx <- replGetContext
x <-
liftIO
. runM
. runError
. evalState (ctx ^. replContextArtifacts)
. runReader (ctx ^. replContextEntryPoint)
$ expressionUpToTyped replPath txt
replFromEither x
compileReplInputIO' :: ReplContext -> Text -> IO (Artifacts, (Either JuvixError (Maybe Core.Node)))
compileReplInputIO' ctx txt =
runM
. runState (ctx ^. replContextArtifacts)
. runReader (ctx ^. replContextEntryPoint)
$ do
r <- compileReplInputIO replPath txt
return (extractNode <$> r)
where
extractNode :: ReplPipelineResult -> Maybe Core.Node
extractNode = \case
ReplPipelineResultNode n -> Just n
ReplPipelineResultImport {} -> Nothing
ReplPipelineResultOpen {} -> Nothing
render' :: (P.HasAnsiBackend a, P.HasTextBackend a) => a -> Repl ()
render' t = do
opts <- State.gets (^. replStateGlobalOptions)
hasAnsi <- liftIO (Ansi.hSupportsANSIColor stdout)
liftIO (P.renderIO (not (opts ^. globalNoColors) && hasAnsi) t)
replNewline :: Repl ()
replNewline = liftIO (putStrLn "")
renderOut :: (P.HasAnsiBackend a, P.HasTextBackend a) => a -> Repl ()
renderOut = render'
renderOutLn :: (P.HasAnsiBackend a, P.HasTextBackend a) => a -> Repl ()
renderOutLn t = renderOut t >> replNewline
printErrorS :: JuvixError -> ReplS ()
printErrorS e = do
opts <- State.gets (^. replStateGlobalOptions)
hasAnsi <- liftIO (Ansi.hSupportsANSIColor stderr)
liftIO $ hPutStrLn stderr $ run (runReader (project' @GenericOptions opts) (Error.render (not (opts ^. globalNoColors) && hasAnsi) False e))
runTransformations ::
forall r.
(Members '[State Artifacts, Error JuvixError, Reader EntryPoint] r) =>
Bool ->
[Core.TransformationId] ->
Core.Node ->
Sem r Core.Node
runTransformations shouldDisambiguate ts n = runCoreInfoTableBuilderArtifacts $ do
sym <- addNode n
applyTransforms shouldDisambiguate ts
getNode sym
where
addNode :: Core.Node -> Sem (Core.InfoTableBuilder ': r) Core.Symbol
addNode node = do
sym <- Core.freshSymbol
Core.registerIdentNode sym node
-- `n` will get filtered out by the transformations unless it has a
-- corresponding entry in `infoIdentifiers`
tab <- Core.getInfoTable
let name = Core.freshIdentName tab "_repl"
idenInfo =
Core.IdentifierInfo
{ _identifierName = name,
_identifierSymbol = sym,
_identifierLocation = Nothing,
_identifierArgsNum = 0,
_identifierType = Core.mkDynamic',
_identifierIsExported = False,
_identifierBuiltin = Nothing,
_identifierPragmas = mempty,
_identifierArgNames = []
}
Core.registerIdent name idenInfo
return sym
applyTransforms :: Bool -> [Core.TransformationId] -> Sem (Core.InfoTableBuilder ': r) ()
applyTransforms shouldDisambiguate' ts' = do
tab <- Core.getInfoTable
tab' <- mapReader Core.fromEntryPoint $ Core.applyTransformations ts' tab
let tab'' =
if
| shouldDisambiguate' -> disambiguateNames tab'
| otherwise -> tab'
Core.setInfoTable tab''
getNode :: Core.Symbol -> Sem (Core.InfoTableBuilder ': r) Core.Node
getNode sym = fromMaybe impossible . flip Core.lookupIdentifierNode' sym <$> Core.getInfoTable