From 5cabd47394a5bb3076f3f5b5a98425665cddef23 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 8 May 2006 12:52:47 +0000 Subject: [PATCH] * Allow function argument default values to refer to other arguments of the function. Implements NIX-45. --- src/libexpr/eval.cc | 77 +++++++++++++------------ src/libutil/aterm-map.cc | 6 +- src/libutil/aterm-map.hh | 5 ++ tests/lang/eval-fail-missing-arg.nix | 1 + tests/lang/eval-fail-undeclared-arg.nix | 1 + 5 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 tests/lang/eval-fail-missing-arg.nix create mode 100644 tests/lang/eval-fail-undeclared-arg.nix diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index ee17c996c6..667961cd50 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -24,49 +24,54 @@ void EvalState::addPrimOp(const string & name, /* Substitute an argument set into the body of a function. */ static Expr substArgs(Expr body, ATermList formals, Expr arg) { - ATermMap subs(ATgetLength(formals) * 2); + unsigned int nrFormals = ATgetLength(formals); + ATermMap subs(nrFormals); - /* ({x ? E1; y ? E2, z}: E3) {x = E4; z = E5;} - - => let {x = E4; y = E2; z = E5; body = E3; } - - => subst(E3, s) - s = { - R = rec {x = E4; y = E2; z = E5} - x -> R.x - y -> R.y - z -> R.z - } - */ - - /* Get the formal arguments. */ - for (ATermIterator i(formals); i; ++i) { - Expr name, def; - if (matchNoDefFormal(*i, name)) - subs.set(name, makeUndefined()); - else if (matchDefFormal(*i, name, def)) - subs.set(name, def); - else abort(); /* can't happen */ - } - - /* Get the actual arguments, and check that they match with the - formals. */ + /* Get the actual arguments and put them in the substitution. */ ATermMap args(128); /* !!! fix */ queryAllAttrs(arg, args); - for (ATermMap::const_iterator i = args.begin(); i != args.end(); ++i) { - Expr cur = subs.get(i->key); - if (!subs.get(i->key)) - throw Error(format("unexpected function argument `%1%'") - % aterm2String(i->key)); + for (ATermMap::const_iterator i = args.begin(); i != args.end(); ++i) subs.set(i->key, i->value); + + /* Get the formal arguments. */ + ATermVector defsUsed; + ATermList recAttrs = ATempty; + for (ATermIterator i(formals); i; ++i) { + Expr name, def = 0; + if (!matchNoDefFormal(*i, name) && !matchDefFormal(*i, name, def)) + abort(); /* can't happen */ + if (subs[name] == 0) { + if (def == 0) throw Error(format("required function argument `%1%' missing") + % aterm2String(name)); + defsUsed.push_back(name); + recAttrs = ATinsert(recAttrs, makeBind(name, def, makeNoPos())); + } } - /* Check that all arguments are defined. */ - for (ATermMap::const_iterator i = subs.begin(); i != subs.end(); ++i) - if (i->value == makeUndefined()) - throw Error(format("required function argument `%1%' missing") - % aterm2String(i->key)); + /* Make a recursive attribute set out of the (argument-name, + value) tuples. This is so that we can support default + parameters that refer to each other, e.g. ({x, y ? x + x}: y) + {x = "foo";} evaluates to "foofoo". */ + if (defsUsed.size() != 0) { + for (ATermMap::const_iterator i = args.begin(); i != args.end(); ++i) + recAttrs = ATinsert(recAttrs, makeBind(i->key, i->value, makeNoPos())); + Expr rec = makeRec(recAttrs, ATempty); + for (ATermVector::iterator i = defsUsed.begin(); i != defsUsed.end(); ++i) + subs.set(*i, makeSelect(rec, *i)); + } + if (subs.size() != nrFormals) { + /* One or more actual arguments were not declared as formal + arguments. Find out which. */ + for (ATermIterator i(formals); i; ++i) { + Expr name, def; + matchNoDefFormal(*i, name) || matchDefFormal(*i, name, def); + subs.remove(name); + } + throw Error(format("unexpected function argument `%1%'") + % aterm2String(subs.begin()->key)); + } + return substitute(Substitution(0, &subs), body); } diff --git a/src/libutil/aterm-map.cc b/src/libutil/aterm-map.cc index 60092382a2..f400464378 100644 --- a/src/libutil/aterm-map.cc +++ b/src/libutil/aterm-map.cc @@ -217,17 +217,17 @@ unsigned int ATermMap::size() void printATermMapStats() { - cout << "RESIZES: " << nrResizes << " " + cerr << "RESIZES: " << nrResizes << " " << sizeTotalAlloc << " " << sizeCurAlloc << " " << sizeMaxAlloc << endl; - cout << "SET: " + cerr << "SET: " << nrItemsSet << " " << nrSetProbes << " " << (double) nrSetProbes / nrItemsSet << endl; - cout << "GET: " + cerr << "GET: " << nrItemsGet << " " << nrGetProbes << " " << (double) nrGetProbes / nrItemsGet << endl; diff --git a/src/libutil/aterm-map.hh b/src/libutil/aterm-map.hh index 6d13d7f9e0..078617a7d6 100644 --- a/src/libutil/aterm-map.hh +++ b/src/libutil/aterm-map.hh @@ -50,6 +50,11 @@ public: ATerm get(ATerm key) const; + ATerm operator [](ATerm key) const + { + return get(key); + } + void remove(ATerm key); unsigned int size(); diff --git a/tests/lang/eval-fail-missing-arg.nix b/tests/lang/eval-fail-missing-arg.nix new file mode 100644 index 0000000000..c4be9797c5 --- /dev/null +++ b/tests/lang/eval-fail-missing-arg.nix @@ -0,0 +1 @@ +({x, y, z}: x + y + z) {x = "foo"; z = "bar";} diff --git a/tests/lang/eval-fail-undeclared-arg.nix b/tests/lang/eval-fail-undeclared-arg.nix new file mode 100644 index 0000000000..cafdf16362 --- /dev/null +++ b/tests/lang/eval-fail-undeclared-arg.nix @@ -0,0 +1 @@ +({x, z}: x + z) {x = "foo"; y = "bla"; z = "bar";}