lix/src/libcmd/repl.cc

1073 lines
31 KiB
C++
Raw Normal View History

#include <iostream>
#include <cstdlib>
2018-10-29 13:44:58 +00:00
#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" {
2018-10-29 13:44:58 +00:00
#include <editline.h>
}
#endif
2018-10-29 13:44:58 +00:00
#include "ansicolor.hh"
#include "shared.hh"
#include "eval.hh"
#include "eval-inline.hh"
#include "attr-path.hh"
#include "store-api.hh"
#include "log-store.hh"
#include "common-eval-args.hh"
2013-09-06 12:58:53 +00:00
#include "get-drvs.hh"
#include "derivations.hh"
#include "globals.hh"
2017-04-25 16:48:40 +00:00
#include "command.hh"
#include "finally.hh"
2020-08-24 16:10:33 +00:00
#include "markdown.hh"
#include "local-fs-store.hh"
#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
#include <gc/gc_cpp.h>
#endif
2017-04-25 16:48:40 +00:00
namespace nix {
struct NixRepl
#if HAVE_BOEHMGC
: gc
#endif
{
std::string curDir;
ref<EvalState> state;
2018-09-10 07:07:50 +00:00
Bindings * autoArgs;
size_t debugTraceIndex;
2021-12-20 19:32:21 +00:00
2013-09-09 14:02:46 +00:00
Strings loadedFiles;
2013-09-09 15:06:14 +00:00
const static int envSize = 32768;
2021-09-14 16:49:22 +00:00
std::shared_ptr<StaticEnv> staticEnv;
Env * env;
int displ;
2013-09-06 17:51:59 +00:00
StringSet varNames;
const Path historyFile;
NixRepl(ref<EvalState> state);
2017-04-25 16:48:40 +00:00
~NixRepl();
void mainLoop(const std::vector<std::string> & files);
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);
2013-09-09 15:06:14 +00:00
void initEnv();
2021-12-20 19:32:21 +00:00
void loadFiles();
2013-09-09 14:02:46 +00:00
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);
2022-02-21 15:28:23 +00:00
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(ref<EvalState> state)
: state(state)
2022-03-28 18:09:21 +00:00
, debugTraceIndex(0)
2021-09-14 16:49:22 +00:00
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
, historyFile(getDataDir() + "/nix/repl-history")
{
curDir = absPath(".");
}
2017-04-25 16:48:40 +00:00
NixRepl::~NixRepl()
{
2018-10-29 13:44:58 +00:00
write_history(historyFile.c_str());
}
std::string runNix(Path program, const Strings & args,
const std::optional<std::string> & input = {})
{
auto subprocessEnv = getEnv();
subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue();
2021-09-14 06:19:41 +00:00
auto res = runProgram(RunOptions {
.program = settings.nixBinDir+ "/" + program,
.args = args,
.environment = subprocessEnv,
.input = input,
});
if (!statusOk(res.first))
throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
return res.second;
}
static NixRepl * curRepl; // ugly
2018-10-29 13:44:58 +00:00
static char * completionCallback(char * s, int *match) {
2020-06-24 19:10:41 +00:00
auto possible = curRepl->completePrefix(s);
if (possible.size() == 1) {
2020-06-24 19:14:49 +00:00
*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) {
2020-06-24 19:10:41 +00:00
*match = 1;
2020-06-24 19:14:49 +00:00
auto *res = strdup(std::string(*possible.begin(), start, len).c_str());
2020-06-24 19:10:41 +00:00
if (!res) throw Error("allocation failure");
return res;
2020-06-24 19:14:49 +00:00
}
2020-06-24 19:10:41 +00:00
}
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
*match = 0;
return nullptr;
2018-10-29 13:44:58 +00:00
}
static int listPossibleCallback(char *s, char ***avp) {
2020-06-24 19:10:41 +00:00
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;
};
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
vp = check((char **)malloc(possible.size() * sizeof(char*)));
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
for (auto & p : possible)
2020-06-24 19:14:49 +00:00
vp[ac++] = check(strdup(p.c_str()));
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
*avp = vp;
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
return ac;
2017-04-25 16:48:40 +00:00
}
namespace {
2020-06-24 19:10:41 +00:00
// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
volatile sig_atomic_t g_signal_received = 0;
2020-06-24 19:10:41 +00:00
void sigintHandler(int signo) {
g_signal_received = signo;
}
}
2017-04-25 16:48:40 +00:00
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(const std::vector<std::string> & files)
{
std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
if (!files.empty()) {
for (auto & i : files)
loadedFiles.push_back(i);
2021-11-30 21:15:02 +00:00
}
2013-09-09 14:02:46 +00:00
2021-12-20 19:32:21 +00:00
loadFiles();
if (!loadedFiles.empty()) notice("");
2018-10-29 13:44:58 +00:00
// Allow nix-repl specific settings in .inputrc
rl_readline_name = "nix-repl";
createDirs(dirOf(historyFile));
#ifndef READLINE
2018-10-29 13:44:58 +00:00
el_hist_size = 1000;
#endif
2018-10-29 13:44:58 +00:00
read_history(historyFile.c_str());
curRepl = this;
#ifndef READLINE
2018-10-29 13:44:58 +00:00
rl_set_complete_func(completionCallback);
rl_set_list_possib_func(listPossibleCallback);
#endif
std::string input;
while (true) {
2016-02-18 13:04:55 +00:00
// 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> " : " ")) {
2022-02-15 16:49:25 +00:00
// ctrl-D should exit the debugger.
state->debugStop = false;
state->debugQuit = true;
2013-09-09 13:02:56 +00:00
break;
2022-02-15 16:49:25 +00:00
}
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 {
2020-06-24 19:14:49 +00:00
printMsg(lvlError, e.msg());
}
} catch (EvalError & e) {
2022-04-29 17:24:54 +00:00
// 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 (debuggerHook && !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)
2013-09-06 17:51:59 +00:00
{
struct sigaction act, old;
sigset_t savedSignalMask, set;
auto setupSignals = [&]() {
2020-06-24 19:10:41 +00:00
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 = [&]() {
2020-06-24 19:10:41 +00:00
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
2020-06-24 19:10:41 +00:00
if (sigaction(SIGINT, &old, 0))
throw SysError("restoring handler for SIGINT");
};
setupSignals();
Finally resetTerminal([&]() { rl_deprep_terminal(); });
2018-10-29 13:44:58 +00:00
char * s = readline(prompt.c_str());
2017-11-28 00:30:05 +00:00
Finally doFree([&]() { free(s); });
restoreSignals();
if (g_signal_received) {
g_signal_received = 0;
input.clear();
return true;
}
2018-10-29 13:44:58 +00:00
if (!s)
2020-06-24 19:10:41 +00:00
return false;
input += s;
2018-04-11 09:42:17 +00:00
input += '\n';
return true;
2013-09-06 17:51:59 +00:00
}
StringSet NixRepl::completePrefix(const std::string & prefix)
2013-09-06 17:51:59 +00:00
{
StringSet completions;
2017-04-25 17:19:15 +00:00
size_t start = prefix.find_last_of(" \n\r\t(){}[]");
std::string prev, cur;
if (start == std::string::npos) {
prev = "";
cur = prefix;
2016-02-18 12:50:52 +00:00
} else {
prev = std::string(prefix, 0, start + 1);
cur = std::string(prefix, start + 1);
2013-09-06 17:51:59 +00:00
}
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);
2022-01-21 15:43:16 +00:00
state->forceAttrs(v, noPos);
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.
}
2013-09-06 17:51:59 +00:00
}
return completions;
2013-09-06 17:51:59 +00:00
}
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)
2022-03-28 21:28:59 +00:00
{
initEnv();
2022-03-28 21:28:59 +00:00
if (dt.expr.staticEnv) {
auto vm = mapStaticEnvBindings(state->symbols, *dt.expr.staticEnv.get(), dt.env);
2022-03-28 21:28:59 +00:00
// add staticenv vars.
for (auto & [name, value] : *(vm.get()))
addVarToScope(state->symbols.create(name), *value);
2022-03-28 21:28:59 +00:00
}
}
bool NixRepl::processLine(std::string line)
{
line = trim(line);
2013-09-09 13:02:56 +00:00
if (line == "") return true;
_isInterrupted = false;
std::string command, arg;
2013-09-09 13:02:56 +00:00
if (line[0] == ':') {
2016-02-18 12:59:51 +00:00
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));
2013-09-09 13:02:56 +00:00
} else {
arg = line;
}
2013-09-09 14:02:46 +00:00
if (command == ":?" || command == ":help") {
2020-12-09 12:07:01 +00:00
// FIXME: convert to Markdown, include in the 'nix repl' manpage.
std::cout
2020-06-24 19:14:49 +00:00
<< "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"
2020-06-24 19:14:49 +00:00
<< " :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"
2020-06-24 19:14:49 +00:00
<< " :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"
2020-06-24 19:14:49 +00:00
<< " :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 (debuggerHook) {
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"
;
}
2021-12-20 19:32:21 +00:00
}
else if (debuggerHook) {
if (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);
2022-03-28 18:09:21 +00:00
}
}
else if (command == ":env") {
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
if (idx == debugTraceIndex) {
printEnvBindings(state->symbols, i.expr, i.env);
break;
}
2022-03-28 18:09:21 +00:00
}
}
else if (command == ":st") {
2022-03-28 18:09:21 +00:00
try {
// change the DebugTrace index.
debugTraceIndex = stoi(arg);
} catch (...) { }
2022-03-28 18:09:21 +00:00
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
if (idx == debugTraceIndex) {
2022-03-28 18:09:21 +00:00
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, state->positions, i);
2022-04-07 17:17:57 +00:00
std::cout << std::endl;
printEnvBindings(state->symbols, i.expr, i.env);
loadDebugTraceEnv(i);
2022-03-28 18:09:21 +00:00
break;
}
2022-02-15 16:49:25 +00:00
}
2021-12-20 19:32:21 +00:00
}
else if (command == ":s" || command == ":step") {
2022-02-15 16:49:25 +00:00
// set flag to stop at next DebugTrace; exit repl.
state->debugStop = true;
return false;
}
else if (command == ":c" || command == ":continue") {
2022-02-15 16:49:25 +00:00
// set flag to run to next breakpoint or end of program; exit repl.
state->debugStop = false;
return false;
}
2013-09-09 11:22:33 +00:00
}
2013-09-09 14:02:46 +00:00
else if (command == ":a" || command == ":add") {
Value v;
2013-09-09 13:02:56 +00:00
evalString(arg, v);
addAttrsToScope(v);
}
2013-09-09 14:02:46 +00:00
else if (command == ":l" || command == ":load") {
state->resetFileCache();
2013-09-09 13:02:56 +00:00
loadFile(arg);
}
else if (command == ":lf" || command == ":load-flake") {
loadFlake(arg);
}
2013-09-09 14:02:46 +00:00
else if (command == ":r" || command == ":reload") {
state->resetFileCache();
2013-09-09 14:02:46 +00:00
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).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);
2019-10-23 14:48:28 +00:00
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;
2013-09-09 13:02:56 +00:00
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") {
2013-09-06 12:58:53 +00:00
Value v;
2013-09-09 13:02:56 +00:00
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});
}
}
2013-09-09 14:02:46 +00:00
else if (command == ":p" || command == ":print") {
Value v;
2013-09-09 13:02:56 +00:00
evalString(arg, v);
printValue(std::cout, v, 1000000000) << std::endl;
}
2022-02-15 16:49:25 +00:00
else if (command == ":q" || command == ":quit") {
state->debugStop = false;
state->debugQuit = true;
2013-09-09 13:02:56 +00:00
return false;
2022-02-15 16:49:25 +00:00
}
2013-09-09 13:02:56 +00:00
else if (command == ":doc") {
Value v;
evalString(arg, v);
2020-08-25 11:31:11 +00:00
if (auto doc = state->getDoc(v)) {
std::string markdown;
if (!doc->args.empty() && doc->name) {
auto args = doc->args;
2020-08-24 16:10:33 +00:00
for (auto & arg : args)
arg = "*" + arg + "*";
2020-08-25 11:31:11 +00:00
markdown +=
"**Synopsis:** `builtins." + (std::string) (*doc->name) + "` "
+ concatStringsSep(" ", args) + "\n\n";
}
markdown += stripIndentation(doc->doc);
2020-08-24 16:10:33 +00:00
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);
};
}
2013-09-09 13:02:56 +00:00
else if (command != "")
throw Error("unknown command '%1%'", command);
2013-09-06 12:58:53 +00:00
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));
2022-01-05 21:25:45 +00:00
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;
}
}
2013-09-09 13:02:56 +00:00
return true;
}
void NixRepl::loadFile(const Path & path)
{
2013-09-09 14:02:46 +00:00
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);
}
2013-09-09 15:06:14 +00:00
void NixRepl::initEnv()
{
env = &state->allocEnv(envSize);
env->up = &state->baseEnv;
2013-09-09 15:06:14 +00:00
displ = 0;
2021-09-14 16:49:22 +00:00
staticEnv->vars.clear();
2013-09-09 15:22:42 +00:00
varNames.clear();
2021-09-14 16:49:22 +00:00
for (auto & i : state->staticBaseEnv->vars)
varNames.emplace(state->symbols[i.first]);
2013-09-09 15:06:14 +00:00
}
2013-09-09 14:02:46 +00:00
void NixRepl::reloadFiles()
{
2013-09-09 15:06:14 +00:00
initEnv();
2021-12-20 19:32:21 +00:00
loadFiles();
}
void NixRepl::loadFiles()
{
2013-09-09 14:02:46 +00:00
Strings old = loadedFiles;
loadedFiles.clear();
bool first = true;
for (auto & i : old) {
if (!first) notice("");
first = false;
notice("Loading '%1%'...", i);
loadFile(i);
2013-09-09 14:02:46 +00:00
}
}
void NixRepl::addAttrsToScope(Value & attrs)
{
2022-02-03 23:31:33 +00:00
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); });
if (displ + attrs.attrs->size() >= envSize)
throw Error("environment full; cannot add more variables");
for (auto & i : *attrs.attrs) {
2022-01-04 01:13:16 +00:00
staticEnv->vars.emplace_back(i.name, displ);
env->values[displ++] = i.value;
varNames.emplace(state->symbols[i.name]);
}
2022-01-04 01:13:16 +00:00
staticEnv->sort();
staticEnv->deduplicate();
notice("Added %1% variables.", attrs.attrs->size());
}
void NixRepl::addVarToScope(const Symbol name, Value & v)
{
2013-09-09 15:06:14 +00:00
if (displ >= envSize)
throw Error("environment full; cannot add more variables");
2021-12-20 19:32:21 +00:00
if (auto oldVar = staticEnv->find(name); oldVar != staticEnv->vars.end())
staticEnv->vars.erase(oldVar);
2021-11-25 15:53:59 +00:00
staticEnv->vars.emplace_back(name, displ);
staticEnv->sort();
2021-12-27 21:48:34 +00:00
env->values[displ++] = &v;
varNames.emplace(state->symbols[name]);
}
Expr * NixRepl::parseString(std::string s)
{
avoid copies of parser input data when given a string yacc will copy the entire input to a newly allocated location so that it can add a second terminating NUL byte. since the parser is a very internal thing to EvalState we can ensure that having two terminating NUL bytes is always possible without copying, and have the parser itself merely check that the expected NULs are present. # before Benchmark 1: nix search --offline nixpkgs hello Time (mean ± σ): 572.4 ms ± 2.3 ms [User: 563.4 ms, System: 8.6 ms] Range (min … max): 566.9 ms … 579.1 ms 50 runs Benchmark 2: nix eval -f ../nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix Time (mean ± σ): 381.7 ms ± 1.0 ms [User: 348.3 ms, System: 33.1 ms] Range (min … max): 380.2 ms … 387.7 ms 50 runs Benchmark 3: nix eval --raw --impure --expr 'with import <nixpkgs/nixos> {}; system' Time (mean ± σ): 2.936 s ± 0.005 s [User: 2.715 s, System: 0.221 s] Range (min … max): 2.923 s … 2.946 s 50 runs # after Benchmark 1: nix search --offline nixpkgs hello Time (mean ± σ): 571.7 ms ± 2.4 ms [User: 563.3 ms, System: 8.0 ms] Range (min … max): 566.7 ms … 579.7 ms 50 runs Benchmark 2: nix eval -f ../nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix Time (mean ± σ): 376.6 ms ± 1.0 ms [User: 345.8 ms, System: 30.5 ms] Range (min … max): 374.5 ms … 379.1 ms 50 runs Benchmark 3: nix eval --raw --impure --expr 'with import <nixpkgs/nixos> {}; system' Time (mean ± σ): 2.922 s ± 0.006 s [User: 2.707 s, System: 0.215 s] Range (min … max): 2.906 s … 2.934 s 50 runs
2021-12-21 12:56:57 +00:00
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);
2022-02-03 23:31:33 +00:00
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();
2022-02-03 23:31:33 +00:00
state->forceValue(v, [&]() { return v.determinePos(noPos); });
switch (v.type()) {
case nInt:
2020-06-24 19:10:41 +00:00
str << ANSI_CYAN << v.integer << ANSI_NORMAL;
break;
case nBool:
2020-06-24 19:10:41 +00:00
str << ANSI_CYAN << (v.boolean ? "true" : "false") << ANSI_NORMAL;
break;
case nString:
2021-09-14 08:38:10 +00:00
str << ANSI_WARNING;
2020-06-24 19:10:41 +00:00
printStringValue(str, v.string.s);
str << ANSI_NORMAL;
break;
case nPath:
2020-06-24 19:10:41 +00:00
str << ANSI_GREEN << v.path << ANSI_NORMAL; // !!! escaping?
break;
case nNull:
2020-06-24 19:10:41 +00:00
str << ANSI_CYAN "null" ANSI_NORMAL;
break;
case nAttrs: {
2020-06-24 19:10:41 +00:00
seen.insert(&v);
2020-06-24 19:10:41 +00:00
bool isDrv = state->isDerivation(v);
2020-06-24 19:10:41 +00:00
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));
else
str << "???";
str << "»";
2020-06-24 19:10:41 +00:00
}
2020-06-24 19:10:41 +00:00
else if (maxDepth > 0) {
str << "{ ";
typedef std::map<std::string, Value *> Sorted;
2020-06-24 19:10:41 +00:00
Sorted sorted;
for (auto & i : *v.attrs)
sorted.emplace(state->symbols[i.name], i.value);
2020-06-24 19:10:41 +00:00
for (auto & i : sorted) {
if (isVarName(i.first))
str << i.first;
else
printStringValue(str, i.first.c_str());
str << " = ";
if (seen.count(i.second))
2020-06-24 19:10:41 +00:00
str << "«repeated»";
else
try {
printValue(str, *i.second, maxDepth - 1, seen);
} catch (AssertionError & e) {
str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
}
str << "; ";
}
2020-06-24 19:10:41 +00:00
str << "}";
} else
str << "{ ... }";
2020-06-24 19:10:41 +00:00
break;
}
case nList:
2020-06-24 19:10:41 +00:00
seen.insert(&v);
str << "[ ";
if (maxDepth > 0)
for (auto elem : v.listItems()) {
if (seen.count(elem))
2020-06-24 19:10:41 +00:00
str << "«repeated»";
else
try {
printValue(str, *elem, maxDepth - 1, seen);
2020-06-24 19:10:41 +00:00
} 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();
}
2020-06-24 19:10:41 +00:00
break;
case nFloat:
2020-06-24 19:10:41 +00:00
str << v.fpoint;
break;
2020-06-19 19:44:08 +00:00
2020-06-24 19:10:41 +00:00
default:
str << ANSI_RED "«unknown»" ANSI_NORMAL;
break;
}
return str;
}
void runRepl(
ref<EvalState> evalState,
2021-12-28 00:35:27 +00:00
const Expr &expr,
2022-05-06 02:26:10 +00:00
const ValMap & extraEnv)
{
auto repl = std::make_unique<NixRepl>(evalState);
repl->initEnv();
// add 'extra' vars.
for (auto & [name, value] : extraEnv)
2021-12-27 21:48:34 +00:00
repl->addVarToScope(repl->state->symbols.create(name), *value);
repl->mainLoop({});
}
struct CmdRepl : StoreCommand, MixEvalArgs
{
std::vector<std::string> files;
2017-04-25 16:48:40 +00:00
CmdRepl()
{
2020-05-11 13:46:18 +00:00
expectArgs({
.label = "files",
.handler = {&files},
.completer = completePath
});
2017-04-25 16:48:40 +00:00
}
std::string description() override
{
return "start an interactive environment for evaluating Nix expressions";
}
2020-12-09 12:07:01 +00:00
std::string doc() override
{
2020-12-09 12:07:01 +00:00
return
#include "repl.md"
;
}
2017-04-25 16:48:40 +00:00
void run(ref<Store> store) override
{
2019-09-02 21:04:27 +00:00
evalSettings.pureEval = false;
auto evalState = make_ref<EvalState>(searchPath, store);
auto repl = std::make_unique<NixRepl>(evalState);
repl->autoArgs = getAutoArgs(*repl->state);
repl->initEnv();
repl->mainLoop(files);
2017-04-25 16:48:40 +00:00
}
};
static auto rCmdRepl = registerCommand<CmdRepl>("repl");
}