about summary refs log tree commit diff
path: root/third_party/nix/src/libexpr
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/nix/src/libexpr')
-rw-r--r--third_party/nix/src/libexpr/CMakeLists.txt85
-rw-r--r--third_party/nix/src/libexpr/attr-path.cc109
-rw-r--r--third_party/nix/src/libexpr/attr-path.hh13
-rw-r--r--third_party/nix/src/libexpr/attr-set.cc122
-rw-r--r--third_party/nix/src/libexpr/attr-set.hh76
-rw-r--r--third_party/nix/src/libexpr/common-eval-args.cc72
-rw-r--r--third_party/nix/src/libexpr/common-eval-args.hh26
-rw-r--r--third_party/nix/src/libexpr/eval-inline.hh90
-rw-r--r--third_party/nix/src/libexpr/eval.cc1878
-rw-r--r--third_party/nix/src/libexpr/eval.hh365
-rw-r--r--third_party/nix/src/libexpr/function-trace.cc19
-rw-r--r--third_party/nix/src/libexpr/function-trace.hh14
-rw-r--r--third_party/nix/src/libexpr/get-drvs.cc446
-rw-r--r--third_party/nix/src/libexpr/get-drvs.hh83
-rw-r--r--third_party/nix/src/libexpr/json-to-value.cc185
-rw-r--r--third_party/nix/src/libexpr/json-to-value.hh13
-rw-r--r--third_party/nix/src/libexpr/lexer.l193
-rw-r--r--third_party/nix/src/libexpr/names.cc121
-rw-r--r--third_party/nix/src/libexpr/names.hh31
-rw-r--r--third_party/nix/src/libexpr/nix-expr.pc.in10
-rw-r--r--third_party/nix/src/libexpr/nixexpr.cc414
-rw-r--r--third_party/nix/src/libexpr/nixexpr.hh353
-rw-r--r--third_party/nix/src/libexpr/parser.cc332
-rw-r--r--third_party/nix/src/libexpr/parser.hh100
-rw-r--r--third_party/nix/src/libexpr/parser.y359
-rw-r--r--third_party/nix/src/libexpr/primops.cc2335
-rw-r--r--third_party/nix/src/libexpr/primops.hh17
-rw-r--r--third_party/nix/src/libexpr/primops/context.cc202
-rw-r--r--third_party/nix/src/libexpr/primops/fetchGit.cc277
-rw-r--r--third_party/nix/src/libexpr/primops/fetchMercurial.cc246
-rw-r--r--third_party/nix/src/libexpr/primops/fromTOML.cc94
-rw-r--r--third_party/nix/src/libexpr/symbol-table.cc24
-rw-r--r--third_party/nix/src/libexpr/symbol-table.hh69
-rw-r--r--third_party/nix/src/libexpr/value-to-json.cc91
-rw-r--r--third_party/nix/src/libexpr/value-to-json.hh19
-rw-r--r--third_party/nix/src/libexpr/value-to-xml.cc184
-rw-r--r--third_party/nix/src/libexpr/value-to-xml.hh14
-rw-r--r--third_party/nix/src/libexpr/value.cc121
-rw-r--r--third_party/nix/src/libexpr/value.hh191
39 files changed, 0 insertions, 9393 deletions
diff --git a/third_party/nix/src/libexpr/CMakeLists.txt b/third_party/nix/src/libexpr/CMakeLists.txt
deleted file mode 100644
index 8cb7143d2c..0000000000
--- a/third_party/nix/src/libexpr/CMakeLists.txt
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- mode: cmake; -*-
-add_library(nixexpr SHARED)
-set_property(TARGET nixexpr PROPERTY CXX_STANDARD 17)
-include_directories(${PROJECT_BINARY_DIR}) # for 'generated/'
-target_include_directories(nixexpr PUBLIC "${nix_SOURCE_DIR}/src")
-
-# Generate lexer & parser for inclusion:
-find_package(BISON)
-find_package(FLEX)
-
-BISON_TARGET(NixParser parser.y
-  ${PROJECT_BINARY_DIR}/generated/parser-tab.cc
-  DEFINES_FILE ${PROJECT_BINARY_DIR}/generated/parser-tab.hh)
-
-FLEX_TARGET(NixLexer lexer.l
-  ${PROJECT_BINARY_DIR}/generated/lexer-tab.cc
-  DEFINES_FILE ${PROJECT_BINARY_DIR}/generated/lexer-tab.hh)
-
-ADD_FLEX_BISON_DEPENDENCY(NixLexer NixParser)
-
-set(HEADER_FILES
-    attr-path.hh
-    attr-set.hh
-    common-eval-args.hh
-    eval.hh
-    eval-inline.hh
-    function-trace.hh
-    get-drvs.hh
-    json-to-value.hh
-    names.hh
-    nixexpr.hh
-    parser.hh
-    primops.hh
-    symbol-table.hh
-    value.hh
-    value-to-json.hh
-    value-to-xml.hh
-)
-
-target_sources(nixexpr
-  PUBLIC
-    ${HEADER_FILES}
-
-  PRIVATE
-    ${PROJECT_BINARY_DIR}/generated/parser-tab.hh
-    ${PROJECT_BINARY_DIR}/generated/parser-tab.cc
-    ${PROJECT_BINARY_DIR}/generated/lexer-tab.hh
-    ${PROJECT_BINARY_DIR}/generated/lexer-tab.cc
-    primops/context.cc
-    primops/fetchGit.cc
-    primops/fetchMercurial.cc
-    primops/fromTOML.cc
-    attr-path.cc
-    attr-set.cc
-    common-eval-args.cc
-    eval.cc
-    function-trace.cc
-    get-drvs.cc
-    json-to-value.cc
-    names.cc
-    nixexpr.cc
-    parser.cc
-    primops.cc
-    symbol-table.cc
-    value.cc
-    value-to-json.cc
-    value-to-xml.cc
-)
-
-target_link_libraries(nixexpr
-  nixmain
-  nixstore
-  nixutil
-
-  absl::btree
-  absl::flat_hash_set
-  absl::node_hash_set
-  absl::strings
-)
-
-configure_file("nix-expr.pc.in" "${PROJECT_BINARY_DIR}/nix-expr.pc" @ONLY)
-INSTALL(FILES "${PROJECT_BINARY_DIR}/nix-expr.pc" DESTINATION "${PKGCONFIG_INSTALL_DIR}")
-
-INSTALL(FILES ${HEADER_FILES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nix/libexpr)
-INSTALL(TARGETS nixexpr DESTINATION ${CMAKE_INSTALL_LIBDIR})
diff --git a/third_party/nix/src/libexpr/attr-path.cc b/third_party/nix/src/libexpr/attr-path.cc
deleted file mode 100644
index 86ebeec2fb..0000000000
--- a/third_party/nix/src/libexpr/attr-path.cc
+++ /dev/null
@@ -1,109 +0,0 @@
-#include "libexpr/attr-path.hh"
-
-#include <absl/strings/numbers.h>
-
-#include "libexpr/eval-inline.hh"
-#include "libutil/util.hh"
-
-namespace nix {
-
-static Strings parseAttrPath(const std::string& s) {
-  Strings res;
-  std::string cur;
-  std::string::const_iterator i = s.begin();
-  while (i != s.end()) {
-    if (*i == '.') {
-      res.push_back(cur);
-      cur.clear();
-    } else if (*i == '"') {
-      ++i;
-      while (true) {
-        if (i == s.end()) {
-          throw Error(format("missing closing quote in selection path '%1%'") %
-                      s);
-        }
-        if (*i == '"') {
-          break;
-        }
-        cur.push_back(*i++);
-      }
-    } else {
-      cur.push_back(*i);
-    }
-    ++i;
-  }
-  if (!cur.empty()) {
-    res.push_back(cur);
-  }
-  return res;
-}
-
-Value* findAlongAttrPath(EvalState& state, const std::string& attrPath,
-                         Bindings* autoArgs, Value& vIn) {
-  Strings tokens = parseAttrPath(attrPath);
-
-  Error attrError =
-      Error(format("attribute selection path '%1%' does not match expression") %
-            attrPath);
-
-  Value* v = &vIn;
-
-  for (auto& attr : tokens) {
-    /* Is i an index (integer) or a normal attribute name? */
-    enum { apAttr, apIndex } apType = apAttr;
-    unsigned int attrIndex;
-    if (absl::SimpleAtoi(attr, &attrIndex)) {
-      apType = apIndex;
-    }
-
-    /* Evaluate the expression. */
-    Value* vNew = state.allocValue();
-    state.autoCallFunction(autoArgs, *v, *vNew);
-    v = vNew;
-    state.forceValue(*v);
-
-    /* It should evaluate to either a set or an expression,
-       according to what is specified in the attrPath. */
-
-    if (apType == apAttr) {
-      if (v->type != tAttrs) {
-        throw TypeError(format("the expression selected by the selection path "
-                               "'%1%' should be a set but is %2%") %
-                        attrPath % showType(*v));
-      }
-
-      if (attr.empty()) {
-        throw Error(format("empty attribute name in selection path '%1%'") %
-                    attrPath);
-      }
-
-      Bindings::iterator a = v->attrs->find(state.symbols.Create(attr));
-      if (a == v->attrs->end()) {
-        throw Error(
-            format("attribute '%1%' in selection path '%2%' not found") % attr %
-            attrPath);
-      }
-      v = &*(a->second).value;
-    }
-
-    else if (apType == apIndex) {
-      if (!v->isList()) {
-        throw TypeError(format("the expression selected by the selection path "
-                               "'%1%' should be a list but is %2%") %
-                        attrPath % showType(*v));
-      }
-
-      if (attrIndex >= v->listSize()) {
-        throw Error(
-            format("list index %1% in selection path '%2%' is out of range") %
-            attrIndex % attrPath);
-      }
-
-      v = (*v->list)[attrIndex];
-    }
-  }
-
-  return v;
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/attr-path.hh b/third_party/nix/src/libexpr/attr-path.hh
deleted file mode 100644
index 97170be840..0000000000
--- a/third_party/nix/src/libexpr/attr-path.hh
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include <map>
-#include <string>
-
-#include "libexpr/eval.hh"
-
-namespace nix {
-
-Value* findAlongAttrPath(EvalState& state, const std::string& attrPath,
-                         Bindings* autoArgs, Value& vIn);
-
-}
diff --git a/third_party/nix/src/libexpr/attr-set.cc b/third_party/nix/src/libexpr/attr-set.cc
deleted file mode 100644
index 808ffce176..0000000000
--- a/third_party/nix/src/libexpr/attr-set.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-#include "libexpr/attr-set.hh"
-
-#include <new>
-
-#include <absl/container/btree_map.h>
-#include <glog/logging.h>
-
-#include "libexpr/eval-inline.hh"
-
-namespace nix {
-
-// This function inherits its name from previous implementations, in
-// which Bindings was backed by an array of elements which was scanned
-// linearly.
-//
-// In that setup, inserting duplicate elements would always yield the
-// first element (until the next sort, which wasn't stable, after
-// which things are more or less undefined).
-//
-// This behaviour is mimicked by using .insert(), which will *not*
-// override existing values.
-void Bindings::push_back(const Attr& attr) {
-  auto [_, inserted] = attributes_.insert({attr.name, attr});
-
-  if (!inserted) {
-    DLOG(WARNING) << "attempted to insert duplicate attribute for key '"
-                  << attr.name << "'";
-  }
-}
-
-size_t Bindings::size() const { return attributes_.size(); }
-
-bool Bindings::empty() { return attributes_.empty(); }
-
-std::vector<const Attr*> Bindings::SortedByKeys() {
-  std::vector<const Attr*> res;
-  res.reserve(attributes_.size());
-
-  for (const auto& [key, value] : attributes_) {
-    res.emplace_back(&value);
-  }
-
-  return res;
-}
-
-Bindings::iterator Bindings::find(const Symbol& name) {
-  return attributes_.find(name);
-}
-
-bool Bindings::Equal(const Bindings* other, EvalState& state) const {
-  if (this == other) {
-    return true;
-  }
-
-  if (this->attributes_.size() != other->attributes_.size()) {
-    return false;
-  }
-
-  Bindings::const_iterator i;
-  Bindings::const_iterator j;
-  for (i = this->cbegin(), j = other->cbegin(); i != this->cend(); ++i, ++j) {
-    if (i->second.name != j->second.name ||
-        !state.eqValues(*i->second.value, *j->second.value)) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-Bindings::iterator Bindings::begin() { return attributes_.begin(); }
-Bindings::iterator Bindings::end() { return attributes_.end(); }
-
-Bindings::const_iterator Bindings::cbegin() const {
-  return attributes_.cbegin();
-}
-
-Bindings::const_iterator Bindings::cend() const { return attributes_.cend(); }
-
-std::unique_ptr<Bindings> Bindings::New(size_t capacity) {
-  if (capacity == 0) {
-    // TODO(tazjin): A lot of 0-capacity Bindings are allocated.
-    // It would be nice to optimize that.
-  }
-
-  return std::make_unique<Bindings>();
-}
-
-std::unique_ptr<Bindings> Bindings::Merge(const Bindings& lhs,
-                                          const Bindings& rhs) {
-  auto bindings = New(lhs.size() + rhs.size());
-
-  // Values are merged by inserting the entire iterator range of both
-  // input sets. The right-hand set (the values of which take
-  // precedence) is inserted *first* because the range insertion
-  // method does not override values.
-  bindings->attributes_.insert(rhs.attributes_.cbegin(),
-                               rhs.attributes_.cend());
-  bindings->attributes_.insert(lhs.attributes_.cbegin(),
-                               lhs.attributes_.cend());
-
-  return bindings;
-}
-
-void EvalState::mkAttrs(Value& v, size_t capacity) {
-  clearValue(v);
-  v.type = tAttrs;
-  v.attrs = Bindings::New(capacity);
-  nrAttrsets++;
-  nrAttrsInAttrsets += capacity;
-}
-
-/* Create a new attribute named 'name' on an existing attribute set stored
-   in 'vAttrs' and return the newly allocated Value which is associated with
-   this attribute. */
-Value* EvalState::allocAttr(Value& vAttrs, const Symbol& name) {
-  Value* v = allocValue();
-  vAttrs.attrs->push_back(Attr(name, v));
-  return v;
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/attr-set.hh b/third_party/nix/src/libexpr/attr-set.hh
deleted file mode 100644
index c02f3930fd..0000000000
--- a/third_party/nix/src/libexpr/attr-set.hh
+++ /dev/null
@@ -1,76 +0,0 @@
-// This file implements the underlying structure of Nix attribute sets.
-#pragma once
-
-#include <absl/container/btree_map.h>
-
-#include "libexpr/nixexpr.hh"
-#include "libexpr/symbol-table.hh"
-#include "libutil/types.hh"
-
-namespace nix {  // TODO(tazjin): ::expr
-
-class EvalState;
-struct Value;
-
-/* Map one attribute name to its value. */
-struct Attr {
-  Symbol name;
-  Value* value;  // TODO(tazjin): Who owns this?
-  Pos* pos;      // TODO(tazjin): Who owns this?
-  Attr(Symbol name, Value* value, Pos* pos = &noPos)
-      : name(name), value(value), pos(pos){};
-};
-
-using AttributeMap = absl::btree_map<Symbol, Attr>;
-
-class Bindings {
- public:
-  using iterator = AttributeMap::iterator;
-  using const_iterator = AttributeMap::const_iterator;
-
-  // Allocate a new attribute set that is visible to the garbage
-  // collector.
-  static std::unique_ptr<Bindings> New(size_t capacity = 0);
-
-  // Create a new attribute set by merging two others. This is used to
-  // implement the `//` operator in Nix.
-  static std::unique_ptr<Bindings> Merge(const Bindings& lhs,
-                                         const Bindings& rhs);
-
-  // Return the number of contained elements.
-  size_t size() const;
-
-  // Is this attribute set empty?
-  bool empty();
-
-  // Insert, but do not replace, values in the attribute set.
-  void push_back(const Attr& attr);
-
-  // Are these two attribute sets deeply equal?
-  // Note: Does not special-case derivations. Use state.eqValues() to check
-  // attrsets that may be derivations.
-  bool Equal(const Bindings* other, EvalState& state) const;
-
-  // Look up a specific element of the attribute set.
-  iterator find(const Symbol& name);
-
-  iterator begin();
-  const_iterator cbegin() const;
-  iterator end();
-  const_iterator cend() const;
-
-  // Returns the elements of the attribute set as a vector, sorted
-  // lexicographically by keys.
-  //
-  // This is used primarily for builtins that have guaranteed
-  // ordering, such as `attrNames` or `attrValues`.
-  std::vector<const Attr*> SortedByKeys();
-
-  // oh no
-  friend class EvalState;
-
- private:
-  AttributeMap attributes_;
-};
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/common-eval-args.cc b/third_party/nix/src/libexpr/common-eval-args.cc
deleted file mode 100644
index f63d3f8276..0000000000
--- a/third_party/nix/src/libexpr/common-eval-args.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-#include "libexpr/common-eval-args.hh"
-
-#include "libexpr/eval.hh"
-#include "libmain/shared.hh"
-#include "libstore/download.hh"
-#include "libutil/util.hh"
-
-namespace nix {
-
-MixEvalArgs::MixEvalArgs() {
-  mkFlag()
-      .longName("arg")
-      .description("argument to be passed to Nix functions")
-      .labels({"name", "expr"})
-      .handler([&](std::vector<std::string> ss) {
-        auto_args_[ss[0]] = std::make_pair(kArgTypeExpr, ss[1]);
-      });
-
-  mkFlag()
-      .longName("argstr")
-      .description("string-valued argument to be passed to Nix functions")
-      .labels({"name", "string"})
-      .handler([&](std::vector<std::string> ss) {
-        auto_args_[ss[0]] = std::make_pair(kArgTypeString, ss[1]);
-      });
-
-  mkFlag()
-      .shortName('I')
-      .longName("include")
-      .description(
-          "add a path to the list of locations used to look up <...> file "
-          "names")
-      .label("path")
-      .handler([&](const std::string& s) { searchPath.push_back(s); });
-}
-
-std::unique_ptr<Bindings> MixEvalArgs::getAutoArgs(EvalState& state) {
-  auto res = Bindings::New(auto_args_.size());
-  for (auto& [arg, arg_value] : auto_args_) {
-    Value* v = state.allocValue();
-    switch (arg_value.first) {
-      case kArgTypeExpr: {
-        state.mkThunk_(
-            *v, state.parseExprFromString(arg_value.second, absPath(".")));
-        break;
-      }
-      case kArgTypeString: {
-        mkString(*v, arg_value.second);
-        break;
-      }
-    }
-
-    res->push_back(Attr(state.symbols.Create(arg), v));
-  }
-  return res;
-}
-
-Path lookupFileArg(EvalState& state, std::string s) {
-  if (isUri(s)) {
-    CachedDownloadRequest request(s);
-    request.unpack = true;
-    return getDownloader()->downloadCached(state.store, request).path;
-  }
-  if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
-    Path p = s.substr(1, s.size() - 2);
-    return state.findFile(p);
-  } else {
-    return absPath(s);
-  }
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/common-eval-args.hh b/third_party/nix/src/libexpr/common-eval-args.hh
deleted file mode 100644
index 5e0e8af79c..0000000000
--- a/third_party/nix/src/libexpr/common-eval-args.hh
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#include "libutil/args.hh"
-
-namespace nix {
-
-class Store;
-class EvalState;
-class Bindings;
-
-enum ArgType { kArgTypeString, kArgTypeExpr };
-
-struct MixEvalArgs : virtual Args {
-  MixEvalArgs();
-
-  std::unique_ptr<Bindings> getAutoArgs(EvalState& state);
-
-  Strings searchPath;
-
- private:
-  std::map<std::string, std::pair<ArgType, std::string>> auto_args_;
-};
-
-Path lookupFileArg(EvalState& state, std::string s);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/eval-inline.hh b/third_party/nix/src/libexpr/eval-inline.hh
deleted file mode 100644
index 5162ab3971..0000000000
--- a/third_party/nix/src/libexpr/eval-inline.hh
+++ /dev/null
@@ -1,90 +0,0 @@
-#pragma once
-
-#include "libexpr/eval.hh"
-
-#define LocalNoInline(f)              \
-  static f __attribute__((noinline)); \
-  f
-#define LocalNoInlineNoReturn(f)                \
-  static f __attribute__((noinline, noreturn)); \
-  f
-
-namespace nix {
-
-LocalNoInlineNoReturn(void throwEvalError(const char* s, const Pos& pos)) {
-  throw EvalError(format(s) % pos);
-}
-
-LocalNoInlineNoReturn(void throwTypeError(const char* s, const Value& v)) {
-  throw TypeError(format(s) % showType(v));
-}
-
-LocalNoInlineNoReturn(void throwTypeError(const char* s, const Value& v,
-                                          const Pos& pos)) {
-  throw TypeError(format(s) % showType(v) % pos);
-}
-
-void EvalState::forceValue(Value& v, const Pos& pos) {
-  if (v.type == tThunk) {
-    Env* env = v.thunk.env;
-    Expr* expr = v.thunk.expr;
-    try {
-      v.type = tBlackhole;
-      // checkInterrupt();
-      expr->eval(*this, *env, v);
-    } catch (...) {
-      v.type = tThunk;
-      v.thunk.env = env;
-      v.thunk.expr = expr;
-      throw;
-    }
-  } else if (v.type == tApp) {
-    callFunction(*v.app.left, *v.app.right, v, noPos);
-  } else if (v.type == tBlackhole) {
-    throwEvalError("infinite recursion encountered, at %1%", pos);
-  }
-}
-
-inline void EvalState::forceAttrs(Value& v) {
-  forceValue(v);
-  if (v.type != tAttrs) {
-    throwTypeError("value is %1% while a set was expected", v);
-  }
-}
-
-inline void EvalState::forceAttrs(Value& v, const Pos& pos) {
-  forceValue(v);
-  if (v.type != tAttrs) {
-    throwTypeError("value is %1% while a set was expected, at %2%", v, pos);
-  }
-}
-
-inline void EvalState::forceList(Value& v) {
-  forceValue(v);
-  if (!v.isList()) {
-    throwTypeError("value is %1% while a list was expected", v);
-  }
-}
-
-inline void EvalState::forceList(Value& v, const Pos& pos) {
-  forceValue(v);
-  if (!v.isList()) {
-    throwTypeError("value is %1% while a list was expected, at %2%", v, pos);
-  }
-}
-
-/* Note: Various places expect the allocated memory to be zeroed. */
-inline void* allocBytes(size_t n) {
-  void* p;
-#if HAVE_BOEHMGC
-  p = GC_MALLOC(n);
-#else
-  p = calloc(n, 1);
-#endif
-  if (!p) {
-    throw std::bad_alloc();
-  }
-  return p;
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/eval.cc b/third_party/nix/src/libexpr/eval.cc
deleted file mode 100644
index e157418117..0000000000
--- a/third_party/nix/src/libexpr/eval.cc
+++ /dev/null
@@ -1,1878 +0,0 @@
-#include "libexpr/eval.hh"
-
-#include <algorithm>
-#include <chrono>
-#include <cstdint>
-#include <cstring>
-#include <fstream>
-#include <iostream>
-#include <memory>
-#include <new>
-#include <optional>
-#include <variant>
-
-#include <absl/base/call_once.h>
-#include <absl/container/flat_hash_set.h>
-#include <absl/strings/match.h>
-#include <glog/logging.h>
-#include <sys/resource.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include "libexpr/eval-inline.hh"
-#include "libexpr/function-trace.hh"
-#include "libexpr/value.hh"
-#include "libstore/derivations.hh"
-#include "libstore/download.hh"
-#include "libstore/globals.hh"
-#include "libstore/store-api.hh"
-#include "libutil/hash.hh"
-#include "libutil/json.hh"
-#include "libutil/util.hh"
-#include "libutil/visitor.hh"
-
-namespace nix {
-namespace {
-
-void ConfigureGc() { /* This function intentionally left blank. */
-}
-
-}  // namespace
-
-namespace expr {
-
-absl::once_flag gc_flag;
-
-void InitGC() { absl::call_once(gc_flag, &ConfigureGc); }
-
-}  // namespace expr
-
-static char* dupString(const char* s) {
-  char* t;
-  t = strdup(s);
-  if (t == nullptr) {
-    throw std::bad_alloc();
-  }
-  return t;
-}
-
-std::shared_ptr<Value*> allocRootValue(Value* v) {
-  return std::make_shared<Value*>(v);
-}
-
-static void printValue(std::ostream& str, std::set<const Value*>& active,
-                       const Value& v) {
-  checkInterrupt();
-
-  if (active.find(&v) != active.end()) {
-    str << "<CYCLE>";
-    return;
-  }
-  active.insert(&v);
-
-  switch (v.type) {
-    case tInt:
-      str << v.integer;
-      break;
-    case tBool:
-      str << (v.boolean ? "true" : "false");
-      break;
-    case tString:
-      str << "\"";
-      for (const char* i = v.string.s; *i != 0; i++) {
-        if (*i == '\"' || *i == '\\') {
-          str << "\\" << *i;
-        } else if (*i == '\n') {
-          str << "\\n";
-        } else if (*i == '\r') {
-          str << "\\r";
-        } else if (*i == '\t') {
-          str << "\\t";
-        } else {
-          str << *i;
-        }
-      }
-      str << "\"";
-      break;
-    case tPath:
-      str << v.path;  // !!! escaping?
-      break;
-    case tNull:
-      str << "null";
-      break;
-    case tAttrs: {
-      str << "{ ";
-      for (auto& i : v.attrs->SortedByKeys()) {
-        str << i->name << " = ";
-        printValue(str, active, *i->value);
-        str << "; ";
-      }
-      str << "}";
-      break;
-    }
-    case tList:
-      str << "[ ";
-      for (unsigned int n = 0; n < v.listSize(); ++n) {
-        printValue(str, active, *(*v.list)[n]);
-        str << " ";
-      }
-      str << "]";
-      break;
-    case tThunk:
-    case tApp:
-      str << "<CODE>";
-      break;
-    case tLambda:
-      str << "<LAMBDA>";
-      break;
-    case tPrimOp:
-      str << "<PRIMOP>";
-      break;
-    case tPrimOpApp:
-      str << "<PRIMOP-APP>";
-      break;
-    case tFloat:
-      str << v.fpoint;
-      break;
-    default:
-      throw Error(
-          absl::StrCat("invalid value of type ", static_cast<int>(v.type)));
-  }
-
-  active.erase(&v);
-}
-
-std::ostream& operator<<(std::ostream& str, const Value& v) {
-  std::set<const Value*> active;
-  printValue(str, active, v);
-  return str;
-}
-
-const Value* getPrimOp(const Value& v) {
-  const Value* primOp = &v;
-  while (primOp->type == tPrimOpApp) {
-    primOp = primOp->primOpApp.left;
-  }
-  assert(primOp->type == tPrimOp);
-  return primOp;
-}
-
-std::string showType(const Value& v) {
-  switch (v.type) {
-    case tInt:
-      return "an integer";
-    case tBool:
-      return "a boolean";
-    case tString:
-      return v.string.context != nullptr ? "a string with context" : "a string";
-    case tPath:
-      return "a path";
-    case tNull:
-      return "null";
-    case tAttrs:
-      return "a set";
-    case tList:
-      return "a list";
-    case tThunk:
-      return "a thunk";
-    case tApp:
-      return "a function application";
-    case tLambda:
-      return "a function";
-    case tBlackhole:
-      return "a black hole";
-    case tPrimOp:
-      return fmt("the built-in function '%s'", std::string(v.primOp->name));
-    case tPrimOpApp:
-      return fmt("the partially applied built-in function '%s'",
-                 std::string(getPrimOp(v)->primOp->name));
-    case _reserved1:
-      LOG(FATAL) << "attempted to show the type string of the deprecated "
-                    "tExternal value";
-      break;
-    case tFloat:
-      return "a float";
-  }
-  LOG(FATAL)
-      << "attempted to determine the type string of an unknown type number ("
-      << static_cast<int>(v.type) << ")";
-  abort();
-}
-
-static Symbol getName(const AttrName& name, EvalState& state, Env& env) {
-  return std::visit(
-      util::overloaded{[&](const Symbol& name) -> Symbol { return name; },
-                       [&](Expr* expr) -> Symbol {
-                         Value nameValue;
-                         expr->eval(state, env, nameValue);
-                         state.forceStringNoCtx(nameValue);
-                         return state.symbols.Create(nameValue.string.s);
-                       }},
-      name);
-}
-
-/* Very hacky way to parse $NIX_PATH, which is colon-separated, but
-   can contain URLs (e.g. "nixpkgs=https://bla...:foo=https://"). */
-static Strings parseNixPath(const std::string& s) {
-  Strings res;
-
-  auto p = s.begin();
-
-  while (p != s.end()) {
-    auto start = p;
-    auto start2 = p;
-
-    while (p != s.end() && *p != ':') {
-      if (*p == '=') {
-        start2 = p + 1;
-      }
-      ++p;
-    }
-
-    if (p == s.end()) {
-      if (p != start) {
-        res.push_back(std::string(start, p));
-      }
-      break;
-    }
-
-    if (*p == ':') {
-      if (isUri(std::string(start2, s.end()))) {
-        ++p;
-        while (p != s.end() && *p != ':') {
-          ++p;
-        }
-      }
-      res.push_back(std::string(start, p));
-      if (p == s.end()) {
-        break;
-      }
-    }
-
-    ++p;
-  }
-
-  return res;
-}
-
-EvalState::EvalState(const Strings& _searchPath, const ref<Store>& store)
-    : sWith(symbols.Create("<with>")),
-      sOutPath(symbols.Create("outPath")),
-      sDrvPath(symbols.Create("drvPath")),
-      sType(symbols.Create("type")),
-      sMeta(symbols.Create("meta")),
-      sName(symbols.Create("name")),
-      sValue(symbols.Create("value")),
-      sSystem(symbols.Create("system")),
-      sOutputs(symbols.Create("outputs")),
-      sOutputName(symbols.Create("outputName")),
-      sIgnoreNulls(symbols.Create("__ignoreNulls")),
-      sFile(symbols.Create("file")),
-      sLine(symbols.Create("line")),
-      sColumn(symbols.Create("column")),
-      sFunctor(symbols.Create("__functor")),
-      sToString(symbols.Create("__toString")),
-      sRight(symbols.Create("right")),
-      sWrong(symbols.Create("wrong")),
-      sStructuredAttrs(symbols.Create("__structuredAttrs")),
-      sBuilder(symbols.Create("builder")),
-      sArgs(symbols.Create("args")),
-      sOutputHash(symbols.Create("outputHash")),
-      sOutputHashAlgo(symbols.Create("outputHashAlgo")),
-      sOutputHashMode(symbols.Create("outputHashMode")),
-      sDerivationNix(std::nullopt),
-      repair(NoRepair),
-      store(store),
-      baseEnv(allocEnv(128)),
-      staticBaseEnv(false, nullptr) {
-  expr::InitGC();
-
-  countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
-
-  /* Initialise the Nix expression search path. */
-  if (!evalSettings.pureEval) {
-    Strings paths = parseNixPath(getEnv("NIX_PATH").value_or(""));
-    for (auto& i : _searchPath) {
-      addToSearchPath(i);
-    }
-    for (auto& i : paths) {
-      addToSearchPath(i);
-    }
-  }
-  addToSearchPath("nix=" +
-                  canonPath(settings.nixDataDir + "/nix/corepkgs", true));
-
-  if (evalSettings.restrictEval || evalSettings.pureEval) {
-    allowedPaths = PathSet();
-
-    for (auto& i : searchPath) {
-      auto r = resolveSearchPathElem(i);
-      if (!r.first) {
-        continue;
-      }
-
-      auto path = r.second;
-
-      if (store->isInStore(r.second)) {
-        PathSet closure;
-        store->computeFSClosure(store->toStorePath(r.second), closure);
-        for (auto& path : closure) {
-          allowedPaths->insert(path);
-        }
-      } else {
-        allowedPaths->insert(r.second);
-      }
-    }
-  }
-
-  createBaseEnv();
-}
-
-EvalState::~EvalState() = default;
-
-Path EvalState::checkSourcePath(const Path& path_) {
-  TraceFileAccess(path_);
-  if (!allowedPaths) {
-    return path_;
-  }
-
-  auto i = resolvedPaths.find(path_);
-  if (i != resolvedPaths.end()) {
-    return i->second;
-  }
-
-  bool found = false;
-
-  /* First canonicalize the path without symlinks, so we make sure an
-   * attacker can't append ../../... to a path that would be in allowedPaths
-   * and thus leak symlink targets.
-   */
-  Path abspath = canonPath(path_);
-
-  for (auto& i : *allowedPaths) {
-    if (isDirOrInDir(abspath, i)) {
-      found = true;
-      break;
-    }
-  }
-
-  if (!found) {
-    throw RestrictedPathError(
-        "access to path '%1%' is forbidden in restricted mode", abspath);
-  }
-
-  /* Resolve symlinks. */
-  DLOG(INFO) << "checking access to '" << abspath << "'";
-  Path path = canonPath(abspath, true);
-
-  for (auto& i : *allowedPaths) {
-    if (isDirOrInDir(path, i)) {
-      resolvedPaths[path_] = path;
-      return path;
-    }
-  }
-
-  throw RestrictedPathError(
-      "access to path '%1%' is forbidden in restricted mode", path);
-}
-
-void EvalState::checkURI(const std::string& uri) {
-  if (!evalSettings.restrictEval) {
-    return;
-  }
-
-  /* 'uri' should be equal to a prefix, or in a subdirectory of a
-     prefix. Thus, the prefix https://github.co does not permit
-     access to https://github.com. Note: this allows 'http://' and
-     'https://' as prefixes for any http/https URI. */
-  for (auto& prefix : evalSettings.allowedUris.get()) {
-    if (uri == prefix ||
-        (uri.size() > prefix.size() && !prefix.empty() &&
-         absl::StartsWith(uri, prefix) &&
-         (prefix[prefix.size() - 1] == '/' || uri[prefix.size()] == '/'))) {
-      return;
-    }
-  }
-
-  /* If the URI is a path, then check it against allowedPaths as
-     well. */
-  if (absl::StartsWith(uri, "/")) {
-    checkSourcePath(uri);
-    return;
-  }
-
-  if (absl::StartsWith(uri, "file://")) {
-    checkSourcePath(std::string(uri, 7));
-    return;
-  }
-
-  throw RestrictedPathError(
-      "access to URI '%s' is forbidden in restricted mode", uri);
-}
-
-Path EvalState::toRealPath(const Path& path, const PathSet& context) {
-  // FIXME: check whether 'path' is in 'context'.
-  return !context.empty() && store->isInStore(path) ? store->toRealPath(path)
-                                                    : path;
-};
-
-Value* EvalState::addConstant(const std::string& name, Value& v) {
-  Value* v2 = allocValue();
-  *v2 = v;
-  staticBaseEnv.vars[symbols.Create(name)] = baseEnvDispl;
-  baseEnv.values[baseEnvDispl++] = v2;
-  std::string name2 =
-      std::string(name, 0, 2) == "__" ? std::string(name, 2) : name;
-  baseEnv.values[0]->attrs->push_back(Attr(symbols.Create(name2), v2));
-  return v2;
-}
-
-Value* EvalState::addPrimOp(const std::string& name, size_t arity,
-                            PrimOpFun primOp) {
-  if (arity == 0) {
-    Value v;
-    primOp(*this, noPos, nullptr, v);
-    return addConstant(name, v);
-  }
-  std::string name2 =
-      std::string(name, 0, 2) == "__" ? std::string(name, 2) : name;
-  Symbol sym = symbols.Create(name2);
-  Value* v = allocValue();
-  v->type = tPrimOp;
-  v->primOp = std::make_shared<PrimOp>(primOp, arity, sym);
-  staticBaseEnv.vars[symbols.Create(name)] = baseEnvDispl;
-  baseEnv.values[baseEnvDispl++] = v;
-  baseEnv.values[0]->attrs->push_back(Attr(sym, v));
-  return v;
-}
-
-Value& EvalState::getBuiltin(const std::string& name) {
-  return *baseEnv.values[0]->attrs->find(symbols.Create(name))->second.value;
-}
-
-/* Every "format" object (even temporary) takes up a few hundred bytes
-   of stack space, which is a real killer in the recursive
-   evaluator.  So here are some helper functions for throwing
-   exceptions. */
-
-LocalNoInlineNoReturn(void throwEvalError(const char* s,
-                                          const std::string& s2)) {
-  throw EvalError(format(s) % s2);
-}
-
-LocalNoInlineNoReturn(void throwEvalError(const char* s, const std::string& s2,
-                                          const Pos& pos)) {
-  throw EvalError(format(s) % s2 % pos);
-}
-
-LocalNoInlineNoReturn(void throwEvalError(const char* s, const std::string& s2,
-                                          const std::string& s3)) {
-  throw EvalError(format(s) % s2 % s3);
-}
-
-LocalNoInlineNoReturn(void throwEvalError(const char* s, const std::string& s2,
-                                          const std::string& s3,
-                                          const Pos& pos)) {
-  throw EvalError(format(s) % s2 % s3 % pos);
-}
-
-LocalNoInlineNoReturn(void throwEvalError(const char* s, const Symbol& sym,
-                                          const Pos& p1, const Pos& p2)) {
-  throw EvalError(format(s) % sym % p1 % p2);
-}
-
-LocalNoInlineNoReturn(void throwTypeError(const char* s, const Pos& pos)) {
-  throw TypeError(format(s) % pos);
-}
-
-LocalNoInlineNoReturn(void throwTypeError(const char* s,
-                                          const std::string& s1)) {
-  throw TypeError(format(s) % s1);
-}
-
-LocalNoInlineNoReturn(void throwTypeError(const char* s, const ExprLambda& fun,
-                                          const Symbol& s2, const Pos& pos)) {
-  throw TypeError(format(s) % fun.showNamePos() % s2 % pos);
-}
-
-LocalNoInlineNoReturn(void throwAssertionError(const char* s,
-                                               const std::string& s1,
-                                               const Pos& pos)) {
-  throw AssertionError(format(s) % s1 % pos);
-}
-
-LocalNoInlineNoReturn(void throwUndefinedVarError(const char* s,
-                                                  const std::string& s1,
-                                                  const Pos& pos)) {
-  throw UndefinedVarError(format(s) % s1 % pos);
-}
-
-LocalNoInline(void addErrorPrefix(Error& e, const char* s,
-                                  const std::string& s2)) {
-  e.addPrefix(format(s) % s2);
-}
-
-LocalNoInline(void addErrorPrefix(Error& e, const char* s,
-                                  const ExprLambda& fun, const Pos& pos)) {
-  e.addPrefix(format(s) % fun.showNamePos() % pos);
-}
-
-LocalNoInline(void addErrorPrefix(Error& e, const char* s,
-                                  const std::string& s2, const Pos& pos)) {
-  e.addPrefix(format(s) % s2 % pos);
-}
-
-void mkString(Value& v, const char* s) { mkStringNoCopy(v, dupString(s)); }
-
-Value& mkString(Value& v, const std::string& s, const PathSet& context) {
-  mkString(v, s.c_str());
-  if (!context.empty()) {
-    size_t n = 0;
-    v.string.context = static_cast<const char**>(
-        allocBytes((context.size() + 1) * sizeof(char*)));
-    for (auto& i : context) {
-      v.string.context[n++] = dupString(i.c_str());
-    }
-    v.string.context[n] = nullptr;
-  }
-  return v;
-}
-
-void mkPath(Value& v, const char* s) { mkPathNoCopy(v, dupString(s)); }
-
-inline Value* EvalState::lookupVar(Env* env, const ExprVar& var, bool noEval) {
-  for (size_t l = var.level; l != 0u; --l, env = env->up) {
-    ;
-  }
-
-  if (!var.fromWith) {
-    return env->values[var.displ];
-  }
-
-  while (true) {
-    if (env->type == Env::HasWithExpr) {
-      if (noEval) {
-        return nullptr;
-      }
-      if (!env->withAttrsExpr) {
-        CHECK(false) << "HasWithExpr evaluated twice";
-      }
-      Value* v = allocValue();
-      evalAttrs(*env->up, env->withAttrsExpr, *v);
-      env->values[0] = v;
-      env->withAttrsExpr = nullptr;
-      env->type = Env::HasWithAttrs;
-    }
-    Bindings::iterator j = env->values[0]->attrs->find(var.name);
-    if (j != env->values[0]->attrs->end()) {
-      if (countCalls && (j->second.pos != nullptr)) {
-        attrSelects[*j->second.pos]++;
-      }
-      return j->second.value;
-    }
-    if (env->prevWith == 0u) {
-      throwUndefinedVarError("undefined variable '%1%' at %2%", var.name,
-                             var.pos);
-    }
-    for (size_t l = env->prevWith; l != 0u; --l, env = env->up) {
-    }
-  }
-}
-
-Value* EvalState::allocValue() {
-  nrValues++;
-  return new Value;
-}
-
-Env& EvalState::allocEnv(size_t size) {
-  if (size > std::numeric_limits<decltype(Env::size)>::max()) {
-    throw Error("environment size %d is too big", size);
-  }
-
-  nrEnvs++;
-  nrValuesInEnvs += size;
-  Env* env = new Env(size);
-  env->type = Env::Plain;
-
-  return *env;
-}
-
-void EvalState::mkList(Value& v, std::shared_ptr<NixList> list) {
-  nrListElems += list->size();
-  clearValue(v);
-  v.type = tList;
-  v.list = list;
-}
-
-void EvalState::mkList(Value& v, size_t size) {
-  EvalState::mkList(v, std::make_shared<NixList>(size));
-}
-
-unsigned long nrThunks = 0;
-
-static inline void mkThunk(Value& v, Env& env, Expr* expr) {
-  v.type = tThunk;
-  v.thunk.env = &env;
-  v.thunk.expr = expr;
-  nrThunks++;
-}
-
-void EvalState::mkThunk_(Value& v, Expr* expr) { mkThunk(v, baseEnv, expr); }
-
-void EvalState::mkPos(Value& v, Pos* pos) {
-  if ((pos != nullptr) && pos->file.has_value() && pos->file.value().set()) {
-    mkAttrs(v, 3);
-    mkString(*allocAttr(v, sFile), pos->file.value());
-    mkInt(*allocAttr(v, sLine), pos->line);
-    mkInt(*allocAttr(v, sColumn), pos->column);
-  } else {
-    mkNull(v);
-  }
-}
-
-/* Create a thunk for the delayed computation of the given expression
-   in the given environment.  But if the expression is a variable,
-   then look it up right away.  This significantly reduces the number
-   of thunks allocated. */
-Value* Expr::maybeThunk(EvalState& state, Env& env) {
-  Value* v = state.allocValue();
-  mkThunk(*v, env, this);
-  return v;
-}
-
-unsigned long nrAvoided = 0;
-
-Value* ExprVar::maybeThunk(EvalState& state, Env& env) {
-  Value* v = state.lookupVar(&env, *this, true);
-  /* The value might not be initialised in the environment yet.
-     In that case, ignore it. */
-  if (v != nullptr) {
-    nrAvoided++;
-    return v;
-  }
-  return Expr::maybeThunk(state, env);
-}
-
-Value* ExprString::maybeThunk(EvalState& state, Env& env) {
-  nrAvoided++;
-  return &v;
-}
-
-Value* ExprInt::maybeThunk(EvalState& state, Env& env) {
-  nrAvoided++;
-  return &v;
-}
-
-Value* ExprFloat::maybeThunk(EvalState& state, Env& env) {
-  nrAvoided++;
-  return &v;
-}
-
-Value* ExprPath::maybeThunk(EvalState& state, Env& env) {
-  nrAvoided++;
-  return &v;
-}
-
-void EvalState::evalFile(const Path& path_, Value& v) {
-  auto path = checkSourcePath(path_);
-
-  FileEvalCache::iterator i;
-  if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) {
-    v = i->second;
-    return;
-  }
-
-  Path path2 = resolveExprPath(path);
-  if ((i = fileEvalCache.find(path2)) != fileEvalCache.end()) {
-    v = i->second;
-    return;
-  }
-
-  DLOG(INFO) << "evaluating file '" << path2 << "'";
-  Expr* e = nullptr;
-
-  auto j = fileParseCache.find(path2);
-  if (j != fileParseCache.end()) {
-    e = j->second;
-  }
-
-  if (e == nullptr) {
-    e = parseExprFromFile(checkSourcePath(path2));
-  }
-
-  fileParseCache[path2] = e;
-
-  try {
-    eval(e, v);
-  } catch (Error& e) {
-    addErrorPrefix(e, "while evaluating the file '%1%':\n", path2);
-    throw;
-  }
-
-  fileEvalCache[path2] = v;
-  if (path != path2) {
-    fileEvalCache[path] = v;
-  }
-}
-
-void EvalState::resetFileCache() {
-  fileEvalCache.clear();
-  fileParseCache.clear();
-}
-
-void EvalState::eval(Expr* e, Value& v) { e->eval(*this, baseEnv, v); }
-
-inline bool EvalState::evalBool(Env& env, Expr* e) {
-  Value v;
-  e->eval(*this, env, v);
-  if (v.type != tBool) {
-    throwTypeError("value is %1% while a Boolean was expected", v);
-  }
-  return v.boolean;
-}
-
-inline bool EvalState::evalBool(Env& env, Expr* e, const Pos& pos) {
-  Value v;
-  e->eval(*this, env, v);
-  if (v.type != tBool) {
-    throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos);
-  }
-  return v.boolean;
-}
-
-inline void EvalState::evalAttrs(Env& env, Expr* e, Value& v) {
-  e->eval(*this, env, v);
-  if (v.type != tAttrs) {
-    throwTypeError("value is %1% while a set was expected", v);
-  }
-}
-
-void Expr::eval(EvalState& state, Env& env, Value& v) { abort(); }
-
-void ExprInt::eval(EvalState& state, Env& env, Value& v) { v = this->v; }
-
-void ExprFloat::eval(EvalState& state, Env& env, Value& v) { v = this->v; }
-
-void ExprString::eval(EvalState& state, Env& env, Value& v) { v = this->v; }
-
-void ExprPath::eval(EvalState& state, Env& env, Value& v) { v = this->v; }
-
-void ExprAttrs::eval(EvalState& state, Env& env, Value& value) {
-  state.mkAttrs(value, attrs.size() + dynamicAttrs.size());
-  Env* dynamicEnv = &env;
-
-  if (recursive) {
-    /* Create a new environment that contains the attributes in
-       this `rec'. */
-    Env& env2(state.allocEnv(attrs.size()));
-    env2.up = &env;
-    dynamicEnv = &env2;
-
-    /* The recursive attributes are evaluated in the new
-       environment, while the inherited attributes are evaluated
-       in the original environment. */
-    size_t displ = 0;
-    for (auto& attr : attrs) {
-      Value* vAttr;
-      vAttr =
-          attr.second.e->maybeThunk(state, attr.second.inherited ? env : env2);
-      env2.values[displ++] = vAttr;
-      value.attrs->push_back(Attr(attr.first, vAttr, &attr.second.pos));
-    }
-  } else {
-    // TODO(tazjin): insert range
-    for (auto& i : attrs) {
-      value.attrs->push_back(
-          Attr(i.first, i.second.e->maybeThunk(state, env), &i.second.pos));
-    }
-  }
-
-  /* Dynamic attrs apply *after* rec. */
-  for (auto& i : dynamicAttrs) {
-    Value nameVal;
-    i.nameExpr->eval(state, *dynamicEnv, nameVal);
-    state.forceValue(nameVal, i.pos);
-    if (nameVal.type == tNull) {
-      continue;
-    }
-    state.forceStringNoCtx(nameVal);
-    Symbol nameSym = state.symbols.Create(nameVal.string.s);
-    Bindings::iterator j = value.attrs->find(nameSym);
-    if (j != value.attrs->end()) {
-      throwEvalError("dynamic attribute '%1%' at %2% already defined at %3%",
-                     nameSym, i.pos, *j->second.pos);
-    }
-
-    value.attrs->push_back(
-        Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), &i.pos));
-  }
-}
-
-void ExprLet::eval(EvalState& state, Env& env, Value& v) {
-  /* Create a new environment that contains the attributes in this
-     `let'. */
-  Env& env2(state.allocEnv(attrs->attrs.size()));
-  env2.up = &env;
-
-  /* The recursive attributes are evaluated in the new environment,
-     while the inherited attributes are evaluated in the original
-     environment. */
-  size_t displ = 0;
-  for (auto& i : attrs->attrs) {
-    env2.values[displ++] =
-        i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
-  }
-
-  body->eval(state, env2, v);
-}
-
-void ExprList::eval(EvalState& state, Env& env, Value& v) {
-  state.mkList(v, elems.size());
-  for (size_t n = 0; n < elems.size(); ++n) {
-    (*v.list)[n] = elems[n]->maybeThunk(state, env);
-  }
-}
-
-void ExprVar::eval(EvalState& state, Env& env, Value& v) {
-  Value* v2 = state.lookupVar(&env, *this, false);
-  state.forceValue(*v2, pos);
-  v = *v2;
-}
-
-static std::string showAttrPath(EvalState& state, Env& env,
-                                const AttrPath& attrPath) {
-  std::ostringstream out;
-  bool first = true;
-  for (auto& i : attrPath) {
-    if (!first) {
-      out << '.';
-    } else {
-      first = false;
-    }
-    out << getName(i, state, env);
-  }
-  return out.str();
-}
-
-uint64_t nrLookups = 0;
-
-void ExprSelect::eval(EvalState& state, Env& env, Value& v) {
-  Value vTmp;
-  Pos* pos2 = nullptr;
-  Value* vAttrs = &vTmp;
-
-  e->eval(state, env, vTmp);
-
-  try {
-    for (auto& i : attrPath) {
-      nrLookups++;
-      Bindings::iterator j;
-      Symbol name = getName(i, state, env);
-      if (def != nullptr) {
-        state.forceValue(*vAttrs, pos);
-        if (vAttrs->type != tAttrs ||
-            (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
-          def->eval(state, env, v);
-          return;
-        }
-      } else {
-        state.forceAttrs(*vAttrs, pos);
-        if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
-          throwEvalError("attribute '%1%' missing, at %2%", name, pos);
-        }
-      }
-      vAttrs = j->second.value;
-      pos2 = j->second.pos;
-      if (state.countCalls && (pos2 != nullptr)) {
-        state.attrSelects[*pos2]++;
-      }
-    }
-
-    state.forceValue(*vAttrs, (pos2 != nullptr ? *pos2 : this->pos));
-
-  } catch (Error& e) {
-    // This code relies on 'sDerivationNix' being correcty mutated at
-    // some prior point (it would previously otherwise have been a
-    // nullptr).
-    //
-    // We haven't seen this fail, so for now the contained value is
-    // just accessed at the risk of potentially crashing.
-    if ((pos2 != nullptr) && pos2->file != state.sDerivationNix.value()) {
-      addErrorPrefix(e, "while evaluating the attribute '%1%' at %2%:\n",
-                     showAttrPath(state, env, attrPath), *pos2);
-    }
-    throw;
-  }
-
-  v = *vAttrs;
-}
-
-void ExprOpHasAttr::eval(EvalState& state, Env& env, Value& v) {
-  Value vTmp;
-  Value* vAttrs = &vTmp;
-
-  e->eval(state, env, vTmp);
-
-  for (auto& i : attrPath) {
-    state.forceValue(*vAttrs);
-    Bindings::iterator j;
-    Symbol name = getName(i, state, env);
-    if (vAttrs->type != tAttrs ||
-        (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
-      mkBool(v, false);
-      return;
-    }
-    vAttrs = j->second.value;
-  }
-
-  mkBool(v, true);
-}
-
-void ExprLambda::eval(EvalState& state, Env& env, Value& v) {
-  v.type = tLambda;
-  v.lambda.env = &env;
-  v.lambda.fun = this;
-}
-
-void ExprApp::eval(EvalState& state, Env& env, Value& v) {
-  /* FIXME: vFun prevents GCC from doing tail call optimisation. */
-  Value vFun;
-  e1->eval(state, env, vFun);
-  state.callFunction(vFun, *(e2->maybeThunk(state, env)), v, pos);
-}
-
-void EvalState::callPrimOp(Value& fun, Value& arg, Value& v, const Pos& pos) {
-  /* Figure out the number of arguments still needed. */
-  size_t argsDone = 0;
-  Value* primOp = &fun;
-  while (primOp->type == tPrimOpApp) {
-    argsDone++;
-    primOp = primOp->primOpApp.left;
-  }
-  assert(primOp->type == tPrimOp);
-  auto arity = primOp->primOp->arity;
-  auto argsLeft = arity - argsDone;
-
-  if (argsLeft == 1) {
-    /* We have all the arguments, so call the primop. */
-
-    /* Put all the arguments in an array. */
-    Value* vArgs[arity];
-    auto n = arity - 1;
-    vArgs[n--] = &arg;
-    for (Value* arg = &fun; arg->type == tPrimOpApp;
-         arg = arg->primOpApp.left) {
-      vArgs[n--] = arg->primOpApp.right;
-    }
-
-    /* And call the primop. */
-    nrPrimOpCalls++;
-    if (countCalls) {
-      primOpCalls[primOp->primOp->name]++;
-    }
-    primOp->primOp->fun(*this, pos, vArgs, v);
-  } else {
-    Value* fun2 = allocValue();
-    *fun2 = fun;
-    v.type = tPrimOpApp;
-    v.primOpApp.left = fun2;
-    v.primOpApp.right = &arg;
-  }
-}
-
-void EvalState::callFunction(Value& fun, Value& arg, Value& v, const Pos& pos) {
-  auto trace = evalSettings.traceFunctionCalls
-                   ? std::make_unique<FunctionCallTrace>(pos)
-                   : nullptr;
-
-  forceValue(fun, pos);
-
-  if (fun.type == tPrimOp || fun.type == tPrimOpApp) {
-    callPrimOp(fun, arg, v, pos);
-    return;
-  }
-
-  // If the value to be called is an attribute set, check whether it
-  // contains an appropriate function in the '__functor' element and
-  // use that.
-  if (fun.type == tAttrs) {
-    auto found = fun.attrs->find(sFunctor);
-    if (found != fun.attrs->end()) {
-      // fun may be allocated on the stack of the calling function,
-      // but for functors we may keep a reference, so heap-allocate a
-      // copy and use that instead
-      auto& fun2 = *allocValue();
-      fun2 = fun;
-      /* !!! Should we use the attr pos here? */
-      Value v2;
-      // functors are called with the element itself as the first
-      // parameter, which is partially applied here
-      callFunction(*found->second.value, fun2, v2, pos);
-      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);
-  }
-
-  ExprLambda& lambda(*fun.lambda.fun);
-
-  auto size = (lambda.arg.empty() ? 0 : 1) +
-              (lambda.matchAttrs ? lambda.formals->formals.size() : 0);
-  Env& env2(allocEnv(size));
-  env2.up = fun.lambda.env;
-
-  size_t displ = 0;
-
-  if (!lambda.matchAttrs) {
-    env2.values[displ++] = &arg;
-
-  } else {
-    forceAttrs(arg, pos);
-
-    if (!lambda.arg.empty()) {
-      env2.values[displ++] = &arg;
-    }
-
-    /* For each formal argument, get the actual argument.  If
-       there is no matching actual argument but the formal
-       argument has a default, use the default. */
-    size_t attrsUsed = 0;
-    for (auto& i : lambda.formals->formals) {
-      Bindings::iterator j = arg.attrs->find(i.name);
-      if (j == arg.attrs->end()) {
-        if (i.def == nullptr) {
-          throwTypeError("%1% called without required argument '%2%', at %3%",
-                         lambda, i.name, pos);
-        }
-        env2.values[displ++] = i.def->maybeThunk(*this, env2);
-      } else {
-        attrsUsed++;
-        env2.values[displ++] = j->second.value;
-      }
-    }
-
-    /* Check that each actual argument is listed as a formal
-       argument (unless the attribute match specifies a `...'). */
-    if (!lambda.formals->ellipsis && attrsUsed != arg.attrs->size()) {
-      /* Nope, so show the first unexpected argument to the
-         user. */
-      for (auto& i : *arg.attrs) {
-        if (lambda.formals->argNames.find(i.second.name) ==
-            lambda.formals->argNames.end()) {
-          throwTypeError("%1% called with unexpected argument '%2%', at %3%",
-                         lambda, i.second.name, pos);
-        }
-      }
-      abort();  // shouldn't happen
-    }
-  }
-
-  nrFunctionCalls++;
-  if (countCalls) {
-    incrFunctionCall(&lambda);
-  }
-
-  /* Evaluate the body.  This is conditional on showTrace, because
-     catching exceptions makes this function not tail-recursive. */
-  if (settings.showTrace) {
-    try {
-      lambda.body->eval(*this, env2, v);
-    } catch (Error& e) {
-      addErrorPrefix(e, "while evaluating %1%, called from %2%:\n", lambda,
-                     pos);
-      throw;
-    }
-  } else {
-    fun.lambda.fun->body->eval(*this, env2, v);
-  }
-}
-
-// Lifted out of callFunction() because it creates a temporary that
-// prevents tail-call optimisation.
-void EvalState::incrFunctionCall(ExprLambda* fun) { functionCalls[fun]++; }
-
-void EvalState::autoCallFunction(Bindings* args, Value& fun, Value& res) {
-  forceValue(fun);
-
-  if (fun.type == tAttrs) {
-    auto found = fun.attrs->find(sFunctor);
-    if (found != fun.attrs->end()) {
-      Value* v = allocValue();
-      callFunction(*found->second.value, fun, *v, noPos);
-      forceValue(*v);
-      return autoCallFunction(args, *v, res);
-    }
-  }
-
-  if (fun.type != tLambda || !fun.lambda.fun->matchAttrs) {
-    res = fun;
-    return;
-  }
-
-  Value* actualArgs = allocValue();
-  mkAttrs(*actualArgs, fun.lambda.fun->formals->formals.size());
-
-  if (fun.lambda.fun->formals->ellipsis) {
-    // If the formals have an ellipsis (eg the function accepts extra args) pass
-    // all available automatic arguments (which includes arguments specified on
-    // the command line via --arg/--argstr)
-    for (auto& [_, v] : *args) {
-      actualArgs->attrs->push_back(v);
-    }
-  } else {
-    // Otherwise, only pass the arguments that the function accepts
-    for (auto& i : fun.lambda.fun->formals->formals) {
-      Bindings::iterator j = args->find(i.name);
-      if (j != args->end()) {
-        actualArgs->attrs->push_back(j->second);
-      } else if (i.def == nullptr) {
-        throwTypeError(
-            "cannot auto-call a function that has an argument without a "
-            "default "
-            "value ('%1%')",
-            i.name);
-      }
-    }
-  }
-
-  callFunction(fun, *actualArgs, res, noPos);
-}
-
-void ExprWith::eval(EvalState& state, Env& env, Value& v) {
-  Env& env2(state.allocEnv(1));
-  env2.up = &env;
-  env2.prevWith = prevWith;
-  env2.type = Env::HasWithExpr;
-  /* placeholder for result of attrs */
-  env2.values[0] = nullptr;
-  env2.withAttrsExpr = this->attrs;
-
-  body->eval(state, env2, v);
-}
-
-void ExprIf::eval(EvalState& state, Env& env, Value& v) {
-  (state.evalBool(env, cond) ? then : else_)->eval(state, env, v);
-}
-
-void ExprAssert::eval(EvalState& state, Env& env, Value& v) {
-  if (!state.evalBool(env, cond, pos)) {
-    std::ostringstream out;
-    cond->show(out);
-    throwAssertionError("assertion %1% failed at %2%", out.str(), pos);
-  }
-  body->eval(state, env, v);
-}
-
-void ExprOpNot::eval(EvalState& state, Env& env, Value& v) {
-  mkBool(v, !state.evalBool(env, e));
-}
-
-void ExprOpEq::eval(EvalState& state, Env& env, Value& v) {
-  Value v1;
-  e1->eval(state, env, v1);
-  Value v2;
-  e2->eval(state, env, v2);
-  mkBool(v, state.eqValues(v1, v2));
-}
-
-void ExprOpNEq::eval(EvalState& state, Env& env, Value& v) {
-  Value v1;
-  e1->eval(state, env, v1);
-  Value v2;
-  e2->eval(state, env, v2);
-  mkBool(v, !state.eqValues(v1, v2));
-}
-
-void ExprOpAnd::eval(EvalState& state, Env& env, Value& v) {
-  mkBool(v, state.evalBool(env, e1, pos) && state.evalBool(env, e2, pos));
-}
-
-void ExprOpOr::eval(EvalState& state, Env& env, Value& v) {
-  mkBool(v, state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos));
-}
-
-void ExprOpImpl::eval(EvalState& state, Env& env, Value& v) {
-  mkBool(v, !state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos));
-}
-
-void ExprOpUpdate::eval(EvalState& state, Env& env, Value& dest) {
-  Value v1;
-  Value v2;
-  state.evalAttrs(env, e1, v1);
-  state.evalAttrs(env, e2, v2);
-
-  state.nrOpUpdates++;
-
-  clearValue(dest);
-  dest.type = tAttrs;
-  dest.attrs = Bindings::Merge(*v1.attrs, *v2.attrs);
-}
-
-void ExprOpConcatLists::eval(EvalState& state, Env& env, Value& v) {
-  Value v1;
-  e1->eval(state, env, v1);
-  Value v2;
-  e2->eval(state, env, v2);
-  state.concatLists(v, {&v1, &v2}, pos);
-}
-
-void EvalState::concatLists(Value& v, const NixList& lists, const Pos& pos) {
-  nrListConcats++;
-
-  auto outlist = std::make_shared<NixList>();
-
-  for (Value* list : lists) {
-    forceList(*list, pos);
-    outlist->insert(outlist->end(), list->list->begin(), list->list->end());
-  }
-
-  mkList(v, outlist);
-}
-
-void ExprConcatStrings::eval(EvalState& state, Env& env, Value& v) {
-  PathSet context;
-  std::ostringstream s;
-  NixInt n = 0;
-  NixFloat nf = 0;
-
-  bool first = !forceString;
-  ValueType firstType = tString;
-
-  for (auto& i : *es) {
-    Value vTmp;
-    i->eval(state, env, vTmp);
-
-    /* If the first element is a path, then the result will also
-       be a path, we don't copy anything (yet - that's done later,
-       since paths are copied when they are used in a derivation),
-       and none of the strings are allowed to have contexts. */
-    if (first) {
-      firstType = vTmp.type;
-      first = false;
-    }
-
-    if (firstType == tInt) {
-      if (vTmp.type == tInt) {
-        n += vTmp.integer;
-      } else if (vTmp.type == tFloat) {
-        // Upgrade the type from int to float;
-        firstType = tFloat;
-        nf = n;
-        nf += vTmp.fpoint;
-      } else {
-        throwEvalError("cannot add %1% to an integer, at %2%", showType(vTmp),
-                       pos);
-      }
-    } else if (firstType == tFloat) {
-      if (vTmp.type == tInt) {
-        nf += vTmp.integer;
-      } else if (vTmp.type == tFloat) {
-        nf += vTmp.fpoint;
-      } else {
-        throwEvalError("cannot add %1% to a float, at %2%", showType(vTmp),
-                       pos);
-      }
-    } else {
-      s << state.coerceToString(pos, vTmp, context, false,
-                                firstType == tString);
-    }
-  }
-
-  if (firstType == tInt) {
-    mkInt(v, n);
-  } else if (firstType == tFloat) {
-    mkFloat(v, nf);
-  } else if (firstType == tPath) {
-    if (!context.empty()) {
-      throwEvalError(
-          "a string that refers to a store path cannot be appended to a path, "
-          "at %1%",
-          pos);
-    }
-    auto path = canonPath(s.str());
-    mkPath(v, path.c_str());
-  } else {
-    mkString(v, s.str(), context);
-  }
-}
-
-void ExprPos::eval(EvalState& state, Env& env, Value& v) {
-  state.mkPos(v, &pos);
-}
-
-template <typename T>
-using traceable_flat_hash_set = absl::flat_hash_set<T>;
-
-void EvalState::forceValueDeep(Value& v) {
-  traceable_flat_hash_set<const Value*> seen;
-
-  std::function<void(Value & v)> recurse;
-
-  recurse = [&](Value& v) {
-    if (seen.find(&v) != seen.end()) {
-      return;
-    }
-    seen.insert(&v);
-
-    forceValue(v);
-
-    if (v.type == tAttrs) {
-      for (auto& i : *v.attrs) {
-        try {
-          recurse(*i.second.value);
-        } catch (Error& e) {
-          addErrorPrefix(e, "while evaluating the attribute '%1%' at %2%:\n",
-                         i.second.name, *i.second.pos);
-          throw;
-        }
-      }
-    } else if (v.isList()) {
-      for (size_t n = 0; n < v.listSize(); ++n) {
-        recurse(*(*v.list)[n]);
-      }
-    }
-  };
-
-  recurse(v);
-}
-
-NixInt EvalState::forceInt(Value& v, const Pos& pos) {
-  forceValue(v, pos);
-  if (v.type != tInt) {
-    throwTypeError("value is %1% while an integer was expected, at %2%", v,
-                   pos);
-  }
-  return v.integer;
-}
-
-NixFloat EvalState::forceFloat(Value& v, const Pos& pos) {
-  forceValue(v, pos);
-  if (v.type == tInt) {
-    return static_cast<NixFloat>(v.integer);
-  }
-  if (v.type != tFloat) {
-    throwTypeError("value is %1% while a float was expected, at %2%", v, pos);
-  }
-  return v.fpoint;
-}
-
-bool EvalState::forceBool(Value& v, const Pos& pos) {
-  forceValue(v);
-  if (v.type != tBool) {
-    throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos);
-  }
-  return v.boolean;
-}
-
-bool EvalState::isFunctor(Value& fun) {
-  return fun.type == tAttrs && fun.attrs->find(sFunctor) != fun.attrs->end();
-}
-
-void EvalState::forceFunction(Value& v, const Pos& pos) {
-  forceValue(v);
-  if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp &&
-      !isFunctor(v)) {
-    throwTypeError("value is %1% while a function was expected, at %2%", v,
-                   pos);
-  }
-}
-
-std::string EvalState::forceString(Value& v, const Pos& pos) {
-  forceValue(v, pos);
-  if (v.type != tString) {
-    if (pos) {
-      throwTypeError("value is %1% while a string was expected, at %2%", v,
-                     pos);
-    } else {
-      throwTypeError("value is %1% while a string was expected", v);
-    }
-  }
-  return std::string(v.string.s);
-}
-
-void copyContext(const Value& v, PathSet& context) {
-  if (v.string.context != nullptr) {
-    for (const char** p = v.string.context; *p != nullptr; ++p) {
-      context.insert(*p);
-    }
-  }
-}
-
-std::string EvalState::forceString(Value& v, PathSet& context, const Pos& pos) {
-  std::string s = forceString(v, pos);
-  copyContext(v, context);
-  return s;
-}
-
-std::string EvalState::forceStringNoCtx(Value& v, const Pos& pos) {
-  std::string s = forceString(v, pos);
-  if (v.string.context != nullptr) {
-    if (pos) {
-      throwEvalError(
-          "the string '%1%' is not allowed to refer to a store path (such as "
-          "'%2%'), at %3%",
-          v.string.s, v.string.context[0], pos);
-    } else {
-      throwEvalError(
-          "the string '%1%' is not allowed to refer to a store path (such as "
-          "'%2%')",
-          v.string.s, v.string.context[0]);
-    }
-  }
-  return s;
-}
-
-bool EvalState::isDerivation(Value& v) {
-  if (v.type != tAttrs) {
-    return false;
-  }
-  Bindings::iterator i = v.attrs->find(sType);
-  if (i == v.attrs->end()) {
-    return false;
-  }
-  forceValue(*i->second.value);
-  if (i->second.value->type != tString) {
-    return false;
-  }
-  return strcmp(i->second.value->string.s, "derivation") == 0;
-}
-
-std::optional<std::string> EvalState::tryAttrsToString(const Pos& pos, Value& v,
-                                                       PathSet& context,
-                                                       bool coerceMore,
-                                                       bool copyToStore) {
-  auto i = v.attrs->find(sToString);
-  if (i != v.attrs->end()) {
-    Value v1;
-    callFunction(*i->second.value, v, v1, pos);
-    return coerceToString(pos, v1, context, coerceMore, copyToStore);
-  }
-
-  return {};
-}
-
-std::string EvalState::coerceToString(const Pos& pos, Value& v,
-                                      PathSet& context, bool coerceMore,
-                                      bool copyToStore) {
-  forceValue(v);
-
-  std::string s;
-
-  if (v.type == tString) {
-    copyContext(v, context);
-    return v.string.s;
-  }
-
-  if (v.type == tPath) {
-    Path path(canonPath(v.path));
-    return copyToStore ? copyPathToStore(context, path) : path;
-  }
-
-  if (v.type == tAttrs) {
-    auto maybeString =
-        tryAttrsToString(pos, v, context, coerceMore, copyToStore);
-    if (maybeString) {
-      return *maybeString;
-    }
-    auto i = v.attrs->find(sOutPath);
-    if (i == v.attrs->end()) {
-      throwTypeError("cannot coerce a set to a string, at %1%", pos);
-    }
-    return coerceToString(pos, *i->second.value, context, coerceMore,
-                          copyToStore);
-  }
-
-  if (coerceMore) {
-    /* Note that `false' is represented as an empty string for
-       shell scripting convenience, just like `null'. */
-    if (v.type == tBool && v.boolean) {
-      return "1";
-    }
-    if (v.type == tBool && !v.boolean) {
-      return "";
-    }
-    if (v.type == tInt) {
-      return std::to_string(v.integer);
-    }
-    if (v.type == tFloat) {
-      return std::to_string(v.fpoint);
-    }
-    if (v.type == tNull) {
-      return "";
-    }
-
-    if (v.isList()) {
-      std::string result;
-      for (size_t n = 0; n < v.listSize(); ++n) {
-        result += coerceToString(pos, *(*v.list)[n], context, coerceMore,
-                                 copyToStore);
-        if (n < v.listSize() - 1
-            /* !!! not quite correct */
-            && (!(*v.list)[n]->isList() || (*v.list)[n]->listSize() != 0)) {
-          result += " ";
-        }
-      }
-      return result;
-    }
-  }
-
-  throwTypeError("cannot coerce %1% to a string, at %2%", v, pos);
-}
-
-std::string EvalState::copyPathToStore(PathSet& context, const Path& path) {
-  if (nix::isDerivation(path)) {
-    throwEvalError("file names are not allowed to end in '%1%'", drvExtension);
-  }
-
-  Path dstPath;
-  if (!srcToStore[path].empty()) {
-    dstPath = srcToStore[path];
-  } else {
-    dstPath =
-        settings.readOnlyMode
-            ? store
-                  ->computeStorePathForPath(baseNameOf(path),
-                                            checkSourcePath(path))
-                  .first
-            : store->addToStore(baseNameOf(path), checkSourcePath(path), true,
-                                htSHA256, defaultPathFilter, repair);
-    srcToStore[path] = dstPath;
-    DLOG(INFO) << "copied source '" << path << "' -> '" << dstPath << "'";
-  }
-
-  context.insert(dstPath);
-  return dstPath;
-}
-
-Path EvalState::coerceToPath(const Pos& pos, Value& v, PathSet& context) {
-  std::string path = coerceToString(pos, v, context, false, false);
-  if (path.empty() || path[0] != '/') {
-    throwEvalError("string '%1%' doesn't represent an absolute path, at %2%",
-                   path, pos);
-  }
-  return path;
-}
-
-bool EvalState::eqValues(Value& v1, Value& v2) {
-  forceValue(v1);
-  forceValue(v2);
-
-  /* !!! Hack to support some old broken code that relies on pointer
-     equality tests between sets.  (Specifically, builderDefs calls
-     uniqList on a list of sets.)  Will remove this eventually. */
-  if (&v1 == &v2) {
-    return true;
-  }
-
-  // Special case type-compatibility between float and int
-  if (v1.type == tInt && v2.type == tFloat) {
-    return v1.integer == v2.fpoint;
-  }
-  if (v1.type == tFloat && v2.type == tInt) {
-    return v1.fpoint == v2.integer;
-  }
-
-  // All other types are not compatible with each other.
-  if (v1.type != v2.type) {
-    return false;
-  }
-
-  switch (v1.type) {
-    case tInt:
-      return v1.integer == v2.integer;
-
-    case tBool:
-      return v1.boolean == v2.boolean;
-
-    case tString:
-      return strcmp(v1.string.s, v2.string.s) == 0;
-
-    case tPath:
-      return strcmp(v1.path, v2.path) == 0;
-
-    case tNull:
-      return true;
-
-    case tList:
-      if (v1.listSize() != v2.listSize()) {
-        return false;
-      }
-      for (size_t n = 0; n < v1.listSize(); ++n) {
-        if (!eqValues(*(*v1.list)[n], *(*v2.list)[n])) {
-          return false;
-        }
-      }
-      return true;
-
-    case tAttrs: {
-      // As an optimisation if both values are pointing towards the
-      // same attribute set, we can skip all this extra work.
-      if (v1.attrs == v2.attrs) {
-        return true;
-      }
-
-      /* If both sets denote a derivation (type = "derivation"),
-         then compare their outPaths. */
-      if (isDerivation(v1) && isDerivation(v2)) {
-        Bindings::iterator i = v1.attrs->find(sOutPath);
-        Bindings::iterator j = v2.attrs->find(sOutPath);
-        if (i != v1.attrs->end() && j != v2.attrs->end()) {
-          return eqValues(*i->second.value, *j->second.value);
-        }
-      }
-
-      return v1.attrs->Equal(v2.attrs.get(), *this);
-    }
-
-    /* Functions are incomparable. */
-    case tLambda:
-    case tPrimOp:
-    case tPrimOpApp:
-      return false;
-
-    case tFloat:
-      return v1.fpoint == v2.fpoint;
-
-    default:
-      throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2));
-  }
-}
-
-void EvalState::printStats() {
-  bool showStats = getEnv("NIX_SHOW_STATS").value_or("0") != "0";
-
-  struct rusage buf;
-  getrusage(RUSAGE_SELF, &buf);
-  float cpuTime = buf.ru_utime.tv_sec +
-                  (static_cast<float>(buf.ru_utime.tv_usec) / 1000000);
-
-  uint64_t bEnvs = nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value*);
-  uint64_t bLists = nrListElems * sizeof(Value*);
-  uint64_t bValues = nrValues * sizeof(Value);
-  uint64_t bAttrsets =
-      nrAttrsets * sizeof(Bindings) + nrAttrsInAttrsets * sizeof(Attr);
-
-  if (showStats) {
-    auto outPath = getEnv("NIX_SHOW_STATS_PATH").value_or("-");
-    std::fstream fs;
-    if (outPath != "-") {
-      fs.open(outPath, std::fstream::out);
-    }
-    JSONObject topObj(outPath == "-" ? std::cerr : fs, true);
-    topObj.attr("cpuTime", cpuTime);
-    {
-      auto envs = topObj.object("envs");
-      envs.attr("number", nrEnvs);
-      envs.attr("elements", nrValuesInEnvs);
-      envs.attr("bytes", bEnvs);
-    }
-    {
-      auto lists = topObj.object("list");
-      lists.attr("elements", nrListElems);
-      lists.attr("bytes", bLists);
-      lists.attr("concats", nrListConcats);
-    }
-    {
-      auto values = topObj.object("values");
-      values.attr("number", nrValues);
-      values.attr("bytes", bValues);
-    }
-    {
-      auto syms = topObj.object("symbols");
-      syms.attr("number", symbols.Size());
-      syms.attr("bytes", symbols.TotalSize());
-    }
-    {
-      auto sets = topObj.object("sets");
-      sets.attr("number", nrAttrsets);
-      sets.attr("bytes", bAttrsets);
-      sets.attr("elements", nrAttrsInAttrsets);
-    }
-    {
-      auto sizes = topObj.object("sizes");
-      sizes.attr("Env", sizeof(Env));
-      sizes.attr("Value", sizeof(Value));
-      sizes.attr("Bindings", sizeof(Bindings));
-      sizes.attr("Attr", sizeof(Attr));
-    }
-    topObj.attr("nrOpUpdates", nrOpUpdates);
-    topObj.attr("nrOpUpdateValuesCopied", nrOpUpdateValuesCopied);
-    topObj.attr("nrThunks", nrThunks);
-    topObj.attr("nrAvoided", nrAvoided);
-    topObj.attr("nrLookups", nrLookups);
-    topObj.attr("nrPrimOpCalls", nrPrimOpCalls);
-    topObj.attr("nrFunctionCalls", nrFunctionCalls);
-
-    if (countCalls) {
-      {
-        auto obj = topObj.object("primops");
-        for (auto& i : primOpCalls) {
-          obj.attr(i.first, i.second);
-        }
-      }
-      {
-        auto list = topObj.list("functions");
-        for (auto& i : functionCalls) {
-          auto obj = list.object();
-          if (i.first->name.has_value()) {
-            obj.attr("name", (const std::string&)i.first->name.value());
-          } else {
-            obj.attr("name", nullptr);
-          }
-          if (i.first->pos) {
-            obj.attr("file", (const std::string&)i.first->pos.file);
-            obj.attr("line", i.first->pos.line);
-            obj.attr("column", i.first->pos.column);
-          }
-          obj.attr("count", i.second);
-        }
-      }
-      {
-        auto list = topObj.list("attributes");
-        for (auto& i : attrSelects) {
-          auto obj = list.object();
-          if (i.first) {
-            obj.attr("file", (const std::string&)i.first.file);
-            obj.attr("line", i.first.line);
-            obj.attr("column", i.first.column);
-          }
-          obj.attr("count", i.second);
-        }
-      }
-    }
-
-    // TODO(tazjin): what is this? commented out because .dump() is gone.
-    // if (getEnv("NIX_SHOW_SYMBOLS", "0") != "0") {
-    //   auto list = topObj.list("symbols");
-    //   symbols.dump([&](const std::string& s) { list.elem(s); });
-    // }
-  }
-}
-
-void EvalState::TraceFileAccess(const Path& realPath) {
-  if (file_access_trace_fn) {
-    if (last_traced_file != realPath) {
-      file_access_trace_fn(realPath);
-      // Basic deduplication.
-      last_traced_file = std::string(realPath);
-    }
-  }
-}
-
-void EvalState::EnableFileAccessTracing(std::function<void(const Path&)> fn) {
-  file_access_trace_fn = fn;
-}
-
-size_t valueSize(const Value& v) {
-  traceable_flat_hash_set<const Bindings*> seenBindings;
-  traceable_flat_hash_set<const Env*> seenEnvs;
-  traceable_flat_hash_set<const NixList*> seenLists;
-  traceable_flat_hash_set<const char*> seenStrings;
-  traceable_flat_hash_set<const Value*> seenValues;
-
-  auto doString = [&](const char* s) -> size_t {
-    if (seenStrings.find(s) != seenStrings.end()) {
-      return 0;
-    }
-    seenStrings.insert(s);
-    return strlen(s) + 1;
-  };
-
-  std::function<size_t(const Value& v)> doValue;
-  std::function<size_t(const Env& v)> doEnv;
-
-  doValue = [&](const Value& v) -> size_t {
-    if (seenValues.find(&v) != seenValues.end()) {
-      return 0;
-    }
-    seenValues.insert(&v);
-
-    size_t sz = sizeof(Value);
-
-    switch (v.type) {
-      case tString:
-        sz += doString(v.string.s);
-        if (v.string.context != nullptr) {
-          for (const char** p = v.string.context; *p != nullptr; ++p) {
-            sz += doString(*p);
-          }
-        }
-        break;
-      case tPath:
-        sz += doString(v.path);
-        break;
-      case tAttrs:
-        if (seenBindings.find(v.attrs.get()) == seenBindings.end()) {
-          seenBindings.insert(v.attrs.get());
-          sz += sizeof(Bindings);
-          for (const auto& i : *v.attrs) {
-            sz += doValue(*i.second.value);
-          }
-        }
-        break;
-      case tList:
-        if (seenLists.find(v.list.get()) == seenLists.end()) {
-          seenLists.insert(v.list.get());
-          sz += v.listSize() * sizeof(Value*);
-          for (const Value* v : *v.list) {
-            sz += doValue(*v);
-          }
-        }
-        break;
-      case tThunk:
-        sz += doEnv(*v.thunk.env);
-        break;
-      case tApp:
-        sz += doValue(*v.app.left);
-        sz += doValue(*v.app.right);
-        break;
-      case tLambda:
-        sz += doEnv(*v.lambda.env);
-        break;
-      case tPrimOpApp:
-        sz += doValue(*v.primOpApp.left);
-        sz += doValue(*v.primOpApp.right);
-        break;
-      default:;
-    }
-
-    return sz;
-  };
-
-  doEnv = [&](const Env& env) -> size_t {
-    if (seenEnvs.find(&env) != seenEnvs.end()) {
-      return 0;
-    }
-    seenEnvs.insert(&env);
-
-    size_t sz = sizeof(Env) + sizeof(Value*) * env.size;
-
-    if (env.type != Env::HasWithExpr) {
-      for (const Value* v : env.values) {
-        if (v != nullptr) {
-          sz += doValue(*v);
-        }
-      }
-    } else {
-      // TODO(kanepyork): trace ExprWith? how important is this accounting?
-    }
-
-    if (env.up != nullptr) {
-      sz += doEnv(*env.up);
-    }
-
-    return sz;
-  };
-
-  return doValue(v);
-}
-
-EvalSettings evalSettings;
-
-static GlobalConfig::Register r1(&evalSettings);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/eval.hh b/third_party/nix/src/libexpr/eval.hh
deleted file mode 100644
index 0352a89a2a..0000000000
--- a/third_party/nix/src/libexpr/eval.hh
+++ /dev/null
@@ -1,365 +0,0 @@
-#pragma once
-
-#include <map>
-#include <optional>
-#include <unordered_map>
-#include <vector>
-
-#include "libexpr/attr-set.hh"
-#include "libexpr/nixexpr.hh"
-#include "libexpr/symbol-table.hh"
-#include "libexpr/value.hh"
-#include "libutil/config.hh"
-#include "libutil/hash.hh"
-
-namespace nix {
-namespace expr {
-
-// Initialise the Boehm GC once per program instance. This should be
-// called in places that require the garbage collector.
-void InitGC();
-
-}  // namespace expr
-
-class Store;
-class EvalState;
-enum RepairFlag : bool;
-
-typedef void (*PrimOpFun)(EvalState& state, const Pos& pos, Value** args,
-                          Value& v);
-
-struct PrimOp {
-  PrimOpFun fun;
-  size_t arity;
-  Symbol name;
-  PrimOp(PrimOpFun fun, size_t arity, Symbol name)
-      : fun(fun), arity(arity), name(name) {}
-};
-
-struct Env {
-  Env(unsigned short size) : size(size) { values = std::vector<Value*>(size); }
-
-  Env* up;
-  unsigned short size;           // used by ‘valueSize’
-  unsigned short prevWith : 14;  // nr of levels up to next `with' environment
-  enum { Plain = 0, HasWithExpr, HasWithAttrs } type : 2;
-  std::vector<Value*> values;
-  Expr* withAttrsExpr = nullptr;
-};
-
-Value& mkString(Value& v, const std::string& s,
-                const PathSet& context = PathSet());
-
-void copyContext(const Value& v, PathSet& context);
-
-/* Cache for calls to addToStore(); maps source paths to the store
-   paths. */
-using SrcToStore = std::map<Path, Path>;
-
-std::ostream& operator<<(std::ostream& str, const Value& v);
-
-using SearchPathElem = std::pair<std::string, std::string>;
-using SearchPath = std::list<SearchPathElem>;
-
-using FileParseCache = std::map<Path, Expr*>;
-
-class EvalState {
- public:
-  SymbolTable symbols;
-
-  const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem,
-      sOutputs, sOutputName, sIgnoreNulls, sFile, sLine, sColumn, sFunctor,
-      sToString, sRight, sWrong, sStructuredAttrs, sBuilder, sArgs, sOutputHash,
-      sOutputHashAlgo, sOutputHashMode;
-
-  // Symbol representing the path to the built-in 'derivation.nix'
-  // file, set during primops initialisation.
-  std::optional<Symbol> sDerivationNix;
-
-  /* If set, force copying files to the Nix store even if they
-     already exist there. */
-  RepairFlag repair;
-
-  /* The allowed filesystem paths in restricted or pure evaluation
-     mode. */
-  std::optional<PathSet> allowedPaths;
-
-  const ref<Store> store;
-
- private:
-  SrcToStore srcToStore;
-
-  /* A cache from path names to parse trees. */
-  FileParseCache fileParseCache;
-
-  /* A cache from path names to values. */
-  using FileEvalCache = std::map<Path, Value>;
-  FileEvalCache fileEvalCache;
-
-  SearchPath searchPath;
-
-  std::map<std::string, std::pair<bool, std::string>> searchPathResolved;
-
-  /* Cache used by checkSourcePath(). */
-  std::unordered_map<Path, Path> resolvedPaths;
-
- public:
-  EvalState(const Strings& _searchPath, const ref<Store>& store);
-  ~EvalState();
-
-  void addToSearchPath(const std::string& s);
-
-  SearchPath getSearchPath() { return searchPath; }
-
-  Path checkSourcePath(const Path& path);
-
-  void checkURI(const std::string& uri);
-
-  /* When using a diverted store and 'path' is in the Nix store, map
-     'path' to the diverted location (e.g. /nix/store/foo is mapped
-     to /home/alice/my-nix/nix/store/foo). However, this is only
-     done if the context is not empty, since otherwise we're
-     probably trying to read from the actual /nix/store. This is
-     intended to distinguish between import-from-derivation and
-     sources stored in the actual /nix/store. */
-  Path toRealPath(const Path& path, const PathSet& context);
-
-  /* Parse a Nix expression from the specified file. */
-  Expr* parseExprFromFile(const Path& path);
-  Expr* parseExprFromFile(const Path& path, StaticEnv& staticEnv);
-
-  /* Parse a Nix expression from the specified string. */
-  Expr* parseExprFromString(const std::string& s, const Path& basePath,
-                            StaticEnv& staticEnv);
-  Expr* parseExprFromString(const std::string& s, const Path& basePath);
-
-  Expr* parseStdin();
-
-  /* Evaluate an expression read from the given file to normal
-     form. */
-  void evalFile(const Path& path, Value& v);
-
-  void resetFileCache();
-
-  /* Look up a file in the search path. */
-  Path findFile(const std::string& path);
-  Path findFile(SearchPath& searchPath, const std::string& path,
-                const Pos& pos = noPos);
-
-  /* If the specified search path element is a URI, download it. */
-  std::pair<bool, std::string> resolveSearchPathElem(
-      const SearchPathElem& elem);
-
-  /* Evaluate an expression to normal form, storing the result in
-     value `v'. */
-  void eval(Expr* e, Value& v);
-
-  /* Evaluation the expression, then verify that it has the expected
-     type. */
-  inline bool evalBool(Env& env, Expr* e);
-  inline bool evalBool(Env& env, Expr* e, const Pos& pos);
-  inline void evalAttrs(Env& env, Expr* e, Value& v);
-
-  /* If `v' is a thunk, enter it and overwrite `v' with the result
-     of the evaluation of the thunk.  If `v' is a delayed function
-     application, call the function and overwrite `v' with the
-     result.  Otherwise, this is a no-op. */
-  inline void forceValue(Value& v, const Pos& pos = noPos);
-
-  /* Force a value, then recursively force list elements and
-     attributes. */
-  void forceValueDeep(Value& v);
-
-  /* Force `v', and then verify that it has the expected type. */
-  NixInt forceInt(Value& v, const Pos& pos);
-  NixFloat forceFloat(Value& v, const Pos& pos);
-  bool forceBool(Value& v, const Pos& pos);
-  inline void forceAttrs(Value& v);
-  inline void forceAttrs(Value& v, const Pos& pos);
-  inline void forceList(Value& v);
-  inline void forceList(Value& v, const Pos& pos);
-  void forceFunction(Value& v, const Pos& pos);  // either lambda or primop
-  std::string forceString(Value& v, const Pos& pos = noPos);
-  std::string forceString(Value& v, PathSet& context, const Pos& pos = noPos);
-  std::string forceStringNoCtx(Value& v, const Pos& pos = noPos);
-
-  /* Return true iff the value `v' denotes a derivation (i.e. a
-     set with attribute `type = "derivation"'). */
-  bool isDerivation(Value& v);
-
-  std::optional<std::string> tryAttrsToString(const Pos& pos, Value& v,
-                                              PathSet& context,
-                                              bool coerceMore = false,
-                                              bool copyToStore = true);
-
-  /* String coercion.  Converts strings, paths and derivations to a
-     string.  If `coerceMore' is set, also converts nulls, integers,
-     booleans and lists to a string.  If `copyToStore' is set,
-     referenced paths are copied to the Nix store as a side effect. */
-  std::string coerceToString(const Pos& pos, Value& v, PathSet& context,
-                             bool coerceMore = false, bool copyToStore = true);
-
-  std::string copyPathToStore(PathSet& context, const Path& path);
-
-  /* Path coercion.  Converts strings, paths and derivations to a
-     path.  The result is guaranteed to be a canonicalised, absolute
-     path.  Nothing is copied to the store. */
-  Path coerceToPath(const Pos& pos, Value& v, PathSet& context);
-
- public:
-  /* The base environment, containing the builtin functions and
-     values. */
-  Env& baseEnv;
-
-  /* The same, but used during parsing to resolve variables. */
-  StaticEnv staticBaseEnv;  // !!! should be private
-
- private:
-  unsigned int baseEnvDispl = 0;
-
-  void createBaseEnv();
-
-  Value* addConstant(const std::string& name, Value& v);
-
-  Value* addPrimOp(const std::string& name, size_t arity, PrimOpFun primOp);
-
- public:
-  Value& getBuiltin(const std::string& name);
-
- private:
-  inline Value* lookupVar(Env* env, const ExprVar& var, bool noEval);
-
-  friend struct ExprVar;
-  friend struct ExprAttrs;
-  friend struct ExprLet;
-
-  Expr* parse(const char* text, const Path& path, const Path& basePath,
-              StaticEnv& staticEnv);
-
- public:
-  /* Do a deep equality test between two values.  That is, list
-     elements and attributes are compared recursively. */
-  bool eqValues(Value& v1, Value& v2);
-
-  bool isFunctor(Value& fun);
-
-  void callFunction(Value& fun, Value& arg, Value& v, const Pos& pos);
-  void callPrimOp(Value& fun, Value& arg, Value& v, const Pos& pos);
-
-  /* Automatically call a function for which each argument has a
-     default value or has a binding in the `args' map.  'args' need
-     not live past the end of the call. */
-  void autoCallFunction(Bindings* args, Value& fun, Value& res);
-
-  /* Allocation primitives. */
-  Value* allocValue();
-  Env& allocEnv(size_t size);
-
-  Value* allocAttr(Value& vAttrs, const Symbol& name);
-
-  // Create a list value from the specified vector.
-  void mkList(Value& v, std::shared_ptr<NixList> list);
-
-  // Create a list value, allocating as many elements as specified in
-  // size. This is used for the many cases in this codebase where
-  // assignment happens into the preallocated list.
-  void mkList(Value& v, size_t size = 0);
-
-  void mkAttrs(Value& v, size_t capacity);
-  void mkThunk_(Value& v, Expr* expr);
-  void mkPos(Value& v, Pos* pos);
-
-  void concatLists(Value& v, const NixList& lists, const Pos& pos);
-
-  /* Print statistics. */
-  void printStats();
-
-  void realiseContext(const PathSet& context);
-
-  /* File access tracing. */
-  void TraceFileAccess(const Path& path);
-  void EnableFileAccessTracing(std::function<void(const Path&)> fn);
-
- private:
-  unsigned long nrEnvs = 0;
-  unsigned long nrValuesInEnvs = 0;
-  unsigned long nrValues = 0;
-  unsigned long nrListElems = 0;
-  unsigned long nrAttrsets = 0;
-  unsigned long nrAttrsInAttrsets = 0;
-  unsigned long nrOpUpdates = 0;
-  unsigned long nrOpUpdateValuesCopied = 0;
-  unsigned long nrListConcats = 0;
-  unsigned long nrPrimOpCalls = 0;
-  unsigned long nrFunctionCalls = 0;
-
-  bool countCalls;
-
-  std::function<void(const Path&)> file_access_trace_fn = nullptr;
-  Path last_traced_file = "";
-
-  using PrimOpCalls = std::map<Symbol, size_t>;
-  PrimOpCalls primOpCalls;
-
-  using FunctionCalls = std::map<ExprLambda*, size_t>;
-  FunctionCalls functionCalls;
-
-  void incrFunctionCall(ExprLambda* fun);
-
-  using AttrSelects = std::map<Pos, size_t>;
-  AttrSelects attrSelects;
-
-  friend struct ExprOpUpdate;
-  friend struct ExprOpConcatLists;
-  friend struct ExprSelect;
-  friend void prim_getAttr(EvalState& state, const Pos& pos, Value** args,
-                           Value& v);
-};
-
-/* Return a string representing the type of the value `v'. */
-std::string showType(const Value& v);
-
-/* Decode a context string ‘!<name>!<path>’ into a pair <path,
-   name>. */
-std::pair<std::string, std::string> decodeContext(const std::string& s);
-
-/* If `path' refers to a directory, then append "/default.nix". */
-Path resolveExprPath(Path path);
-
-struct InvalidPathError : EvalError {
-  Path path;
-  InvalidPathError(const Path& path);
-#ifdef EXCEPTION_NEEDS_THROW_SPEC
-  ~InvalidPathError() noexcept {};
-#endif
-};
-
-struct EvalSettings : Config {
-  Setting<bool> restrictEval{
-      this, false, "restrict-eval",
-      "Whether to restrict file system access to paths in $NIX_PATH, "
-      "and network access to the URI prefixes listed in 'allowed-uris'."};
-
-  Setting<bool> pureEval{this, false, "pure-eval",
-                         "Whether to restrict file system and network access "
-                         "to files specified by cryptographic hash."};
-
-  Setting<bool> enableImportFromDerivation{
-      this, true, "allow-import-from-derivation",
-      "Whether the evaluator allows importing the result of a derivation."};
-
-  Setting<Strings> allowedUris{
-      this,
-      {},
-      "allowed-uris",
-      "Prefixes of URIs that builtin functions such as fetchurl and fetchGit "
-      "are allowed to fetch."};
-
-  Setting<bool> traceFunctionCalls{this, false, "trace-function-calls",
-                                   "Emit log messages for each function entry "
-                                   "and exit at the 'vomit' log level (-vvvv)"};
-};
-
-extern EvalSettings evalSettings;
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/function-trace.cc b/third_party/nix/src/libexpr/function-trace.cc
deleted file mode 100644
index b1b856965c..0000000000
--- a/third_party/nix/src/libexpr/function-trace.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "libexpr/function-trace.hh"
-
-#include <glog/logging.h>
-
-namespace nix {
-
-FunctionCallTrace::FunctionCallTrace(const Pos& pos) : pos(pos) {
-  auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
-  auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
-  LOG(INFO) << "function-trace entered " << pos << " at " << ns.count();
-}
-
-FunctionCallTrace::~FunctionCallTrace() {
-  auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
-  auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
-  LOG(INFO) << "function-trace exited " << pos << " at " << ns.count();
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/function-trace.hh b/third_party/nix/src/libexpr/function-trace.hh
deleted file mode 100644
index 6b810159b8..0000000000
--- a/third_party/nix/src/libexpr/function-trace.hh
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#include <chrono>
-
-#include "libexpr/eval.hh"
-
-namespace nix {
-
-struct FunctionCallTrace {
-  const Pos& pos;
-  FunctionCallTrace(const Pos& pos);
-  ~FunctionCallTrace();
-};
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/get-drvs.cc b/third_party/nix/src/libexpr/get-drvs.cc
deleted file mode 100644
index d81fb5dfad..0000000000
--- a/third_party/nix/src/libexpr/get-drvs.cc
+++ /dev/null
@@ -1,446 +0,0 @@
-#include "libexpr/get-drvs.hh"
-
-#include <cstring>
-#include <regex>
-#include <utility>
-
-#include <absl/container/flat_hash_set.h>
-#include <absl/strings/numbers.h>
-#include <glog/logging.h>
-
-#include "libexpr/eval-inline.hh"
-#include "libstore/derivations.hh"
-#include "libutil/util.hh"
-
-namespace nix {
-
-DrvInfo::DrvInfo(EvalState& state, std::string attrPath,
-                 std::shared_ptr<Bindings> attrs)
-    : state(&state), attrs(attrs), attrPath(std::move(attrPath)) {}
-
-DrvInfo::DrvInfo(EvalState& state, const ref<Store>& store,
-                 const std::string& drvPathWithOutputs)
-    : state(&state), attrPath("") {
-  auto spec = parseDrvPathWithOutputs(drvPathWithOutputs);
-
-  drvPath = spec.first;
-
-  auto drv = store->derivationFromPath(drvPath);
-
-  name = storePathToName(drvPath);
-
-  if (spec.second.size() > 1) {
-    throw Error(
-        "building more than one derivation output is not supported, in '%s'",
-        drvPathWithOutputs);
-  }
-
-  outputName = spec.second.empty() ? get(drv.env, "outputName", "out")
-                                   : *spec.second.begin();
-
-  auto i = drv.outputs.find(outputName);
-  if (i == drv.outputs.end()) {
-    throw Error("derivation '%s' does not have output '%s'", drvPath,
-                outputName);
-  }
-
-  outPath = i->second.path;
-}
-
-std::string DrvInfo::queryName() const {
-  if (name.empty() && (attrs != nullptr)) {
-    auto i = attrs->find(state->sName);
-    if (i == attrs->end()) {
-      throw TypeError("derivation name missing");
-    }
-    name = state->forceStringNoCtx(*i->second.value);
-  }
-  return name;
-}
-
-std::string DrvInfo::querySystem() const {
-  if (system.empty() && (attrs != nullptr)) {
-    auto i = attrs->find(state->sSystem);
-    system = i == attrs->end()
-                 ? "unknown"
-                 : state->forceStringNoCtx(*i->second.value, *i->second.pos);
-  }
-  return system;
-}
-
-std::string DrvInfo::queryDrvPath() const {
-  if (drvPath.empty() && (attrs != nullptr)) {
-    Bindings::iterator i = attrs->find(state->sDrvPath);
-    PathSet context;
-    drvPath = i != attrs->end() ? state->coerceToPath(*i->second.pos,
-                                                      *i->second.value, context)
-                                : "";
-  }
-  return drvPath;
-}
-
-std::string DrvInfo::queryOutPath() const {
-  if (outPath.empty() && (attrs != nullptr)) {
-    Bindings::iterator i = attrs->find(state->sOutPath);
-    PathSet context;
-    outPath = i != attrs->end() ? state->coerceToPath(*i->second.pos,
-                                                      *i->second.value, context)
-                                : "";
-  }
-  return outPath;
-}
-
-DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) {
-  if (outputs.empty()) {
-    /* Get the ‘outputs’ list. */
-    Bindings::iterator i;
-    if ((attrs != nullptr) &&
-        (i = attrs->find(state->sOutputs)) != attrs->end()) {
-      state->forceList(*i->second.value, *i->second.pos);
-
-      /* For each output... */
-      for (unsigned int j = 0; j < i->second.value->listSize(); ++j) {
-        /* Evaluate the corresponding set. */
-        std::string name = state->forceStringNoCtx(*(*i->second.value->list)[j],
-                                                   *i->second.pos);
-        Bindings::iterator out = attrs->find(state->symbols.Create(name));
-        if (out == attrs->end()) {
-          continue;  // FIXME: throw error?
-        }
-        state->forceAttrs(*out->second.value);
-
-        /* And evaluate its ‘outPath’ attribute. */
-        Bindings::iterator outPath =
-            out->second.value->attrs->find(state->sOutPath);
-        if (outPath == out->second.value->attrs->end()) {
-          continue;  // FIXME: throw error?
-        }
-        PathSet context;
-        outputs[name] = state->coerceToPath(*outPath->second.pos,
-                                            *outPath->second.value, context);
-      }
-    } else {
-      outputs["out"] = queryOutPath();
-    }
-  }
-  if (!onlyOutputsToInstall || (attrs == nullptr)) {
-    return outputs;
-  }
-
-  /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
-  const Value* outTI = queryMeta("outputsToInstall");
-  if (outTI == nullptr) {
-    return outputs;
-  }
-  const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
-  /* ^ this shows during `nix-env -i` right under the bad derivation */
-  if (!outTI->isList()) {
-    throw errMsg;
-  }
-  Outputs result;
-
-  for (Value* i : *outTI->list) {
-    if (i->type != tString) {
-      throw errMsg;
-    }
-    auto out = outputs.find(i->string.s);
-    if (out == outputs.end()) {
-      throw errMsg;
-    }
-    result.insert(*out);
-  }
-  return result;
-}
-
-std::string DrvInfo::queryOutputName() const {
-  if (outputName.empty() && (attrs != nullptr)) {
-    Bindings::iterator i = attrs->find(state->sOutputName);
-    outputName =
-        i != attrs->end() ? state->forceStringNoCtx(*i->second.value) : "";
-  }
-  return outputName;
-}
-
-Bindings* DrvInfo::getMeta() {
-  if (meta != nullptr) {
-    return meta.get();
-  }
-  if (attrs == nullptr) {
-    return nullptr;
-  }
-  Bindings::iterator a = attrs->find(state->sMeta);
-  if (a == attrs->end()) {
-    return nullptr;
-  }
-  state->forceAttrs(*a->second.value, *a->second.pos);
-  meta = a->second.value->attrs;
-  return meta.get();
-}
-
-StringSet DrvInfo::queryMetaNames() {
-  StringSet res;
-  if (getMeta() == nullptr) {
-    return res;
-  }
-  for (auto& i : *meta) {
-    res.insert(i.second.name);
-  }
-  return res;
-}
-
-bool DrvInfo::checkMeta(Value& v) {
-  state->forceValue(v);
-  if (v.isList()) {
-    for (unsigned int n = 0; n < v.listSize(); ++n) {
-      if (!checkMeta(*(*v.list)[n])) {
-        return false;
-      }
-    }
-    return true;
-  }
-  if (v.type == tAttrs) {
-    Bindings::iterator i = v.attrs->find(state->sOutPath);
-    if (i != v.attrs->end()) {
-      return false;
-    }
-    for (auto& i : *v.attrs) {
-      if (!checkMeta(*i.second.value)) {
-        return false;
-      }
-    }
-    return true;
-  } else {
-    return v.type == tInt || v.type == tBool || v.type == tString ||
-           v.type == tFloat;
-  }
-}
-
-Value* DrvInfo::queryMeta(const std::string& name) {
-  if (getMeta() == nullptr) {
-    return nullptr;
-  }
-  Bindings::iterator a = meta->find(state->symbols.Create(name));
-  if (a == meta->end() || !checkMeta(*a->second.value)) {
-    return nullptr;
-  }
-  return a->second.value;
-}
-
-std::string DrvInfo::queryMetaString(const std::string& name) {
-  Value* v = queryMeta(name);
-  if ((v == nullptr) || v->type != tString) {
-    return "";
-  }
-  return v->string.s;
-}
-
-NixInt DrvInfo::queryMetaInt(const std::string& name, NixInt def) {
-  Value* v = queryMeta(name);
-  if (v == nullptr) {
-    return def;
-  }
-  if (v->type == tInt) {
-    return v->integer;
-  }
-  if (v->type == tString) {
-    /* Backwards compatibility with before we had support for
-       integer meta fields. */
-    NixInt n;
-    if (absl::SimpleAtoi(v->string.s, &n)) {
-      return n;
-    }
-  }
-  return def;
-}
-
-NixFloat DrvInfo::queryMetaFloat(const std::string& name, NixFloat def) {
-  Value* v = queryMeta(name);
-  if (v == nullptr) {
-    return def;
-  }
-  if (v->type == tFloat) {
-    return v->fpoint;
-  }
-  if (v->type == tString) {
-    /* Backwards compatibility with before we had support for
-       float meta fields. */
-    NixFloat n;
-    if (string2Float(v->string.s, n)) {
-      return n;
-    }
-  }
-  return def;
-}
-
-bool DrvInfo::queryMetaBool(const std::string& name, bool def) {
-  Value* v = queryMeta(name);
-  if (v == nullptr) {
-    return def;
-  }
-  if (v->type == tBool) {
-    return v->boolean;
-  }
-  if (v->type == tString) {
-    /* Backwards compatibility with before we had support for
-       Boolean meta fields. */
-    if (strcmp(v->string.s, "true") == 0) {
-      return true;
-    }
-    if (strcmp(v->string.s, "false") == 0) {
-      return false;
-    }
-  }
-  return def;
-}
-
-void DrvInfo::setMeta(const std::string& name, Value* v) {
-  std::shared_ptr<Bindings> old = meta;
-  meta = std::shared_ptr<Bindings>(Bindings::New(old->size() + 1).release());
-  Symbol sym = state->symbols.Create(name);
-  if (old != nullptr) {
-    for (auto i : *old) {
-      if (i.second.name != sym) {
-        meta->push_back(i.second);
-      }
-    }
-  }
-  if (v != nullptr) {
-    meta->push_back(Attr(sym, v));
-  }
-}
-
-/* Cache for already considered attrsets. */
-using Done = absl::flat_hash_set<std::shared_ptr<Bindings>>;
-
-/* Evaluate value `v'.  If it evaluates to a set of type `derivation',
-   then put information about it in `drvs' (unless it's already in `done').
-   The result boolean indicates whether it makes sense
-   for the caller to recursively search for derivations in `v'. */
-static bool getDerivation(EvalState& state, Value& v,
-                          const std::string& attrPath, DrvInfos& drvs,
-                          Done& done, bool ignoreAssertionFailures) {
-  try {
-    state.forceValue(v);
-    if (!state.isDerivation(v)) {
-      return true;
-    }
-
-    /* Remove spurious duplicates (e.g., a set like `rec { x =
-       derivation {...}; y = x;}'. */
-    if (done.find(v.attrs) != done.end()) {
-      return false;
-    }
-    done.insert(v.attrs);
-
-    DrvInfo drv(state, attrPath, v.attrs);
-
-    drv.queryName();
-
-    drvs.push_back(drv);
-
-    return false;
-
-  } catch (AssertionError& e) {
-    if (ignoreAssertionFailures) {
-      return false;
-    }
-    throw;
-  }
-}
-
-std::optional<DrvInfo> getDerivation(EvalState& state, Value& v,
-                                     bool ignoreAssertionFailures) {
-  Done done;
-  DrvInfos drvs;
-  getDerivation(state, v, "", drvs, done, ignoreAssertionFailures);
-  if (drvs.size() != 1) {
-    return {};
-  }
-  return std::move(drvs.front());
-}
-
-static std::string addToPath(const std::string& s1, const std::string& s2) {
-  return s1.empty() ? s2 : s1 + "." + s2;
-}
-
-static std::regex attrRegex("[A-Za-z_][A-Za-z0-9-_+]*");
-
-static void getDerivations(EvalState& state, Value& vIn,
-                           const std::string& pathPrefix, Bindings* autoArgs,
-                           DrvInfos& drvs, Done& done,
-                           bool ignoreAssertionFailures) {
-  Value v;
-  state.autoCallFunction(autoArgs, vIn, v);
-
-  /* Process the expression. */
-  if (!getDerivation(state, v, pathPrefix, drvs, done,
-                     ignoreAssertionFailures)) {
-    ;
-
-  } else if (v.type == tAttrs) {
-    /* !!! undocumented hackery to support combining channels in
-       nix-env.cc. */
-    bool combineChannels =
-        v.attrs->find(state.symbols.Create("_combineChannels")) !=
-        v.attrs->end();
-
-    /* Consider the attributes in sorted order to get more
-       deterministic behaviour in nix-env operations (e.g. when
-       there are names clashes between derivations, the derivation
-       bound to the attribute with the "lower" name should take
-       precedence). */
-    for (auto& i : v.attrs->SortedByKeys()) {
-      DLOG(INFO) << "evaluating attribute '" << i->name << "'";
-      if (!std::regex_match(std::string(i->name), attrRegex)) {
-        continue;
-      }
-      std::string pathPrefix2 = addToPath(pathPrefix, i->name);
-      if (combineChannels) {
-        getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done,
-                       ignoreAssertionFailures);
-      } else if (getDerivation(state, *i->value, pathPrefix2, drvs, done,
-                               ignoreAssertionFailures)) {
-        /* If the value of this attribute is itself a set,
-           should we recurse into it?  => Only if it has a
-           `recurseForDerivations = true' attribute. */
-        if (i->value->type == tAttrs) {
-          Bindings::iterator j = i->value->attrs->find(
-              state.symbols.Create("recurseForDerivations"));
-          if (j != i->value->attrs->end() &&
-              state.forceBool(*j->second.value, *j->second.pos)) {
-            getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done,
-                           ignoreAssertionFailures);
-          }
-        }
-      }
-    }
-  }
-
-  else if (v.isList()) {
-    for (unsigned int n = 0; n < v.listSize(); ++n) {
-      std::string pathPrefix2 =
-          addToPath(pathPrefix, (format("%1%") % n).str());
-      if (getDerivation(state, *(*v.list)[n], pathPrefix2, drvs, done,
-                        ignoreAssertionFailures)) {
-        getDerivations(state, *(*v.list)[n], pathPrefix2, autoArgs, drvs, done,
-                       ignoreAssertionFailures);
-      }
-    }
-  }
-
-  else {
-    throw TypeError(
-        "expression does not evaluate to a derivation (or a set or list of "
-        "those)");
-  }
-}
-
-void getDerivations(EvalState& state, Value& v, const std::string& pathPrefix,
-                    Bindings* autoArgs, DrvInfos& drvs,
-                    bool ignoreAssertionFailures) {
-  Done done;
-  getDerivations(state, v, pathPrefix, autoArgs, drvs, done,
-                 ignoreAssertionFailures);
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/get-drvs.hh b/third_party/nix/src/libexpr/get-drvs.hh
deleted file mode 100644
index 3de266d0c0..0000000000
--- a/third_party/nix/src/libexpr/get-drvs.hh
+++ /dev/null
@@ -1,83 +0,0 @@
-#pragma once
-
-#include <map>
-#include <string>
-
-#include "libexpr/eval.hh"
-
-namespace nix {
-
-struct DrvInfo {
- public:
-  typedef std::map<std::string, Path> Outputs;
-
- private:
-  EvalState* state;
-
-  mutable std::string name;
-  mutable std::string system;
-  mutable std::string drvPath;
-  mutable std::string outPath;
-  mutable std::string outputName;
-  Outputs outputs;
-
-  bool failed = false;  // set if we get an AssertionError
-
-  std::shared_ptr<Bindings> attrs = nullptr;
-  std::shared_ptr<Bindings> meta = nullptr;
-
-  Bindings* getMeta();
-
-  bool checkMeta(Value& v);
-
- public:
-  std::string attrPath; /* path towards the derivation */
-
-  DrvInfo(EvalState& state) : state(&state){};
-  DrvInfo(EvalState& state, std::string attrPath,
-          std::shared_ptr<Bindings> attrs);
-  DrvInfo(EvalState& state, const ref<Store>& store,
-          const std::string& drvPathWithOutputs);
-
-  std::string queryName() const;
-  std::string querySystem() const;
-  std::string queryDrvPath() const;
-  std::string queryOutPath() const;
-  std::string queryOutputName() const;
-  /** Return the list of outputs. The "outputs to install" are determined by
-   * `meta.outputsToInstall`. */
-  Outputs queryOutputs(bool onlyOutputsToInstall = false);
-
-  StringSet queryMetaNames();
-  Value* queryMeta(const std::string& name);
-  std::string queryMetaString(const std::string& name);
-  NixInt queryMetaInt(const std::string& name, NixInt def);
-  NixFloat queryMetaFloat(const std::string& name, NixFloat def);
-  bool queryMetaBool(const std::string& name, bool def);
-  void setMeta(const std::string& name, Value* v);
-
-  /*
-  MetaInfo queryMetaInfo(EvalState & state) const;
-  MetaValue queryMetaInfo(EvalState & state, const std::string & name) const;
-  */
-
-  void setName(const std::string& s) { name = s; }
-  void setDrvPath(const std::string& s) { drvPath = s; }
-  void setOutPath(const std::string& s) { outPath = s; }
-
-  void setFailed() { failed = true; };
-  bool hasFailed() { return failed; };
-};
-
-using DrvInfos = std::list<DrvInfo>;
-
-/* If value `v' denotes a derivation, return a DrvInfo object
-   describing it. Otherwise return nothing. */
-std::optional<DrvInfo> getDerivation(EvalState& state, Value& v,
-                                     bool ignoreAssertionFailures);
-
-void getDerivations(EvalState& state, Value& v, const std::string& pathPrefix,
-                    Bindings* autoArgs, DrvInfos& drvs,
-                    bool ignoreAssertionFailures);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/json-to-value.cc b/third_party/nix/src/libexpr/json-to-value.cc
deleted file mode 100644
index 5f8352dde6..0000000000
--- a/third_party/nix/src/libexpr/json-to-value.cc
+++ /dev/null
@@ -1,185 +0,0 @@
-#include "libexpr/json-to-value.hh"
-
-#include <cstring>
-
-namespace nix {
-
-static void skipWhitespace(const char*& s) {
-  while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') {
-    s++;
-  }
-}
-
-static std::string parseJSONString(const char*& s) {
-  std::string res;
-  if (*s++ != '"') {
-    throw JSONParseError("expected JSON string");
-  }
-  while (*s != '"') {
-    if (*s == 0) {
-      throw JSONParseError("got end-of-string in JSON string");
-    }
-    if (*s == '\\') {
-      s++;
-      if (*s == '"') {
-        res += '"';
-      } else if (*s == '\\') {
-        res += '\\';
-      } else if (*s == '/') {
-        res += '/';
-      } else if (*s == '/') {
-        res += '/';
-      } else if (*s == 'b') {
-        res += '\b';
-      } else if (*s == 'f') {
-        res += '\f';
-      } else if (*s == 'n') {
-        res += '\n';
-      } else if (*s == 'r') {
-        res += '\r';
-      } else if (*s == 't') {
-        res += '\t';
-      } else if (*s == 'u') {
-        throw JSONParseError(
-            "\\u characters in JSON strings are currently not supported");
-      } else {
-        throw JSONParseError("invalid escaped character in JSON string");
-      }
-      s++;
-    } else {
-      res += *s++;
-    }
-  }
-  s++;
-  return res;
-}
-
-static void parseJSON(EvalState& state, const char*& s, Value& v) {
-  skipWhitespace(s);
-
-  if (*s == 0) {
-    throw JSONParseError("expected JSON value");
-  }
-
-  if (*s == '[') {
-    s++;
-    NixList values;
-    values.reserve(128);
-    skipWhitespace(s);
-    while (true) {
-      if (values.empty() && *s == ']') {
-        break;
-      }
-      Value* v2 = state.allocValue();
-      parseJSON(state, s, *v2);
-      values.push_back(v2);
-      skipWhitespace(s);
-      if (*s == ']') {
-        break;
-      }
-      if (*s != ',') {
-        throw JSONParseError("expected ',' or ']' after JSON array element");
-      }
-      s++;
-    }
-    s++;
-    state.mkList(v, values.size());
-    for (size_t n = 0; n < values.size(); ++n) {
-      (*v.list)[n] = values[n];
-    }
-  }
-
-  else if (*s == '{') {
-    s++;
-    ValueMap attrs;
-    while (true) {
-      skipWhitespace(s);
-      if (attrs.empty() && *s == '}') {
-        break;
-      }
-      std::string name = parseJSONString(s);
-      skipWhitespace(s);
-      if (*s != ':') {
-        throw JSONParseError("expected ':' in JSON object");
-      }
-      s++;
-      Value* v2 = state.allocValue();
-      parseJSON(state, s, *v2);
-      attrs[state.symbols.Create(name)] = v2;
-      skipWhitespace(s);
-      if (*s == '}') {
-        break;
-      }
-      if (*s != ',') {
-        throw JSONParseError("expected ',' or '}' after JSON member");
-      }
-      s++;
-    }
-    state.mkAttrs(v, attrs.size());
-    for (auto& i : attrs) {
-      v.attrs->push_back(Attr(i.first, i.second));
-    }
-    s++;
-  }
-
-  else if (*s == '"') {
-    mkString(v, parseJSONString(s));
-  }
-
-  else if ((isdigit(*s) != 0) || *s == '-' || *s == '.') {
-    // Buffer into a std::string first, then use built-in C++ conversions
-    std::string tmp_number;
-    ValueType number_type = tInt;
-
-    while ((isdigit(*s) != 0) || *s == '-' || *s == '.' || *s == 'e' ||
-           *s == 'E') {
-      if (*s == '.' || *s == 'e' || *s == 'E') {
-        number_type = tFloat;
-      }
-      tmp_number += *s++;
-    }
-
-    try {
-      if (number_type == tFloat) {
-        mkFloat(v, stod(tmp_number));
-      } else {
-        mkInt(v, stol(tmp_number));
-      }
-    } catch (std::invalid_argument& e) {
-      throw JSONParseError("invalid JSON number");
-    } catch (std::out_of_range& e) {
-      throw JSONParseError("out-of-range JSON number");
-    }
-  }
-
-  else if (strncmp(s, "true", 4) == 0) {
-    s += 4;
-    mkBool(v, true);
-  }
-
-  else if (strncmp(s, "false", 5) == 0) {
-    s += 5;
-    mkBool(v, false);
-  }
-
-  else if (strncmp(s, "null", 4) == 0) {
-    s += 4;
-    mkNull(v);
-  }
-
-  else {
-    throw JSONParseError("unrecognised JSON value");
-  }
-}
-
-void parseJSON(EvalState& state, const std::string& s_, Value& v) {
-  const char* s = s_.c_str();
-  parseJSON(state, s, v);
-  skipWhitespace(s);
-  if (*s != 0) {
-    throw JSONParseError(
-        format("expected end-of-string while parsing JSON value: %1%") % s);
-  }
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/json-to-value.hh b/third_party/nix/src/libexpr/json-to-value.hh
deleted file mode 100644
index 7f258f2137..0000000000
--- a/third_party/nix/src/libexpr/json-to-value.hh
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include <string>
-
-#include "libexpr/eval.hh"
-
-namespace nix {
-
-MakeError(JSONParseError, EvalError);
-
-void parseJSON(EvalState& state, const std::string& s, Value& v);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/lexer.l b/third_party/nix/src/libexpr/lexer.l
deleted file mode 100644
index d5b8a45936..0000000000
--- a/third_party/nix/src/libexpr/lexer.l
+++ /dev/null
@@ -1,193 +0,0 @@
-%option reentrant bison-bridge bison-locations
-%option noyywrap
-%option never-interactive
-%option stack
-%option nodefault
-%option nounput noyy_top_state
-
-
-%s DEFAULT
-%x STRING
-%x IND_STRING
-
-
-%{
-#include <boost/lexical_cast.hpp>
-
-#include "generated/parser-tab.hh"
-#include "libexpr/nixexpr.hh"
-#include "libexpr/parser.hh"
-
-using namespace nix;
-
-namespace nix {
-
-static void initLoc(YYLTYPE* loc) {
-  loc->first_line = loc->last_line = 1;
-  loc->first_column = loc->last_column = 1;
-}
-
-static void adjustLoc(YYLTYPE* loc, const char* s, size_t len) {
-  loc->first_line = loc->last_line;
-  loc->first_column = loc->last_column;
-
-  while (len--) {
-    switch (*s++) {
-      case '\r':
-        if (*s == '\n') /* cr/lf */
-          s++;
-        /* fall through */
-      case '\n':
-        ++loc->last_line;
-        loc->last_column = 1;
-        break;
-      default:
-        ++loc->last_column;
-    }
-  }
-}
-
-}
-
-#define YY_USER_INIT initLoc(yylloc)
-#define YY_USER_ACTION adjustLoc(yylloc, yytext, yyleng);
-
-#define PUSH_STATE(state) yy_push_state(state, yyscanner)
-#define POP_STATE() yy_pop_state(yyscanner)
-
-%}
-
-
-ANY         .|\n
-ID          [a-zA-Z\_][a-zA-Z0-9\_\'\-]*
-INT         [0-9]+
-FLOAT       (([1-9][0-9]*\.[0-9]*)|(0?\.[0-9]+))([Ee][+-]?[0-9]+)?
-PATH        [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+\/?
-HPATH       \~(\/[a-zA-Z0-9\.\_\-\+]+)+\/?
-SPATH       \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\>
-URI         [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']+
-
-
-%%
-
-
-if          { return IF; }
-then        { return THEN; }
-else        { return ELSE; }
-assert      { return ASSERT; }
-with        { return WITH; }
-let         { return LET; }
-in          { return IN; }
-rec         { return REC; }
-inherit     { return INHERIT; }
-or          { return OR_KW; }
-\.\.\.      { return ELLIPSIS; }
-
-\=\=        { return EQ; }
-\!\=        { return NEQ; }
-\<\=        { return LEQ; }
-\>\=        { return GEQ; }
-\&\&        { return AND; }
-\|\|        { return OR; }
-\-\>        { return IMPL; }
-\/\/        { return UPDATE; }
-\+\+        { return CONCAT; }
-
-{ID}        { yylval->id = strdup(yytext); return ID; }
-{INT}       { errno = 0;
-              try {
-                  yylval->n = boost::lexical_cast<int64_t>(yytext);
-              } catch (const boost::bad_lexical_cast &) {
-                  throw ParseError(format("invalid integer '%1%'") % yytext);
-              }
-              return INT;
-            }
-{FLOAT}     { errno = 0;
-              yylval->nf = strtod(yytext, 0);
-              if (errno != 0)
-                  throw ParseError(format("invalid float '%1%'") % yytext);
-              return FLOAT;
-            }
-
-\$\{        { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
-
-\}          { /* State INITIAL only exists at the bottom of the stack and is
-                 used as a marker. DEFAULT replaces it everywhere else.
-                 Popping when in INITIAL state causes an empty stack exception,
-                 so don't */
-              if (YYSTATE != INITIAL)
-                POP_STATE();
-              return '}';
-            }
-\{          { PUSH_STATE(DEFAULT); return '{'; }
-
-\"          { PUSH_STATE(STRING); return '"'; }
-<STRING>([^\$\"\\]|\$[^\{\"\\]|\\{ANY}|\$\\{ANY})*\$/\" |
-<STRING>([^\$\"\\]|\$[^\{\"\\]|\\{ANY}|\$\\{ANY})+ {
-                /* It is impossible to match strings ending with '$' with one
-                   regex because trailing contexts are only valid at the end
-                   of a rule. (A sane but undocumented limitation.) */
-                yylval->e = unescapeStr(data->symbols, yytext, yyleng);
-                return STR;
-              }
-<STRING>\$\{  { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
-<STRING>\"    { POP_STATE(); return '"'; }
-<STRING>\$|\\|\$\\ {
-                /* This can only occur when we reach EOF, otherwise the above
-                   (...|\$[^\{\"\\]|\\.|\$\\.)+ would have triggered.
-                   This is technically invalid, but we leave the problem to the
-                   parser who fails with exact location. */
-                return STR;
-              }
-
-\'\'(\ *\n)?     { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }
-<IND_STRING>([^\$\']|\$[^\{\']|\'[^\'\$])+ {
-                   yylval->e = new ExprIndStr(yytext);
-                   return IND_STR;
-                 }
-<IND_STRING>\'\'\$ |
-<IND_STRING>\$   {
-                   yylval->e = new ExprIndStr("$");
-                   return IND_STR;
-                 }
-<IND_STRING>\'\'\' {
-                   yylval->e = new ExprIndStr("''");
-                   return IND_STR;
-                 }
-<IND_STRING>\'\'\\{ANY} {
-                   yylval->e = unescapeStr(data->symbols, yytext + 2, yyleng - 2);
-                   return IND_STR;
-                 }
-<IND_STRING>\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
-<IND_STRING>\'\' { POP_STATE(); return IND_STRING_CLOSE; }
-<IND_STRING>\'   {
-                   yylval->e = new ExprIndStr("'");
-                   return IND_STR;
-                 }
-
-
-{PATH}      { if (yytext[yyleng-1] == '/')
-                  throw ParseError("path '%s' has a trailing slash", yytext);
-              yylval->path = strdup(yytext);
-              return PATH;
-            }
-{HPATH}     { if (yytext[yyleng-1] == '/')
-                  throw ParseError("path '%s' has a trailing slash", yytext);
-              yylval->path = strdup(yytext);
-              return HPATH;
-            }
-{SPATH}     { yylval->path = strdup(yytext); return SPATH; }
-{URI}       { yylval->uri = strdup(yytext); return URI; }
-
-[ \t\r\n]+    /* eat up whitespace */
-\#[^\r\n]*    /* single-line comments */
-\/\*([^*]|\*+[^*/])*\*+\/  /* long comments */
-
-{ANY}       {
-              /* Don't return a negative number, as this will cause
-                 Bison to stop parsing without an error. */
-              return (unsigned char) yytext[0];
-            }
-
-%%
-
diff --git a/third_party/nix/src/libexpr/names.cc b/third_party/nix/src/libexpr/names.cc
deleted file mode 100644
index 1e9c2f2f4a..0000000000
--- a/third_party/nix/src/libexpr/names.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-#include "libexpr/names.hh"
-
-#include <memory>
-
-#include <absl/strings/numbers.h>
-
-#include "libutil/util.hh"
-
-namespace nix {
-
-DrvName::DrvName() { name = ""; }
-
-/* Parse a derivation name.  The `name' part of a derivation name is
-   everything up to but not including the first dash *not* followed by
-   a letter.  The `version' part is the rest (excluding the separating
-   dash).  E.g., `apache-httpd-2.0.48' is parsed to (`apache-httpd',
-   '2.0.48'). */
-DrvName::DrvName(const std::string& s) : hits(0) {
-  name = fullName = s;
-  for (unsigned int i = 0; i < s.size(); ++i) {
-    /* !!! isalpha/isdigit are affected by the locale. */
-    if (s[i] == '-' && i + 1 < s.size() && (isalpha(s[i + 1]) == 0)) {
-      name = std::string(s, 0, i);
-      version = std::string(s, i + 1);
-      break;
-    }
-  }
-}
-
-bool DrvName::matches(DrvName& n) {
-  if (name != "*") {
-    if (!regex) {
-      regex = std::make_unique<std::regex>(name, std::regex::extended);
-    }
-    if (!std::regex_match(n.name, *regex)) {
-      return false;
-    }
-  }
-  return !(!version.empty() && version != n.version);
-}
-
-std::string nextComponent(std::string::const_iterator& p,
-                          const std::string::const_iterator end) {
-  /* Skip any dots and dashes (component separators). */
-  while (p != end && (*p == '.' || *p == '-')) {
-    ++p;
-  }
-
-  if (p == end) {
-    return "";
-  }
-
-  /* If the first character is a digit, consume the longest sequence
-     of digits.  Otherwise, consume the longest sequence of
-     non-digit, non-separator characters. */
-  std::string s;
-  if (isdigit(*p) != 0) {
-    while (p != end && (isdigit(*p) != 0)) {
-      s += *p++;
-    }
-  } else {
-    while (p != end && ((isdigit(*p) == 0) && *p != '.' && *p != '-')) {
-      s += *p++;
-    }
-  }
-
-  return s;
-}
-
-static bool componentsLT(const std::string& c1, const std::string& c2) {
-  int n1;
-  int n2;
-  bool c1Num = absl::SimpleAtoi(c1, &n1);
-  bool c2Num = absl::SimpleAtoi(c2, &n2);
-
-  if (c1Num && c2Num) {
-    return n1 < n2;
-  }
-  if (c1.empty() && c2Num) {
-    return true;
-  } else if (c1 == "pre" && c2 != "pre") {
-    return true;
-  } else if (c2 == "pre") {
-    return false;
-    /* Assume that `2.3a' < `2.3.1'. */
-  } else if (c2Num) {
-    return true;
-  } else if (c1Num) {
-    return false;
-  } else {
-    return c1 < c2;
-  }
-}
-
-int compareVersions(const std::string& v1, const std::string& v2) {
-  std::string::const_iterator p1 = v1.begin();
-  std::string::const_iterator p2 = v2.begin();
-
-  while (p1 != v1.end() || p2 != v2.end()) {
-    std::string c1 = nextComponent(p1, v1.end());
-    std::string c2 = nextComponent(p2, v2.end());
-    if (componentsLT(c1, c2)) {
-      return -1;
-    }
-    if (componentsLT(c2, c1)) {
-      return 1;
-    }
-  }
-
-  return 0;
-}
-
-DrvNames drvNamesFromArgs(const Strings& opArgs) {
-  DrvNames result;
-  for (auto& i : opArgs) {
-    result.push_back(DrvName(i));
-  }
-  return result;
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/names.hh b/third_party/nix/src/libexpr/names.hh
deleted file mode 100644
index 061388d517..0000000000
--- a/third_party/nix/src/libexpr/names.hh
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#include <memory>
-#include <regex>
-
-#include "libutil/types.hh"
-
-namespace nix {
-
-struct DrvName {
-  std::string fullName;
-  std::string name;
-  std::string version;
-  unsigned int hits;
-
-  DrvName();
-  DrvName(const std::string& s);
-  bool matches(DrvName& n);
-
- private:
-  std::unique_ptr<std::regex> regex;
-};
-
-typedef std::list<DrvName> DrvNames;
-
-std::string nextComponent(std::string::const_iterator& p,
-                          const std::string::const_iterator end);
-int compareVersions(const std::string& v1, const std::string& v2);
-DrvNames drvNamesFromArgs(const Strings& opArgs);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/nix-expr.pc.in b/third_party/nix/src/libexpr/nix-expr.pc.in
deleted file mode 100644
index 99b0ae2c68..0000000000
--- a/third_party/nix/src/libexpr/nix-expr.pc.in
+++ /dev/null
@@ -1,10 +0,0 @@
-prefix=@CMAKE_INSTALL_PREFIX@
-libdir=@CMAKE_INSTALL_FULL_LIBDIR@
-includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
-
-Name: Nix
-Description: Nix Package Manager
-Version: @PACKAGE_VERSION@
-Requires: nix-store bdw-gc
-Libs: -L${libdir} -lnixexpr
-Cflags: -I${includedir}/nix
diff --git a/third_party/nix/src/libexpr/nixexpr.cc b/third_party/nix/src/libexpr/nixexpr.cc
deleted file mode 100644
index 24ce6ec3fc..0000000000
--- a/third_party/nix/src/libexpr/nixexpr.cc
+++ /dev/null
@@ -1,414 +0,0 @@
-#include "libexpr/nixexpr.hh"
-
-#include <cstdlib>
-#include <variant>
-
-#include "libstore/derivations.hh"
-#include "libutil/util.hh"
-#include "libutil/visitor.hh"
-
-namespace nix {
-
-/* Displaying abstract syntax trees. */
-
-std::ostream& operator<<(std::ostream& str, const Expr& e) {
-  e.show(str);
-  return str;
-}
-
-static void showString(std::ostream& str, const std::string& s) {
-  str << '"';
-  for (auto c : std::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 std::string& s) {
-  if (s.empty()) {
-    str << "\"\"";
-  } else if (s == "if") {  // FIXME: handle other keywords
-    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) const { abort(); }
-
-void ExprInt::show(std::ostream& str) const { str << n; }
-
-void ExprFloat::show(std::ostream& str) const { str << nf; }
-
-void ExprString::show(std::ostream& str) const { showString(str, s); }
-
-void ExprPath::show(std::ostream& str) const { str << s; }
-
-void ExprVar::show(std::ostream& str) const { str << name; }
-
-void ExprSelect::show(std::ostream& str) const {
-  str << "(" << *e << ")." << showAttrPath(attrPath);
-  if (def != nullptr) {
-    str << " or (" << *def << ")";
-  }
-}
-
-void ExprOpHasAttr::show(std::ostream& str) const {
-  str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")";
-}
-
-void ExprAttrs::show(std::ostream& str) const {
-  if (recursive) {
-    str << "rec ";
-  }
-  str << "{ ";
-  for (auto& i : attrs) {
-    if (i.second.inherited) {
-      str << "inherit " << i.first << " "
-          << "; ";
-    } else {
-      str << i.first << " = " << *i.second.e << "; ";
-    }
-  }
-  for (auto& i : dynamicAttrs) {
-    str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; ";
-  }
-  str << "}";
-}
-
-void ExprList::show(std::ostream& str) const {
-  str << "[ ";
-  for (auto& i : elems) {
-    str << "(" << *i << ") ";
-  }
-  str << "]";
-}
-
-void ExprLambda::show(std::ostream& str) const {
-  str << "(";
-  if (matchAttrs) {
-    str << "{ ";
-    bool first = true;
-    for (auto& i : formals->formals) {
-      if (first) {
-        first = false;
-      } else {
-        str << ", ";
-      }
-      str << i.name;
-      if (i.def != nullptr) {
-        str << " ? " << *i.def;
-      }
-    }
-    if (formals->ellipsis) {
-      if (!first) {
-        str << ", ";
-      }
-      str << "...";
-    }
-    str << " }";
-    if (!arg.empty()) {
-      str << " @ ";
-    }
-  }
-  if (!arg.empty()) {
-    str << arg;
-  }
-  str << ": " << *body << ")";
-}
-
-void ExprLet::show(std::ostream& str) const {
-  str << "(let ";
-  for (auto& i : attrs->attrs) {
-    if (i.second.inherited) {
-      str << "inherit " << i.first << "; ";
-    } else {
-      str << i.first << " = " << *i.second.e << "; ";
-    }
-  }
-  str << "in " << *body << ")";
-}
-
-void ExprWith::show(std::ostream& str) const {
-  str << "(with " << *attrs << "; " << *body << ")";
-}
-
-void ExprIf::show(std::ostream& str) const {
-  str << "(if " << *cond << " then " << *then << " else " << *else_ << ")";
-}
-
-void ExprAssert::show(std::ostream& str) const {
-  str << "assert " << *cond << "; " << *body;
-}
-
-void ExprOpNot::show(std::ostream& str) const { str << "(! " << *e << ")"; }
-
-void ExprConcatStrings::show(std::ostream& str) const {
-  bool first = true;
-  str << "(";
-  for (auto& i : *es) {
-    if (first) {
-      first = false;
-    } else {
-      str << " + ";
-    }
-    str << *i;
-  }
-  str << ")";
-}
-
-void ExprPos::show(std::ostream& str) const { str << "__curPos"; }
-
-std::ostream& operator<<(std::ostream& str, const Pos& pos) {
-  if (!pos || !pos.file.has_value()) {
-    str << "undefined position";
-  } else {
-    str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") %
-            std::string(pos.file.value()) % pos.line % pos.column)
-               .str();
-  }
-  return str;
-}
-
-std::string showAttrPath(const AttrPath& attrPath) {
-  std::ostringstream out;
-  bool first = true;
-  for (auto& attr : attrPath) {
-    if (!first) {
-      out << '.';
-    } else {
-      first = false;
-    }
-
-    std::visit(util::overloaded{
-                   [&](const Symbol& sym) { out << sym; },
-                   [&](const Expr* expr) { out << "\"${" << *expr << "}\""; }},
-               attr);
-  }
-  return out.str();
-}
-
-Pos noPos;
-
-/* Computing levels/displacements for variables. */
-
-void Expr::bindVars(const StaticEnv& env) { abort(); }
-
-void ExprInt::bindVars(const StaticEnv& env) {}
-
-void ExprFloat::bindVars(const StaticEnv& env) {}
-
-void ExprString::bindVars(const StaticEnv& env) {}
-
-void ExprPath::bindVars(const StaticEnv& env) {}
-
-void ExprVar::bindVars(const StaticEnv& env) {
-  /* Check whether the variable appears in the environment.  If so,
-     set its level and displacement. */
-  const StaticEnv* curEnv;
-  unsigned int level;
-  int withLevel = -1;
-  for (curEnv = &env, level = 0; curEnv != nullptr;
-       curEnv = curEnv->up, level++) {
-    if (curEnv->isWith) {
-      if (withLevel == -1) {
-        withLevel = level;
-      }
-    } else {
-      auto i = curEnv->vars.find(name);
-      if (i != curEnv->vars.end()) {
-        fromWith = false;
-        this->level = level;
-        displ = i->second;
-        return;
-      }
-    }
-  }
-
-  /* Otherwise, the variable must be obtained from the nearest
-     enclosing `with'.  If there is no `with', then we can issue an
-     "undefined variable" error now. */
-  if (withLevel == -1) {
-    throw UndefinedVarError(format("undefined variable '%1%' at %2%") % name %
-                            pos);
-  }
-
-  fromWith = true;
-  this->level = withLevel;
-}
-
-void ExprSelect::bindVars(const StaticEnv& env) {
-  e->bindVars(env);
-  if (def != nullptr) {
-    def->bindVars(env);
-  }
-  for (auto& i : attrPath) {
-    if (auto* expr = std::get_if<Expr*>(&i)) {
-      (*expr)->bindVars(env);
-    }
-  }
-}
-
-void ExprOpHasAttr::bindVars(const StaticEnv& env) {
-  e->bindVars(env);
-  for (auto& i : attrPath) {
-    if (auto* expr = std::get_if<Expr*>(&i)) {
-      (*expr)->bindVars(env);
-    }
-  }
-}
-
-void ExprAttrs::bindVars(const StaticEnv& env) {
-  const StaticEnv* dynamicEnv = &env;
-  StaticEnv newEnv(/* isWith = */ false, &env);
-
-  if (recursive) {
-    dynamicEnv = &newEnv;
-
-    unsigned int displ = 0;
-    for (auto& i : attrs) {
-      newEnv.vars[i.first] = i.second.displ = displ++;
-    }
-
-    for (auto& i : attrs) {
-      i.second.e->bindVars(i.second.inherited ? env : newEnv);
-    }
-  }
-
-  else {
-    for (auto& i : attrs) {
-      i.second.e->bindVars(env);
-    }
-  }
-
-  for (auto& i : dynamicAttrs) {
-    i.nameExpr->bindVars(*dynamicEnv);
-    i.valueExpr->bindVars(*dynamicEnv);
-  }
-}
-
-void ExprList::bindVars(const StaticEnv& env) {
-  for (auto& i : elems) {
-    i->bindVars(env);
-  }
-}
-
-void ExprLambda::bindVars(const StaticEnv& env) {
-  StaticEnv newEnv(false, &env);
-
-  unsigned int displ = 0;
-
-  if (!arg.empty()) {
-    newEnv.vars[arg] = displ++;
-  }
-
-  if (matchAttrs) {
-    for (auto& i : formals->formals) {
-      newEnv.vars[i.name] = displ++;
-    }
-
-    for (auto& i : formals->formals) {
-      if (i.def != nullptr) {
-        i.def->bindVars(newEnv);
-      }
-    }
-  }
-
-  body->bindVars(newEnv);
-}
-
-void ExprLet::bindVars(const StaticEnv& env) {
-  StaticEnv newEnv(false, &env);
-
-  unsigned int displ = 0;
-  for (auto& i : attrs->attrs) {
-    newEnv.vars[i.first] = i.second.displ = displ++;
-  }
-
-  for (auto& i : attrs->attrs) {
-    i.second.e->bindVars(i.second.inherited ? env : newEnv);
-  }
-
-  body->bindVars(newEnv);
-}
-
-void ExprWith::bindVars(const StaticEnv& env) {
-  /* Does this `with' have an enclosing `with'?  If so, record its
-     level so that `lookupVar' can look up variables in the previous
-     `with' if this one doesn't contain the desired attribute. */
-  const StaticEnv* curEnv;
-  unsigned int level;
-  prevWith = 0;
-  for (curEnv = &env, level = 1; curEnv != nullptr;
-       curEnv = curEnv->up, level++) {
-    if (curEnv->isWith) {
-      prevWith = level;
-      break;
-    }
-  }
-
-  attrs->bindVars(env);
-  StaticEnv newEnv(true, &env);
-  body->bindVars(newEnv);
-}
-
-void ExprIf::bindVars(const StaticEnv& env) {
-  cond->bindVars(env);
-  then->bindVars(env);
-  else_->bindVars(env);
-}
-
-void ExprAssert::bindVars(const StaticEnv& env) {
-  cond->bindVars(env);
-  body->bindVars(env);
-}
-
-void ExprOpNot::bindVars(const StaticEnv& env) { e->bindVars(env); }
-
-void ExprConcatStrings::bindVars(const StaticEnv& env) {
-  for (auto& i : *es) {
-    i->bindVars(env);
-  }
-}
-
-void ExprPos::bindVars(const StaticEnv& env) {}
-
-/* Storing function names. */
-void ExprLambda::setName(Symbol& name) { this->name = name; }
-
-std::string ExprLambda::showNamePos() const {
-  return (format("%1% at %2%") %
-          (name.has_value() ? "'" + std::string(name.value()) + "'"
-                            : "anonymous function") %
-          pos)
-      .str();
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/nixexpr.hh b/third_party/nix/src/libexpr/nixexpr.hh
deleted file mode 100644
index 22b5f871f0..0000000000
--- a/third_party/nix/src/libexpr/nixexpr.hh
+++ /dev/null
@@ -1,353 +0,0 @@
-#pragma once
-
-#include <map>
-#include <optional>
-#include <variant>
-
-#include <absl/container/flat_hash_map.h>
-
-#include "libexpr/symbol-table.hh"
-#include "libexpr/value.hh"
-#include "libutil/types.hh"  // TODO(tazjin): audit this include
-
-namespace nix {
-
-MakeError(EvalError, Error);
-MakeError(ParseError, Error);
-MakeError(AssertionError, EvalError);
-MakeError(ThrownError, AssertionError);
-MakeError(Abort, EvalError);
-MakeError(TypeError, EvalError);
-MakeError(UndefinedVarError, Error);
-MakeError(RestrictedPathError, Error);
-
-/* Position objects. */
-
-struct Pos {
-  std::optional<Symbol> file;
-  unsigned int line, column;
-  Pos(const std::optional<Symbol>& file, unsigned int line, unsigned int column)
-      : file(file), line(line), column(column){};
-
-  // TODO(tazjin): remove this - empty pos is never useful
-  Pos() : file(std::nullopt), line(0), column(0){};
-
-  operator bool() const { return line != 0; }
-
-  bool operator<(const Pos& p2) const {
-    if (!file.has_value()) {
-      return true;
-    }
-
-    if (!line) {
-      return p2.line;
-    }
-    if (!p2.line) {
-      return false;
-    }
-    int d = ((std::string)file.value()).compare((std::string)p2.file.value());
-    if (d < 0) {
-      return true;
-    }
-    if (d > 0) {
-      return false;
-    }
-    if (line < p2.line) {
-      return true;
-    }
-    if (line > p2.line) {
-      return false;
-    }
-    return column < p2.column;
-  }
-};
-
-extern Pos noPos;
-
-std::ostream& operator<<(std::ostream& str, const Pos& pos);
-
-struct Env;
-struct Value;
-class EvalState;
-struct StaticEnv;
-
-/* An attribute path is a sequence of attribute names. */
-using AttrName = std::variant<Symbol, Expr*>;
-using AttrPath = std::vector<AttrName>;
-using AttrNameVector = std::vector<AttrName>;
-
-using VectorExprs = std::vector<nix::Expr*>;
-
-std::string showAttrPath(const AttrPath& attrPath);
-
-/* Abstract syntax of Nix expressions. */
-
-struct Expr {
-  virtual ~Expr(){};
-  virtual void show(std::ostream& str) const;
-  virtual void bindVars(const StaticEnv& env);
-  virtual void eval(EvalState& state, Env& env, Value& v);
-  virtual Value* maybeThunk(EvalState& state, Env& env);
-};
-
-std::ostream& operator<<(std::ostream& str, const Expr& e);
-
-#define COMMON_METHODS                             \
-  void show(std::ostream& str) const;              \
-  void eval(EvalState& state, Env& env, Value& v); \
-  void bindVars(const StaticEnv& env);
-
-struct ExprInt : Expr {
-  NixInt n;
-  Value v;
-  ExprInt(NixInt n) : n(n) { mkInt(v, n); };
-  COMMON_METHODS
-  Value* maybeThunk(EvalState& state, Env& env);
-};
-
-struct ExprFloat : Expr {
-  NixFloat nf;
-  Value v;
-  ExprFloat(NixFloat nf) : nf(nf) { mkFloat(v, nf); };
-  COMMON_METHODS
-  Value* maybeThunk(EvalState& state, Env& env);
-};
-
-struct ExprString : Expr {
-  Symbol s;
-  Value v;
-  ExprString(const Symbol& s) : s(s) { mkString(v, s); };
-  COMMON_METHODS
-  Value* maybeThunk(EvalState& state, Env& env);
-};
-
-/* Temporary class used during parsing of indented strings. */
-struct ExprIndStr : Expr {
-  std::string s;
-  ExprIndStr(const std::string& s) : s(s){};
-};
-
-struct ExprPath : Expr {
-  std::string s;
-  Value v;
-  ExprPath(const std::string& s) : s(s) { mkPathNoCopy(v, this->s.c_str()); };
-  COMMON_METHODS
-  Value* maybeThunk(EvalState& state, Env& env);
-};
-
-struct ExprVar : Expr {
-  Pos pos;
-  Symbol name;
-
-  /* Whether the variable comes from an environment (e.g. a rec, let
-     or function argument) or from a "with". */
-  bool fromWith;
-
-  /* In the former case, the value is obtained by going `level'
-     levels up from the current environment and getting the
-     `displ'th value in that environment.  In the latter case, the
-     value is obtained by getting the attribute named `name' from
-     the set stored in the environment that is `level' levels up
-     from the current one.*/
-  unsigned int level;
-  unsigned int displ;
-
-  ExprVar(const Symbol& name) : name(name){};
-  ExprVar(const Pos& pos, const Symbol& name) : pos(pos), name(name){};
-  COMMON_METHODS
-  Value* maybeThunk(EvalState& state, Env& env);
-};
-
-// [tazjin] I *think* that this struct describes the syntactic
-// construct for "selecting" something out of an attribute set, e.g.
-// `a.b.c` => ExprSelect{"b", "c"}.
-//
-// Each path element has got a pointer to an expression, which seems
-// to be the thing preceding its period, but afaict that is only set
-// for the first one in a path.
-struct ExprSelect : Expr {
-  Pos pos;
-  Expr *e, *def;
-  AttrPath attrPath;
-  ExprSelect(const Pos& pos, Expr* e, const AttrPath& attrPath, Expr* def)
-      : pos(pos), e(e), def(def), attrPath(attrPath){};
-  ExprSelect(const Pos& pos, Expr* e, const Symbol& name)
-      : pos(pos), e(e), def(0) {
-    attrPath.push_back(AttrName(name));
-  };
-  COMMON_METHODS
-};
-
-struct ExprOpHasAttr : Expr {
-  Expr* e;
-  AttrPath attrPath;
-  ExprOpHasAttr(Expr* e, const AttrPath& attrPath) : e(e), attrPath(attrPath){};
-  COMMON_METHODS
-};
-
-struct ExprAttrs : Expr {
-  bool recursive;
-
-  struct AttrDef {
-    bool inherited;
-    Expr* e;
-    Pos pos;
-    unsigned int displ;  // displacement
-    AttrDef(Expr* e, const Pos& pos, bool inherited = false)
-        : inherited(inherited), e(e), pos(pos), displ(0){};
-    AttrDef(){};
-  };
-
-  using AttrDefs = absl::flat_hash_map<Symbol, AttrDef>;
-  AttrDefs attrs;
-
-  struct DynamicAttrDef {
-    Expr *nameExpr, *valueExpr;
-    Pos pos;
-    DynamicAttrDef(Expr* nameExpr, Expr* valueExpr, const Pos& pos)
-        : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos){};
-  };
-
-  using DynamicAttrDefs = std::vector<DynamicAttrDef>;
-  DynamicAttrDefs dynamicAttrs;
-
-  ExprAttrs() : recursive(false){};
-  COMMON_METHODS
-};
-
-struct ExprList : Expr {
-  VectorExprs elems;
-  ExprList(){};
-  COMMON_METHODS
-};
-
-struct Formal {
-  Symbol name;
-  Expr* def;  // def = default, not definition
-  Formal(const Symbol& name, Expr* def) : name(name), def(def){};
-};
-
-// Describes structured function arguments (e.g. `{ a }: ...`)
-struct Formals {
-  using Formals_ = std::list<Formal>;
-  Formals_ formals;
-  std::set<Symbol> argNames;  // used during parsing
-  bool ellipsis;
-};
-
-struct ExprLambda : Expr {
- public:
-  Pos pos;
-  std::optional<Symbol> name;
-  Symbol arg;
-  bool matchAttrs;
-  Formals* formals;
-  Expr* body;
-  ExprLambda(const Pos& pos, const Symbol& arg, bool matchAttrs,
-             Formals* formals, Expr* body)
-      : pos(pos),
-        arg(arg),
-        matchAttrs(matchAttrs),
-        formals(formals),
-        body(body) {
-    if (!arg.empty() && formals &&
-        formals->argNames.find(arg) != formals->argNames.end()) {
-      throw ParseError(
-          format("duplicate formal function argument '%1%' at %2%") % arg %
-          pos);
-    }
-  };
-  void setName(Symbol& name);
-  std::string showNamePos() const;
-  COMMON_METHODS
-};
-
-struct ExprLet : Expr {
-  ExprAttrs* attrs;
-  Expr* body;
-  ExprLet(ExprAttrs* attrs, Expr* body) : attrs(attrs), body(body){};
-  COMMON_METHODS
-};
-
-struct ExprWith : Expr {
-  Pos pos;
-  Expr *attrs, *body;
-  size_t prevWith;
-  ExprWith(const Pos& pos, Expr* attrs, Expr* body)
-      : pos(pos), attrs(attrs), body(body){};
-  COMMON_METHODS
-};
-
-struct ExprIf : Expr {
-  Expr *cond, *then, *else_;
-  ExprIf(Expr* cond, Expr* then, Expr* else_)
-      : cond(cond), then(then), else_(else_){};
-  COMMON_METHODS
-};
-
-struct ExprAssert : Expr {
-  Pos pos;
-  Expr *cond, *body;
-  ExprAssert(const Pos& pos, Expr* cond, Expr* body)
-      : pos(pos), cond(cond), body(body){};
-  COMMON_METHODS
-};
-
-struct ExprOpNot : Expr {
-  Expr* e;
-  ExprOpNot(Expr* e) : e(e){};
-  COMMON_METHODS
-};
-
-#define MakeBinOp(name, s)                                                 \
-  struct name : Expr {                                                     \
-    Pos pos;                                                               \
-    Expr *e1, *e2;                                                         \
-    name(Expr* e1, Expr* e2) : e1(e1), e2(e2){};                           \
-    name(const Pos& pos, Expr* e1, Expr* e2) : pos(pos), e1(e1), e2(e2){}; \
-    void show(std::ostream& str) const {                                   \
-      str << "(" << *e1 << " " s " " << *e2 << ")";                        \
-    }                                                                      \
-    void bindVars(const StaticEnv& env) {                                  \
-      e1->bindVars(env);                                                   \
-      e2->bindVars(env);                                                   \
-    }                                                                      \
-    void eval(EvalState& state, Env& env, Value& v);                       \
-  };
-
-MakeBinOp(ExprApp, "");
-MakeBinOp(ExprOpEq, "==");
-MakeBinOp(ExprOpNEq, "!=");
-MakeBinOp(ExprOpAnd, "&&");
-MakeBinOp(ExprOpOr, "||");
-MakeBinOp(ExprOpImpl, "->");
-MakeBinOp(ExprOpUpdate, "//");
-MakeBinOp(ExprOpConcatLists, "++");
-
-struct ExprConcatStrings : Expr {
-  Pos pos;
-  bool forceString;
-  nix::VectorExprs* es;
-  ExprConcatStrings(const Pos& pos, bool forceString, nix::VectorExprs* es)
-      : pos(pos), forceString(forceString), es(es){};
-  COMMON_METHODS
-};
-
-struct ExprPos : Expr {
-  Pos pos;
-  ExprPos(const Pos& pos) : pos(pos){};
-  COMMON_METHODS
-};
-
-/* Static environments are used to map variable names onto (level,
-   displacement) pairs used to obtain the value of the variable at
-   runtime. */
-struct StaticEnv {
-  bool isWith;
-  const StaticEnv* up;
-  typedef absl::flat_hash_map<Symbol, unsigned int> Vars;
-  Vars vars;
-  StaticEnv(bool isWith, const StaticEnv* up) : isWith(isWith), up(up){};
-};
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/parser.cc b/third_party/nix/src/libexpr/parser.cc
deleted file mode 100644
index 756fdfa128..0000000000
--- a/third_party/nix/src/libexpr/parser.cc
+++ /dev/null
@@ -1,332 +0,0 @@
-#include "libexpr/parser.hh"
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "libexpr/eval.hh"
-#include "libstore/download.hh"
-#include "libstore/store-api.hh"
-
-namespace nix {
-
-void addAttr(ExprAttrs* attrs, AttrPath& attrPath, Expr* e, const Pos& pos) {
-  AttrPath::iterator i;
-  // All attrpaths have at least one attr
-  assert(!attrPath.empty());
-  // Checking attrPath validity.
-  // ===========================
-  for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
-    if (const auto* sym = std::get_if<Symbol>(&(*i)); sym && sym->set()) {
-      ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(*sym);
-      if (j != attrs->attrs.end()) {
-        if (!j->second.inherited) {
-          ExprAttrs* attrs2 = dynamic_cast<ExprAttrs*>(j->second.e);
-          if (!attrs2) {
-            dupAttr(attrPath, pos, j->second.pos);
-          }
-          attrs = attrs2;
-        } else {
-          dupAttr(attrPath, pos, j->second.pos);
-        }
-      } else {
-        ExprAttrs* nested = new ExprAttrs;
-        attrs->attrs[*sym] = ExprAttrs::AttrDef(nested, pos);
-        attrs = nested;
-      }
-    } else {
-      // Yes, this code does not handle all conditions
-      // exhaustively. We use std::get to throw if the condition
-      // that isn't covered happens, which is potentially a
-      // behaviour change from the previous default constructed
-      // Symbol. It should alert us about anything untoward going
-      // on here.
-      auto* expr = std::get<Expr*>(*i);
-
-      ExprAttrs* nested = new ExprAttrs;
-      attrs->dynamicAttrs.push_back(
-          ExprAttrs::DynamicAttrDef(expr, nested, pos));
-      attrs = nested;
-    }
-  }
-  // Expr insertion.
-  // ==========================
-  if (auto* sym = std::get_if<Symbol>(&(*i)); sym && sym->set()) {
-    ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(*sym);
-    if (j != attrs->attrs.end()) {
-      // This attr path is already defined. However, if both
-      // e and the expr pointed by the attr path are two attribute sets,
-      // we want to merge them.
-      // Otherwise, throw an error.
-      auto ae = dynamic_cast<ExprAttrs*>(e);
-      auto jAttrs = dynamic_cast<ExprAttrs*>(j->second.e);
-      if (jAttrs && ae) {
-        for (auto& ad : ae->attrs) {
-          auto j2 = jAttrs->attrs.find(ad.first);
-          if (j2 !=
-              jAttrs->attrs.end()) {  // Attr already defined in iAttrs, error.
-            dupAttr(ad.first, j2->second.pos, ad.second.pos);
-          }
-          jAttrs->attrs[ad.first] = ad.second;
-        }
-      } else {
-        dupAttr(attrPath, pos, j->second.pos);
-      }
-    } else {
-      // This attr path is not defined. Let's create it.
-      attrs->attrs[*sym] = ExprAttrs::AttrDef(e, pos);
-    }
-  } else {
-    // Same caveat as the identical line above.
-    auto* expr = std::get<Expr*>(*i);
-    attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(expr, e, pos));
-  }
-}
-
-void addFormal(const Pos& pos, Formals* formals, const Formal& formal) {
-  if (formals->argNames.find(formal.name) != formals->argNames.end()) {
-    throw ParseError(format("duplicate formal function argument '%1%' at %2%") %
-                     formal.name % pos);
-  }
-  formals->formals.push_front(formal);
-  formals->argNames.insert(formal.name);
-}
-
-Expr* stripIndentation(const Pos& pos, SymbolTable& symbols, VectorExprs& es) {
-  if (es.empty()) {
-    return new ExprString(symbols.Create(""));
-  }
-
-  /* Figure out the minimum indentation.  Note that by design
-     whitespace-only final lines are not taken into account.  (So
-     the " " in "\n ''" is ignored, but the " " in "\n foo''" is.) */
-  bool atStartOfLine = true; /* = seen only whitespace in the current line */
-  size_t minIndent = 1000000;
-  size_t curIndent = 0;
-  for (auto& i : es) {
-    ExprIndStr* e = dynamic_cast<ExprIndStr*>(i);
-    if (!e) {
-      /* Anti-quotations end the current start-of-line whitespace. */
-      if (atStartOfLine) {
-        atStartOfLine = false;
-        if (curIndent < minIndent) {
-          minIndent = curIndent;
-        }
-      }
-      continue;
-    }
-    for (size_t j = 0; j < e->s.size(); ++j) {
-      if (atStartOfLine) {
-        if (e->s[j] == ' ') {
-          curIndent++;
-        } else if (e->s[j] == '\n') {
-          /* Empty line, doesn't influence minimum
-             indentation. */
-          curIndent = 0;
-        } else {
-          atStartOfLine = false;
-          if (curIndent < minIndent) {
-            minIndent = curIndent;
-          }
-        }
-      } else if (e->s[j] == '\n') {
-        atStartOfLine = true;
-        curIndent = 0;
-      }
-    }
-  }
-
-  /* Strip spaces from each line. */
-  VectorExprs* es2 = new VectorExprs;
-  atStartOfLine = true;
-  size_t curDropped = 0;
-  size_t n = es.size();
-  for (VectorExprs::iterator i = es.begin(); i != es.end(); ++i, --n) {
-    ExprIndStr* e = dynamic_cast<ExprIndStr*>(*i);
-    if (!e) {
-      atStartOfLine = false;
-      curDropped = 0;
-      es2->push_back(*i);
-      continue;
-    }
-
-    std::string s2;
-    for (size_t j = 0; j < e->s.size(); ++j) {
-      if (atStartOfLine) {
-        if (e->s[j] == ' ') {
-          if (curDropped++ >= minIndent) {
-            s2 += e->s[j];
-          }
-        } else if (e->s[j] == '\n') {
-          curDropped = 0;
-          s2 += e->s[j];
-        } else {
-          atStartOfLine = false;
-          curDropped = 0;
-          s2 += e->s[j];
-        }
-      } else {
-        s2 += e->s[j];
-        if (e->s[j] == '\n') {
-          atStartOfLine = true;
-        }
-      }
-    }
-
-    /* Remove the last line if it is empty and consists only of
-       spaces. */
-    if (n == 1) {
-      std::string::size_type p = s2.find_last_of('\n');
-      if (p != std::string::npos &&
-          s2.find_first_not_of(' ', p + 1) == std::string::npos) {
-        s2 = std::string(s2, 0, p + 1);
-      }
-    }
-
-    es2->push_back(new ExprString(symbols.Create(s2)));
-  }
-
-  /* If this is a single string, then don't do a concatenation. */
-  return es2->size() == 1 && dynamic_cast<ExprString*>((*es2)[0])
-             ? (*es2)[0]
-             : new ExprConcatStrings(pos, true, es2);
-}
-
-Path resolveExprPath(Path path) {
-  assert(path[0] == '/');
-
-  /* If `path' is a symlink, follow it.  This is so that relative
-     path references work. */
-  struct stat st;
-  while (true) {
-    if (lstat(path.c_str(), &st)) {
-      throw SysError(format("getting status of '%1%'") % path);
-    }
-    if (!S_ISLNK(st.st_mode)) {
-      break;
-    }
-    path = absPath(readLink(path), dirOf(path));
-  }
-
-  /* If `path' refers to a directory, append `/default.nix'. */
-  if (S_ISDIR(st.st_mode)) {
-    path = canonPath(path + "/default.nix");
-  }
-
-  return path;
-}
-
-// These methods are actually declared in eval.hh, and were - for some
-// reason - previously implemented in parser.y.
-
-Expr* EvalState::parseExprFromFile(const Path& path) {
-  return parseExprFromFile(path, staticBaseEnv);
-}
-
-Expr* EvalState::parseExprFromFile(const Path& path, StaticEnv& staticEnv) {
-  return parse(readFile(path).c_str(), path, dirOf(path), staticEnv);
-}
-
-Expr* EvalState::parseExprFromString(const std::string& s, const Path& basePath,
-                                     StaticEnv& staticEnv) {
-  return parse(s.c_str(), "(std::string)", basePath, staticEnv);
-}
-
-Expr* EvalState::parseExprFromString(const std::string& s,
-                                     const Path& basePath) {
-  return parseExprFromString(s, basePath, staticBaseEnv);
-}
-
-Expr* EvalState::parseStdin() {
-  // Activity act(*logger, lvlTalkative, format("parsing standard input"));
-  return parseExprFromString(drainFD(0), absPath("."));
-}
-
-void EvalState::addToSearchPath(const std::string& s) {
-  size_t pos = s.find('=');
-  std::string prefix;
-  Path path;
-  if (pos == std::string::npos) {
-    path = s;
-  } else {
-    prefix = std::string(s, 0, pos);
-    path = std::string(s, pos + 1);
-  }
-
-  searchPath.emplace_back(prefix, path);
-}
-
-Path EvalState::findFile(const std::string& path) {
-  return findFile(searchPath, path);
-}
-
-Path EvalState::findFile(SearchPath& searchPath, const std::string& path,
-                         const Pos& pos) {
-  for (auto& i : searchPath) {
-    std::string suffix;
-    if (i.first.empty()) {
-      suffix = "/" + path;
-    } else {
-      auto s = i.first.size();
-      if (path.compare(0, s, i.first) != 0 ||
-          (path.size() > s && path[s] != '/')) {
-        continue;
-      }
-      suffix = path.size() == s ? "" : "/" + std::string(path, s);
-    }
-    auto r = resolveSearchPathElem(i);
-    if (!r.first) {
-      continue;
-    }
-    Path res = r.second + suffix;
-    if (pathExists(res)) {
-      return canonPath(res);
-    }
-  }
-  format f = format(
-      "file '%1%' was not found in the Nix search path (add it using $NIX_PATH "
-      "or -I)" +
-      std::string(pos ? ", at %2%" : ""));
-  f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
-  throw ThrownError(f % path % pos);
-}
-
-std::pair<bool, std::string> EvalState::resolveSearchPathElem(
-    const SearchPathElem& elem) {
-  auto i = searchPathResolved.find(elem.second);
-  if (i != searchPathResolved.end()) {
-    return i->second;
-  }
-
-  std::pair<bool, std::string> res;
-
-  if (isUri(elem.second)) {
-    try {
-      CachedDownloadRequest request(elem.second);
-      request.unpack = true;
-      res = {true, getDownloader()->downloadCached(store, request).path};
-    } catch (DownloadError& e) {
-      LOG(WARNING) << "Nix search path entry '" << elem.second
-                   << "' cannot be downloaded, ignoring";
-      res = {false, ""};
-    }
-  } else {
-    auto path = absPath(elem.second);
-    if (pathExists(path)) {
-      res = {true, path};
-    } else {
-      LOG(WARNING) << "Nix search path entry '" << elem.second
-                   << "' does not exist, ignoring";
-      res = {false, ""};
-    }
-  }
-
-  DLOG(INFO) << "resolved search path element '" << elem.second << "' to '"
-             << res.second << "'";
-
-  searchPathResolved[elem.second] = res;
-  return res;
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/parser.hh b/third_party/nix/src/libexpr/parser.hh
deleted file mode 100644
index 70b5450b5a..0000000000
--- a/third_party/nix/src/libexpr/parser.hh
+++ /dev/null
@@ -1,100 +0,0 @@
-// Parser utilities for use in parser.y
-#pragma once
-
-// TODO(tazjin): Audit these includes, they were in parser.y
-#include <optional>
-#include <variant>
-
-#include <glog/logging.h>
-
-#include "libexpr/eval.hh"
-#include "libexpr/nixexpr.hh"
-#include "libutil/util.hh"
-
-#define YY_DECL                                                               \
-  int yylex(YYSTYPE* yylval_param, YYLTYPE* yylloc_param, yyscan_t yyscanner, \
-            nix::ParseData* data)
-
-#define CUR_POS makeCurPos(*yylocp, data)
-
-namespace nix {
-
-struct ParseData {
-  EvalState& state;
-  SymbolTable& symbols;
-  Expr* result;
-  Path basePath;
-  std::optional<Symbol> path;
-  std::string error;
-  Symbol sLetBody;
-
-  ParseData(EvalState& state)
-      : state(state),
-        symbols(state.symbols),
-        sLetBody(symbols.Create("<let-body>")){};
-};
-
-// Clang fails to identify these functions as used, probably because
-// of some interaction between the lexer/parser codegen and something
-// else.
-//
-// To avoid warnings for that we disable -Wunused-function in this block.
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-function"
-
-// TODO(tazjin): move dupAttr to anonymous namespace
-static void dupAttr(const AttrPath& attrPath, const Pos& pos,
-                    const Pos& prevPos) {
-  throw ParseError(format("attribute '%1%' at %2% already defined at %3%") %
-                   showAttrPath(attrPath) % pos % prevPos);
-}
-
-static void dupAttr(Symbol attr, const Pos& pos, const Pos& prevPos) {
-  throw ParseError(format("attribute '%1%' at %2% already defined at %3%") %
-                   attr % pos % prevPos);
-}
-
-void addAttr(ExprAttrs* attrs, AttrPath& attrPath, Expr* e, const Pos& pos);
-
-void addFormal(const Pos& pos, Formals* formals, const Formal& formal);
-
-Expr* stripIndentation(const Pos& pos, SymbolTable& symbols, VectorExprs& es);
-
-Path resolveExprPath(Path path);
-
-// implementations originally from lexer.l
-
-static Expr* unescapeStr(SymbolTable& symbols, const char* s, size_t length) {
-  std::string t;
-  t.reserve(length);
-  char c;
-  while ((c = *s++)) {
-    if (c == '\\') {
-      assert(*s);
-      c = *s++;
-      if (c == 'n') {
-        t += '\n';
-      } else if (c == 'r') {
-        t += '\r';
-      } else if (c == 't') {
-        t += '\t';
-      } else {
-        t += c;
-      }
-    } else if (c == '\r') {
-      /* Normalise CR and CR/LF into LF. */
-      t += '\n';
-      if (*s == '\n') {
-        s++;
-      } /* cr/lf */
-    } else {
-      t += c;
-    }
-  }
-  return new ExprString(symbols.Create(t));
-}
-
-#pragma clang diagnostic pop  // re-enable -Wunused-function
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/parser.y b/third_party/nix/src/libexpr/parser.y
deleted file mode 100644
index ba6490e60a..0000000000
--- a/third_party/nix/src/libexpr/parser.y
+++ /dev/null
@@ -1,359 +0,0 @@
-%glr-parser
-%locations
-%define parse.error verbose
-%define api.pure true
-%defines
-/* %no-lines */
-%parse-param { void * scanner }
-%parse-param { nix::ParseData * data }
-%lex-param { void * scanner }
-%lex-param { nix::ParseData * data }
-%expect 1
-%expect-rr 1
-
-%code requires {
-#define YY_NO_INPUT 1 // disable unused yyinput features
-#include "libexpr/parser.hh"
-
-struct YYSTYPE {
-  union {
-    nix::Expr * e;
-    nix::ExprList * list;
-    nix::ExprAttrs * attrs;
-    nix::Formals * formals;
-    nix::Formal * formal;
-    nix::NixInt n;
-    nix::NixFloat nf;
-    const char * id; // !!! -> Symbol
-    char * path;
-    char * uri;
-    nix::AttrNameVector * attrNames;
-    nix::VectorExprs * string_parts;
-  };
-};
-
-}
-
-%{
-
-#include "generated/parser-tab.hh"
-#include "generated/lexer-tab.hh"
-
-YY_DECL;
-
-using namespace nix;
-
-namespace nix {
-
-static inline Pos makeCurPos(const YYLTYPE& loc, ParseData* data) {
-  return Pos(data->path, loc.first_line, loc.first_column);
-}
-
-void yyerror(YYLTYPE* loc, yyscan_t scanner, ParseData* data,
-             const char* error) {
-  data->error = (format("%1%, at %2%") % error % makeCurPos(*loc, data)).str();
-}
-
-}
-
-%}
-
-%type <e> start expr expr_function expr_if expr_op
-%type <e> expr_app expr_select expr_simple
-%type <list> expr_list
-%type <attrs> binds
-%type <formals> formals
-%type <formal> formal
-%type <attrNames> attrs attrpath
-%type <string_parts> string_parts_interpolated ind_string_parts
-%type <e> string_parts string_attr
-%type <id> attr
-%token <id> ID ATTRPATH
-%token <e> STR IND_STR
-%token <n> INT
-%token <nf> FLOAT
-%token <path> PATH HPATH SPATH
-%token <uri> URI
-%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
-%token DOLLAR_CURLY /* == ${ */
-%token IND_STRING_OPEN IND_STRING_CLOSE
-%token ELLIPSIS
-
-%right IMPL
-%left OR
-%left AND
-%nonassoc EQ NEQ
-%nonassoc '<' '>' LEQ GEQ
-%right UPDATE
-%left NOT
-%left '+' '-'
-%left '*' '/'
-%right CONCAT
-%nonassoc '?'
-%nonassoc NEGATE
-
-%%
-
-start: expr { data->result = $1; };
-
-expr: expr_function;
-
-expr_function
-  : ID ':' expr_function
-    { $$ = new ExprLambda(CUR_POS, data->symbols.Create($1), false, 0, $3); }
-  | '{' formals '}' ':' expr_function
-    { $$ = new ExprLambda(CUR_POS, data->symbols.Create(""), true, $2, $5); }
-  | '{' formals '}' '@' ID ':' expr_function
-    { $$ = new ExprLambda(CUR_POS, data->symbols.Create($5), true, $2, $7); }
-  | ID '@' '{' formals '}' ':' expr_function
-    { $$ = new ExprLambda(CUR_POS, data->symbols.Create($1), true, $4, $7); }
-  | ASSERT expr ';' expr_function
-    { $$ = new ExprAssert(CUR_POS, $2, $4); }
-  | WITH expr ';' expr_function
-    { $$ = new ExprWith(CUR_POS, $2, $4); }
-  | LET binds IN expr_function
-    { if (!$2->dynamicAttrs.empty())
-        throw ParseError(format("dynamic attributes not allowed in let at %1%")
-            % CUR_POS);
-      $$ = new ExprLet($2, $4);
-    }
-  | expr_if
-  ;
-
-expr_if
-  : IF expr THEN expr ELSE expr { $$ = new ExprIf($2, $4, $6); }
-  | expr_op
-  ;
-
-expr_op
-  : '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
-  | '-' expr_op %prec NEGATE { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.Create("__sub")), new ExprInt(0)), $2); }
-  | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
-  | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
-  | expr_op '<' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.Create("__lessThan")), $1), $3); }
-  | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.Create("__lessThan")), $3), $1)); }
-  | expr_op '>' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.Create("__lessThan")), $3), $1); }
-  | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.Create("__lessThan")), $1), $3)); }
-  | expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); }
-  | expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); }
-  | expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); }
-  | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); }
-  | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
-  | expr_op '+' expr_op
-    { $$ = new ExprConcatStrings(CUR_POS, false, new nix::VectorExprs({$1, $3})); }
-  | expr_op '-' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.Create("__sub")), $1), $3); }
-  | expr_op '*' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.Create("__mul")), $1), $3); }
-  | expr_op '/' expr_op { $$ = new ExprApp(CUR_POS, new ExprApp(new ExprVar(data->symbols.Create("__div")), $1), $3); }
-  | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); }
-  | expr_app
-  ;
-
-expr_app
-  : expr_app expr_select
-    { $$ = new ExprApp(CUR_POS, $1, $2); }
-  | expr_select { $$ = $1; }
-  ;
-
-expr_select
-  : expr_simple '.' attrpath
-    { $$ = new ExprSelect(CUR_POS, $1, *$3, 0); }
-  | expr_simple '.' attrpath OR_KW expr_select
-    { $$ = new ExprSelect(CUR_POS, $1, *$3, $5); }
-  | /* Backwards compatibility: because Nixpkgs has a rarely used
-       function named ‘or’, allow stuff like ‘map or [...]’. */
-    expr_simple OR_KW
-    { $$ = new ExprApp(CUR_POS, $1, new ExprVar(CUR_POS, data->symbols.Create("or"))); }
-  | expr_simple { $$ = $1; }
-  ;
-
-expr_simple
-  : ID {
-      if (strcmp($1, "__curPos") == 0)
-          $$ = new ExprPos(CUR_POS);
-      else
-          $$ = new ExprVar(CUR_POS, data->symbols.Create($1));
-  }
-  | INT { $$ = new ExprInt($1); }
-  | FLOAT { $$ = new ExprFloat($1); }
-  | '"' string_parts '"' { $$ = $2; }
-  | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
-      $$ = stripIndentation(CUR_POS, data->symbols, *$2);
-  }
-  | PATH { $$ = new ExprPath(absPath($1, data->basePath)); }
-  | HPATH { $$ = new ExprPath(getHome() + std::string{$1 + 1}); }
-  | SPATH {
-      std::string path($1 + 1, strlen($1) - 2);
-      $$ = new ExprApp(CUR_POS,
-          new ExprApp(new ExprVar(data->symbols.Create("__findFile")),
-              new ExprVar(data->symbols.Create("__nixPath"))),
-          new ExprString(data->symbols.Create(path)));
-  }
-  | URI { $$ = new ExprString(data->symbols.Create($1)); }
-  | '(' expr ')' { $$ = $2; }
-  /* Let expressions `let {..., body = ...}' are just desugared
-     into `(rec {..., body = ...}).body'. */
-  | LET '{' binds '}'
-    { $3->recursive = true; $$ = new ExprSelect(noPos, $3, data->symbols.Create("body")); }
-  | REC '{' binds '}'
-    { $3->recursive = true; $$ = $3; }
-  | '{' binds '}'
-    { $$ = $2; }
-  | '[' expr_list ']' { $$ = $2; }
-  ;
-
-string_parts
-  : STR
-  | string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); }
-  | { $$ = new ExprString(data->symbols.Create("")); }
-  ;
-
-string_parts_interpolated
-  : string_parts_interpolated STR { $$ = $1; $1->push_back($2); }
-  | string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->push_back($3); }
-  | DOLLAR_CURLY expr '}' { $$ = new nix::VectorExprs; $$->push_back($2); }
-  | STR DOLLAR_CURLY expr '}' {
-      $$ = new nix::VectorExprs;
-      $$->push_back($1);
-      $$->push_back($3);
-    }
-  ;
-
-ind_string_parts
-  : ind_string_parts IND_STR { $$ = $1; $1->push_back($2); }
-  | ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->push_back($3); }
-  | { $$ = new nix::VectorExprs; }
-  ;
-
-binds
-  : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); }
-  | binds INHERIT attrs ';'
-    { $$ = $1;
-      for (auto & i : *$3) {
-          auto sym = std::get<Symbol>(i);
-          if ($$->attrs.find(sym) != $$->attrs.end()) {
-              dupAttr(sym, makeCurPos(@3, data), $$->attrs[sym].pos);
-          }
-          Pos pos = makeCurPos(@3, data);
-          $$->attrs[sym] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, sym), pos, true);
-      }
-    }
-  | binds INHERIT '(' expr ')' attrs ';'
-    { $$ = $1;
-      /* !!! Should ensure sharing of the expression in $4. */
-      for (auto & i : *$6) {
-          auto sym = std::get<Symbol>(i);
-          if ($$->attrs.find(sym) != $$->attrs.end()) {
-            dupAttr(sym, makeCurPos(@6, data), $$->attrs[sym].pos);
-          }
-          $$->attrs[sym] = ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, sym), makeCurPos(@6, data));
-      }
-    }
-  | { $$ = new ExprAttrs; }
-  ;
-
-attrs
-  : attrs attr { $$ = $1; $1->push_back(AttrName(data->symbols.Create($2))); }
-  | attrs string_attr
-    { $$ = $1;
-      ExprString * str = dynamic_cast<ExprString *>($2);
-      if (str) {
-          $$->push_back(AttrName(str->s));
-          delete str;
-      } else
-          throw ParseError(format("dynamic attributes not allowed in inherit at %1%")
-              % makeCurPos(@2, data));
-    }
-  | { $$ = new AttrPath; }
-  ;
-
-attrpath
-  : attrpath '.' attr { $$ = $1; $1->push_back(AttrName(data->symbols.Create($3))); }
-  | attrpath '.' string_attr
-    { $$ = $1;
-      ExprString * str = dynamic_cast<ExprString *>($3);
-      if (str) {
-          $$->push_back(AttrName(str->s));
-          delete str;
-      } else {
-          $$->push_back(AttrName($3));
-      }
-    }
-  | attr { $$ = new nix::AttrNameVector; $$->push_back(AttrName(data->symbols.Create($1))); }
-  | string_attr
-    { $$ = new nix::AttrNameVector;
-      ExprString *str = dynamic_cast<ExprString *>($1);
-      if (str) {
-          $$->push_back(AttrName(str->s));
-          delete str;
-      } else
-          $$->push_back(AttrName($1));
-    }
-  ;
-
-attr
-  : ID { $$ = $1; }
-  | OR_KW { $$ = "or"; }
-  ;
-
-string_attr
-  : '"' string_parts '"' { $$ = $2; }
-  | DOLLAR_CURLY expr '}' { $$ = $2; }
-  ;
-
-expr_list
-  : expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */ }
-  | { $$ = new ExprList; }
-  ;
-
-formals
-  : formal ',' formals
-    { $$ = $3; addFormal(CUR_POS, $$, *$1); }
-  | formal
-    { $$ = new Formals; addFormal(CUR_POS, $$, *$1); $$->ellipsis = false; }
-  |
-    { $$ = new Formals; $$->ellipsis = false; }
-  | ELLIPSIS
-    { $$ = new Formals; $$->ellipsis = true; }
-  ;
-
-formal
-  : ID { $$ = new Formal(data->symbols.Create($1), 0); }
-  | ID '?' expr { $$ = new Formal(data->symbols.Create($1), $3); }
-  ;
-
-%%
-
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include "libexpr/eval.hh"
-#include "libstore/store-api.hh"
-
-
-namespace nix {
-
-Expr* EvalState::parse(const char* text, const Path& path, const Path& basePath,
-                       StaticEnv& staticEnv) {
-  yyscan_t scanner;
-  ParseData data(*this);
-  data.basePath = basePath;
-  data.path = data.symbols.Create(path);
-
-  yylex_init(&scanner);
-  yy_scan_string(text, scanner);
-  int res = yyparse(scanner, &data);
-  yylex_destroy(scanner);
-
-  if (res) {
-    throw ParseError(data.error);
-  }
-
-  data.result->bindVars(staticEnv);
-
-  return data.result;
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/primops.cc b/third_party/nix/src/libexpr/primops.cc
deleted file mode 100644
index 761ff954a4..0000000000
--- a/third_party/nix/src/libexpr/primops.cc
+++ /dev/null
@@ -1,2335 +0,0 @@
-#include "libexpr/primops.hh"
-
-#include <algorithm>
-#include <cstring>
-#include <iostream>
-#include <regex>
-
-#include <absl/strings/str_split.h>
-#include <glog/logging.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "libexpr/eval-inline.hh"
-#include "libexpr/eval.hh"
-#include "libexpr/json-to-value.hh"
-#include "libexpr/names.hh"
-#include "libexpr/value-to-json.hh"
-#include "libexpr/value-to-xml.hh"
-#include "libstore/derivations.hh"
-#include "libstore/download.hh"
-#include "libstore/globals.hh"
-#include "libstore/store-api.hh"
-#include "libutil/archive.hh"
-#include "libutil/json.hh"
-#include "libutil/status.hh"
-#include "libutil/util.hh"
-
-namespace nix {
-
-/*************************************************************
- * Miscellaneous
- *************************************************************/
-
-/* Decode a context string ‘!<name>!<path>’ into a pair <path,
-   name>. */
-std::pair<std::string, std::string> decodeContext(const std::string& s) {
-  if (s.at(0) == '!') {
-    size_t index = s.find('!', 1);
-    return std::pair<std::string, std::string>(std::string(s, index + 1),
-                                               std::string(s, 1, index - 1));
-  }
-  return std::pair<std::string, std::string>(
-      s.at(0) == '/' ? s : std::string(s, 1), "");
-}
-
-InvalidPathError::InvalidPathError(const Path& path)
-    : EvalError(format("path '%1%' is not valid") % path), path(path) {}
-
-void EvalState::realiseContext(const PathSet& context) {
-  PathSet drvs;
-
-  for (auto& i : context) {
-    std::pair<std::string, std::string> decoded = decodeContext(i);
-    Path ctx = decoded.first;
-    assert(store->isStorePath(ctx));
-    if (!store->isValidPath(ctx)) {
-      throw InvalidPathError(ctx);
-    }
-    if (!decoded.second.empty() && nix::isDerivation(ctx)) {
-      drvs.insert(decoded.first + "!" + decoded.second);
-
-      /* Add the output of this derivation to the allowed
-         paths. */
-      if (allowedPaths) {
-        auto drv = store->derivationFromPath(decoded.first);
-        auto i = drv.outputs.find(decoded.second);
-        if (i == drv.outputs.end()) {
-          throw Error("derivation '%s' does not have an output named '%s'",
-                      decoded.first, decoded.second);
-        }
-        allowedPaths->insert(i->second.path);
-      }
-    }
-  }
-
-  if (drvs.empty()) {
-    return;
-  }
-
-  if (!evalSettings.enableImportFromDerivation) {
-    throw EvalError(format("attempted to realize '%1%' during evaluation but "
-                           "'allow-import-from-derivation' is false") %
-                    *(drvs.begin()));
-  }
-
-  /* For performance, prefetch all substitute info. */
-  PathSet willBuild;
-  PathSet willSubstitute;
-  PathSet unknown;
-  unsigned long long downloadSize;
-  unsigned long long narSize;
-  store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize,
-                      narSize);
-
-  nix::util::OkOrThrow(store->buildPaths(std::cerr, drvs));
-}
-
-/* Load and evaluate an expression from path specified by the
-   argument. */
-static void prim_scopedImport(EvalState& state, const Pos& pos, Value** args,
-                              Value& v) {
-  PathSet context;
-  Path path = state.coerceToPath(pos, *args[1], context);
-
-  try {
-    state.realiseContext(context);
-  } catch (InvalidPathError& e) {
-    throw EvalError(
-        format("cannot import '%1%', since path '%2%' is not valid, at %3%") %
-        path % e.path % pos);
-  }
-
-  Path realPath = state.checkSourcePath(state.toRealPath(path, context));
-
-  if (state.store->isStorePath(path) && state.store->isValidPath(path) &&
-      isDerivation(path)) {
-    Derivation drv = readDerivation(realPath);
-    Value& w = *state.allocValue();
-    state.mkAttrs(w, 3 + drv.outputs.size());
-    Value* v2 = state.allocAttr(w, state.sDrvPath);
-    mkString(*v2, path, {"=" + path});
-    v2 = state.allocAttr(w, state.sName);
-    mkString(*v2, drv.env["name"]);
-    Value* outputsVal = state.allocAttr(w, state.symbols.Create("outputs"));
-    state.mkList(*outputsVal, drv.outputs.size());
-    unsigned int outputs_index = 0;
-
-    for (const auto& o : drv.outputs) {
-      v2 = state.allocAttr(w, state.symbols.Create(o.first));
-      mkString(*v2, o.second.path, {"!" + o.first + "!" + path});
-      (*outputsVal->list)[outputs_index] = state.allocValue();
-      mkString(*((*outputsVal->list)[outputs_index++]), o.first);
-    }
-
-    Value fun;
-    state.evalFile(
-        settings.nixDataDir + "/nix/corepkgs/imported-drv-to-derivation.nix",
-        fun);
-    state.forceFunction(fun, pos);
-    mkApp(v, fun, w);
-    state.forceAttrs(v, pos);
-  } else {
-    state.forceAttrs(*args[0]);
-    if (args[0]->attrs->empty()) {
-      state.evalFile(realPath, v);
-    } else {
-      Env* env = &state.allocEnv(args[0]->attrs->size());
-      env->up = &state.baseEnv;
-
-      StaticEnv staticEnv(false, &state.staticBaseEnv);
-
-      unsigned int displ = 0;
-      for (auto& attr : *args[0]->attrs) {
-        staticEnv.vars[attr.second.name] = displ;
-        env->values[displ++] = attr.second.value;
-      }
-
-      DLOG(INFO) << "evaluating file '" << realPath << "'";
-      Expr* e = state.parseExprFromFile(resolveExprPath(realPath), staticEnv);
-
-      e->eval(state, *env, v);
-    }
-  }
-}
-
-/* Return a string representing the type of the expression. */
-static void prim_typeOf(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  state.forceValue(*args[0]);
-  std::string t;
-  switch (args[0]->type) {
-    case tInt:
-      t = "int";
-      break;
-    case tBool:
-      t = "bool";
-      break;
-    case tString:
-      t = "string";
-      break;
-    case tPath:
-      t = "path";
-      break;
-    case tNull:
-      t = "null";
-      break;
-    case tAttrs:
-      t = "set";
-      break;
-    case tList:
-      t = "list";
-      break;
-    case tLambda:
-    case tPrimOp:
-    case tPrimOpApp:
-      t = "lambda";
-      break;
-    case tFloat:
-      t = "float";
-      break;
-    default:
-      abort();
-  }
-  mkString(v, state.symbols.Create(t));
-}
-
-/* Determine whether the argument is the null value. */
-static void prim_isNull(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  state.forceValue(*args[0]);
-  mkBool(v, args[0]->type == tNull);
-}
-
-/* Determine whether the argument is a function. */
-static void prim_isFunction(EvalState& state, const Pos& pos, Value** args,
-                            Value& v) {
-  state.forceValue(*args[0]);
-  bool res;
-  switch (args[0]->type) {
-    case tLambda:
-    case tPrimOp:
-    case tPrimOpApp:
-      res = true;
-      break;
-    default:
-      res = false;
-      break;
-  }
-  mkBool(v, res);
-}
-
-/* Determine whether the argument is an integer. */
-static void prim_isInt(EvalState& state, const Pos& pos, Value** args,
-                       Value& v) {
-  state.forceValue(*args[0]);
-  mkBool(v, args[0]->type == tInt);
-}
-
-/* Determine whether the argument is a float. */
-static void prim_isFloat(EvalState& state, const Pos& pos, Value** args,
-                         Value& v) {
-  state.forceValue(*args[0]);
-  mkBool(v, args[0]->type == tFloat);
-}
-
-/* Determine whether the argument is a string. */
-static void prim_isString(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  state.forceValue(*args[0]);
-  mkBool(v, args[0]->type == tString);
-}
-
-/* Determine whether the argument is a Boolean. */
-static void prim_isBool(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  state.forceValue(*args[0]);
-  mkBool(v, args[0]->type == tBool);
-}
-
-/* Determine whether the argument is a path. */
-static void prim_isPath(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  state.forceValue(*args[0]);
-  mkBool(v, args[0]->type == tPath);
-}
-
-struct CompareValues {
-  bool operator()(const Value* v1, const Value* v2) const {
-    if (v1->type == tFloat && v2->type == tInt) {
-      return v1->fpoint < v2->integer;
-    }
-    if (v1->type == tInt && v2->type == tFloat) {
-      return v1->integer < v2->fpoint;
-    }
-    if (v1->type != v2->type) {
-      throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) %
-                      showType(*v2));
-    }
-    switch (v1->type) {
-      case tInt:
-        return v1->integer < v2->integer;
-      case tFloat:
-        return v1->fpoint < v2->fpoint;
-      case tString:
-        return strcmp(v1->string.s, v2->string.s) < 0;
-      case tPath:
-        return strcmp(v1->path, v2->path) < 0;
-      default:
-        throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) %
-                        showType(*v2));
-    }
-  }
-};
-
-typedef std::list<Value*> ValueList;
-
-static void prim_genericClosure(EvalState& state, const Pos& pos, Value** args,
-                                Value& v) {
-  state.forceAttrs(*args[0], pos);
-
-  /* Get the start set. */
-  Bindings::iterator startSet =
-      args[0]->attrs->find(state.symbols.Create("startSet"));
-  if (startSet == args[0]->attrs->end()) {
-    throw EvalError(format("attribute 'startSet' required, at %1%") % pos);
-  }
-  state.forceList(*startSet->second.value, pos);
-
-  ValueList workSet;
-  for (Value* elem : *startSet->second.value->list) {
-    workSet.push_back(elem);
-  }
-
-  /* Get the operator. */
-  Bindings::iterator op =
-      args[0]->attrs->find(state.symbols.Create("operator"));
-  if (op == args[0]->attrs->end()) {
-    throw EvalError(format("attribute 'operator' required, at %1%") % pos);
-  }
-  state.forceValue(*op->second.value);
-
-  /* Construct the closure by applying the operator to element of
-     `workSet', adding the result to `workSet', continuing until
-     no new elements are found. */
-  ValueList res;
-  // `doneKeys' doesn't need to be a GC root, because its values are
-  // reachable from res.
-  std::set<Value*, CompareValues> doneKeys;
-  while (!workSet.empty()) {
-    Value* e = *(workSet.begin());
-    workSet.pop_front();
-
-    state.forceAttrs(*e, pos);
-
-    Bindings::iterator key = e->attrs->find(state.symbols.Create("key"));
-    if (key == e->attrs->end()) {
-      throw EvalError(format("attribute 'key' required, at %1%") % pos);
-    }
-    state.forceValue(*key->second.value);
-
-    if (doneKeys.find(key->second.value) != doneKeys.end()) {
-      continue;
-    }
-    doneKeys.insert(key->second.value);
-    res.push_back(e);
-
-    /* Call the `operator' function with `e' as argument. */
-    Value call;
-    mkApp(call, *op->second.value, *e);
-    state.forceList(call, pos);
-
-    /* Add the values returned by the operator to the work set. */
-    for (unsigned int n = 0; n < call.listSize(); ++n) {
-      state.forceValue(*(*call.list)[n]);
-      workSet.push_back((*call.list)[n]);
-    }
-  }
-
-  /* Create the result list. */
-  state.mkList(v, res.size());
-  unsigned int n = 0;
-  for (auto& i : res) {
-    (*v.list)[n++] = i;
-  }
-}
-
-static void prim_abort(EvalState& state, const Pos& pos, Value** args,
-                       Value& v) {
-  PathSet context;
-  std::string s = state.coerceToString(pos, *args[0], context);
-  throw Abort(
-      format("evaluation aborted with the following error message: '%1%'") % s);
-}
-
-static void prim_throw(EvalState& state, const Pos& pos, Value** args,
-                       Value& v) {
-  PathSet context;
-  std::string s = state.coerceToString(pos, *args[0], context);
-  throw ThrownError(s);
-}
-
-static void prim_addErrorContext(EvalState& state, const Pos& pos, Value** args,
-                                 Value& v) {
-  try {
-    state.forceValue(*args[1]);
-    v = *args[1];
-  } catch (Error& e) {
-    PathSet context;
-    e.addPrefix(format("%1%\n") % state.coerceToString(pos, *args[0], context));
-    throw;
-  }
-}
-
-/* Try evaluating the argument. Success => {success=true; value=something;},
- * else => {success=false; value=false;} */
-static void prim_tryEval(EvalState& state, const Pos& pos, Value** args,
-                         Value& v) {
-  state.mkAttrs(v, 2);
-  try {
-    state.forceValue(*args[0]);
-    v.attrs->push_back(Attr(state.sValue, args[0]));
-    mkBool(*state.allocAttr(v, state.symbols.Create("success")), true);
-  } catch (AssertionError& e) {
-    mkBool(*state.allocAttr(v, state.sValue), false);
-    mkBool(*state.allocAttr(v, state.symbols.Create("success")), false);
-  }
-}
-
-/* Return an environment variable.  Use with care. */
-static void prim_getEnv(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  std::string name = state.forceStringNoCtx(*args[0], pos);
-  mkString(v, evalSettings.restrictEval || evalSettings.pureEval
-                  ? ""
-                  : getEnv(name).value_or(""));
-}
-
-/* Evaluate the first argument, then return the second argument. */
-static void prim_seq(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  state.forceValue(*args[0]);
-  state.forceValue(*args[1]);
-  v = *args[1];
-}
-
-/* Evaluate the first argument deeply (i.e. recursing into lists and
-   attrsets), then return the second argument. */
-static void prim_deepSeq(EvalState& state, const Pos& pos, Value** args,
-                         Value& v) {
-  state.forceValueDeep(*args[0]);
-  state.forceValue(*args[1]);
-  v = *args[1];
-}
-
-/* Evaluate the first expression and print it on standard error.  Then
-   return the second expression.  Useful for debugging. */
-static void prim_trace(EvalState& state, const Pos& pos, Value** args,
-                       Value& v) {
-  state.forceValue(*args[0]);
-  if (args[0]->type == tString) {
-    LOG(INFO) << "trace: " << args[0]->string.s;
-  } else {
-    LOG(INFO) << "trace: " << *args[0];
-  }
-  state.forceValue(*args[1]);
-  v = *args[1];
-}
-
-void prim_valueSize(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  /* We're not forcing the argument on purpose. */
-  mkInt(v, valueSize(*args[0]));
-}
-
-/*************************************************************
- * Derivations
- *************************************************************/
-
-/* 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. */
-static void prim_derivationStrict(EvalState& state, const Pos& pos,
-                                  Value** args, Value& v) {
-  state.forceAttrs(*args[0], pos);
-
-  /* Figure out the name first (for stack backtraces). */
-  Bindings::iterator attr = args[0]->attrs->find(state.sName);
-  if (attr == args[0]->attrs->end()) {
-    throw EvalError(format("required attribute 'name' missing, at %1%") % pos);
-  }
-  std::string drvName;
-  Pos& posDrvName(*attr->second.pos);
-  try {
-    drvName = state.forceStringNoCtx(*attr->second.value, pos);
-  } catch (Error& e) {
-    e.addPrefix(
-        format("while evaluating the derivation attribute 'name' at %1%:\n") %
-        posDrvName);
-    throw;
-  }
-
-  /* Check whether attributes should be passed as a JSON file. */
-  std::ostringstream jsonBuf;
-  std::unique_ptr<JSONObject> jsonObject;
-  attr = args[0]->attrs->find(state.sStructuredAttrs);
-  if (attr != args[0]->attrs->end() &&
-      state.forceBool(*attr->second.value, pos)) {
-    jsonObject = std::make_unique<JSONObject>(jsonBuf);
-  }
-
-  /* Check whether null attributes should be ignored. */
-  bool ignoreNulls = false;
-  attr = args[0]->attrs->find(state.sIgnoreNulls);
-  if (attr != args[0]->attrs->end()) {
-    ignoreNulls = state.forceBool(*attr->second.value, pos);
-  }
-
-  /* Build the derivation expression by processing the attributes. */
-  Derivation drv;
-
-  PathSet context;
-
-  std::optional<std::string> outputHash;
-  std::string outputHashAlgo;
-  bool outputHashRecursive = false;
-
-  StringSet outputs;
-  outputs.insert("out");
-
-  for (auto& i : args[0]->attrs->SortedByKeys()) {
-    if (i->name == state.sIgnoreNulls) {
-      continue;
-    }
-    const std::string& key = i->name;
-
-    auto handleHashMode = [&](const std::string& s) {
-      if (s == "recursive") {
-        outputHashRecursive = true;
-      } else if (s == "flat") {
-        outputHashRecursive = false;
-      } else {
-        throw EvalError(
-            "invalid value '%s' for 'outputHashMode' attribute, at %s", s,
-            posDrvName);
-      }
-    };
-
-    auto handleOutputs = [&](const Strings& ss) {
-      outputs.clear();
-      for (auto& j : ss) {
-        if (outputs.find(j) != outputs.end()) {
-          throw EvalError(format("duplicate derivation output '%1%', at %2%") %
-                          j % posDrvName);
-        }
-        /* !!! Check whether j is a valid attribute
-           name. */
-        /* Derivations cannot be named ‘drv’, because
-           then we'd have an attribute ‘drvPath’ in
-           the resulting set. */
-        if (j == "drv") {
-          throw EvalError(
-              format("invalid derivation output name 'drv', at %1%") %
-              posDrvName);
-        }
-        outputs.insert(j);
-      }
-      if (outputs.empty()) {
-        throw EvalError(
-            format("derivation cannot have an empty set of outputs, at %1%") %
-            posDrvName);
-      }
-    };
-
-    try {
-      if (ignoreNulls) {
-        state.forceValue(*i->value);
-        if (i->value->type == tNull) {
-          continue;
-        }
-      }
-
-      /* The `args' attribute is special: it supplies the
-         command-line arguments to the builder. */
-      if (i->name == state.sArgs) {
-        state.forceList(*i->value, pos);
-        for (unsigned int n = 0; n < i->value->listSize(); ++n) {
-          std::string s = state.coerceToString(
-              posDrvName, *(*i->value->list)[n], context, true);
-          drv.args.push_back(s);
-        }
-      }
-
-      /* All other attributes are passed to the builder through
-         the environment. */
-      else {
-        if (jsonObject) {
-          if (i->name == state.sStructuredAttrs) {
-            continue;
-          }
-
-          auto placeholder(jsonObject->placeholder(key));
-          printValueAsJSON(state, true, *i->value, placeholder, context);
-
-          if (i->name == state.sBuilder) {
-            drv.builder = state.forceString(*i->value, context, posDrvName);
-          } else if (i->name == state.sSystem) {
-            drv.platform = state.forceStringNoCtx(*i->value, posDrvName);
-          } else if (i->name == state.sOutputHash) {
-            outputHash = state.forceStringNoCtx(*i->value, posDrvName);
-          } else if (i->name == state.sOutputHashAlgo) {
-            outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName);
-          } else if (i->name == state.sOutputHashMode) {
-            handleHashMode(state.forceStringNoCtx(*i->value, posDrvName));
-          } else if (i->name == state.sOutputs) {
-            /* Require ‘outputs’ to be a list of strings. */
-            state.forceList(*i->value, posDrvName);
-            Strings ss;
-            for (unsigned int n = 0; n < i->value->listSize(); ++n) {
-              ss.emplace_back(
-                  state.forceStringNoCtx(*(*i->value->list)[n], posDrvName));
-            }
-            handleOutputs(ss);
-          }
-
-        } else {
-          auto s = state.coerceToString(posDrvName, *i->value, context, true);
-          drv.env.emplace(key, s);
-          if (i->name == state.sBuilder) {
-            drv.builder = s;
-          } else if (i->name == state.sSystem) {
-            drv.platform = s;
-          } else if (i->name == state.sOutputHash) {
-            outputHash = s;
-          } else if (i->name == state.sOutputHashAlgo) {
-            outputHashAlgo = s;
-          } else if (i->name == state.sOutputHashMode) {
-            handleHashMode(s);
-          } else if (i->name == state.sOutputs) {
-            handleOutputs(absl::StrSplit(s, absl::ByAnyChar(" \t\n\r"),
-                                         absl::SkipEmpty()));
-          }
-        }
-      }
-
-    } catch (Error& e) {
-      e.addPrefix(format("while evaluating the attribute '%1%' of the "
-                         "derivation '%2%' at %3%:\n") %
-                  key % drvName % posDrvName);
-      throw;
-    }
-  }
-
-  if (jsonObject) {
-    jsonObject.reset();
-    drv.env.emplace("__json", jsonBuf.str());
-  }
-
-  /* Everything in the context of the strings in the derivation
-     attributes should be added as dependencies of the resulting
-     derivation. */
-  for (auto& path : context) {
-    /* Paths marked with `=' denote that the path of a derivation
-       is explicitly passed to the builder.  Since that allows the
-       builder to gain access to every path in the dependency
-       graph of the derivation (including all outputs), all paths
-       in the graph must be added to this derivation's list of
-       inputs to ensure that they are available when the builder
-       runs. */
-    if (path.at(0) == '=') {
-      /* !!! This doesn't work if readOnlyMode is set. */
-      PathSet refs;
-      state.store->computeFSClosure(std::string(path, 1), refs);
-      for (auto& j : refs) {
-        drv.inputSrcs.insert(j);
-        if (isDerivation(j)) {
-          drv.inputDrvs[j] = state.store->queryDerivationOutputNames(j);
-        }
-      }
-    }
-
-    /* Handle derivation outputs of the form ‘!<name>!<path>’. */
-    else if (path.at(0) == '!') {
-      std::pair<std::string, std::string> ctx = decodeContext(path);
-      drv.inputDrvs[ctx.first].insert(ctx.second);
-    }
-
-    /* Otherwise it's a source file. */
-    else {
-      drv.inputSrcs.insert(path);
-    }
-  }
-
-  /* Do we have all required attributes? */
-  if (drv.builder.empty()) {
-    throw EvalError(format("required attribute 'builder' missing, at %1%") %
-                    posDrvName);
-  }
-  if (drv.platform.empty()) {
-    throw EvalError(format("required attribute 'system' missing, at %1%") %
-                    posDrvName);
-  }
-
-  /* Check whether the derivation name is valid. */
-  checkStoreName(drvName);
-  if (isDerivation(drvName)) {
-    throw EvalError(
-        format("derivation names are not allowed to end in '%1%', at %2%") %
-        drvExtension % posDrvName);
-  }
-
-  if (outputHash) {
-    /* Handle fixed-output derivations. */
-    if (outputs.size() != 1 || *(outputs.begin()) != "out") {
-      throw Error(format("multiple outputs are not supported in fixed-output "
-                         "derivations, at %1%") %
-                  posDrvName);
-    }
-
-    HashType ht =
-        outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo);
-    auto hash_ = Hash::deserialize(*outputHash, ht);
-    auto h = Hash::unwrap_throw(hash_);
-
-    Path outPath =
-        state.store->makeFixedOutputPath(outputHashRecursive, h, drvName);
-    if (!jsonObject) {
-      drv.env["out"] = outPath;
-    }
-    drv.outputs["out"] = DerivationOutput(
-        outPath, (outputHashRecursive ? "r:" : "") + printHashType(h.type),
-        h.to_string(Base16, false));
-  }
-
-  else {
-    /* Construct the "masked" store derivation, which is the final
-       one except that in the list of outputs, the output paths
-       are empty, and the corresponding environment variables have
-       an empty value.  This ensures that changes in the set of
-       output names do get reflected in the hash. */
-    for (auto& i : outputs) {
-      if (!jsonObject) {
-        drv.env[i] = "";
-      }
-      drv.outputs[i] = DerivationOutput("", "", "");
-    }
-
-    /* Use the masked derivation expression to compute the output
-       path. */
-    Hash h = hashDerivationModulo(*state.store, drv);
-
-    for (auto& i : drv.outputs) {
-      if (i.second.path.empty()) {
-        Path outPath = state.store->makeOutputPath(i.first, h, drvName);
-        if (!jsonObject) {
-          drv.env[i.first] = outPath;
-        }
-        i.second.path = outPath;
-      }
-    }
-  }
-
-  /* Write the resulting term into the Nix store directory. */
-  Path drvPath = writeDerivation(state.store, drv, drvName, state.repair);
-
-  DLOG(INFO) << "instantiated '" << drvName << "' -> '" << drvPath << "'";
-
-  /* Optimisation, but required in read-only mode! because in that
-     case we don't actually write store derivations, so we can't
-     read them later. */
-  drvHashes[drvPath] = hashDerivationModulo(*state.store, drv);
-
-  state.mkAttrs(v, 1 + drv.outputs.size());
-  mkString(*state.allocAttr(v, state.sDrvPath), drvPath, {"=" + drvPath});
-  for (auto& i : drv.outputs) {
-    mkString(*state.allocAttr(v, state.symbols.Create(i.first)), i.second.path,
-             {"!" + i.first + "!" + drvPath});
-  }
-}
-
-/* Return a placeholder string for the specified output that will be
-   substituted by the corresponding output path at build time. For
-   example, 'placeholder "out"' returns the string
-   /1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9. At build
-   time, any occurence of this string in an derivation attribute will
-   be replaced with the concrete path in the Nix store of the output
-   ‘out’. */
-static void prim_placeholder(EvalState& state, const Pos& pos, Value** args,
-                             Value& v) {
-  mkString(v, hashPlaceholder(state.forceStringNoCtx(*args[0], pos)));
-}
-
-/*************************************************************
- * Paths
- *************************************************************/
-
-/* Convert the argument to a path.  !!! obsolete? */
-static void prim_toPath(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  PathSet context;
-  Path path = state.coerceToPath(pos, *args[0], context);
-  mkString(v, canonPath(path), context);
-}
-
-/* Allow a valid store path to be used in an expression.  This is
-   useful in some generated expressions such as in nix-push, which
-   generates a call to a function with an already existing store path
-   as argument.  You don't want to use `toPath' here because it copies
-   the path to the Nix store, which yields a copy like
-   /nix/store/newhash-oldhash-oldname.  In the past, `toPath' had
-   special case behaviour for store paths, but that created weird
-   corner cases. */
-static void prim_storePath(EvalState& state, const Pos& pos, Value** args,
-                           Value& v) {
-  PathSet context;
-  Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context));
-  /* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
-     directly in the store.  The latter condition is necessary so
-     e.g. nix-push does the right thing. */
-  if (!state.store->isStorePath(path)) {
-    path = canonPath(path, true);
-  }
-  if (!state.store->isInStore(path)) {
-    throw EvalError(format("path '%1%' is not in the Nix store, at %2%") %
-                    path % pos);
-  }
-  Path path2 = state.store->toStorePath(path);
-  if (!settings.readOnlyMode) {
-    state.store->ensurePath(path2);
-  }
-  context.insert(path2);
-  mkString(v, path, context);
-}
-
-static void prim_pathExists(EvalState& state, const Pos& pos, Value** args,
-                            Value& v) {
-  PathSet context;
-  Path path = state.coerceToPath(pos, *args[0], context);
-  try {
-    state.realiseContext(context);
-  } catch (InvalidPathError& e) {
-    throw EvalError(format("cannot check the existence of '%1%', since path "
-                           "'%2%' is not valid, at %3%") %
-                    path % e.path % pos);
-  }
-
-  try {
-    mkBool(v, pathExists(state.checkSourcePath(path)));
-  } catch (SysError& e) {
-    /* Don't give away info from errors while canonicalising
-       ‘path’ in restricted mode. */
-    mkBool(v, false);
-  } catch (RestrictedPathError& e) {
-    mkBool(v, false);
-  }
-}
-
-/* Return the base name of the given string, i.e., everything
-   following the last slash. */
-static void prim_baseNameOf(EvalState& state, const Pos& pos, Value** args,
-                            Value& v) {
-  PathSet context;
-  mkString(
-      v, baseNameOf(state.coerceToString(pos, *args[0], context, false, false)),
-      context);
-}
-
-/* Return the directory of the given path, i.e., everything before the
-   last slash.  Return either a path or a string depending on the type
-   of the argument. */
-static void prim_dirOf(EvalState& state, const Pos& pos, Value** args,
-                       Value& v) {
-  PathSet context;
-  Path dir = dirOf(state.coerceToString(pos, *args[0], context, false, false));
-  if (args[0]->type == tPath) {
-    mkPath(v, dir.c_str());
-  } else {
-    mkString(v, dir, context);
-  }
-}
-
-/* Return the contents of a file as a string. */
-static void prim_readFile(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  PathSet context;
-  Path path = state.coerceToPath(pos, *args[0], context);
-  try {
-    state.realiseContext(context);
-  } catch (InvalidPathError& e) {
-    throw EvalError(
-        format("cannot read '%1%', since path '%2%' is not valid, at %3%") %
-        path % e.path % pos);
-  }
-  std::string s =
-      readFile(state.checkSourcePath(state.toRealPath(path, context)));
-  if (s.find(static_cast<char>(0)) != std::string::npos) {
-    throw Error(format("the contents of the file '%1%' cannot be represented "
-                       "as a Nix string") %
-                path);
-  }
-  mkString(v, s.c_str());
-}
-
-/* Find a file in the Nix search path. Used to implement <x> paths,
-   which are desugared to 'findFile __nixPath "x"'. */
-static void prim_findFile(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  state.forceList(*args[0], pos);
-
-  SearchPath searchPath;
-
-  for (unsigned int n = 0; n < args[0]->listSize(); ++n) {
-    Value& v2(*(*args[0]->list)[n]);
-    state.forceAttrs(v2, pos);
-
-    std::string prefix;
-    Bindings::iterator i = v2.attrs->find(state.symbols.Create("prefix"));
-    if (i != v2.attrs->end()) {
-      prefix = state.forceStringNoCtx(*i->second.value, pos);
-    }
-
-    i = v2.attrs->find(state.symbols.Create("path"));
-    if (i == v2.attrs->end()) {
-      throw EvalError(format("attribute 'path' missing, at %1%") % pos);
-    }
-
-    PathSet context;
-    std::string path =
-        state.coerceToString(pos, *i->second.value, context, false, false);
-
-    try {
-      state.realiseContext(context);
-    } catch (InvalidPathError& e) {
-      throw EvalError(
-          format("cannot find '%1%', since path '%2%' is not valid, at %3%") %
-          path % e.path % pos);
-    }
-
-    searchPath.emplace_back(prefix, path);
-  }
-
-  std::string path = state.forceStringNoCtx(*args[1], pos);
-
-  mkPath(v,
-         state.checkSourcePath(state.findFile(searchPath, path, pos)).c_str());
-}
-
-/* Return the cryptographic hash of a file in base-16. */
-static void prim_hashFile(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  std::string type = state.forceStringNoCtx(*args[0], pos);
-  HashType ht = parseHashType(type);
-  if (ht == htUnknown) {
-    throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
-  }
-
-  PathSet context;  // discarded
-  Path p = state.coerceToPath(pos, *args[1], context);
-
-  mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base16, false),
-           context);
-}
-
-/* Read a directory (without . or ..) */
-static void prim_readDir(EvalState& state, const Pos& pos, Value** args,
-                         Value& v) {
-  PathSet ctx;
-  Path path = state.coerceToPath(pos, *args[0], ctx);
-  try {
-    state.realiseContext(ctx);
-  } catch (InvalidPathError& e) {
-    throw EvalError(
-        format("cannot read '%1%', since path '%2%' is not valid, at %3%") %
-        path % e.path % pos);
-  }
-
-  DirEntries entries = readDirectory(state.checkSourcePath(path));
-  state.mkAttrs(v, entries.size());
-
-  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.name);
-    }
-    mkStringNoCopy(*ent_val,
-                   ent.type == DT_REG
-                       ? "regular"
-                       : ent.type == DT_DIR
-                             ? "directory"
-                             : ent.type == DT_LNK ? "symlink" : "unknown");
-  }
-}
-
-/*************************************************************
- * Creating files
- *************************************************************/
-
-/* Convert the argument (which can be any Nix expression) to an XML
-   representation returned in a string.  Not all Nix expressions can
-   be sensibly or completely represented (e.g., functions). */
-static void prim_toXML(EvalState& state, const Pos& pos, Value** args,
-                       Value& v) {
-  std::ostringstream out;
-  PathSet context;
-  printValueAsXML(state, true, false, *args[0], out, context);
-  mkString(v, out.str(), context);
-}
-
-/* Convert the argument (which can be any Nix expression) to a JSON
-   string.  Not all Nix expressions can be sensibly or completely
-   represented (e.g., functions). */
-static void prim_toJSON(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  std::ostringstream out;
-  PathSet context;
-  printValueAsJSON(state, true, *args[0], out, context);
-  mkString(v, out.str(), context);
-}
-
-/* Parse a JSON string to a value. */
-static void prim_fromJSON(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  std::string s = state.forceStringNoCtx(*args[0], pos);
-  parseJSON(state, s, v);
-}
-
-/* Store a string in the Nix store as a source file that can be used
-   as an input by derivations. */
-static void prim_toFile(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  PathSet context;
-  std::string name = state.forceStringNoCtx(*args[0], pos);
-  std::string contents = state.forceString(*args[1], context, pos);
-
-  PathSet refs;
-
-  for (auto path : context) {
-    if (path.at(0) != '/') {
-      throw EvalError(format("in 'toFile': the file '%1%' cannot refer to "
-                             "derivation outputs, at %2%") %
-                      name % pos);
-    }
-    refs.insert(path);
-  }
-
-  Path storePath =
-      settings.readOnlyMode
-          ? state.store->computeStorePathForText(name, contents, refs)
-          : state.store->addTextToStore(name, contents, refs, state.repair);
-
-  /* Note: we don't need to add `context' to the context of the
-     result, since `storePath' itself has references to the paths
-     used in args[1]. */
-
-  mkString(v, storePath, {storePath});
-}
-
-static void addPath(EvalState& state, const Pos& pos, const std::string& name,
-                    const Path& path_, Value* filterFun, bool recursive,
-                    const Hash& expectedHash, Value& v) {
-  const auto path = evalSettings.pureEval && expectedHash
-                        ? path_
-                        : state.checkSourcePath(path_);
-  PathFilter filter = filterFun != nullptr ? ([&](const Path& path) {
-    auto st = lstat(path);
-
-    /* Call the filter function.  The first argument is the path,
-       the second is a string indicating the type of the file. */
-    Value arg1;
-    mkString(arg1, path);
-
-    Value fun2;
-    state.callFunction(*filterFun, arg1, fun2, noPos);
-
-    Value arg2;
-    mkString(arg2, S_ISREG(st.st_mode)
-                       ? "regular"
-                       : S_ISDIR(st.st_mode)
-                             ? "directory"
-                             : S_ISLNK(st.st_mode)
-                                   ? "symlink"
-                                   : "unknown" /* not supported, will fail! */);
-
-    Value res;
-    state.callFunction(fun2, arg2, res, noPos);
-
-    return state.forceBool(res, pos);
-  })
-                                           : defaultPathFilter;
-
-  Path expectedStorePath;
-  if (expectedHash) {
-    expectedStorePath =
-        state.store->makeFixedOutputPath(recursive, expectedHash, name);
-  }
-  Path dstPath;
-  if (!expectedHash || !state.store->isValidPath(expectedStorePath)) {
-    dstPath = settings.readOnlyMode
-                  ? state.store
-                        ->computeStorePathForPath(name, path, recursive,
-                                                  htSHA256, filter)
-                        .first
-                  : state.store->addToStore(name, path, recursive, htSHA256,
-                                            filter, state.repair);
-    if (expectedHash && expectedStorePath != dstPath) {
-      throw Error(format("store path mismatch in (possibly filtered) path "
-                         "added from '%1%'") %
-                  path);
-    }
-  } else {
-    dstPath = expectedStorePath;
-  }
-
-  mkString(v, dstPath, {dstPath});
-}
-
-static void prim_filterSource(EvalState& state, const Pos& pos, Value** args,
-                              Value& v) {
-  PathSet context;
-  Path path = state.coerceToPath(pos, *args[1], context);
-  if (!context.empty()) {
-    throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") %
-                    path % pos);
-  }
-
-  state.forceValue(*args[0]);
-  if (args[0]->type != tLambda) {
-    throw TypeError(format("first argument in call to 'filterSource' is not a "
-                           "function but %1%, at %2%") %
-                    showType(*args[0]) % pos);
-  }
-
-  addPath(state, pos, baseNameOf(path), path, args[0], true, Hash(), v);
-}
-
-static void prim_path(EvalState& state, const Pos& pos, Value** args,
-                      Value& v) {
-  state.forceAttrs(*args[0], pos);
-  Path path;
-  std::string name;
-  Value* filterFun = nullptr;
-  auto recursive = true;
-  Hash expectedHash;
-
-  for (auto& attr : *args[0]->attrs) {
-    const std::string& n(attr.second.name);
-    if (n == "path") {
-      PathSet context;
-      path = state.coerceToPath(*attr.second.pos, *attr.second.value, context);
-      if (!context.empty()) {
-        throw EvalError(
-            format("string '%1%' cannot refer to other paths, at %2%") % path %
-            *attr.second.pos);
-      }
-    } else if (attr.second.name == state.sName) {
-      name = state.forceStringNoCtx(*attr.second.value, *attr.second.pos);
-    } else if (n == "filter") {
-      state.forceValue(*attr.second.value);
-      filterFun = attr.second.value;
-    } else if (n == "recursive") {
-      recursive = state.forceBool(*attr.second.value, *attr.second.pos);
-    } else if (n == "sha256") {
-      auto hash_ = Hash::deserialize(
-          state.forceStringNoCtx(*attr.second.value, *attr.second.pos),
-          htSHA256);
-      expectedHash = Hash::unwrap_throw(hash_);
-    } else {
-      throw EvalError(
-          format("unsupported argument '%1%' to 'addPath', at %2%") %
-          attr.second.name % *attr.second.pos);
-    }
-  }
-  if (path.empty()) {
-    throw EvalError(format("'path' required, at %1%") % pos);
-  }
-  if (name.empty()) {
-    name = baseNameOf(path);
-  }
-
-  addPath(state, pos, name, path, filterFun, recursive, expectedHash, v);
-}
-
-/*************************************************************
- * Sets
- *************************************************************/
-
-/* Return the names of the attributes in a set as a sorted list of
-   strings. */
-static void prim_attrNames(EvalState& state, const Pos& pos, Value** args,
-                           Value& v) {
-  state.forceAttrs(*args[0], pos);
-
-  state.mkList(v, args[0]->attrs->size());
-
-  unsigned int n = 0;
-  for (auto& [key, value] : *args[0]->attrs) {
-    mkString(*((*v.list)[n++] = state.allocValue()), key);
-  }
-}
-
-/* Return the values of the attributes in a set as a list, in the same
-   order as attrNames. */
-static void prim_attrValues(EvalState& state, const Pos& pos, Value** input,
-                            Value& output) {
-  state.forceAttrs(*input[0], pos);
-
-  state.mkList(output, input[0]->attrs->size());
-
-  unsigned int n = 0;
-  for (auto& [key, value] : *input[0]->attrs) {
-    (*output.list)[n++] = value.value;
-  }
-}
-
-/* Dynamic version of the `.' operator. */
-void prim_getAttr(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  std::string attr = state.forceStringNoCtx(*args[0], pos);
-  state.forceAttrs(*args[1], pos);
-  // !!! Should we create a symbol here or just do a lookup?
-  Bindings::iterator i = args[1]->attrs->find(state.symbols.Create(attr));
-  if (i == args[1]->attrs->end()) {
-    throw EvalError(format("attribute '%1%' missing, at %2%") % attr % pos);
-  }
-  // !!! add to stack trace?
-  if (state.countCalls && (i->second.pos != nullptr)) {
-    state.attrSelects[*i->second.pos]++;
-  }
-  state.forceValue(*i->second.value);
-  v = *i->second.value;
-}
-
-/* Return position information of the specified attribute. */
-void prim_unsafeGetAttrPos(EvalState& state, const Pos& pos, Value** args,
-                           Value& v) {
-  std::string attr = state.forceStringNoCtx(*args[0], pos);
-  state.forceAttrs(*args[1], pos);
-  Bindings::iterator i = args[1]->attrs->find(state.symbols.Create(attr));
-  if (i == args[1]->attrs->end()) {
-    mkNull(v);
-  } else {
-    state.mkPos(v, i->second.pos);
-  }
-}
-
-/* Dynamic version of the `?' operator. */
-static void prim_hasAttr(EvalState& state, const Pos& pos, Value** args,
-                         Value& v) {
-  std::string attr = state.forceStringNoCtx(*args[0], pos);
-  state.forceAttrs(*args[1], pos);
-  mkBool(v, args[1]->attrs->find(state.symbols.Create(attr)) !=
-                args[1]->attrs->end());
-}
-
-/* Determine whether the argument is a set. */
-static void prim_isAttrs(EvalState& state, const Pos& pos, Value** args,
-                         Value& v) {
-  state.forceValue(*args[0]);
-  mkBool(v, args[0]->type == tAttrs);
-}
-
-static void prim_removeAttrs(EvalState& state, const Pos& pos, Value** args,
-                             Value& v) {
-  state.forceAttrs(*args[0], pos);
-  state.forceList(*args[1], pos);
-
-  /* Get the attribute names to be removed. */
-  std::set<Symbol> names;
-  for (unsigned int i = 0; i < args[1]->listSize(); ++i) {
-    state.forceStringNoCtx(*(*args[1]->list)[i], pos);
-    names.insert(state.symbols.Create((*args[1]->list)[i]->string.s));
-  }
-
-  /* Copy all attributes not in that set.  Note that we don't need
-     to sort v.attrs because it's a subset of an already sorted
-     vector. */
-  state.mkAttrs(v, args[0]->attrs->size());
-  for (auto& i : *args[0]->attrs) {
-    if (names.find(i.second.name) == names.end()) {
-      v.attrs->push_back(i.second);
-    }
-  }
-}
-
-/* Builds a set from a list specifying (name, value) pairs.  To be
-   precise, a list [{name = "name1"; value = value1;} ... {name =
-   "nameN"; value = valueN;}] is transformed to {name1 = value1;
-   ... nameN = valueN;}.  In case of duplicate occurences of the same
-   name, the first takes precedence. */
-static void prim_listToAttrs(EvalState& state, const Pos& pos, Value** args,
-                             Value& v) {
-  state.forceList(*args[0], pos);
-
-  state.mkAttrs(v, args[0]->listSize());
-
-  std::set<Symbol> seen;
-
-  for (unsigned int i = 0; i < args[0]->listSize(); ++i) {
-    Value& v2(*(*args[0]->list)[i]);
-    state.forceAttrs(v2, pos);
-
-    Bindings::iterator j = v2.attrs->find(state.sName);
-    if (j == v2.attrs->end()) {
-      throw TypeError(
-          format(
-              "'name' attribute missing in a call to 'listToAttrs', at %1%") %
-          pos);
-    }
-    std::string name = state.forceStringNoCtx(*j->second.value, pos);
-
-    Symbol sym = state.symbols.Create(name);
-    if (seen.find(sym) == seen.end()) {
-      Bindings::iterator j2 =
-          // TODO(tazjin): this line used to construct the symbol again:
-          // state.symbols.Create(state.sValue));
-          // Why?
-          v2.attrs->find(state.sValue);
-      if (j2 == v2.attrs->end()) {
-        throw TypeError(format("'value' attribute missing in a call to "
-                               "'listToAttrs', at %1%") %
-                        pos);
-      }
-
-      v.attrs->push_back(Attr(sym, j2->second.value, j2->second.pos));
-      seen.insert(sym);
-    }
-  }
-}
-
-/* Return the right-biased intersection of two sets as1 and as2,
-   i.e. a set that contains every attribute from as2 that is also a
-   member of as1. */
-static void prim_intersectAttrs(EvalState& state, const Pos& pos, Value** args,
-                                Value& v) {
-  state.forceAttrs(*args[0], pos);
-  state.forceAttrs(*args[1], pos);
-
-  state.mkAttrs(v, std::min(args[0]->attrs->size(), args[1]->attrs->size()));
-
-  for (auto& i : *args[0]->attrs) {
-    Bindings::iterator j = args[1]->attrs->find(i.second.name);
-    if (j != args[1]->attrs->end()) {
-      v.attrs->push_back(j->second);
-    }
-  }
-}
-
-/* Collect each attribute named `attr' from a list of attribute sets.
-   Sets that don't contain the named attribute are ignored.
-
-   Example:
-     catAttrs "a" [{a = 1;} {b = 0;} {a = 2;}]
-     => [1 2]
-*/
-static void prim_catAttrs(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  Symbol attrName = state.symbols.Create(state.forceStringNoCtx(*args[0], pos));
-  state.forceList(*args[1], pos);
-
-  Value* res[args[1]->listSize()];
-  unsigned int found = 0;
-
-  for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
-    Value& v2(*(*args[1]->list)[n]);
-    state.forceAttrs(v2, pos);
-    Bindings::iterator i = v2.attrs->find(attrName);
-    if (i != v2.attrs->end()) {
-      res[found++] = i->second.value;
-    }
-  }
-
-  state.mkList(v, found);
-  for (unsigned int n = 0; n < found; ++n) {
-    (*v.list)[n] = res[n];
-  }
-}
-
-/* Return a set containing the names of the formal arguments expected
-   by the function `f'.  The value of each attribute is a Boolean
-   denoting whether the corresponding argument has a default value.  For
-   instance,
-
-      functionArgs ({ x, y ? 123}: ...)
-   => { x = false; y = true; }
-
-   "Formal argument" here refers to the attributes pattern-matched by
-   the function.  Plain lambdas are not included, e.g.
-
-      functionArgs (x: ...)
-   => { }
-*/
-static void prim_functionArgs(EvalState& state, const Pos& pos, Value** args,
-                              Value& v) {
-  state.forceValue(*args[0]);
-  if (args[0]->type != tLambda) {
-    throw TypeError(format("'functionArgs' requires a function, at %1%") % pos);
-  }
-
-  if (!args[0]->lambda.fun->matchAttrs) {
-    state.mkAttrs(v, 0);
-    return;
-  }
-
-  state.mkAttrs(v, args[0]->lambda.fun->formals->formals.size());
-  for (auto& i : args[0]->lambda.fun->formals->formals) {
-    // !!! should optimise booleans (allocate only once)
-    // TODO(tazjin): figure out what the above comment means
-    mkBool(*state.allocAttr(v, i.name), i.def != nullptr);
-  }
-}
-
-/* Apply a function to every element of an attribute set. */
-static void prim_mapAttrs(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  state.forceAttrs(*args[1], pos);
-
-  state.mkAttrs(v, args[1]->attrs->size());
-
-  for (auto& i : *args[1]->attrs) {
-    Value* vName = state.allocValue();
-    Value* vFun2 = state.allocValue();
-    mkString(*vName, i.second.name);
-    mkApp(*vFun2, *args[0], *vName);
-    mkApp(*state.allocAttr(v, i.second.name), *vFun2, *i.second.value);
-  }
-}
-
-/*************************************************************
- * Lists
- *************************************************************/
-
-/* Determine whether the argument is a list. */
-static void prim_isList(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  state.forceValue(*args[0]);
-  mkBool(v, args[0]->isList());
-}
-
-static void elemAt(EvalState& state, const Pos& pos, Value& list, int n,
-                   Value& v) {
-  state.forceList(list, pos);
-  if (n < 0 || static_cast<unsigned int>(n) >= list.listSize()) {
-    throw Error(format("list index %1% is out of bounds, at %2%") % n % pos);
-  }
-  state.forceValue(*(*list.list)[n]);
-  v = *(*list.list)[n];
-}
-
-/* Return the n-1'th element of a list. */
-static void prim_elemAt(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  elemAt(state, pos, *args[0], state.forceInt(*args[1], pos), v);
-}
-
-/* Return the first element of a list. */
-static void prim_head(EvalState& state, const Pos& pos, Value** args,
-                      Value& v) {
-  elemAt(state, pos, *args[0], 0, v);
-}
-
-/* Return a list consisting of everything but the first element of
-   a list.  Warning: this function takes O(n) time, so you probably
-   don't want to use it!  */
-static void prim_tail(EvalState& state, const Pos& pos, Value** args,
-                      Value& v) {
-  state.forceList(*args[0], pos);
-  if (args[0]->listSize() == 0) {
-    throw Error(format("'tail' called on an empty list, at %1%") % pos);
-  }
-  state.mkList(v, args[0]->listSize() - 1);
-  for (unsigned int n = 0; n < v.listSize(); ++n) {
-    (*v.list)[n] = (*args[0]->list)[n + 1];
-  }
-}
-
-/* Apply a function to every element of a list. */
-static void prim_map(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  state.forceList(*args[1], pos);
-
-  state.mkList(v, args[1]->listSize());
-
-  for (unsigned int n = 0; n < v.listSize(); ++n) {
-    mkApp(*((*v.list)[n] = state.allocValue()), *args[0], *(*args[1]->list)[n]);
-  }
-}
-
-/* Filter a list using a predicate; that is, return a list containing
-   every element from the list for which the predicate function
-   returns true. */
-static void prim_filter(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  state.forceFunction(*args[0], pos);
-  state.forceList(*args[1], pos);
-
-  // FIXME: putting this on the stack is risky.
-  Value* vs[args[1]->listSize()];
-  unsigned int k = 0;
-
-  bool same = true;
-  for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
-    Value res;
-    state.callFunction(*args[0], *(*args[1]->list)[n], res, noPos);
-    if (state.forceBool(res, pos)) {
-      vs[k++] = (*args[1]->list)[n];
-    } else {
-      same = false;
-    }
-  }
-
-  if (same) {
-    v = *args[1];
-  } else {
-    state.mkList(v, k);
-    for (unsigned int n = 0; n < k; ++n) {
-      (*v.list)[n] = vs[n];
-    }
-  }
-}
-
-/* Return true if a list contains a given element. */
-static void prim_elem(EvalState& state, const Pos& pos, Value** args,
-                      Value& v) {
-  bool res = false;
-  state.forceList(*args[1], pos);
-  for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
-    if (state.eqValues(*args[0], *(*args[1]->list)[n])) {
-      res = true;
-      break;
-    }
-  }
-  mkBool(v, res);
-}
-
-/* Concatenate a list of lists. */
-static void prim_concatLists(EvalState& state, const Pos& pos, Value** args,
-                             Value& v) {
-  state.forceList(*args[0], pos);
-  state.concatLists(v, *args[0]->list, pos);
-}
-
-/* Return the length of a list.  This is an O(1) time operation. */
-static void prim_length(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  state.forceList(*args[0], pos);
-  mkInt(v, args[0]->listSize());
-}
-
-/* Reduce a list by applying a binary operator, from left to
-   right. The operator is applied strictly. */
-static void prim_foldlStrict(EvalState& state, const Pos& pos, Value** args,
-                             Value& v) {
-  state.forceFunction(*args[0], pos);
-  state.forceList(*args[2], pos);
-
-  if (args[2]->listSize() != 0u) {
-    Value* vCur = args[1];
-
-    for (unsigned int n = 0; n < args[2]->listSize(); ++n) {
-      Value vTmp;
-      state.callFunction(*args[0], *vCur, vTmp, pos);
-      vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
-      state.callFunction(vTmp, *(*args[2]->list)[n], *vCur, pos);
-    }
-    state.forceValue(v);
-  } else {
-    state.forceValue(*args[1]);
-    v = *args[1];
-  }
-}
-
-static void anyOrAll(bool any, EvalState& state, const Pos& pos, Value** args,
-                     Value& v) {
-  state.forceFunction(*args[0], pos);
-  state.forceList(*args[1], pos);
-
-  Value vTmp;
-  for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
-    state.callFunction(*args[0], *(*args[1]->list)[n], vTmp, pos);
-    bool res = state.forceBool(vTmp, pos);
-    if (res == any) {
-      mkBool(v, any);
-      return;
-    }
-  }
-
-  mkBool(v, !any);
-}
-
-static void prim_any(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  anyOrAll(true, state, pos, args, v);
-}
-
-static void prim_all(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  anyOrAll(false, state, pos, args, v);
-}
-
-static void prim_genList(EvalState& state, const Pos& pos, Value** args,
-                         Value& v) {
-  auto len = state.forceInt(*args[1], pos);
-
-  if (len < 0) {
-    throw EvalError(format("cannot create list of size %1%, at %2%") % len %
-                    pos);
-  }
-
-  state.mkList(v, len);
-
-  for (unsigned int n = 0; n < static_cast<unsigned int>(len); ++n) {
-    Value* arg = state.allocValue();
-    mkInt(*arg, n);
-    mkApp(*((*v.list)[n] = state.allocValue()), *args[0], *arg);
-  }
-}
-
-static void prim_lessThan(EvalState& state, const Pos& pos, Value** args,
-                          Value& v);
-
-static void prim_sort(EvalState& state, const Pos& pos, Value** args,
-                      Value& v) {
-  state.forceFunction(*args[0], pos);
-  state.forceList(*args[1], pos);
-
-  // Copy of the input list which can be sorted in place.
-  v.type = tList;
-  v.list = std::make_shared<NixList>(*args[1]->list);
-
-  std::for_each(v.list->begin(), v.list->end(),
-                [&](Value* val) { state.forceValue(*val); });
-
-  auto comparator = [&](Value* a, Value* b) {
-    /* Optimization: if the comparator is lessThan, bypass
-       callFunction. */
-    if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan) {
-      return CompareValues()(a, b);
-    }
-
-    Value vTmp1{};
-    Value vTmp2{};
-    state.callFunction(*args[0], *a, vTmp1, pos);
-    state.callFunction(vTmp1, *b, vTmp2, pos);
-    return state.forceBool(vTmp2, pos);
-  };
-
-  /* FIXME: std::sort can segfault if the comparator is not a strict
-     weak ordering. What to do? std::stable_sort() seems more
-     resilient, but no guarantees... */
-  std::stable_sort(v.list->begin(), v.list->end(), comparator);
-}
-
-static void prim_partition(EvalState& state, const Pos& pos, Value** args,
-                           Value& v) {
-  state.forceFunction(*args[0], pos);
-  state.forceList(*args[1], pos);
-
-  std::shared_ptr<NixList> right = std::make_shared<NixList>();
-  std::shared_ptr<NixList> wrong = std::make_shared<NixList>();
-
-  for (Value* elem : *args[1]->list) {
-    state.forceValue(*elem, pos);
-
-    Value res;
-    state.callFunction(*args[0], *elem, res, pos);
-    if (state.forceBool(res, pos)) {
-      right->push_back(elem);
-    } else {
-      wrong->push_back(elem);
-    }
-  }
-
-  state.mkAttrs(v, 2);
-
-  Value* vRight = state.allocAttr(v, state.sRight);
-  state.mkList(*vRight, right);
-
-  Value* vWrong = state.allocAttr(v, state.sWrong);
-  state.mkList(*vWrong, wrong);
-}
-
-/* concatMap = f: list: concatLists (map f list); */
-/* C++-version is to avoid allocating `mkApp', call `f' eagerly */
-static void prim_concatMap(EvalState& state, const Pos& pos, Value** args,
-                           Value& v) {
-  state.forceFunction(*args[0], pos);
-  state.forceList(*args[1], pos);
-
-  std::shared_ptr<NixList> outlist = std::make_shared<NixList>();
-
-  for (Value* elem : *args[1]->list) {
-    auto out = state.allocValue();
-    state.callFunction(*args[0], *elem, *out, pos);
-    state.forceList(*out, pos);
-
-    outlist->insert(outlist->end(), out->list->begin(), out->list->end());
-  }
-
-  state.mkList(v, outlist);
-}
-
-/*************************************************************
- * Integer arithmetic
- *************************************************************/
-
-static void prim_add(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  state.forceValue(*args[0], pos);
-  state.forceValue(*args[1], pos);
-  if (args[0]->type == tFloat || args[1]->type == tFloat) {
-    mkFloat(v,
-            state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos));
-  } else {
-    mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos));
-  }
-}
-
-static void prim_sub(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  state.forceValue(*args[0], pos);
-  state.forceValue(*args[1], pos);
-  if (args[0]->type == tFloat || args[1]->type == tFloat) {
-    mkFloat(v,
-            state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos));
-  } else {
-    mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos));
-  }
-}
-
-static void prim_mul(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  state.forceValue(*args[0], pos);
-  state.forceValue(*args[1], pos);
-  if (args[0]->type == tFloat || args[1]->type == tFloat) {
-    mkFloat(v,
-            state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos));
-  } else {
-    mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos));
-  }
-}
-
-static void prim_div(EvalState& state, const Pos& pos, Value** args, Value& v) {
-  state.forceValue(*args[0], pos);
-  state.forceValue(*args[1], pos);
-
-  NixFloat f2 = state.forceFloat(*args[1], pos);
-  if (f2 == 0) {
-    throw EvalError(format("division by zero, at %1%") % pos);
-  }
-
-  if (args[0]->type == tFloat || args[1]->type == tFloat) {
-    mkFloat(v,
-            state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
-  } else {
-    NixInt i1 = state.forceInt(*args[0], pos);
-    NixInt i2 = state.forceInt(*args[1], pos);
-    /* Avoid division overflow as it might raise SIGFPE. */
-    if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1) {
-      throw EvalError(format("overflow in integer division, at %1%") % pos);
-    }
-    mkInt(v, i1 / i2);
-  }
-}
-
-static void prim_bitAnd(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  mkInt(v, state.forceInt(*args[0], pos) & state.forceInt(*args[1], pos));
-}
-
-static void prim_bitOr(EvalState& state, const Pos& pos, Value** args,
-                       Value& v) {
-  mkInt(v, state.forceInt(*args[0], pos) | state.forceInt(*args[1], pos));
-}
-
-static void prim_bitXor(EvalState& state, const Pos& pos, Value** args,
-                        Value& v) {
-  mkInt(v, state.forceInt(*args[0], pos) ^ state.forceInt(*args[1], pos));
-}
-
-static void prim_lessThan(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  state.forceValue(*args[0]);
-  state.forceValue(*args[1]);
-  CompareValues comp;
-  mkBool(v, comp(args[0], args[1]));
-}
-
-/*************************************************************
- * String manipulation
- *************************************************************/
-
-/* 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 void prim_toString(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  PathSet context;
-  std::string s = state.coerceToString(pos, *args[0], context, true, false);
-  mkString(v, s, context);
-}
-
-/* `substring start len str' returns the substring of `str' starting
-   at character position `min(start, stringLength str)' inclusive and
-   ending at `min(start + len, stringLength str)'.  `start' must be
-   non-negative. */
-static void prim_substring(EvalState& state, const Pos& pos, Value** args,
-                           Value& v) {
-  int start = state.forceInt(*args[0], pos);
-  int len = state.forceInt(*args[1], pos);
-  PathSet context;
-  std::string s = state.coerceToString(pos, *args[2], context);
-
-  if (start < 0) {
-    throw EvalError(format("negative start position in 'substring', at %1%") %
-                    pos);
-  }
-
-  mkString(v,
-           static_cast<unsigned int>(start) >= s.size()
-               ? ""
-               : std::string(s, start, len),
-           context);
-}
-
-static void prim_stringLength(EvalState& state, const Pos& pos, Value** args,
-                              Value& v) {
-  PathSet context;
-  std::string s = state.coerceToString(pos, *args[0], context);
-  mkInt(v, s.size());
-}
-
-/* Return the cryptographic hash of a string in base-16. */
-static void prim_hashString(EvalState& state, const Pos& pos, Value** args,
-                            Value& v) {
-  std::string type = state.forceStringNoCtx(*args[0], pos);
-  HashType ht = parseHashType(type);
-  if (ht == htUnknown) {
-    throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
-  }
-
-  PathSet context;  // discarded
-  std::string s = state.forceString(*args[1], context, pos);
-
-  mkString(v, hashString(ht, s).to_string(Base16, false), 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) {
-  auto re = state.forceStringNoCtx(*args[0], pos);
-
-  try {
-    std::regex regex(re, std::regex::extended);
-
-    PathSet context;
-    const std::string str = state.forceString(*args[1], context, pos);
-
-    std::smatch match;
-    if (!std::regex_match(str, match, regex)) {
-      mkNull(v);
-      return;
-    }
-
-    // the first match is the whole string
-    const size_t len = match.size() - 1;
-    state.mkList(v, len);
-    for (size_t i = 0; i < len; ++i) {
-      if (!match[i + 1].matched) {
-        mkNull(*((*v.list)[i] = state.allocValue()));
-      } else {
-        mkString(*((*v.list)[i] = state.allocValue()),
-                 match[i + 1].str().c_str());
-      }
-    }
-
-  } catch (std::regex_error& e) {
-    if (e.code() == std::regex_constants::error_space) {
-      // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
-      throw EvalError("memory limit exceeded by regular expression '%s', at %s",
-                      re, pos);
-    }
-    throw EvalError("invalid regular expression '%s', at %s", re, pos);
-  }
-}
-
-/* Split a std::string with a regular expression, and return a list of the
-   non-matching parts interleaved by the lists of the matching groups. */
-static void prim_split(EvalState& state, const Pos& pos, Value** args,
-                       Value& v) {
-  auto re = state.forceStringNoCtx(*args[0], pos);
-
-  try {
-    std::regex regex(re, std::regex::extended);
-
-    PathSet context;
-    const std::string str = state.forceString(*args[1], context, pos);
-
-    auto begin = std::sregex_iterator(str.begin(), str.end(), regex);
-    auto end = std::sregex_iterator();
-
-    // Any matches results are surrounded by non-matching results.
-    const size_t len = std::distance(begin, end);
-    state.mkList(v, 2 * len + 1);
-    size_t idx = 0;
-    Value* elem;
-
-    if (len == 0) {
-      (*v.list)[idx++] = args[1];
-      return;
-    }
-
-    for (std::sregex_iterator i = begin; i != end; ++i) {
-      assert(idx <= 2 * len + 1 - 3);
-      std::smatch match = *i;
-
-      // Add a string for non-matched characters.
-      elem = (*v.list)[idx++] = state.allocValue();
-      mkString(*elem, match.prefix().str().c_str());
-
-      // Add a list for matched substrings.
-      const size_t slen = match.size() - 1;
-      elem = (*v.list)[idx++] = state.allocValue();
-
-      // Start at 1, beacause the first match is the whole string.
-      state.mkList(*elem, slen);
-      for (size_t si = 0; si < slen; ++si) {
-        if (!match[si + 1].matched) {
-          mkNull(*((*elem->list)[si] = state.allocValue()));
-        } else {
-          mkString(*((*elem->list)[si] = state.allocValue()),
-                   match[si + 1].str().c_str());
-        }
-      }
-
-      // Add a string for non-matched suffix characters.
-      if (idx == 2 * len) {
-        elem = (*v.list)[idx++] = state.allocValue();
-        mkString(*elem, match.suffix().str().c_str());
-      }
-    }
-    assert(idx == 2 * len + 1);
-
-  } catch (std::regex_error& e) {
-    if (e.code() == std::regex_constants::error_space) {
-      // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
-      throw EvalError("memory limit exceeded by regular expression '%s', at %s",
-                      re, pos);
-    }
-    throw EvalError("invalid regular expression '%s', at %s", re, pos);
-  }
-}
-
-static void prim_concatStringSep(EvalState& state, const Pos& pos, Value** args,
-                                 Value& v) {
-  PathSet context;
-
-  auto sep = state.forceString(*args[0], context, pos);
-  state.forceList(*args[1], pos);
-
-  std::string res;
-  res.reserve((args[1]->listSize() + 32) * sep.size());
-  bool first = true;
-
-  for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
-    if (first) {
-      first = false;
-    } else {
-      res += sep;
-    }
-
-    res += state.coerceToString(pos, *(*args[1]->list)[n], context);
-  }
-
-  mkString(v, res, context);
-}
-
-static void prim_replaceStrings(EvalState& state, const Pos& pos, Value** args,
-                                Value& v) {
-  state.forceList(*args[0], pos);
-  state.forceList(*args[1], pos);
-  if (args[0]->listSize() != args[1]->listSize()) {
-    throw EvalError(format("'from' and 'to' arguments to 'replaceStrings' have "
-                           "different lengths, at %1%") %
-                    pos);
-  }
-
-  std::vector<std::string> from;
-  from.reserve(args[0]->listSize());
-  for (unsigned int n = 0; n < args[0]->listSize(); ++n) {
-    from.push_back(state.forceString(*(*args[0]->list)[n], pos));
-  }
-
-  std::vector<std::pair<std::string, PathSet>> to;
-  to.reserve(args[1]->listSize());
-  for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
-    PathSet ctx;
-    auto s = state.forceString(*(*args[1]->list)[n], ctx, pos);
-    to.emplace_back(std::move(s), std::move(ctx));
-  }
-
-  PathSet context;
-  auto s = state.forceString(*args[2], context, pos);
-
-  std::string res;
-  // Loops one past last character to handle the case where 'from' contains an
-  // empty string.
-  for (size_t p = 0; p <= s.size();) {
-    bool found = false;
-    auto i = from.begin();
-    auto j = to.begin();
-    for (; i != from.end(); ++i, ++j) {
-      if (s.compare(p, i->size(), *i) == 0) {
-        found = true;
-        res += j->first;
-        if (i->empty()) {
-          if (p < s.size()) {
-            res += s[p];
-          }
-          p++;
-        } else {
-          p += i->size();
-        }
-        for (auto& path : j->second) {
-          context.insert(path);
-        }
-        j->second.clear();
-        break;
-      }
-    }
-    if (!found) {
-      if (p < s.size()) {
-        res += s[p];
-      }
-      p++;
-    }
-  }
-
-  mkString(v, res, context);
-}
-
-/*************************************************************
- * Versions
- *************************************************************/
-
-static void prim_parseDrvName(EvalState& state, const Pos& pos, Value** args,
-                              Value& v) {
-  std::string name = state.forceStringNoCtx(*args[0], pos);
-  DrvName parsed(name);
-  state.mkAttrs(v, 2);
-  mkString(*state.allocAttr(v, state.sName), parsed.name);
-  mkString(*state.allocAttr(v, state.symbols.Create("version")),
-           parsed.version);
-}
-
-static void prim_compareVersions(EvalState& state, const Pos& pos, Value** args,
-                                 Value& v) {
-  std::string version1 = state.forceStringNoCtx(*args[0], pos);
-  std::string version2 = state.forceStringNoCtx(*args[1], pos);
-  mkInt(v, compareVersions(version1, version2));
-}
-
-static void prim_splitVersion(EvalState& state, const Pos& pos, Value** args,
-                              Value& v) {
-  std::string version = state.forceStringNoCtx(*args[0], pos);
-  auto iter = version.cbegin();
-  Strings components;
-  while (iter != version.cend()) {
-    auto component = nextComponent(iter, version.cend());
-    if (component.empty()) {
-      break;
-    }
-    components.emplace_back(std::move(component));
-  }
-  state.mkList(v, components.size());
-  unsigned int n = 0;
-  for (auto& component : components) {
-    auto listElem = (*v.list)[n++] = state.allocValue();
-    mkString(*listElem, component);
-  }
-}
-
-/*************************************************************
- * Networking
- *************************************************************/
-
-void fetch(EvalState& state, const Pos& pos, Value** args, Value& v,
-           const std::string& who, bool unpack,
-           const std::string& defaultName) {
-  CachedDownloadRequest request("");
-  request.unpack = unpack;
-  request.name = defaultName;
-
-  state.forceValue(*args[0]);
-
-  if (args[0]->type == tAttrs) {
-    state.forceAttrs(*args[0], pos);
-
-    for (auto& attr : *args[0]->attrs) {
-      std::string n(attr.second.name);
-      if (n == "url") {
-        request.uri =
-            state.forceStringNoCtx(*attr.second.value, *attr.second.pos);
-      } else if (n == "sha256") {
-        auto hash_ = Hash::deserialize(
-            state.forceStringNoCtx(*attr.second.value, *attr.second.pos),
-            htSHA256);
-        request.expectedHash = Hash::unwrap_throw(hash_);
-      } else if (n == "name") {
-        request.name =
-            state.forceStringNoCtx(*attr.second.value, *attr.second.pos);
-      } else {
-        throw EvalError(format("unsupported argument '%1%' to '%2%', at %3%") %
-                        attr.second.name % who % attr.second.pos);
-      }
-    }
-
-    if (request.uri.empty()) {
-      throw EvalError(format("'url' argument required, at %1%") % pos);
-    }
-
-  } else {
-    request.uri = state.forceStringNoCtx(*args[0], pos);
-  }
-
-  state.checkURI(request.uri);
-
-  if (evalSettings.pureEval && !request.expectedHash) {
-    throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument",
-                who);
-  }
-
-  auto res = getDownloader()->downloadCached(state.store, request);
-
-  if (state.allowedPaths) {
-    state.allowedPaths->insert(res.path);
-  }
-
-  mkString(v, res.storePath, PathSet({res.storePath}));
-}
-
-static void prim_fetchurl(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  fetch(state, pos, args, v, "fetchurl", false, "");
-}
-
-static void prim_fetchTarball(EvalState& state, const Pos& pos, Value** args,
-                              Value& v) {
-  fetch(state, pos, args, v, "fetchTarball", true, "source");
-}
-
-/*************************************************************
- * Primop registration
- *************************************************************/
-
-RegisterPrimOp::PrimOps* RegisterPrimOp::primOps;
-
-RegisterPrimOp::RegisterPrimOp(const std::string& name, size_t arity,
-                               PrimOpFun fun) {
-  if (primOps == nullptr) {
-    primOps = new PrimOps;
-  }
-  primOps->emplace_back(name, arity, fun);
-}
-
-void EvalState::createBaseEnv() {
-  baseEnv.up = nullptr;
-
-  /* Add global constants such as `true' to the base environment. */
-  Value v;
-
-  /* `builtins' must be first! */
-  mkAttrs(v, 128);
-  addConstant("builtins", v);
-
-  mkBool(v, true);
-  addConstant("true", v);
-
-  mkBool(v, false);
-  addConstant("false", v);
-
-  mkNull(v);
-  addConstant("null", v);
-
-  auto vThrow = addPrimOp("throw", 1, prim_throw);
-
-  auto addPurityError = [&](const std::string& name) {
-    Value* v2 = allocValue();
-    mkString(*v2, fmt("'%s' is not allowed in pure evaluation mode", name));
-    mkApp(v, *vThrow, *v2);
-    addConstant(name, v);
-  };
-
-  if (!evalSettings.pureEval) {
-    mkInt(v, time(nullptr));
-    addConstant("__currentTime", v);
-  }
-
-  if (!evalSettings.pureEval) {
-    mkString(v, settings.thisSystem);
-    addConstant("__currentSystem", v);
-  }
-
-  mkString(v, nixVersion);
-  addConstant("__nixVersion", v);
-
-  mkString(v, store->storeDir);
-  addConstant("__storeDir", v);
-
-  /* Language version.  This should be increased every time a new
-     language feature gets added.  It's not necessary to increase it
-     when primops get added, because you can just use `builtins ?
-     primOp' to check. */
-  mkInt(v, 5);
-  addConstant("__langVersion", v);
-
-  // Miscellaneous
-  auto vScopedImport = addPrimOp("scopedImport", 2, prim_scopedImport);
-  Value* v2 = allocValue();
-  mkAttrs(*v2, 0);
-  mkApp(v, *vScopedImport, *v2);
-  forceValue(v);
-  addConstant("import", v);
-  addPrimOp("__typeOf", 1, prim_typeOf);
-  addPrimOp("isNull", 1, prim_isNull);
-  addPrimOp("__isFunction", 1, prim_isFunction);
-  addPrimOp("__isString", 1, prim_isString);
-  addPrimOp("__isInt", 1, prim_isInt);
-  addPrimOp("__isFloat", 1, prim_isFloat);
-  addPrimOp("__isBool", 1, prim_isBool);
-  addPrimOp("__isPath", 1, prim_isPath);
-  addPrimOp("__genericClosure", 1, prim_genericClosure);
-  addPrimOp("abort", 1, prim_abort);
-  addPrimOp("__addErrorContext", 2, prim_addErrorContext);
-  addPrimOp("__tryEval", 1, prim_tryEval);
-  addPrimOp("__getEnv", 1, prim_getEnv);
-
-  // Strictness
-  addPrimOp("__seq", 2, prim_seq);
-  addPrimOp("__deepSeq", 2, prim_deepSeq);
-
-  // Debugging
-  addPrimOp("__trace", 2, prim_trace);
-  addPrimOp("__valueSize", 1, prim_valueSize);
-
-  // Paths
-  addPrimOp("__toPath", 1, prim_toPath);
-  if (evalSettings.pureEval) {
-    addPurityError("__storePath");
-  } else {
-    addPrimOp("__storePath", 1, prim_storePath);
-  }
-  addPrimOp("__pathExists", 1, prim_pathExists);
-  addPrimOp("baseNameOf", 1, prim_baseNameOf);
-  addPrimOp("dirOf", 1, prim_dirOf);
-  addPrimOp("__readFile", 1, prim_readFile);
-  addPrimOp("__readDir", 1, prim_readDir);
-  addPrimOp("__findFile", 2, prim_findFile);
-  addPrimOp("__hashFile", 2, prim_hashFile);
-
-  // Creating files
-  addPrimOp("__toXML", 1, prim_toXML);
-  addPrimOp("__toJSON", 1, prim_toJSON);
-  addPrimOp("__fromJSON", 1, prim_fromJSON);
-  addPrimOp("__toFile", 2, prim_toFile);
-  addPrimOp("__filterSource", 2, prim_filterSource);
-  addPrimOp("__path", 1, prim_path);
-
-  // Sets
-  addPrimOp("__attrNames", 1, prim_attrNames);
-  addPrimOp("__attrValues", 1, prim_attrValues);
-  addPrimOp("__getAttr", 2, prim_getAttr);
-  addPrimOp("__unsafeGetAttrPos", 2, prim_unsafeGetAttrPos);
-  addPrimOp("__hasAttr", 2, prim_hasAttr);
-  addPrimOp("__isAttrs", 1, prim_isAttrs);
-  addPrimOp("removeAttrs", 2, prim_removeAttrs);
-  addPrimOp("__listToAttrs", 1, prim_listToAttrs);
-  addPrimOp("__intersectAttrs", 2, prim_intersectAttrs);
-  addPrimOp("__catAttrs", 2, prim_catAttrs);
-  addPrimOp("__functionArgs", 1, prim_functionArgs);
-  addPrimOp("__mapAttrs", 2, prim_mapAttrs);
-
-  // Lists
-  addPrimOp("__isList", 1, prim_isList);
-  addPrimOp("__elemAt", 2, prim_elemAt);
-  addPrimOp("__head", 1, prim_head);
-  addPrimOp("__tail", 1, prim_tail);
-  addPrimOp("map", 2, prim_map);
-  addPrimOp("__filter", 2, prim_filter);
-  addPrimOp("__elem", 2, prim_elem);
-  addPrimOp("__concatLists", 1, prim_concatLists);
-  addPrimOp("__length", 1, prim_length);
-  addPrimOp("__foldl'", 3, prim_foldlStrict);
-  addPrimOp("__any", 2, prim_any);
-  addPrimOp("__all", 2, prim_all);
-  addPrimOp("__genList", 2, prim_genList);
-  addPrimOp("__sort", 2, prim_sort);
-  addPrimOp("__partition", 2, prim_partition);
-  addPrimOp("__concatMap", 2, prim_concatMap);
-
-  // Integer arithmetic
-  addPrimOp("__add", 2, prim_add);
-  addPrimOp("__sub", 2, prim_sub);
-  addPrimOp("__mul", 2, prim_mul);
-  addPrimOp("__div", 2, prim_div);
-  addPrimOp("__bitAnd", 2, prim_bitAnd);
-  addPrimOp("__bitOr", 2, prim_bitOr);
-  addPrimOp("__bitXor", 2, prim_bitXor);
-  addPrimOp("__lessThan", 2, prim_lessThan);
-
-  // String manipulation
-  addPrimOp("toString", 1, prim_toString);
-  addPrimOp("__substring", 3, prim_substring);
-  addPrimOp("__stringLength", 1, prim_stringLength);
-  addPrimOp("__hashString", 2, prim_hashString);
-  addPrimOp("__match", 2, prim_match);
-  addPrimOp("__split", 2, prim_split);
-  addPrimOp("__concatStringsSep", 2, prim_concatStringSep);
-  addPrimOp("__replaceStrings", 3, prim_replaceStrings);
-
-  // Versions
-  addPrimOp("__parseDrvName", 1, prim_parseDrvName);
-  addPrimOp("__compareVersions", 2, prim_compareVersions);
-  addPrimOp("__splitVersion", 1, prim_splitVersion);
-
-  // Derivations
-  addPrimOp("derivationStrict", 1, prim_derivationStrict);
-  addPrimOp("placeholder", 1, prim_placeholder);
-
-  // Networking
-  addPrimOp("__fetchurl", 1, prim_fetchurl);
-  addPrimOp("fetchTarball", 1, prim_fetchTarball);
-
-  /* Add a wrapper around the derivation primop that computes the
-     `drvPath' and `outPath' attributes lazily. */
-  std::string path =
-      canonPath(settings.nixDataDir + "/nix/corepkgs/derivation.nix", true);
-  sDerivationNix = symbols.Create(path);
-  evalFile(path, v);
-  addConstant("derivation", v);
-
-  /* Add a value containing the current Nix expression search path. */
-  mkList(v, searchPath.size());
-  int n = 0;
-  for (auto& i : searchPath) {
-    v2 = (*v.list)[n++] = allocValue();
-    mkAttrs(*v2, 2);
-    mkString(*allocAttr(*v2, symbols.Create("path")), i.second);
-    mkString(*allocAttr(*v2, symbols.Create("prefix")), i.first);
-  }
-  addConstant("__nixPath", v);
-
-  if (RegisterPrimOp::primOps != nullptr) {
-    for (auto& primOp : *RegisterPrimOp::primOps) {
-      addPrimOp(std::get<0>(primOp), std::get<1>(primOp), std::get<2>(primOp));
-    }
-  }
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/primops.hh b/third_party/nix/src/libexpr/primops.hh
deleted file mode 100644
index ab5f647202..0000000000
--- a/third_party/nix/src/libexpr/primops.hh
+++ /dev/null
@@ -1,17 +0,0 @@
-#include <tuple>
-#include <vector>
-
-#include "libexpr/eval.hh"
-
-namespace nix {
-
-struct RegisterPrimOp {
-  using PrimOps = std::vector<std::tuple<std::string, size_t, PrimOpFun> >;
-  static PrimOps* primOps;
-  /* You can register a constant by passing an arity of 0. fun
-     will get called during EvalState initialization, so there
-     may be primops not yet added and builtins is not yet sorted. */
-  RegisterPrimOp(const std::string& name, size_t arity, PrimOpFun fun);
-};
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/primops/context.cc b/third_party/nix/src/libexpr/primops/context.cc
deleted file mode 100644
index fb8879ead1..0000000000
--- a/third_party/nix/src/libexpr/primops/context.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-#include "libexpr/eval-inline.hh"
-#include "libexpr/primops.hh"
-#include "libstore/derivations.hh"
-
-namespace nix {
-
-static void prim_unsafeDiscardStringContext(EvalState& state, const Pos& pos,
-                                            Value** args, Value& v) {
-  PathSet context;
-  std::string s = state.coerceToString(pos, *args[0], context);
-  mkString(v, s, PathSet());
-}
-
-static RegisterPrimOp r1("__unsafeDiscardStringContext", 1,
-                         prim_unsafeDiscardStringContext);
-
-static void prim_hasContext(EvalState& state, const Pos& pos, Value** args,
-                            Value& v) {
-  PathSet context;
-  state.forceString(*args[0], context, pos);
-  mkBool(v, !context.empty());
-}
-
-static RegisterPrimOp r2("__hasContext", 1, prim_hasContext);
-
-/* Sometimes we want to pass a derivation path (i.e. pkg.drvPath) to a
-   builder without causing the derivation to be built (for instance,
-   in the derivation that builds NARs in nix-push, when doing
-   source-only deployment).  This primop marks the string context so
-   that builtins.derivation adds the path to drv.inputSrcs rather than
-   drv.inputDrvs. */
-static void prim_unsafeDiscardOutputDependency(EvalState& state, const Pos& pos,
-                                               Value** args, Value& v) {
-  PathSet context;
-  std::string s = state.coerceToString(pos, *args[0], context);
-
-  PathSet context2;
-  for (auto& p : context) {
-    context2.insert(p.at(0) == '=' ? std::string(p, 1) : p);
-  }
-
-  mkString(v, s, context2);
-}
-
-static RegisterPrimOp r3("__unsafeDiscardOutputDependency", 1,
-                         prim_unsafeDiscardOutputDependency);
-
-/* Extract the context of a string as a structured Nix value.
-
-   The context is represented as an attribute set whose keys are the
-   paths in the context set and whose values are attribute sets with
-   the following keys:
-     path: True if the relevant path is in the context as a plain store
-           path (i.e. the kind of context you get when interpolating
-           a Nix path (e.g. ./.) into a string). False if missing.
-     allOutputs: True if the relevant path is a derivation and it is
-                  in the context as a drv file with all of its outputs
-                  (i.e. the kind of context you get when referencing
-                  .drvPath of some derivation). False if missing.
-     outputs: If a non-empty list, the relevant path is a derivation
-              and the provided outputs are referenced in the context
-              (i.e. the kind of context you get when referencing
-              .outPath of some derivation). Empty list if missing.
-   Note that for a given path any combination of the above attributes
-   may be present.
-*/
-static void prim_getContext(EvalState& state, const Pos& pos, Value** args,
-                            Value& v) {
-  struct ContextInfo {
-    bool path = false;
-    bool allOutputs = false;
-    Strings outputs;
-  };
-  PathSet context;
-  state.forceString(*args[0], context, pos);
-  auto contextInfos = std::map<Path, ContextInfo>();
-  for (const auto& p : context) {
-    Path drv;
-    std::string output;
-    const Path* path = &p;
-    if (p.at(0) == '=') {
-      drv = std::string(p, 1);
-      path = &drv;
-    } else if (p.at(0) == '!') {
-      std::pair<std::string, std::string> ctx = decodeContext(p);
-      drv = ctx.first;
-      output = ctx.second;
-      path = &drv;
-    }
-    auto isPath = drv.empty();
-    auto isAllOutputs = (!drv.empty()) && output.empty();
-
-    auto iter = contextInfos.find(*path);
-    if (iter == contextInfos.end()) {
-      contextInfos.emplace(
-          *path,
-          ContextInfo{isPath, isAllOutputs,
-                      output.empty() ? Strings{} : Strings{std::move(output)}});
-    } else {
-      if (isPath) {
-        iter->second.path = true;
-      } else if (isAllOutputs) {
-        iter->second.allOutputs = true;
-      } else {
-        iter->second.outputs.emplace_back(std::move(output));
-      }
-    }
-  }
-
-  state.mkAttrs(v, contextInfos.size());
-
-  auto sPath = state.symbols.Create("path");
-  auto sAllOutputs = state.symbols.Create("allOutputs");
-  for (const auto& info : contextInfos) {
-    auto& infoVal = *state.allocAttr(v, state.symbols.Create(info.first));
-    state.mkAttrs(infoVal, 3);
-    if (info.second.path) {
-      mkBool(*state.allocAttr(infoVal, sPath), true);
-    }
-    if (info.second.allOutputs) {
-      mkBool(*state.allocAttr(infoVal, sAllOutputs), true);
-    }
-    if (!info.second.outputs.empty()) {
-      auto& outputsVal = *state.allocAttr(infoVal, state.sOutputs);
-      state.mkList(outputsVal, info.second.outputs.size());
-      size_t i = 0;
-      for (const auto& output : info.second.outputs) {
-        mkString(*((*outputsVal.list)[i++] = state.allocValue()), output);
-      }
-    }
-  }
-}
-
-static RegisterPrimOp r4("__getContext", 1, prim_getContext);
-
-/* Append the given context to a given string.
-
-   See the commentary above unsafeGetContext for details of the
-   context representation.
-*/
-static void prim_appendContext(EvalState& state, const Pos& pos, Value** args,
-                               Value& v) {
-  PathSet context;
-  auto orig = state.forceString(*args[0], context, pos);
-
-  state.forceAttrs(*args[1], pos);
-
-  auto sPath = state.symbols.Create("path");
-  auto sAllOutputs = state.symbols.Create("allOutputs");
-  for (const auto& attr_iter : *args[1]->attrs) {
-    const Attr* i = &attr_iter.second;  // TODO(tazjin): get rid of this
-    if (!state.store->isStorePath(i->name)) {
-      throw EvalError("Context key '%s' is not a store path, at %s", i->name,
-                      i->pos);
-    }
-    if (!settings.readOnlyMode) {
-      state.store->ensurePath(i->name);
-    }
-    state.forceAttrs(*i->value, *i->pos);
-    auto iter = i->value->attrs->find(sPath);
-    if (iter != i->value->attrs->end()) {
-      if (state.forceBool(*iter->second.value, *iter->second.pos)) {
-        context.insert(i->name);
-      }
-    }
-
-    iter = i->value->attrs->find(sAllOutputs);
-    if (iter != i->value->attrs->end()) {
-      if (state.forceBool(*iter->second.value, *iter->second.pos)) {
-        if (!isDerivation(i->name)) {
-          throw EvalError(
-              "Tried to add all-outputs context of %s, which is not a "
-              "derivation, to a string, at %s",
-              i->name, i->pos);
-        }
-        context.insert("=" + std::string(i->name));
-      }
-    }
-
-    iter = i->value->attrs->find(state.sOutputs);
-    if (iter != i->value->attrs->end()) {
-      state.forceList(*iter->second.value, *iter->second.pos);
-      if (iter->second.value->listSize() && !isDerivation(i->name)) {
-        throw EvalError(
-            "Tried to add derivation output context of %s, which is not a "
-            "derivation, to a string, at %s",
-            i->name, i->pos);
-      }
-      for (unsigned int n = 0; n < iter->second.value->listSize(); ++n) {
-        auto name = state.forceStringNoCtx(*(*iter->second.value->list)[n],
-                                           *iter->second.pos);
-        context.insert("!" + name + "!" + std::string(i->name));
-      }
-    }
-  }
-
-  mkString(v, orig, context);
-}
-
-static RegisterPrimOp r5("__appendContext", 2, prim_appendContext);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/primops/fetchGit.cc b/third_party/nix/src/libexpr/primops/fetchGit.cc
deleted file mode 100644
index 72b498d30b..0000000000
--- a/third_party/nix/src/libexpr/primops/fetchGit.cc
+++ /dev/null
@@ -1,277 +0,0 @@
-#include <nlohmann/json.hpp>
-#include <regex>
-
-#include <absl/strings/ascii.h>
-#include <absl/strings/match.h>
-#include <absl/strings/str_split.h>
-#include <glog/logging.h>
-#include <sys/time.h>
-
-#include "libexpr/eval-inline.hh"
-#include "libexpr/primops.hh"
-#include "libstore/download.hh"
-#include "libstore/pathlocks.hh"
-#include "libstore/store-api.hh"
-#include "libutil/hash.hh"
-
-using namespace std::string_literals;
-
-namespace nix {
-
-struct GitInfo {
-  Path storePath;
-  std::string rev;
-  std::string shortRev;
-  uint64_t revCount = 0;
-};
-
-std::regex revRegex("^[0-9a-fA-F]{40}$");
-
-GitInfo exportGit(ref<Store> store, const std::string& uri,
-                  std::optional<std::string> ref, std::string rev,
-                  const std::string& name) {
-  if (evalSettings.pureEval && rev == "") {
-    throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision");
-  }
-
-  if (!ref && rev == "" && absl::StartsWith(uri, "/") &&
-      pathExists(uri + "/.git")) {
-    bool clean = true;
-
-    try {
-      runProgram("git", true,
-                 {"-C", uri, "diff-index", "--quiet", "HEAD", "--"});
-    } catch (ExecError& e) {
-      if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) {
-        throw;
-      }
-      clean = false;
-    }
-
-    if (!clean) {
-      /* This is an unclean working tree. So copy all tracked
-         files. */
-
-      GitInfo gitInfo;
-      gitInfo.rev = "0000000000000000000000000000000000000000";
-      gitInfo.shortRev = std::string(gitInfo.rev, 0, 7);
-
-      std::set<std::string> files =
-          absl::StrSplit(runProgram("git", true, {"-C", uri, "ls-files", "-z"}),
-                         absl::ByChar('\0'), absl::SkipEmpty());
-
-      PathFilter filter = [&](const Path& p) -> bool {
-        assert(absl::StartsWith(p, uri));
-        std::string file(p, uri.size() + 1);
-
-        auto st = lstat(p);
-
-        if (S_ISDIR(st.st_mode)) {
-          auto prefix = file + "/";
-          auto i = files.lower_bound(prefix);
-          return i != files.end() && absl::StartsWith(*i, prefix);
-        }
-
-        return files.count(file);
-      };
-
-      gitInfo.storePath =
-          store->addToStore("source", uri, true, htSHA256, filter);
-
-      return gitInfo;
-    }
-
-    // clean working tree, but no ref or rev specified.  Use 'HEAD'.
-    rev = absl::StripTrailingAsciiWhitespace(
-        runProgram("git", true, {"-C", uri, "rev-parse", "HEAD"}));
-    ref = "HEAD"s;
-  }
-
-  if (!ref) {
-    ref = "HEAD"s;
-  }
-
-  if (rev != "" && !std::regex_match(rev, revRegex)) {
-    throw Error("invalid Git revision '%s'", rev);
-  }
-
-  deletePath(getCacheDir() + "/nix/git");
-
-  Path cacheDir = getCacheDir() + "/nix/gitv2/" +
-                  hashString(htSHA256, uri).to_string(Base32, false);
-
-  if (!pathExists(cacheDir)) {
-    createDirs(dirOf(cacheDir));
-    runProgram("git", true, {"init", "--bare", cacheDir});
-  }
-
-  Path localRefFile;
-  if (ref->compare(0, 5, "refs/") == 0) {
-    localRefFile = cacheDir + "/" + *ref;
-  } else {
-    localRefFile = cacheDir + "/refs/heads/" + *ref;
-  }
-
-  bool doFetch;
-  time_t now = time(0);
-  /* If a rev was specified, we need to fetch if it's not in the
-     repo. */
-  if (rev != "") {
-    try {
-      runProgram("git", true, {"-C", cacheDir, "cat-file", "-e", rev});
-      doFetch = false;
-    } catch (ExecError& e) {
-      if (WIFEXITED(e.status)) {
-        doFetch = true;
-      } else {
-        throw;
-      }
-    }
-  } else {
-    /* If the local ref is older than ‘tarball-ttl’ seconds, do a
-       git fetch to update the local ref to the remote ref. */
-    struct stat st;
-    doFetch = stat(localRefFile.c_str(), &st) != 0 ||
-              static_cast<uint64_t>(st.st_mtime) + settings.tarballTtl <=
-                  static_cast<uint64_t>(now);
-  }
-  if (doFetch) {
-    DLOG(INFO) << "fetching Git repository '" << uri << "'";
-
-    // FIXME: git stderr messes up our progress indicator, so
-    // we're using --quiet for now. Should process its stderr.
-    runProgram("git", true,
-               {"-C", cacheDir, "fetch", "--quiet", "--force", "--", uri,
-                fmt("%s:%s", *ref, *ref)});
-
-    struct timeval times[2];
-    times[0].tv_sec = now;
-    times[0].tv_usec = 0;
-    times[1].tv_sec = now;
-    times[1].tv_usec = 0;
-
-    utimes(localRefFile.c_str(), times);
-  }
-
-  // FIXME: check whether rev is an ancestor of ref.
-  GitInfo gitInfo;
-  gitInfo.rev =
-      rev != "" ? rev
-                : absl::StripTrailingAsciiWhitespace(readFile(localRefFile));
-  gitInfo.shortRev = std::string(gitInfo.rev, 0, 7);
-
-  DLOG(INFO) << "using revision " << gitInfo.rev << " of repo '" << uri << "'";
-
-  std::string storeLinkName =
-      hashString(htSHA512, name + std::string("\0"s) + gitInfo.rev)
-          .to_string(Base32, false);
-  Path storeLink = cacheDir + "/" + storeLinkName + ".link";
-  PathLocks storeLinkLock({storeLink}, fmt("waiting for lock on '%1%'...",
-                                           storeLink));  // FIXME: broken
-
-  try {
-    auto json = nlohmann::json::parse(readFile(storeLink));
-
-    assert(json["name"] == name && json["rev"] == gitInfo.rev);
-
-    gitInfo.storePath = json["storePath"];
-
-    if (store->isValidPath(gitInfo.storePath)) {
-      gitInfo.revCount = json["revCount"];
-      return gitInfo;
-    }
-
-  } catch (SysError& e) {
-    if (e.errNo != ENOENT) {
-      throw;
-    }
-  }
-
-  // FIXME: should pipe this, or find some better way to extract a
-  // revision.
-  auto tar = runProgram("git", true, {"-C", cacheDir, "archive", gitInfo.rev});
-
-  Path tmpDir = createTempDir();
-  AutoDelete delTmpDir(tmpDir, true);
-
-  runProgram("tar", true, {"x", "-C", tmpDir}, tar);
-
-  gitInfo.storePath = store->addToStore(name, tmpDir);
-
-  gitInfo.revCount = std::stoull(runProgram(
-      "git", true, {"-C", cacheDir, "rev-list", "--count", gitInfo.rev}));
-
-  nlohmann::json json;
-  json["storePath"] = gitInfo.storePath;
-  json["uri"] = uri;
-  json["name"] = name;
-  json["rev"] = gitInfo.rev;
-  json["revCount"] = gitInfo.revCount;
-
-  writeFile(storeLink, json.dump());
-
-  return gitInfo;
-}
-
-static void prim_fetchGit(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  std::string url;
-  std::optional<std::string> ref;
-  std::string rev;
-  std::string name = "source";
-  PathSet context;
-
-  state.forceValue(*args[0]);
-
-  if (args[0]->type == tAttrs) {
-    state.forceAttrs(*args[0], pos);
-
-    for (auto& attr_iter : *args[0]->attrs) {
-      auto& attr = attr_iter.second;
-      std::string n(attr.name);
-      if (n == "url") {
-        url =
-            state.coerceToString(*attr.pos, *attr.value, context, false, false);
-      } else if (n == "ref") {
-        ref = state.forceStringNoCtx(*attr.value, *attr.pos);
-      } else if (n == "rev") {
-        rev = state.forceStringNoCtx(*attr.value, *attr.pos);
-      } else if (n == "name") {
-        name = state.forceStringNoCtx(*attr.value, *attr.pos);
-      } else {
-        throw EvalError("unsupported argument '%s' to 'fetchGit', at %s",
-                        attr.name, *attr.pos);
-      }
-    }
-
-    if (url.empty()) {
-      throw EvalError(format("'url' argument required, at %1%") % pos);
-    }
-
-  } else {
-    url = state.coerceToString(pos, *args[0], context, false, false);
-  }
-
-  // FIXME: git externals probably can be used to bypass the URI
-  // whitelist. Ah well.
-  state.checkURI(url);
-
-  auto gitInfo = exportGit(state.store, url, ref, rev, name);
-
-  state.mkAttrs(v, 8);
-  mkString(*state.allocAttr(v, state.sOutPath), gitInfo.storePath,
-           PathSet({gitInfo.storePath}));
-  mkString(*state.allocAttr(v, state.symbols.Create("rev")), gitInfo.rev);
-  mkString(*state.allocAttr(v, state.symbols.Create("shortRev")),
-           gitInfo.shortRev);
-  mkInt(*state.allocAttr(v, state.symbols.Create("revCount")),
-        gitInfo.revCount);
-
-  if (state.allowedPaths) {
-    state.allowedPaths->insert(state.store->toRealPath(gitInfo.storePath));
-  }
-}
-
-static RegisterPrimOp r("fetchGit", 1, prim_fetchGit);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/primops/fetchMercurial.cc b/third_party/nix/src/libexpr/primops/fetchMercurial.cc
deleted file mode 100644
index 13dc61766f..0000000000
--- a/third_party/nix/src/libexpr/primops/fetchMercurial.cc
+++ /dev/null
@@ -1,246 +0,0 @@
-#include <nlohmann/json.hpp>
-#include <regex>
-
-#include <absl/strings/ascii.h>
-#include <absl/strings/match.h>
-#include <absl/strings/str_split.h>
-#include <glog/logging.h>
-#include <sys/time.h>
-
-#include "libexpr/eval-inline.hh"
-#include "libexpr/primops.hh"
-#include "libstore/download.hh"
-#include "libstore/pathlocks.hh"
-#include "libstore/store-api.hh"
-
-using namespace std::string_literals;
-
-namespace nix {
-
-struct HgInfo {
-  Path storePath;
-  std::string branch;
-  std::string rev;
-  uint64_t revCount = 0;
-};
-
-std::regex commitHashRegex("^[0-9a-fA-F]{40}$");
-
-HgInfo exportMercurial(ref<Store> store, const std::string& uri,
-                       std::string rev, const std::string& name) {
-  if (evalSettings.pureEval && rev == "") {
-    throw Error(
-        "in pure evaluation mode, 'fetchMercurial' requires a Mercurial "
-        "revision");
-  }
-
-  if (rev == "" && absl::StartsWith(uri, "/") && pathExists(uri + "/.hg")) {
-    bool clean = runProgram("hg", true,
-                            {"status", "-R", uri, "--modified", "--added",
-                             "--removed"}) == "";
-
-    if (!clean) {
-      /* This is an unclean working tree. So copy all tracked
-         files. */
-
-      DLOG(INFO) << "copying unclean Mercurial working tree '" << uri << "'";
-
-      HgInfo hgInfo;
-      hgInfo.rev = "0000000000000000000000000000000000000000";
-      hgInfo.branch = absl::StripTrailingAsciiWhitespace(
-          runProgram("hg", true, {"branch", "-R", uri}));
-
-      std::set<std::string> files = absl::StrSplit(
-          runProgram("hg", true,
-                     {"status", "-R", uri, "--clean", "--modified", "--added",
-                      "--no-status", "--print0"}),
-          absl::ByChar('\0'), absl::SkipEmpty());
-
-      PathFilter filter = [&](const Path& p) -> bool {
-        assert(absl::StartsWith(p, uri));
-        std::string file(p, uri.size() + 1);
-
-        auto st = lstat(p);
-
-        if (S_ISDIR(st.st_mode)) {
-          auto prefix = file + "/";
-          auto i = files.lower_bound(prefix);
-          return i != files.end() && absl::StartsWith(*i, prefix);
-        }
-
-        return files.count(file);
-      };
-
-      hgInfo.storePath =
-          store->addToStore("source", uri, true, htSHA256, filter);
-
-      return hgInfo;
-    }
-  }
-
-  if (rev == "") {
-    rev = "default";
-  }
-
-  Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(),
-                      hashString(htSHA256, uri).to_string(Base32, false));
-
-  Path stampFile = fmt("%s/.hg/%s.stamp", cacheDir,
-                       hashString(htSHA512, rev).to_string(Base32, false));
-
-  /* If we haven't pulled this repo less than ‘tarball-ttl’ seconds,
-     do so now. */
-  time_t now = time(0);
-  struct stat st;
-  if (stat(stampFile.c_str(), &st) != 0 ||
-      static_cast<uint64_t>(st.st_mtime) + settings.tarballTtl <=
-          static_cast<uint64_t>(now)) {
-    /* Except that if this is a commit hash that we already have,
-       we don't have to pull again. */
-    if (!(std::regex_match(rev, commitHashRegex) && pathExists(cacheDir) &&
-          runProgram(RunOptions("hg", {"log", "-R", cacheDir, "-r", rev,
-                                       "--template", "1"})
-                         .killStderr(true))
-                  .second == "1")) {
-      DLOG(INFO) << "fetching Mercurial repository '" << uri << "'";
-
-      if (pathExists(cacheDir)) {
-        try {
-          runProgram("hg", true, {"pull", "-R", cacheDir, "--", uri});
-        } catch (ExecError& e) {
-          std::string transJournal = cacheDir + "/.hg/store/journal";
-          /* hg throws "abandoned transaction" error only if this file exists */
-          if (pathExists(transJournal)) {
-            runProgram("hg", true, {"recover", "-R", cacheDir});
-            runProgram("hg", true, {"pull", "-R", cacheDir, "--", uri});
-          } else {
-            throw ExecError(e.status,
-                            fmt("'hg pull' %s", statusToString(e.status)));
-          }
-        }
-      } else {
-        createDirs(dirOf(cacheDir));
-        runProgram("hg", true, {"clone", "--noupdate", "--", uri, cacheDir});
-      }
-    }
-
-    writeFile(stampFile, "");
-  }
-
-  std::vector<std::string> tokens =
-      absl::StrSplit(runProgram("hg", true,
-                                {"log", "-R", cacheDir, "-r", rev, "--template",
-                                 "{node} {rev} {branch}"}),
-                     absl::ByAnyChar(" \t\n\r"), absl::SkipEmpty());
-  assert(tokens.size() == 3);
-
-  HgInfo hgInfo;
-  hgInfo.rev = tokens[0];
-  hgInfo.revCount = std::stoull(tokens[1]);
-  hgInfo.branch = tokens[2];
-
-  std::string storeLinkName =
-      hashString(htSHA512, name + std::string("\0"s) + hgInfo.rev)
-          .to_string(Base32, false);
-  Path storeLink = fmt("%s/.hg/%s.link", cacheDir, storeLinkName);
-
-  try {
-    auto json = nlohmann::json::parse(readFile(storeLink));
-
-    assert(json["name"] == name && json["rev"] == hgInfo.rev);
-
-    hgInfo.storePath = json["storePath"];
-
-    if (store->isValidPath(hgInfo.storePath)) {
-      DLOG(INFO) << "using cached Mercurial store path '" << hgInfo.storePath
-                 << "'";
-      return hgInfo;
-    }
-
-  } catch (SysError& e) {
-    if (e.errNo != ENOENT) {
-      throw;
-    }
-  }
-
-  Path tmpDir = createTempDir();
-  AutoDelete delTmpDir(tmpDir, true);
-
-  runProgram("hg", true, {"archive", "-R", cacheDir, "-r", rev, tmpDir});
-
-  deletePath(tmpDir + "/.hg_archival.txt");
-
-  hgInfo.storePath = store->addToStore(name, tmpDir);
-
-  nlohmann::json json;
-  json["storePath"] = hgInfo.storePath;
-  json["uri"] = uri;
-  json["name"] = name;
-  json["branch"] = hgInfo.branch;
-  json["rev"] = hgInfo.rev;
-  json["revCount"] = hgInfo.revCount;
-
-  writeFile(storeLink, json.dump());
-
-  return hgInfo;
-}
-
-static void prim_fetchMercurial(EvalState& state, const Pos& pos, Value** args,
-                                Value& v) {
-  std::string url;
-  std::string rev;
-  std::string name = "source";
-  PathSet context;
-
-  state.forceValue(*args[0]);
-
-  if (args[0]->type == tAttrs) {
-    state.forceAttrs(*args[0], pos);
-
-    for (auto& attr_iter : *args[0]->attrs) {
-      auto& attr = attr_iter.second;
-      std::string n(attr.name);
-      if (n == "url") {
-        url =
-            state.coerceToString(*attr.pos, *attr.value, context, false, false);
-      } else if (n == "rev") {
-        rev = state.forceStringNoCtx(*attr.value, *attr.pos);
-      } else if (n == "name") {
-        name = state.forceStringNoCtx(*attr.value, *attr.pos);
-      } else {
-        throw EvalError("unsupported argument '%s' to 'fetchMercurial', at %s",
-                        attr.name, *attr.pos);
-      }
-    }
-
-    if (url.empty()) {
-      throw EvalError(format("'url' argument required, at %1%") % pos);
-    }
-
-  } else {
-    url = state.coerceToString(pos, *args[0], context, false, false);
-  }
-
-  // FIXME: git externals probably can be used to bypass the URI
-  // whitelist. Ah well.
-  state.checkURI(url);
-
-  auto hgInfo = exportMercurial(state.store, url, rev, name);
-
-  state.mkAttrs(v, 8);
-  mkString(*state.allocAttr(v, state.sOutPath), hgInfo.storePath,
-           PathSet({hgInfo.storePath}));
-  mkString(*state.allocAttr(v, state.symbols.Create("branch")), hgInfo.branch);
-  mkString(*state.allocAttr(v, state.symbols.Create("rev")), hgInfo.rev);
-  mkString(*state.allocAttr(v, state.symbols.Create("shortRev")),
-           std::string(hgInfo.rev, 0, 12));
-  mkInt(*state.allocAttr(v, state.symbols.Create("revCount")), hgInfo.revCount);
-
-  if (state.allowedPaths) {
-    state.allowedPaths->insert(state.store->toRealPath(hgInfo.storePath));
-  }
-}
-
-static RegisterPrimOp r("fetchMercurial", 1, prim_fetchMercurial);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/primops/fromTOML.cc b/third_party/nix/src/libexpr/primops/fromTOML.cc
deleted file mode 100644
index e3d2a49407..0000000000
--- a/third_party/nix/src/libexpr/primops/fromTOML.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-#include "cpptoml/cpptoml.h"
-#include "libexpr/eval-inline.hh"
-#include "libexpr/primops.hh"
-
-namespace nix {
-
-static void prim_fromTOML(EvalState& state, const Pos& pos, Value** args,
-                          Value& v) {
-  using namespace cpptoml;
-
-  auto toml = state.forceStringNoCtx(*args[0], pos);
-
-  std::istringstream tomlStream(toml);
-
-  std::function<void(Value&, std::shared_ptr<base>)> visit;
-
-  visit = [&](Value& v, std::shared_ptr<base> t) {
-    if (auto t2 = t->as_table()) {
-      size_t size = 0;
-      for (auto& i : *t2) {
-        (void)i;
-        size++;
-      }
-
-      state.mkAttrs(v, size);
-
-      for (auto& i : *t2) {
-        auto& v2 = *state.allocAttr(v, state.symbols.Create(i.first));
-
-        if (auto i2 = i.second->as_table_array()) {
-          size_t size2 = i2->get().size();
-          state.mkList(v2, size2);
-          for (size_t j = 0; j < size2; ++j) {
-            visit(*((*v2.list)[j] = state.allocValue()), i2->get()[j]);
-          }
-        } else {
-          visit(v2, i.second);
-        }
-      }
-    }
-
-    else if (auto t2 = t->as_array()) {
-      size_t size = t2->get().size();
-
-      state.mkList(v, size);
-
-      for (size_t i = 0; i < size; ++i) {
-        visit(*((*v.list)[i] = state.allocValue()), t2->get()[i]);
-      }
-    }
-
-    // Handle cases like 'a = [[{ a = true }]]', which IMHO should be
-    // parsed as a array containing an array containing a table,
-    // but instead are parsed as an array containing a table array
-    // containing a table.
-    else if (auto t2 = t->as_table_array()) {
-      size_t size = t2->get().size();
-
-      state.mkList(v, size);
-
-      for (size_t j = 0; j < size; ++j) {
-        visit(*((*v.list)[j] = state.allocValue()), t2->get()[j]);
-      }
-    }
-
-    else if (t->is_value()) {
-      if (auto val = t->as<int64_t>()) {
-        mkInt(v, val->get());
-      } else if (auto val = t->as<NixFloat>()) {
-        mkFloat(v, val->get());
-      } else if (auto val = t->as<bool>()) {
-        mkBool(v, val->get());
-      } else if (auto val = t->as<std::string>()) {
-        mkString(v, val->get());
-      } else {
-        throw EvalError("unsupported value type in TOML");
-      }
-    }
-
-    else {
-      abort();
-    }
-  };
-
-  try {
-    visit(v, parser(tomlStream).parse());
-  } catch (std::runtime_error& e) {
-    throw EvalError("while parsing a TOML string at %s: %s", pos, e.what());
-  }
-}
-
-static RegisterPrimOp r("fromTOML", 1, prim_fromTOML);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/symbol-table.cc b/third_party/nix/src/libexpr/symbol-table.cc
deleted file mode 100644
index 2b27ca54c2..0000000000
--- a/third_party/nix/src/libexpr/symbol-table.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-#include "libexpr/symbol-table.hh"
-
-#include <absl/container/node_hash_set.h>
-#include <absl/strings/string_view.h>
-
-namespace nix {
-
-Symbol SymbolTable::Create(absl::string_view sym) {
-  auto it = symbols_.emplace(sym);
-  const std::string* ptr = &(*it.first);
-  return Symbol(ptr);
-}
-
-size_t SymbolTable::Size() const { return symbols_.size(); }
-
-size_t SymbolTable::TotalSize() const {
-  size_t n = 0;
-  for (auto& i : symbols_) {
-    n += i.size();
-  }
-  return n;
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/symbol-table.hh b/third_party/nix/src/libexpr/symbol-table.hh
deleted file mode 100644
index c259965885..0000000000
--- a/third_party/nix/src/libexpr/symbol-table.hh
+++ /dev/null
@@ -1,69 +0,0 @@
-#pragma once
-
-#include <absl/container/node_hash_set.h>
-#include <absl/strings/string_view.h>
-
-namespace nix {  // TODO(tazjin): ::expr
-
-// TODO(tazjin): Replace with a simpler struct, or get rid of.
-class Symbol {
- private:
-  const std::string* s;  // pointer into SymbolTable
-  Symbol(const std::string* s) : s(s){};
-  friend class SymbolTable;
-
- public:
-  bool operator==(const Symbol& s2) const { return s == s2.s; }
-
-  bool operator!=(const Symbol& s2) const { return s != s2.s; }
-
-  bool operator<(const Symbol& s2) const { return *s < *s2.s; }
-
-  operator const std::string&() const { return *s; }
-
-  bool set() const { return s; }
-
-  bool empty() const { return s->empty(); }
-
-  friend std::ostream& operator<<(std::ostream& str, const Symbol& sym);
-
-  template <typename H>
-  friend H AbslHashValue(H h, const Symbol& c) {
-    return H::combine(std::move(h), c.s);
-  }
-};
-
-// SymbolTable is a hash-set based symbol-interning mechanism.
-//
-// TODO(tazjin): Figure out which things use this. AttrSets, ...?
-// Is it possible this only exists because AttrSet wasn't a map?
-//
-// Original comment:
-//
-// Symbol table used by the parser and evaluator to represent and look
-// up identifiers and attributes efficiently. SymbolTable::create()
-// converts a string into a symbol. Symbols have the property that
-// they can be compared efficiently (using a pointer equality test),
-// because the symbol table stores only one copy of each string.
-class SymbolTable {
- public:
-  // Create a new symbol in this table by emplacing the provided
-  // string into it.
-  //
-  // The symbol will reference an existing symbol if the symbol is
-  // already interned.
-  Symbol Create(absl::string_view sym);
-
-  // Return the number of symbols interned.
-  size_t Size() const;
-
-  // Return the total size (in bytes)
-  size_t TotalSize() const;
-
- private:
-  // flat_hash_set does not retain pointer stability on rehashing,
-  // hence "interned" strings/symbols are stored on the heap.
-  absl::node_hash_set<std::string> symbols_;
-};
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/value-to-json.cc b/third_party/nix/src/libexpr/value-to-json.cc
deleted file mode 100644
index a338d4eed7..0000000000
--- a/third_party/nix/src/libexpr/value-to-json.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-#include "libexpr/value-to-json.hh"
-
-#include <cstdlib>
-#include <iomanip>
-
-#include "libexpr/eval-inline.hh"
-#include "libutil/json.hh"
-#include "libutil/util.hh"
-
-namespace nix {
-
-void printValueAsJSON(EvalState& state, bool strict, Value& v,
-                      JSONPlaceholder& out, PathSet& context) {
-  checkInterrupt();
-
-  if (strict) {
-    state.forceValue(v);
-  }
-
-  switch (v.type) {
-    case tInt:
-      out.write(v.integer);
-      break;
-
-    case tBool:
-      out.write(v.boolean);
-      break;
-
-    case tString:
-      copyContext(v, context);
-      out.write(v.string.s);
-      break;
-
-    case tPath:
-      out.write(state.copyPathToStore(context, v.path));
-      break;
-
-    case tNull:
-      out.write(nullptr);
-      break;
-
-    case tAttrs: {
-      auto maybeString =
-          state.tryAttrsToString(noPos, v, context, false, false);
-      if (maybeString) {
-        out.write(*maybeString);
-        break;
-      }
-      auto i = v.attrs->find(state.sOutPath);
-      if (i == v.attrs->end()) {
-        auto obj(out.object());
-        StringSet names;
-        for (auto& j : *v.attrs) {
-          names.insert(j.second.name);
-        }
-        for (auto& j : names) {
-          auto [_, a] = *v.attrs->find(state.symbols.Create(j));
-          auto placeholder(obj.placeholder(j));
-          printValueAsJSON(state, strict, *a.value, placeholder, context);
-        }
-      } else {
-        printValueAsJSON(state, strict, *i->second.value, out, context);
-      }
-      break;
-    }
-
-    case tList: {
-      auto list(out.list());
-      for (unsigned int n = 0; n < v.listSize(); ++n) {
-        auto placeholder(list.placeholder());
-        printValueAsJSON(state, strict, *(*v.list)[n], placeholder, context);
-      }
-      break;
-    }
-
-    case tFloat:
-      out.write(v.fpoint);
-      break;
-
-    default:
-      throw TypeError(format("cannot convert %1% to JSON") % showType(v));
-  }
-}
-
-void printValueAsJSON(EvalState& state, bool strict, Value& v,
-                      std::ostream& str, PathSet& context) {
-  JSONPlaceholder out(str);
-  printValueAsJSON(state, strict, v, out, context);
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/value-to-json.hh b/third_party/nix/src/libexpr/value-to-json.hh
deleted file mode 100644
index 294d776045..0000000000
--- a/third_party/nix/src/libexpr/value-to-json.hh
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include <map>
-#include <string>
-
-#include "libexpr/eval.hh"
-#include "libexpr/nixexpr.hh"
-
-namespace nix {
-
-class JSONPlaceholder;
-
-void printValueAsJSON(EvalState& state, bool strict, Value& v,
-                      JSONPlaceholder& out, PathSet& context);
-
-void printValueAsJSON(EvalState& state, bool strict, Value& v,
-                      std::ostream& str, PathSet& context);
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/value-to-xml.cc b/third_party/nix/src/libexpr/value-to-xml.cc
deleted file mode 100644
index 921973881f..0000000000
--- a/third_party/nix/src/libexpr/value-to-xml.cc
+++ /dev/null
@@ -1,184 +0,0 @@
-#include "libexpr/value-to-xml.hh"
-
-#include <cstdlib>
-
-#include "libexpr/eval-inline.hh"
-#include "libutil/util.hh"
-#include "libutil/xml-writer.hh"
-
-namespace nix {
-
-static XMLAttrs singletonAttrs(const std::string& name,
-                               const std::string& value) {
-  XMLAttrs attrs;
-  attrs[name] = value;
-  return attrs;
-}
-
-static void printValueAsXML(EvalState& state, bool strict, bool location,
-                            Value& v, XMLWriter& doc, PathSet& context,
-                            PathSet& drvsSeen);
-
-static void posToXML(XMLAttrs& xmlAttrs, const Pos& pos) {
-  xmlAttrs["path"] = pos.file.value();
-  xmlAttrs["line"] = (format("%1%") % pos.line).str();
-  xmlAttrs["column"] = (format("%1%") % pos.column).str();
-}
-
-static void showAttrs(EvalState& state, bool strict, bool location,
-                      Bindings& attrs, XMLWriter& doc, PathSet& context,
-                      PathSet& drvsSeen) {
-  StringSet names;
-
-  for (auto& i : attrs) {
-    names.insert(i.second.name);
-  }
-
-  for (auto& i : names) {
-    auto& [_, a] = *attrs.find(state.symbols.Create(i));
-
-    XMLAttrs xmlAttrs;
-    xmlAttrs["name"] = i;
-    if (location && a.pos != &noPos) {
-      posToXML(xmlAttrs, *a.pos);
-    }
-
-    XMLOpenElement elem(doc, "attr", xmlAttrs);
-    printValueAsXML(state, strict, location, *a.value, doc, context, drvsSeen);
-  }
-}
-
-static void printValueAsXML(EvalState& state, bool strict, bool location,
-                            Value& v, XMLWriter& doc, PathSet& context,
-                            PathSet& drvsSeen) {
-  checkInterrupt();
-
-  if (strict) {
-    state.forceValue(v);
-  }
-
-  switch (v.type) {
-    case tInt:
-      doc.writeEmptyElement(
-          "int", singletonAttrs("value", (format("%1%") % v.integer).str()));
-      break;
-
-    case tBool:
-      doc.writeEmptyElement(
-          "bool", singletonAttrs("value", v.boolean ? "true" : "false"));
-      break;
-
-    case tString:
-      /* !!! show the context? */
-      copyContext(v, context);
-      doc.writeEmptyElement("string", singletonAttrs("value", v.string.s));
-      break;
-
-    case tPath:
-      doc.writeEmptyElement("path", singletonAttrs("value", v.path));
-      break;
-
-    case tNull:
-      doc.writeEmptyElement("null");
-      break;
-
-    case tAttrs:
-      if (state.isDerivation(v)) {
-        XMLAttrs xmlAttrs;
-
-        Bindings::iterator a =
-            v.attrs->find(state.symbols.Create("derivation"));
-
-        Path drvPath;
-        a = v.attrs->find(state.sDrvPath);
-        if (a != v.attrs->end()) {
-          if (strict) {
-            state.forceValue(*a->second.value);
-          }
-          if (a->second.value->type == tString) {
-            xmlAttrs["drvPath"] = drvPath = a->second.value->string.s;
-          }
-        }
-
-        a = v.attrs->find(state.sOutPath);
-        if (a != v.attrs->end()) {
-          if (strict) {
-            state.forceValue(*a->second.value);
-          }
-          if (a->second.value->type == tString) {
-            xmlAttrs["outPath"] = a->second.value->string.s;
-          }
-        }
-
-        XMLOpenElement _(doc, "derivation", xmlAttrs);
-
-        if (!drvPath.empty() && drvsSeen.find(drvPath) == drvsSeen.end()) {
-          drvsSeen.insert(drvPath);
-          showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
-        } else {
-          doc.writeEmptyElement("repeated");
-        }
-      }
-
-      else {
-        XMLOpenElement _(doc, "attrs");
-        showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
-      }
-
-      break;
-
-    case tList: {
-      XMLOpenElement _(doc, "list");
-      for (unsigned int n = 0; n < v.listSize(); ++n) {
-        printValueAsXML(state, strict, location, *(*v.list)[n], doc, context,
-                        drvsSeen);
-      }
-      break;
-    }
-
-    case tLambda: {
-      XMLAttrs xmlAttrs;
-      if (location) {
-        posToXML(xmlAttrs, v.lambda.fun->pos);
-      }
-      XMLOpenElement _(doc, "function", xmlAttrs);
-
-      if (v.lambda.fun->matchAttrs) {
-        XMLAttrs attrs;
-        if (!v.lambda.fun->arg.empty()) {
-          attrs["name"] = v.lambda.fun->arg;
-        }
-        if (v.lambda.fun->formals->ellipsis) {
-          attrs["ellipsis"] = "1";
-        }
-        XMLOpenElement _(doc, "attrspat", attrs);
-        for (auto& i : v.lambda.fun->formals->formals) {
-          doc.writeEmptyElement("attr", singletonAttrs("name", i.name));
-        }
-      } else {
-        doc.writeEmptyElement("varpat",
-                              singletonAttrs("name", v.lambda.fun->arg));
-      }
-
-      break;
-    }
-
-    case tFloat:
-      doc.writeEmptyElement(
-          "float", singletonAttrs("value", (format("%1%") % v.fpoint).str()));
-      break;
-
-    default:
-      doc.writeEmptyElement("unevaluated");
-  }
-}
-
-void printValueAsXML(EvalState& state, bool strict, bool location, Value& v,
-                     std::ostream& out, PathSet& context) {
-  XMLWriter doc(true, out);
-  XMLOpenElement root(doc, "expr");
-  PathSet drvsSeen;
-  printValueAsXML(state, strict, location, v, doc, context, drvsSeen);
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/value-to-xml.hh b/third_party/nix/src/libexpr/value-to-xml.hh
deleted file mode 100644
index 18c5279236..0000000000
--- a/third_party/nix/src/libexpr/value-to-xml.hh
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#include <map>
-#include <string>
-
-#include "libexpr/eval.hh"
-#include "libexpr/nixexpr.hh"
-
-namespace nix {
-
-void printValueAsXML(EvalState& state, bool strict, bool location, Value& v,
-                     std::ostream& out, PathSet& context);
-
-}
diff --git a/third_party/nix/src/libexpr/value.cc b/third_party/nix/src/libexpr/value.cc
deleted file mode 100644
index 93fe187478..0000000000
--- a/third_party/nix/src/libexpr/value.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-#include "libexpr/value.hh"
-
-#include <glog/logging.h>
-
-namespace nix {
-
-Value::Value(const Value& copy) { *this = copy; }
-
-Value::Value(Value&& move) { *this = move; }
-
-Value& Value::operator=(const Value& copy) {
-  if (type != copy.type) {
-    memset(this, 0, sizeof(*this));
-  }
-  type = copy.type;
-  switch (type) {
-    case tInt:
-      integer = copy.integer;
-      break;
-    case tBool:
-      boolean = copy.boolean;
-      break;
-    case tString:
-      string = copy.string;
-      break;
-    case tPath:
-      path = copy.path;
-      break;
-    case tNull:
-      /* no fields */
-      break;
-    case tAttrs:
-      attrs = copy.attrs;
-      break;
-    case tList:
-      list = copy.list;
-      break;
-    case tThunk:
-      thunk = copy.thunk;
-      break;
-    case tApp:
-      app = copy.app;
-      break;
-    case tLambda:
-      lambda = copy.lambda;
-      break;
-    case tBlackhole:
-      /* no fields */
-      break;
-    case tPrimOp:
-      primOp = copy.primOp;
-      break;
-    case tPrimOpApp:
-      primOpApp = copy.primOpApp;
-      break;
-    case _reserved1:
-      LOG(FATAL) << "attempted to assign a tExternal value";
-      break;
-    case tFloat:
-      fpoint = copy.fpoint;
-      break;
-  }
-  return *this;
-}
-
-Value& Value::operator=(Value&& move) {
-  if (type != move.type) {
-    memset(this, 0, sizeof(*this));
-  }
-  type = move.type;
-  switch (type) {
-    case tInt:
-      integer = move.integer;
-      break;
-    case tBool:
-      boolean = move.boolean;
-      break;
-    case tString:
-      string = move.string;
-      break;
-    case tPath:
-      path = move.path;
-      break;
-    case tNull:
-      /* no fields */
-      break;
-    case tAttrs:
-      attrs = move.attrs;
-      break;
-    case tList:
-      list = move.list;
-      break;
-    case tThunk:
-      thunk = move.thunk;
-      break;
-    case tApp:
-      app = move.app;
-      break;
-    case tLambda:
-      lambda = move.lambda;
-      break;
-    case tBlackhole:
-      /* no fields */
-      break;
-    case tPrimOp:
-      primOp = move.primOp;
-      break;
-    case tPrimOpApp:
-      primOpApp = move.primOpApp;
-      break;
-    case _reserved1:
-      LOG(FATAL) << "attempted to assign a tExternal value";
-      break;
-    case tFloat:
-      fpoint = move.fpoint;
-      break;
-  }
-  return *this;
-}
-
-}  // namespace nix
diff --git a/third_party/nix/src/libexpr/value.hh b/third_party/nix/src/libexpr/value.hh
deleted file mode 100644
index 82021c77c4..0000000000
--- a/third_party/nix/src/libexpr/value.hh
+++ /dev/null
@@ -1,191 +0,0 @@
-#pragma once
-
-#include <tuple>
-#include <vector>
-
-#include "libexpr/symbol-table.hh"
-#include "libutil/types.hh"
-
-namespace nix {
-
-using ValueType = enum {
-  tInt = 1,
-  tBool,
-  tString,
-  tPath,
-  tNull,
-  tAttrs,
-  tList,
-  tThunk,
-  tApp,
-  tLambda,
-  tBlackhole,
-  tPrimOp,
-  tPrimOpApp,
-  _reserved1,  // formerly tExternal
-  tFloat
-};
-
-class Bindings;
-struct Env;
-struct Expr;
-struct ExprLambda;
-struct PrimOp;
-struct PrimOp;
-class Symbol;
-
-typedef int64_t NixInt;
-typedef double NixFloat;
-
-// Forward declaration of Value is required because the following
-// types are mutually recursive.
-//
-// TODO(tazjin): Really, these types need some serious refactoring.
-struct Value;
-
-/* Strings in the evaluator carry a so-called `context' which
-   is a list of strings representing store paths.  This is to
-   allow users to write things like
-
-   "--with-freetype2-library=" + freetype + "/lib"
-
-   where `freetype' is a derivation (or a source to be copied
-   to the store).  If we just concatenated the strings without
-   keeping track of the referenced store paths, then if the
-   string is used as a derivation attribute, the derivation
-   will not have the correct dependencies in its inputDrvs and
-   inputSrcs.
-
-   The semantics of the context is as follows: when a string
-   with context C is used as a derivation attribute, then the
-   derivations in C will be added to the inputDrvs of the
-   derivation, and the other store paths in C will be added to
-   the inputSrcs of the derivations.
-
-   For canonicity, the store paths should be in sorted order. */
-struct NixString {
-  const char* s;
-  const char** context;  // must be in sorted order
-};
-
-struct NixThunk {
-  Env* env;
-  Expr* expr;
-};
-
-struct NixApp {
-  Value *left, *right;
-};
-
-struct NixLambda {
-  Env* env;
-  ExprLambda* fun;
-};
-
-struct NixPrimOpApp {
-  Value *left, *right;
-};
-
-using NixList = std::vector<Value*>;
-
-struct Value {
-  ValueType type;
-  union {  // TODO(tazjin): std::variant
-    NixInt integer;
-    bool boolean;
-    NixString string;
-    const char* path;
-    std::shared_ptr<Bindings> attrs;
-    std::shared_ptr<NixList> list;
-    NixThunk thunk;
-    NixApp app;  // TODO(tazjin): "app"?
-    NixLambda lambda;
-    std::shared_ptr<PrimOp> primOp;
-    NixPrimOpApp primOpApp;
-    NixFloat fpoint;
-  };
-
-  Value() : type(tInt), attrs(nullptr) {
-    static_assert(offsetof(Value, attrs) + sizeof(attrs) == sizeof(Value));
-  }
-
-  Value(const Value& copy);
-  Value(Value&& move);
-  ~Value() {}
-  Value& operator=(const Value& copy);
-  Value& operator=(Value&& move);
-
-  bool isList() const { return type == tList; }
-
-  size_t listSize() const { return list->size(); }
-};
-
-/* After overwriting an app node, be sure to clear pointers in the
-   Value to ensure that the target isn't kept alive unnecessarily. */
-static inline void clearValue(Value& v) { v.app.left = v.app.right = 0; }
-
-static inline void mkInt(Value& v, NixInt n) {
-  clearValue(v);
-  v.type = tInt;
-  v.integer = n;
-}
-
-static inline void mkFloat(Value& v, NixFloat n) {
-  clearValue(v);
-  v.type = tFloat;
-  v.fpoint = n;
-}
-
-static inline void mkBool(Value& v, bool b) {
-  clearValue(v);
-  v.type = tBool;
-  v.boolean = b;
-}
-
-static inline void mkNull(Value& v) {
-  clearValue(v);
-  v.type = tNull;
-}
-
-static inline void mkApp(Value& v, Value& left, Value& right) {
-  v.type = tApp;
-  v.app.left = &left;
-  v.app.right = &right;
-}
-
-static inline void mkPrimOpApp(Value& v, Value& left, Value& right) {
-  v.type = tPrimOpApp;
-  v.app.left = &left;
-  v.app.right = &right;
-}
-
-static inline void mkStringNoCopy(Value& v, const char* s) {
-  v.type = tString;
-  v.string.s = s;
-  v.string.context = 0;
-}
-
-static inline void mkString(Value& v, const Symbol& s) {
-  mkStringNoCopy(v, ((const std::string&)s).c_str());
-}
-
-void mkString(Value& v, const char* s);
-
-static inline void mkPathNoCopy(Value& v, const char* s) {
-  clearValue(v);
-  v.type = tPath;
-  v.path = s;
-}
-
-void mkPath(Value& v, const char* s);
-
-/* Compute the size in bytes of the given value, including all values
-   and environments reachable from it. Static expressions (Exprs) are
-   not included. */
-size_t valueSize(const Value& v);
-
-using ValueMap = std::map<Symbol, Value*>;
-
-std::shared_ptr<Value*> allocRootValue(Value* v);
-
-}  // namespace nix