aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--doc/manual/manual.is-valid0
-rw-r--r--src/libcmd/command.cc46
-rw-r--r--src/libcmd/command.hh8
-rw-r--r--src/libcmd/local.mk6
-rw-r--r--src/libcmd/repl.cc (renamed from src/nix/repl.cc)154
-rw-r--r--src/libexpr/eval.cc346
-rw-r--r--src/libexpr/eval.hh35
-rw-r--r--src/libexpr/nixexpr.cc140
-rw-r--r--src/libexpr/nixexpr.hh39
-rw-r--r--src/libexpr/parser.y7
-rw-r--r--src/libexpr/primops.cc32
-rw-r--r--src/libutil/error.hh9
13 files changed, 679 insertions, 146 deletions
diff --git a/Makefile b/Makefile
index 5040d2884..02228910a 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ makefiles = \
misc/systemd/local.mk \
misc/launchd/local.mk \
misc/upstart/local.mk \
- doc/manual/local.mk \
+ # doc/manual/local.mk \
tests/local.mk \
tests/plugins/local.mk
@@ -34,4 +34,5 @@ endif
include mk/lib.mk
+# GLOBAL_CXXFLAGS += -g -Wall -include config.h -std=c++17 -fstack-usage
GLOBAL_CXXFLAGS += -g -Wall -include config.h -std=c++17 -I src
diff --git a/doc/manual/manual.is-valid b/doc/manual/manual.is-valid
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/manual/manual.is-valid
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index 6d183dfad..56288665a 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -86,8 +86,17 @@ ref<Store> CopyCommand::getDstStore()
EvalCommand::EvalCommand()
{
+ addFlag({
+ .longName = "debugger",
+ .description = "start an interactive environment if evaluation fails",
+ .handler = {&startReplOnEvalErrors, true},
+ });
}
+extern std::function<void(const Error * error, const Env & env, const Expr & expr)> debuggerHook;
+
+
+
EvalCommand::~EvalCommand()
{
if (evalState)
@@ -103,7 +112,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 +122,41 @@ ref<EvalState> EvalCommand::getEvalState()
searchPath, getEvalStore(), getStore())
#endif
;
+ if (startReplOnEvalErrors)
+ debuggerHook = [evalState{ref<EvalState>(evalState)}](const Error * error, const Env & env, const Expr & expr) {
+ // clear the screen.
+ // std::cout << "\033[2J\033[1;1H";
+
+ if (error)
+ printError("%s\n\n" ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL, error->what());
+ else
+ {
+ auto iter = evalState->debugTraces.begin();
+ if (iter != evalState->debugTraces.end()) {
+ std::cout << "\n" << "… " << iter->hint.str() << "\n";
+
+ if (iter->pos.has_value() && (*iter->pos)) {
+ auto pos = iter->pos.value();
+ std::cout << "\n";
+ printAtPos(pos, std::cout);
+
+ auto loc = getCodeLines(pos);
+ if (loc.has_value()) {
+ std::cout << "\n";
+ printCodeLines(std::cout, "", pos, *loc);
+ std::cout << "\n";
+ }
+ }
+ }
+ }
+
+ if (expr.staticenv)
+ {
+ auto vm = mapStaticEnvBindings(*expr.staticenv.get(), env);
+ runRepl(evalState, error, expr, *vm);
+ }
+ };
+ }
return ref<EvalState>(evalState);
}
diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh
index bd2a0a7ee..146a08ed6 100644
--- a/src/libcmd/command.hh
+++ b/src/libcmd/command.hh
@@ -58,6 +58,8 @@ struct CopyCommand : virtual StoreCommand
struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
+ bool startReplOnEvalErrors = false;
+
EvalCommand();
~EvalCommand();
@@ -323,4 +325,10 @@ void printClosureDiff(
const StorePath & afterPath,
std::string_view indent);
+
+void runRepl(
+ ref<EvalState> evalState,
+ const Error *debugError,
+ const Expr &expr,
+ const std::map<std::string, Value *> & extraEnv);
}
diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk
index 7a2f83cc7..713c1bf11 100644
--- a/src/libcmd/local.mk
+++ b/src/libcmd/local.mk
@@ -6,10 +6,12 @@ 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 += -llowdown -pthread
+# libcmd_LDFLAGS = $(EDITLINE_LIBS) -llowdown -pthread
libcmd_LDFLAGS += $(LOWDOWN_LIBS) -pthread
-libcmd_LIBS = libstore libutil libexpr libmain libfetchers
+libcmd_LIBS = libstore libutil libexpr libmain libfetchers libnix
$(eval $(call install-file-in, $(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644))
diff --git a/src/nix/repl.cc b/src/libcmd/repl.cc
index e9bebff17..51bbbbc57 100644
--- a/src/nix/repl.cc
+++ b/src/libcmd/repl.cc
@@ -46,20 +46,22 @@ struct NixRepl
#endif
{
string curDir;
- std::unique_ptr<EvalState> state;
+ ref<EvalState> state;
Bindings * autoArgs;
+ const Error *debugError;
+
Strings loadedFiles;
const static int envSize = 32768;
- StaticEnv staticEnv;
+ std::shared_ptr<StaticEnv> staticEnv;
Env * env;
int displ;
StringSet varNames;
const Path historyFile;
- NixRepl(const Strings & searchPath, nix::ref<Store> store);
+ NixRepl(ref<EvalState> state);
~NixRepl();
void mainLoop(const std::vector<std::string> & files);
StringSet completePrefix(string prefix);
@@ -69,6 +71,7 @@ struct NixRepl
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);
@@ -76,8 +79,8 @@ struct NixRepl
void evalString(string s, Value & v);
typedef 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::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
+ std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
};
@@ -90,9 +93,9 @@ string removeWhitespace(string s)
}
-NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store)
- : state(std::make_unique<EvalState>(searchPath, store))
- , staticEnv(false, &state->staticBaseEnv)
+NixRepl::NixRepl(ref<EvalState> state)
+ : state(state)
+ , staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
, historyFile(getDataDir() + "/nix/repl-history")
{
curDir = absPath(".");
@@ -201,10 +204,12 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
string error = ANSI_RED "error:" ANSI_NORMAL " ";
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
- for (auto & i : files)
- loadedFiles.push_back(i);
+ if (!files.empty()) {
+ for (auto & i : files)
+ loadedFiles.push_back(i);
+ }
- reloadFiles();
+ loadFiles();
if (!loadedFiles.empty()) notice("");
// Allow nix-repl specific settings in .inputrc
@@ -393,7 +398,6 @@ StorePath NixRepl::getDerivationPath(Value & v) {
return drvPath;
}
-
bool NixRepl::processLine(string line)
{
if (line == "") return true;
@@ -431,7 +435,61 @@ bool NixRepl::processLine(string line)
<< " :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"
- << " :st [bool] Enable, disable or toggle showing traces for errors\n";
+ << " :st [bool] Enable, disable or toggle showing traces for errors\n"
+ << " :d <cmd> Debug mode commands\n"
+ << " :d stack Show call stack\n"
+ << " :d env Show env stack\n"
+ << " :d error Show current error\n"
+ << " :d go Go until end of program, exception, or builtins.break().\n"
+ << " :d step Go one step\n"
+ ;
+
+ }
+
+ else if (command == ":d" || command == ":debug") {
+ if (arg == "stack") {
+ for (auto iter = this->state->debugTraces.begin();
+ iter != this->state->debugTraces.end(); ++iter) {
+ std::cout << "\n" << "… " << iter->hint.str() << "\n";
+
+ if (iter->pos.has_value() && (*iter->pos)) {
+ auto pos = iter->pos.value();
+ std::cout << "\n";
+ printAtPos(pos, std::cout);
+
+ auto loc = getCodeLines(pos);
+ if (loc.has_value()) {
+ std::cout << "\n";
+ printCodeLines(std::cout, "", pos, *loc);
+ std::cout << "\n";
+ }
+ }
+ }
+ } else if (arg == "env") {
+ auto iter = this->state->debugTraces.begin();
+ if (iter != this->state->debugTraces.end()) {
+ printStaticEnvBindings(iter->expr);
+ }
+ }
+ else if (arg == "error") {
+ if (this->debugError) {
+ showErrorInfo(std::cout, debugError->info(), true);
+ }
+ else
+ {
+ notice("error information not available");
+ }
+ }
+ else if (arg == "step") {
+ // set flag and exit repl.
+ state->debugStop = true;
+ return false;
+ }
+ else if (arg == "go") {
+ // set flag and exit repl.
+ state->debugStop = false;
+ return false;
+ }
}
else if (command == ":a" || command == ":add") {
@@ -646,10 +704,10 @@ void NixRepl::initEnv()
env = &state->allocEnv(envSize);
env->up = &state->baseEnv;
displ = 0;
- staticEnv.vars.clear();
+ staticEnv->vars.clear();
varNames.clear();
- for (auto & i : state->staticBaseEnv.vars)
+ for (auto & i : state->staticBaseEnv->vars)
varNames.insert(i.first);
}
@@ -658,6 +716,12 @@ void NixRepl::reloadFiles()
{
initEnv();
+ loadFiles();
+}
+
+
+void NixRepl::loadFiles()
+{
Strings old = loadedFiles;
loadedFiles.clear();
@@ -678,12 +742,12 @@ void NixRepl::addAttrsToScope(Value & attrs)
throw Error("environment full; cannot add more variables");
for (auto & i : *attrs.attrs) {
- staticEnv.vars.emplace_back(i.name, displ);
+ staticEnv->vars.emplace_back(i.name, displ);
env->values[displ++] = i.value;
varNames.insert((string) i.name);
}
- staticEnv.sort();
- staticEnv.deduplicate();
+ staticEnv->sort();
+ staticEnv->deduplicate();
notice("Added %1% variables.", attrs.attrs->size());
}
@@ -692,15 +756,14 @@ 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();
+ 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.insert((string) name);
}
-
Expr * NixRepl::parseString(string s)
{
Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv);
@@ -860,6 +923,45 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
return str;
}
+void runRepl(
+ ref<EvalState> evalState,
+ const Error *debugError,
+ const Expr &expr,
+ const std::map<std::string, Value *> & extraEnv)
+{
+ auto repl = std::make_unique<NixRepl>(evalState);
+
+ // repl->debugError = debugError;
+
+ repl->initEnv();
+
+ // auto dts = debugError ?
+ // std::unique_ptr<DebugTraceStacker>(
+ // // tack on a final DebugTrace for the error position.
+ // new DebugTraceStacker(
+ // *evalState,
+ // DebugTrace
+ // {.pos = debugError->info().errPos,
+ // .expr = expr,
+ // .env = *repl->env,
+ // .hint = debugError->info().msg
+ // })
+ // )
+ // : nullptr;
+
+ // add 'extra' vars.
+ std::set<std::string> names;
+ for (auto & [name, value] : extraEnv) {
+ // names.insert(ANSI_BOLD + name + ANSI_NORMAL);
+ names.insert(name);
+ repl->addVarToScope(repl->state->symbols.create(name), *value);
+ }
+
+ printError(hintfmt("The following extra variables are in scope: %s\n", concatStringsSep(", ", names)).str());
+
+ repl->mainLoop({});
+}
+
struct CmdRepl : StoreCommand, MixEvalArgs
{
std::vector<std::string> files;
@@ -888,8 +990,12 @@ struct CmdRepl : StoreCommand, MixEvalArgs
void run(ref<Store> store) override
{
evalSettings.pureEval = false;
- auto repl = std::make_unique<NixRepl>(searchPath, openStore());
+
+ auto evalState = make_ref<EvalState>(searchPath, store);
+
+ auto repl = std::make_unique<NixRepl>(evalState);
repl->autoArgs = getAutoArgs(*repl->state);
+ repl->initEnv();
repl->mainLoop(files);
}
};
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 2d4eb57fc..790b00ace 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -18,6 +18,7 @@
#include <sys/resource.h>
#include <iostream>
#include <fstream>
+#include <functional>
#include <sys/resource.h>
@@ -36,6 +37,7 @@
namespace nix {
+std::function<void(const Error * error, const Env & env, const Expr & expr)> debuggerHook;
static char * allocString(size_t size)
{
@@ -435,6 +437,7 @@ EvalState::EvalState(
, emptyBindings(0)
, store(store)
, buildStore(buildStore ? buildStore : store)
+ , debugStop(true)
, regexCache(makeRegexCache())
#if HAVE_BOEHMGC
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
@@ -442,7 +445,7 @@ EvalState::EvalState(
, valueAllocCache(std::make_shared<void *>(nullptr))
#endif
, baseEnv(allocEnv(128))
- , staticBaseEnv(false, 0)
+ , staticBaseEnv(new StaticEnv(false, 0))
{
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
@@ -617,7 +620,7 @@ Value * EvalState::addConstant(const string & name, Value & v)
void EvalState::addConstant(const string & name, Value * v)
{
- staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
+ staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
@@ -642,7 +645,7 @@ Value * EvalState::addPrimOp(const string & name,
Value * v = allocValue();
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym });
- staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
+ staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
return v;
@@ -668,7 +671,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
Value * v = allocValue();
v->mkPrimOp(new PrimOp(std::move(primOp)));
- staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
+ staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v));
return v;
@@ -697,90 +700,237 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
return {};
}
+void printStaticEnvBindings(const StaticEnv &se, int lvl)
+{
+ std::cout << "Env level " << lvl << std::endl;
+
+ if (se.up) {
+ std::cout << ANSI_MAGENTA;
+ for (auto i = se.vars.begin(); i != se.vars.end(); ++i)
+ {
+ std::cout << i->first << " ";
+ }
+ std::cout << ANSI_NORMAL;
+
+ std::cout << std::endl;
+ std::cout << std::endl;
+
+ printStaticEnvBindings(*se.up, ++lvl);
+ }
+ else
+ {
+ std::cout << ANSI_MAGENTA;
+ // for the top level, don't print the double underscore ones; they are in builtins.
+ for (auto i = se.vars.begin(); i != se.vars.end(); ++i)
+ {
+ if (((string)i->first).substr(0,2) != "__")
+ std::cout << i->first << " ";
+ }
+ std::cout << ANSI_NORMAL;
+ std::cout << std::endl;
+ std::cout << std::endl;
+
+ }
+
+}
+
+void printStaticEnvBindings(const Expr &expr)
+{
+ // just print the names for now
+ if (expr.staticenv)
+ {
+ printStaticEnvBindings(*expr.staticenv.get(), 0);
+ }
+}
+
+void mapStaticEnvBindings(const StaticEnv &se, const Env &env, valmap & vm)
+{
+ // add bindings for the next level up first, so that the bindings for this level
+ // override the higher levels.
+ // The top level bindings (builtins) are skipped since they are added for us by initEnv()
+ if (env.up && se.up) {
+ mapStaticEnvBindings(*se.up, *env.up,vm);
+
+ // iterate through staticenv bindings and add them.
+ auto map = valmap();
+ for (auto iter = se.vars.begin(); iter != se.vars.end(); ++iter)
+ {
+ map[iter->first] = env.values[iter->second];
+ }
+
+ vm.merge(map);
+ }
+}
+
+valmap * mapStaticEnvBindings(const StaticEnv &se, const Env &env)
+{
+ auto vm = new valmap();
+ mapStaticEnvBindings(se, env, *vm);
+ return vm;
+}
+
/* Every "format" object (even temporary) takes up a few hundred bytes
of stack space, which is a real killer in the recursive
evaluator. So here are some helper functions for throwing
exceptions. */
-LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2))
+LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, Env & env, Expr *expr))
{
- throw EvalError(s, s2);
+ auto error = EvalError(s, s2);
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+ throw error;
}
-LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2))
+LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, Env & env, Expr *expr))
{
- throw EvalError({
+ auto error = EvalError({
.msg = hintfmt(s, s2),
.errPos = pos
});
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3))
+LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3, Env & env, Expr *expr))
{
- throw EvalError(s, s2, s3);
+ auto error = EvalError(s, s2, s3);
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3))
+LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3, Env & env, Expr *expr))
{
- throw EvalError({
+ auto error = EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = pos
});
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2))
+LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2, Env & env, Expr *expr))
{
// p1 is where the error occurred; p2 is a position mentioned in the message.
- throw EvalError({
+ auto error = EvalError({
.msg = hintfmt(s, sym, p2),
.errPos = p1
});
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s))
+LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, Env & env, Expr *expr))
{
- throw TypeError({
+ auto error = TypeError({
.msg = hintfmt(s),
.errPos = pos
});
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2))
+LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v, Env & env, Expr *expr))
{
- throw TypeError({
- .msg = hintfmt(s, fun.showNamePos(), s2),
+ auto error = TypeError({
+ .msg = hintfmt(s, v),
.errPos = pos
});
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
+LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const string &s2, Env & env, Expr *expr))
{
- throw TypeError(s, showType(v));
+ auto error = TypeError({
+ .msg = hintfmt(s, s2),
+ .errPos = pos
+ });
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
+}
+
+LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2, Env & env, Expr *expr))
+{
+ auto error = TypeError({
+ .msg = hintfmt(s, fun.showNamePos(), s2),
+ .errPos = pos
+ });
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1))
+// LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, Env & env, Expr *expr))
+// {
+// auto error = TypeError({
+// .msg = hintfmt(s, showType(v))
+// .errPos = e ;
+// }
+
+LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1, Env & env, Expr *expr))
{
- throw AssertionError({
+ auto error = AssertionError({
.msg = hintfmt(s, s1),
.errPos = pos
});
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1))
+LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1, Env & env, Expr *expr))
{
- throw UndefinedVarError({
+ auto error = UndefinedVarError({
.msg = hintfmt(s, s1),
.errPos = pos
});
+
+ if (debuggerHook && expr) {
+ debuggerHook(&error, env, *expr);
+ }
+
+ throw error;
}
-LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const string & s1))
+LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const string & s1, Env & env, Expr *expr))
{
- throw MissingArgumentError({
+ auto error = MissingArgumentError({
.msg = hintfmt(s, s1),
.errPos = pos
});
+
+ if (debuggerHook && expr)
+ debuggerHook(&error, env, *expr);
+
+ throw error;
}
LocalNoInline(void addErrorTrace(Error & e, const char * s, const string & s2))
@@ -793,6 +943,27 @@ LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, con
e.addTrace(pos, s, s2);
}
+LocalNoInline(std::unique_ptr<DebugTraceStacker>
+ makeDebugTraceStacker(EvalState &state, Expr &expr, Env &env, std::optional<ErrPos> pos, const char * s, const string & s2))
+{
+ return std::unique_ptr<DebugTraceStacker>(
+ new DebugTraceStacker(
+ state,
+ DebugTrace
+ {.pos = pos,
+ .expr = expr,
+ .env = env,
+ .hint = hintfmt(s, s2)
+ }));
+}
+
+DebugTraceStacker::DebugTraceStacker(EvalState &evalState, DebugTrace t)
+:evalState(evalState), trace(t)
+{
+ evalState.debugTraces.push_front(t);
+ if (evalState.debugStop && debuggerHook)
+ debuggerHook(0, t.env, t.expr);
+}
void Value::mkString(std::string_view s)
{
@@ -850,8 +1021,9 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
if (countCalls) attrSelects[*j->pos]++;
return j->value;
}
- if (!env->prevWith)
- throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name);
+ if (!env->prevWith) {
+ throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name, *env, (Expr*)&var);
+ }
for (size_t l = env->prevWith; l; --l, env = env->up) ;
}
}
@@ -882,6 +1054,7 @@ Value * EvalState::allocValue()
Env & EvalState::allocEnv(size_t size)
{
+
nrEnvs++;
nrValuesInEnvs += size;
Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
@@ -892,6 +1065,15 @@ Env & EvalState::allocEnv(size_t size)
return *env;
}
+Env & fakeEnv(size_t size)
+{
+ // making a fake Env so we'll have one to pass to exception ftns.
+ // a placeholder until we can pass real envs everywhere they're needed.
+ Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
+ env->type = Env::Plain;
+
+ return *env;
+}
void EvalState::mkList(Value & v, size_t size)
{
@@ -1024,6 +1206,16 @@ void EvalState::cacheFile(
fileParseCache[resolvedPath] = e;
try {
+ auto dts =
+ debuggerHook ?
+ makeDebugTraceStacker(
+ *this,
+ *e,
+ this->baseEnv,
+ (e->getPos() ? std::optional(ErrPos(*e->getPos())) : std::nullopt),
+ "while evaluating the file '%1%':", resolvedPath)
+ : nullptr;
+
// Enforce that 'flake.nix' is a direct attrset, not a
// computation.
if (mustBeTrivial &&
@@ -1175,7 +1367,8 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
Symbol nameSym = state.symbols.create(nameVal.string.s);
Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end())
- throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos);
+ throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos,
+ env, this);
i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */
@@ -1247,6 +1440,16 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
e->eval(state, env, vTmp);
try {
+ auto dts =
+ debuggerHook ?
+ makeDebugTraceStacker(
+ state,
+ *this,
+ env,
+ *pos2,
+ "while evaluating the attribute '%1%'",
+ showAttrPath(state, env, attrPath))
+ : nullptr;
for (auto & i : attrPath) {
state.nrLookups++;
@@ -1263,7 +1466,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
} else {
state.forceAttrs(*vAttrs, pos);
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
- throwEvalError(pos, "attribute '%1%' missing", name);
+ throwEvalError(pos, "attribute '%1%' missing", name, env, this);
}
vAttrs = j->value;
pos2 = j->pos;
@@ -1350,7 +1553,6 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!lambda.hasFormals())
env2.values[displ++] = args[0];
-
else {
forceAttrs(*args[0], pos);
@@ -1365,7 +1567,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto j = args[0]->attrs->get(i.name);
if (!j) {
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
- lambda, i.name);
+ lambda, i.name, *fun.lambda.env, &lambda);
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
attrsUsed++;
@@ -1380,7 +1582,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
user. */
for (auto & i : *args[0]->attrs)
if (!lambda.formals->has(i.name))
- throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
+ throwTypeError(pos, "%1% called with unexpected argument '%2%'",
+ lambda, i.name, *fun.lambda.env, &lambda);
abort(); // can't happen
}
}
@@ -1390,6 +1593,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* Evaluate the body. */
try {
+ auto dts =
+ debuggerHook ?
+ makeDebugTraceStacker(*this, *lambda.body, env2, lambda.pos,
+ "while evaluating %s",
+ (lambda.name.set()
+ ? "'" + (string) lambda.name + "'"
+ : "anonymous lambda"))
+ : nullptr;
+
lambda.body->eval(*this, env2, vCur);
} catch (Error & e) {
if (loggerSettings.showTrace.get()) {
@@ -1544,8 +1756,9 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See
-https://nixos.org/manual/nix/stable/#ss-functions.)", i.name);
-
+https://nixos.org/manual/nix/stable/#ss-functions.)",
+ i.name,
+ *fun.lambda.env, fun.lambda.fun);
}
}
}
@@ -1577,7 +1790,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
if (!state.evalBool(env, cond, pos)) {
std::ostringstream out;
cond->show(out);
- throwAssertionError(pos, "assertion '%1%' failed", out.str());
+ throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, this);
}
body->eval(state, env, v);
}
@@ -1753,15 +1966,16 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
firstType = nFloat;
nf = n;
nf += vTmp.fpoint;
- } else
- throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp));
+ } else {
+ throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, this);
+ }
} else if (firstType == nFloat) {
if (vTmp.type() == nInt) {
nf += vTmp.integer;
} else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint;
} else
- throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp));
+ throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, this);
} else {
if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not
@@ -1808,6 +2022,16 @@ void EvalState::forceValueDeep(Value & v)
if (v.type() == nAttrs) {
for (auto & i : *v.attrs)
try {
+
+ auto dts =
+ debuggerHook ?
+ // if the value is a thunk, we're evaling. otherwise no trace necessary.
+ (i.value->isThunk() ?
+ makeDebugTraceStacker(*this, *v.thunk.expr, *v.thunk.env, *i.pos,
+ "while evaluating the attribute '%1%'", i.name)
+ : nullptr)
+ : nullptr;
+
recurse(*i.value);
} catch (Error & e) {
addErrorTrace(e, *i.pos, "while evaluating the attribute '%1%'", i.name);
@@ -1829,7 +2053,8 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type() != nInt)
- throwTypeError(pos, "value is %1% while an integer was expected", v);
+ throwTypeError(pos, "value is %1% while an integer was expected", v,
+ fakeEnv(1), 0);
return v.integer;
}
@@ -1840,7 +2065,8 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
if (v.type() == nInt)
return v.integer;
else if (v.type() != nFloat)
- throwTypeError(pos, "value is %1% while a float was expected", v);
+ throwTypeError(pos, "value is %1% while a float was expected", v,
+ fakeEnv(1), 0);
return v.fpoint;
}
@@ -1849,7 +2075,8 @@ bool EvalState::forceBool(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type() != nBool)
- throwTypeError(pos, "value is %1% while a Boolean was expected", v);
+ throwTypeError(pos, "value is %1% while a Boolean was expected", v,
+ fakeEnv(1), 0);
return v.boolean;
}
@@ -1864,7 +2091,8 @@ void EvalState::forceFunction(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v))
- throwTypeError(pos, "value is %1% while a function was expected", v);
+ throwTypeError(pos, "value is %1% while a function was expected", v,
+ fakeEnv(1), 0);
}
@@ -1872,10 +2100,8 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type() != nString) {
- if (pos)
- throwTypeError(pos, "value is %1% while a string was expected", v);
- else
- throwTypeError("value is %1% while a string was expected", v);
+ throwTypeError(pos, "value is %1% while a string was expected", v,
+ fakeEnv(1), 0);
}
return v.string.s;
}
@@ -1926,10 +2152,10 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos)
if (v.string.context) {
if (pos)
throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
- v.string.s, v.string.context[0]);
+ v.string.s, v.string.context[0], fakeEnv(1), 0);
else
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
- v.string.s, v.string.context[0]);
+ v.string.s, v.string.context[0], fakeEnv(1), 0);
}
return s;
}
@@ -1983,7 +2209,9 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
if (maybeString)
return std::move(*maybeString);
auto i = v.attrs->find(sOutPath);
- if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string");
+ if (i == v.attrs->end())
+ throwTypeError(pos, "cannot coerce a set to a string",
+ fakeEnv(1), 0);
return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
}
@@ -1991,7 +2219,6 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
return v.external->coerceToString(pos, context, coerceMore, copyToStore);
if (coerceMore) {
-
/* Note that `false' is represented as an empty string for
shell scripting convenience, just like `null'. */
if (v.type() == nBool && v.boolean) return "1";
@@ -2013,14 +2240,17 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
}
}
- throwTypeError(pos, "cannot coerce %1% to a string", v);
+ throwTypeError(pos, "cannot coerce %1% to a string", v,
+ fakeEnv(1), 0);
}
string EvalState::copyPathToStore(PathSet & context, const Path & path)
{
if (nix::isDerivation(path))
- throwEvalError("file names are not allowed to end in '%1%'", drvExtension);
+ throwEvalError("file names are not allowed to end in '%1%'",
+ drvExtension,
+ fakeEnv(1), 0);
Path dstPath;
auto i = srcToStore.find(path);
@@ -2045,7 +2275,8 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
{
string path = coerceToString(pos, v, context, false, false).toOwned();
if (path == "" || path[0] != '/')
- throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
+ throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path,
+ fakeEnv(1), 0);
return path;
}
@@ -2124,7 +2355,10 @@ bool EvalState::eqValues(Value & v1, Value & v2)
return v1.fpoint == v2.fpoint;
default:
- throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2));
+ throwEvalError("cannot compare %1% with %2%",
+ showType(v1),
+ showType(v2),
+ fakeEnv(1), 0);
}
}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index ce5a16e11..8c4430bc8 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -25,6 +25,9 @@ enum RepairFlag : bool;
typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v);
+extern std::function<void(const Error * error, const Env & env, const Expr & expr)> debuggerHook;
+void printStaticEnvBindings(const Expr &expr);
+void printStaticEnvBindings(const StaticEnv &se, int lvl = 0);
struct PrimOp
{
@@ -35,6 +38,7 @@ struct PrimOp
const char * doc = nullptr;
};
+typedef std::map<std::string, Value *> valmap;
struct Env
{
@@ -44,6 +48,7 @@ struct Env
Value * values[0];
};
+valmap * mapStaticEnvBindings(const StaticEnv &se, const Env &env);
void copyContext(const Value & v, PathSet & context);
@@ -68,6 +73,12 @@ struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache();
+struct DebugTrace {
+ std::optional<ErrPos> pos;
+ const Expr &expr;
+ const Env &env;
+ hintformat hint;
+};
class EvalState
{
@@ -104,6 +115,9 @@ public:
RootValue vCallFlake = nullptr;
RootValue vImportedDrvToDerivation = nullptr;
+ bool debugStop;
+ std::list<DebugTrace> debugTraces;
+
private:
SrcToStore srcToStore;
@@ -178,10 +192,10 @@ public:
/* Parse a Nix expression from the specified file. */
Expr * parseExprFromFile(const Path & path);
- Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv);
+ Expr * parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv);
/* Parse a Nix expression from the specified string. */
- Expr * parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv);
+ Expr * parseExprFromString(std::string s, const Path & basePath, , std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromString(std::string s, const Path & basePath);
Expr * parseStdin();
@@ -191,7 +205,7 @@ public:
trivial (i.e. doesn't require arbitrary computation). */
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false);
- /* Like `cacheFile`, but with an already parsed expression. */
+ /* Like `evalFile`, but with an already parsed expression. */
void cacheFile(
const Path & path,
const Path & resolvedPath,
@@ -276,7 +290,7 @@ public:
Env & baseEnv;
/* The same, but used during parsing to resolve variables. */
- StaticEnv staticBaseEnv; // !!! should be private
+ std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
private:
@@ -317,7 +331,7 @@ private:
friend struct ExprLet;
Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path,
- const PathView basePath, StaticEnv & staticEnv);
+ const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv);
public:
@@ -412,6 +426,17 @@ private:
friend struct Value;
};
+class DebugTraceStacker {
+ public:
+ DebugTraceStacker(EvalState &evalState, DebugTrace t);
+ ~DebugTraceStacker()
+ {
+ // assert(evalState.debugTraces.front() == trace);
+ evalState.debugTraces.pop_front();
+ }
+ EvalState &evalState;
+ DebugTrace trace;
+};
/* Return a string representing the type of the value `v'. */
string showType(ValueType type);
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index 2d2cd96cd..41ee92d27 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -4,7 +4,6 @@
#include <cstdlib>
-
namespace nix {
@@ -247,35 +246,46 @@ Pos noPos;
/* Computing levels/displacements for variables. */
-void Expr::bindVars(const StaticEnv & env)
+void Expr::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
abort();
}
-void ExprInt::bindVars(const StaticEnv & env)
+void ExprInt::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
}
-void ExprFloat::bindVars(const StaticEnv & env)
+void ExprFloat::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
}
-void ExprString::bindVars(const StaticEnv & env)
+void ExprString::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
}
-void ExprPath::bindVars(const StaticEnv & env)
+void ExprPath::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
}
-void ExprVar::bindVars(const StaticEnv & env)
+void ExprVar::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
/* Check whether the variable appears in the environment. If so,
set its level and displacement. */
const StaticEnv * curEnv;
Level level;
int withLevel = -1;
- for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) {
+ for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up, level++) {
if (curEnv->isWith) {
if (withLevel == -1) withLevel = level;
} else {
@@ -292,17 +302,22 @@ void ExprVar::bindVars(const StaticEnv & env)
/* Otherwise, the variable must be obtained from the nearest
enclosing `with'. If there is no `with', then we can issue an
"undefined variable" error now. */
- if (withLevel == -1)
+ if (withLevel == -1)
+ {
throw UndefinedVarError({
- .msg = hintfmt("undefined variable '%1%'", name),
+ .msg = hintfmt("undefined variable (ExprVar bindvars) '%1%'", name),
.errPos = pos
});
+ }
fromWith = true;
this->level = withLevel;
}
-void ExprSelect::bindVars(const StaticEnv & env)
+void ExprSelect::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
e->bindVars(env);
if (def) def->bindVars(env);
for (auto & i : attrPath)
@@ -310,64 +325,79 @@ void ExprSelect::bindVars(const StaticEnv & env)
i.expr->bindVars(env);
}
-void ExprOpHasAttr::bindVars(const StaticEnv & env)
+void ExprOpHasAttr::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
e->bindVars(env);
for (auto & i : attrPath)
if (!i.symbol.set())
i.expr->bindVars(env);
}
-void ExprAttrs::bindVars(const StaticEnv & env)
+void ExprAttrs::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
- const StaticEnv * dynamicEnv = &env;
- StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0);
+ if (debuggerHook)
+ staticenv = env;
if (recursive) {
- dynamicEnv = &newEnv;
+ auto newEnv = std::shared_ptr<StaticEnv>(new StaticEnv(false, env.get(), recursive ? attrs.size() : 0));
Displacement displ = 0;
for (auto & i : attrs)
- newEnv.vars.emplace_back(i.first, i.second.displ = displ++);
+ newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
// No need to sort newEnv since attrs is in sorted order.
for (auto & i : attrs)
i.second.e->bindVars(i.second.inherited ? env : newEnv);
- }
- else
+ for (auto & i : dynamicAttrs) {
+ i.nameExpr->bindVars(newEnv);
+ i.valueExpr->bindVars(newEnv);
+ }
+ }
+ else {
for (auto & i : attrs)
i.second.e->bindVars(env);
- for (auto & i : dynamicAttrs) {
- i.nameExpr->bindVars(*dynamicEnv);
- i.valueExpr->bindVars(*dynamicEnv);
+ for (auto & i : dynamicAttrs) {
+ i.nameExpr->bindVars(env);
+ i.valueExpr->bindVars(env);
+ }
}
}
-void ExprList::bindVars(const StaticEnv & env)
+void ExprList::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
for (auto & i : elems)
i->bindVars(env);
}
-void ExprLambda::bindVars(const StaticEnv & env)
+void ExprLambda::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
- StaticEnv newEnv(
- false, &env,
- (hasFormals() ? formals->formals.size() : 0) +
- (arg.empty() ? 0 : 1));
+ if (debuggerHook)
+ staticenv = env;
+
+ auto newEnv = std::shared_ptr<StaticEnv>(
+ new StaticEnv(
+ false, env.get(),
+ (hasFormals() ? formals->formals.size() : 0) +
+ (arg.empty() ? 0 : 1)));
Displacement displ = 0;
- if (!arg.empty()) newEnv.vars.emplace_back(arg, displ++);
+ if (!arg.empty()) newEnv->vars.emplace_back(arg, displ++);
if (hasFormals()) {
for (auto & i : formals->formals)
- newEnv.vars.emplace_back(i.name, displ++);
+ newEnv->vars.emplace_back(i.name, displ++);
- newEnv.sort();
+ newEnv->sort();
for (auto & i : formals->formals)
if (i.def) i.def->bindVars(newEnv);
@@ -376,20 +406,26 @@ void ExprLambda::bindVars(const StaticEnv & env)
body->bindVars(newEnv);
}
-void ExprCall::bindVars(const StaticEnv & env)
+void ExprCall::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
fun->bindVars(env);
for (auto e : args)
e->bindVars(env);
}
-void ExprLet::bindVars(const StaticEnv & env)
+void ExprLet::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
- StaticEnv newEnv(false, &env, attrs->attrs.size());
+ if (debuggerHook)
+ staticenv = env;
+
+ auto newEnv = std::shared_ptr<StaticEnv>(new StaticEnv(false, env.get(), attrs->attrs.size()));
Displacement displ = 0;
for (auto & i : attrs->attrs)
- newEnv.vars.emplace_back(i.first, i.second.displ = displ++);
+ newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
// No need to sort newEnv since attrs->attrs is in sorted order.
@@ -399,51 +435,69 @@ void ExprLet::bindVars(const StaticEnv & env)
body->bindVars(newEnv);
}
-void ExprWith::bindVars(const StaticEnv & env)
+void ExprWith::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
/* Does this `with' have an enclosing `with'? If so, record its
level so that `lookupVar' can look up variables in the previous
`with' if this one doesn't contain the desired attribute. */
const StaticEnv * curEnv;
Level level;
prevWith = 0;
- for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++)
+ for (curEnv = env.get(), level = 1; curEnv; curEnv = curEnv->up, level++)
if (curEnv->isWith) {
prevWith = level;
break;
}
attrs->bindVars(env);
- StaticEnv newEnv(true, &env);
+ auto newEnv = std::shared_ptr<StaticEnv>(new StaticEnv(true, env.get()));
body->bindVars(newEnv);
}
-void ExprIf::bindVars(const StaticEnv & env)
+void ExprIf::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
cond->bindVars(env);
then->bindVars(env);
else_->bindVars(env);
}
-void ExprAssert::bindVars(const StaticEnv & env)
+void ExprAssert::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
cond->bindVars(env);
body->bindVars(env);
}
-void ExprOpNot::bindVars(const StaticEnv & env)
+void ExprOpNot::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
e->bindVars(env);
}
-void ExprConcatStrings::bindVars(const StaticEnv & env)
+void ExprConcatStrings::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
for (auto & i : *es)
i.second->bindVars(env);
}
-void ExprPos::bindVars(const StaticEnv & env)
+void ExprPos::bindVars(const std::shared_ptr<const StaticEnv> &env)
{
+ if (debuggerHook)
+ staticenv = env;
+
}
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 4e923ac89..64375b5ab 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -18,6 +18,7 @@ MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error);
+extern std::function<void(const Error * error, const Env & env, const Expr & expr)> debuggerHook;
/* Position objects. */
@@ -77,10 +78,13 @@ struct Expr
{
virtual ~Expr() { };
virtual void show(std::ostream & str) const;
- virtual void bindVars(const StaticEnv & env);
+ virtual void bindVars(const std::shared_ptr<const StaticEnv> & env);
virtual void eval(EvalState & state, Env & env, Value & v);
virtual Value * maybeThunk(EvalState & state, Env & env);
virtual void setName(Symbol & name);
+
+ std::shared_ptr<const StaticEnv> staticenv;
+ virtual Pos* getPos() = 0;
};
std::ostream & operator << (std::ostream & str, const Expr & e);
@@ -88,15 +92,16 @@ std::ostream & operator << (std::ostream & str, const Expr & e);
#define COMMON_METHODS \
void show(std::ostream & str) const; \
void eval(EvalState & state, Env & env, Value & v); \
- void bindVars(const StaticEnv & env);
+ void bindVars(const std::shared_ptr<const StaticEnv> & env);
struct ExprInt : Expr
{
NixInt n;
Value v;
ExprInt(NixInt n) : n(n) { v.mkInt(n); };
- COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
+ Pos* getPos() { return 0; }
+ COMMON_METHODS
};
struct ExprFloat : Expr
@@ -104,8 +109,9 @@ struct ExprFloat : Expr
NixFloat nf;
Value v;
ExprFloat(NixFloat nf) : nf(nf) { v.mkFloat(nf); };
- COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
+ Pos* getPos() { return 0; }
+ COMMON_METHODS
};
struct ExprString : Expr
@@ -113,8 +119,9 @@ struct ExprString : Expr
string s;
Value v;
ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); };
- COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
+ Pos* getPos() { return 0; }
+ COMMON_METHODS
};
struct ExprPath : Expr
@@ -122,8 +129,9 @@ struct ExprPath : Expr
string s;
Value v;
ExprPath(const string & s) : s(s) { v.mkPath(this->s.c_str()); };
- COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
+ Pos* getPos() { return 0; }
+ COMMON_METHODS
};
typedef uint32_t Level;
@@ -149,8 +157,9 @@ struct ExprVar : Expr
ExprVar(const Symbol & name) : name(name) { };
ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { };
- COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
+ Pos* getPos() { return &pos; }
+ COMMON_METHODS
};
struct ExprSelect : Expr
@@ -160,6 +169,7 @@ struct ExprSelect : Expr
AttrPath attrPath;
ExprSelect(const Pos & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { };
ExprSelect(const Pos & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
@@ -168,6 +178,7 @@ struct ExprOpHasAttr : Expr
Expr * e;
AttrPath attrPath;
ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { };
+ Pos* getPos() { return e->getPos(); }
COMMON_METHODS
};
@@ -196,6 +207,7 @@ struct ExprAttrs : Expr
DynamicAttrDefs dynamicAttrs;
ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { };
ExprAttrs() : recursive(false), pos(noPos) { };
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
@@ -203,6 +215,7 @@ struct ExprList : Expr
{
std::vector<Expr *> elems;
ExprList() { };
+ Pos* getPos() { return 0; }
COMMON_METHODS
};
@@ -251,6 +264,7 @@ struct ExprLambda : Expr
void setName(Symbol & name);
string showNamePos() const;
inline bool hasFormals() const { return formals != nullptr; }
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
@@ -262,6 +276,7 @@ struct ExprCall : Expr
ExprCall(const Pos & pos, Expr * fun, std::vector<Expr *> && args)
: fun(fun), args(args), pos(pos)
{ }
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
@@ -270,6 +285,7 @@ struct ExprLet : Expr
ExprAttrs * attrs;
Expr * body;
ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { };
+ Pos* getPos() { return 0; }
COMMON_METHODS
};
@@ -279,6 +295,7 @@ struct ExprWith : Expr
Expr * attrs, * body;
size_t prevWith;
ExprWith(const Pos & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
@@ -287,6 +304,7 @@ struct ExprIf : Expr
Pos pos;
Expr * cond, * then, * else_;
ExprIf(const Pos & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { };
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
@@ -295,6 +313,7 @@ struct ExprAssert : Expr
Pos pos;
Expr * cond, * body;
ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
@@ -302,6 +321,7 @@ struct ExprOpNot : Expr
{
Expr * e;
ExprOpNot(Expr * e) : e(e) { };
+ Pos* getPos() { return 0; }
COMMON_METHODS
};
@@ -316,11 +336,12 @@ struct ExprOpNot : Expr
{ \
str << "(" << *e1 << " " s " " << *e2 << ")"; \
} \
- void bindVars(const StaticEnv & env) \
+ void bindVars(const std::shared_ptr<const StaticEnv> & env) \
{ \
e1->bindVars(env); e2->bindVars(env); \
} \
void eval(EvalState & state, Env & env, Value & v); \
+ Pos* getPos() { return &pos; } \
};
MakeBinOp(ExprOpEq, "==")
@@ -338,6 +359,7 @@ struct ExprConcatStrings : Expr
vector<std::pair<Pos, Expr *> > * es;
ExprConcatStrings(const Pos & pos, bool forceString, vector<std::pair<Pos, Expr *> > * es)
: pos(pos), forceString(forceString), es(es) { };
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
@@ -345,6 +367,7 @@ struct ExprPos : Expr
{
Pos pos;
ExprPos(const Pos & pos) : pos(pos) { };
+ Pos* getPos() { return &pos; }
COMMON_METHODS
};
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index f0c80ebd5..d9291e7a2 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -23,6 +23,7 @@
#include "nixexpr.hh"
#include "eval.hh"
#include "globals.hh"
+#include <iostream>
namespace nix {
@@ -643,7 +644,7 @@ namespace nix {
Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
- const PathView path, const PathView basePath, StaticEnv & staticEnv)
+ const PathView path, const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
yyscan_t scanner;
ParseData data(*this);
@@ -706,7 +707,7 @@ Expr * EvalState::parseExprFromFile(const Path & path)
}
-Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv)
+Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv)
{
auto buffer = readFile(path);
// readFile should have left some extra space for terminators
@@ -715,7 +716,7 @@ Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv)
}
-Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv)
+Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
s.append("\0\0", 2);
return parse(s.data(), s.size(), foString, "", basePath, staticEnv);
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index defb861e6..3b429f328 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -215,11 +215,11 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
Env * env = &state.allocEnv(vScope->attrs->size());
env->up = &state.baseEnv;
- StaticEnv staticEnv(false, &state.staticBaseEnv, vScope->attrs->size());
+ auto staticEnv = std::shared_ptr<StaticEnv>(new StaticEnv(false, state.staticBaseEnv.get(), vScope->attrs->size()));
unsigned int displ = 0;
for (auto & attr : *vScope->attrs) {
- staticEnv.vars.emplace_back(attr.name, displ);
+ staticEnv->vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value;
}
@@ -698,6 +698,32 @@ static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
.fun = prim_genericClosure,
});
+static RegisterPrimOp primop_break({
+ .name = "break",
+ .args = {"v"},
+ .doc = R"(
+ In debug mode, pause Nix expression evaluation and enter the repl.
+ )",
+ .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
+ {
+ std::cout << "primop_break, value: " << *args[0] << std::endl;
+ // PathSet context;
+ // string s = state.coerceToString(pos, *args[0], context);
+ if (debuggerHook && !state.debugTraces.empty())
+ {
+ auto &dt = state.debugTraces.front();
+ // std::optional<ErrPos> pos;
+ // const Expr &expr;
+ // const Env &env;
+ // hintformat hint;
+ debuggerHook(nullptr, dt.env, dt.expr);
+
+ // returning the value we were passed.
+ v = *args[0];
+ }
+ }
+});
+
static RegisterPrimOp primop_abort({
.name = "abort",
.args = {"s"},
@@ -3896,7 +3922,7 @@ void EvalState::createBaseEnv()
because attribute lookups expect it to be sorted. */
baseEnv.values[0]->attrs->sort();
- staticBaseEnv.sort();
+ staticBaseEnv->sort();
/* Note: we have to initialize the 'derivation' constant *after*
building baseEnv/staticBaseEnv because it uses 'builtins'. */
diff --git a/src/libutil/error.hh b/src/libutil/error.hh
index 6fe5e4857..0c86f090e 100644
--- a/src/libutil/error.hh
+++ b/src/libutil/error.hh
@@ -100,6 +100,15 @@ struct ErrPos {
}
};
+std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos);
+void printCodeLines(std::ostream & out,
+ const string & prefix,
+ const ErrPos & errPos,
+ const LinesOfCode & loc);
+
+void printAtPos(const ErrPos & pos, std::ostream & out);
+
+
struct Trace {
std::optional<ErrPos> pos;
hintformat hint;