1
1
mirror of https://github.com/anoma/juvix.git synced 2024-12-29 18:43:42 +03:00
juvix/app/Commands/Repl.hs

665 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)
import Juvix.Data.CodeAnn (Ann)
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
Run test suite in parallel (#2507) ## Overview This PR makes the compiler pipeline thread-safe so that the test suite can be run in parallel. This is achieved by: * Removing use of `{get, set, with}CurrentDir` functions. * Adding locking around shared file resources like the the global-project and internal build directory. NB: **Locking is disabled for the main compiler target**, as it is single threaded they are not required. ## Run test suite in parallel To run the test suite in parallel you must add `--ta '+RTS -N -RTS'` to your stack test arguments. For example: ``` stack test --fast --ta '+RTS -N -RTS' ``` The `-N` instructs the Haskell runtime to choose the number of threads to use based on how many processors there are on your machine. You can use `-Nn` to see the number of threads to `n`. These flags are already [set in the Makefile](https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/Makefile#L26) when you or CI uses `stack test`. ## Locking The Haskell package [filelock](https://hackage.haskell.org/package/filelock) is used for locking. File locks are used instead of MVars because Juvix code does not control when new threads are created, they are created by the test suite. This means that MVars created by Juvix code will have no effect, because they are created independently on each test-suite thread. Additionally the resources we're locking live on the filesystem and so can be conveniently tagged by path. ### FileLock The filelock library is wrapped in a FileLock effect: https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/FileLock/Base.hs#L6-L8 There is an [IO interpreter](https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/FileLock/IO.hs#L8) that uses filelock and an [no-op interpreter](https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/FileLock/Permissive.hs#L7) that just runs actions unconditionally. ### TaggedLock To make the file locks simpler to use a TaggedLock effect is introduced: https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/TaggedLock/Base.hs#L5-L11 And convenience function: https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/TaggedLock.hs#L28 This allows an action to be locked, tagged by a directory that may or may not exist. For example in the following code, an action is performed on a directory `root` that may delete the directory before repopulating the files. So the lockfile cannot be stored in the `root` itself. https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Extra/Files.hs#L55-L60 ## Pipeline As noted above, we only use locking in the test suite. The main app target pipeline is single threaded and so locking is unnecessary. So the interpretation of locks is parameterised so that locking can be disabled https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Compiler/Pipeline/Run.hs#L64
2023-11-16 18:19:52 +03:00
import Juvix.Data.Effect.TaggedLock
import Juvix.Data.Error.GenericError qualified as Error
import Juvix.Data.NameKind
import Juvix.Extra.Paths qualified as P
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 P.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 (^. replRoot . rootRootDir)
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
Run test suite in parallel (#2507) ## Overview This PR makes the compiler pipeline thread-safe so that the test suite can be run in parallel. This is achieved by: * Removing use of `{get, set, with}CurrentDir` functions. * Adding locking around shared file resources like the the global-project and internal build directory. NB: **Locking is disabled for the main compiler target**, as it is single threaded they are not required. ## Run test suite in parallel To run the test suite in parallel you must add `--ta '+RTS -N -RTS'` to your stack test arguments. For example: ``` stack test --fast --ta '+RTS -N -RTS' ``` The `-N` instructs the Haskell runtime to choose the number of threads to use based on how many processors there are on your machine. You can use `-Nn` to see the number of threads to `n`. These flags are already [set in the Makefile](https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/Makefile#L26) when you or CI uses `stack test`. ## Locking The Haskell package [filelock](https://hackage.haskell.org/package/filelock) is used for locking. File locks are used instead of MVars because Juvix code does not control when new threads are created, they are created by the test suite. This means that MVars created by Juvix code will have no effect, because they are created independently on each test-suite thread. Additionally the resources we're locking live on the filesystem and so can be conveniently tagged by path. ### FileLock The filelock library is wrapped in a FileLock effect: https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/FileLock/Base.hs#L6-L8 There is an [IO interpreter](https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/FileLock/IO.hs#L8) that uses filelock and an [no-op interpreter](https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/FileLock/Permissive.hs#L7) that just runs actions unconditionally. ### TaggedLock To make the file locks simpler to use a TaggedLock effect is introduced: https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/TaggedLock/Base.hs#L5-L11 And convenience function: https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Data/Effect/TaggedLock.hs#L28 This allows an action to be locked, tagged by a directory that may or may not exist. For example in the following code, an action is performed on a directory `root` that may delete the directory before repopulating the files. So the lockfile cannot be stored in the `root` itself. https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Extra/Files.hs#L55-L60 ## Pipeline As noted above, we only use locking in the test suite. The main app target pipeline is single threaded and so locking is unnecessary. So the interpretation of locks is parameterised so that locking can be disabled https://github.com/anoma/juvix/blob/e6dca22cfdcff936add5e7134f9c6f20416504a5/src/Juvix/Compiler/Pipeline/Run.hs#L64
2023-11-16 18:19:52 +03:00
. runTaggedLockPermissive
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 :: (Root -> a -> GlobalOptions -> IO EntryPoint) -> a -> Repl EntryPoint
getReplEntryPoint f inputFile = do
root <- Reader.asks (^. replRoot)
gopts <- State.gets (^. replStateGlobalOptions)
liftIO (set entryPointSymbolPruningMode KeepAll <$> f root 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 P.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
let s' :: Doc Ann = pretty s
msg = "No documentation available for" <+> s'
renderOutLn (toAnsiText True msg)
2023-05-30 19:19:39 +03:00
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 (^. replStateRoot . rootRootDir)
liftIO $ putStrLn (pack (toFilePath r))
runCommand :: (Members '[Embed IO, App] r) => ReplOptions -> Sem r ()
runCommand opts = do
root <- askRoot
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
{ _replRoot = root,
_replOptions = opts
}
iniState =
ReplState
{ _replStateRoot = root,
_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
root <- State.gets (^. replStateRoot)
let buildRoot = root ^. rootRootDir
buildDir = resolveAbsBuildDir buildRoot (root ^. rootBuildDir)
pkg = root ^. rootPackage
mstdlibPath <- liftIO (runM (runFilesIO (packageStdlib buildRoot buildDir (pkg ^. packageDependencies))))
case mstdlibPath of
Just stdlibPath ->
Just
. set entryPointResolverRoot stdlibPath
<$> getReplEntryPointFromPath (stdlibPath <//> P.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 (^. replStateRoot . rootInvokeDir)
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 P.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 P.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 P.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