From 3bee459df2d6a803fb1eb657850dddb983c31eaa Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Fri, 2 Feb 2024 18:53:49 -0800 Subject: [PATCH] `:quit` in the debugger should quit the whole program --- src/libcmd/repl.cc | 63 ++++++++++++++++++++++++--------- src/libcmd/repl.hh | 4 +-- src/libexpr/eval.cc | 12 +++++-- src/libexpr/eval.hh | 5 ++- src/libexpr/primops.cc | 11 +----- src/libexpr/repl-exit-status.hh | 20 +++++++++++ src/libmain/shared.cc | 2 -- src/libmain/shared.hh | 10 +----- src/libutil/exit.cc | 7 ++++ src/libutil/exit.hh | 19 ++++++++++ 10 files changed, 109 insertions(+), 44 deletions(-) create mode 100644 src/libexpr/repl-exit-status.hh create mode 100644 src/libutil/exit.cc create mode 100644 src/libutil/exit.hh diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index d7d8f9819d85..cdeef4a03f99 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -52,6 +52,27 @@ extern "C" { namespace nix { +/** + * Returned by `NixRepl::processLine`. + */ +enum class ProcessLineResult { + /** + * The user exited with `:quit`. The REPL should exit. The surrounding + * program or evaluation (e.g., if the REPL was acting as the debugger) + * should also exit. + */ + QuitAll, + /** + * The user exited with `:continue`. The REPL should exit, but the program + * should continue running. + */ + QuitOnce, + /** + * The user did not exit. The REPL should request another line of input. + */ + Continue, +}; + struct NixRepl : AbstractNixRepl #if HAVE_BOEHMGC @@ -75,13 +96,13 @@ struct NixRepl std::function getValues); virtual ~NixRepl(); - void mainLoop() override; + ReplExitStatus mainLoop() override; void initEnv() override; StringSet completePrefix(const std::string & prefix); bool getLine(std::string & input, const std::string & prompt); StorePath getDerivationPath(Value & v); - bool processLine(std::string line); + ProcessLineResult processLine(std::string line); void loadFile(const Path & path); void loadFlake(const std::string & flakeRef); @@ -243,7 +264,7 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi return out; } -void NixRepl::mainLoop() +ReplExitStatus NixRepl::mainLoop() { std::string error = ANSI_RED "error:" ANSI_NORMAL " "; notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n"); @@ -277,15 +298,25 @@ void NixRepl::mainLoop() // When continuing input from previous lines, don't print a prompt, just align to the same // number of chars as the prompt. if (!getLine(input, input.empty() ? "nix-repl> " : " ")) { - // ctrl-D should exit the debugger. + // Ctrl-D should exit the debugger. state->debugStop = false; - state->debugQuit = true; logger->cout(""); - break; + // TODO: Should Ctrl-D exit just the current debugger session or + // the entire program? + return ReplExitStatus::QuitAll; } logger->resume(); try { - if (!removeWhitespace(input).empty() && !processLine(input)) return; + switch (processLine(input)) { + case ProcessLineResult::QuitAll: + return ReplExitStatus::QuitAll; + case ProcessLineResult::QuitOnce: + return ReplExitStatus::Continue; + case ProcessLineResult::Continue: + break; + default: + abort(); + } } 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 @@ -475,10 +506,11 @@ void NixRepl::loadDebugTraceEnv(DebugTrace & dt) } } -bool NixRepl::processLine(std::string line) +ProcessLineResult NixRepl::processLine(std::string line) { line = trim(line); - if (line == "") return true; + if (line.empty()) + return ProcessLineResult::Continue; _isInterrupted = false; @@ -573,13 +605,13 @@ bool NixRepl::processLine(std::string line) else if (state->debugRepl && (command == ":s" || command == ":step")) { // set flag to stop at next DebugTrace; exit repl. state->debugStop = true; - return false; + return ProcessLineResult::QuitOnce; } else if (state->debugRepl && (command == ":c" || command == ":continue")) { // set flag to run to next breakpoint or end of program; exit repl. state->debugStop = false; - return false; + return ProcessLineResult::QuitOnce; } else if (command == ":a" || command == ":add") { @@ -722,8 +754,7 @@ bool NixRepl::processLine(std::string line) else if (command == ":q" || command == ":quit") { state->debugStop = false; - state->debugQuit = true; - return false; + return ProcessLineResult::QuitAll; } else if (command == ":doc") { @@ -784,7 +815,7 @@ bool NixRepl::processLine(std::string line) } } - return true; + return ProcessLineResult::Continue; } void NixRepl::loadFile(const Path & path) @@ -915,7 +946,7 @@ std::unique_ptr AbstractNixRepl::create( } -void AbstractNixRepl::runSimple( +ReplExitStatus AbstractNixRepl::runSimple( ref evalState, const ValMap & extraEnv) { @@ -937,7 +968,7 @@ void AbstractNixRepl::runSimple( for (auto & [name, value] : extraEnv) repl->addVarToScope(repl->state->symbols.create(name), *value); - repl->mainLoop(); + return repl->mainLoop(); } } diff --git a/src/libcmd/repl.hh b/src/libcmd/repl.hh index 6d88883fefaf..21aa8bfc7cfd 100644 --- a/src/libcmd/repl.hh +++ b/src/libcmd/repl.hh @@ -28,13 +28,13 @@ struct AbstractNixRepl const SearchPath & searchPath, nix::ref store, ref state, std::function getValues); - static void runSimple( + static ReplExitStatus runSimple( ref evalState, const ValMap & extraEnv); virtual void initEnv() = 0; - virtual void mainLoop() = 0; + virtual ReplExitStatus mainLoop() = 0; }; } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 91fd3ddf8639..fda24ac4cd43 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -3,6 +3,7 @@ #include "hash.hh" #include "primops.hh" #include "print-options.hh" +#include "shared.hh" #include "types.hh" #include "util.hh" #include "store-api.hh" @@ -456,7 +457,6 @@ EvalState::EvalState( , buildStore(buildStore ? buildStore : store) , debugRepl(nullptr) , debugStop(false) - , debugQuit(false) , trylevel(0) , regexCache(makeRegexCache()) #if HAVE_BOEHMGC @@ -832,7 +832,15 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & auto se = getStaticEnv(expr); if (se) { auto vm = mapStaticEnvBindings(symbols, *se.get(), env); - (debugRepl)(ref(shared_from_this()), *vm); + auto exitStatus = (debugRepl)(ref(shared_from_this()), *vm); + switch (exitStatus) { + case ReplExitStatus::QuitAll: + throw Exit(0); + case ReplExitStatus::Continue: + break; + default: + abort(); + } } } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 2368187b14bb..ad4140a45733 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -10,6 +10,7 @@ #include "experimental-features.hh" #include "input-accessor.hh" #include "search-path.hh" +#include "repl-exit-status.hh" #include #include @@ -257,9 +258,8 @@ public: /** * Debugger */ - void (* debugRepl)(ref es, const ValMap & extraEnv); + ReplExitStatus (* debugRepl)(ref es, const ValMap & extraEnv); bool debugStop; - bool debugQuit; int trylevel; std::list debugTraces; std::map> exprEnvs; @@ -819,7 +819,6 @@ struct DebugTraceStacker { DebugTraceStacker(EvalState & evalState, DebugTrace t); ~DebugTraceStacker() { - // assert(evalState.debugTraces.front() == trace); evalState.debugTraces.pop_front(); } EvalState & evalState; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 1197b6e13d51..515d1aebf160 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -763,15 +763,6 @@ static RegisterPrimOp primop_break({ auto & dt = state.debugTraces.front(); state.runDebugRepl(&error, dt.env, dt.expr); - - if (state.debugQuit) { - // If the user elects to quit the repl, throw an exception. - throw Error(ErrorInfo{ - .level = lvlInfo, - .msg = hintfmt("quit the debugger"), - .errPos = nullptr, - }); - } } // Return the value we were passed. @@ -882,7 +873,7 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va /* increment state.trylevel, and decrement it when this function returns. */ MaintainCount trylevel(state.trylevel); - void (* savedDebugRepl)(ref es, const ValMap & extraEnv) = nullptr; + ReplExitStatus (* savedDebugRepl)(ref es, const ValMap & extraEnv) = nullptr; if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry) { /* to prevent starting the repl from exceptions withing a tryEval, null it. */ diff --git a/src/libexpr/repl-exit-status.hh b/src/libexpr/repl-exit-status.hh new file mode 100644 index 000000000000..08299ff61ae4 --- /dev/null +++ b/src/libexpr/repl-exit-status.hh @@ -0,0 +1,20 @@ +#pragma once + +namespace nix { + +/** + * Exit status returned from the REPL. + */ +enum class ReplExitStatus { + /** + * The user exited with `:quit`. The program (e.g., if the REPL was acting + * as the debugger) should exit. + */ + QuitAll, + /** + * The user exited with `:continue`. The program should continue running. + */ + Continue, +}; + +} diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 862ef355b958..d14ce8d8d865 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -408,6 +408,4 @@ PrintFreed::~PrintFreed() showBytes(results.bytesFreed)); } -Exit::~Exit() { } - } diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh index c68f6cd83e9c..99c3dffab300 100644 --- a/src/libmain/shared.hh +++ b/src/libmain/shared.hh @@ -7,6 +7,7 @@ #include "common-args.hh" #include "path.hh" #include "derived-path.hh" +#include "exit.hh" #include @@ -15,15 +16,6 @@ namespace nix { -class Exit : public std::exception -{ -public: - int status; - Exit() : status(0) { } - Exit(int status) : status(status) { } - virtual ~Exit(); -}; - int handleExceptions(const std::string & programName, std::function fun); /** diff --git a/src/libutil/exit.cc b/src/libutil/exit.cc new file mode 100644 index 000000000000..73cd8b04ee8a --- /dev/null +++ b/src/libutil/exit.cc @@ -0,0 +1,7 @@ +#include "exit.hh" + +namespace nix { + +Exit::~Exit() {} + +} diff --git a/src/libutil/exit.hh b/src/libutil/exit.hh new file mode 100644 index 000000000000..c55434374920 --- /dev/null +++ b/src/libutil/exit.hh @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace nix { + +/** + * Exit the program with a given exit code. + */ +class Exit : public std::exception +{ +public: + int status; + Exit() : status(0) { } + Exit(int status) : status(status) { } + virtual ~Exit(); +}; + +}