Unify and refactor value printing

Previously, there were two mostly-identical value printers -- one in
`libexpr/eval.cc` (which didn't force values) and one in
`libcmd/repl.cc` (which did force values and also printed ANSI color
codes).

This PR unifies both of these printers into `print.cc` and provides a
`PrintOptions` struct for controlling the output, which allows for
toggling whether values are forced, whether repeated values are tracked,
and whether ANSI color codes are displayed.

Additionally, `PrintOptions` allows tuning the maximum number of
attributes, list items, and bytes in a string that will be displayed;
this makes it ideal for contexts where printing too much output (e.g.
all of Nixpkgs) is distracting. (As requested by @roberth in
https://github.com/NixOS/nix/pull/9554#issuecomment-1845095735)

Please read the tests for example output.

Future work:
- It would be nice to provide this function as a builtin, perhaps
  `builtins.toStringDebug` -- a printing function that never fails would
  be useful when debugging Nix code.
- It would be nice to support customizing `PrintOptions` members on the
  command line, e.g. `--option to-string-max-attrs 1000`.

(cherry picked from commit 0fa08b451682fb3311fe58112ff05c4fe5bee3a4, )

===

Restore ambiguous value printer for `nix-instantiate`

The Nix team has requested that this output format remain unchanged.
I've added a warning to the man page explaining that `nix-instantiate
--eval` output will not parse correctly in many situations.

(cherry picked from commit df84dd4d8dd3fd6381ac2ca3064432ab31a16b79)

Change-Id: I7cca6b4b53cd0642f2d49af657d5676a8554c9f8
This commit is contained in:
eldritch horrors 2024-03-08 03:05:47 +01:00
parent 0e8f505f66
commit 512c1f05c3
20 changed files with 1354 additions and 299 deletions

View file

@ -35,13 +35,50 @@ standard input.
- `--parse`\ - `--parse`\
Just parse the input files, and print their abstract syntax trees on Just parse the input files, and print their abstract syntax trees on
standard output in ATerm format. standard output as a Nix expression.
- `--eval`\ - `--eval`\
Just parse and evaluate the input files, and print the resulting Just parse and evaluate the input files, and print the resulting
values on standard output. No instantiation of store derivations values on standard output. No instantiation of store derivations
takes place. 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`\ - `--find-file`\
Look up the given files in Nixs search path (as specified by the Look up the given files in Nixs search path (as specified by the
`NIX_PATH` environment variable). If found, print the corresponding `NIX_PATH` environment variable). If found, print the corresponding
@ -61,11 +98,11 @@ standard input.
- `--json`\ - `--json`\
When used with `--eval`, print the resulting value as an 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`\ - `--xml`\
When used with `--eval`, print the resulting value as an 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` The schema is the same as that used by the [`toXML`
built-in](../language/builtins.md). built-in](../language/builtins.md).
@ -133,28 +170,29 @@ $ nix-instantiate --eval --xml --expr '1 + 2'
The difference between non-strict and strict evaluation: The difference between non-strict and strict evaluation:
```console ```console
$ nix-instantiate --eval --xml --expr 'rec { x = "foo"; y = x; }' $ nix-instantiate --eval --xml --expr '{ x = {}; }'
... <?xml version='1.0' encoding='utf-8'?>
<attr name="x"> <expr>
<string value="foo" /> <attrs>
</attr> <attr column="3" line="1" name="x">
<attr name="y">
<unevaluated /> <unevaluated />
</attr> </attr>
... </attrs>
</expr>
``` ```
Note that `y` is left unevaluated (the XML representation doesnt Note that `y` is left unevaluated (the XML representation doesnt
attempt to show non-normal forms). attempt to show non-normal forms).
```console ```console
$ nix-instantiate --eval --xml --strict --expr 'rec { x = "foo"; y = x; }' $ nix-instantiate --eval --xml --strict --expr '{ x = {}; }'
... <?xml version='1.0' encoding='utf-8'?>
<attr name="x"> <expr>
<string value="foo" /> <attrs>
<attr column="3" line="1" name="x">
<attrs>
</attrs>
</attr> </attr>
<attr name="y"> </attrs>
<string value="foo" /> </expr>
</attr>
...
``` ```

View file

@ -90,9 +90,17 @@ struct NixRepl
void evalString(std::string s, Value & v); void evalString(std::string s, Value & v);
void loadDebugTraceEnv(DebugTrace & dt); void loadDebugTraceEnv(DebugTrace & dt);
typedef std::set<Value *> ValuesSeen; void printValue(std::ostream & str,
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth); Value & v,
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen); 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) std::string removeWhitespace(std::string s)
@ -713,7 +721,8 @@ bool NixRepl::processLine(std::string line)
else if (command == ":p" || command == ":print") { else if (command == ":p" || command == ":print") {
Value v; Value v;
evalString(arg, 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") { else if (command == ":q" || command == ":quit") {
@ -775,7 +784,8 @@ bool NixRepl::processLine(std::string line)
} else { } else {
Value v; Value v;
evalString(line, 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( std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state, const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<AnnotatedValues()> getValues) std::function<AnnotatedValues()> getValues)

View file

@ -100,117 +100,23 @@ RootValue allocRootValue(Value * v)
#endif #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 // Pretty print types for assertion errors
std::ostream & operator << (std::ostream & os, const ValueType t) { std::ostream & operator << (std::ostream & os, const ValueType t) {
os << showType(t); os << showType(t);
return os; return os;
} }
std::string printValue(const EvalState & state, const Value & v) std::string printValue(EvalState & state, Value & v)
{ {
std::ostringstream out; std::ostringstream out;
v.print(state.symbols, out); v.print(state, out);
return out.str(); 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 * getPrimOp(const Value &v) {
const Value * primOp = &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) void Value::mkPrimOp(PrimOp * p)
{ {
p->check(); p->check();

View file

@ -83,6 +83,8 @@ struct PrimOp
void check(); void check();
}; };
std::ostream & operator<<(std::ostream & output, PrimOp & primOp);
/** /**
* Info about a constant * 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); 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); std::ostream & operator << (std::ostream & os, const ValueType t);

View file

@ -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();
}
}
}

View file

@ -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);
}

View file

@ -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();
};
}

View file

@ -1,24 +1,64 @@
#include "print.hh" #include <limits>
#include <unordered_set> #include <unordered_set>
#include "print.hh"
#include "ansicolor.hh"
#include "store-api.hh"
#include "english.hh"
namespace nix { namespace nix {
std::ostream & void printElided(
printLiteralString(std::ostream & str, const std::string_view string) 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, size_t maxLength, bool ansiColors)
{
size_t charsPrinted = 0;
if (ansiColors)
str << ANSI_MAGENTA;
str << "\""; str << "\"";
for (auto i = string.begin(); i != string.end(); ++i) { 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; if (*i == '\"' || *i == '\\') str << "\\" << *i;
else if (*i == '\n') str << "\\n"; else if (*i == '\n') str << "\\n";
else if (*i == '\r') str << "\\r"; else if (*i == '\r') str << "\\r";
else if (*i == '\t') str << "\\t"; else if (*i == '\t') str << "\\t";
else if (*i == '$' && *(i+1) == '{') str << "\\" << *i; else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
else str << *i; else str << *i;
charsPrinted++;
} }
str << "\""; str << "\"";
if (ansiColors)
str << ANSI_NORMAL;
return str; 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 & std::ostream &
printLiteralBool(std::ostream & str, bool boolean) printLiteralBool(std::ostream & str, bool boolean)
{ {
@ -90,5 +130,373 @@ printAttributeName(std::ostream & str, std::string_view name) {
return str; 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);
}
} }

View file

@ -9,6 +9,9 @@
#include <iostream> #include <iostream>
#include "eval.hh"
#include "print-options.hh"
namespace nix { namespace nix {
/** /**
@ -16,6 +19,7 @@ namespace nix {
* *
* Quotes and fairly minimal escaping are added. * Quotes and fairly minimal escaping are added.
* *
* @param o The output stream to print to
* @param s The logical string * @param s The logical string
*/ */
std::ostream & printLiteralString(std::ostream & o, std::string_view s); 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); std::ostream & printIdentifier(std::ostream & o, std::string_view s);
void printValue(EvalState & state, std::ostream & str, Value & v, PrintOptions options = PrintOptions {});
} }

View file

@ -8,6 +8,7 @@
#include "value/context.hh" #include "value/context.hh"
#include "input-accessor.hh" #include "input-accessor.hh"
#include "source-path.hh" #include "source-path.hh"
#include "print-options.hh"
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
#include <gc/gc_allocator.h> #include <gc/gc_allocator.h>
@ -70,7 +71,7 @@ class StorePath;
class Store; class Store;
class EvalState; class EvalState;
class XMLWriter; class XMLWriter;
class Printer;
typedef int64_t NixInt; typedef int64_t NixInt;
typedef double NixFloat; typedef double NixFloat;
@ -82,6 +83,7 @@ typedef double NixFloat;
class ExternalValueBase class ExternalValueBase
{ {
friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
friend class Printer;
protected: protected:
/** /**
* Print out the value * Print out the value
@ -139,11 +141,9 @@ private:
friend std::string showType(const Value & v); friend std::string showType(const Value & v);
void print(const SymbolTable &symbols, std::ostream &str, std::set<const void *> *seen, int depth) const;
public: 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 // Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's // These should be removed eventually, by putting the functionality that's
@ -351,10 +351,15 @@ public:
inline void mkPrimOpApp(Value * l, Value * r) inline void mkPrimOpApp(Value * l, Value * r)
{ {
internalType = tPrimOpApp; internalType = tPrimOpApp;
app.left = l; primOpApp.left = l;
app.right = r; primOpApp.right = r;
} }
/**
* For a `tPrimOpApp` value, get the original `PrimOp` value.
*/
PrimOp * primOpAppPrimOp() const;
inline void mkExternal(ExternalValueBase * e) inline void mkExternal(ExternalValueBase * e)
{ {
clearValue(); clearValue();

18
src/libutil/english.cc Normal file
View file

@ -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;
}
}

18
src/libutil/english.hh Normal file
View file

@ -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);
}

View file

@ -9,6 +9,8 @@
#include "eval.hh" #include "eval.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "profiles.hh" #include "profiles.hh"
#include "print-ambiguous.hh"
#include <limits>
namespace nix { namespace nix {
@ -106,7 +108,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
the store; we need it for future modifications of the the store; we need it for future modifications of the
environment. */ environment. */
std::ostringstream str; 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", auto manifestFile = state.store->addTextToStore("env-manifest.nix",
str.str(), references); str.str(), references);

View file

@ -1,4 +1,5 @@
#include "globals.hh" #include "globals.hh"
#include "print-ambiguous.hh"
#include "shared.hh" #include "shared.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
@ -25,7 +26,6 @@ static int rootNr = 0;
enum OutputKind { okPlain, okXML, okJSON }; enum OutputKind { okPlain, okXML, okJSON };
void processExpr(EvalState & state, const Strings & attrPaths, void processExpr(EvalState & state, const Strings & attrPaths,
bool parseOnly, bool strict, Bindings & autoArgs, bool parseOnly, bool strict, Bindings & autoArgs,
bool evalOnly, OutputKind output, bool location, Expr * e) bool evalOnly, OutputKind output, bool location, Expr * e)
@ -57,7 +57,8 @@ void processExpr(EvalState & state, const Strings & attrPaths,
std::cout << std::endl; std::cout << std::endl;
} else { } else {
if (strict) state.forceValueDeep(vRes); 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; std::cout << std::endl;
} }
} else { } else {

View file

@ -1 +1 @@
trace: [ <CODE> ] trace: [ «thunk» ]

View file

@ -0,0 +1 @@
[ { } { } ]

View file

@ -0,0 +1,2 @@
# Tests that empty attribute sets are not printed as `«repeated»`.
[ {} {} ]

View file

@ -0,0 +1 @@
[ [ ] [ ] ]

View file

@ -0,0 +1 @@
[ [] [] ]

View file

@ -1,6 +1,7 @@
#include "tests/libexpr.hh" #include "tests/libexpr.hh"
#include "value.hh" #include "value.hh"
#include "print.hh"
namespace nix { namespace nix {
@ -12,7 +13,7 @@ struct ValuePrintingTests : LibExprTest
void test(Value v, std::string_view expected, A... args) void test(Value v, std::string_view expected, A... args)
{ {
std::stringstream out; std::stringstream out;
v.print(state.symbols, out, args...); v.print(state, out, args...);
ASSERT_EQ(out.str(), expected); ASSERT_EQ(out.str(), expected);
} }
}; };
@ -84,7 +85,7 @@ TEST_F(ValuePrintingTests, tList)
vList.bigList.elems[1] = &vTwo; vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3; vList.bigList.size = 3;
test(vList, "[ 1 2 (nullptr) ]"); test(vList, "[ 1 2 «nullptr» ]");
} }
TEST_F(ValuePrintingTests, vThunk) TEST_F(ValuePrintingTests, vThunk)
@ -92,7 +93,7 @@ TEST_F(ValuePrintingTests, vThunk)
Value vThunk; Value vThunk;
vThunk.mkThunk(nullptr, nullptr); vThunk.mkThunk(nullptr, nullptr);
test(vThunk, "<CODE>"); test(vThunk, "«thunk»");
} }
TEST_F(ValuePrintingTests, vApp) TEST_F(ValuePrintingTests, vApp)
@ -100,32 +101,55 @@ TEST_F(ValuePrintingTests, vApp)
Value vApp; Value vApp;
vApp.mkApp(nullptr, nullptr); vApp.mkApp(nullptr, nullptr);
test(vApp, "<CODE>"); test(vApp, "«thunk»");
} }
TEST_F(ValuePrintingTests, vLambda) TEST_F(ValuePrintingTests, vLambda)
{ {
Value vLambda; Env env {
vLambda.mkLambda(nullptr, nullptr); .up = nullptr,
.values = { }
};
PosTable::Origin origin((std::monostate()));
auto posIdx = state.positions.add(origin, 1, 1);
auto body = ExprInt(0);
auto formals = Formals {};
test(vLambda, "<LAMBDA>"); ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
Value vLambda;
vLambda.mkLambda(&env, &eLambda);
test(vLambda, "«lambda @ «none»:1:1»");
eLambda.setName(createSymbol("puppy"));
test(vLambda, "«lambda puppy @ «none»:1:1»");
} }
TEST_F(ValuePrintingTests, vPrimOp) TEST_F(ValuePrintingTests, vPrimOp)
{ {
Value vPrimOp; Value vPrimOp;
PrimOp primOp{}; PrimOp primOp{
.name = "puppy"
};
vPrimOp.mkPrimOp(&primOp); vPrimOp.mkPrimOp(&primOp);
test(vPrimOp, "<PRIMOP>"); test(vPrimOp, "«primop puppy»");
} }
TEST_F(ValuePrintingTests, vPrimOpApp) TEST_F(ValuePrintingTests, vPrimOpApp)
{ {
Value vPrimOpApp; PrimOp primOp{
vPrimOpApp.mkPrimOpApp(nullptr, nullptr); .name = "puppy"
};
Value vPrimOp;
vPrimOp.mkPrimOp(&primOp);
test(vPrimOpApp, "<PRIMOP-APP>"); Value vPrimOpApp;
vPrimOpApp.mkPrimOpApp(&vPrimOp, nullptr);
test(vPrimOpApp, "«partially applied primop puppy»");
} }
TEST_F(ValuePrintingTests, vExternal) TEST_F(ValuePrintingTests, vExternal)
@ -176,9 +200,14 @@ TEST_F(ValuePrintingTests, depthAttrs)
Value vTwo; Value vTwo;
vTwo.mkInt(2); vTwo.mkInt(2);
BindingsBuilder builderEmpty(state, state.allocBindings(0));
Value vAttrsEmpty;
vAttrsEmpty.mkAttrs(builderEmpty.finish());
BindingsBuilder builder(state, state.allocBindings(10)); BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne); builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo); builder.insert(state.symbols.create("two"), &vTwo);
builder.insert(state.symbols.create("nested"), &vAttrsEmpty);
Value vAttrs; Value vAttrs;
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
@ -191,10 +220,10 @@ TEST_F(ValuePrintingTests, depthAttrs)
Value vNested; Value vNested;
vNested.mkAttrs(builder2.finish()); vNested.mkAttrs(builder2.finish());
test(vNested, "{ nested = «too deep»; one = «too deep»; two = «too deep»; }", false, 1); test(vNested, "{ nested = { ... }; one = 1; two = 2; }", PrintOptions { .maxDepth = 1 });
test(vNested, "{ nested = { one = «too deep»; two = «too deep»; }; one = 1; two = 2; }", false, 2); test(vNested, "{ nested = { nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 });
test(vNested, "{ nested = { one = 1; two = 2; }; one = 1; two = 2; }", false, 3); test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 });
test(vNested, "{ nested = { one = 1; two = 2; }; one = 1; two = 2; }", false, 4); test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 4 });
} }
TEST_F(ValuePrintingTests, depthList) TEST_F(ValuePrintingTests, depthList)
@ -227,11 +256,561 @@ TEST_F(ValuePrintingTests, depthList)
vList.bigList.elems[2] = &vNested; vList.bigList.elems[2] = &vNested;
vList.bigList.size = 3; vList.bigList.size = 3;
test(vList, "[ «too deep» «too deep» «too deep» ]", false, 1); test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 });
test(vList, "[ 1 2 { nested = «too deep»; one = «too deep»; two = «too deep»; } ]", false, 2); test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 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; } ]", PrintOptions { .maxDepth = 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; } ]", PrintOptions { .maxDepth = 4 });
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", false, 5); 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 } // namespace nix