about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2006-05-08T12·52+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2006-05-08T12·52+0000
commit5cabd47394a5bb3076f3f5b5a98425665cddef23 (patch)
treeb334853dc567f3b8c5fc56dcad1321478b6e1722
parent310e605995dc104bee29d330ac135e3e2bb82f97 (diff)
* Allow function argument default values to refer to other arguments
  of the function.  Implements NIX-45.

-rw-r--r--src/libexpr/eval.cc75
-rw-r--r--src/libutil/aterm-map.cc6
-rw-r--r--src/libutil/aterm-map.hh5
-rw-r--r--tests/lang/eval-fail-missing-arg.nix1
-rw-r--r--tests/lang/eval-fail-undeclared-arg.nix1
5 files changed, 50 insertions, 38 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index ee17c996c6f9..667961cd506a 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 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)
+        subs.set(i->key, i->value);
     
     /* Get the formal arguments. */
+    ATermVector defsUsed;
+    ATermList recAttrs = ATempty;
     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 */
+        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()));
+        }
     }
 
-    /* Get the actual arguments, and check that they match with the
-       formals. */
-    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));
-        subs.set(i->key, i->value);
+    /* 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));
     }
-
-    /* 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));
     
+    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 60092382a2f3..f400464378f5 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 6d13d7f9e0d1..078617a7d673 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 000000000000..c4be9797c534
--- /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 000000000000..cafdf1636272
--- /dev/null
+++ b/tests/lang/eval-fail-undeclared-arg.nix
@@ -0,0 +1 @@
+({x, z}: x + z) {x = "foo"; y = "bla"; z = "bar";}