aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libcmd/command.cc31
-rw-r--r--src/libcmd/command.hh6
-rw-r--r--src/libcmd/local.mk5
-rw-r--r--src/libcmd/repl.cc (renamed from src/nix/repl.cc)64
-rw-r--r--src/libexpr/eval.cc254
-rw-r--r--src/libexpr/eval.hh12
-rw-r--r--src/libexpr/nixexpr.cc126
-rw-r--r--src/libexpr/nixexpr.hh9
-rw-r--r--src/libexpr/parser.y6
-rw-r--r--src/libexpr/primops.cc2
-rw-r--r--src/libutil/error.hh6
11 files changed, 400 insertions, 121 deletions
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index fd3edfc46..4c5d985aa 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -56,6 +56,37 @@ void StoreCommand::run()
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;
+
+ref<EvalState> EvalCommand::getEvalState()
+{
+ if (!evalState) {
+ evalState = std::make_shared<EvalState>(searchPath, getStore());
+ if (startReplOnEvalErrors)
+ debuggerHook = [evalState{ref<EvalState>(evalState)}](const Error & error, const Env & env, const Expr & expr) {
+ printError("%s\n\n" ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL, error.what());
+
+ printStaticEnvBindings(expr);
+
+ std::cout << "expr: " << std::endl;
+ expr.show(std::cout);
+ std::cout << std::endl;
+
+ if (expr.staticenv)
+ {
+ auto vm = mapStaticEnvBindings(*expr.staticenv.get(), env);
+ runRepl(evalState, *vm);
+ }
+ };
+ }
+ return ref<EvalState>(evalState);
}
EvalCommand::~EvalCommand()
diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh
index 07f398468..0d847d255 100644
--- a/src/libcmd/command.hh
+++ b/src/libcmd/command.hh
@@ -45,6 +45,8 @@ private:
struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
+ bool startReplOnEvalErrors = false;
+
EvalCommand();
~EvalCommand();
@@ -310,4 +312,8 @@ void printClosureDiff(
const StorePath & afterPath,
std::string_view indent);
+void runRepl(
+ ref<EvalState> evalState,
+ const std::map<std::string, Value *> & extraEnv);
+
}
diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk
index 8b0662753..1ec258a54 100644
--- a/src/libcmd/local.mk
+++ b/src/libcmd/local.mk
@@ -6,10 +6,11 @@ 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 = $(EDITLINE_LIBS) -llowdown
libcmd_LDFLAGS += -llowdown -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 fd86174f2..6faa9f9fa 100644
--- a/src/nix/repl.cc
+++ b/src/libcmd/repl.cc
@@ -47,20 +47,20 @@ struct NixRepl
#endif
{
string curDir;
- std::unique_ptr<EvalState> state;
+ ref<EvalState> state;
Bindings * autoArgs;
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);
@@ -72,13 +72,13 @@ struct NixRepl
void initEnv();
void reloadFiles();
void addAttrsToScope(Value & attrs);
- void addVarToScope(const Symbol & name, Value & v);
+ void addVarToScope(const Symbol & name, Value * v);
Expr * parseString(string s);
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);
};
@@ -91,9 +91,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(".");
@@ -202,8 +202,9 @@ 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();
if (!loadedFiles.empty()) notice("");
@@ -560,8 +561,8 @@ bool NixRepl::processLine(string line)
isVarName(name = removeWhitespace(string(line, 0, p))))
{
Expr * e = parseString(string(line, p + 1));
- Value & v(*state->allocValue());
- v.mkThunk(env, e);
+ Value *v = new Value(*state->allocValue());
+ v->mkThunk(env, e);
addVarToScope(state->symbols.create(name), v);
} else {
Value v;
@@ -609,10 +610,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);
}
@@ -643,12 +644,12 @@ void NixRepl::addAttrsToScope(Value & attrs)
}
-void NixRepl::addVarToScope(const Symbol & name, Value & v)
+void NixRepl::addVarToScope(const Symbol & name, Value * v)
{
if (displ >= envSize)
throw Error("environment full; cannot add more variables");
- staticEnv.vars.emplace_back(name, displ);
- staticEnv.sort();
+ staticEnv->vars.emplace_back(name, displ);
+ staticEnv->sort();
env->values[displ++] = &v;
varNames.insert((string) name);
}
@@ -813,6 +814,28 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
return str;
}
+void runRepl(
+ ref<EvalState> evalState,
+ const std::map<std::string, Value *> & extraEnv)
+{
+ auto repl = std::make_unique<NixRepl>(evalState);
+
+ repl->initEnv();
+
+ 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());
+ // printError("The following extra variables are in scope: %s\n", concatStringsSep(", ", names));
+
+ repl->mainLoop({});
+}
+
struct CmdRepl : StoreCommand, MixEvalArgs
{
std::vector<std::string> files;
@@ -841,7 +864,10 @@ 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->mainLoop(files);
}
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 97fc04711..a20123f34 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -35,6 +35,7 @@
namespace nix {
+std::function<void(const Error & error, const Env & env, const Expr & expr)> debuggerHook;
static char * dupString(const char * s)
{
@@ -417,7 +418,7 @@ EvalState::EvalState(
, buildStore(buildStore ? buildStore : store)
, regexCache(makeRegexCache())
, baseEnv(allocEnv(128))
- , staticBaseEnv(false, 0)
+ , staticBaseEnv(new StaticEnv(false, 0))
{
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
@@ -615,7 +616,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;
@@ -641,7 +642,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;
@@ -671,84 +672,210 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
}
+
+void printStaticEnvBindings(const StaticEnv &se, int lvl)
+{
+ for (auto i = se.vars.begin(); i != se.vars.end(); ++i)
+ {
+ std::cout << lvl << i->first << std::endl;
+ }
+
+ if (se.up) {
+ printStaticEnvBindings(*se.up, ++lvl);
+ }
+
+}
+
+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.
+ 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({
+ auto error = TypeError({
+ .msg = hintfmt(s, v),
+ .errPos = pos
+ });
+
+ if (debuggerHook && expr)
+ debuggerHook(error, env, *expr);
+
+ throw error;
+}
+
+LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const string &s2, Env & env, Expr *expr))
+{
+ 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 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))
@@ -761,13 +888,11 @@ LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, con
e.addTrace(pos, s, s2);
}
-
void mkString(Value & v, const char * s)
{
v.mkString(dupString(s));
}
-
Value & mkString(Value & v, std::string_view s, const PathSet & context)
{
v.mkString(dupStringWithLen(s.data(), s.size()));
@@ -808,8 +933,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, 0);
+ }
for (size_t l = env->prevWith; l; --l, env = env->up) ;
}
}
@@ -825,6 +951,7 @@ Value * EvalState::allocValue()
Env & EvalState::allocEnv(size_t size)
{
+
nrEnvs++;
nrValuesInEnvs += size;
Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
@@ -835,6 +962,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)
{
@@ -1118,7 +1254,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 */
@@ -1206,7 +1343,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;
@@ -1293,7 +1430,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);
@@ -1308,7 +1444,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++;
@@ -1487,8 +1623,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);
}
}
}
@@ -1522,7 +1659,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);
}
@@ -1672,15 +1809,16 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
firstType = nFloat;
nf = n;
nf += vTmp.fpoint;
- } else
- throwEvalError(pos, "cannot add %1% to an integer", showType(vTmp));
+ } else {
+ throwEvalError(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(pos, "cannot add %1% to a float", showType(vTmp));
+ throwEvalError(pos, "cannot add %1% to a float", showType(vTmp), env, this);
} else
/* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type
@@ -1745,7 +1883,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;
}
@@ -1756,7 +1895,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;
}
@@ -1765,7 +1905,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;
}
@@ -1780,7 +1921,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);
}
@@ -1788,10 +1930,8 @@ string 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 string(v.string.s);
}
@@ -1842,10 +1982,10 @@ string 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;
}
@@ -1898,7 +2038,9 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
return *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);
}
@@ -1906,7 +2048,6 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
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";
@@ -1929,14 +2070,17 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
}
}
- 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);
@@ -1961,7 +2105,8 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
{
string path = coerceToString(pos, v, context, false, false);
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;
}
@@ -2040,7 +2185,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 1aab8e166..485c2df83 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -24,6 +24,8 @@ 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);
struct PrimOp
{
@@ -34,6 +36,7 @@ struct PrimOp
const char * doc = nullptr;
};
+typedef std::map<std::string, Value *> valmap;
struct Env
{
@@ -43,6 +46,7 @@ struct Env
Value * values[0];
};
+valmap * mapStaticEnvBindings(const StaticEnv &se, const Env &env);
Value & mkString(Value & v, std::string_view s, const PathSet & context = PathSet());
@@ -175,10 +179,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_view s, const Path & basePath, StaticEnv & staticEnv);
+ Expr * parseExprFromString(std::string_view s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromString(std::string_view s, const Path & basePath);
Expr * parseStdin();
@@ -267,7 +271,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:
@@ -308,7 +312,7 @@ private:
friend struct ExprLet;
Expr * parse(const char * text, FileOrigin origin, const Path & path,
- const Path & basePath, StaticEnv & staticEnv);
+ const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv);
public:
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index 57c2f6e44..696b149e3 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,21 +325,24 @@ 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)
@@ -334,30 +352,42 @@ void ExprAttrs::bindVars(const StaticEnv & env)
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;
@@ -378,6 +408,9 @@ void ExprLambda::bindVars(const StaticEnv & env)
void ExprCall::bindVars(const StaticEnv & env)
{
+ if (debuggerHook)
+ staticenv = env;
+
fun->bindVars(env);
for (auto e : args)
e->bindVars(env);
@@ -385,7 +418,10 @@ void ExprCall::bindVars(const StaticEnv & env)
void ExprLet::bindVars(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)
@@ -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->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 13256272c..825933fa1 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,12 @@ 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;
};
std::ostream & operator << (std::ostream & str, const Expr & e);
@@ -88,7 +91,7 @@ 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
{
@@ -313,7 +316,7 @@ 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); \
} \
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index c1f4e72e0..58af0df7d 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -590,7 +590,7 @@ namespace nix {
Expr * EvalState::parse(const char * text, FileOrigin origin,
- const Path & path, const Path & basePath, StaticEnv & staticEnv)
+ const Path & path, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
yyscan_t scanner;
ParseData data(*this);
@@ -653,13 +653,13 @@ 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)
{
return parse(readFile(path).c_str(), foFile, path, dirOf(path), staticEnv);
}
-Expr * EvalState::parseExprFromString(std::string_view s, const Path & basePath, StaticEnv & staticEnv)
+Expr * EvalState::parseExprFromString(std::string_view s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
return parse(s.data(), foString, "", basePath, staticEnv);
}
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 8cbeaa520..a9ee96bfa 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -184,7 +184,7 @@ 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) {
diff --git a/src/libutil/error.hh b/src/libutil/error.hh
index ff58d3e00..b6670c8b2 100644
--- a/src/libutil/error.hh
+++ b/src/libutil/error.hh
@@ -100,6 +100,12 @@ struct ErrPos {
}
};
+std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos);
+void printCodeLines(std::ostream & out,
+ const string & prefix,
+ const ErrPos & errPos,
+ const LinesOfCode & loc);
+
struct Trace {
std::optional<ErrPos> pos;
hintformat hint;