aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/src/command-ref/nix-instantiate.md80
-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
-rw-r--r--tests/functional/lang/eval-okay-print.err.exp2
-rw-r--r--tests/functional/lang/eval-okay-repeated-empty-attrs.exp1
-rw-r--r--tests/functional/lang/eval-okay-repeated-empty-attrs.nix2
-rw-r--r--tests/functional/lang/eval-okay-repeated-empty-list.exp1
-rw-r--r--tests/functional/lang/eval-okay-repeated-empty-list.nix1
-rw-r--r--tests/unit/libexpr/value/print.cc617
20 files changed, 1351 insertions, 296 deletions
diff --git a/doc/manual/src/command-ref/nix-instantiate.md b/doc/manual/src/command-ref/nix-instantiate.md
index e1b4a3e80..483150aa8 100644
--- a/doc/manual/src/command-ref/nix-instantiate.md
+++ b/doc/manual/src/command-ref/nix-instantiate.md
@@ -35,13 +35,50 @@ standard input.
- `--parse`\
Just parse the input files, and print their abstract syntax trees on
- standard output in ATerm format.
+ standard output as a Nix expression.
- `--eval`\
Just parse and evaluate the input files, and print the resulting
values on standard output. No instantiation of store derivations
takes place.
+ > **Warning**
+ >
+ > This option produces ambiguous output which is not suitable for machine
+ > consumption. For example, these two Nix expressions print the same result
+ > despite having different types:
+ >
+ > ```console
+ > $ nix-instantiate --eval --expr '{ a = {}; }'
+ > { a = <CODE>; }
+ > $ nix-instantiate --eval --expr '{ a = <CODE>; }'
+ > { a = <CODE>; }
+ > ```
+ >
+ > For human-readable output, `nix eval` (experimental) is more informative:
+ >
+ > ```console
+ > $ nix-instantiate --eval --expr 'a: a'
+ > <LAMBDA>
+ > $ nix eval --expr 'a: a'
+ > «lambda @ «string»:1:1»
+ > ```
+ >
+ > For machine-readable output, the `--xml` option produces unambiguous
+ > output:
+ >
+ > ```console
+ > $ nix-instantiate --eval --xml --expr '{ foo = <CODE>; }'
+ > <?xml version='1.0' encoding='utf-8'?>
+ > <expr>
+ > <attrs>
+ > <attr column="3" line="1" name="foo">
+ > <unevaluated />
+ > </attr>
+ > </attrs>
+ > </expr>
+ > ```
+
- `--find-file`\
Look up the given files in Nix’s search path (as specified by the
`NIX_PATH` environment variable). If found, print the corresponding
@@ -61,11 +98,11 @@ standard input.
- `--json`\
When used with `--eval`, print the resulting value as an JSON
- representation of the abstract syntax tree rather than as an ATerm.
+ representation of the abstract syntax tree rather than as a Nix expression.
- `--xml`\
When used with `--eval`, print the resulting value as an XML
- representation of the abstract syntax tree rather than as an ATerm.
+ representation of the abstract syntax tree rather than as a Nix expression.
The schema is the same as that used by the [`toXML`
built-in](../language/builtins.md).
@@ -133,28 +170,29 @@ $ nix-instantiate --eval --xml --expr '1 + 2'
The difference between non-strict and strict evaluation:
```console
-$ nix-instantiate --eval --xml --expr 'rec { x = "foo"; y = x; }'
-...
- <attr name="x">
- <string value="foo" />
- </attr>
- <attr name="y">
- <unevaluated />
- </attr>
-...
+$ nix-instantiate --eval --xml --expr '{ x = {}; }'
+<?xml version='1.0' encoding='utf-8'?>
+<expr>
+ <attrs>
+ <attr column="3" line="1" name="x">
+ <unevaluated />
+ </attr>
+ </attrs>
+</expr>
```
Note that `y` is left unevaluated (the XML representation doesn’t
attempt to show non-normal forms).
```console
-$ nix-instantiate --eval --xml --strict --expr 'rec { x = "foo"; y = x; }'
-...
- <attr name="x">
- <string value="foo" />
- </attr>
- <attr name="y">
- <string value="foo" />
- </attr>
-...
+$ nix-instantiate --eval --xml --strict --expr '{ x = {}; }'
+<?xml version='1.0' encoding='utf-8'?>
+<expr>
+ <attrs>
+ <attr column="3" line="1" name="x">
+ <attrs>
+ </attrs>
+ </attr>
+ </attrs>
+</expr>
```
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 {
diff --git a/tests/functional/lang/eval-okay-print.err.exp b/tests/functional/lang/eval-okay-print.err.exp
index 3fc99be3e..80aa17c6e 100644
--- a/tests/functional/lang/eval-okay-print.err.exp
+++ b/tests/functional/lang/eval-okay-print.err.exp
@@ -1 +1 @@
-trace: [ <CODE> ]
+trace: [ «thunk» ]
diff --git a/tests/functional/lang/eval-okay-repeated-empty-attrs.exp b/tests/functional/lang/eval-okay-repeated-empty-attrs.exp
new file mode 100644
index 000000000..d21e6db6b
--- /dev/null
+++ b/tests/functional/lang/eval-okay-repeated-empty-attrs.exp
@@ -0,0 +1 @@
+[ { } { } ]
diff --git a/tests/functional/lang/eval-okay-repeated-empty-attrs.nix b/tests/functional/lang/eval-okay-repeated-empty-attrs.nix
new file mode 100644
index 000000000..030a3b85c
--- /dev/null
+++ b/tests/functional/lang/eval-okay-repeated-empty-attrs.nix
@@ -0,0 +1,2 @@
+# Tests that empty attribute sets are not printed as `«repeated»`.
+[ {} {} ]
diff --git a/tests/functional/lang/eval-okay-repeated-empty-list.exp b/tests/functional/lang/eval-okay-repeated-empty-list.exp
new file mode 100644
index 000000000..701fc7e20
--- /dev/null
+++ b/tests/functional/lang/eval-okay-repeated-empty-list.exp
@@ -0,0 +1 @@
+[ [ ] [ ] ]
diff --git a/tests/functional/lang/eval-okay-repeated-empty-list.nix b/tests/functional/lang/eval-okay-repeated-empty-list.nix
new file mode 100644
index 000000000..376c51be8
--- /dev/null
+++ b/tests/functional/lang/eval-okay-repeated-empty-list.nix
@@ -0,0 +1 @@
+[ [] [] ]
diff --git a/tests/unit/libexpr/value/print.cc b/tests/unit/libexpr/value/print.cc
index a4f6fc014..98131112e 100644
--- a/tests/unit/libexpr/value/print.cc
+++ b/tests/unit/libexpr/value/print.cc
@@ -1,6 +1,7 @@
#include "tests/libexpr.hh"
#include "value.hh"
+#include "print.hh"
namespace nix {
@@ -12,7 +13,7 @@ struct ValuePrintingTests : LibExprTest
void test(Value v, std::string_view expected, A... args)
{
std::stringstream out;
- v.print(state.symbols, out, args...);
+ v.print(state, out, args...);
ASSERT_EQ(out.str(), expected);
}
};
@@ -84,7 +85,7 @@ TEST_F(ValuePrintingTests, tList)
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3;
- test(vList, "[ 1 2 (nullptr) ]");
+ test(vList, "[ 1 2 «nullptr» ]");
}
TEST_F(ValuePrintingTests, vThunk)
@@ -92,7 +93,7 @@ TEST_F(ValuePrintingTests, vThunk)
Value vThunk;
vThunk.mkThunk(nullptr, nullptr);
- test(vThunk, "<CODE>");
+ test(vThunk, "«thunk»");
}
TEST_F(ValuePrintingTests, vApp)
@@ -100,32 +101,55 @@ TEST_F(ValuePrintingTests, vApp)
Value vApp;
vApp.mkApp(nullptr, nullptr);
- test(vApp, "<CODE>");
+ test(vApp, "«thunk»");
}
TEST_F(ValuePrintingTests, vLambda)
{
+ Env env {
+ .up = nullptr,
+ .values = { }
+ };
+ PosTable::Origin origin((std::monostate()));
+ auto posIdx = state.positions.add(origin, 1, 1);
+ auto body = ExprInt(0);
+ auto formals = Formals {};
+
+ ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
+
Value vLambda;
- vLambda.mkLambda(nullptr, nullptr);
+ vLambda.mkLambda(&env, &eLambda);
+
+ test(vLambda, "«lambda @ «none»:1:1»");
+
+ eLambda.setName(createSymbol("puppy"));
- test(vLambda, "<LAMBDA>");
+ test(vLambda, "«lambda puppy @ «none»:1:1»");
}
TEST_F(ValuePrintingTests, vPrimOp)
{
Value vPrimOp;
- PrimOp primOp{};
+ PrimOp primOp{
+ .name = "puppy"
+ };
vPrimOp.mkPrimOp(&primOp);
- test(vPrimOp, "<PRIMOP>");
+ test(vPrimOp, "«primop puppy»");
}
TEST_F(ValuePrintingTests, vPrimOpApp)
{
+ PrimOp primOp{
+ .name = "puppy"
+ };
+ Value vPrimOp;
+ vPrimOp.mkPrimOp(&primOp);
+
Value vPrimOpApp;
- vPrimOpApp.mkPrimOpApp(nullptr, nullptr);
+ vPrimOpApp.mkPrimOpApp(&vPrimOp, nullptr);
- test(vPrimOpApp, "<PRIMOP-APP>");
+ test(vPrimOpApp, "«partially applied primop puppy»");
}
TEST_F(ValuePrintingTests, vExternal)
@@ -176,9 +200,14 @@ TEST_F(ValuePrintingTests, depthAttrs)
Value vTwo;
vTwo.mkInt(2);
+ BindingsBuilder builderEmpty(state, state.allocBindings(0));
+ Value vAttrsEmpty;
+ vAttrsEmpty.mkAttrs(builderEmpty.finish());
+
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
+ builder.insert(state.symbols.create("nested"), &vAttrsEmpty);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
@@ -191,10 +220,10 @@ TEST_F(ValuePrintingTests, depthAttrs)
Value vNested;
vNested.mkAttrs(builder2.finish());
- test(vNested, "{ nested = «too deep»; one = «too deep»; two = «too deep»; }", false, 1);
- test(vNested, "{ nested = { one = «too deep»; two = «too deep»; }; one = 1; two = 2; }", false, 2);
- test(vNested, "{ nested = { one = 1; two = 2; }; one = 1; two = 2; }", false, 3);
- test(vNested, "{ nested = { one = 1; two = 2; }; one = 1; two = 2; }", false, 4);
+ test(vNested, "{ nested = { ... }; one = 1; two = 2; }", PrintOptions { .maxDepth = 1 });
+ test(vNested, "{ nested = { nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 });
+ test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 });
+ test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 4 });
}
TEST_F(ValuePrintingTests, depthList)
@@ -227,11 +256,561 @@ TEST_F(ValuePrintingTests, depthList)
vList.bigList.elems[2] = &vNested;
vList.bigList.size = 3;
- test(vList, "[ «too deep» «too deep» «too deep» ]", false, 1);
- test(vList, "[ 1 2 { nested = «too deep»; one = «too deep»; two = «too deep»; } ]", false, 2);
- test(vList, "[ 1 2 { nested = { one = «too deep»; two = «too deep»; }; one = 1; two = 2; } ]", false, 3);
- test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", false, 4);
- test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", false, 5);
+ test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 });
+ test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 2 });
+ test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 3 });
+ test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 4 });
+ test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 5 });
+}
+
+struct StringPrintingTests : LibExprTest
+{
+ template<class... A>
+ void test(std::string_view literal, std::string_view expected, unsigned int maxLength, A... args)
+ {
+ Value v;
+ v.mkString(literal);
+
+ std::stringstream out;
+ printValue(state, out, v, PrintOptions {
+ .maxStringLength = maxLength
+ });
+ ASSERT_EQ(out.str(), expected);
+ }
+};
+
+TEST_F(StringPrintingTests, maxLengthTruncation)
+{
+ test("abcdefghi", "\"abcdefghi\"", 10);
+ test("abcdefghij", "\"abcdefghij\"", 10);
+ test("abcdefghijk", "\"abcdefghij\" «1 byte elided»", 10);
+ test("abcdefghijkl", "\"abcdefghij\" «2 bytes elided»", 10);
+ test("abcdefghijklm", "\"abcdefghij\" «3 bytes elided»", 10);
+}
+
+// Check that printing an attrset shows 'important' attributes like `type`
+// first, but only reorder the attrs when we have a maxAttrs budget.
+TEST_F(ValuePrintingTests, attrsTypeFirst)
+{
+ Value vType;
+ vType.mkString("puppy");
+
+ Value vApple;
+ vApple.mkString("apple");
+
+ BindingsBuilder builder(state, state.allocBindings(10));
+ builder.insert(state.symbols.create("type"), &vType);
+ builder.insert(state.symbols.create("apple"), &vApple);
+
+ Value vAttrs;
+ vAttrs.mkAttrs(builder.finish());
+
+ test(vAttrs,
+ "{ type = \"puppy\"; apple = \"apple\"; }",
+ PrintOptions {
+ .maxAttrs = 100
+ });
+
+ test(vAttrs,
+ "{ apple = \"apple\"; type = \"puppy\"; }",
+ PrintOptions { });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsInt)
+{
+ Value v;
+ v.mkInt(10);
+
+ test(v,
+ ANSI_CYAN "10" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsFloat)
+{
+ Value v;
+ v.mkFloat(1.6);
+
+ test(v,
+ ANSI_CYAN "1.6" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsBool)
+{
+ Value v;
+ v.mkBool(true);
+
+ test(v,
+ ANSI_CYAN "true" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsString)
+{
+ Value v;
+ v.mkString("puppy");
+
+ test(v,
+ ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsStringElided)
+{
+ Value v;
+ v.mkString("puppy");
+
+ test(v,
+ ANSI_MAGENTA "\"pup\"" ANSI_FAINT " «2 bytes elided»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true,
+ .maxStringLength = 3
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsPath)
+{
+ Value v;
+ v.mkPath(state.rootPath(CanonPath("puppy")));
+
+ test(v,
+ ANSI_GREEN "/puppy" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsNull)
+{
+ Value v;
+ v.mkNull();
+
+ test(v,
+ ANSI_CYAN "null" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsAttrs)
+{
+ Value vOne;
+ vOne.mkInt(1);
+
+ Value vTwo;
+ vTwo.mkInt(2);
+
+ BindingsBuilder builder(state, state.allocBindings(10));
+ builder.insert(state.symbols.create("one"), &vOne);
+ builder.insert(state.symbols.create("two"), &vTwo);
+
+ Value vAttrs;
+ vAttrs.mkAttrs(builder.finish());
+
+ test(vAttrs,
+ "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; two = " ANSI_CYAN "2" ANSI_NORMAL "; }",
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsDerivation)
+{
+ Value vDerivation;
+ vDerivation.mkString("derivation");
+
+ BindingsBuilder builder(state, state.allocBindings(10));
+ builder.insert(state.sType, &vDerivation);
+
+ Value vAttrs;
+ vAttrs.mkAttrs(builder.finish());
+
+ test(vAttrs,
+ ANSI_GREEN "«derivation»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true,
+ .force = true,
+ .derivationPaths = true
+ });
+
+ test(vAttrs,
+ "{ type = " ANSI_MAGENTA "\"derivation\"" ANSI_NORMAL "; }",
+ PrintOptions {
+ .ansiColors = true,
+ .force = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsError)
+{
+ Value throw_ = state.getBuiltin("throw");
+ Value message;
+ message.mkString("uh oh!");
+ Value vError;
+ vError.mkApp(&throw_, &message);
+
+ test(vError,
+ ANSI_RED
+ "«"
+ ANSI_RED
+ "error:"
+ ANSI_NORMAL
+ "\n … while calling the '"
+ ANSI_MAGENTA
+ "throw"
+ ANSI_NORMAL
+ "' builtin\n\n "
+ ANSI_RED
+ "error:"
+ ANSI_NORMAL
+ " uh oh!»"
+ ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true,
+ .force = true,
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsDerivationError)
+{
+ Value throw_ = state.getBuiltin("throw");
+ Value message;
+ message.mkString("uh oh!");
+ Value vError;
+ vError.mkApp(&throw_, &message);
+
+ Value vDerivation;
+ vDerivation.mkString("derivation");
+
+ BindingsBuilder builder(state, state.allocBindings(10));
+ builder.insert(state.sType, &vDerivation);
+ builder.insert(state.sDrvPath, &vError);
+
+ Value vAttrs;
+ vAttrs.mkAttrs(builder.finish());
+
+ test(vAttrs,
+ "{ drvPath = "
+ ANSI_RED
+ "«"
+ ANSI_RED
+ "error:"
+ ANSI_NORMAL
+ "\n … while calling the '"
+ ANSI_MAGENTA
+ "throw"
+ ANSI_NORMAL
+ "' builtin\n\n "
+ ANSI_RED
+ "error:"
+ ANSI_NORMAL
+ " uh oh!»"
+ ANSI_NORMAL
+ "; type = "
+ ANSI_MAGENTA
+ "\"derivation\""
+ ANSI_NORMAL
+ "; }",
+ PrintOptions {
+ .ansiColors = true,
+ .force = true
+ });
+
+ test(vAttrs,
+ ANSI_RED
+ "«"
+ ANSI_RED
+ "error:"
+ ANSI_NORMAL
+ "\n … while calling the '"
+ ANSI_MAGENTA
+ "throw"
+ ANSI_NORMAL
+ "' builtin\n\n "
+ ANSI_RED
+ "error:"
+ ANSI_NORMAL
+ " uh oh!»"
+ ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true,
+ .force = true,
+ .derivationPaths = true,
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsAssert)
+{
+ ExprVar eFalse(state.symbols.create("false"));
+ eFalse.bindVars(state, state.staticBaseEnv);
+ ExprInt eInt(1);
+
+ ExprAssert expr(noPos, &eFalse, &eInt);
+
+ Value v;
+ state.mkThunk_(v, &expr);
+
+ test(v,
+ ANSI_RED "«" ANSI_RED "error:" ANSI_NORMAL " assertion '" ANSI_MAGENTA "false" ANSI_NORMAL "' failed»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true,
+ .force = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsList)
+{
+ Value vOne;
+ vOne.mkInt(1);
+
+ Value vTwo;
+ vTwo.mkInt(2);
+
+ Value vList;
+ state.mkList(vList, 5);
+ vList.bigList.elems[0] = &vOne;
+ vList.bigList.elems[1] = &vTwo;
+ vList.bigList.size = 3;
+
+ test(vList,
+ "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]",
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsLambda)
+{
+ Env env {
+ .up = nullptr,
+ .values = { }
+ };
+ PosTable::Origin origin((std::monostate()));
+ auto posIdx = state.positions.add(origin, 1, 1);
+ auto body = ExprInt(0);
+ auto formals = Formals {};
+
+ ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
+
+ Value vLambda;
+ vLambda.mkLambda(&env, &eLambda);
+
+ test(vLambda,
+ ANSI_BLUE "«lambda @ «none»:1:1»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true,
+ .force = true
+ });
+
+ eLambda.setName(createSymbol("puppy"));
+
+ test(vLambda,
+ ANSI_BLUE "«lambda puppy @ «none»:1:1»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true,
+ .force = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsPrimOp)
+{
+ PrimOp primOp{
+ .name = "puppy"
+ };
+ Value v;
+ v.mkPrimOp(&primOp);
+
+ test(v,
+ ANSI_BLUE "«primop puppy»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsPrimOpApp)
+{
+ PrimOp primOp{
+ .name = "puppy"
+ };
+ Value vPrimOp;
+ vPrimOp.mkPrimOp(&primOp);
+
+ Value v;
+ v.mkPrimOpApp(&vPrimOp, nullptr);
+
+ test(v,
+ ANSI_BLUE "«partially applied primop puppy»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsThunk)
+{
+ Value v;
+ v.mkThunk(nullptr, nullptr);
+
+ test(v,
+ ANSI_MAGENTA "«thunk»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsBlackhole)
+{
+ Value v;
+ v.mkBlackhole();
+
+ test(v,
+ ANSI_RED "«potential infinite recursion»" ANSI_NORMAL,
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated)
+{
+ BindingsBuilder emptyBuilder(state, state.allocBindings(1));
+
+ Value vEmpty;
+ vEmpty.mkAttrs(emptyBuilder.finish());
+
+ BindingsBuilder builder(state, state.allocBindings(10));
+ builder.insert(state.symbols.create("a"), &vEmpty);
+ builder.insert(state.symbols.create("b"), &vEmpty);
+
+ Value vAttrs;
+ vAttrs.mkAttrs(builder.finish());
+
+ test(vAttrs,
+ "{ a = { }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }",
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsListRepeated)
+{
+ BindingsBuilder emptyBuilder(state, state.allocBindings(1));
+
+ Value vEmpty;
+ vEmpty.mkAttrs(emptyBuilder.finish());
+
+ Value vList;
+ state.mkList(vList, 3);
+ vList.bigList.elems[0] = &vEmpty;
+ vList.bigList.elems[1] = &vEmpty;
+ vList.bigList.size = 2;
+
+ test(vList,
+ "[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]",
+ PrintOptions {
+ .ansiColors = true
+ });
+}
+
+TEST_F(ValuePrintingTests, listRepeated)
+{
+ BindingsBuilder emptyBuilder(state, state.allocBindings(1));
+
+ Value vEmpty;
+ vEmpty.mkAttrs(emptyBuilder.finish());
+
+ Value vList;
+ state.mkList(vList, 3);
+ vList.bigList.elems[0] = &vEmpty;
+ vList.bigList.elems[1] = &vEmpty;
+ vList.bigList.size = 2;
+
+ test(vList, "[ { } «repeated» ]", PrintOptions { });
+ test(vList,
+ "[ { } { } ]",
+ PrintOptions {
+ .trackRepeated = false
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
+{
+ Value vOne;
+ vOne.mkInt(1);
+
+ Value vTwo;
+ vTwo.mkInt(2);
+
+ BindingsBuilder builder(state, state.allocBindings(10));
+ builder.insert(state.symbols.create("one"), &vOne);
+ builder.insert(state.symbols.create("two"), &vTwo);
+
+ Value vAttrs;
+ vAttrs.mkAttrs(builder.finish());
+
+ test(vAttrs,
+ "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT " «1 attribute elided»" ANSI_NORMAL "}",
+ PrintOptions {
+ .ansiColors = true,
+ .maxAttrs = 1
+ });
+
+ Value vThree;
+ vThree.mkInt(3);
+
+ builder.insert(state.symbols.create("three"), &vThree);
+ vAttrs.mkAttrs(builder.finish());
+
+ test(vAttrs,
+ "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT " «2 attributes elided»" ANSI_NORMAL "}",
+ PrintOptions {
+ .ansiColors = true,
+ .maxAttrs = 1
+ });
+}
+
+TEST_F(ValuePrintingTests, ansiColorsListElided)
+{
+ BindingsBuilder emptyBuilder(state, state.allocBindings(1));
+
+ Value vOne;
+ vOne.mkInt(1);
+
+ Value vTwo;
+ vTwo.mkInt(2);
+
+ Value vList;
+ state.mkList(vList, 4);
+ vList.bigList.elems[0] = &vOne;
+ vList.bigList.elems[1] = &vTwo;
+ vList.bigList.size = 2;
+
+ test(vList,
+ "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT " «1 item elided»" ANSI_NORMAL "]",
+ PrintOptions {
+ .ansiColors = true,
+ .maxListItems = 1
+ });
+
+ Value vThree;
+ vThree.mkInt(3);
+
+ vList.bigList.elems[2] = &vThree;
+ vList.bigList.size = 3;
+
+ test(vList,
+ "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT " «2 items elided»" ANSI_NORMAL "]",
+ PrintOptions {
+ .ansiColors = true,
+ .maxListItems = 1
+ });
}
} // namespace nix