about summary refs log tree commit diff
path: root/src/libexpr/primops.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr/primops.cc')
-rw-r--r--src/libexpr/primops.cc697
1 files changed, 376 insertions, 321 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 01806a6c9493..6b9350cf87cd 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -17,6 +17,11 @@
 namespace nix {
 
 
+/*************************************************************
+ * Constants
+ *************************************************************/
+
+
 static Expr primBuiltins(EvalState & state, const ATermVector & args)
 {
     /* Return an attribute set containing all primops.  This allows
@@ -41,6 +46,47 @@ static Expr primBuiltins(EvalState & state, const ATermVector & args)
 }
 
 
+/* Boolean constructors. */
+static Expr primTrue(EvalState & state, const ATermVector & args)
+{
+    return eTrue;
+}
+
+
+static Expr primFalse(EvalState & state, const ATermVector & args)
+{
+    return eFalse;
+}
+
+
+/* Return the null value. */
+static Expr primNull(EvalState & state, const ATermVector & args)
+{
+    return makeNull();
+}
+
+
+/* Return a string constant representing the current platform.  Note!
+   that differs between platforms, so Nix expressions using
+   `__currentSystem' can evaluate to different values on different
+   platforms. */
+static Expr primCurrentSystem(EvalState & state, const ATermVector & args)
+{
+    return makeStr(thisSystem);
+}
+
+
+static Expr primCurrentTime(EvalState & state, const ATermVector & args)
+{
+    return ATmake("Int(<int>)", time(0));
+}
+
+
+/*************************************************************
+ * Miscellaneous
+ *************************************************************/
+
+
 /* Load and evaluate an expression from path specified by the
    argument. */ 
 static Expr primImport(EvalState & state, const ATermVector & args)
@@ -61,16 +107,201 @@ static Expr primImport(EvalState & state, const ATermVector & args)
 }
 
 
-static Expr primPathExists(EvalState & state, const ATermVector & args)
+/* Convert the argument to a string.  Paths are *not* copied to the
+   store, so `toString /foo/bar' yields `"/foo/bar"', not
+   `"/nix/store/whatever..."'. */
+static Expr primToString(EvalState & state, const ATermVector & args)
 {
     PathSet context;
-    Path path = coerceToPath(state, args[0], context);
-    if (!context.empty())
-        throw EvalError(format("string `%1%' cannot refer to other paths") % path);
-    return makeBool(pathExists(path));
+    string s = coerceToString(state, args[0], context, true, false);
+    return makeStr(s, context);
+}
+
+
+/* Determine whether the argument is the null value. */
+static Expr primIsNull(EvalState & state, const ATermVector & args)
+{
+    return makeBool(matchNull(evalExpr(state, args[0])));
+}
+
+
+static Path findDependency(Path dir, string dep)
+{
+    if (dep[0] == '/') throw EvalError(
+        format("illegal absolute dependency `%1%'") % dep);
+
+    Path p = canonPath(dir + "/" + dep);
+
+    if (pathExists(p))
+        return p;
+    else
+        return "";
+}
+
+
+/* Make path `p' relative to directory `pivot'.  E.g.,
+   relativise("/a/b/c", "a/b/x/y") => "../x/y".  Both input paths
+   should be in absolute canonical form. */
+static string relativise(Path pivot, Path p)
+{
+    assert(pivot.size() > 0 && pivot[0] == '/');
+    assert(p.size() > 0 && p[0] == '/');
+        
+    if (pivot == p) return ".";
+
+    /* `p' is in `pivot'? */
+    Path pivot2 = pivot + "/";
+    if (p.substr(0, pivot2.size()) == pivot2) {
+        return p.substr(pivot2.size());
+    }
+
+    /* Otherwise, `p' is in a parent of `pivot'.  Find up till which
+       path component `p' and `pivot' match, and add an appropriate
+       number of `..' components. */
+    string::size_type i = 1;
+    while (1) {
+        string::size_type j = pivot.find('/', i);
+        if (j == string::npos) break;
+        j++;
+        if (pivot.substr(0, j) != p.substr(0, j)) break;
+        i = j;
+    }
+
+    string prefix;
+    unsigned int slashes = count(pivot.begin() + i, pivot.end(), '/') + 1;
+    while (slashes--) {
+        prefix += "../";
+    }
+
+    return prefix + p.substr(i);
+}
+
+
+static Expr primDependencyClosure(EvalState & state, const ATermVector & args)
+{
+    startNest(nest, lvlDebug, "finding dependencies");
+
+    Expr attrs = evalExpr(state, args[0]);
+
+    /* Get the start set. */
+    Expr startSet = queryAttr(attrs, "startSet");
+    if (!startSet) throw EvalError("attribute `startSet' required");
+    ATermList startSet2 = evalList(state, startSet);
+
+    Path pivot;
+    PathSet workSet;
+    for (ATermIterator i(startSet2); i; ++i) {
+        PathSet context; /* !!! what to do? */
+        Path p = coerceToPath(state, *i, context);
+        workSet.insert(p);
+        pivot = dirOf(p);
+    }
+
+    /* Get the search path. */
+    PathSet searchPath;
+    Expr e = queryAttr(attrs, "searchPath");
+    if (e) {
+        ATermList list = evalList(state, e);
+        for (ATermIterator i(list); i; ++i) {
+            PathSet context; /* !!! what to do? */
+            Path p = coerceToPath(state, *i, context);
+            searchPath.insert(p);
+        }
+    }
+
+    Expr scanner = queryAttr(attrs, "scanner");
+    if (!scanner) throw EvalError("attribute `scanner' required");
+    
+    /* Construct the dependency closure by querying the dependency of
+       each path in `workSet', adding the dependencies to
+       `workSet'. */
+    PathSet doneSet;
+    while (!workSet.empty()) {
+	Path path = *(workSet.begin());
+	workSet.erase(path);
+
+	if (doneSet.find(path) != doneSet.end()) continue;
+        doneSet.insert(path);
+
+        try {
+            
+            /* Call the `scanner' function with `path' as argument. */
+            debug(format("finding dependencies in `%1%'") % path);
+            ATermList deps = evalList(state, makeCall(scanner, makeStr(path)));
+
+            /* Try to find the dependencies relative to the `path'. */
+            for (ATermIterator i(deps); i; ++i) {
+                string s = evalStringNoCtx(state, *i);
+                
+                Path dep = findDependency(dirOf(path), s);
+
+                if (dep == "") {
+                    for (PathSet::iterator j = searchPath.begin();
+                         j != searchPath.end(); ++j)
+                    {
+                        dep = findDependency(*j, s);
+                        if (dep != "") break;
+                    }
+                }
+                
+                if (dep == "")
+                    debug(format("did NOT find dependency `%1%'") % s);
+                else {
+                    debug(format("found dependency `%1%'") % dep);
+                    workSet.insert(dep);
+                }
+            }
+
+        } catch (Error & e) {
+            e.addPrefix(format("while finding dependencies in `%1%':\n")
+                % path);
+            throw;
+        }
+    }
+
+    /* Return a list of the dependencies we've just found. */
+    ATermList deps = ATempty;
+    for (PathSet::iterator i = doneSet.begin(); i != doneSet.end(); ++i) {
+        deps = ATinsert(deps, makeStr(relativise(pivot, *i)));
+        deps = ATinsert(deps, makeStr(*i));
+    }
+
+    debug(format("dependency list is `%1%'") % makeList(deps));
+    
+    return makeList(deps);
 }
 
 
+static Expr primAbort(EvalState & state, const ATermVector & args)
+{
+    PathSet context;
+    throw Abort(format("evaluation aborted with the following error message: `%1%'") %
+        evalString(state, args[0], context));
+}
+
+
+/* Return an environment variable.  Use with care. */
+static Expr primGetEnv(EvalState & state, const ATermVector & args)
+{
+    string name = evalStringNoCtx(state, args[0]);
+    return makeStr(getEnv(name));
+}
+
+
+static Expr primRelativise(EvalState & state, const ATermVector & args)
+{
+    PathSet context; /* !!! what to do? */
+    Path pivot = coerceToPath(state, args[0], context);
+    Path path = coerceToPath(state, args[1], context);
+    return makeStr(relativise(pivot, path));
+}
+
+
+/*************************************************************
+ * Derivations
+ *************************************************************/
+
+
 /* Returns the hash of a derivation modulo fixed-output
    subderivations.  A fixed-output derivation is a derivation with one
    output (`out') for which an expected hash and hash algorithm are
@@ -324,6 +555,30 @@ static Expr primDerivationLazy(EvalState & state, const ATermVector & args)
 }
 
 
+/*************************************************************
+ * Paths
+ *************************************************************/
+
+
+/* Convert the argument to a path.  !!! obsolete? */
+static Expr primToPath(EvalState & state, const ATermVector & args)
+{
+    PathSet context;
+    string path = coerceToPath(state, args[0], context);
+    return makeStr(canonPath(path), context);
+}
+
+
+static Expr primPathExists(EvalState & state, const ATermVector & args)
+{
+    PathSet context;
+    Path path = coerceToPath(state, args[0], context);
+    if (!context.empty())
+        throw EvalError(format("string `%1%' cannot refer to other paths") % path);
+    return makeBool(pathExists(path));
+}
+
+
 /* Return the base name of the given string, i.e., everything
    following the last slash. */
 static Expr primBaseNameOf(EvalState & state, const ATermVector & args)
@@ -346,24 +601,9 @@ static Expr primDirOf(EvalState & state, const ATermVector & args)
 }
 
 
-/* Convert the argument to a string.  Paths are *not* copied to the
-   store, so `toString /foo/bar' yields `"/foo/bar"', not
-   `"/nix/store/whatever..."'. */
-static Expr primToString(EvalState & state, const ATermVector & args)
-{
-    PathSet context;
-    string s = coerceToString(state, args[0], context, true, false);
-    return makeStr(s, context);
-}
-
-
-/* Convert the argument to a path.  !!! obsolete? */
-static Expr primToPath(EvalState & state, const ATermVector & args)
-{
-    PathSet context;
-    string path = coerceToPath(state, args[0], context);
-    return makeStr(canonPath(path), context);
-}
+/*************************************************************
+ * Creating files
+ *************************************************************/
 
 
 /* Convert the argument (which can be any Nix expression) to an XML
@@ -406,193 +646,120 @@ static Expr primToFile(EvalState & state, const ATermVector & args)
 }
 
 
-/* Boolean constructors. */
-static Expr primTrue(EvalState & state, const ATermVector & args)
+struct FilterFromExpr : PathFilter
 {
-    return eTrue;
-}
+    EvalState & state;
+    Expr filter;
+    
+    FilterFromExpr(EvalState & state, Expr filter)
+        : state(state), filter(filter)
+    {
+    }
 
+    bool operator () (const Path & path)
+    {
+        struct stat st;
+        if (lstat(path.c_str(), &st))
+            throw SysError(format("getting attributes of path `%1%'") % path);
 
-static Expr primFalse(EvalState & state, const ATermVector & args)
-{
-    return eFalse;
-}
+        Expr call =
+            makeCall(
+                makeCall(filter, makeStr(path)),
+                makeStr(
+                    S_ISREG(st.st_mode) ? "regular" :
+                    S_ISDIR(st.st_mode) ? "directory" :
+                    S_ISLNK(st.st_mode) ? "symlink" :
+                    "unknown" /* not supported, will fail! */
+                    ));
+                
+        return evalBool(state, call);
+    }
+};
 
 
-/* Return the null value. */
-static Expr primNull(EvalState & state, const ATermVector & args)
+static Expr primFilterSource(EvalState & state, const ATermVector & args)
 {
-    return makeNull();
-}
-
+    PathSet context;
+    Path path = coerceToPath(state, args[1], context);
+    if (!context.empty())
+        throw EvalError(format("string `%1%' cannot refer to other paths") % path);
 
-/* Determine whether the argument is the null value. */
-static Expr primIsNull(EvalState & state, const ATermVector & args)
-{
-    return makeBool(matchNull(evalExpr(state, args[0])));
-}
+    FilterFromExpr filter(state, args[0]);
 
+    Path dstPath = readOnlyMode
+        ? computeStorePathForPath(path, false, false, "", filter).first
+        : store->addToStore(path, false, false, "", filter);
 
-/* Determine whether the argument is a list. */
-static Expr primIsList(EvalState & state, const ATermVector & args)
-{
-    ATermList list;
-    return makeBool(matchList(evalExpr(state, args[0]), list));
+    return makeStr(dstPath, singleton<PathSet>(dstPath));
 }
 
 
-static Path findDependency(Path dir, string dep)
-{
-    if (dep[0] == '/') throw EvalError(
-        format("illegal absolute dependency `%1%'") % dep);
-
-    Path p = canonPath(dir + "/" + dep);
-
-    if (pathExists(p))
-        return p;
-    else
-        return "";
-}
+/*************************************************************
+ * Attribute sets
+ *************************************************************/
 
 
-/* Make path `p' relative to directory `pivot'.  E.g.,
-   relativise("/a/b/c", "a/b/x/y") => "../x/y".  Both input paths
-   should be in absolute canonical form. */
-static string relativise(Path pivot, Path p)
+/* Return the names of the attributes in an attribute set as a sorted
+   list of strings. */
+static Expr primAttrNames(EvalState & state, const ATermVector & args)
 {
-    assert(pivot.size() > 0 && pivot[0] == '/');
-    assert(p.size() > 0 && p[0] == '/');
-        
-    if (pivot == p) return ".";
-
-    /* `p' is in `pivot'? */
-    Path pivot2 = pivot + "/";
-    if (p.substr(0, pivot2.size()) == pivot2) {
-        return p.substr(pivot2.size());
-    }
+    ATermMap attrs;
+    queryAllAttrs(evalExpr(state, args[0]), attrs);
 
-    /* Otherwise, `p' is in a parent of `pivot'.  Find up till which
-       path component `p' and `pivot' match, and add an appropriate
-       number of `..' components. */
-    string::size_type i = 1;
-    while (1) {
-        string::size_type j = pivot.find('/', i);
-        if (j == string::npos) break;
-        j++;
-        if (pivot.substr(0, j) != p.substr(0, j)) break;
-        i = j;
-    }
+    StringSet names;
+    for (ATermMap::const_iterator i = attrs.begin(); i != attrs.end(); ++i)
+        names.insert(aterm2String(i->key));
 
-    string prefix;
-    unsigned int slashes = count(pivot.begin() + i, pivot.end(), '/') + 1;
-    while (slashes--) {
-        prefix += "../";
-    }
+    ATermList list = ATempty;
+    for (StringSet::const_reverse_iterator i = names.rbegin();
+         i != names.rend(); ++i)
+        list = ATinsert(list, makeStr(*i, PathSet()));
 
-    return prefix + p.substr(i);
+    return makeList(list);
 }
 
 
-static Expr primDependencyClosure(EvalState & state, const ATermVector & args)
+/* Dynamic version of the `.' operator. */
+static Expr primGetAttr(EvalState & state, const ATermVector & args)
 {
-    startNest(nest, lvlDebug, "finding dependencies");
-
-    Expr attrs = evalExpr(state, args[0]);
+    string attr = evalStringNoCtx(state, args[0]);
+    return evalExpr(state, makeSelect(args[1], toATerm(attr)));
+}
 
-    /* Get the start set. */
-    Expr startSet = queryAttr(attrs, "startSet");
-    if (!startSet) throw EvalError("attribute `startSet' required");
-    ATermList startSet2 = evalList(state, startSet);
 
-    Path pivot;
-    PathSet workSet;
-    for (ATermIterator i(startSet2); i; ++i) {
-        PathSet context; /* !!! what to do? */
-        Path p = coerceToPath(state, *i, context);
-        workSet.insert(p);
-        pivot = dirOf(p);
-    }
+/* Dynamic version of the `?' operator. */
+static Expr primHasAttr(EvalState & state, const ATermVector & args)
+{
+    string attr = evalStringNoCtx(state, args[0]);
+    return evalExpr(state, makeOpHasAttr(args[1], toATerm(attr)));
+}
 
-    /* Get the search path. */
-    PathSet searchPath;
-    Expr e = queryAttr(attrs, "searchPath");
-    if (e) {
-        ATermList list = evalList(state, e);
-        for (ATermIterator i(list); i; ++i) {
-            PathSet context; /* !!! what to do? */
-            Path p = coerceToPath(state, *i, context);
-            searchPath.insert(p);
-        }
-    }
 
-    Expr scanner = queryAttr(attrs, "scanner");
-    if (!scanner) throw EvalError("attribute `scanner' required");
+static Expr primRemoveAttrs(EvalState & state, const ATermVector & args)
+{
+    ATermMap attrs;
+    queryAllAttrs(evalExpr(state, args[0]), attrs, true);
     
-    /* Construct the dependency closure by querying the dependency of
-       each path in `workSet', adding the dependencies to
-       `workSet'. */
-    PathSet doneSet;
-    while (!workSet.empty()) {
-	Path path = *(workSet.begin());
-	workSet.erase(path);
-
-	if (doneSet.find(path) != doneSet.end()) continue;
-        doneSet.insert(path);
-
-        try {
-            
-            /* Call the `scanner' function with `path' as argument. */
-            debug(format("finding dependencies in `%1%'") % path);
-            ATermList deps = evalList(state, makeCall(scanner, makeStr(path)));
-
-            /* Try to find the dependencies relative to the `path'. */
-            for (ATermIterator i(deps); i; ++i) {
-                string s = evalStringNoCtx(state, *i);
-                
-                Path dep = findDependency(dirOf(path), s);
+    ATermList list = evalList(state, args[1]);
 
-                if (dep == "") {
-                    for (PathSet::iterator j = searchPath.begin();
-                         j != searchPath.end(); ++j)
-                    {
-                        dep = findDependency(*j, s);
-                        if (dep != "") break;
-                    }
-                }
-                
-                if (dep == "")
-                    debug(format("did NOT find dependency `%1%'") % s);
-                else {
-                    debug(format("found dependency `%1%'") % dep);
-                    workSet.insert(dep);
-                }
-            }
+    for (ATermIterator i(list); i; ++i)
+        /* It's not an error for *i not to exist. */
+        attrs.remove(toATerm(evalStringNoCtx(state, *i)));
 
-        } catch (Error & e) {
-            e.addPrefix(format("while finding dependencies in `%1%':\n")
-                % path);
-            throw;
-        }
-    }
+    return makeAttrs(attrs);
+}
 
-    /* Return a list of the dependencies we've just found. */
-    ATermList deps = ATempty;
-    for (PathSet::iterator i = doneSet.begin(); i != doneSet.end(); ++i) {
-        deps = ATinsert(deps, makeStr(relativise(pivot, *i)));
-        deps = ATinsert(deps, makeStr(*i));
-    }
 
-    debug(format("dependency list is `%1%'") % makeList(deps));
-    
-    return makeList(deps);
-}
+/*************************************************************
+ * Lists
+ *************************************************************/
 
 
-static Expr primAbort(EvalState & state, const ATermVector & args)
+/* Determine whether the argument is a list. */
+static Expr primIsList(EvalState & state, const ATermVector & args)
 {
-    PathSet context;
-    throw Abort(format("evaluation aborted with the following error message: `%1%'") %
-        evalString(state, args[0], context));
+    ATermList list;
+    return makeBool(matchList(evalExpr(state, args[0]), list));
 }
 
 
@@ -617,34 +784,6 @@ static Expr primTail(EvalState & state, const ATermVector & args)
 }
 
 
-/* Return an environment variable.  Use with care. */
-static Expr primGetEnv(EvalState & state, const ATermVector & args)
-{
-    string name = evalStringNoCtx(state, args[0]);
-    return makeStr(getEnv(name));
-}
-
-
-/* Return the names of the attributes in an attribute set as a sorted
-   list of strings. */
-static Expr primAttrNames(EvalState & state, const ATermVector & args)
-{
-    ATermMap attrs;
-    queryAllAttrs(evalExpr(state, args[0]), attrs);
-
-    StringSet names;
-    for (ATermMap::const_iterator i = attrs.begin(); i != attrs.end(); ++i)
-        names.insert(aterm2String(i->key));
-
-    ATermList list = ATempty;
-    for (StringSet::const_reverse_iterator i = names.rbegin();
-         i != names.rend(); ++i)
-        list = ATinsert(list, makeStr(*i, PathSet()));
-
-    return makeList(list);
-}
-
-
 /* Apply a function to every element of a list. */
 static Expr primMap(EvalState & state, const ATermVector & args)
 {
@@ -659,60 +798,9 @@ static Expr primMap(EvalState & state, const ATermVector & args)
 }
 
 
-/* Return a string constant representing the current platform.  Note!
-   that differs between platforms, so Nix expressions using
-   `__currentSystem' can evaluate to different values on different
-   platforms. */
-static Expr primCurrentSystem(EvalState & state, const ATermVector & args)
-{
-    return makeStr(thisSystem);
-}
-
-
-static Expr primCurrentTime(EvalState & state, const ATermVector & args)
-{
-    return ATmake("Int(<int>)", time(0));
-}
-
-
-/* Dynamic version of the `.' operator. */
-static Expr primGetAttr(EvalState & state, const ATermVector & args)
-{
-    string attr = evalStringNoCtx(state, args[0]);
-    return evalExpr(state, makeSelect(args[1], toATerm(attr)));
-}
-
-
-/* Dynamic version of the `?' operator. */
-static Expr primHasAttr(EvalState & state, const ATermVector & args)
-{
-    string attr = evalStringNoCtx(state, args[0]);
-    return evalExpr(state, makeOpHasAttr(args[1], toATerm(attr)));
-}
-
-
-static Expr primRemoveAttrs(EvalState & state, const ATermVector & args)
-{
-    ATermMap attrs;
-    queryAllAttrs(evalExpr(state, args[0]), attrs, true);
-    
-    ATermList list = evalList(state, args[1]);
-
-    for (ATermIterator i(list); i; ++i)
-        /* It's not an error for *i not to exist. */
-        attrs.remove(toATerm(evalStringNoCtx(state, *i)));
-
-    return makeAttrs(attrs);
-}
-
-
-static Expr primRelativise(EvalState & state, const ATermVector & args)
-{
-    PathSet context; /* !!! what to do? */
-    Path pivot = coerceToPath(state, args[0], context);
-    Path path = coerceToPath(state, args[1], context);
-    return makeStr(relativise(pivot, path));
-}
+/*************************************************************
+ * Integer arithmetic
+ *************************************************************/
 
 
 static Expr primAdd(EvalState & state, const ATermVector & args)
@@ -739,54 +827,6 @@ static Expr primLessThan(EvalState & state, const ATermVector & args)
 }
 
 
-struct FilterFromExpr : PathFilter
-{
-    EvalState & state;
-    Expr filter;
-    
-    FilterFromExpr(EvalState & state, Expr filter)
-        : state(state), filter(filter)
-    {
-    }
-
-    bool operator () (const Path & path)
-    {
-        struct stat st;
-        if (lstat(path.c_str(), &st))
-            throw SysError(format("getting attributes of path `%1%'") % path);
-
-        Expr call =
-            makeCall(
-                makeCall(filter, makeStr(path)),
-                makeStr(
-                    S_ISREG(st.st_mode) ? "regular" :
-                    S_ISDIR(st.st_mode) ? "directory" :
-                    S_ISLNK(st.st_mode) ? "symlink" :
-                    "unknown" /* not supported, will fail! */
-                    ));
-                
-        return evalBool(state, call);
-    }
-};
-
-
-static Expr primFilterSource(EvalState & state, const ATermVector & args)
-{
-    PathSet context;
-    Path path = coerceToPath(state, args[1], context);
-    if (!context.empty())
-        throw EvalError(format("string `%1%' cannot refer to other paths") % path);
-
-    FilterFromExpr filter(state, args[0]);
-
-    Path dstPath = readOnlyMode
-        ? computeStorePathForPath(path, false, false, "", filter).first
-        : store->addToStore(path, false, false, "", filter);
-
-    return makeStr(dstPath, singleton<PathSet>(dstPath));
-}
-
-
 /*************************************************************
  * String manipulation
  *************************************************************/
@@ -821,41 +861,56 @@ void EvalState::addPrimOps()
 {
     addPrimOp("builtins", 0, primBuiltins);
         
+    // Constants
     addPrimOp("true", 0, primTrue);
     addPrimOp("false", 0, primFalse);
     addPrimOp("null", 0, primNull);
     addPrimOp("__currentSystem", 0, primCurrentSystem);
     addPrimOp("__currentTime", 0, primCurrentTime);
 
+    // Miscellaneous
     addPrimOp("import", 1, primImport);
-    addPrimOp("__pathExists", 1, primPathExists);
-    addPrimOp("derivation!", 1, primDerivationStrict);
-    addPrimOp("derivation", 1, primDerivationLazy);
-    addPrimOp("baseNameOf", 1, primBaseNameOf);
-    addPrimOp("dirOf", 1, primDirOf);
     addPrimOp("toString", 1, primToString);
-    addPrimOp("__toPath", 1, primToPath);
-    addPrimOp("__toXML", 1, primToXML);
     addPrimOp("isNull", 1, primIsNull);
-    addPrimOp("__isList", 1, primIsList);
     addPrimOp("dependencyClosure", 1, primDependencyClosure);
     addPrimOp("abort", 1, primAbort);
-    addPrimOp("__head", 1, primHead);
-    addPrimOp("__tail", 1, primTail);
     addPrimOp("__getEnv", 1, primGetEnv);
-    addPrimOp("__attrNames", 1, primAttrNames);
 
-    addPrimOp("map", 2, primMap);
+    addPrimOp("relativise", 2, primRelativise);
+
+    // Derivations
+    addPrimOp("derivation!", 1, primDerivationStrict);
+    addPrimOp("derivation", 1, primDerivationLazy);
+
+    // Paths
+    addPrimOp("__toPath", 1, primToPath);
+    addPrimOp("__pathExists", 1, primPathExists);
+    addPrimOp("baseNameOf", 1, primBaseNameOf);
+    addPrimOp("dirOf", 1, primDirOf);
+
+    // Creating files
+    addPrimOp("__toXML", 1, primToXML);
+    addPrimOp("__toFile", 2, primToFile);
+    addPrimOp("__filterSource", 2, primFilterSource);
+
+    // Attribute sets
+    addPrimOp("__attrNames", 1, primAttrNames);
     addPrimOp("__getAttr", 2, primGetAttr);
     addPrimOp("__hasAttr", 2, primHasAttr);
     addPrimOp("removeAttrs", 2, primRemoveAttrs);
-    addPrimOp("relativise", 2, primRelativise);
+
+    // Lists
+    addPrimOp("__isList", 1, primIsList);
+    addPrimOp("__head", 1, primHead);
+    addPrimOp("__tail", 1, primTail);
+    addPrimOp("map", 2, primMap);
+
+    // Integer arithmetic
     addPrimOp("__add", 2, primAdd);
     addPrimOp("__sub", 2, primSub);
     addPrimOp("__lessThan", 2, primLessThan);
-    addPrimOp("__toFile", 2, primToFile);
-    addPrimOp("__filterSource", 2, primFilterSource);
 
+    // String manipulation
     addPrimOp("__substring", 3, prim_substring);
     addPrimOp("__stringLength", 1, prim_stringLength);
 }