about summary refs log tree commit diff
path: root/src/libexpr
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/Makefile.am22
-rw-r--r--src/libexpr/eval.cc265
-rw-r--r--src/libexpr/eval.hh42
-rw-r--r--src/libexpr/fix-expr.cc215
-rw-r--r--src/libexpr/fix-expr.hh75
-rw-r--r--src/libexpr/main.cc117
-rw-r--r--src/libexpr/nix.sdf209
-rw-r--r--src/libexpr/nixexpr.cc215
-rw-r--r--src/libexpr/nixexpr.hh75
-rw-r--r--src/libexpr/parser.cc164
-rw-r--r--src/libexpr/parser.hh10
-rw-r--r--src/libexpr/primops.cc246
-rw-r--r--src/libexpr/primops.hh34
13 files changed, 1689 insertions, 0 deletions
diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am
new file mode 100644
index 000000000000..6fe79850116e
--- /dev/null
+++ b/src/libexpr/Makefile.am
@@ -0,0 +1,22 @@
+bin_PROGRAMS = nix-instantiate
+
+nix_instantiate_SOURCES = nixexpr.cc parser.cc eval.cc primops.cc main.cc
+nix_instantiate_LDADD = ../libmain/libmain.a ../libstore/libstore.a ../libutil/libutil.a \
+ ../boost/format/libformat.a -L../../externals/inst/lib -ldb_cxx \
+ -lsglr -lATB -lconversion -lasfix2 -lmept -lATerm
+
+AM_CXXFLAGS = \
+ -I.. -I../../externals/inst/include -I../libutil -I../libstore -I../libmain
+
+
+# Parse table generation.
+
+parser.o: parse-table.h
+
+parse-table.h: nix.tbl
+	../bin2c/bin2c nixParseTable < $< > $@ || (rm $@ && exit 1)
+
+%.tbl: %.sdf
+	../../externals/inst/bin/sdf2table -s -i $< -o $@
+
+CLEANFILES = parse-table.h nix.tbl
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
new file mode 100644
index 000000000000..b110c3a4a41c
--- /dev/null
+++ b/src/libexpr/eval.cc
@@ -0,0 +1,265 @@
+#include "eval.hh"
+#include "parser.hh"
+#include "primops.hh"
+
+
+EvalState::EvalState()
+    : normalForms(32768, 75)
+{
+    blackHole = ATmake("BlackHole()");
+    if (!blackHole) throw Error("cannot build black hole");
+    nrEvaluated = nrCached = 0;
+}
+
+
+/* Substitute an argument set into the body of a function. */
+static Expr substArgs(Expr body, ATermList formals, Expr arg)
+{
+    ATMatcher m;
+    ATermMap subs;
+    Expr undefined = ATmake("Undefined");
+
+    /* Get the formal arguments. */
+    for (ATermIterator i(formals); i; ++i) {
+        Expr name, def;
+        if (atMatch(m, *i) >> "NoDefFormal" >> name)
+            subs.set(name, undefined);
+        else if (atMatch(m, *i) >> "DefFormal" >> name >> def)
+            subs.set(name, def);
+        else abort(); /* can't happen */
+    }
+
+    /* Get the actual arguments, and check that they match with the
+       formals. */
+    ATermMap args;
+    queryAllAttrs(arg, args);
+    for (ATermIterator i(args.keys()); i; ++i) {
+        Expr key = *i;
+        Expr cur = subs.get(key);
+        if (!cur)
+            throw badTerm(format("function has no formal argument `%1%'")
+                % aterm2String(key), arg);
+        subs.set(key, args.get(key));
+    }
+
+    /* Check that all arguments are defined. */
+    for (ATermIterator i(subs.keys()); i; ++i)
+        if (subs.get(*i) == undefined)
+            throw badTerm(format("formal argument `%1%' missing")
+                % aterm2String(*i), arg);
+    
+    return substitute(subs, body);
+}
+
+
+/* Transform a mutually recursive set into a non-recursive set.  Each
+   attribute is transformed into an expression that has all references
+   to attributes substituted with selection expressions on the
+   original set.  E.g., e = `rec {x = f x y, y = x}' becomes `{x = f
+   (e.x) (e.y), y = e.x}'. */
+ATerm expandRec(ATerm e, ATermList bnds)
+{
+    ATMatcher m;
+
+    /* Create the substitution list. */
+    ATermMap subs;
+    for (ATermIterator i(bnds); i; ++i) {
+        string s;
+        Expr e2;
+        if (!(atMatch(m, *i) >> "Bind" >> s >> e2))
+            abort(); /* can't happen */
+        subs.set(s, ATmake("Select(<term>, <str>)", e, s.c_str()));
+    }
+
+    /* Create the non-recursive set. */
+    ATermMap as;
+    for (ATermIterator i(bnds); i; ++i) {
+        string s;
+        Expr e2;
+        if (!(atMatch(m, *i) >> "Bind" >> s >> e2))
+            abort(); /* can't happen */
+        as.set(s, substitute(subs, e2));
+    }
+
+    return makeAttrs(as);
+}
+
+
+string evalString(EvalState & state, Expr e)
+{
+    e = evalExpr(state, e);
+    ATMatcher m;
+    string s;
+    if (!(atMatch(m, e) >> "Str" >> s))
+        throw badTerm("string expected", e);
+    return s;
+}
+
+
+Path evalPath(EvalState & state, Expr e)
+{
+    e = evalExpr(state, e);
+    ATMatcher m;
+    string s;
+    if (!(atMatch(m, e) >> "Path" >> s))
+        throw badTerm("path expected", e);
+    return s;
+}
+
+
+bool evalBool(EvalState & state, Expr e)
+{
+    e = evalExpr(state, e);
+    ATMatcher m;
+    if (atMatch(m, e) >> "Bool" >> "True") return true;
+    else if (atMatch(m, e) >> "Bool" >> "False") return false;
+    else throw badTerm("expecting a boolean", e);
+}
+
+
+Expr evalExpr2(EvalState & state, Expr e)
+{
+    ATMatcher m;
+    Expr e1, e2, e3, e4;
+    string s1;
+
+    /* Normal forms. */
+    if (atMatch(m, e) >> "Str" ||
+        atMatch(m, e) >> "Path" ||
+        atMatch(m, e) >> "Uri" ||
+        atMatch(m, e) >> "Bool" ||
+        atMatch(m, e) >> "Function" ||
+        atMatch(m, e) >> "Attrs" ||
+        atMatch(m, e) >> "List")
+        return e;
+
+    /* Any encountered variables must be undeclared or primops. */
+    if (atMatch(m, e) >> "Var" >> s1) {
+        if (s1 == "null") return primNull(state);
+        return e;
+    }
+
+    /* Function application. */
+    if (atMatch(m, e) >> "Call" >> e1 >> e2) {
+
+        ATermList formals;
+        
+        /* Evaluate the left-hand side. */
+        e1 = evalExpr(state, e1);
+
+        /* Is it a primop or a function? */
+        if (atMatch(m, e1) >> "Var" >> s1) {
+            if (s1 == "import") return primImport(state, e2);
+            if (s1 == "derivation") return primDerivation(state, e2);
+            if (s1 == "toString") return primToString(state, e2);
+            if (s1 == "baseNameOf") return primBaseNameOf(state, e2);
+            if (s1 == "isNull") return primIsNull(state, e2);
+            else throw badTerm("undefined variable/primop", e1);
+        }
+
+        else if (atMatch(m, e1) >> "Function" >> formals >> e4)
+            return evalExpr(state, 
+                substArgs(e4, formals, evalExpr(state, e2)));
+        
+        else throw badTerm("expecting a function or primop", e1);
+    }
+
+    /* Attribute selection. */
+    if (atMatch(m, e) >> "Select" >> e1 >> s1) {
+        Expr a = queryAttr(evalExpr(state, e1), s1);
+        if (!a) throw badTerm(format("missing attribute `%1%'") % s1, e);
+        return evalExpr(state, a);
+    }
+
+    /* Mutually recursive sets. */
+    ATermList bnds;
+    if (atMatch(m, e) >> "Rec" >> bnds)
+        return expandRec(e, bnds);
+
+    /* Let expressions `let {..., body = ...}' are just desugared
+       into `(rec {..., body = ...}).body'. */
+    if (atMatch(m, e) >> "LetRec" >> bnds)
+        return evalExpr(state, ATmake("Select(Rec(<term>), \"body\")", bnds));
+
+    /* Conditionals. */
+    if (atMatch(m, e) >> "If" >> e1 >> e2 >> e3) {
+        if (evalBool(state, e1))
+            return evalExpr(state, e2);
+        else
+            return evalExpr(state, e3);
+    }
+
+    /* Assertions. */
+    if (atMatch(m, e) >> "Assert" >> e1 >> e2) {
+        if (!evalBool(state, e1)) throw badTerm("guard failed", e);
+        return evalExpr(state, e2);
+    }
+
+    /* Generic equality. */
+    if (atMatch(m, e) >> "OpEq" >> e1 >> e2)
+        return makeBool(evalExpr(state, e1) == evalExpr(state, e2));
+
+    /* Generic inequality. */
+    if (atMatch(m, e) >> "OpNEq" >> e1 >> e2)
+        return makeBool(evalExpr(state, e1) != evalExpr(state, e2));
+
+    /* Negation. */
+    if (atMatch(m, e) >> "OpNot" >> e1)
+        return makeBool(!evalBool(state, e1));
+
+    /* Implication. */
+    if (atMatch(m, e) >> "OpImpl" >> e1 >> e2)
+        return makeBool(!evalBool(state, e1) || evalBool(state, e2));
+
+    /* Conjunction (logical AND). */
+    if (atMatch(m, e) >> "OpAnd" >> e1 >> e2)
+        return makeBool(evalBool(state, e1) && evalBool(state, e2));
+
+    /* Disjunction (logical OR). */
+    if (atMatch(m, e) >> "OpOr" >> e1 >> e2)
+        return makeBool(evalBool(state, e1) || evalBool(state, e2));
+
+    /* Barf. */
+    throw badTerm("invalid expression", e);
+}
+
+
+Expr evalExpr(EvalState & state, Expr e)
+{
+    startNest(nest, lvlVomit,
+        format("evaluating expression: %1%") % e);
+
+    state.nrEvaluated++;
+
+    /* Consult the memo table to quickly get the normal form of
+       previously evaluated expressions. */
+    Expr nf = state.normalForms.get(e);
+    if (nf) {
+        if (nf == state.blackHole)
+            throw badTerm("infinite recursion", e);
+        state.nrCached++;
+        return nf;
+    }
+
+    /* Otherwise, evaluate and memoize. */
+    state.normalForms.set(e, state.blackHole);
+    nf = evalExpr2(state, e);
+    state.normalForms.set(e, nf);
+    return nf;
+}
+
+
+Expr evalFile(EvalState & state, const Path & path)
+{
+    startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path);
+    Expr e = parseExprFromFile(path);
+    return evalExpr(state, e);
+}
+
+
+void printEvalStats(EvalState & state)
+{
+    debug(format("evaluated %1% expressions, %2% cache hits, %3%%% efficiency")
+        % state.nrEvaluated % state.nrCached
+        % ((float) state.nrCached / (float) state.nrEvaluated * 100));
+}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
new file mode 100644
index 000000000000..0bc052676deb
--- /dev/null
+++ b/src/libexpr/eval.hh
@@ -0,0 +1,42 @@
+#ifndef __EVAL_H
+#define __EVAL_H
+
+#include <map>
+
+#include "aterm.hh"
+#include "hash.hh"
+#include "nixexpr.hh"
+
+
+typedef map<Path, PathSet> DrvPaths;
+typedef map<Path, Hash> DrvHashes;
+
+struct EvalState 
+{
+    ATermMap normalForms;
+    DrvPaths drvPaths;
+    DrvHashes drvHashes; /* normalised derivation hashes */
+    Expr blackHole;
+
+    unsigned int nrEvaluated;
+    unsigned int nrCached;
+
+    EvalState();
+};
+
+
+/* Evaluate an expression to normal form. */
+Expr evalExpr(EvalState & state, Expr e);
+
+/* Evaluate an expression read from the given file to normal form. */
+Expr evalFile(EvalState & state, const Path & path);
+
+/* Specific results. */
+string evalString(EvalState & state, Expr e);
+Path evalPath(EvalState & state, Expr e);
+
+/* Print statistics. */
+void printEvalStats(EvalState & state);
+
+
+#endif /* !__EVAL_H */
diff --git a/src/libexpr/fix-expr.cc b/src/libexpr/fix-expr.cc
new file mode 100644
index 000000000000..e9c5a3ba633f
--- /dev/null
+++ b/src/libexpr/fix-expr.cc
@@ -0,0 +1,215 @@
+#include "fix-expr.hh"
+#include "expr.hh"
+
+
+ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct)
+{
+    table = ATtableCreate(initialSize, maxLoadPct);
+    if (!table) throw Error("cannot create ATerm table");
+}
+
+
+ATermMap::ATermMap(const ATermMap & map)
+    : table(0)
+{
+    ATermList keys = map.keys();
+
+    /* !!! adjust allocation for load pct */
+    table = ATtableCreate(ATgetLength(keys), map.maxLoadPct);
+    if (!table) throw Error("cannot create ATerm table");
+
+    for (ATermIterator i(keys); i; ++i)
+        set(*i, map.get(*i));
+}
+
+
+ATermMap::~ATermMap()
+{
+    if (table) ATtableDestroy(table);
+}
+
+
+void ATermMap::set(ATerm key, ATerm value)
+{
+    return ATtablePut(table, key, value);
+}
+
+
+void ATermMap::set(const string & key, ATerm value)
+{
+    set(string2ATerm(key), value);
+}
+
+
+ATerm ATermMap::get(ATerm key) const
+{
+    return ATtableGet(table, key);
+}
+
+
+ATerm ATermMap::get(const string & key) const
+{
+    return get(string2ATerm(key));
+}
+
+
+void ATermMap::remove(ATerm key)
+{
+    ATtableRemove(table, key);
+}
+
+
+void ATermMap::remove(const string & key)
+{
+    remove(string2ATerm(key));
+}
+
+
+ATermList ATermMap::keys() const
+{
+    ATermList keys = ATtableKeys(table);
+    if (!keys) throw Error("cannot query aterm map keys");
+    return keys;
+}
+
+
+ATerm string2ATerm(const string & s)
+{
+    return (ATerm) ATmakeAppl0(ATmakeAFun((char *) s.c_str(), 0, ATtrue));
+}
+
+
+string aterm2String(ATerm t)
+{
+    return ATgetName(ATgetAFun(t));
+}
+    
+
+ATerm bottomupRewrite(TermFun & f, ATerm e)
+{
+    if (ATgetType(e) == AT_APPL) {
+        AFun fun = ATgetAFun(e);
+        int arity = ATgetArity(fun);
+        ATermList args = ATempty;
+
+        for (int i = arity - 1; i >= 0; i--)
+            args = ATinsert(args, bottomupRewrite(f, ATgetArgument(e, i)));
+        
+        e = (ATerm) ATmakeApplList(fun, args);
+    }
+
+    else if (ATgetType(e) == AT_LIST) {
+        ATermList in = (ATermList) e;
+        ATermList out = ATempty;
+
+        for (ATermIterator i(in); i; ++i)
+            out = ATinsert(out, bottomupRewrite(f, *i));
+
+        e = (ATerm) ATreverse(out);
+    }
+
+    return f(e);
+}
+
+
+void queryAllAttrs(Expr e, ATermMap & attrs)
+{
+    ATMatcher m;
+    ATermList bnds;
+    if (!(atMatch(m, e) >> "Attrs" >> bnds))
+        throw badTerm("expected attribute set", e);
+
+    for (ATermIterator i(bnds); i; ++i) {
+        string s;
+        Expr e;
+        if (!(atMatch(m, *i) >> "Bind" >> s >> e))
+            abort(); /* can't happen */
+        attrs.set(s, e);
+    }
+}
+
+
+Expr queryAttr(Expr e, const string & name)
+{
+    ATermMap attrs;
+    queryAllAttrs(e, attrs);
+    return attrs.get(name);
+}
+
+
+Expr makeAttrs(const ATermMap & attrs)
+{
+    ATermList bnds = ATempty;
+    for (ATermIterator i(attrs.keys()); i; ++i)
+        bnds = ATinsert(bnds, 
+            ATmake("Bind(<term>, <term>)", *i, attrs.get(*i)));
+    return ATmake("Attrs(<term>)", ATreverse(bnds));
+}
+
+
+Expr substitute(const ATermMap & subs, Expr e)
+{
+    ATMatcher m;
+    string s;
+
+    if (atMatch(m, e) >> "Var" >> s) {
+        Expr sub = subs.get(s);
+        return sub ? sub : e;
+    }
+
+    /* In case of a function, filter out all variables bound by this
+       function. */
+    ATermList formals;
+    ATerm body;
+    if (atMatch(m, e) >> "Function" >> formals >> body) {
+        ATermMap subs2(subs);
+        for (ATermIterator i(formals); i; ++i) {
+            Expr def;
+            if (!(atMatch(m, *i) >> "NoDefFormal" >> s) &&
+                !(atMatch(m, *i) >> "DefFormal" >> s >> def))
+                abort();
+            subs2.remove(s);
+        }
+        return ATmake("Function(<term>, <term>)", formals,
+            substitute(subs2, body));
+    }
+
+    /* Idem for a mutually recursive attribute set. */
+    ATermList bindings;
+    if (atMatch(m, e) >> "Rec" >> bindings) {
+        ATermMap subs2(subs);
+        for (ATermIterator i(bindings); i; ++i) {
+            Expr e;
+            if (!(atMatch(m, *i) >> "Bind" >> s >> e))
+                abort(); /* can't happen */
+            subs2.remove(s);
+        }
+        return ATmake("Rec(<term>)", substitute(subs2, (ATerm) bindings));
+    }
+
+    if (ATgetType(e) == AT_APPL) {
+        AFun fun = ATgetAFun(e);
+        int arity = ATgetArity(fun);
+        ATermList args = ATempty;
+
+        for (int i = arity - 1; i >= 0; i--)
+            args = ATinsert(args, substitute(subs, ATgetArgument(e, i)));
+        
+        return (ATerm) ATmakeApplList(fun, args);
+    }
+
+    if (ATgetType(e) == AT_LIST) {
+        ATermList out = ATempty;
+        for (ATermIterator i((ATermList) e); i; ++i)
+            out = ATinsert(out, substitute(subs, *i));
+        return (ATerm) ATreverse(out);
+    }
+
+    return e;
+}
+
+
+Expr makeBool(bool b)
+{
+    return b ? ATmake("Bool(True)") : ATmake("Bool(False)");
+}
diff --git a/src/libexpr/fix-expr.hh b/src/libexpr/fix-expr.hh
new file mode 100644
index 000000000000..6c1e51d9ccd6
--- /dev/null
+++ b/src/libexpr/fix-expr.hh
@@ -0,0 +1,75 @@
+#ifndef __FIXEXPR_H
+#define __FIXEXPR_H
+
+#include <map>
+
+#include <aterm2.h>
+
+#include "util.hh"
+
+
+/* Fix expressions are represented as ATerms.  The maximal sharing
+   property of the ATerm library allows us to implement caching of
+   normals forms efficiently. */
+typedef ATerm Expr;
+
+
+/* Mappings from ATerms to ATerms.  This is just a wrapper around
+   ATerm tables. */
+class ATermMap
+{
+private:
+    unsigned int maxLoadPct;
+    ATermTable table;
+    
+public:
+    ATermMap(unsigned int initialSize = 16, unsigned int maxLoadPct = 75);
+    ATermMap(const ATermMap & map);
+    ~ATermMap();
+
+    void set(ATerm key, ATerm value);
+    void set(const string & key, ATerm value);
+
+    ATerm get(ATerm key) const;
+    ATerm get(const string & key) const;
+
+    void remove(ATerm key);
+    void remove(const string & key);
+
+    ATermList keys() const;
+};
+
+
+/* Convert a string to an ATerm (i.e., a quoted nullary function
+   applicaton). */
+ATerm string2ATerm(const string & s);
+string aterm2String(ATerm t);
+
+/* Generic bottomup traversal over ATerms.  The traversal first
+   recursively descends into subterms, and then applies the given term
+   function to the resulting term. */
+struct TermFun
+{
+    virtual ATerm operator () (ATerm e) = 0;
+};
+ATerm bottomupRewrite(TermFun & f, ATerm e);
+
+/* Query all attributes in an attribute set expression.  The
+   expression must be in normal form. */
+void queryAllAttrs(Expr e, ATermMap & attrs);
+
+/* Query a specific attribute from an attribute set expression.  The
+   expression must be in normal form. */
+Expr queryAttr(Expr e, const string & name);
+
+/* Create an attribute set expression from an Attrs value. */
+Expr makeAttrs(const ATermMap & attrs);
+
+/* Perform a set of substitutions on an expression. */
+Expr substitute(const ATermMap & subs, Expr e);
+
+/* Create an expression representing a boolean. */
+Expr makeBool(bool b);
+
+
+#endif /* !__FIXEXPR_H */
diff --git a/src/libexpr/main.cc b/src/libexpr/main.cc
new file mode 100644
index 000000000000..aa6883ff84b8
--- /dev/null
+++ b/src/libexpr/main.cc
@@ -0,0 +1,117 @@
+#include <map>
+#include <iostream>
+
+#include "globals.hh"
+#include "normalise.hh"
+#include "shared.hh"
+#include "eval.hh"
+
+
+#if 0
+static Path searchPath(const Paths & searchDirs, const Path & relPath)
+{
+    if (string(relPath, 0, 1) == "/") return relPath;
+
+    for (Paths::const_iterator i = searchDirs.begin();
+         i != searchDirs.end(); i++)
+    {
+        Path path = *i + "/" + relPath;
+        if (pathExists(path)) return path;
+    }
+
+    throw Error(
+        format("path `%1%' not found in any of the search directories")
+        % relPath);
+}
+#endif
+
+
+static Expr evalStdin(EvalState & state)
+{
+    startNest(nest, lvlTalkative, format("evaluating standard input"));
+    Expr e = ATreadFromFile(stdin);
+    if (!e) 
+        throw Error(format("unable to read a term from stdin"));
+    return evalExpr(state, e);
+}
+
+
+static void printNixExpr(EvalState & state, Expr e)
+{
+    ATMatcher m;
+    ATermList es;
+
+    if (atMatch(m, e) >> "Attrs" >> es) {
+        Expr a = queryAttr(e, "type");
+        if (a && evalString(state, a) == "derivation") {
+            a = queryAttr(e, "drvPath");
+            if (a) {
+                cout << format("%1%\n") % evalPath(state, a);
+                return;
+            }
+        }
+    }
+
+    if (ATgetType(e) == AT_LIST) {
+        for (ATermIterator i((ATermList) e); i; ++i)
+            printNixExpr(state, evalExpr(state, *i));
+        return;
+    }
+
+    throw badTerm("top level does not evaluate to one or more Nix expressions", e);
+}
+
+
+void run(Strings args)
+{
+    EvalState state;
+    Strings files;
+    bool readStdin = false;
+
+#if 0
+    state.searchDirs.push_back(".");
+    state.searchDirs.push_back(nixDataDir + "/nix");
+#endif
+    
+    for (Strings::iterator it = args.begin();
+         it != args.end(); )
+    {
+        string arg = *it++;
+
+#if 0
+        if (arg == "--includedir" || arg == "-I") {
+            if (it == args.end())
+                throw UsageError(format("argument required in `%1%'") % arg);
+            state.searchDirs.push_back(*it++);
+        }
+        else
+#endif
+        if (arg == "--verbose" || arg == "-v")
+            verbosity = (Verbosity) ((int) verbosity + 1);
+        else if (arg == "-")
+            readStdin = true;
+        else if (arg[0] == '-')
+            throw UsageError(format("unknown flag `%1%`") % arg);
+        else
+            files.push_back(arg);
+    }
+
+    openDB();
+
+    if (readStdin) {
+        Expr e = evalStdin(state);
+        printNixExpr(state, e);
+    }
+
+    for (Strings::iterator it = files.begin();
+         it != files.end(); it++)
+    {
+        Expr e = evalFile(state, absPath(*it));
+        printNixExpr(state, e);
+    }
+
+    printEvalStats(state);
+}
+
+
+string programId = "nix-instantiate";
diff --git a/src/libexpr/nix.sdf b/src/libexpr/nix.sdf
new file mode 100644
index 000000000000..615bdb974775
--- /dev/null
+++ b/src/libexpr/nix.sdf
@@ -0,0 +1,209 @@
+definition
+
+module Main
+imports Fix
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Top level syntax.
+
+module Fix
+imports Fix-Exprs Fix-Layout
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Expressions.
+
+module Fix-Exprs
+imports Fix-Lexicals URI
+exports
+  sorts Expr Formal Bind Binds BindSemi ExprList
+  context-free syntax
+
+    Id -> Expr {cons("Var")}
+
+    Int -> Expr {cons("Int")}
+
+    Str -> Expr {cons("Str")}
+
+    Uri -> Expr {cons("Uri")}
+
+    Path -> Expr {cons("Path")}
+
+    "(" Expr ")" -> Expr {bracket}
+
+    Expr Expr -> Expr {cons("Call"), left}
+
+    "{" {Formal ","}* "}" ":" Expr -> Expr {cons("Function")}
+    Id -> Formal {cons("NoDefFormal")}
+    Id "?" Expr -> Formal {cons("DefFormal")}
+
+    "assert" Expr ";" Expr -> Expr {cons("Assert")}
+
+    "rec" "{" Binds "}" -> Expr {cons("Rec")}
+    "let" "{" Binds "}" -> Expr {cons("LetRec")}
+    "{" Binds "}" -> Expr {cons("Attrs")}
+
+    Id "=" Expr -> Bind {cons("Bind")}
+    {Bind ";"}* -> Binds
+    Bind ";" -> BindSemi 
+    BindSemi* -> Binds
+
+    "[" ExprList "]" -> Expr {cons("List")}
+    "" -> ExprList {cons("ExprNil")}
+    Expr ExprList -> ExprList {cons("ExprCons")}
+
+    Expr "." Id -> Expr {cons("Select")}
+
+    "if" Expr "then" Expr "else" Expr -> Expr {cons("If")}
+
+    Expr "==" Expr -> Expr {cons("OpEq"), non-assoc}
+    Expr "!=" Expr -> Expr {cons("OpNEq"), non-assoc}
+
+    "!" Expr -> Expr {cons("OpNot")}
+    Expr "&&" Expr -> Expr {cons("OpAnd"), right}
+    Expr "||" Expr -> Expr {cons("OpOr"), right}
+    Expr "->" Expr -> Expr {cons("OpImpl"), right}
+
+    Bool -> Expr {cons("Bool")}
+
+  context-free priorities
+
+    Expr "." Id -> Expr
+  > Expr ExprList -> ExprList
+  > Expr Expr -> Expr
+  > "!" Expr -> Expr
+  > Expr "==" Expr -> Expr
+  > Expr "!=" Expr -> Expr
+  > Expr "&&" Expr -> Expr
+  > Expr "||" Expr -> Expr
+  > Expr "->" Expr -> Expr
+  > "assert" Expr ";" Expr -> Expr
+  > "{" {Formal ","}* "}" ":" Expr -> Expr
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Lexical syntax.
+
+module Fix-Lexicals
+exports
+  sorts Id Int Str Path PathComp Bool
+  lexical syntax
+    [a-zA-Z\_][a-zA-Z0-9\_\']* -> Id
+    "rec" -> Id {reject}
+    "let" -> Id {reject}
+    "if" -> Id {reject}
+    "then" -> Id {reject}
+    "else" -> Id {reject}
+    "true" -> Id {reject}
+    "false" -> Id {reject}
+    "assert" -> Id {reject}
+
+    [0-9]+ -> Int
+
+    "\"" ~[\n\"]* "\"" -> Str
+
+    PathComp ("/" PathComp)+ -> Path
+    [a-zA-Z0-9\.\_\-\+]+ -> PathComp
+
+    "true" -> Bool
+    "false" -> Bool
+
+  lexical restrictions
+    Id -/- [a-zA-Z0-9\_\']
+    Int -/- [0-9]
+    Path -/- [a-zA-Z0-9\.\_\-\+\/]
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% URIs (RFC 2396, appendix A).
+
+module URI
+exports
+  sorts Uri Uhierpart Uopaquepart Uuricnoslash Unetpath Uabspath
+        Urelpath Urelsegment Uscheme Uauthority Uregname Userver
+        Uuserinfo Uhostport Uhost Uhostname Udomainlabel Utoplabel
+        UIPv4address Uport Upath Upathsegments Usegment Uparam
+        Upchar Uquery Ufragment Uuric Ureserved Uunreserved Umark
+        Uescaped Uhex Ualphanum Ualpha Ulowalpha Uupalpha Udigit
+  lexical syntax
+    Uscheme ":" (Uhierpart | Uopaquepart) -> Uri
+
+    (Unetpath | Uabspath) ("?" Uquery)? -> Uhierpart
+    Uuricnoslash Uuric* -> Uopaquepart
+
+    Uunreserved | Uescaped | [\;\?\:\@\&\=\+\$\,] -> Uuricnoslash
+
+    "//" Uauthority Uabspath? -> Unetpath
+    "/" Upathsegments -> Uabspath
+    "//" Uuric* -> Uabspath {reject}
+    Urelsegment Uabspath? -> Urelpath
+
+    (Uunreserved | Uescaped | [\;\@\&\=\+\$\,])+ -> Urelsegment
+
+    Ualpha (Ualpha | Udigit | [\+\-\.])* -> Uscheme
+
+    Userver | Uregname -> Uauthority
+
+    (Uunreserved | Uescaped | [\$\,\;\:\@\&\=\+])+ -> Uregname
+
+    ((Uuserinfo "@") Uhostport) -> Userver
+    (Uunreserved | Uescaped | [\;\:\&\=\+\$\,])* -> Uuserinfo
+
+    Uhost (":" Uport)? -> Uhostport
+    Uhostname | UIPv4address -> Uhost
+    (Udomainlabel ".")+ Utoplabel "."? -> Uhostname
+    Ualphanum | Ualphanum (Ualphanum | "-")* Ualphanum -> Udomainlabel
+    Ualpha | Ualpha (Ualphanum | "-")* Ualphanum -> Utoplabel
+    Udigit+ "." Udigit+ "." Udigit+ "." Udigit+ -> UIPv4address
+    Udigit* -> Uport
+
+    Uabspath | Uopaquepart -> Upath
+    Usegment ("/" Usegment)* -> Upathsegments
+    Upchar* (";" Uparam)* -> Usegment
+    Upchar* -> Uparam
+    Uunreserved | Uescaped | [\:\@\&\=\+\$\,] -> Upchar
+
+    Uuric* -> Uquery
+
+    Uuric* -> Ufragment
+
+    Ureserved | Uunreserved | Uescaped -> Uuric
+    [\;\/\?\:\@\&\=\+\$\,] -> Ureserved
+    Ualphanum | Umark -> Uunreserved
+    [\-\_\.\!\~\*\'\(\)] -> Umark
+
+    "%" Uhex Uhex -> Uescaped
+    Udigit | [A-Fa-f] -> Uhex
+
+    Ualpha | Udigit -> Ualphanum
+    Ulowalpha | Uupalpha -> Ualpha
+
+    [a-z] -> Ulowalpha
+    [A-Z] -> Uupalpha
+    [0-9] -> Udigit
+
+  lexical restrictions
+    Uri -/- [a-zA-Z0-9\-\_\.\!\~\*\'\(\)]
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Layout.
+
+module Fix-Layout
+exports
+  sorts HashComment Asterisk Comment EOF
+  lexical syntax
+    [\ \t\n] -> LAYOUT
+    HashComment -> LAYOUT
+    Comment -> LAYOUT
+    "#" ~[\n]* ([\n] | EOF) -> HashComment
+    "//" ~[\n]* ([\n] | EOF) -> HashComment
+    "/*" ( ~[\*] | Asterisk )* "*/" -> Comment
+    [\*] -> Asterisk
+    "" -> EOF
+  lexical restrictions
+    Asterisk -/- [\/]
+    EOF -/- ~[]
+  context-free restrictions
+    LAYOUT? -/- [\ \t\n] | [\#]
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
new file mode 100644
index 000000000000..816b39dc1ae3
--- /dev/null
+++ b/src/libexpr/nixexpr.cc
@@ -0,0 +1,215 @@
+#include "nixexpr.hh"
+#include "storeexpr.hh"
+
+
+ATermMap::ATermMap(unsigned int initialSize, unsigned int maxLoadPct)
+{
+    table = ATtableCreate(initialSize, maxLoadPct);
+    if (!table) throw Error("cannot create ATerm table");
+}
+
+
+ATermMap::ATermMap(const ATermMap & map)
+    : table(0)
+{
+    ATermList keys = map.keys();
+
+    /* !!! adjust allocation for load pct */
+    table = ATtableCreate(ATgetLength(keys), map.maxLoadPct);
+    if (!table) throw Error("cannot create ATerm table");
+
+    for (ATermIterator i(keys); i; ++i)
+        set(*i, map.get(*i));
+}
+
+
+ATermMap::~ATermMap()
+{
+    if (table) ATtableDestroy(table);
+}
+
+
+void ATermMap::set(ATerm key, ATerm value)
+{
+    return ATtablePut(table, key, value);
+}
+
+
+void ATermMap::set(const string & key, ATerm value)
+{
+    set(string2ATerm(key), value);
+}
+
+
+ATerm ATermMap::get(ATerm key) const
+{
+    return ATtableGet(table, key);
+}
+
+
+ATerm ATermMap::get(const string & key) const
+{
+    return get(string2ATerm(key));
+}
+
+
+void ATermMap::remove(ATerm key)
+{
+    ATtableRemove(table, key);
+}
+
+
+void ATermMap::remove(const string & key)
+{
+    remove(string2ATerm(key));
+}
+
+
+ATermList ATermMap::keys() const
+{
+    ATermList keys = ATtableKeys(table);
+    if (!keys) throw Error("cannot query aterm map keys");
+    return keys;
+}
+
+
+ATerm string2ATerm(const string & s)
+{
+    return (ATerm) ATmakeAppl0(ATmakeAFun((char *) s.c_str(), 0, ATtrue));
+}
+
+
+string aterm2String(ATerm t)
+{
+    return ATgetName(ATgetAFun(t));
+}
+    
+
+ATerm bottomupRewrite(TermFun & f, ATerm e)
+{
+    if (ATgetType(e) == AT_APPL) {
+        AFun fun = ATgetAFun(e);
+        int arity = ATgetArity(fun);
+        ATermList args = ATempty;
+
+        for (int i = arity - 1; i >= 0; i--)
+            args = ATinsert(args, bottomupRewrite(f, ATgetArgument(e, i)));
+        
+        e = (ATerm) ATmakeApplList(fun, args);
+    }
+
+    else if (ATgetType(e) == AT_LIST) {
+        ATermList in = (ATermList) e;
+        ATermList out = ATempty;
+
+        for (ATermIterator i(in); i; ++i)
+            out = ATinsert(out, bottomupRewrite(f, *i));
+
+        e = (ATerm) ATreverse(out);
+    }
+
+    return f(e);
+}
+
+
+void queryAllAttrs(Expr e, ATermMap & attrs)
+{
+    ATMatcher m;
+    ATermList bnds;
+    if (!(atMatch(m, e) >> "Attrs" >> bnds))
+        throw badTerm("expected attribute set", e);
+
+    for (ATermIterator i(bnds); i; ++i) {
+        string s;
+        Expr e;
+        if (!(atMatch(m, *i) >> "Bind" >> s >> e))
+            abort(); /* can't happen */
+        attrs.set(s, e);
+    }
+}
+
+
+Expr queryAttr(Expr e, const string & name)
+{
+    ATermMap attrs;
+    queryAllAttrs(e, attrs);
+    return attrs.get(name);
+}
+
+
+Expr makeAttrs(const ATermMap & attrs)
+{
+    ATermList bnds = ATempty;
+    for (ATermIterator i(attrs.keys()); i; ++i)
+        bnds = ATinsert(bnds, 
+            ATmake("Bind(<term>, <term>)", *i, attrs.get(*i)));
+    return ATmake("Attrs(<term>)", ATreverse(bnds));
+}
+
+
+Expr substitute(const ATermMap & subs, Expr e)
+{
+    ATMatcher m;
+    string s;
+
+    if (atMatch(m, e) >> "Var" >> s) {
+        Expr sub = subs.get(s);
+        return sub ? sub : e;
+    }
+
+    /* In case of a function, filter out all variables bound by this
+       function. */
+    ATermList formals;
+    ATerm body;
+    if (atMatch(m, e) >> "Function" >> formals >> body) {
+        ATermMap subs2(subs);
+        for (ATermIterator i(formals); i; ++i) {
+            Expr def;
+            if (!(atMatch(m, *i) >> "NoDefFormal" >> s) &&
+                !(atMatch(m, *i) >> "DefFormal" >> s >> def))
+                abort();
+            subs2.remove(s);
+        }
+        return ATmake("Function(<term>, <term>)", formals,
+            substitute(subs2, body));
+    }
+
+    /* Idem for a mutually recursive attribute set. */
+    ATermList bindings;
+    if (atMatch(m, e) >> "Rec" >> bindings) {
+        ATermMap subs2(subs);
+        for (ATermIterator i(bindings); i; ++i) {
+            Expr e;
+            if (!(atMatch(m, *i) >> "Bind" >> s >> e))
+                abort(); /* can't happen */
+            subs2.remove(s);
+        }
+        return ATmake("Rec(<term>)", substitute(subs2, (ATerm) bindings));
+    }
+
+    if (ATgetType(e) == AT_APPL) {
+        AFun fun = ATgetAFun(e);
+        int arity = ATgetArity(fun);
+        ATermList args = ATempty;
+
+        for (int i = arity - 1; i >= 0; i--)
+            args = ATinsert(args, substitute(subs, ATgetArgument(e, i)));
+        
+        return (ATerm) ATmakeApplList(fun, args);
+    }
+
+    if (ATgetType(e) == AT_LIST) {
+        ATermList out = ATempty;
+        for (ATermIterator i((ATermList) e); i; ++i)
+            out = ATinsert(out, substitute(subs, *i));
+        return (ATerm) ATreverse(out);
+    }
+
+    return e;
+}
+
+
+Expr makeBool(bool b)
+{
+    return b ? ATmake("Bool(True)") : ATmake("Bool(False)");
+}
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
new file mode 100644
index 000000000000..011c2900e12a
--- /dev/null
+++ b/src/libexpr/nixexpr.hh
@@ -0,0 +1,75 @@
+#ifndef __NIXEXPR_H
+#define __NIXEXPR_H
+
+#include <map>
+
+#include <aterm2.h>
+
+#include "util.hh"
+
+
+/* Nix expressions are represented as ATerms.  The maximal sharing
+   property of the ATerm library allows us to implement caching of
+   normals forms efficiently. */
+typedef ATerm Expr;
+
+
+/* Mappings from ATerms to ATerms.  This is just a wrapper around
+   ATerm tables. */
+class ATermMap
+{
+private:
+    unsigned int maxLoadPct;
+    ATermTable table;
+    
+public:
+    ATermMap(unsigned int initialSize = 16, unsigned int maxLoadPct = 75);
+    ATermMap(const ATermMap & map);
+    ~ATermMap();
+
+    void set(ATerm key, ATerm value);
+    void set(const string & key, ATerm value);
+
+    ATerm get(ATerm key) const;
+    ATerm get(const string & key) const;
+
+    void remove(ATerm key);
+    void remove(const string & key);
+
+    ATermList keys() const;
+};
+
+
+/* Convert a string to an ATerm (i.e., a quoted nullary function
+   applicaton). */
+ATerm string2ATerm(const string & s);
+string aterm2String(ATerm t);
+
+/* Generic bottomup traversal over ATerms.  The traversal first
+   recursively descends into subterms, and then applies the given term
+   function to the resulting term. */
+struct TermFun
+{
+    virtual ATerm operator () (ATerm e) = 0;
+};
+ATerm bottomupRewrite(TermFun & f, ATerm e);
+
+/* Query all attributes in an attribute set expression.  The
+   expression must be in normal form. */
+void queryAllAttrs(Expr e, ATermMap & attrs);
+
+/* Query a specific attribute from an attribute set expression.  The
+   expression must be in normal form. */
+Expr queryAttr(Expr e, const string & name);
+
+/* Create an attribute set expression from an Attrs value. */
+Expr makeAttrs(const ATermMap & attrs);
+
+/* Perform a set of substitutions on an expression. */
+Expr substitute(const ATermMap & subs, Expr e);
+
+/* Create an expression representing a boolean. */
+Expr makeBool(bool b);
+
+
+#endif /* !__NIXEXPR_H */
diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc
new file mode 100644
index 000000000000..b2c74af33e77
--- /dev/null
+++ b/src/libexpr/parser.cc
@@ -0,0 +1,164 @@
+#include <sstream>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+extern "C" {
+#include <sglr.h>
+#include <asfix2.h>
+}
+
+#include "aterm.hh"
+#include "parser.hh"
+#include "shared.hh"
+#include "parse-table.h"
+
+
+/* Cleanup cleans up an imploded parse tree into an actual abstract
+   syntax tree that we can evaluate.  It removes quotes around
+   strings, converts integer literals into actual integers, and
+   absolutises paths relative to the directory containing the input
+   file. */
+struct Cleanup : TermFun
+{
+    string basePath;
+
+    virtual ATerm operator () (ATerm e)
+    {
+        ATMatcher m;
+        string s;
+
+        if (atMatch(m, e) >> "Str" >> s) {
+            return ATmake("Str(<str>)",
+                string(s, 1, s.size() - 2).c_str());
+        }
+
+        if (atMatch(m, e) >> "Path" >> s) {
+            if (s[0] != '/')
+                s = basePath + "/" + s;
+            return ATmake("Path(<str>)", canonPath(s).c_str());
+        }
+
+        if (atMatch(m, e) >> "Int" >> s) {
+            istringstream s2(s);
+            int n;
+            s2 >> n;
+            return ATmake("Int(<int>)", n);
+        }
+
+        if (atMatch(m, e) >> "Bool" >> "true")
+            return ATmake("Bool(True)");
+        
+        if (atMatch(m, e) >> "Bool" >> "false")
+            return ATmake("Bool(False)");
+
+        if (atMatch(m, e) >> "ExprNil")
+            return (ATerm) ATempty;
+
+        ATerm e1;
+        ATermList e2;
+        if (atMatch(m, e) >> "ExprCons" >> e1 >> e2)
+            return (ATerm) ATinsert(e2, e1);
+
+        return e;
+    }
+};
+
+
+Expr parseExprFromFile(Path path)
+{
+#if 0
+    /* Perhaps this is already an imploded parse tree? */
+    Expr e = ATreadFromNamedFile(path.c_str());
+    if (e) return e;
+#endif
+
+    /* If `path' refers to a directory, append `/default.nix'. */
+    struct stat st;
+    if (stat(path.c_str(), &st))
+        throw SysError(format("getting status of `%1%'") % path);
+    if (S_ISDIR(st.st_mode))
+        path = canonPath(path + "/default.nix");
+
+    /* Initialise the SDF libraries. */
+    static bool initialised = false;
+    static ATerm parseTable = 0;
+    static language lang = 0;
+
+    if (!initialised) {
+        PT_initMEPTApi();
+        PT_initAsFix2Api();
+        SGinitParser(ATfalse);
+
+        ATprotect(&parseTable);
+        parseTable = ATreadFromBinaryString(
+            (char *) nixParseTable, sizeof nixParseTable);
+        if (!parseTable)
+            throw Error(format("cannot construct parse table term"));
+
+        ATprotect(&lang);
+        lang = ATmake("Nix");
+        if (!SGopenLanguageFromTerm(
+                (char *) programId.c_str(), lang, parseTable))
+            throw Error(format("cannot open language"));
+
+        SG_STARTSYMBOL_ON();
+        SG_OUTPUT_ON();
+        SG_ASFIX2ME_ON();
+        SG_AMBIGUITY_ERROR_ON();
+        SG_FILTER_OFF();
+
+        initialised = true;
+    }
+
+    /* Read the input file.  We can't use SGparseFile() because it's
+       broken, so we read the input ourselves and call
+       SGparseString(). */
+    AutoCloseFD fd = open(path.c_str(), O_RDONLY);
+    if (fd == -1) throw SysError(format("opening `%1%'") % path);
+
+    if (fstat(fd, &st) == -1)
+        throw SysError(format("statting `%1%'") % path);
+
+    char text[st.st_size + 1];
+    readFull(fd, (unsigned char *) text, st.st_size);
+    text[st.st_size] = 0;
+
+    /* Parse it. */
+    ATerm result = SGparseString(lang, "Expr", text);
+    if (!result)
+        throw SysError(format("parse failed in `%1%'") % path);
+    if (SGisParseError(result))
+        throw Error(format("parse error in `%1%': %2%")
+            % path % result);
+
+    /* Implode it. */
+    PT_ParseTree tree = PT_makeParseTreeFromTerm(result);
+    if (!tree)
+        throw Error(format("cannot create parse tree"));
+    
+    ATerm imploded = PT_implodeParseTree(tree,
+        ATtrue,
+        ATtrue,
+        ATtrue,
+        ATtrue,
+        ATtrue,
+        ATtrue,
+        ATfalse,
+        ATtrue,
+        ATtrue,
+        ATtrue,
+        ATfalse);
+    if (!imploded)
+        throw Error(format("cannot implode parse tree"));
+
+    printMsg(lvlVomit, format("imploded parse tree of `%1%': %2%")
+        % path % imploded);
+
+    /* Finally, clean it up. */
+    Cleanup cleanup;
+    cleanup.basePath = dirOf(path);
+    return bottomupRewrite(cleanup, imploded);
+}
diff --git a/src/libexpr/parser.hh b/src/libexpr/parser.hh
new file mode 100644
index 000000000000..5983ec5629e4
--- /dev/null
+++ b/src/libexpr/parser.hh
@@ -0,0 +1,10 @@
+#ifndef __PARSER_H
+#define __PARSER_H
+
+#include "nixexpr.hh"
+
+
+Expr parseExprFromFile(Path path);
+
+
+#endif /* !__PARSER_H */
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
new file mode 100644
index 000000000000..097933115342
--- /dev/null
+++ b/src/libexpr/primops.cc
@@ -0,0 +1,246 @@
+#include "primops.hh"
+#include "normalise.hh"
+#include "globals.hh"
+
+
+Expr primImport(EvalState & state, Expr arg)
+{
+    ATMatcher m;
+    string path;
+    if (!(atMatch(m, arg) >> "Path" >> path))
+        throw badTerm("path expected", arg);
+    return evalFile(state, path);
+}
+
+
+static PathSet storeExprRootsCached(EvalState & state, const Path & nePath)
+{
+    DrvPaths::iterator i = state.drvPaths.find(nePath);
+    if (i != state.drvPaths.end())
+        return i->second;
+    else {
+        PathSet paths = storeExprRoots(nePath);
+        state.drvPaths[nePath] = paths;
+        return paths;
+    }
+}
+
+
+static Hash hashDerivation(EvalState & state, StoreExpr ne)
+{
+    if (ne.type == StoreExpr::neDerivation) {
+	PathSet inputs2;
+        for (PathSet::iterator i = ne.derivation.inputs.begin();
+             i != ne.derivation.inputs.end(); i++)
+        {
+            DrvHashes::iterator j = state.drvHashes.find(*i);
+            if (j == state.drvHashes.end())
+                throw Error(format("don't know expression `%1%'") % (string) *i);
+            inputs2.insert(j->second);
+        }
+	ne.derivation.inputs = inputs2;
+    }
+    return hashTerm(unparseStoreExpr(ne));
+}
+
+
+static Path copyAtom(EvalState & state, const Path & srcPath)
+{
+    /* !!! should be cached */
+    Path dstPath(addToStore(srcPath));
+
+    ClosureElem elem;
+    StoreExpr ne;
+    ne.type = StoreExpr::neClosure;
+    ne.closure.roots.insert(dstPath);
+    ne.closure.elems[dstPath] = elem;
+
+    Hash drvHash = hashDerivation(state, ne);
+    Path drvPath = writeTerm(unparseStoreExpr(ne), "");
+    state.drvHashes[drvPath] = drvHash;
+
+    printMsg(lvlChatty, format("copied `%1%' -> closure `%2%'")
+        % srcPath % drvPath);
+    return drvPath;
+}
+
+
+static string addInput(EvalState & state, 
+    Path & nePath, StoreExpr & ne)
+{
+    PathSet paths = storeExprRootsCached(state, nePath);
+    if (paths.size() != 1) abort();
+    Path path = *(paths.begin());
+    ne.derivation.inputs.insert(nePath);
+    return path;
+}
+
+
+static string processBinding(EvalState & state, Expr e, StoreExpr & ne)
+{
+    e = evalExpr(state, e);
+
+    ATMatcher m;
+    string s;
+    ATermList es;
+
+    if (atMatch(m, e) >> "Str" >> s) return s;
+    if (atMatch(m, e) >> "Uri" >> s) return s;
+    if (atMatch(m, e) >> "Bool" >> "True") return "1";
+    if (atMatch(m, e) >> "Bool" >> "False") return "";
+
+    if (atMatch(m, e) >> "Attrs" >> es) {
+        Expr a = queryAttr(e, "type");
+        if (a && evalString(state, a) == "derivation") {
+            a = queryAttr(e, "drvPath");
+            if (a) {
+                Path drvPath = evalPath(state, a);
+                return addInput(state, drvPath, ne);
+            }
+        }
+    }
+
+    if (atMatch(m, e) >> "Path" >> s) {
+        Path drvPath = copyAtom(state, s);
+        return addInput(state, drvPath, ne);
+    }
+    
+    if (atMatch(m, e) >> "List" >> es) {
+	string s;
+	bool first = true;
+        for (ATermIterator i(es); i; ++i) {
+            startNest(nest, lvlVomit, format("processing list element"));
+	    if (!first) s = s + " "; else first = false;
+	    s += processBinding(state, evalExpr(state, *i), ne);
+        }
+	return s;
+    }
+
+    if (atMatch(m, e) >> "Null") return "";
+    
+    throw badTerm("invalid derivation binding", e);
+}
+
+
+Expr primDerivation(EvalState & state, Expr args)
+{
+    startNest(nest, lvlVomit, "evaluating derivation");
+
+    ATermMap attrs;
+    args = evalExpr(state, args);
+    queryAllAttrs(args, attrs);
+
+    /* Build the derivation expression by processing the attributes. */
+    StoreExpr ne;
+    ne.type = StoreExpr::neDerivation;
+
+    string drvName;
+    Path outPath;
+    Hash outHash;
+    bool outHashGiven = false;
+
+    for (ATermIterator i(attrs.keys()); i; ++i) {
+        string key = aterm2String(*i);
+        Expr value = attrs.get(key);
+        startNest(nest, lvlVomit, format("processing attribute `%1%'") % key);
+
+        /* The `args' attribute is special: it supplies the
+           command-line arguments to the builder. */
+        if (key == "args") {
+            throw Error("args not implemented");
+#if 0
+            ATermList args;
+            if (!(ATmatch(value, "[<list>]", &args))
+                throw badTerm("list expected", value);
+            while (!ATisEmpty(args)) {
+                Expr arg = evalExpr(state, ATgetFirst(args));
+                ne.derivation.args.push_back(processBinding(state, arg, ne));
+                args = ATgetNext(args);
+            }
+#endif
+        }
+
+        /* All other attributes are passed to the builder through the
+           environment. */
+        else {
+            string s = processBinding(state, value, ne);
+            ne.derivation.env[key] = s;
+            if (key == "builder") ne.derivation.builder = s;
+            else if (key == "system") ne.derivation.platform = s;
+            else if (key == "name") drvName = s;
+            else if (key == "outPath") outPath = s;
+            else if (key == "id") { 
+                outHash = parseHash(s);
+                outHashGiven = true;
+            }
+        }
+    }
+    
+    /* Do we have all required attributes? */
+    if (ne.derivation.builder == "")
+        throw badTerm("required attribute `builder' missing", args);
+    if (ne.derivation.platform == "")
+        throw badTerm("required attribute `system' missing", args);
+    if (drvName == "")
+        throw badTerm("required attribute `name' missing", args);
+        
+    /* Determine the output path. */
+    if (!outHashGiven) outHash = hashDerivation(state, ne);
+    if (outPath == "")
+        /* Hash the Nix expression with no outputs to produce a
+           unique but deterministic path name for this derivation. */
+        outPath = canonPath(nixStore + "/" + 
+            ((string) outHash).c_str() + "-" + drvName);
+    ne.derivation.env["out"] = outPath;
+    ne.derivation.outputs.insert(outPath);
+
+    /* Write the resulting term into the Nix store directory. */
+    Hash drvHash = outHashGiven
+        ? hashString((string) outHash + outPath)
+        : hashDerivation(state, ne);
+    Path drvPath = writeTerm(unparseStoreExpr(ne), "-d-" + drvName);
+    state.drvHashes[drvPath] = drvHash;
+
+    printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'")
+        % drvName % drvPath);
+
+    attrs.set("outPath", ATmake("Path(<str>)", outPath.c_str()));
+    attrs.set("drvPath", ATmake("Path(<str>)", drvPath.c_str()));
+    attrs.set("type", ATmake("Str(\"derivation\")"));
+
+    return makeAttrs(attrs);
+}
+
+
+Expr primBaseNameOf(EvalState & state, Expr arg)
+{
+    string s = evalString(state, arg);
+    return ATmake("Str(<str>)", baseNameOf(s).c_str());
+}
+
+
+Expr primToString(EvalState & state, Expr arg)
+{
+    arg = evalExpr(state, arg);
+    ATMatcher m;
+    string s;
+    if (atMatch(m, arg) >> "Str" >> s ||
+        atMatch(m, arg) >> "Path" >> s ||
+        atMatch(m, arg) >> "Uri" >> s)
+        return ATmake("Str(<str>)", s.c_str());
+    else throw badTerm("cannot coerce to string", arg);
+}
+
+
+Expr primNull(EvalState & state)
+{
+    return ATmake("Null");
+}
+
+
+Expr primIsNull(EvalState & state, Expr arg)
+{
+    arg = evalExpr(state, arg);
+    ATMatcher m;
+    return makeBool(atMatch(m, arg) >> "Null");
+}
diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh
new file mode 100644
index 000000000000..76d587afdb1e
--- /dev/null
+++ b/src/libexpr/primops.hh
@@ -0,0 +1,34 @@
+#ifndef __PRIMOPS_H
+#define __PRIMOPS_H
+
+#include "eval.hh"
+
+
+/* Load and evaluate an expression from path specified by the
+   argument. */ 
+Expr primImport(EvalState & state, Expr arg);
+
+/* Construct (as a unobservable) side effect) a Nix derivation
+   expression that performs the derivation described by the argument
+   set.  Returns the original set extended with the following
+   attributes: `outPath' containing the primary output path of the
+   derivation; `drvPath' containing the path of the Nix expression;
+   and `type' set to `derivation' to indicate that this is a
+   derivation. */
+Expr primDerivation(EvalState & state, Expr args);
+
+/* Return the base name of the given string, i.e., everything
+   following the last slash. */
+Expr primBaseNameOf(EvalState & state, Expr arg);
+
+/* Convert the argument (which can be a path or a uri) to a string. */
+Expr primToString(EvalState & state, Expr arg);
+
+/* Return the null value. */
+Expr primNull(EvalState & state);
+
+/* Determine whether the argument is the null value. */
+Expr primIsNull(EvalState & state, Expr arg);
+
+
+#endif /* !__PRIMOPS_H */