aboutsummaryrefslogtreecommitdiff
path: root/src/libcmd
diff options
context:
space:
mode:
authorGuillaume Maudoux <guillaume.maudoux@tweag.io>2022-09-07 00:34:03 +0200
committerGuillaume Maudoux <guillaume.maudoux@tweag.io>2022-09-07 00:34:03 +0200
commiteb460a9529dd79995b6b788d59322fbc8f989214 (patch)
tree2dec54ef6b3096d787cb49cb0f2f6b7041b1f6c1 /src/libcmd
parent9ff892aad4ee13532ea84cb6e4a2a53d70945efe (diff)
parent96001098796c9011d1670cc8a7acd00ef49b2d7a (diff)
WIP: broken merge but need a git checkpoint
Diffstat (limited to 'src/libcmd')
-rw-r--r--src/libcmd/command.cc12
-rw-r--r--src/libcmd/command.hh24
-rw-r--r--src/libcmd/installables.cc160
-rw-r--r--src/libcmd/installables.hh7
-rw-r--r--src/libcmd/local.mk4
-rw-r--r--src/libcmd/markdown.cc6
-rw-r--r--src/libcmd/repl.cc1130
7 files changed, 1291 insertions, 52 deletions
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index f28cfe5de..14bb27936 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -86,6 +86,11 @@ ref<Store> CopyCommand::getDstStore()
EvalCommand::EvalCommand()
{
+ addFlag({
+ .longName = "debugger",
+ .description = "start an interactive environment if evaluation fails",
+ .handler = {&startReplOnEvalErrors, true},
+ });
}
EvalCommand::~EvalCommand()
@@ -103,7 +108,7 @@ ref<Store> EvalCommand::getEvalStore()
ref<EvalState> EvalCommand::getEvalState()
{
- if (!evalState)
+ if (!evalState) {
evalState =
#if HAVE_BOEHMGC
std::allocate_shared<EvalState>(traceable_allocator<EvalState>(),
@@ -113,6 +118,11 @@ ref<EvalState> EvalCommand::getEvalState()
searchPath, getEvalStore(), getStore())
#endif
;
+
+ if (startReplOnEvalErrors) {
+ evalState->debugRepl = &runRepl;
+ };
+ }
return ref<EvalState>(evalState);
}
diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh
index 078e2a2ce..3b4b40981 100644
--- a/src/libcmd/command.hh
+++ b/src/libcmd/command.hh
@@ -57,6 +57,9 @@ struct CopyCommand : virtual StoreCommand
struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
+ bool startReplOnEvalErrors = false;
+ bool ignoreExceptionsDuringTry = false;
+
EvalCommand();
~EvalCommand();
@@ -75,10 +78,16 @@ struct MixFlakeOptions : virtual Args, EvalCommand
{
flake::LockFlags lockFlags;
+ std::optional<std::string> needsFlakeInputCompletion = {};
+
MixFlakeOptions();
- virtual std::optional<FlakeRef> getFlakeRefForCompletion()
+ virtual std::vector<std::string> getFlakesForCompletion()
{ return {}; }
+
+ void completeFlakeInput(std::string_view prefix);
+
+ void completionHook() override;
};
struct SourceExprCommand : virtual Args, MixFlakeOptions
@@ -114,12 +123,13 @@ struct InstallablesCommand : virtual Args, SourceExprCommand
InstallablesCommand();
void prepare() override;
+ Installables load();
virtual bool useDefaultInstallables() { return true; }
- std::optional<FlakeRef> getFlakeRefForCompletion() override;
+ std::vector<std::string> getFlakesForCompletion() override;
-private:
+protected:
std::vector<std::string> _installables;
};
@@ -133,9 +143,9 @@ struct InstallableCommand : virtual Args, SourceExprCommand
void prepare() override;
- std::optional<FlakeRef> getFlakeRefForCompletion() override
+ std::vector<std::string> getFlakesForCompletion() override
{
- return parseFlakeRefWithFragment(_installable, absPath(".")).first;
+ return {_installable};
}
private:
@@ -270,4 +280,8 @@ void printClosureDiff(
const StorePath & afterPath,
std::string_view indent);
+
+void runRepl(
+ ref<EvalState> evalState,
+ const ValMap & extraEnv);
}
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index 9f5eec2b5..00a7a33b7 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -23,17 +23,6 @@
namespace nix {
-void completeFlakeInputPath(
- ref<EvalState> evalState,
- const FlakeRef & flakeRef,
- std::string_view prefix)
-{
- auto flake = flake::getFlake(*evalState, flakeRef, true);
- for (auto & input : flake.inputs)
- if (hasPrefix(input.first, prefix))
- completions->add(input.first);
-}
-
MixFlakeOptions::MixFlakeOptions()
{
auto category = "Common flake-related options";
@@ -86,8 +75,7 @@ MixFlakeOptions::MixFlakeOptions()
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
}},
.completer = {[&](size_t, std::string_view prefix) {
- if (auto flakeRef = getFlakeRefForCompletion())
- completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
+ needsFlakeInputCompletion = {std::string(prefix)};
}}
});
@@ -103,12 +91,10 @@ MixFlakeOptions::MixFlakeOptions()
parseFlakeRef(flakeRef, absPath("."), true));
}},
.completer = {[&](size_t n, std::string_view prefix) {
- if (n == 0) {
- if (auto flakeRef = getFlakeRefForCompletion())
- completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
- } else if (n == 1) {
+ if (n == 0)
+ needsFlakeInputCompletion = {std::string(prefix)};
+ else if (n == 1)
completeFlakeRef(getEvalState()->store, prefix);
- }
}}
});
@@ -139,6 +125,24 @@ MixFlakeOptions::MixFlakeOptions()
});
}
+void MixFlakeOptions::completeFlakeInput(std::string_view prefix)
+{
+ auto evalState = getEvalState();
+ for (auto & flakeRefS : getFlakesForCompletion()) {
+ auto flakeRef = parseFlakeRefWithFragment(expandTilde(flakeRefS), absPath(".")).first;
+ auto flake = flake::getFlake(*evalState, flakeRef, true);
+ for (auto & input : flake.inputs)
+ if (hasPrefix(input.first, prefix))
+ completions->add(input.first);
+ }
+}
+
+void MixFlakeOptions::completionHook()
+{
+ if (auto & prefix = needsFlakeInputCompletion)
+ completeFlakeInput(*prefix);
+}
+
SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
{
addFlag({
@@ -146,7 +150,8 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
.shortName = 'f',
.description =
"Interpret installables as attribute paths relative to the Nix expression stored in *file*. "
- "If *file* is the character -, then a Nix expression will be read from standard input.",
+ "If *file* is the character -, then a Nix expression will be read from standard input. "
+ "Implies `--impure`.",
.category = installablesCategory,
.labels = {"file"},
.handler = {&file},
@@ -440,10 +445,8 @@ DerivedPaths InstallableValue::toDerivedPaths()
// Group by derivation, helps with .all in particular
for (auto & drv : toDerivations()) {
- auto outputName = drv.outputName;
- if (outputName == "")
- throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath));
- drvsToOutputs[drv.drvPath].insert(outputName);
+ for (auto & outputName : drv.outputsToInstall)
+ drvsToOutputs[drv.drvPath].insert(outputName);
drvsToCopy.insert(drv.drvPath);
}
@@ -466,9 +469,19 @@ struct InstallableAttrPath : InstallableValue
SourceExprCommand & cmd;
RootValue v;
std::string attrPath;
-
- InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath)
- : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath)
+ OutputsSpec outputsSpec;
+
+ InstallableAttrPath(
+ ref<EvalState> state,
+ SourceExprCommand & cmd,
+ Value * v,
+ const std::string & attrPath,
+ OutputsSpec outputsSpec)
+ : InstallableValue(state)
+ , cmd(cmd)
+ , v(allocRootValue(v))
+ , attrPath(attrPath)
+ , outputsSpec(std::move(outputsSpec))
{ }
std::string what() const override { return attrPath; }
@@ -497,7 +510,19 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
auto drvPath = drvInfo.queryDrvPath();
if (!drvPath)
throw Error("'%s' is not a derivation", what());
- res.push_back({ *drvPath, drvInfo.queryOutputName() });
+
+ std::set<std::string> outputsToInstall;
+
+ if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
+ outputsToInstall = *outputNames;
+ else
+ for (auto & output : drvInfo.queryOutputs(false, std::get_if<DefaultOutputs>(&outputsSpec)))
+ outputsToInstall.insert(output.first);
+
+ res.push_back(DerivationInfo {
+ .drvPath = *drvPath,
+ .outputsToInstall = std::move(outputsToInstall)
+ });
}
return res;
@@ -574,6 +599,7 @@ InstallableFlake::InstallableFlake(
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
+ OutputsSpec outputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags)
@@ -581,6 +607,7 @@ InstallableFlake::InstallableFlake(
flakeRef(flakeRef),
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
prefixes(fragment == "" ? Strings{} : prefixes),
+ outputsSpec(std::move(outputsSpec)),
lockFlags(lockFlags)
{
if (cmd && cmd->getAutoArgs(*state)->size())
@@ -589,6 +616,8 @@ InstallableFlake::InstallableFlake(
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
{
+ Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
+
auto attr = getCursor(*state);
auto attrPath = attr->getAttrPathStr();
@@ -598,9 +627,41 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
auto drvPath = attr->forceDerivation();
+ std::set<std::string> outputsToInstall;
+ std::optional<NixInt> priority;
+
+ if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
+ if (aOutputSpecified->getBool()) {
+ if (auto aOutputName = attr->maybeGetAttr("outputName"))
+ outputsToInstall = { aOutputName->getString() };
+ }
+ }
+
+ else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
+ if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
+ for (auto & s : aOutputsToInstall->getListOfStrings())
+ outputsToInstall.insert(s);
+ if (auto aPriority = aMeta->maybeGetAttr("priority"))
+ priority = aPriority->getInt();
+ }
+
+ if (outputsToInstall.empty() || std::get_if<AllOutputs>(&outputsSpec)) {
+ outputsToInstall.clear();
+ if (auto aOutputs = attr->maybeGetAttr(state->sOutputs))
+ for (auto & s : aOutputs->getListOfStrings())
+ outputsToInstall.insert(s);
+ }
+
+ if (outputsToInstall.empty())
+ outputsToInstall.insert("out");
+
+ if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
+ outputsToInstall = *outputNames;
+
auto drvInfo = DerivationInfo {
- std::move(drvPath),
- attr->getAttr(state->sOutputName)->getString()
+ .drvPath = std::move(drvPath),
+ .outputsToInstall = std::move(outputsToInstall),
+ .priority = priority,
};
return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
@@ -723,8 +784,14 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
state->eval(e, *vFile);
}
- for (auto & s : ss)
- result.push_back(std::make_shared<InstallableAttrPath>(state, *this, vFile, s == "." ? "" : s));
+ for (auto & s : ss) {
+ auto [prefix, outputsSpec] = parseOutputsSpec(s);
+ result.push_back(
+ std::make_shared<InstallableAttrPath>(
+ state, *this, vFile,
+ prefix == "." ? "" : prefix,
+ outputsSpec));
+ }
} else {
@@ -743,12 +810,13 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
}
try {
- auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath("."));
+ auto [flakeRef, fragment, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(s, absPath("."));
result.push_back(std::make_shared<InstallableFlake>(
this,
getEvalState(),
std::move(flakeRef),
fragment,
+ outputsSpec,
getDefaultFlakeAttrPaths(),
getDefaultFlakeAttrPathPrefixes(),
lockFlags));
@@ -822,12 +890,13 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
auto drvOutputs = drv.outputsAndOptPaths(*store);
for (auto & output : bfd.outputs) {
- if (!outputHashes.count(output))
+ auto outputHash = get(outputHashes, output);
+ if (!outputHash)
throw Error(
"the derivation '%s' doesn't have an output named '%s'",
store->printStorePath(bfd.drvPath), output);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
- DrvOutput outputId { outputHashes.at(output), output };
+ DrvOutput outputId { *outputHash, output };
auto realisation = store->queryRealisation(outputId);
if (!realisation)
throw Error(
@@ -838,10 +907,11 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
} else {
// If ca-derivations isn't enabled, assume that
// the output path is statically known.
- assert(drvOutputs.count(output));
- assert(drvOutputs.at(output).second);
+ auto drvOutput = get(drvOutputs, output);
+ assert(drvOutput);
+ assert(drvOutput->second);
outputs.insert_or_assign(
- output, *drvOutputs.at(output).second);
+ output, *drvOutput->second);
}
}
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
@@ -856,6 +926,9 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
break;
case Realise::Outputs: {
+ if (settings.printMissing)
+ printMissing(store, pathsToBuild, lvlInfo);
+
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
if (!buildResult.success())
buildResult.rethrow();
@@ -969,21 +1042,26 @@ InstallablesCommand::InstallablesCommand()
void InstallablesCommand::prepare()
{
+ installables = load();
+}
+
+Installables InstallablesCommand::load() {
+ Installables installables;
if (_installables.empty() && useDefaultInstallables())
// FIXME: commands like "nix profile install" should not have a
// default, probably.
_installables.push_back(".");
- installables = parseInstallables(getStore(), _installables);
+ return parseInstallables(getStore(), _installables);
}
-std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
+std::vector<std::string> InstallablesCommand::getFlakesForCompletion()
{
if (_installables.empty()) {
if (useDefaultInstallables())
- return parseFlakeRefWithFragment(".", absPath(".")).first;
+ return {"."};
return {};
}
- return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first;
+ return _installables;
}
InstallableCommand::InstallableCommand(bool supportReadOnlyMode)
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index de8b08525..948f78919 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -132,6 +132,8 @@ struct Installable
const std::vector<std::shared_ptr<Installable>> & installables);
};
+typedef std::vector<std::shared_ptr<Installable>> Installables;
+
struct InstallableValue : Installable
{
ref<EvalState> state;
@@ -141,7 +143,8 @@ struct InstallableValue : Installable
struct DerivationInfo
{
StorePath drvPath;
- std::string outputName;
+ std::set<std::string> outputsToInstall;
+ std::optional<NixInt> priority;
};
virtual std::vector<DerivationInfo> toDerivations() = 0;
@@ -156,6 +159,7 @@ struct InstallableFlake : InstallableValue
FlakeRef flakeRef;
Strings attrPaths;
Strings prefixes;
+ OutputsSpec outputsSpec;
const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
@@ -164,6 +168,7 @@ struct InstallableFlake : InstallableValue
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
+ OutputsSpec outputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags);
diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk
index 7a2f83cc7..3a4de6bcb 100644
--- a/src/libcmd/local.mk
+++ b/src/libcmd/local.mk
@@ -6,9 +6,9 @@ libcmd_DIR := $(d)
libcmd_SOURCES := $(wildcard $(d)/*.cc)
-libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers
+libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix
-libcmd_LDFLAGS += $(LOWDOWN_LIBS) -pthread
+libcmd_LDFLAGS = $(EDITLINE_LIBS) -llowdown -pthread
libcmd_LIBS = libstore libutil libexpr libmain libfetchers
diff --git a/src/libcmd/markdown.cc b/src/libcmd/markdown.cc
index 29bb4d31e..668a07763 100644
--- a/src/libcmd/markdown.cc
+++ b/src/libcmd/markdown.cc
@@ -9,14 +9,16 @@ namespace nix {
std::string renderMarkdownToTerminal(std::string_view markdown)
{
+ int windowWidth = getWindowSize().second;
+
struct lowdown_opts opts {
.type = LOWDOWN_TERM,
.maxdepth = 20,
- .cols = std::max(getWindowSize().second, (unsigned short) 80),
+ .cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0,
.vmargin = 0,
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
- .oflags = 0,
+ .oflags = LOWDOWN_TERM_NOLINK,
};
auto doc = lowdown_doc_new(&opts);
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
new file mode 100644
index 000000000..a8de6b80b
--- /dev/null
+++ b/src/libcmd/repl.cc
@@ -0,0 +1,1130 @@
+#include <iostream>
+#include <cstdlib>
+#include <cstring>
+#include <climits>
+
+#include <setjmp.h>
+
+#ifdef READLINE
+#include <readline/history.h>
+#include <readline/readline.h>
+#else
+// editline < 1.15.2 don't wrap their API for C++ usage
+// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
+// This results in linker errors due to to name-mangling of editline C symbols.
+// For compatibility with these versions, we wrap the API here
+// (wrapping multiple times on newer versions is no problem).
+extern "C" {
+#include <editline.h>
+}
+#endif
+
+#include "ansicolor.hh"
+#include "shared.hh"
+#include "eval.hh"
+#include "eval-cache.hh"
+#include "eval-inline.hh"
+#include "attr-path.hh"
+#include "store-api.hh"
+#include "log-store.hh"
+#include "common-eval-args.hh"
+#include "get-drvs.hh"
+#include "derivations.hh"
+#include "globals.hh"
+#include "command.hh"
+#include "finally.hh"
+#include "markdown.hh"
+#include "local-fs-store.hh"
+#include "progress-bar.hh"
+
+#if HAVE_BOEHMGC
+#define GC_INCLUDE_NEW
+#include <gc/gc_cpp.h>
+#endif
+
+namespace nix {
+
+struct NixRepl
+ #if HAVE_BOEHMGC
+ : gc
+ #endif
+{
+ std::string curDir;
+ ref<EvalState> state;
+ Bindings * autoArgs;
+
+ size_t debugTraceIndex;
+
+ Strings loadedFiles;
+ typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
+ std::function<AnnotatedValues()> getValues;
+
+ const static int envSize = 32768;
+ std::shared_ptr<StaticEnv> staticEnv;
+ Env * env;
+ int displ;
+ StringSet varNames;
+
+ const Path historyFile;
+
+ NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state,
+ std::function<AnnotatedValues()> getValues);
+ ~NixRepl();
+ void mainLoop();
+ StringSet completePrefix(const std::string & prefix);
+ bool getLine(std::string & input, const std::string & prompt);
+ StorePath getDerivationPath(Value & v);
+ bool processLine(std::string line);
+
+ void loadFile(const Path & path);
+ void loadFlake(const std::string & flakeRef);
+ void initEnv();
+ void loadFiles();
+ void reloadFiles();
+ void addAttrsToScope(Value & attrs);
+ void addVarToScope(const Symbol name, Value & v);
+ Expr * parseString(std::string s);
+ void evalString(std::string s, Value & v);
+ void loadDebugTraceEnv(DebugTrace & dt);
+
+ typedef std::set<Value *> ValuesSeen;
+ std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
+ std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
+};
+
+
+std::string removeWhitespace(std::string s)
+{
+ s = chomp(s);
+ size_t n = s.find_first_not_of(" \n\r\t");
+ if (n != std::string::npos) s = std::string(s, n);
+ return s;
+}
+
+
+NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
+ std::function<NixRepl::AnnotatedValues()> getValues)
+ : state(state)
+ , debugTraceIndex(0)
+ , getValues(getValues)
+ , staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
+ , historyFile(getDataDir() + "/nix/repl-history")
+{
+ curDir = absPath(".");
+}
+
+
+NixRepl::~NixRepl()
+{
+ write_history(historyFile.c_str());
+}
+
+void runNix(Path program, const Strings & args,
+ const std::optional<std::string> & input = {})
+{
+ auto subprocessEnv = getEnv();
+ subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue();
+
+ runProgram2(RunOptions {
+ .program = settings.nixBinDir+ "/" + program,
+ .args = args,
+ .environment = subprocessEnv,
+ .input = input,
+ });
+
+ return;
+}
+
+static NixRepl * curRepl; // ugly
+
+static char * completionCallback(char * s, int *match) {
+ auto possible = curRepl->completePrefix(s);
+ if (possible.size() == 1) {
+ *match = 1;
+ auto *res = strdup(possible.begin()->c_str() + strlen(s));
+ if (!res) throw Error("allocation failure");
+ return res;
+ } else if (possible.size() > 1) {
+ auto checkAllHaveSameAt = [&](size_t pos) {
+ auto &first = *possible.begin();
+ for (auto &p : possible) {
+ if (p.size() <= pos || p[pos] != first[pos])
+ return false;
+ }
+ return true;
+ };
+ size_t start = strlen(s);
+ size_t len = 0;
+ while (checkAllHaveSameAt(start + len)) ++len;
+ if (len > 0) {
+ *match = 1;
+ auto *res = strdup(std::string(*possible.begin(), start, len).c_str());
+ if (!res) throw Error("allocation failure");
+ return res;
+ }
+ }
+
+ *match = 0;
+ return nullptr;
+}
+
+static int listPossibleCallback(char *s, char ***avp) {
+ auto possible = curRepl->completePrefix(s);
+
+ if (possible.size() > (INT_MAX / sizeof(char*)))
+ throw Error("too many completions");
+
+ int ac = 0;
+ char **vp = nullptr;
+
+ auto check = [&](auto *p) {
+ if (!p) {
+ if (vp) {
+ while (--ac >= 0)
+ free(vp[ac]);
+ free(vp);
+ }
+ throw Error("allocation failure");
+ }
+ return p;
+ };
+
+ vp = check((char **)malloc(possible.size() * sizeof(char*)));
+
+ for (auto & p : possible)
+ vp[ac++] = check(strdup(p.c_str()));
+
+ *avp = vp;
+
+ return ac;
+}
+
+namespace {
+ // Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
+ volatile sig_atomic_t g_signal_received = 0;
+
+ void sigintHandler(int signo) {
+ g_signal_received = signo;
+ }
+}
+
+static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt)
+{
+ if (dt.isError)
+ out << ANSI_RED "error: " << ANSI_NORMAL;
+ out << dt.hint.str() << "\n";
+
+ // prefer direct pos, but if noPos then try the expr.
+ auto pos = *dt.pos
+ ? *dt.pos
+ : positions[dt.expr.getPos() ? dt.expr.getPos() : noPos];
+
+ if (pos) {
+ printAtPos(pos, out);
+
+ auto loc = getCodeLines(pos);
+ if (loc.has_value()) {
+ out << "\n";
+ printCodeLines(out, "", pos, *loc);
+ out << "\n";
+ }
+ }
+
+ return out;
+}
+
+void NixRepl::mainLoop()
+{
+ std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
+ notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
+
+ loadFiles();
+
+ // Allow nix-repl specific settings in .inputrc
+ rl_readline_name = "nix-repl";
+ createDirs(dirOf(historyFile));
+#ifndef READLINE
+ el_hist_size = 1000;
+#endif
+ read_history(historyFile.c_str());
+ curRepl = this;
+#ifndef READLINE
+ rl_set_complete_func(completionCallback);
+ rl_set_list_possib_func(listPossibleCallback);
+#endif
+
+ /* Stop the progress bar because it interferes with the display of
+ the repl. */
+ stopProgressBar();
+
+ std::string input;
+
+ while (true) {
+ // When continuing input from previous lines, don't print a prompt, just align to the same
+ // number of chars as the prompt.
+ if (!getLine(input, input.empty() ? "nix-repl> " : " ")) {
+ // ctrl-D should exit the debugger.
+ state->debugStop = false;
+ state->debugQuit = true;
+ break;
+ }
+ try {
+ if (!removeWhitespace(input).empty() && !processLine(input)) return;
+ } catch (ParseError & e) {
+ if (e.msg().find("unexpected end of file") != std::string::npos) {
+ // For parse errors on incomplete input, we continue waiting for the next line of
+ // input without clearing the input so far.
+ continue;
+ } else {
+ printMsg(lvlError, e.msg());
+ }
+ } catch (EvalError & e) {
+ // in debugger mode, an EvalError should trigger another repl session.
+ // when that session returns the exception will land here. No need to show it again;
+ // show the error for this repl session instead.
+ if (state->debugRepl && !state->debugTraces.empty())
+ showDebugTrace(std::cout, state->positions, state->debugTraces.front());
+ else
+ printMsg(lvlError, e.msg());
+ } catch (Error & e) {
+ printMsg(lvlError, e.msg());
+ } catch (Interrupted & e) {
+ printMsg(lvlError, e.msg());
+ }
+
+ // We handled the current input fully, so we should clear it
+ // and read brand new input.
+ input.clear();
+ std::cout << std::endl;
+ }
+}
+
+
+bool NixRepl::getLine(std::string & input, const std::string & prompt)
+{
+ struct sigaction act, old;
+ sigset_t savedSignalMask, set;
+
+ auto setupSignals = [&]() {
+ act.sa_handler = sigintHandler;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGINT, &act, &old))
+ throw SysError("installing handler for SIGINT");
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGINT);
+ if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
+ throw SysError("unblocking SIGINT");
+ };
+ auto restoreSignals = [&]() {
+ if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
+ throw SysError("restoring signals");
+
+ if (sigaction(SIGINT, &old, 0))
+ throw SysError("restoring handler for SIGINT");
+ };
+
+ setupSignals();
+ Finally resetTerminal([&]() { rl_deprep_terminal(); });
+ char * s = readline(prompt.c_str());
+ Finally doFree([&]() { free(s); });
+ restoreSignals();
+
+ if (g_signal_received) {
+ g_signal_received = 0;
+ input.clear();
+ return true;
+ }
+
+ if (!s)
+ return false;
+ input += s;
+ input += '\n';
+ return true;
+}
+
+
+StringSet NixRepl::completePrefix(const std::string & prefix)
+{
+ StringSet completions;
+
+ size_t start = prefix.find_last_of(" \n\r\t(){}[]");
+ std::string prev, cur;
+ if (start == std::string::npos) {
+ prev = "";
+ cur = prefix;
+ } else {
+ prev = std::string(prefix, 0, start + 1);
+ cur = std::string(prefix, start + 1);
+ }
+
+ size_t slash, dot;
+
+ if ((slash = cur.rfind('/')) != std::string::npos) {
+ try {
+ auto dir = std::string(cur, 0, slash);
+ auto prefix2 = std::string(cur, slash + 1);
+ for (auto & entry : readDirectory(dir == "" ? "/" : dir)) {
+ if (entry.name[0] != '.' && hasPrefix(entry.name, prefix2))
+ completions.insert(prev + dir + "/" + entry.name);
+ }
+ } catch (Error &) {
+ }
+ } else if ((dot = cur.rfind('.')) == std::string::npos) {
+ /* This is a variable name; look it up in the current scope. */
+ StringSet::iterator i = varNames.lower_bound(cur);
+ while (i != varNames.end()) {
+ if (i->substr(0, cur.size()) != cur) break;
+ completions.insert(prev + *i);
+ i++;
+ }
+ } else {
+ try {
+ /* This is an expression that should evaluate to an
+ attribute set. Evaluate it to get the names of the
+ attributes. */
+ auto expr = cur.substr(0, dot);
+ auto cur2 = cur.substr(dot + 1);
+
+ Expr * e = parseString(expr);
+ Value v;
+ e->eval(*state, *env, v);
+ state->forceAttrs(v, noPos, "nevermind, it is ignored anyway");
+
+ for (auto & i : *v.attrs) {
+ std::string_view name = state->symbols[i.name];
+ if (name.substr(0, cur2.size()) != cur2) continue;
+ completions.insert(concatStrings(prev, expr, ".", name));
+ }
+
+ } catch (ParseError & e) {
+ // Quietly ignore parse errors.
+ } catch (EvalError & e) {
+ // Quietly ignore evaluation errors.
+ } catch (UndefinedVarError & e) {
+ // Quietly ignore undefined variable errors.
+ } catch (BadURL & e) {
+ // Quietly ignore BadURL flake-related errors.
+ }
+ }
+
+ return completions;
+}
+
+
+static bool isVarName(std::string_view s)
+{
+ if (s.size() == 0) return false;
+ char c = s[0];
+ if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false;
+ for (auto & i : s)
+ if (!((i >= 'a' && i <= 'z') ||
+ (i >= 'A' && i <= 'Z') ||
+ (i >= '0' && i <= '9') ||
+ i == '_' || i == '-' || i == '\''))
+ return false;
+ return true;
+}
+
+
+StorePath NixRepl::getDerivationPath(Value & v) {
+ auto drvInfo = getDerivation(*state, v, false);
+ if (!drvInfo)
+ throw Error("expression does not evaluate to a derivation, so I can't build it");
+ auto drvPath = drvInfo->queryDrvPath();
+ if (!drvPath)
+ throw Error("expression did not evaluate to a valid derivation (no 'drvPath' attribute)");
+ if (!state->store->isValidPath(*drvPath))
+ throw Error("expression evaluated to invalid derivation '%s'", state->store->printStorePath(*drvPath));
+ return *drvPath;
+}
+
+void NixRepl::loadDebugTraceEnv(DebugTrace & dt)
+{
+ initEnv();
+
+ auto se = state->getStaticEnv(dt.expr);
+ if (se) {
+ auto vm = mapStaticEnvBindings(state->symbols, *se.get(), dt.env);
+
+ // add staticenv vars.
+ for (auto & [name, value] : *(vm.get()))
+ addVarToScope(state->symbols.create(name), *value);
+ }
+}
+
+bool NixRepl::processLine(std::string line)
+{
+ line = trim(line);
+ if (line == "") return true;
+
+ _isInterrupted = false;
+
+ std::string command, arg;
+
+ if (line[0] == ':') {
+ size_t p = line.find_first_of(" \n\r\t");
+ command = line.substr(0, p);
+ if (p != std::string::npos) arg = removeWhitespace(line.substr(p));
+ } else {
+ arg = line;
+ }
+
+ if (command == ":?" || command == ":help") {
+ // FIXME: convert to Markdown, include in the 'nix repl' manpage.
+ std::cout
+ << "The following commands are available:\n"
+ << "\n"
+ << " <expr> Evaluate and print expression\n"
+ << " <x> = <expr> Bind expression to variable\n"
+ << " :a <expr> Add attributes from resulting set to scope\n"
+ << " :b <expr> Build a derivation\n"
+ << " :bl <expr> Build a derivation, creating GC roots in the working directory\n"
+ << " :e <expr> Open package or function in $EDITOR\n"
+ << " :i <expr> Build derivation, then install result into current profile\n"
+ << " :l <path> Load Nix expression and add it to scope\n"
+ << " :lf <ref> Load Nix flake and add it to scope\n"
+ << " :p <expr> Evaluate and print expression recursively\n"
+ << " :q Exit nix-repl\n"
+ << " :r Reload all files\n"
+ << " :sh <expr> Build dependencies of derivation, then start nix-shell\n"
+ << " :t <expr> Describe result of evaluation\n"
+ << " :u <expr> Build derivation, then start nix-shell\n"
+ << " :doc <expr> Show documentation of a builtin function\n"
+ << " :log <expr> Show logs for a derivation\n"
+ << " :te [bool] Enable, disable or toggle showing traces for errors\n"
+ ;
+ if (state->debugRepl) {
+ std::cout
+ << "\n"
+ << " Debug mode commands\n"
+ << " :env Show env stack\n"
+ << " :bt Show trace stack\n"
+ << " :st Show current trace\n"
+ << " :st <idx> Change to another trace in the stack\n"
+ << " :c Go until end of program, exception, or builtins.break\n"
+ << " :s Go one step\n"
+ ;
+ }
+
+ }
+
+ else if (state->debugRepl && (command == ":bt" || command == ":backtrace")) {
+ for (const auto & [idx, i] : enumerate(state->debugTraces)) {
+ std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
+ showDebugTrace(std::cout, state->positions, i);
+ }
+ }
+
+ else if (state->debugRepl && (command == ":env")) {
+ for (const auto & [idx, i] : enumerate(state->debugTraces)) {
+ if (idx == debugTraceIndex) {
+ printEnvBindings(*state, i.expr, i.env);
+ break;
+ }
+ }
+ }
+
+ else if (state->debugRepl && (command == ":st")) {
+ try {
+ // change the DebugTrace index.
+ debugTraceIndex = stoi(arg);
+ } catch (...) { }
+
+ for (const auto & [idx, i] : enumerate(state->debugTraces)) {
+ if (idx == debugTraceIndex) {
+ std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
+ showDebugTrace(std::cout, state->positions, i);
+ std::cout << std::endl;
+ printEnvBindings(*state, i.expr, i.env);
+ loadDebugTraceEnv(i);
+ break;
+ }
+ }
+ }
+
+ else if (state->debugRepl && (command == ":s" || command == ":step")) {
+ // set flag to stop at next DebugTrace; exit repl.
+ state->debugStop = true;
+ return false;
+ }
+
+ else if (state->debugRepl && (command == ":c" || command == ":continue")) {
+ // set flag to run to next breakpoint or end of program; exit repl.
+ state->debugStop = false;
+ return false;
+ }
+
+ else if (command == ":a" || command == ":add") {
+ Value v;
+ evalString(arg, v);
+ addAttrsToScope(v);
+ }
+
+ else if (command == ":l" || command == ":load") {
+ state->resetFileCache();
+ loadFile(arg);
+ }
+
+ else if (command == ":lf" || command == ":load-flake") {
+ loadFlake(arg);
+ }
+
+ else if (command == ":r" || command == ":reload") {
+ state->resetFileCache();
+ reloadFiles();
+ }
+
+ else if (command == ":e" || command == ":edit") {
+ Value v;
+ evalString(arg, v);
+
+ const auto [file, line] = [&] () -> std::pair<std::string, uint32_t> {
+ if (v.type() == nPath || v.type() == nString) {
+ PathSet context;
+ auto filename = state->coerceToString(noPos, v, context, "while evaluating the filename to edit").toOwned();
+ state->symbols.create(filename);
+ return {filename, 0};
+ } else if (v.isLambda()) {
+ auto pos = state->positions[v.lambda.fun->pos];
+ return {pos.file, pos.line};
+ } else {
+ // assume it's a derivation
+ return findPackageFilename(*state, v, arg);
+ }
+ }();
+
+ // Open in EDITOR
+ auto args = editorFor(file, line);
+ auto editor = args.front();
+ args.pop_front();
+
+ // runProgram redirects stdout to a StringSink,
+ // using runProgram2 to allow editors to display their UI
+ runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args });
+
+ // Reload right after exiting the editor
+ state->resetFileCache();
+ reloadFiles();
+ }
+
+ else if (command == ":t") {
+ Value v;
+ evalString(arg, v);
+ logger->cout(showType(v));
+ }
+
+ else if (command == ":u") {
+ Value v, f, result;
+ evalString(arg, v);
+ evalString("drv: (import <nixpkgs> {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f);
+ state->callFunction(f, v, result, PosIdx());
+
+ StorePath drvPath = getDerivationPath(result);
+ runNix("nix-shell", {state->store->printStorePath(drvPath)});
+ }
+
+ else if (command == ":b" || command == ":bl" || command == ":i" || command == ":sh" || command == ":log") {
+ Value v;
+ evalString(arg, v);
+ StorePath drvPath = getDerivationPath(v);
+ Path drvPathRaw = state->store->printStorePath(drvPath);
+
+ if (command == ":b" || command == ":bl") {
+ state->store->buildPaths({DerivedPath::Built{drvPath}});
+ auto drv = state->store->readDerivation(drvPath);
+ logger->cout("\nThis derivation produced the following outputs:");
+ for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) {
+ auto localStore = state->store.dynamic_pointer_cast<LocalFSStore>();
+ if (localStore && command == ":bl") {
+ std::string symlink = "repl-result-" + outputName;
+ localStore->addPermRoot(outputPath, absPath(symlink));
+ logger->cout(" ./%s -> %s", symlink, state->store->printStorePath(outputPath));
+ } else {
+ logger->cout(" %s -> %s", outputName, state->store->printStorePath(outputPath));
+ }
+ }
+ } else if (command == ":i") {
+ runNix("nix-env", {"-i", drvPathRaw});
+ } else if (command == ":log") {
+ settings.readOnlyMode = true;
+ Finally roModeReset([&]() {
+ settings.readOnlyMode = false;
+ });
+ auto subs = getDefaultSubstituters();
+
+ subs.push_front(state->store);
+
+ bool foundLog = false;
+ RunPager pager;
+ for (auto & sub : subs) {
+ auto * logSubP = dynamic_cast<LogStore *>(&*sub);
+ if (!logSubP) {
+ printInfo("Skipped '%s' which does not support retrieving build logs", sub->getUri());
+ continue;
+ }
+ auto & logSub = *logSubP;
+
+ auto log = logSub.getBuildLog(drvPath);
+ if (log) {
+ printInfo("got build log for '%s' from '%s'", drvPathRaw, logSub.getUri());
+ logger->writeToStdout(*log);
+ foundLog = true;
+ break;
+ }
+ }
+ if (!foundLog) throw Error("build log of '%s' is not available", drvPathRaw);
+ } else {
+ runNix("nix-shell", {drvPathRaw});
+ }
+ }
+
+ else if (command == ":p" || command == ":print") {
+ Value v;
+ evalString(arg, v);
+ printValue(std::cout, v, 1000000000) << std::endl;
+ }
+
+ else if (command == ":q" || command == ":quit") {
+ state->debugStop = false;
+ state->debugQuit = true;
+ return false;
+ }
+
+ else if (command == ":doc") {
+ Value v;
+ evalString(arg, v);
+ if (auto doc = state->getDoc(v)) {
+ std::string markdown;
+
+ if (!doc->args.empty() && doc->name) {
+ auto args = doc->args;
+ for (auto & arg : args)
+ arg = "*" + arg + "*";
+
+ markdown +=
+ "**Synopsis:** `builtins." + (std::string) (*doc->name) + "` "
+ + concatStringsSep(" ", args) + "\n\n";
+ }
+
+ markdown += stripIndentation(doc->doc);
+
+ logger->cout(trim(renderMarkdownToTerminal(markdown)));
+ } else
+ throw Error("value does not have documentation");
+ }
+
+ else if (command == ":te" || command == ":trace-enable") {
+ if (arg == "false" || (arg == "" && loggerSettings.showTrace)) {
+ std::cout << "not showing error traces\n";
+ loggerSettings.showTrace = false;
+ } else if (arg == "true" || (arg == "" && !loggerSettings.showTrace)) {
+ std::cout << "showing error traces\n";
+ loggerSettings.showTrace = true;
+ } else {
+ throw Error("unexpected argument '%s' to %s", arg, command);
+ };
+ }
+
+ else if (command != "")
+ throw Error("unknown command '%1%'", command);
+
+ else {
+ size_t p = line.find('=');
+ std::string name;
+ if (p != std::string::npos &&
+ p < line.size() &&
+ line[p + 1] != '=' &&
+ isVarName(name = removeWhitespace(line.substr(0, p))))
+ {
+ Expr * e = parseString(line.substr(p + 1));
+ Value & v(*state->allocValue());
+ v.mkThunk(env, e);
+ addVarToScope(state->symbols.create(name), v);
+ } else {
+ Value v;
+ evalString(line, v);
+ printValue(std::cout, v, 1) << std::endl;
+ }
+ }
+
+ return true;
+}
+
+void NixRepl::loadFile(const Path & path)
+{
+ loadedFiles.remove(path);
+ loadedFiles.push_back(path);
+ Value v, v2;
+ state->evalFile(lookupFileArg(*state, path), v);
+ state->autoCallFunction(*autoArgs, v, v2);
+ addAttrsToScope(v2);
+}
+
+void NixRepl::loadFlake(const std::string & flakeRefS)
+{
+ if (flakeRefS.empty())
+ throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)");
+
+ auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true);
+ if (evalSettings.pureEval && !flakeRef.input.isLocked())
+ throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
+
+ Value v;
+
+ flake::callFlake(*state,
+ flake::lockFlake(*state, flakeRef,
+ flake::LockFlags {
+ .updateLockFile = false,
+ .useRegistries = !evalSettings.pureEval,
+ .allowMutable = !evalSettings.pureEval,
+ }),
+ v);
+ addAttrsToScope(v);
+}
+
+
+void NixRepl::initEnv()
+{
+ env = &state->allocEnv(envSize);
+ env->up = &state->baseEnv;
+ displ = 0;
+ staticEnv->vars.clear();
+
+ varNames.clear();
+ for (auto & i : state->staticBaseEnv->vars)
+ varNames.emplace(state->symbols[i.first]);
+}
+
+
+void NixRepl::reloadFiles()
+{
+ initEnv();
+
+ loadFiles();
+}
+
+
+void NixRepl::loadFiles()
+{
+ Strings old = loadedFiles;
+ loadedFiles.clear();
+
+ for (auto & i : old) {
+ notice("Loading '%1%'...", i);
+ loadFile(i);
+ }
+
+ for (auto & [i, what] : getValues()) {
+ notice("Loading installable '%1%'...", what);
+ addAttrsToScope(*i);
+ }
+}
+
+
+void NixRepl::addAttrsToScope(Value & attrs)
+{
+ state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope");
+ if (displ + attrs.attrs->size() >= envSize)
+ throw Error("environment full; cannot add more variables");
+
+ for (auto & i : *attrs.attrs) {
+ staticEnv->vars.emplace_back(i.name, displ);
+ env->values[displ++] = i.value;
+ varNames.emplace(state->symbols[i.name]);
+ }
+ staticEnv->sort();
+ staticEnv->deduplicate();
+ notice("Added %1% variables.", attrs.attrs->size());
+}
+
+
+void NixRepl::addVarToScope(const Symbol name, Value & v)
+{
+ if (displ >= envSize)
+ throw Error("environment full; cannot add more variables");
+ if (auto oldVar = staticEnv->find(name); oldVar != staticEnv->vars.end())
+ staticEnv->vars.erase(oldVar);
+ staticEnv->vars.emplace_back(name, displ);
+ staticEnv->sort();
+ env->values[displ++] = &v;
+ varNames.emplace(state->symbols[name]);
+}
+
+
+Expr * NixRepl::parseString(std::string s)
+{
+ Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv);
+ return e;
+}
+
+
+void NixRepl::evalString(std::string s, Value & v)
+{
+ Expr * e = parseString(s);
+ e->eval(*state, *env, v);
+ state->forceValue(v, [&]() { return v.determinePos(noPos); });
+}
+
+
+std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth)
+{
+ ValuesSeen seen;
+ return printValue(str, v, maxDepth, seen);
+}
+
+
+std::ostream & printStringValue(std::ostream & str, const char * string) {
+ str << "\"";
+ for (const char * i = string; *i; i++)
+ if (*i == '\"' || *i == '\\') str << "\\" << *i;
+ else if (*i == '\n') str << "\\n";
+ else if (*i == '\r') str << "\\r";
+ else if (*i == '\t') str << "\\t";
+ else str << *i;
+ str << "\"";
+ return str;
+}
+
+
+// FIXME: lot of cut&paste from Nix's eval.cc.
+std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen)
+{
+ str.flush();
+ checkInterrupt();
+
+ state->forceValue(v, [&]() { return v.determinePos(noPos); });
+
+ switch (v.type()) {
+
+ case nInt:
+ str << ANSI_CYAN << v.integer << ANSI_NORMAL;
+ break;
+
+ case nBool:
+ str << ANSI_CYAN << (v.boolean ? "true" : "false") << ANSI_NORMAL;
+ break;
+
+ case nString:
+ str << ANSI_WARNING;
+ printStringValue(str, v.string.s);
+ str << ANSI_NORMAL;
+ break;
+
+ case nPath:
+ str << ANSI_GREEN << v.path << ANSI_NORMAL; // !!! escaping?
+ break;
+
+ case nNull:
+ str << ANSI_CYAN "null" ANSI_NORMAL;
+ break;
+
+ case nAttrs: {
+ seen.insert(&v);
+
+ bool isDrv = state->isDerivation(v);
+
+ if (isDrv) {
+ str << "«derivation ";
+ Bindings::iterator i = v.attrs->find(state->sDrvPath);
+ PathSet context;
+ if (i != v.attrs->end())
+ str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
+ else
+ str << "???";
+ str << "»";
+ }
+
+ else if (maxDepth > 0) {
+ str << "{ ";
+
+ typedef std::map<std::string, Value *> Sorted;
+ Sorted sorted;
+ for (auto & i : *v.attrs)
+ sorted.emplace(state->symbols[i.name], i.value);
+
+ for (auto & i : sorted) {
+ if (isVarName(i.first))
+ str << i.first;
+ else
+ printStringValue(str, i.first.c_str());
+ str << " = ";
+ if (seen.count(i.second))
+ str << "«repeated»";
+ else
+ try {
+ printValue(str, *i.second, maxDepth - 1, seen);
+ } catch (AssertionError & e) {
+ str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
+ }
+ str << "; ";
+ }
+
+ str << "}";
+ } else
+ str << "{ ... }";
+
+ break;
+ }
+
+ case nList:
+ seen.insert(&v);
+
+ str << "[ ";
+ if (maxDepth > 0)
+ for (auto elem : v.listItems()) {
+ if (seen.count(elem))
+ str << "«repeated»";
+ else
+ try {
+ printValue(str, *elem, maxDepth - 1, seen);
+ } catch (AssertionError & e) {
+ str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
+ }
+ str << " ";
+ }
+ else
+ str << "... ";
+ str << "]";
+ break;
+
+ case nFunction:
+ if (v.isLambda()) {
+ std::ostringstream s;
+ s << state->positions[v.lambda.fun->pos];
+ str << ANSI_BLUE "«lambda @ " << filterANSIEscapes(s.str()) << "»" ANSI_NORMAL;
+ } else if (v.isPrimOp()) {
+ str << ANSI_MAGENTA "«primop»" ANSI_NORMAL;
+ } else if (v.isPrimOpApp()) {
+ str << ANSI_BLUE "«primop-app»" ANSI_NORMAL;
+ } else {
+ abort();
+ }
+ break;
+
+ case nFloat:
+ str << v.fpoint;
+ break;
+
+ default:
+ str << ANSI_RED "«unknown»" ANSI_NORMAL;
+ break;
+ }
+
+ return str;
+}
+
+void runRepl(
+ ref<EvalState>evalState,
+ const ValMap & extraEnv)
+{
+ auto getValues = [&]()->NixRepl::AnnotatedValues{
+ NixRepl::AnnotatedValues values;
+ return values;
+ };
+ const Strings & searchPath = {};
+ auto repl = std::make_unique<NixRepl>(
+ searchPath,
+ openStore(),
+ evalState,
+ getValues
+ );
+
+ repl->initEnv();
+
+ // add 'extra' vars.
+ for (auto & [name, value] : extraEnv)
+ repl->addVarToScope(repl->state->symbols.create(name), *value);
+
+ repl->mainLoop();
+}
+
+struct CmdRepl : InstallablesCommand
+{
+ CmdRepl() {
+ evalSettings.pureEval = false;
+ }
+
+ void prepare()
+ {
+ if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
+ warn("future versions of Nix will require using `--file` to load a file");
+ if (this->_installables.size() > 1)
+ warn("more than one input file is not currently supported");
+ auto filePath = this->_installables[0].data();
+ file = std::optional(filePath);
+ _installables.front() = _installables.back();
+ _installables.pop_back();
+ }
+ installables = InstallablesCommand::load();
+ }
+
+ std::vector<std::string> files;
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {""};
+ }
+
+ bool useDefaultInstallables() override
+ {
+ return file.has_value() or expr.has_value();
+ }
+
+ bool forceImpureByDefault() override
+ {
+ return true;
+ }
+
+ std::string description() override
+ {
+ return "start an interactive environment for evaluating Nix expressions";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "repl.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto state = getEvalState();
+ auto getValues = [&]()->NixRepl::AnnotatedValues{
+ auto installables = load();
+ NixRepl::AnnotatedValues values;
+ for (auto & installable: installables){
+ auto what = installable->what();
+ if (file){
+ auto [val, pos] = installable->toValue(*state);
+ auto what = installable->what();
+ state->forceValue(*val, pos);
+ auto autoArgs = getAutoArgs(*state);
+ auto valPost = state->allocValue();
+ state->autoCallFunction(*autoArgs, *val, *valPost);
+ state->forceValue(*valPost, pos);
+ values.push_back( {valPost, what });
+ } else {
+ auto [val, pos] = installable->toValue(*state);
+ values.push_back( {val, what} );
+ }
+ }
+ return values;
+ };
+ auto repl = std::make_unique<NixRepl>(
+ searchPath,
+ openStore(),
+ state,
+ getValues
+ );
+ repl->autoArgs = getAutoArgs(*repl->state);
+ repl->initEnv();
+ repl->mainLoop();
+ }
+};
+
+static auto rCmdRepl = registerCommand<CmdRepl>("repl");
+
+}