forked from lix-project/lix
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:
parent
0e8f505f66
commit
512c1f05c3
|
@ -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 Nix’s search path (as specified by the
|
Look up the given files in Nix’s 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 doesn’t
|
Note that `y` is left unevaluated (the XML representation doesn’t
|
||||||
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>
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
|
99
src/libexpr/print-ambiguous.cc
Normal file
99
src/libexpr/print-ambiguous.cc
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
src/libexpr/print-ambiguous.hh
Normal file
24
src/libexpr/print-ambiguous.hh
Normal 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);
|
||||||
|
|
||||||
|
}
|
52
src/libexpr/print-options.hh
Normal file
52
src/libexpr/print-options.hh
Normal 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
18
src/libutil/english.cc
Normal 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
18
src/libutil/english.hh
Normal 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);
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
trace: [ <CODE> ]
|
trace: [ «thunk» ]
|
||||||
|
|
1
tests/functional/lang/eval-okay-repeated-empty-attrs.exp
Normal file
1
tests/functional/lang/eval-okay-repeated-empty-attrs.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ { } { } ]
|
2
tests/functional/lang/eval-okay-repeated-empty-attrs.nix
Normal file
2
tests/functional/lang/eval-okay-repeated-empty-attrs.nix
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Tests that empty attribute sets are not printed as `«repeated»`.
|
||||||
|
[ {} {} ]
|
1
tests/functional/lang/eval-okay-repeated-empty-list.exp
Normal file
1
tests/functional/lang/eval-okay-repeated-empty-list.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ [ ] [ ] ]
|
1
tests/functional/lang/eval-okay-repeated-empty-list.nix
Normal file
1
tests/functional/lang/eval-okay-repeated-empty-list.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ [] [] ]
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue