diff --git a/example.nix b/example.nix new file mode 100644 index 00000000000..23932081ea3 --- /dev/null +++ b/example.nix @@ -0,0 +1,20 @@ +let + showPos = + pos: if pos == null then "" else "${pos.file}:${toString pos.line}:${toString pos.column}"; + + # ({pos,isThunk, printValue} -> a -> b ) -> (a -> c) -> a -> c + tracePos = builtins.seqWithCallPos ( + { + pos, + printValue, + terminalWidth, + ... + }: + a: + let + msg = "${showPos pos} ${printValue a}"; + in + builtins.seq a builtins.trace msg null + ) (a: a); +in + tracePos 42 diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 369c4967240..dbaec4c8ee2 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1649,7 +1649,7 @@ void EvalState::callFunction(Value & fun, std::span args, Value & vRes, if (countCalls) primOpCalls[fn->name]++; try { - fn->fun(*this, vCur.determinePos(noPos), args.data(), vCur); + fn->fun(*this, vCur.determinePos(pos), args.data(), vCur); } catch (Error & e) { if (fn->addTrace) addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); @@ -1697,7 +1697,7 @@ void EvalState::callFunction(Value & fun, std::span args, Value & vRes, // 1. Unify this and above code. Heavily redundant. // 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc) // so the debugger allows to inspect the wrong parameters passed to the builtin. - fn->fun(*this, vCur.determinePos(noPos), vArgs, vCur); + fn->fun(*this, vCur.determinePos(pos), vArgs, vCur); } catch (Error & e) { if (fn->addTrace) addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); @@ -1760,7 +1760,6 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v) SmallValueVector<4> vArgs(args.size()); for (size_t i = 0; i < args.size(); ++i) vArgs[i] = args[i]->maybeThunk(state, env); - state.callFunction(vFun, vArgs, v, pos); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 54682ea318f..cdbfdd548e1 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -14,6 +14,8 @@ #include "value-to-xml.hh" #include "primops.hh" #include "fetch-to-store.hh" +#include "terminal.hh" +#include #include #include @@ -513,6 +515,97 @@ static RegisterPrimOp primop_isFunction({ .fun = prim_isFunction, }); +void prim_isThunk(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + auto &arg = *args[0]; + v.mkBool(arg.isThunk()); +} + +static RegisterPrimOp primop_isThunk({ + .name = "isThunk", + .args = {"v"}, + .doc = R"( + Returns `true` if the argument is a thunk, `false` otherwise. + + This is internal, because the thunk-ness of a value is not a stable property, while the Nix language is meant to be referentially transparent. + )", + .fun = prim_isThunk, + .internal = true +}); + +void prim_printValue(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + auto &arg = *args[0]; + std::stringstream ss; + printValue(state, ss, arg); + v.mkString(ss.str()); +} + +static RegisterPrimOp primop_printValue({ + .name = "printValue", + .args = {"v"}, + .doc = R"( + Print any value to an informative string. + + This is internal, because the format is not a stable behavior, while the Nix language is meant to be stable over time. + )", + .fun = prim_printValue, + .internal = true +}); + + +/* Determine whether the argument is an integer. */ +static void prim_seqWithCallPos(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + auto p = args[0]; + auto f = args[1]; + auto a = args[2]; + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + auto intro_args = state.buildBindings(4); + auto pos_attrs = state.allocValue(); + intro_args.insert(state.symbols.create("pos"), pos_attrs, noPos); + + auto vIsThunk = get(state.internalPrimOps, "isThunk"); + assert(vIsThunk); + intro_args.insert(state.symbols.create("isThunk"), *vIsThunk, noPos); + + auto vPrintValue = get(state.internalPrimOps, "printValue"); + assert(vPrintValue); + intro_args.insert(state.symbols.create("printValue"), *vPrintValue, noPos); + + auto [rows,cols] = getWindowSize(); + auto vCols = state.allocValue(); + vCols->mkInt(cols); + intro_args.insert(state.symbols.create("terminalWidth"), vCols , noPos); + + Value intro_args_value; + intro_args_value.mkAttrs(intro_args); + state.mkPos(*pos_attrs, pos); + + Value * pargs[2] = {&intro_args_value, a}; + + Value * _pRes = state.allocValue(); + try { + state.callFunction(*p, std::span(pargs, 2), *_pRes, pos); + } catch (Error & e) { + e.addTrace(state.positions[pos], "while calling the first function passed to builtins.seqWithCallPos"); + ignoreExceptionExceptInterrupt(lvlDebug); + } + + state.callFunction(*f, *a, v, pos); +} + +static RegisterPrimOp primop_seqWithCallPos({ + .name = "__seqWithCallPos", + .args = {"p", "f", "a"}, + .doc = R"( + Stuff + )", + .fun = prim_seqWithCallPos, +}); + + /* Determine whether the argument is an integer. */ static void prim_isInt(EvalState & state, const PosIdx pos, Value * * args, Value & v) {