aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libcmd/repl.cc158
-rw-r--r--src/libexpr/eval.cc126
-rw-r--r--src/libexpr/eval.hh4
-rw-r--r--src/libexpr/print-ambiguous.cc99
-rw-r--r--src/libexpr/print-ambiguous.hh24
-rw-r--r--src/libexpr/print-options.hh52
-rw-r--r--src/libexpr/print.cc412
-rw-r--r--src/libexpr/print.hh6
-rw-r--r--src/libexpr/value.hh17
-rw-r--r--src/libutil/english.cc18
-rw-r--r--src/libutil/english.hh18
-rw-r--r--src/nix-env/user-env.cc4
-rw-r--r--src/nix-instantiate/nix-instantiate.cc5
13 files changed, 688 insertions, 255 deletions
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 83d719bfe..80b252c31 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -90,9 +90,17 @@ struct NixRepl
void evalString(std::string s, Value & v);
void loadDebugTraceEnv(DebugTrace & dt);
- typedef std::set<Value *> ValuesSeen;
- std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
- std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
+ void printValue(std::ostream & str,
+ Value & v,
+ unsigned int maxDepth = std::numeric_limits<unsigned int>::max())
+ {
+ ::nix::printValue(*state, str, v, PrintOptions {
+ .ansiColors = true,
+ .force = true,
+ .derivationPaths = true,
+ .maxDepth = maxDepth
+ });
+ }
};
std::string removeWhitespace(std::string s)
@@ -713,7 +721,8 @@ bool NixRepl::processLine(std::string line)
else if (command == ":p" || command == ":print") {
Value v;
evalString(arg, v);
- printValue(std::cout, v, 1000000000) << std::endl;
+ printValue(std::cout, v);
+ std::cout << std::endl;
}
else if (command == ":q" || command == ":quit") {
@@ -775,7 +784,8 @@ bool NixRepl::processLine(std::string line)
} else {
Value v;
evalString(line, v);
- printValue(std::cout, v, 1) << std::endl;
+ printValue(std::cout, v, 1);
+ std::cout << std::endl;
}
}
@@ -897,144 +907,6 @@ void NixRepl::evalString(std::string s, Value & v)
}
-std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth)
-{
- ValuesSeen seen;
- return printValue(str, v, maxDepth, seen);
-}
-
-
-
-
-// FIXME: lot of cut&paste from Nix's eval.cc.
-std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen)
-{
- str.flush();
- checkInterrupt();
-
- state->forceValue(v, v.determinePos(noPos));
-
- switch (v.type()) {
-
- case nInt:
- str << ANSI_CYAN << v.integer << ANSI_NORMAL;
- break;
-
- case nBool:
- str << ANSI_CYAN;
- printLiteralBool(str, v.boolean);
- str << ANSI_NORMAL;
- break;
-
- case nString:
- str << ANSI_WARNING;
- printLiteralString(str, v.string.s);
- str << ANSI_NORMAL;
- break;
-
- case nPath:
- str << ANSI_GREEN << v.path().to_string() << ANSI_NORMAL; // !!! escaping?
- break;
-
- case nNull:
- str << ANSI_CYAN "null" ANSI_NORMAL;
- break;
-
- case nAttrs: {
- seen.insert(&v);
-
- bool isDrv = state->isDerivation(v);
-
- if (isDrv) {
- str << "«derivation ";
- Bindings::iterator i = v.attrs->find(state->sDrvPath);
- NixStringContext context;
- if (i != v.attrs->end())
- str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
- else
- str << "???";
- str << "»";
- }
-
- else if (maxDepth > 0) {
- str << "{ ";
-
- typedef std::map<std::string, Value *> Sorted;
- Sorted sorted;
- for (auto & i : *v.attrs)
- sorted.emplace(state->symbols[i.name], i.value);
-
- for (auto & i : sorted) {
- printAttributeName(str, i.first);
- str << " = ";
- if (seen.count(i.second))
- str << "«repeated»";
- else
- try {
- printValue(str, *i.second, maxDepth - 1, seen);
- } catch (AssertionError & e) {
- str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
- }
- str << "; ";
- }
-
- str << "}";
- } else
- str << "{ ... }";
-
- break;
- }
-
- case nList:
- seen.insert(&v);
-
- str << "[ ";
- if (maxDepth > 0)
- for (auto elem : v.listItems()) {
- if (seen.count(elem))
- str << "«repeated»";
- else
- try {
- printValue(str, *elem, maxDepth - 1, seen);
- } catch (AssertionError & e) {
- str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
- }
- str << " ";
- }
- else
- str << "... ";
- str << "]";
- break;
-
- case nFunction:
- if (v.isLambda()) {
- std::ostringstream s;
- s << state->positions[v.lambda.fun->pos];
- str << ANSI_BLUE "«lambda @ " << filterANSIEscapes(s.str()) << "»" ANSI_NORMAL;
- } else if (v.isPrimOp()) {
- str << ANSI_MAGENTA "«primop»" ANSI_NORMAL;
- } else if (v.isPrimOpApp()) {
- str << ANSI_BLUE "«primop-app»" ANSI_NORMAL;
- } else {
- abort();
- }
- break;
-
- case nFloat:
- str << v.fpoint;
- break;
-
- case nThunk:
- case nExternal:
- default:
- str << ANSI_RED "«unknown»" ANSI_NORMAL;
- break;
- }
-
- return str;
-}
-
-
std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<AnnotatedValues()> getValues)
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 8e3077a67..729b17887 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -100,117 +100,23 @@ RootValue allocRootValue(Value * v)
#endif
}
-void Value::print(const SymbolTable &symbols, std::ostream &str,
- std::set<const void *> *seen, int depth) const
-
-{
- checkInterrupt();
-
- if (depth <= 0) {
- str << "«too deep»";
- return;
- }
- switch (internalType) {
- case tInt:
- str << integer;
- break;
- case tBool:
- printLiteralBool(str, boolean);
- break;
- case tString:
- printLiteralString(str, string.s);
- break;
- case tPath:
- str << path().to_string(); // !!! escaping?
- break;
- case tNull:
- str << "null";
- break;
- case tAttrs: {
- if (seen && !attrs->empty() && !seen->insert(attrs).second)
- str << "«repeated»";
- else {
- str << "{ ";
- for (auto & i : attrs->lexicographicOrder(symbols)) {
- str << symbols[i->name] << " = ";
- i->value->print(symbols, str, seen, depth - 1);
- str << "; ";
- }
- str << "}";
- }
- break;
- }
- case tList1:
- case tList2:
- case tListN:
- if (seen && listSize() && !seen->insert(listElems()).second)
- str << "«repeated»";
- else {
- str << "[ ";
- for (auto v2 : listItems()) {
- if (v2)
- v2->print(symbols, str, seen, depth - 1);
- else
- str << "(nullptr)";
- str << " ";
- }
- str << "]";
- }
- break;
- case tThunk:
- case tApp:
- if (!isBlackhole()) {
- str << "<CODE>";
- } else {
- // Although we know for sure that it's going to be an infinite recursion
- // when this value is accessed _in the current context_, it's likely
- // that the user will misinterpret a simpler «infinite recursion» output
- // as a definitive statement about the value, while in fact it may be
- // a valid value after `builtins.trace` and perhaps some other steps
- // have completed.
- str << "«potential infinite recursion»";
- }
- break;
- case tLambda:
- str << "<LAMBDA>";
- break;
- case tPrimOp:
- str << "<PRIMOP>";
- break;
- case tPrimOpApp:
- str << "<PRIMOP-APP>";
- break;
- case tExternal:
- str << *external;
- break;
- case tFloat:
- str << fpoint;
- break;
- default:
- printError("Nix evaluator internal error: Value::print(): invalid value type %1%", internalType);
- abort();
- }
-}
-
-void Value::print(const SymbolTable &symbols, std::ostream &str,
- bool showRepeated, int depth) const {
- std::set<const void *> seen;
- print(symbols, str, showRepeated ? nullptr : &seen, depth);
-}
-
// Pretty print types for assertion errors
std::ostream & operator << (std::ostream & os, const ValueType t) {
os << showType(t);
return os;
}
-std::string printValue(const EvalState & state, const Value & v)
+std::string printValue(EvalState & state, Value & v)
{
std::ostringstream out;
- v.print(state.symbols, out);
+ v.print(state, out);
return out.str();
}
+void Value::print(EvalState & state, std::ostream & str, PrintOptions options)
+{
+ printValue(state, str, *this, options);
+}
const Value * getPrimOp(const Value &v) {
const Value * primOp = &v;
@@ -717,6 +623,26 @@ void PrimOp::check()
}
+std::ostream & operator<<(std::ostream & output, PrimOp & primOp)
+{
+ output << "primop " << primOp.name;
+ return output;
+}
+
+
+PrimOp * Value::primOpAppPrimOp() const
+{
+ Value * left = primOpApp.left;
+ while (left && !left->isPrimOp()) {
+ left = left->primOpApp.left;
+ }
+
+ if (!left)
+ return nullptr;
+ return left->primOp;
+}
+
+
void Value::mkPrimOp(PrimOp * p)
{
p->check();
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index d070fdab8..8355e443e 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -83,6 +83,8 @@ struct PrimOp
void check();
};
+std::ostream & operator<<(std::ostream & output, PrimOp & primOp);
+
/**
* Info about a constant
*/
@@ -126,7 +128,7 @@ std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const Stati
void copyContext(const Value & v, NixStringContext & context);
-std::string printValue(const EvalState & state, const Value & v);
+std::string printValue(EvalState & state, Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t);
diff --git a/src/libexpr/print-ambiguous.cc b/src/libexpr/print-ambiguous.cc
new file mode 100644
index 000000000..fb59bbf4b
--- /dev/null
+++ b/src/libexpr/print-ambiguous.cc
@@ -0,0 +1,99 @@
+#include "print-ambiguous.hh"
+#include "print.hh"
+
+namespace nix {
+
+// See: https://github.com/NixOS/nix/issues/9730
+void printAmbiguous(
+ Value &v,
+ const SymbolTable &symbols,
+ std::ostream &str,
+ std::set<const void *> *seen,
+ int depth)
+{
+ checkInterrupt();
+
+ if (depth <= 0) {
+ str << "«too deep»";
+ return;
+ }
+ switch (v.type()) {
+ case nInt:
+ str << v.integer;
+ break;
+ case nBool:
+ printLiteralBool(str, v.boolean);
+ break;
+ case nString:
+ printLiteralString(str, v.string.s);
+ break;
+ case nPath:
+ str << v.path().to_string(); // !!! escaping?
+ break;
+ case nNull:
+ str << "null";
+ break;
+ case nAttrs: {
+ if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second)
+ str << "«repeated»";
+ else {
+ str << "{ ";
+ for (auto & i : v.attrs->lexicographicOrder(symbols)) {
+ str << symbols[i->name] << " = ";
+ printAmbiguous(*i->value, symbols, str, seen, depth - 1);
+ str << "; ";
+ }
+ str << "}";
+ }
+ break;
+ }
+ case nList:
+ if (seen && v.listSize() && !seen->insert(v.listElems()).second)
+ str << "«repeated»";
+ else {
+ str << "[ ";
+ for (auto v2 : v.listItems()) {
+ if (v2)
+ printAmbiguous(*v2, symbols, str, seen, depth - 1);
+ else
+ str << "(nullptr)";
+ str << " ";
+ }
+ str << "]";
+ }
+ break;
+ case nThunk:
+ if (!v.isBlackhole()) {
+ str << "<CODE>";
+ } else {
+ // Although we know for sure that it's going to be an infinite recursion
+ // when this value is accessed _in the current context_, it's likely
+ // that the user will misinterpret a simpler «infinite recursion» output
+ // as a definitive statement about the value, while in fact it may be
+ // a valid value after `builtins.trace` and perhaps some other steps
+ // have completed.
+ str << "«potential infinite recursion»";
+ }
+ break;
+ case nFunction:
+ if (v.isLambda()) {
+ str << "<LAMBDA>";
+ } else if (v.isPrimOp()) {
+ str << "<PRIMOP>";
+ } else if (v.isPrimOpApp()) {
+ str << "<PRIMOP-APP>";
+ }
+ break;
+ case nExternal:
+ str << *v.external;
+ break;
+ case nFloat:
+ str << v.fpoint;
+ break;
+ default:
+ printError("Nix evaluator internal error: printAmbiguous: invalid value type");
+ abort();
+ }
+}
+
+}
diff --git a/src/libexpr/print-ambiguous.hh b/src/libexpr/print-ambiguous.hh
new file mode 100644
index 000000000..50c260a9b
--- /dev/null
+++ b/src/libexpr/print-ambiguous.hh
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "value.hh"
+
+namespace nix {
+
+/**
+ * Print a value in the deprecated format used by `nix-instantiate --eval` and
+ * `nix-env` (for manifests).
+ *
+ * This output can't be changed because it's part of the `nix-instantiate` API,
+ * but it produces ambiguous output; unevaluated thunks and lambdas (and a few
+ * other types) are printed as Nix path syntax like `<CODE>`.
+ *
+ * See: https://github.com/NixOS/nix/issues/9730
+ */
+void printAmbiguous(
+ Value &v,
+ const SymbolTable &symbols,
+ std::ostream &str,
+ std::set<const void *> *seen,
+ int depth);
+
+}
diff --git a/src/libexpr/print-options.hh b/src/libexpr/print-options.hh
new file mode 100644
index 000000000..11ff9ae87
--- /dev/null
+++ b/src/libexpr/print-options.hh
@@ -0,0 +1,52 @@
+#pragma once
+/**
+ * @file
+ * @brief Options for printing Nix values.
+ */
+
+#include <limits>
+
+namespace nix {
+
+/**
+ * Options for printing Nix values.
+ */
+struct PrintOptions
+{
+ /**
+ * If true, output ANSI color sequences.
+ */
+ bool ansiColors = false;
+ /**
+ * If true, force values.
+ */
+ bool force = false;
+ /**
+ * If true and `force` is set, print derivations as
+ * `«derivation /nix/store/...»` instead of as attribute sets.
+ */
+ bool derivationPaths = false;
+ /**
+ * If true, track which values have been printed and skip them on
+ * subsequent encounters. Useful for self-referential values.
+ */
+ bool trackRepeated = true;
+ /**
+ * Maximum depth to evaluate to.
+ */
+ size_t maxDepth = std::numeric_limits<size_t>::max();
+ /**
+ * Maximum number of attributes in an attribute set to print.
+ */
+ size_t maxAttrs = std::numeric_limits<size_t>::max();
+ /**
+ * Maximum number of list items to print.
+ */
+ size_t maxListItems = std::numeric_limits<size_t>::max();
+ /**
+ * Maximum string length to print.
+ */
+ size_t maxStringLength = std::numeric_limits<size_t>::max();
+};
+
+}
diff --git a/src/libexpr/print.cc b/src/libexpr/print.cc
index 53ba70bdd..0e8f80888 100644
--- a/src/libexpr/print.cc
+++ b/src/libexpr/print.cc
@@ -1,25 +1,65 @@
-#include "print.hh"
+#include <limits>
#include <unordered_set>
+#include "print.hh"
+#include "ansicolor.hh"
+#include "store-api.hh"
+#include "english.hh"
+
namespace nix {
+void printElided(
+ std::ostream & output,
+ unsigned int value,
+ const std::string_view single,
+ const std::string_view plural,
+ bool ansiColors)
+{
+ if (ansiColors)
+ output << ANSI_FAINT;
+ output << " «";
+ pluralize(output, value, single, plural);
+ output << " elided»";
+ if (ansiColors)
+ output << ANSI_NORMAL;
+}
+
+
std::ostream &
-printLiteralString(std::ostream & str, const std::string_view string)
+printLiteralString(std::ostream & str, const std::string_view string, size_t maxLength, bool ansiColors)
{
+ size_t charsPrinted = 0;
+ if (ansiColors)
+ str << ANSI_MAGENTA;
str << "\"";
for (auto i = string.begin(); i != string.end(); ++i) {
+ if (charsPrinted >= maxLength) {
+ str << "\"";
+ printElided(str, string.length() - charsPrinted, "byte", "bytes", ansiColors);
+ return str;
+ }
+
if (*i == '\"' || *i == '\\') str << "\\" << *i;
else if (*i == '\n') str << "\\n";
else if (*i == '\r') str << "\\r";
else if (*i == '\t') str << "\\t";
else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
else str << *i;
+ charsPrinted++;
}
str << "\"";
+ if (ansiColors)
+ str << ANSI_NORMAL;
return str;
}
std::ostream &
+printLiteralString(std::ostream & str, const std::string_view string)
+{
+ return printLiteralString(str, string, std::numeric_limits<size_t>::max(), false);
+}
+
+std::ostream &
printLiteralBool(std::ostream & str, bool boolean)
{
str << (boolean ? "true" : "false");
@@ -90,5 +130,373 @@ printAttributeName(std::ostream & str, std::string_view name) {
return str;
}
+bool isImportantAttrName(const std::string& attrName)
+{
+ return attrName == "type" || attrName == "_type";
+}
+
+typedef std::pair<std::string, Value *> AttrPair;
+
+struct ImportantFirstAttrNameCmp
+{
+
+ bool operator()(const AttrPair& lhs, const AttrPair& rhs) const
+ {
+ auto lhsIsImportant = isImportantAttrName(lhs.first);
+ auto rhsIsImportant = isImportantAttrName(rhs.first);
+ return std::forward_as_tuple(!lhsIsImportant, lhs.first)
+ < std::forward_as_tuple(!rhsIsImportant, rhs.first);
+ }
+};
+
+typedef std::set<Value *> ValuesSeen;
+
+class Printer
+{
+private:
+ std::ostream & output;
+ EvalState & state;
+ PrintOptions options;
+ std::optional<ValuesSeen> seen;
+
+ void printRepeated()
+ {
+ if (options.ansiColors)
+ output << ANSI_MAGENTA;
+ output << "«repeated»";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printNullptr()
+ {
+ if (options.ansiColors)
+ output << ANSI_MAGENTA;
+ output << "«nullptr»";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printElided(unsigned int value, const std::string_view single, const std::string_view plural)
+ {
+ ::nix::printElided(output, value, single, plural, options.ansiColors);
+ }
+
+ void printInt(Value & v)
+ {
+ if (options.ansiColors)
+ output << ANSI_CYAN;
+ output << v.integer;
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printFloat(Value & v)
+ {
+ if (options.ansiColors)
+ output << ANSI_CYAN;
+ output << v.fpoint;
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printBool(Value & v)
+ {
+ if (options.ansiColors)
+ output << ANSI_CYAN;
+ printLiteralBool(output, v.boolean);
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printString(Value & v)
+ {
+ printLiteralString(output, v.string.s, options.maxStringLength, options.ansiColors);
+ }
+
+ void printPath(Value & v)
+ {
+ if (options.ansiColors)
+ output << ANSI_GREEN;
+ output << v.path().to_string(); // !!! escaping?
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printNull()
+ {
+ if (options.ansiColors)
+ output << ANSI_CYAN;
+ output << "null";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printDerivation(Value & v)
+ {
+ try {
+ Bindings::iterator i = v.attrs->find(state.sDrvPath);
+ NixStringContext context;
+ std::string storePath;
+ if (i != v.attrs->end())
+ storePath = state.store->printStorePath(state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
+
+ if (options.ansiColors)
+ output << ANSI_GREEN;
+ output << "«derivation";
+ if (!storePath.empty()) {
+ output << " " << storePath;
+ }
+ output << "»";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ } catch (BaseError & e) {
+ printError_(e);
+ }
+ }
+
+ void printAttrs(Value & v, size_t depth)
+ {
+ if (seen && !seen->insert(&v).second) {
+ printRepeated();
+ return;
+ }
+
+ if (options.force && options.derivationPaths && state.isDerivation(v)) {
+ printDerivation(v);
+ } else if (depth < options.maxDepth) {
+ output << "{ ";
+
+ std::vector<std::pair<std::string, Value *>> sorted;
+ for (auto & i : *v.attrs)
+ sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
+
+ if (options.maxAttrs == std::numeric_limits<size_t>::max())
+ std::sort(sorted.begin(), sorted.end());
+ else
+ std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
+
+ size_t attrsPrinted = 0;
+ for (auto & i : sorted) {
+ if (attrsPrinted >= options.maxAttrs) {
+ printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
+ break;
+ }
+
+ printAttributeName(output, i.first);
+ output << " = ";
+ print(*i.second, depth + 1);
+ output << "; ";
+ attrsPrinted++;
+ }
+
+ output << "}";
+ } else
+ output << "{ ... }";
+ }
+
+ void printList(Value & v, size_t depth)
+ {
+ if (seen && v.listSize() && !seen->insert(&v).second) {
+ printRepeated();
+ return;
+ }
+
+ output << "[ ";
+ if (depth < options.maxDepth) {
+ size_t listItemsPrinted = 0;
+ for (auto elem : v.listItems()) {
+ if (listItemsPrinted >= options.maxListItems) {
+ printElided(v.listSize() - listItemsPrinted, "item", "items");
+ break;
+ }
+
+ if (elem) {
+ print(*elem, depth + 1);
+ } else {
+ printNullptr();
+ }
+ output << " ";
+ listItemsPrinted++;
+ }
+ }
+ else
+ output << "... ";
+ output << "]";
+ }
+
+ void printFunction(Value & v)
+ {
+ if (options.ansiColors)
+ output << ANSI_BLUE;
+ output << "«";
+
+ if (v.isLambda()) {
+ output << "lambda";
+ if (v.lambda.fun) {
+ if (v.lambda.fun->name) {
+ output << " " << state.symbols[v.lambda.fun->name];
+ }
+
+ std::ostringstream s;
+ s << state.positions[v.lambda.fun->pos];
+ output << " @ " << filterANSIEscapes(s.str());
+ }
+ } else if (v.isPrimOp()) {
+ if (v.primOp)
+ output << *v.primOp;
+ else
+ output << "primop";
+ } else if (v.isPrimOpApp()) {
+ output << "partially applied ";
+ auto primOp = v.primOpAppPrimOp();
+ if (primOp)
+ output << *primOp;
+ else
+ output << "primop";
+ } else {
+ abort();
+ }
+
+ output << "»";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printThunk(Value & v)
+ {
+ if (v.isBlackhole()) {
+ // Although we know for sure that it's going to be an infinite recursion
+ // when this value is accessed _in the current context_, it's likely
+ // that the user will misinterpret a simpler «infinite recursion» output
+ // as a definitive statement about the value, while in fact it may be
+ // a valid value after `builtins.trace` and perhaps some other steps
+ // have completed.
+ if (options.ansiColors)
+ output << ANSI_RED;
+ output << "«potential infinite recursion»";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ } else if (v.isThunk() || v.isApp()) {
+ if (options.ansiColors)
+ output << ANSI_MAGENTA;
+ output << "«thunk»";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ } else {
+ abort();
+ }
+ }
+
+ void printExternal(Value & v)
+ {
+ v.external->print(output);
+ }
+
+ void printUnknown()
+ {
+ if (options.ansiColors)
+ output << ANSI_RED;
+ output << "«unknown»";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void printError_(BaseError & e)
+ {
+ if (options.ansiColors)
+ output << ANSI_RED;
+ output << "«" << e.msg() << "»";
+ if (options.ansiColors)
+ output << ANSI_NORMAL;
+ }
+
+ void print(Value & v, size_t depth)
+ {
+ output.flush();
+ checkInterrupt();
+
+ if (options.force) {
+ try {
+ state.forceValue(v, v.determinePos(noPos));
+ } catch (BaseError & e) {
+ printError_(e);
+ return;
+ }
+ }
+
+ switch (v.type()) {
+
+ case nInt:
+ printInt(v);
+ break;
+
+ case nFloat:
+ printFloat(v);
+ break;
+
+ case nBool:
+ printBool(v);
+ break;
+
+ case nString:
+ printString(v);
+ break;
+
+ case nPath:
+ printPath(v);
+ break;
+
+ case nNull:
+ printNull();
+ break;
+
+ case nAttrs:
+ printAttrs(v, depth);
+ break;
+
+ case nList:
+ printList(v, depth);
+ break;
+
+ case nFunction:
+ printFunction(v);
+ break;
+
+ case nThunk:
+ printThunk(v);
+ break;
+
+ case nExternal:
+ printExternal(v);
+ break;
+
+ default:
+ printUnknown();
+ break;
+ }
+ }
+
+public:
+ Printer(std::ostream & output, EvalState & state, PrintOptions options)
+ : output(output), state(state), options(options) { }
+
+ void print(Value & v)
+ {
+ if (options.trackRepeated) {
+ seen.emplace();
+ } else {
+ seen.reset();
+ }
+
+ ValuesSeen seen;
+ print(v, 0);
+ }
+};
+
+void printValue(EvalState & state, std::ostream & output, Value & v, PrintOptions options)
+{
+ Printer(output, state, options).print(v);
+}
}
diff --git a/src/libexpr/print.hh b/src/libexpr/print.hh
index abf830864..40207d777 100644
--- a/src/libexpr/print.hh
+++ b/src/libexpr/print.hh
@@ -9,6 +9,9 @@
#include <iostream>
+#include "eval.hh"
+#include "print-options.hh"
+
namespace nix {
/**
@@ -16,6 +19,7 @@ namespace nix {
*
* Quotes and fairly minimal escaping are added.
*
+ * @param o The output stream to print to
* @param s The logical string
*/
std::ostream & printLiteralString(std::ostream & o, std::string_view s);
@@ -53,4 +57,6 @@ bool isReservedKeyword(const std::string_view str);
*/
std::ostream & printIdentifier(std::ostream & o, std::string_view s);
+void printValue(EvalState & state, std::ostream & str, Value & v, PrintOptions options = PrintOptions {});
+
}
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index f90082e61..b7b9ed94a 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -8,6 +8,7 @@
#include "value/context.hh"
#include "input-accessor.hh"
#include "source-path.hh"
+#include "print-options.hh"
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
@@ -70,7 +71,7 @@ class StorePath;
class Store;
class EvalState;
class XMLWriter;
-
+class Printer;
typedef int64_t NixInt;
typedef double NixFloat;
@@ -82,6 +83,7 @@ typedef double NixFloat;
class ExternalValueBase
{
friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
+ friend class Printer;
protected:
/**
* Print out the value
@@ -139,11 +141,9 @@ private:
friend std::string showType(const Value & v);
- void print(const SymbolTable &symbols, std::ostream &str, std::set<const void *> *seen, int depth) const;
-
public:
- void print(const SymbolTable &symbols, std::ostream &str, bool showRepeated = false, int depth = INT_MAX) const;
+ void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {});
// Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's
@@ -351,10 +351,15 @@ public:
inline void mkPrimOpApp(Value * l, Value * r)
{
internalType = tPrimOpApp;
- app.left = l;
- app.right = r;
+ primOpApp.left = l;
+ primOpApp.right = r;
}
+ /**
+ * For a `tPrimOpApp` value, get the original `PrimOp` value.
+ */
+ PrimOp * primOpAppPrimOp() const;
+
inline void mkExternal(ExternalValueBase * e)
{
clearValue();
diff --git a/src/libutil/english.cc b/src/libutil/english.cc
new file mode 100644
index 000000000..8c93c9156
--- /dev/null
+++ b/src/libutil/english.cc
@@ -0,0 +1,18 @@
+#include "english.hh"
+
+namespace nix {
+
+std::ostream & pluralize(
+ std::ostream & output,
+ unsigned int count,
+ const std::string_view single,
+ const std::string_view plural)
+{
+ if (count == 1)
+ output << "1 " << single;
+ else
+ output << count << " " << plural;
+ return output;
+}
+
+}
diff --git a/src/libutil/english.hh b/src/libutil/english.hh
new file mode 100644
index 000000000..9c6c93571
--- /dev/null
+++ b/src/libutil/english.hh
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <iostream>
+
+namespace nix {
+
+/**
+ * Pluralize a given value.
+ *
+ * If `count == 1`, prints `1 {single}` to `output`, otherwise prints `{count} {plural}`.
+ */
+std::ostream & pluralize(
+ std::ostream & output,
+ unsigned int count,
+ const std::string_view single,
+ const std::string_view plural);
+
+}
diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc
index d2b917095..757938914 100644
--- a/src/nix-env/user-env.cc
+++ b/src/nix-env/user-env.cc
@@ -9,6 +9,8 @@
#include "eval.hh"
#include "eval-inline.hh"
#include "profiles.hh"
+#include "print-ambiguous.hh"
+#include <limits>
namespace nix {
@@ -106,7 +108,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
the store; we need it for future modifications of the
environment. */
std::ostringstream str;
- manifest.print(state.symbols, str, true);
+ printAmbiguous(manifest, state.symbols, str, nullptr, std::numeric_limits<int>::max());
auto manifestFile = state.store->addTextToStore("env-manifest.nix",
str.str(), references);
diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc
index e1686bdb5..3b99b73b4 100644
--- a/src/nix-instantiate/nix-instantiate.cc
+++ b/src/nix-instantiate/nix-instantiate.cc
@@ -1,4 +1,5 @@
#include "globals.hh"
+#include "print-ambiguous.hh"
#include "shared.hh"
#include "eval.hh"
#include "eval-inline.hh"
@@ -25,7 +26,6 @@ static int rootNr = 0;
enum OutputKind { okPlain, okXML, okJSON };
-
void processExpr(EvalState & state, const Strings & attrPaths,
bool parseOnly, bool strict, Bindings & autoArgs,
bool evalOnly, OutputKind output, bool location, Expr * e)
@@ -57,7 +57,8 @@ void processExpr(EvalState & state, const Strings & attrPaths,
std::cout << std::endl;
} else {
if (strict) state.forceValueDeep(vRes);
- vRes.print(state.symbols, std::cout);
+ std::set<const void *> seen;
+ printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits<int>::max());
std::cout << std::endl;
}
} else {