about summary refs log tree commit diff
path: root/src/libexpr/eval.cc
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2006-10-16T15·55+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2006-10-16T15·55+0000
commitd7efd7639420f4c840cbfdfcbbb3c45292f3ac54 (patch)
treed48871893e6d3446b6298b0e5e612086233e3947 /src/libexpr/eval.cc
parent4c9aa821b985b8e334790a03497a56af3a021f3b (diff)
* Big cleanup of the semantics of paths, strings, contexts, string
  concatenation and string coercion.  This was a big mess (see
  e.g. NIX-67).  Contexts are now folded into strings, so that they
  don't cause evaluation errors when they're not expected.  The
  semantics of paths has been clarified (see nixexpr-ast.def).
  toString() and coerceToString() have been merged.

  Semantic change: paths are now copied to the store when they're in a
  concatenation (and in most other situations - that's the
  formalisation of the meaning of a path).  So

    "foo " + ./bla

  evaluates to "foo /nix/store/hash...-bla", not "foo
  /path/to/current-dir/bla".  This prevents accidental impurities, and
  is more consistent with the treatment of derivation outputs, e.g.,
  `"foo " + bla' where `bla' is a derivation.  (Here `bla' would be
  replaced by the output path of `bla'.)

Diffstat (limited to 'src/libexpr/eval.cc')
-rw-r--r--src/libexpr/eval.cc210
1 files changed, 115 insertions, 95 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 4f97c761ed..2bb29f871c 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -3,6 +3,7 @@
 #include "hash.hh"
 #include "util.hh"
 #include "store.hh"
+#include "derivations.hh"
 #include "nixexpr-ast.hh"
 
 
@@ -157,23 +158,24 @@ static Expr updateAttrs(Expr e1, Expr e2)
 }
 
 
-string evalString(EvalState & state, Expr e)
+string evalString(EvalState & state, Expr e, PathSet & context)
 {
     e = evalExpr(state, e);
-    ATerm s;
-    if (!matchStr(e, s))
+    string s;
+    if (!matchStr(e, s, context))
         throw TypeError(format("value is %1% while a string was expected") % showType(e));
-    return aterm2String(s);
+    return s;
 }
 
 
-Path evalPath(EvalState & state, Expr e)
+string evalStringNoCtx(EvalState & state, Expr e)
 {
-    e = evalExpr(state, e);
-    ATerm s;
-    if (!matchPath(e, s))
-        throw TypeError(format("value is %1% while a path was expected") % showType(e));
-    return aterm2String(s);
+    PathSet context;
+    string s = evalString(state, e, context);
+    if (!context.empty())
+        throw EvalError(format("the string `%1%' is not allowed to refer to a store path (such as `%2%')")
+            % s % *(context.begin()));
+    return s;
 }
 
 
@@ -206,76 +208,84 @@ ATermList evalList(EvalState & state, Expr e)
 }
 
 
-/* String concatenation and context nodes: in order to allow users to
-   write things like
-
-     "--with-freetype2-library=" + freetype + "/lib"
-
-   where `freetype' is a derivation, we automatically coerce
-   derivations into their output path (e.g.,
-   /nix/store/hashcode-freetype) in concatenations.  However, if we do
-   this naively, we could introduce an undeclared dependency: when the
-   string is used in another derivation, that derivation would not
-   have an explicitly dependency on `freetype' in its inputDrvs
-   field.  Thus `freetype' would not necessarily be built.
+static void flattenList(EvalState & state, Expr e, ATermList & result)
+{
+    ATermList es;
+    e = evalExpr(state, e);
+    if (matchList(e, es))
+        for (ATermIterator i(es); i; ++i)
+            flattenList(state, *i, result);
+    else
+        result = ATinsert(result, e);
+}
 
-   To prevent this, we wrap the string resulting from the
-   concatenation in a *context node*, like this:
 
-     Context([freetype],
-       Str("--with-freetype2-library=/nix/store/hashcode-freetype/lib"))
+ATermList flattenList(EvalState & state, Expr e)
+{
+    ATermList result = ATempty;
+    flattenList(state, e, result);
+    return ATreverse(result);
+}
 
-   Thus the context is the list of all derivations used in the
-   computation of a value.  These contexts are propagated through
-   further concatenations.  In processBinding() in primops.cc, context
-   nodes are unwrapped and added to inputDrvs.
 
-   !!! Should the ordering of the context list have a canonical form?
+string coerceToString(EvalState & state, Expr e, PathSet & context,
+    bool coerceMore, bool copyToStore)
+{
+    e = evalExpr(state, e);
 
-   !!! Contexts are not currently recognised in most places in the
-   evaluator. */
+    string s;
 
+    if (matchStr(e, s, context)) return s;
 
-/* Coerce a value to a string, keeping track of contexts. */
-string coerceToStringWithContext(EvalState & state,
-    ATermList & context, Expr e, bool & isPath)
-{
-    isPath = false;
-    
-    e = evalExpr(state, e);
+    ATerm s2;
+    if (matchPath(e, s2)) {
+        Path path(canonPath(aterm2String(s2)));
 
-    bool isWrapped = false;
-    ATermList es;
-    ATerm e2;
-    if (matchContext(e, es, e2)) {
-        isWrapped = true;
-        e = e2;
-        context = ATconcat(es, context);
-    }
-    
-    ATerm s;
-    if (matchStr(e, s)) return aterm2String(s);
-    
-    if (matchPath(e, s)) {
-        isPath = true;
-        Path path = aterm2String(s);
-        if (isInStore(path) && !isWrapped) {
-            context = ATinsert(context, makePath(toATerm(toStorePath(path))));
+        if (!copyToStore) return path;
+        
+        if (isDerivation(path))
+            throw EvalError(format("file names are not allowed to end in `%1%'")
+                % drvExtension);
+
+        Path dstPath;
+        if (state.srcToStore[path] != "")
+            dstPath = state.srcToStore[path];
+        else {
+            dstPath = addToStore(path);
+            state.srcToStore[path] = dstPath;
+            printMsg(lvlChatty, format("copied source `%1%' -> `%2%'")
+                % path % dstPath);
         }
-        return path;
-    }
 
-    if (matchAttrs(e, es)) {
-        ATermMap attrs(128); /* !!! */
-        queryAllAttrs(e, attrs, false);
-
-        Expr a = attrs.get(toATerm("type"));
-        if (a && evalString(state, a) == "derivation") {
-            a = attrs.get(toATerm("outPath"));
-            if (!a) throw TypeError("output path missing from derivation");
-            isPath = true;
-            context = ATinsert(context, e);
-            return evalPath(state, a);
+        context.insert(dstPath);
+        return dstPath;
+    }
+        
+    ATermList es;
+    if (matchAttrs(e, es))
+        return coerceToString(state, makeSelect(e, toATerm("outPath")),
+            context, coerceMore, copyToStore);
+
+    if (coerceMore) {
+
+        /* Note that `false' is represented as an empty string for
+           shell scripting convenience, just like `null'. */
+        if (e == eTrue) return "1";
+        if (e == eFalse) return "";
+        int n;
+        if (matchInt(e, n)) return int2String(n);
+        if (matchNull(e)) return "";
+
+        if (matchList(e, es)) {
+            string result;
+            es = flattenList(state, e);
+            bool first = true;
+            for (ATermIterator i(es); i; ++i) {
+                if (!first) result += " "; else first = false;
+                result += coerceToString(state, *i,
+                    context, coerceMore, copyToStore);
+            }
+            return result;
         }
     }
     
@@ -283,31 +293,41 @@ string coerceToStringWithContext(EvalState & state,
 }
 
 
-/* Wrap an expression in a context if the context is not empty. */
-Expr wrapInContext(ATermList context, Expr e)
-{
-    return context == ATempty ? e : makeContext(context, e);
-}
-
-
-static ATerm concatStrings(EvalState & state, const ATermVector & args)
+/* Common implementation of `+', ConcatStrings and `~'. */
+static ATerm concatStrings(EvalState & state, const ATermVector & args,
+    string separator = "")
 {
-    ATermList context = ATempty;
+    PathSet context;
     std::ostringstream s;
-    bool isPath = false;
 
-    /* Note that if the first argument in the concatenation is a path,
-       then the result is also a path. */
+    /* If the first element is a path, then the result will also be a
+       path, we don't copy anything (yet - that's done later, since
+       paths are copied when they are used in a derivation), and none
+       of the strings are allowed to have contexts. */
+    ATerm dummy;
+    bool isPath = !args.empty() && matchPath(args.front(), dummy);
 
     for (ATermVector::const_iterator i = args.begin(); i != args.end(); ++i) {
-        bool isPath2;
-        s << coerceToStringWithContext(state, context, *i, isPath2);
-        if (i == args.begin()) isPath = isPath2;
+        if (i != args.begin()) s << separator;
+        s << coerceToString(state, *i, context, false, !isPath);
     }
 
-    return wrapInContext(context, isPath
-        ? makePath(toATerm(canonPath(s.str())))
-        : makeStr(toATerm(s.str())));
+    if (isPath && !context.empty())
+        throw EvalError(format("a string that refers to a store path cannot be appended to a path, in `%1%'")
+            % s.str());
+    
+    return isPath
+        ? makePath(toATerm(s.str()))
+        : makeStr(s.str(), context);
+}
+
+
+Path coerceToPath(EvalState & state, Expr e, PathSet & context)
+{
+    string path = coerceToString(state, e, context, false, false);
+    if (path == "" || path[0] != '/')
+        throw EvalError(format("string `%1%' doesn't represent an absolute path") % path);
+    return path;
 }
 
 
@@ -352,8 +372,7 @@ Expr evalExpr2(EvalState & state, Expr e)
         sym == symFunction1 ||
         sym == symAttrs ||
         sym == symList ||
-        sym == symPrimOp ||
-        sym == symContext)
+        sym == symPrimOp)
         return e;
     
     /* The `Closed' constructor is just a way to prevent substitutions
@@ -371,6 +390,7 @@ Expr evalExpr2(EvalState & state, Expr e)
         ATermBlob fun;
         if (!matchPrimOpDef(primOp, arity, fun)) abort();
         if (arity == 0)
+            /* !!! backtrace for primop call */
             return ((PrimOp) ATgetBlobData(fun)) (state, ATermVector());
         else
             return makePrimOp(arity, fun, ATempty);
@@ -397,6 +417,7 @@ Expr evalExpr2(EvalState & state, Expr e)
                 ATermVector args2(arity);
                 for (ATermIterator i(args); i; ++i)
                     args2[--arity] = *i;
+                /* !!! backtrace for primop call */
                 return ((PrimOp) ATgetBlobData((ATermBlob) fun))
                     (state, args2);
             } else
@@ -550,11 +571,10 @@ Expr evalExpr2(EvalState & state, Expr e)
     if (matchSubPath(e, e1, e2)) {
         static bool haveWarned = false;
         warnOnce(haveWarned, "the subpath operator (~) is deprecated, use string concatenation (+) instead");
-        ATermList context = ATempty;
-        bool dummy;
-        string s1 = coerceToStringWithContext(state, context, e1, dummy);
-        string s2 = coerceToStringWithContext(state, context, e2, dummy);
-        return wrapInContext(context, makePath(toATerm(canonPath(s1 + "/" + s2))));
+        ATermVector args;
+        args.push_back(e1);
+        args.push_back(e2);
+        return concatStrings(state, args, "/");
     }
 
     /* List concatenation. */