From 33b1679d75f2a3a5dac053431a41897ebf96a3f3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Aug 2020 13:11:56 +0200 Subject: [PATCH] Allow primops to have Markdown documentation --- doc/manual/src/expressions/builtins.md | 3 -- src/libexpr/eval.cc | 28 +++++++++++++++++ src/libexpr/eval.hh | 4 +++ src/libexpr/primops.cc | 43 ++++++++++++++++++++------ src/libexpr/primops.hh | 8 +++-- src/nix/repl.cc | 21 ++++++++++++- 6 files changed, 92 insertions(+), 15 deletions(-) diff --git a/doc/manual/src/expressions/builtins.md b/doc/manual/src/expressions/builtins.md index 7c4a62f54..c258fb3b3 100644 --- a/doc/manual/src/expressions/builtins.md +++ b/doc/manual/src/expressions/builtins.md @@ -9,9 +9,6 @@ scope. Instead, you can access them through the `builtins` built-in value, which is a set that contains all built-in functions and values. For instance, `derivation` is also available as `builtins.derivation`. - - `abort` *s*; `builtins.abort` *s* - Abort Nix expression evaluation, print error message *s*. - - `builtins.add` *e1* *e2* Return the sum of the numbers *e1* and *e2*. diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 9a155c055..6ac8bbc9f 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -525,6 +525,34 @@ Value * EvalState::addPrimOp(const string & name, } +Value * EvalState::addPrimOp(PrimOp && primOp) +{ + /* Hack to make constants lazy: turn them into a application of + the primop to a dummy value. */ + if (primOp.arity == 0) { + primOp.arity = 1; + auto vPrimOp = allocValue(); + vPrimOp->type = tPrimOp; + vPrimOp->primOp = new PrimOp(std::move(primOp)); + Value v; + mkApp(v, *vPrimOp, *vPrimOp); + return addConstant(primOp.name, v); + } + + Symbol envName = primOp.name; + if (hasPrefix(primOp.name, "__")) + primOp.name = symbols.create(std::string(primOp.name, 2)); + + Value * v = allocValue(); + v->type = tPrimOp; + v->primOp = new PrimOp(std::move(primOp)); + staticBaseEnv.vars[envName] = baseEnvDispl; + baseEnv.values[baseEnvDispl++] = v; + baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v)); + return v; +} + + Value & EvalState::getBuiltin(const string & name) { return *baseEnv.values[0]->attrs->find(symbols.create(name))->value; diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 38e025a3d..db8eb3e16 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -30,6 +30,8 @@ struct PrimOp PrimOpFun fun; size_t arity; Symbol name; + std::vector args; + const char * doc = nullptr; }; @@ -240,6 +242,8 @@ private: Value * addPrimOp(const string & name, size_t arity, PrimOpFun primOp); + Value * addPrimOp(PrimOp && primOp); + public: Value & getBuiltin(const string & name); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 4e35dedb0..902a37e6b 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -445,12 +445,19 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar } -static void prim_abort(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string s = state.coerceToString(pos, *args[0], context); - throw Abort("evaluation aborted with the following error message: '%1%'", s); -} +static RegisterPrimOp primop_abort({ + .name = "abort", + .args = {"s"}, + .doc = R"( + Abort Nix expression evaluation and print the error message *s*. + )", + .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + { + PathSet context; + string s = state.coerceToString(pos, *args[0], context); + throw Abort("evaluation aborted with the following error message: '%1%'", s); + } +}); static void prim_throw(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -2238,7 +2245,20 @@ RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun, std::optional requiredFeature) { if (!primOps) primOps = new PrimOps; - primOps->push_back({name, arity, fun, requiredFeature}); + primOps->push_back({ + .name = name, + .args = {}, + .arity = arity, + .requiredFeature = std::move(requiredFeature), + .fun = fun + }); +} + + +RegisterPrimOp::RegisterPrimOp(Info && info) +{ + if (!primOps) primOps = new PrimOps; + primOps->push_back(std::move(info)); } @@ -2314,7 +2334,6 @@ void EvalState::createBaseEnv() addPrimOp("__isBool", 1, prim_isBool); addPrimOp("__isPath", 1, prim_isPath); addPrimOp("__genericClosure", 1, prim_genericClosure); - addPrimOp("abort", 1, prim_abort); addPrimOp("__addErrorContext", 2, prim_addErrorContext); addPrimOp("__tryEval", 1, prim_tryEval); addPrimOp("__getEnv", 1, prim_getEnv); @@ -2431,7 +2450,13 @@ void EvalState::createBaseEnv() if (RegisterPrimOp::primOps) for (auto & primOp : *RegisterPrimOp::primOps) if (!primOp.requiredFeature || settings.isExperimentalFeatureEnabled(*primOp.requiredFeature)) - addPrimOp(primOp.name, primOp.arity, primOp.primOp); + addPrimOp({ + .fun = primOp.fun, + .arity = std::max(primOp.args.size(), primOp.arity), + .name = symbols.create(primOp.name), + .args = std::move(primOp.args), + .doc = primOp.doc, + }); /* Now that we've added all primops, sort the `builtins' set, because attribute lookups expect it to be sorted. */ diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh index 75c460ecf..ed5e2ea58 100644 --- a/src/libexpr/primops.hh +++ b/src/libexpr/primops.hh @@ -10,9 +10,11 @@ struct RegisterPrimOp struct Info { std::string name; - size_t arity; - PrimOpFun primOp; + std::vector args; + size_t arity = 0; + const char * doc; std::optional requiredFeature; + PrimOpFun fun; }; typedef std::vector PrimOps; @@ -26,6 +28,8 @@ struct RegisterPrimOp size_t arity, PrimOpFun fun, std::optional requiredFeature = {}); + + RegisterPrimOp(Info && info); }; /* These primops are disabled without enableNativeCode, but plugins diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 0cbe9643c..d370ca767 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -416,7 +416,8 @@ bool NixRepl::processLine(string line) << " :r Reload all files\n" << " :s Build dependencies of derivation, then start nix-shell\n" << " :t Describe result of evaluation\n" - << " :u Build derivation, then start nix-shell\n"; + << " :u Build derivation, then start nix-shell\n" + << " :doc Show documentation of a builtin function\n"; } else if (command == ":a" || command == ":add") { @@ -509,6 +510,24 @@ bool NixRepl::processLine(string line) else if (command == ":q" || command == ":quit") return false; + else if (command == ":doc") { + Value v; + evalString(arg, v); + if (v.type == tPrimOp || v.type == tPrimOpApp) { + auto v2 = &v; + while (v2->type == tPrimOpApp) + v2 = v2->primOpApp.left; + if (v2->primOp->doc) { + // FIXME: format markdown. + if (!v2->primOp->args.empty()) + std::cout << fmt("Arguments: %s\n\n", concatStringsSep(" ", v2->primOp->args)); + std::cout << trim(stripIndentation(v2->primOp->doc)) << "\n"; + } else + throw Error("builtin function '%s' does not have documentation", v2->primOp->name); + } else + throw Error("value does not have documentation"); + } + else if (command != "") throw Error("unknown command '%1%'", command);