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/eval.cc82
-rw-r--r--src/libexpr/eval.hh25
-rw-r--r--src/libexpr/nixexpr.cc68
-rw-r--r--src/libexpr/nixexpr.hh2
-rw-r--r--src/libexpr/parser.y12
-rw-r--r--src/libexpr/primops.cc99
-rw-r--r--src/libexpr/symbol-table.hh6
-rw-r--r--src/libexpr/value-to-json.cc11
-rw-r--r--src/libexpr/value-to-xml.cc11
-rw-r--r--src/libexpr/value.hh50
10 files changed, 251 insertions, 115 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 5b9db6eeaa6e..298f6a3a60e3 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -104,6 +104,9 @@ static void printValue(std::ostream & str, std::set<const Value *> & active, con
     case tPrimOpApp:
         str << "<PRIMOP-APP>";
         break;
+    case tExternal:
+        str << *v.external;
+        break;
     default:
         throw Error("invalid value");
     }
@@ -136,6 +139,7 @@ string showType(const Value & v)
         case tBlackhole: return "a black hole";
         case tPrimOp: return "a built-in function";
         case tPrimOpApp: return "a partially applied built-in function";
+        case tExternal: return v.external->showType();
     }
     abort();
 }
@@ -180,6 +184,7 @@ EvalState::EvalState(const Strings & _searchPath)
     , sFile(symbols.create("file"))
     , sLine(symbols.create("line"))
     , sColumn(symbols.create("column"))
+    , sFunctor(symbols.create("__functor"))
     , repair(false)
     , baseEnv(allocEnv(128))
     , staticBaseEnv(false, 0)
@@ -212,9 +217,9 @@ EvalState::EvalState(const Strings & _searchPath)
            allocated.  This might be a problem on systems that don't
            overcommit. */
         if (!getenv("GC_INITIAL_HEAP_SIZE")) {
-            size_t maxSize = 384 * 1024 * 1024;
             size_t size = 32 * 1024 * 1024;
 #if HAVE_SYSCONF && defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
+            size_t maxSize = 384 * 1024 * 1024;
             long pageSize = sysconf(_SC_PAGESIZE);
             long pages = sysconf(_SC_PHYS_PAGES);
             if (pageSize != -1)
@@ -242,7 +247,6 @@ EvalState::EvalState(const Strings & _searchPath)
 EvalState::~EvalState()
 {
     fileEvalCache.clear();
-    printCanaries();
 }
 
 
@@ -312,11 +316,6 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, co
     throw EvalError(format(s) % sym % p1 % p2);
 }
 
-LocalNoInlineNoReturn(void throwTypeError(const char * s))
-{
-    throw TypeError(s);
-}
-
 LocalNoInlineNoReturn(void throwTypeError(const char * s, const Pos & pos))
 {
     throw TypeError(format(s) % pos);
@@ -327,11 +326,6 @@ LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1))
     throw TypeError(format(s) % s1);
 }
 
-LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1, const string & s2))
-{
-    throw TypeError(format(s) % s1 % s2);
-}
-
 LocalNoInlineNoReturn(void throwTypeError(const char * s, const ExprLambda & fun, const Symbol & s2, const Pos & pos))
 {
     throw TypeError(format(s) % fun.showNamePos() % s2 % pos);
@@ -895,6 +889,17 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
         return;
     }
 
+    if (fun.type == tAttrs) {
+      auto found = fun.attrs->find(sFunctor);
+      if (found != fun.attrs->end()) {
+        forceValue(*found->value);
+        Value * v2 = allocValue();
+        callFunction(*found->value, fun, *v2, pos);
+        forceValue(*v2);
+        return callFunction(*v2, arg, v, pos);
+      }
+    }
+
     if (fun.type != tLambda)
         throwTypeError("attempt to call something which is not a function but %1%, at %2%", fun, pos);
 
@@ -1255,9 +1260,9 @@ void copyContext(const Value & v, PathSet & context)
 }
 
 
-string EvalState::forceString(Value & v, PathSet & context)
+string EvalState::forceString(Value & v, PathSet & context, const Pos & pos)
 {
-    string s = forceString(v);
+    string s = forceString(v, pos);
     copyContext(v, context);
     return s;
 }
@@ -1312,6 +1317,9 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
         return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
     }
 
+    if (v.type == tExternal)
+        return v.external->coerceToString(pos, context, coerceMore, copyToStore);
+
     if (coerceMore) {
 
         /* Note that `false' is represented as an empty string for
@@ -1432,6 +1440,9 @@ bool EvalState::eqValues(Value & v1, Value & v2)
         case tPrimOpApp:
             return false;
 
+        case tExternal:
+            return *v1.external == *v2.external;
+
         default:
             throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2));
     }
@@ -1502,26 +1513,6 @@ void EvalState::printStats()
 }
 
 
-void EvalState::printCanaries()
-{
-#if HAVE_BOEHMGC
-    if (!settings.get("debug-gc", false)) return;
-
-    GC_gcollect();
-
-    if (gcCanaries.empty()) {
-        printMsg(lvlError, "all canaries have been garbage-collected");
-        return;
-    }
-
-    printMsg(lvlError, "the following canaries have not been garbage-collected:");
-
-    for (auto i : gcCanaries)
-        printMsg(lvlError, format("  %1%") % i->string.s);
-#endif
-}
-
-
 size_t valueSize(Value & v)
 {
     std::set<const void *> seen;
@@ -1573,6 +1564,11 @@ size_t valueSize(Value & v)
             sz += doValue(*v.primOpApp.left);
             sz += doValue(*v.primOpApp.right);
             break;
+        case tExternal:
+            if (seen.find(v.external) != seen.end()) break;
+            seen.insert(v.external);
+            sz += v.external->valueSize(seen);
+            break;
         default:
             ;
         }
@@ -1599,4 +1595,22 @@ size_t valueSize(Value & v)
 }
 
 
+string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
+{
+    throw TypeError(format("cannot coerce %1% to a string, at %2%") %
+        showType() % pos);
+}
+
+
+bool ExternalValueBase::operator==(const ExternalValueBase & b) const
+{
+    return false;
+}
+
+
+std::ostream & operator << (std::ostream & str, const ExternalValueBase & v) {
+    return v.print(str);
+}
+
+
 }
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index daf53846ffd5..f7415fb78dfd 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -39,10 +39,10 @@ public:
     typedef uint32_t size_t;
 
 private:
-    size_t size_, capacity;
+    size_t size_, capacity_;
     Attr attrs[0];
 
-    Bindings(uint32_t capacity) : size_(0), capacity(capacity) { }
+    Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
     Bindings(const Bindings & bindings) = delete;
 
 public:
@@ -54,7 +54,7 @@ public:
 
     void push_back(const Attr & attr)
     {
-        assert(size_ < capacity);
+        assert(size_ < capacity_);
         attrs[size_++] = attr;
     }
 
@@ -76,6 +76,8 @@ public:
 
     void sort();
 
+    size_t capacity() { return capacity_; }
+
     friend class EvalState;
 };
 
@@ -126,7 +128,7 @@ public:
 
     const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
         sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
-        sFile, sLine, sColumn;
+        sFile, sLine, sColumn, sFunctor;
     Symbol sDerivationNix;
 
     /* If set, force copying files to the Nix store even if they
@@ -169,7 +171,7 @@ public:
 
     /* Look up a file in the search path. */
     Path findFile(const string & path);
-    Path findFile(SearchPath & searchPath, const string & path);
+    Path findFile(SearchPath & searchPath, const string & path, const Pos & pos = noPos);
 
     /* Evaluate an expression to normal form, storing the result in
        value `v'. */
@@ -200,7 +202,7 @@ public:
     inline void forceList(Value & v, const Pos & pos);
     void forceFunction(Value & v, const Pos & pos); // either lambda or primop
     string forceString(Value & v, const Pos & pos = noPos);
-    string forceString(Value & v, PathSet & context);
+    string forceString(Value & v, PathSet & context, const Pos & pos = noPos);
     string forceStringNoCtx(Value & v, const Pos & pos = noPos);
 
     /* Return true iff the value `v' denotes a derivation (i.e. a
@@ -287,8 +289,6 @@ public:
     /* Print statistics. */
     void printStats();
 
-    void printCanaries();
-
 private:
 
     unsigned long nrEnvs;
@@ -320,12 +320,6 @@ private:
     friend struct ExprOpConcatLists;
     friend struct ExprSelect;
     friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v);
-
-#if HAVE_BOEHMGC
-    std::set<Value *> gcCanaries;
-    friend void canaryFinalizer(GC_PTR obj, GC_PTR client_data);
-    friend void prim_gcCanary(EvalState & state, const Pos & pos, Value * * args, Value & v);
-#endif
 };
 
 
@@ -340,6 +334,9 @@ struct InvalidPathError : EvalError
 {
     Path path;
     InvalidPathError(const Path & path);
+#ifdef EXCEPTION_NEEDS_THROW_SPEC
+    ~InvalidPathError() throw () { };
+#endif
 };
 
 /* Realise all paths in `context' */
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index c8521718b82a..50997e096fd1 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -16,6 +16,47 @@ std::ostream & operator << (std::ostream & str, Expr & e)
     return str;
 }
 
+static void showString(std::ostream & str, const string & s)
+{
+    str << '"';
+    for (auto c : (string) s)
+        if (c == '"' || c == '\\' || c == '$') str << "\\" << c;
+        else if (c == '\n') str << "\\n";
+        else if (c == '\r') str << "\\r";
+        else if (c == '\t') str << "\\t";
+        else str << c;
+    str << '"';
+}
+
+static void showId(std::ostream & str, const string & s)
+{
+    assert(!s.empty());
+    if (s == "if")
+        str << '"' << s << '"';
+    else {
+        char c = s[0];
+        if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) {
+            showString(str, s);
+            return;
+        }
+        for (auto c : s)
+            if (!((c >= 'a' && c <= 'z') ||
+                  (c >= 'A' && c <= 'Z') ||
+                  (c >= '0' && c <= '9') ||
+                  c == '_' || c == '\'' || c == '-')) {
+                showString(str, s);
+                return;
+            }
+        str << s;
+    }
+}
+
+std::ostream & operator << (std::ostream & str, const Symbol & sym)
+{
+    showId(str, *sym.s);
+    return str;
+}
+
 void Expr::show(std::ostream & str)
 {
     abort();
@@ -28,7 +69,7 @@ void ExprInt::show(std::ostream & str)
 
 void ExprString::show(std::ostream & str)
 {
-    str << "\"" << s << "\""; // !!! escaping
+    showString(str, s);
 }
 
 void ExprPath::show(std::ostream & str)
@@ -44,12 +85,12 @@ void ExprVar::show(std::ostream & str)
 void ExprSelect::show(std::ostream & str)
 {
     str << "(" << *e << ")." << showAttrPath(attrPath);
-    if (def) str << " or " << *def;
+    if (def) str << " or (" << *def << ")";
 }
 
 void ExprOpHasAttr::show(std::ostream & str)
 {
-    str << "(" << *e << ") ? " << showAttrPath(attrPath);
+    str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")";
 }
 
 void ExprAttrs::show(std::ostream & str)
@@ -85,6 +126,10 @@ void ExprLambda::show(std::ostream & str)
             str << i->name;
             if (i->def) str << " ? " << *i->def;
         }
+        if (formals->ellipsis) {
+            if (!first) str << ", ";
+            str << "...";
+        }
         str << " }";
         if (!arg.empty()) str << " @ ";
     }
@@ -94,23 +139,24 @@ void ExprLambda::show(std::ostream & str)
 
 void ExprLet::show(std::ostream & str)
 {
-    str << "let ";
+    str << "(let ";
     foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs)
-        if (i->second.inherited)
+        if (i->second.inherited) {
             str << "inherit " << i->first << "; ";
+        }
         else
             str << i->first << " = " << *i->second.e << "; ";
-    str << "in " << *body;
+    str << "in " << *body << ")";
 }
 
 void ExprWith::show(std::ostream & str)
 {
-    str << "with " << *attrs << "; " << *body;
+    str << "(with " << *attrs << "; " << *body << ")";
 }
 
 void ExprIf::show(std::ostream & str)
 {
-    str << "if " << *cond << " then " << *then << " else " << *else_;
+    str << "(if " << *cond << " then " << *then << " else " << *else_ << ")";
 }
 
 void ExprAssert::show(std::ostream & str)
@@ -120,16 +166,18 @@ void ExprAssert::show(std::ostream & str)
 
 void ExprOpNot::show(std::ostream & str)
 {
-    str << "! " << *e;
+    str << "(! " << *e << ")";
 }
 
 void ExprConcatStrings::show(std::ostream & str)
 {
     bool first = true;
+    str << "(";
     foreach (vector<Expr *>::iterator, i, *es) {
         if (first) first = false; else str << " + ";
         str << **i;
     }
+    str << ")";
 }
 
 void ExprPos::show(std::ostream & str)
@@ -143,7 +191,7 @@ std::ostream & operator << (std::ostream & str, const Pos & pos)
     if (!pos)
         str << "undefined position";
     else
-        str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % pos.file % pos.line % pos.column).str();
+        str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % (string) pos.file % pos.line % pos.column).str();
     return str;
 }
 
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 0eaa362fd68b..121dc58f25f7 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -280,7 +280,7 @@ struct ExprOpNot : Expr
         Expr##name(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
         void show(std::ostream & str) \
         { \
-            str << *e1 << " " s " " << *e2; \
+            str << "(" << *e1 << " " s " " << *e2 << ")";   \
         } \
         void bindVars(const StaticEnv & env) \
         { \
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index dcb270b862a3..7d877cd67862 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -53,10 +53,6 @@ namespace nix {
 #include "parser-tab.hh"
 #include "lexer-tab.hh"
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
 YY_DECL;
 
 using namespace nix;
@@ -630,7 +626,7 @@ Path EvalState::findFile(const string & path)
 }
 
 
-Path EvalState::findFile(SearchPath & searchPath, const string & path)
+Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos & pos)
 {
     foreach (SearchPath::iterator, i, searchPath) {
         Path res;
@@ -645,7 +641,11 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path)
         }
         if (pathExists(res)) return canonPath(res);
     }
-    throw ThrownError(format("file ‘%1%’ was not found in the Nix search path (add it using $NIX_PATH or -I)") % path);
+    format f = format(
+        "file ‘%1%’ was not found in the Nix search path (add it using $NIX_PATH or -I)"
+        + string(pos ? ", at %2%" : ""));
+    f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
+    throw ThrownError(f % path % pos);
 }
 
 
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index a7884cb85521..6c30e6d549ec 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -35,7 +35,7 @@ std::pair<string, string> decodeContext(const string & s)
         size_t index = s.find("!", 1);
         return std::pair<string, string>(string(s, index + 1), string(s, 1, index - 1));
     } else
-        return std::pair<string, string>(s, "");
+        return std::pair<string, string>(s.at(0) == '/' ? s: string(s, 1), "");
 }
 
 
@@ -51,7 +51,7 @@ void realiseContext(const PathSet & context)
         assert(isStorePath(ctx));
         if (!store->isValidPath(ctx))
             throw InvalidPathError(ctx);
-        if (isDerivation(ctx))
+        if (!decoded.second.empty() && isDerivation(ctx))
             drvs.insert(decoded.first + "!" + decoded.second);
     }
     if (!drvs.empty()) {
@@ -84,16 +84,19 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
         Derivation drv = readDerivation(path);
         Value & w = *state.allocValue();
         state.mkAttrs(w, 2 + drv.outputs.size());
-        mkString(*state.allocAttr(w, state.sDrvPath), path, singleton<PathSet>("=" + path));
-        state.mkList(*state.allocAttr(w, state.symbols.create("outputs")), drv.outputs.size());
+        Value * v2 = state.allocAttr(w, state.sDrvPath);
+        mkString(*v2, path, singleton<PathSet>("=" + path));
+        Value * outputsVal =
+            state.allocAttr(w, state.symbols.create("outputs"));
+        state.mkList(*outputsVal, drv.outputs.size());
         unsigned int outputs_index = 0;
 
-        Value * outputsVal = w.attrs->find(state.symbols.create("outputs"))->value;
-        foreach (DerivationOutputs::iterator, i, drv.outputs) {
-            mkString(*state.allocAttr(w, state.symbols.create(i->first)),
-                i->second.path, singleton<PathSet>("!" + i->first + "!" + path));
-            mkString(*(outputsVal->list.elems[outputs_index++] = state.allocValue()),
-                i->first);
+        for (const auto & o : drv.outputs) {
+            v2 = state.allocAttr(w, state.symbols.create(o.first));
+            mkString(*v2, o.second.path,
+                singleton<PathSet>("!" + o.first + "!" + path));
+            outputsVal->list.elems[outputs_index] = state.allocValue();
+            mkString(*(outputsVal->list.elems[outputs_index++]), o.first);
         }
         w.attrs->sort();
         Value fun;
@@ -184,6 +187,9 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
         case tPrimOpApp:
             t = "lambda";
             break;
+	case tExternal:
+            t = args[0]->external->typeOf();
+            break;
         default: abort();
     }
     mkString(v, state.symbols.create(t));
@@ -411,32 +417,6 @@ static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value
 }
 
 
-#if HAVE_BOEHMGC
-void canaryFinalizer(GC_PTR obj, GC_PTR client_data)
-{
-    Value * v = (Value *) obj;
-    EvalState & state(* (EvalState *) client_data);
-    printMsg(lvlError, format("canary ‘%1%’ garbage-collected") % v->string.s);
-    auto i = state.gcCanaries.find(v);
-    assert(i != state.gcCanaries.end());
-    state.gcCanaries.erase(i);
-}
-#endif
-
-
-void prim_gcCanary(EvalState & state, const Pos & pos, Value * * args, Value & v)
-{
-    string s = state.forceStringNoCtx(*args[0], pos);
-    state.mkList(v, 1);
-    Value * canary = v.list.elems[0] = state.allocValue();
-#if HAVE_BOEHMGC
-    state.gcCanaries.insert(canary);
-    GC_register_finalizer(canary, canaryFinalizer, &state, 0, 0);
-#endif
-    mkString(*canary, s);
-}
-
-
 void prim_valueSize(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     /* We're not forcing the argument on purpose. */
@@ -750,8 +730,12 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va
 {
     PathSet context;
     Path path = state.coerceToPath(pos, *args[0], context);
-    if (!context.empty())
-        throw EvalError(format("string ‘%1%’ cannot refer to other paths, at %2%") % path % pos);
+    try {
+        realiseContext(context);
+    } catch (InvalidPathError & e) {
+        throw EvalError(format("cannot read ‘%1%’, since path ‘%2%’ is not valid, at %3%")
+            % path % e.path % pos);
+    }
     mkString(v, readFile(path).c_str());
 }
 
@@ -791,7 +775,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
             % path % e.path % pos);
     }
 
-    mkPath(v, state.findFile(searchPath, path).c_str());
+    mkPath(v, state.findFile(searchPath, path, pos).c_str());
 }
 
 /* Read a directory (without . or ..) */
@@ -812,7 +796,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
     for (auto & ent : entries) {
         Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name));
         if (ent.type == DT_UNKNOWN)
-            ent.type = getFileType(path);
+            ent.type = getFileType(path + "/" + ent.name);
         mkStringNoCopy(*ent_val,
             ent.type == DT_REG ? "regular" :
             ent.type == DT_DIR ? "directory" :
@@ -867,7 +851,7 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
 {
     PathSet context;
     string name = state.forceStringNoCtx(*args[0], pos);
-    string contents = state.forceString(*args[1], context);
+    string contents = state.forceString(*args[1], context, pos);
 
     PathSet refs;
 
@@ -1424,10 +1408,37 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args,
       throw Error(format("unknown hash type ‘%1%’, at %2%") % type % pos);
 
     PathSet context; // discarded
-    string s = state.forceString(*args[1], context);
+    string s = state.forceString(*args[1], context, pos);
 
     mkString(v, printHash(hashString(ht, s)), context);
-};
+}
+
+
+/* Match a regular expression against a string and return either
+   ‘null’ or a list containing substring matches. */
+static void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    Regex regex(state.forceStringNoCtx(*args[0], pos), true);
+
+    PathSet context;
+    string s = state.forceString(*args[1], context, pos);
+
+    Regex::Subs subs;
+    if (!regex.matches(s, subs)) {
+        mkNull(v);
+        return;
+    }
+
+    unsigned int len = subs.empty() ? 0 : subs.rbegin()->first + 1;
+    state.mkList(v, len);
+    for (unsigned int n = 0; n < len; ++n) {
+        auto i = subs.find(n);
+        if (i == subs.end())
+            mkNull(*(v.list.elems[n] = state.allocValue()));
+        else
+            mkString(*(v.list.elems[n] = state.allocValue()), i->second);
+    }
+}
 
 
 /*************************************************************
@@ -1523,7 +1534,6 @@ void EvalState::createBaseEnv()
 
     // Debugging
     addPrimOp("__trace", 2, prim_trace);
-    addPrimOp("__gcCanary", 1, prim_gcCanary);
     addPrimOp("__valueSize", 1, prim_valueSize);
 
     // Paths
@@ -1581,6 +1591,7 @@ void EvalState::createBaseEnv()
     addPrimOp("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
     addPrimOp("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency);
     addPrimOp("__hashString", 2, prim_hashString);
+    addPrimOp("__match", 2, prim_match);
 
     // Versions
     addPrimOp("__parseDrvName", 1, prim_parseDrvName);
diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh
index 140662b51501..2fdf820211c8 100644
--- a/src/libexpr/symbol-table.hh
+++ b/src/libexpr/symbol-table.hh
@@ -58,12 +58,6 @@ public:
     friend std::ostream & operator << (std::ostream & str, const Symbol & sym);
 };
 
-inline std::ostream & operator << (std::ostream & str, const Symbol & sym)
-{
-    str << *sym.s;
-    return str;
-}
-
 class SymbolTable
 {
 private:
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
index d1ec9b566d66..cdb71341875a 100644
--- a/src/libexpr/value-to-json.cc
+++ b/src/libexpr/value-to-json.cc
@@ -80,10 +80,21 @@ void printValueAsJSON(EvalState & state, bool strict,
             break;
         }
 
+	case tExternal:
+            v.external->printValueAsJSON(state, strict, str, context);
+            break;
+
         default:
             throw TypeError(format("cannot convert %1% to JSON") % showType(v));
     }
 }
 
 
+void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
+      std::ostream & str, PathSet & context) const
+{
+    throw TypeError(format("cannot convert %1% to JSON") % showType());
+}
+
+
 }
diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc
index 3934a83eec25..bbbb7039bf70 100644
--- a/src/libexpr/value-to-xml.cc
+++ b/src/libexpr/value-to-xml.cc
@@ -144,12 +144,23 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
             break;
         }
 
+        case tExternal:
+            v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen);
+            break;
+
         default:
             doc.writeEmptyElement("unevaluated");
     }
 }
 
 
+void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
+    bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen) const
+{
+    doc.writeEmptyElement("unevaluated");
+}
+
+
 void printValueAsXML(EvalState & state, bool strict, bool location,
     Value & v, std::ostream & out, PathSet & context)
 {
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index 5f18f629e632..c06b5a6d1153 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -19,6 +19,7 @@ typedef enum {
     tBlackhole,
     tPrimOp,
     tPrimOpApp,
+    tExternal,
 } ValueType;
 
 
@@ -29,10 +30,58 @@ struct ExprLambda;
 struct PrimOp;
 struct PrimOp;
 class Symbol;
+struct Pos;
+class EvalState;
+class XMLWriter;
 
 
 typedef long NixInt;
 
+/* External values must descend from ExternalValueBase, so that
+ * type-agnostic nix functions (e.g. showType) can be implemented
+ */
+class ExternalValueBase
+{
+    friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
+    protected:
+    /* Print out the value */
+    virtual std::ostream & print(std::ostream & str) const = 0;
+
+    public:
+    /* Return a simple string describing the type */
+    virtual string showType() const = 0;
+
+    /* Return a string to be used in builtins.typeOf */
+    virtual string typeOf() const = 0;
+
+    /* How much space does this value take up */
+    virtual size_t valueSize(std::set<const void *> & seen) const = 0;
+
+    /* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
+     * error
+     */
+    virtual string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
+
+    /* Compare to another value of the same type. Defaults to uncomparable,
+     * i.e. always false.
+     */
+    virtual bool operator==(const ExternalValueBase & b) const;
+
+    /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
+    virtual void printValueAsJSON(EvalState & state, bool strict,
+        std::ostream & str, PathSet & context) const;
+
+    /* Print the value as XML. Defaults to unevaluated */
+    virtual void printValueAsXML(EvalState & state, bool strict, bool location,
+        XMLWriter & doc, PathSet & context, PathSet & drvsSeen) const;
+
+    virtual ~ExternalValueBase()
+    {
+    };
+};
+
+std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
+
 
 struct Value
 {
@@ -88,6 +137,7 @@ struct Value
         struct {
             Value * left, * right;
         } primOpApp;
+        ExternalValueBase * external;
     };
 };