diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,22 @@
 # Revision history for zeolite-lang
 
+## 0.4.0.0  -- 2020-05-05
+
+* **[new]** Allows modules to specify custom linker flags and private include
+  paths in `.zeolite-module`. This lets categories written in C++ depend on
+  external libraries written in other languages.
+
+* **[behavior]** Adds optimization of dependency inclusion for categories that
+  are defined in C++ sources. This should eliminate linking in object files that
+  are not needed by the binary.
+
+* **[behavior]** Adds checks to prevent a module from defining a category that
+  was declared in another module.
+
+* **[breaking]** Updates the `.zeolite-module` format to require associating
+  externally-defined categories with the source files that define them. This
+  allows finer-grained linking of binaries and tests.
+
 ## 0.3.0.0  -- 2020-05-03
 
 * **[breaking]** Updates syntax for discarding all returns to use `\` instead of
diff --git a/base/.zeolite-module b/base/.zeolite-module
--- a/base/.zeolite-module
+++ b/base/.zeolite-module
@@ -1,9 +1,27 @@
 root: ".."
 path: "base"
 extra_files: [
+  category_source {
+    source: "base/Category_Bool.cpp"
+    categories: [Bool]
+  }
+  category_source {
+    source: "base/Category_Char.cpp"
+    categories: [Char]
+  }
+  category_source {
+    source: "base/Category_Float.cpp"
+    categories: [Float]
+  }
+  category_source {
+    source: "base/Category_Int.cpp"
+    categories: [Int]
+  }
+  category_source {
+    source: "base/Category_String.cpp"
+    categories: [String]
+  }
   "base/argv.cpp"
-  "base/builtin.cpp"
-  "base/builtin.hpp"
   "base/category-header.hpp"
   "base/category-source.cpp"
   "base/category-source.hpp"
@@ -13,35 +31,8 @@
   "base/logging.hpp"
   "base/types.cpp"
   "base/types.hpp"
-  "capture-thread/include/thread-capture.h"
-  "capture-thread/include/thread-crosser.h"
-  "capture-thread/src/thread-crosser.cc"
-]
-extra_paths: [
-  "base"
-  "capture-thread/include"
-]
-always_include: [
-  AsBool
-  AsChar
-  AsFloat
-  AsInt
-  AsString
-  Bool
-  Char
-  Equals
-  Float
-  Formatted
-  Int
-  LessThan
-  ReadPosition
-  String
-]
-external: [
-  Bool
-  Char
-  Float
-  Int
-  String
+  "base/thread-capture.h"
+  "base/thread-crosser.h"
+  "base/thread-crosser.cc"
 ]
-mode: incremental
+mode: incremental {}
diff --git a/base/Category_Bool.cpp b/base/Category_Bool.cpp
new file mode 100644
--- /dev/null
+++ b/base/Category_Bool.cpp
@@ -0,0 +1,214 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2020 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "Category_Bool.hpp"
+
+#include <map>
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Equals.hpp"
+
+
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+namespace ZEOLITE_DYNAMIC_NAMESPACE {
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+namespace {
+const int collection_Bool = 0;
+}  // namespace
+const void* const Functions_Bool = &collection_Bool;
+namespace {
+class Category_Bool;
+class Type_Bool;
+Type_Bool& CreateType_Bool(Params<0>::Type params);
+class Value_Bool;
+S<TypeValue> CreateValue(Type_Bool& parent, const ParamTuple& params, const ValueTuple& args);
+struct Category_Bool : public TypeCategory {
+  std::string CategoryName() const final { return "Bool"; }
+  Category_Bool() {
+    CycleCheck<Category_Bool>::Check();
+    CycleCheck<Category_Bool> marker(*this);
+    TRACE_FUNCTION("Bool (init @category)")
+  }
+  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Category_Bool::*)(const ParamTuple&, const ValueTuple&);
+    return TypeCategory::Dispatch(label, params, args);
+  }
+};
+Category_Bool& CreateCategory_Bool() {
+  static auto& category = *new Category_Bool();
+  return category;
+}
+struct Type_Bool : public TypeInstance {
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  void BuildTypeName(std::ostream& output) const final {
+    return TypeInstance::TypeNameFrom(output, parent);
+  }
+  Category_Bool& parent;
+  bool CanConvertFrom(const TypeInstance& from) const final {
+    std::vector<const TypeInstance*> args;
+    if (!from.TypeArgsForParent(parent, args)) return false;
+    if(args.size() != 0) {
+      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
+    }
+    return true;
+  }
+  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
+    if (&category == &GetCategory_Bool()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsBool()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsInt()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsFloat()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_Formatted()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    return false;
+  }
+  Type_Bool(Category_Bool& p, Params<0>::Type params) : parent(p) {
+    CycleCheck<Type_Bool>::Check();
+    CycleCheck<Type_Bool> marker(*this);
+    TRACE_FUNCTION("Bool (init @type)")
+  }
+  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Type_Bool::*)(const ParamTuple&, const ValueTuple&);
+    static const CallType Table_Equals[] = {
+      &Type_Bool::Call_equals,
+    };
+    if (label.collection == Functions_Equals) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Equals[label.function_num])(params, args);
+    }
+    return TypeInstance::Dispatch(label, params, args);
+  }
+  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
+};
+Type_Bool& CreateType_Bool(Params<0>::Type params) {
+  static auto& cache = *new InstanceMap<0,Type_Bool>();
+  auto& cached = cache[params];
+  if (!cached) { cached = R_get(new Type_Bool(CreateCategory_Bool(), params)); }
+  return *cached;
+}
+struct Value_Bool : public TypeValue {
+  Value_Bool(Type_Bool& p, bool value) : parent(p), value_(value) {}
+  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
+    using CallType = ReturnTuple(Value_Bool::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
+    static const CallType Table_AsBool[] = {
+      &Value_Bool::Call_asBool,
+    };
+    static const CallType Table_AsFloat[] = {
+      &Value_Bool::Call_asFloat,
+    };
+    static const CallType Table_AsInt[] = {
+      &Value_Bool::Call_asInt,
+    };
+    static const CallType Table_Formatted[] = {
+      &Value_Bool::Call_formatted,
+    };
+    if (label.collection == Functions_AsBool) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsBool[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsFloat) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsFloat[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsInt) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsInt[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_Formatted) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Formatted[label.function_num])(self, params, args);
+    }
+    return TypeValue::Dispatch(self, label, params, args);
+  }
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  bool AsBool() const final { return value_; }
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  Type_Bool& parent;
+  const bool value_;
+};
+ReturnTuple Type_Bool::Call_equals(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Bool.equals")
+  const bool Var_arg1 = (args.At(0))->AsBool();
+  const bool Var_arg2 = (args.At(1))->AsBool();
+  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+}
+ReturnTuple Value_Bool::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Bool.asBool")
+  return ReturnTuple(Var_self);
+}
+ReturnTuple Value_Bool::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Bool.asFloat")
+  return ReturnTuple(Box_Float(value_ ? 1.0 : 0.0));
+}
+ReturnTuple Value_Bool::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Bool.asInt")
+  return ReturnTuple(Box_Int(value_? 1 : 0));
+}
+ReturnTuple Value_Bool::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Bool.formatted")
+  return ReturnTuple(Box_String(value_? "true" : "false"));
+}
+const S<TypeValue>& Var_true = *new S<TypeValue>(new Value_Bool(CreateType_Bool(Params<0>::Type()), true));
+const S<TypeValue>& Var_false = *new S<TypeValue>(new Value_Bool(CreateType_Bool(Params<0>::Type()), false));
+}  // namespace
+TypeCategory& GetCategory_Bool() {
+  return CreateCategory_Bool();
+}
+TypeInstance& GetType_Bool(Params<0>::Type params) {
+  return CreateType_Bool(params);
+}
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
+using namespace ZEOLITE_DYNAMIC_NAMESPACE;
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+
+
+S<TypeValue> Box_Bool(bool value) {
+  return value? Var_true : Var_false;
+}
diff --git a/base/Category_Char.cpp b/base/Category_Char.cpp
new file mode 100644
--- /dev/null
+++ b/base/Category_Char.cpp
@@ -0,0 +1,250 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2020 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "Category_Char.hpp"
+
+#include <map>
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsChar.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Equals.hpp"
+#include "Category_LessThan.hpp"
+
+
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+namespace ZEOLITE_DYNAMIC_NAMESPACE {
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+namespace {
+const int collection_Char = 0;
+}  // namespace
+const void* const Functions_Char = &collection_Char;
+namespace {
+class Category_Char;
+class Type_Char;
+Type_Char& CreateType_Char(Params<0>::Type params);
+class Value_Char;
+S<TypeValue> CreateValue(Type_Char& parent, const ParamTuple& params, const ValueTuple& args);
+struct Category_Char : public TypeCategory {
+  std::string CategoryName() const final { return "Char"; }
+  Category_Char() {
+    CycleCheck<Category_Char>::Check();
+    CycleCheck<Category_Char> marker(*this);
+    TRACE_FUNCTION("Char (init @category)")
+  }
+  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Category_Char::*)(const ParamTuple&, const ValueTuple&);
+    return TypeCategory::Dispatch(label, params, args);
+  }
+};
+Category_Char& CreateCategory_Char() {
+  static auto& category = *new Category_Char();
+  return category;
+}
+struct Type_Char : public TypeInstance {
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  void BuildTypeName(std::ostream& output) const final {
+    return TypeInstance::TypeNameFrom(output, parent);
+  }
+  Category_Char& parent;
+  bool CanConvertFrom(const TypeInstance& from) const final {
+    std::vector<const TypeInstance*> args;
+    if (!from.TypeArgsForParent(parent, args)) return false;
+    if(args.size() != 0) {
+      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
+    }
+    return true;
+  }
+  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
+    if (&category == &GetCategory_Char()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsBool()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsChar()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsInt()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsFloat()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_Formatted()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    return false;
+  }
+  Type_Char(Category_Char& p, Params<0>::Type params) : parent(p) {
+    CycleCheck<Type_Char>::Check();
+    CycleCheck<Type_Char> marker(*this);
+    TRACE_FUNCTION("Char (init @type)")
+  }
+  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Type_Char::*)(const ParamTuple&, const ValueTuple&);
+    static const CallType Table_Equals[] = {
+      &Type_Char::Call_equals,
+    };
+    static const CallType Table_LessThan[] = {
+      &Type_Char::Call_lessThan,
+    };
+    if (label.collection == Functions_Equals) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Equals[label.function_num])(params, args);
+    }
+    if (label.collection == Functions_LessThan) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_LessThan[label.function_num])(params, args);
+    }
+    return TypeInstance::Dispatch(label, params, args);
+  }
+  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
+};
+Type_Char& CreateType_Char(Params<0>::Type params) {
+  static auto& cache = *new InstanceMap<0,Type_Char>();
+  auto& cached = cache[params];
+  if (!cached) { cached = R_get(new Type_Char(CreateCategory_Char(), params)); }
+  return *cached;
+}
+struct Value_Char : public TypeValue {
+  Value_Char(Type_Char& p, PrimChar value) : parent(p), value_(value) {}
+  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
+    using CallType = ReturnTuple(Value_Char::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
+    static const CallType Table_AsBool[] = {
+      &Value_Char::Call_asBool,
+    };
+    static const CallType Table_AsChar[] = {
+      &Value_Char::Call_asChar,
+    };
+    static const CallType Table_AsFloat[] = {
+      &Value_Char::Call_asFloat,
+    };
+    static const CallType Table_AsInt[] = {
+      &Value_Char::Call_asInt,
+    };
+    static const CallType Table_Formatted[] = {
+      &Value_Char::Call_formatted,
+    };
+    if (label.collection == Functions_AsBool) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsBool[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsChar) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsChar[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsFloat) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsFloat[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsInt) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsInt[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_Formatted) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Formatted[label.function_num])(self, params, args);
+    }
+    return TypeValue::Dispatch(self, label, params, args);
+  }
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  PrimChar AsChar() const final { return value_; }
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  Type_Char& parent;
+  const PrimChar value_;
+};
+ReturnTuple Type_Char::Call_equals(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Char.equals")
+  const PrimChar Var_arg1 = (args.At(0))->AsChar();
+  const PrimChar Var_arg2 = (args.At(1))->AsChar();
+      return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+}
+ReturnTuple Type_Char::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Char.lessThan")
+  const PrimChar Var_arg1 = (args.At(0))->AsChar();
+  const PrimChar Var_arg2 = (args.At(1))->AsChar();
+  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+}
+ReturnTuple Value_Char::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Char.asBool")
+  return ReturnTuple(Box_Bool(value_ != '\0'));
+}
+ReturnTuple Value_Char::Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Char.asChar")
+  return ReturnTuple(Var_self);
+}
+ReturnTuple Value_Char::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Char.asFloat")
+  return ReturnTuple(Box_Float(value_));
+}
+ReturnTuple Value_Char::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Char.asInt")
+  return ReturnTuple(Box_Int(value_));
+}
+ReturnTuple Value_Char::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Char.formatted")
+  std::ostringstream output;
+  output << value_;
+  return ReturnTuple(Box_String(output.str()));
+}
+}  // namespace
+TypeCategory& GetCategory_Char() {
+  return CreateCategory_Char();
+}
+TypeInstance& GetType_Char(Params<0>::Type params) {
+  return CreateType_Char(params);
+}
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
+using namespace ZEOLITE_DYNAMIC_NAMESPACE;
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+
+
+S<TypeValue> Box_Char(PrimChar value) {
+  return S_get(new Value_Char(CreateType_Char(Params<0>::Type()), value));
+}
diff --git a/base/Category_Float.cpp b/base/Category_Float.cpp
new file mode 100644
--- /dev/null
+++ b/base/Category_Float.cpp
@@ -0,0 +1,231 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2020 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "Category_Float.hpp"
+
+#include <map>
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Equals.hpp"
+#include "Category_LessThan.hpp"
+
+
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+namespace ZEOLITE_DYNAMIC_NAMESPACE {
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+namespace {
+const int collection_Float = 0;
+}  // namespace
+const void* const Functions_Float = &collection_Float;
+namespace {
+class Category_Float;
+class Type_Float;
+Type_Float& CreateType_Float(Params<0>::Type params);
+class Value_Float;
+S<TypeValue> CreateValue(Type_Float& parent, const ParamTuple& params, const ValueTuple& args);
+struct Category_Float : public TypeCategory {
+  std::string CategoryName() const final { return "Float"; }
+  Category_Float() {
+    CycleCheck<Category_Float>::Check();
+    CycleCheck<Category_Float> marker(*this);
+    TRACE_FUNCTION("Float (init @category)")
+  }
+  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Category_Float::*)(const ParamTuple&, const ValueTuple&);
+    return TypeCategory::Dispatch(label, params, args);
+  }
+};
+Category_Float& CreateCategory_Float() {
+  static auto& category = *new Category_Float();
+  return category;
+}
+struct Type_Float : public TypeInstance {
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  void BuildTypeName(std::ostream& output) const final {
+    return TypeInstance::TypeNameFrom(output, parent);
+  }
+  Category_Float& parent;
+  bool CanConvertFrom(const TypeInstance& from) const final {
+    std::vector<const TypeInstance*> args;
+    if (!from.TypeArgsForParent(parent, args)) return false;
+    if(args.size() != 0) {
+      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
+    }
+    return true;
+  }
+  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
+    if (&category == &GetCategory_Float()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsBool()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsInt()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsFloat()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_Formatted()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    return false;
+  }
+  Type_Float(Category_Float& p, Params<0>::Type params) : parent(p) {
+    CycleCheck<Type_Float>::Check();
+    CycleCheck<Type_Float> marker(*this);
+    TRACE_FUNCTION("Float (init @type)")
+  }
+  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Type_Float::*)(const ParamTuple&, const ValueTuple&);
+    static const CallType Table_Equals[] = {
+      &Type_Float::Call_equals,
+    };
+    static const CallType Table_LessThan[] = {
+      &Type_Float::Call_lessThan,
+    };
+    if (label.collection == Functions_Equals) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Equals[label.function_num])(params, args);
+    }
+    if (label.collection == Functions_LessThan) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_LessThan[label.function_num])(params, args);
+    }
+    return TypeInstance::Dispatch(label, params, args);
+  }
+  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
+};
+Type_Float& CreateType_Float(Params<0>::Type params) {
+  static auto& cache = *new InstanceMap<0,Type_Float>();
+  auto& cached = cache[params];
+  if (!cached) { cached = R_get(new Type_Float(CreateCategory_Float(), params)); }
+  return *cached;
+}
+struct Value_Float : public TypeValue {
+  Value_Float(Type_Float& p, PrimFloat value) : parent(p), value_(value) {}
+  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
+    using CallType = ReturnTuple(Value_Float::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
+    static const CallType Table_AsBool[] = {
+      &Value_Float::Call_asBool,
+    };
+    static const CallType Table_AsFloat[] = {
+      &Value_Float::Call_asFloat,
+    };
+    static const CallType Table_AsInt[] = {
+      &Value_Float::Call_asInt,
+    };
+    static const CallType Table_Formatted[] = {
+      &Value_Float::Call_formatted,
+    };
+    if (label.collection == Functions_AsBool) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsBool[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsFloat) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsFloat[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsInt) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsInt[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_Formatted) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Formatted[label.function_num])(self, params, args);
+    }
+    return TypeValue::Dispatch(self, label, params, args);
+  }
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  PrimFloat AsFloat() const final { return value_; }
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  Type_Float& parent;
+  const PrimFloat value_;
+};
+ReturnTuple Type_Float::Call_equals(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Float.equals")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
+  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+}
+ReturnTuple Type_Float::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Float.lessThan")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
+  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+}
+ReturnTuple Value_Float::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Float.asBool")
+  return ReturnTuple(Box_Bool(value_ != 0.0));
+}
+ReturnTuple Value_Float::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Float.asFloat")
+  return ReturnTuple(Var_self);
+}
+ReturnTuple Value_Float::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Float.asInt")
+  return ReturnTuple(Box_Int(value_));
+}
+ReturnTuple Value_Float::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Float.formatted")
+  std::ostringstream output;
+  output << value_;
+  return ReturnTuple(Box_String(output.str()));
+}
+}  // namespace
+TypeCategory& GetCategory_Float() {
+  return CreateCategory_Float();
+}
+TypeInstance& GetType_Float(Params<0>::Type params) {
+  return CreateType_Float(params);
+}
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
+using namespace ZEOLITE_DYNAMIC_NAMESPACE;
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+
+
+S<TypeValue> Box_Float(PrimFloat value) {
+  return S_get(new Value_Float(CreateType_Float(Params<0>::Type()), value));
+}
diff --git a/base/Category_Int.cpp b/base/Category_Int.cpp
new file mode 100644
--- /dev/null
+++ b/base/Category_Int.cpp
@@ -0,0 +1,250 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2020 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "Category_Int.hpp"
+
+#include <map>
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsChar.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Equals.hpp"
+#include "Category_LessThan.hpp"
+
+
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+namespace ZEOLITE_DYNAMIC_NAMESPACE {
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+namespace {
+const int collection_Int = 0;
+}  // namespace
+const void* const Functions_Int = &collection_Int;
+namespace {
+class Category_Int;
+class Type_Int;
+Type_Int& CreateType_Int(Params<0>::Type params);
+class Value_Int;
+S<TypeValue> CreateValue(Type_Int& parent, const ParamTuple& params, const ValueTuple& args);
+struct Category_Int : public TypeCategory {
+  std::string CategoryName() const final { return "Int"; }
+  Category_Int() {
+    CycleCheck<Category_Int>::Check();
+    CycleCheck<Category_Int> marker(*this);
+    TRACE_FUNCTION("Int (init @category)")
+  }
+  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Category_Int::*)(const ParamTuple&, const ValueTuple&);
+    return TypeCategory::Dispatch(label, params, args);
+  }
+};
+Category_Int& CreateCategory_Int() {
+  static auto& category = *new Category_Int();
+  return category;
+}
+struct Type_Int : public TypeInstance {
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  void BuildTypeName(std::ostream& output) const final {
+    return TypeInstance::TypeNameFrom(output, parent);
+  }
+  Category_Int& parent;
+  bool CanConvertFrom(const TypeInstance& from) const final {
+    std::vector<const TypeInstance*> args;
+    if (!from.TypeArgsForParent(parent, args)) return false;
+    if(args.size() != 0) {
+      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
+    }
+    return true;
+  }
+  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
+    if (&category == &GetCategory_Int()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsBool()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsChar()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsInt()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsFloat()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_Formatted()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    return false;
+  }
+  Type_Int(Category_Int& p, Params<0>::Type params) : parent(p) {
+    CycleCheck<Type_Int>::Check();
+    CycleCheck<Type_Int> marker(*this);
+    TRACE_FUNCTION("Int (init @type)")
+  }
+  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Type_Int::*)(const ParamTuple&, const ValueTuple&);
+    static const CallType Table_Equals[] = {
+      &Type_Int::Call_equals,
+    };
+    static const CallType Table_LessThan[] = {
+      &Type_Int::Call_lessThan,
+    };
+    if (label.collection == Functions_Equals) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Equals[label.function_num])(params, args);
+    }
+    if (label.collection == Functions_LessThan) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_LessThan[label.function_num])(params, args);
+    }
+    return TypeInstance::Dispatch(label, params, args);
+  }
+  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
+};
+Type_Int& CreateType_Int(Params<0>::Type params) {
+  static auto& cache = *new InstanceMap<0,Type_Int>();
+  auto& cached = cache[params];
+  if (!cached) { cached = R_get(new Type_Int(CreateCategory_Int(), params)); }
+  return *cached;
+}
+struct Value_Int : public TypeValue {
+  Value_Int(Type_Int& p, PrimInt value) : parent(p), value_(value) {}
+  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
+    using CallType = ReturnTuple(Value_Int::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
+    static const CallType Table_AsBool[] = {
+      &Value_Int::Call_asBool,
+    };
+    static const CallType Table_AsChar[] = {
+      &Value_Int::Call_asChar,
+    };
+    static const CallType Table_AsFloat[] = {
+      &Value_Int::Call_asFloat,
+    };
+    static const CallType Table_AsInt[] = {
+      &Value_Int::Call_asInt,
+    };
+    static const CallType Table_Formatted[] = {
+      &Value_Int::Call_formatted,
+    };
+    if (label.collection == Functions_AsBool) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsBool[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsChar) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsChar[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsFloat) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsFloat[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_AsInt) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsInt[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_Formatted) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Formatted[label.function_num])(self, params, args);
+    }
+    return TypeValue::Dispatch(self, label, params, args);
+  }
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  PrimInt AsInt() const final { return value_; }
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  Type_Int& parent;
+  const PrimInt value_;
+};
+ReturnTuple Type_Int::Call_equals(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Int.equals")
+  const PrimInt Var_arg1 = (args.At(0))->AsInt();
+  const PrimInt Var_arg2 = (args.At(1))->AsInt();
+  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+}
+ReturnTuple Type_Int::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Int.lessThan")
+  const PrimInt Var_arg1 = (args.At(0))->AsInt();
+  const PrimInt Var_arg2 = (args.At(1))->AsInt();
+  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+}
+ReturnTuple Value_Int::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Int.asBool")
+  return ReturnTuple(Box_Bool(value_ != 0));
+}
+ReturnTuple Value_Int::Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Int.asChar")
+  return ReturnTuple(Box_Char(value_ % 0xff));
+}
+ReturnTuple Value_Int::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Int.asFloat")
+  return ReturnTuple(Box_Float(value_));
+}
+ReturnTuple Value_Int::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Int.asInt")
+  return ReturnTuple(Var_self);
+}
+ReturnTuple Value_Int::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Int.formatted")
+  std::ostringstream output;
+  output << value_;
+  return ReturnTuple(Box_String(output.str()));
+}
+}  // namespace
+TypeCategory& GetCategory_Int() {
+  return CreateCategory_Int();
+}
+TypeInstance& GetType_Int(Params<0>::Type params) {
+  return CreateType_Int(params);
+}
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
+using namespace ZEOLITE_DYNAMIC_NAMESPACE;
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+
+
+S<TypeValue> Box_Int(PrimInt value) {
+  return S_get(new Value_Int(CreateType_Int(Params<0>::Type()), value));
+}
diff --git a/base/Category_String.cpp b/base/Category_String.cpp
new file mode 100644
--- /dev/null
+++ b/base/Category_String.cpp
@@ -0,0 +1,245 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2020 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "Category_String.hpp"
+
+#include <map>
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_ReadPosition.hpp"
+#include "Category_Char.hpp"
+#include "Category_Equals.hpp"
+#include "Category_LessThan.hpp"
+
+
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+namespace ZEOLITE_DYNAMIC_NAMESPACE {
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+namespace {
+const int collection_String = 0;
+}  // namespace
+const void* const Functions_String = &collection_String;
+const ValueFunction& Function_String_subSequence = (*new ValueFunction{ 0, 2, 1, "String", "subSequence", Functions_String, 0 });
+namespace {
+class Category_String;
+class Type_String;
+Type_String& CreateType_String(Params<0>::Type params);
+class Value_String;
+S<TypeValue> CreateValue(Type_String& parent, const ParamTuple& params, const ValueTuple& args);
+struct Category_String : public TypeCategory {
+  std::string CategoryName() const final { return "String"; }
+  Category_String() {
+    CycleCheck<Category_String>::Check();
+    CycleCheck<Category_String> marker(*this);
+    TRACE_FUNCTION("String (init @category)")
+  }
+  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Category_String::*)(const ParamTuple&, const ValueTuple&);
+    return TypeCategory::Dispatch(label, params, args);
+  }
+};
+Category_String& CreateCategory_String() {
+  static auto& category = *new Category_String();
+  return category;
+}
+struct Type_String : public TypeInstance {
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  void BuildTypeName(std::ostream& output) const final {
+    return TypeInstance::TypeNameFrom(output, parent);
+  }
+  Category_String& parent;
+  bool CanConvertFrom(const TypeInstance& from) const final {
+    std::vector<const TypeInstance*> args;
+    if (!from.TypeArgsForParent(parent, args)) return false;
+    if(args.size() != 0) {
+      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
+    }
+    return true;
+  }
+  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
+    if (&category == &GetCategory_String()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_AsBool()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_Formatted()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    if (&category == &GetCategory_ReadPosition()) {
+      args = std::vector<const TypeInstance*>{&GetType_Char(T_get())};
+      return true;
+    }
+    return false;
+  }
+  Type_String(Category_String& p, Params<0>::Type params) : parent(p) {
+    CycleCheck<Type_String>::Check();
+    CycleCheck<Type_String> marker(*this);
+    TRACE_FUNCTION("String (init @type)")
+  }
+  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Type_String::*)(const ParamTuple&, const ValueTuple&);
+    static const CallType Table_Equals[] = {
+      &Type_String::Call_equals,
+    };
+    static const CallType Table_LessThan[] = {
+      &Type_String::Call_lessThan,
+    };
+    if (label.collection == Functions_Equals) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Equals[label.function_num])(params, args);
+    }
+    if (label.collection == Functions_LessThan) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_LessThan[label.function_num])(params, args);
+    }
+    return TypeInstance::Dispatch(label, params, args);
+  }
+  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
+};
+Type_String& CreateType_String(Params<0>::Type params) {
+  static auto& cache = *new InstanceMap<0,Type_String>();
+  auto& cached = cache[params];
+  if (!cached) { cached = R_get(new Type_String(CreateCategory_String(), params)); }
+  return *cached;
+}
+struct Value_String : public TypeValue {
+  Value_String(Type_String& p, const PrimString& value) : parent(p), value_(value) {}
+  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
+    using CallType = ReturnTuple(Value_String::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
+    static const CallType Table_AsBool[] = {
+      &Value_String::Call_asBool,
+    };
+    static const CallType Table_Formatted[] = {
+      &Value_String::Call_formatted,
+    };
+    static const CallType Table_ReadPosition[] = {
+      &Value_String::Call_readPosition,
+      &Value_String::Call_readSize,
+      &Value_String::Call_subSequence,
+    };
+    static const CallType Table_String[] = {
+      &Value_String::Call_subSequence,
+    };
+    if (label.collection == Functions_AsBool) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_AsBool[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_Formatted) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Formatted[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_ReadPosition) {
+      if (label.function_num < 0 || label.function_num >= 3) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_ReadPosition[label.function_num])(self, params, args);
+    }
+    if (label.collection == Functions_String) {
+      if (label.function_num < 0 || label.function_num >= 1) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_String[label.function_num])(self, params, args);
+    }
+    return TypeValue::Dispatch(self, label, params, args);
+  }
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  const PrimString& AsString() const final { return value_; }
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_readPosition(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_readSize(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_subSequence(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
+  Type_String& parent;
+  const PrimString value_;
+};
+ReturnTuple Type_String::Call_equals(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("String.equals")
+  const S<TypeValue>& Var_arg1 = (args.At(0));
+  const S<TypeValue>& Var_arg2 = (args.At(1));
+  return ReturnTuple(Box_Bool(Var_arg1->AsString()==Var_arg2->AsString()));
+}
+ReturnTuple Type_String::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("String.lessThan")
+  const S<TypeValue>& Var_arg1 = (args.At(0));
+  const S<TypeValue>& Var_arg2 = (args.At(1));
+  return ReturnTuple(Box_Bool(Var_arg1->AsString()<Var_arg2->AsString()));
+}
+ReturnTuple Value_String::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("String.asBool")
+  return ReturnTuple(Box_Bool(value_.size() != 0));
+}
+ReturnTuple Value_String::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("String.formatted")
+  return ReturnTuple(Var_self);
+}
+ReturnTuple Value_String::Call_readPosition(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("String.readPosition")
+  const PrimInt Var_arg1 = (args.At(0))->AsInt();
+  if (Var_arg1 < 0 || Var_arg1 >= value_.size()) {
+    FAIL() << "Read position " << Var_arg1 << " is out of bounds";
+  }
+  return ReturnTuple(Box_Char(value_[Var_arg1]));
+}
+ReturnTuple Value_String::Call_readSize(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("String.readSize")
+  return ReturnTuple(Box_Int(value_.size()));
+}
+ReturnTuple Value_String::Call_subSequence(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("String.subSequence")
+  const PrimInt Var_arg1 = (args.At(0))->AsInt();
+  const PrimInt Var_arg2 = (args.At(1))->AsInt();
+  if (Var_arg1 < 0 || Var_arg1 > value_.size()) {
+    FAIL() << "Subsequence position " << Var_arg1 << " is out of bounds";
+  }
+  if (Var_arg2 < 0 || Var_arg1 + Var_arg2 > value_.size()) {
+    FAIL() << "Subsequence size " << Var_arg2 << " is invalid";
+  }
+  return ReturnTuple(Box_String(value_.substr(Var_arg1,Var_arg2)));
+}
+}  // namespace
+TypeCategory& GetCategory_String() {
+  return CreateCategory_String();
+}
+TypeInstance& GetType_String(Params<0>::Type params) {
+  return CreateType_String(params);
+}
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
+using namespace ZEOLITE_DYNAMIC_NAMESPACE;
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+
+
+S<TypeValue> Box_String(const PrimString& value) {
+  return S_get(new Value_String(CreateType_String(Params<0>::Type()), value));
+}
diff --git a/base/builtin.0rp b/base/builtin.0rp
--- a/base/builtin.0rp
+++ b/base/builtin.0rp
@@ -36,25 +36,25 @@
   defines LessThan<Char>
 }
 
-concrete Int {
+concrete Float {
   refines AsBool
-  refines AsChar
   refines AsInt
   refines AsFloat
   refines Formatted
 
-  defines Equals<Int>
-  defines LessThan<Int>
+  defines Equals<Float>
+  defines LessThan<Float>
 }
 
-concrete Float {
+concrete Int {
   refines AsBool
+  refines AsChar
   refines AsInt
   refines AsFloat
   refines Formatted
 
-  defines Equals<Float>
-  defines LessThan<Float>
+  defines Equals<Int>
+  defines LessThan<Int>
 }
 
 concrete String {
@@ -72,10 +72,6 @@
   asBool () -> (Bool)
 }
 
-@value interface AsInt {
-  asInt () -> (Int)
-}
-
 @value interface AsChar {
   asChar () -> (Char)
 }
@@ -84,12 +80,8 @@
   asFloat () -> (Float)
 }
 
-@type interface LessThan<#x|> {
-  lessThan (#x,#x) -> (Bool)
-}
-
-@type interface Equals<#x|> {
-  equals (#x,#x) -> (Bool)
+@value interface AsInt {
+  asInt () -> (Int)
 }
 
 @value interface Formatted {
@@ -100,4 +92,12 @@
   readPosition (Int) -> (#x)
   readSize () -> (Int)
   subSequence (Int,Int) -> (ReadPosition<#x>)
+}
+
+@type interface Equals<#x|> {
+  equals (#x,#x) -> (Bool)
+}
+
+@type interface LessThan<#x|> {
+  lessThan (#x,#x) -> (Bool)
 }
diff --git a/base/builtin.cpp b/base/builtin.cpp
deleted file mode 100644
--- a/base/builtin.cpp
+++ /dev/null
@@ -1,1192 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2020 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "builtin.hpp"
-
-#include <map>
-#include <sstream>
-
-#include "category-source.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsChar.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_Bool.hpp"
-#include "Category_Char.hpp"
-#include "Category_Equals.hpp"
-#include "Category_Float.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Int.hpp"
-#include "Category_LessThan.hpp"
-#include "Category_ReadPosition.hpp"
-#include "Category_String.hpp"
-
-
-void BuiltinFail(const S<TypeValue>& formatted) {
-  FAIL() << TypeValue::Call(formatted, Function_Formatted_formatted,
-                            ParamTuple(), ArgTuple()).Only()->AsString();
-  __builtin_unreachable();
-}
-
-namespace {
-
-struct OptionalEmpty : public TypeValue {
-  ReturnTuple Dispatch(const S<TypeValue>& self,
-                       const ValueFunction& label,
-                       const ParamTuple& params, const ValueTuple& args) final {
-    FAIL() << "Function called on empty value";
-    __builtin_unreachable();
-  }
-
-  std::string CategoryName() const final { return "empty"; }
-
-  bool Present() const final { return false; }
-};
-
-struct Type_Intersect : public TypeInstance {
-  Type_Intersect(L<TypeInstance*> params) : params_(params.begin(), params.end()) {}
-
-  std::string CategoryName() const final { return "(intersection)"; }
-
-  void BuildTypeName(std::ostream& output) const final {
-    if (params_.empty()) {
-      output << "any";
-    } else {
-      output << "[";
-      bool first = true;
-      for (const auto param : params_) {
-        if (!first) output << "&";
-        first = false;
-        param->BuildTypeName(output);
-      }
-      output << "]";
-    }
-  }
-
-  MergeType InstanceMergeType() const final
-  { return MergeType::INTERSECT; }
-
-  std::vector<const TypeInstance*> MergedTypes() const final
-  { return params_; }
-
-  const L<const TypeInstance*> params_;
-};
-
-struct Type_Union : public TypeInstance {
-  Type_Union(L<TypeInstance*> params) : params_(params.begin(), params.end()) {}
-
-  std::string CategoryName() const final { return "(union)"; }
-
-  void BuildTypeName(std::ostream& output) const final {
-    if (params_.empty()) {
-      output << "all";
-    } else {
-      output << "[";
-      bool first = true;
-      for (const auto param : params_) {
-        if (!first) output << "|";
-        first = false;
-        param->BuildTypeName(output);
-      }
-      output << "]";
-    }
-  }
-
-  MergeType InstanceMergeType() const final
-  { return MergeType::UNION; }
-
-  std::vector<const TypeInstance*> MergedTypes() const final
-  { return params_; }
-
-  const L<const TypeInstance*> params_;
-};
-
-}  // namespace
-
-
-TypeInstance& Merge_Intersect(L<TypeInstance*> params) {
-  static auto& cache = *new std::map<L<TypeInstance*>,R<Type_Intersect>>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_Intersect(params)); }
-  return *cached;
-}
-
-TypeInstance& Merge_Union(L<TypeInstance*> params) {
-  static auto& cache = *new std::map<L<TypeInstance*>,R<Type_Union>>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_Union(params)); }
-  return *cached;
-}
-
-TypeInstance& GetMerged_Any() {
-  static auto& instance = Merge_Intersect(L_get<TypeInstance*>());
-  return instance;
-}
-
-TypeInstance& GetMerged_All() {
-  static auto& instance = Merge_Union(L_get<TypeInstance*>());
-  return instance;
-}
-
-
-const S<TypeValue>& Var_empty = *new S<TypeValue>(new OptionalEmpty());
-
-
-// Bool
-
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-namespace ZEOLITE_DYNAMIC_NAMESPACE {
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-namespace {
-const int collection_Bool = 0;
-}  // namespace
-const void* const Functions_Bool = &collection_Bool;
-namespace {
-class Category_Bool;
-class Type_Bool;
-Type_Bool& CreateType_Bool(Params<0>::Type params);
-class Value_Bool;
-S<TypeValue> CreateValue(Type_Bool& parent, const ParamTuple& params, const ValueTuple& args);
-struct Category_Bool : public TypeCategory {
-  std::string CategoryName() const final { return "Bool"; }
-  Category_Bool() {
-    CycleCheck<Category_Bool>::Check();
-    CycleCheck<Category_Bool> marker(*this);
-    TRACE_FUNCTION("Bool (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_Bool::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_Bool& CreateCategory_Bool() {
-  static auto& category = *new Category_Bool();
-  return category;
-}
-struct Type_Bool : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_Bool& parent;
-  bool CanConvertFrom(const TypeInstance& from) const final {
-    std::vector<const TypeInstance*> args;
-    if (!from.TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
-    if (&category == &GetCategory_Bool()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsBool()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsInt()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsFloat()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_Formatted()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    return false;
-  }
-  Type_Bool(Category_Bool& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_Bool>::Check();
-    CycleCheck<Type_Bool> marker(*this);
-    TRACE_FUNCTION("Bool (init @type)")
-  }
-  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_Bool::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Equals[] = {
-      &Type_Bool::Call_equals,
-    };
-    if (label.collection == Functions_Equals) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Equals[label.function_num])(params, args);
-    }
-    return TypeInstance::Dispatch(label, params, args);
-  }
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-};
-Type_Bool& CreateType_Bool(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_Bool>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_Bool(CreateCategory_Bool(), params)); }
-  return *cached;
-}
-struct Value_Bool : public TypeValue {
-  Value_Bool(Type_Bool& p, bool value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_Bool::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_Bool::Call_asBool,
-    };
-    static const CallType Table_AsFloat[] = {
-      &Value_Bool::Call_asFloat,
-    };
-    static const CallType Table_AsInt[] = {
-      &Value_Bool::Call_asInt,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_Bool::Call_formatted,
-    };
-    if (label.collection == Functions_AsBool) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsBool[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsFloat) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsFloat[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsInt) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsInt[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_Formatted) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Formatted[label.function_num])(self, params, args);
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  bool AsBool() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  Type_Bool& parent;
-  const bool value_;
-};
-ReturnTuple Type_Bool::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.equals")
-  const bool Var_arg1 = (args.At(0))->AsBool();
-  const bool Var_arg2 = (args.At(1))->AsBool();
-  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-}
-ReturnTuple Value_Bool::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.asBool")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_Bool::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.asFloat")
-  return ReturnTuple(Box_Float(value_ ? 1.0 : 0.0));
-}
-ReturnTuple Value_Bool::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.asInt")
-  return ReturnTuple(Box_Int(value_? 1 : 0));
-}
-ReturnTuple Value_Bool::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.formatted")
-  return ReturnTuple(Box_String(value_? "true" : "false"));
-}
-const S<TypeValue>& Var_true = *new S<TypeValue>(new Value_Bool(CreateType_Bool(Params<0>::Type()), true));
-const S<TypeValue>& Var_false = *new S<TypeValue>(new Value_Bool(CreateType_Bool(Params<0>::Type()), false));
-}  // namespace
-TypeCategory& GetCategory_Bool() {
-  return CreateCategory_Bool();
-}
-TypeInstance& GetType_Bool(Params<0>::Type params) {
-  return CreateType_Bool(params);
-}
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
-using namespace ZEOLITE_DYNAMIC_NAMESPACE;
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-
-
-// Char
-
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-namespace ZEOLITE_DYNAMIC_NAMESPACE {
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-namespace {
-const int collection_Char = 0;
-}  // namespace
-const void* const Functions_Char = &collection_Char;
-namespace {
-class Category_Char;
-class Type_Char;
-Type_Char& CreateType_Char(Params<0>::Type params);
-class Value_Char;
-S<TypeValue> CreateValue(Type_Char& parent, const ParamTuple& params, const ValueTuple& args);
-struct Category_Char : public TypeCategory {
-  std::string CategoryName() const final { return "Char"; }
-  Category_Char() {
-    CycleCheck<Category_Char>::Check();
-    CycleCheck<Category_Char> marker(*this);
-    TRACE_FUNCTION("Char (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_Char::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_Char& CreateCategory_Char() {
-  static auto& category = *new Category_Char();
-  return category;
-}
-struct Type_Char : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_Char& parent;
-  bool CanConvertFrom(const TypeInstance& from) const final {
-    std::vector<const TypeInstance*> args;
-    if (!from.TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
-    if (&category == &GetCategory_Char()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsBool()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsChar()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsInt()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsFloat()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_Formatted()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    return false;
-  }
-  Type_Char(Category_Char& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_Char>::Check();
-    CycleCheck<Type_Char> marker(*this);
-    TRACE_FUNCTION("Char (init @type)")
-  }
-  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_Char::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Equals[] = {
-      &Type_Char::Call_equals,
-    };
-    static const CallType Table_LessThan[] = {
-      &Type_Char::Call_lessThan,
-    };
-    if (label.collection == Functions_Equals) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Equals[label.function_num])(params, args);
-    }
-    if (label.collection == Functions_LessThan) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_LessThan[label.function_num])(params, args);
-    }
-    return TypeInstance::Dispatch(label, params, args);
-  }
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
-};
-Type_Char& CreateType_Char(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_Char>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_Char(CreateCategory_Char(), params)); }
-  return *cached;
-}
-struct Value_Char : public TypeValue {
-  Value_Char(Type_Char& p, PrimChar value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_Char::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_Char::Call_asBool,
-    };
-    static const CallType Table_AsChar[] = {
-      &Value_Char::Call_asChar,
-    };
-    static const CallType Table_AsFloat[] = {
-      &Value_Char::Call_asFloat,
-    };
-    static const CallType Table_AsInt[] = {
-      &Value_Char::Call_asInt,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_Char::Call_formatted,
-    };
-    if (label.collection == Functions_AsBool) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsBool[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsChar) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsChar[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsFloat) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsFloat[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsInt) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsInt[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_Formatted) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Formatted[label.function_num])(self, params, args);
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  PrimChar AsChar() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  Type_Char& parent;
-  const PrimChar value_;
-};
-ReturnTuple Type_Char::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.equals")
-  const PrimChar Var_arg1 = (args.At(0))->AsChar();
-  const PrimChar Var_arg2 = (args.At(1))->AsChar();
-      return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-}
-ReturnTuple Type_Char::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.lessThan")
-  const PrimChar Var_arg1 = (args.At(0))->AsChar();
-  const PrimChar Var_arg2 = (args.At(1))->AsChar();
-  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-}
-ReturnTuple Value_Char::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.asBool")
-  return ReturnTuple(Box_Bool(value_ != '\0'));
-}
-ReturnTuple Value_Char::Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.asChar")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_Char::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.asFloat")
-  return ReturnTuple(Box_Float(value_));
-}
-ReturnTuple Value_Char::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.asInt")
-  return ReturnTuple(Box_Int(value_));
-}
-ReturnTuple Value_Char::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.formatted")
-  std::ostringstream output;
-  output << value_;
-  return ReturnTuple(Box_String(output.str()));
-}
-}  // namespace
-TypeCategory& GetCategory_Char() {
-  return CreateCategory_Char();
-}
-TypeInstance& GetType_Char(Params<0>::Type params) {
-  return CreateType_Char(params);
-}
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
-using namespace ZEOLITE_DYNAMIC_NAMESPACE;
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-
-
-// Int
-
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-namespace ZEOLITE_DYNAMIC_NAMESPACE {
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-namespace {
-const int collection_Int = 0;
-}  // namespace
-const void* const Functions_Int = &collection_Int;
-namespace {
-class Category_Int;
-class Type_Int;
-Type_Int& CreateType_Int(Params<0>::Type params);
-class Value_Int;
-S<TypeValue> CreateValue(Type_Int& parent, const ParamTuple& params, const ValueTuple& args);
-struct Category_Int : public TypeCategory {
-  std::string CategoryName() const final { return "Int"; }
-  Category_Int() {
-    CycleCheck<Category_Int>::Check();
-    CycleCheck<Category_Int> marker(*this);
-    TRACE_FUNCTION("Int (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_Int::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_Int& CreateCategory_Int() {
-  static auto& category = *new Category_Int();
-  return category;
-}
-struct Type_Int : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_Int& parent;
-  bool CanConvertFrom(const TypeInstance& from) const final {
-    std::vector<const TypeInstance*> args;
-    if (!from.TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
-    if (&category == &GetCategory_Int()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsBool()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsChar()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsInt()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsFloat()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_Formatted()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    return false;
-  }
-  Type_Int(Category_Int& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_Int>::Check();
-    CycleCheck<Type_Int> marker(*this);
-    TRACE_FUNCTION("Int (init @type)")
-  }
-  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_Int::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Equals[] = {
-      &Type_Int::Call_equals,
-    };
-    static const CallType Table_LessThan[] = {
-      &Type_Int::Call_lessThan,
-    };
-    if (label.collection == Functions_Equals) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Equals[label.function_num])(params, args);
-    }
-    if (label.collection == Functions_LessThan) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_LessThan[label.function_num])(params, args);
-    }
-    return TypeInstance::Dispatch(label, params, args);
-  }
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
-};
-Type_Int& CreateType_Int(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_Int>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_Int(CreateCategory_Int(), params)); }
-  return *cached;
-}
-struct Value_Int : public TypeValue {
-  Value_Int(Type_Int& p, PrimInt value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_Int::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_Int::Call_asBool,
-    };
-    static const CallType Table_AsChar[] = {
-      &Value_Int::Call_asChar,
-    };
-    static const CallType Table_AsFloat[] = {
-      &Value_Int::Call_asFloat,
-    };
-    static const CallType Table_AsInt[] = {
-      &Value_Int::Call_asInt,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_Int::Call_formatted,
-    };
-    if (label.collection == Functions_AsBool) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsBool[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsChar) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsChar[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsFloat) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsFloat[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsInt) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsInt[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_Formatted) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Formatted[label.function_num])(self, params, args);
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  PrimInt AsInt() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  Type_Int& parent;
-  const PrimInt value_;
-};
-ReturnTuple Type_Int::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.equals")
-  const PrimInt Var_arg1 = (args.At(0))->AsInt();
-  const PrimInt Var_arg2 = (args.At(1))->AsInt();
-  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-}
-ReturnTuple Type_Int::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.lessThan")
-  const PrimInt Var_arg1 = (args.At(0))->AsInt();
-  const PrimInt Var_arg2 = (args.At(1))->AsInt();
-  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-}
-ReturnTuple Value_Int::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.asBool")
-  return ReturnTuple(Box_Bool(value_ != 0));
-}
-ReturnTuple Value_Int::Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.asChar")
-  return ReturnTuple(Box_Char(value_ % 0xff));
-}
-ReturnTuple Value_Int::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.asFloat")
-  return ReturnTuple(Box_Float(value_));
-}
-ReturnTuple Value_Int::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.asInt")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_Int::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.formatted")
-  std::ostringstream output;
-  output << value_;
-  return ReturnTuple(Box_String(output.str()));
-}
-}  // namespace
-TypeCategory& GetCategory_Int() {
-  return CreateCategory_Int();
-}
-TypeInstance& GetType_Int(Params<0>::Type params) {
-  return CreateType_Int(params);
-}
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
-using namespace ZEOLITE_DYNAMIC_NAMESPACE;
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-
-
-// Float
-
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-namespace ZEOLITE_DYNAMIC_NAMESPACE {
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-namespace {
-const int collection_Float = 0;
-}  // namespace
-const void* const Functions_Float = &collection_Float;
-namespace {
-class Category_Float;
-class Type_Float;
-Type_Float& CreateType_Float(Params<0>::Type params);
-class Value_Float;
-S<TypeValue> CreateValue(Type_Float& parent, const ParamTuple& params, const ValueTuple& args);
-struct Category_Float : public TypeCategory {
-  std::string CategoryName() const final { return "Float"; }
-  Category_Float() {
-    CycleCheck<Category_Float>::Check();
-    CycleCheck<Category_Float> marker(*this);
-    TRACE_FUNCTION("Float (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_Float::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_Float& CreateCategory_Float() {
-  static auto& category = *new Category_Float();
-  return category;
-}
-struct Type_Float : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_Float& parent;
-  bool CanConvertFrom(const TypeInstance& from) const final {
-    std::vector<const TypeInstance*> args;
-    if (!from.TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
-    if (&category == &GetCategory_Float()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsBool()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsInt()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsFloat()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_Formatted()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    return false;
-  }
-  Type_Float(Category_Float& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_Float>::Check();
-    CycleCheck<Type_Float> marker(*this);
-    TRACE_FUNCTION("Float (init @type)")
-  }
-  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_Float::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Equals[] = {
-      &Type_Float::Call_equals,
-    };
-    static const CallType Table_LessThan[] = {
-      &Type_Float::Call_lessThan,
-    };
-    if (label.collection == Functions_Equals) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Equals[label.function_num])(params, args);
-    }
-    if (label.collection == Functions_LessThan) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_LessThan[label.function_num])(params, args);
-    }
-    return TypeInstance::Dispatch(label, params, args);
-  }
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
-};
-Type_Float& CreateType_Float(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_Float>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_Float(CreateCategory_Float(), params)); }
-  return *cached;
-}
-struct Value_Float : public TypeValue {
-  Value_Float(Type_Float& p, PrimFloat value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_Float::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_Float::Call_asBool,
-    };
-    static const CallType Table_AsFloat[] = {
-      &Value_Float::Call_asFloat,
-    };
-    static const CallType Table_AsInt[] = {
-      &Value_Float::Call_asInt,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_Float::Call_formatted,
-    };
-    if (label.collection == Functions_AsBool) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsBool[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsFloat) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsFloat[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_AsInt) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsInt[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_Formatted) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Formatted[label.function_num])(self, params, args);
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  PrimFloat AsFloat() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  Type_Float& parent;
-  const PrimFloat value_;
-};
-ReturnTuple Type_Float::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.equals")
-  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
-  const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
-  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-}
-ReturnTuple Type_Float::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.lessThan")
-  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
-  const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
-  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-}
-ReturnTuple Value_Float::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.asBool")
-  return ReturnTuple(Box_Bool(value_ != 0.0));
-}
-ReturnTuple Value_Float::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.asFloat")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_Float::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.asInt")
-  return ReturnTuple(Box_Int(value_));
-}
-ReturnTuple Value_Float::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.formatted")
-  std::ostringstream output;
-  output << value_;
-  return ReturnTuple(Box_String(output.str()));
-}
-}  // namespace
-TypeCategory& GetCategory_Float() {
-  return CreateCategory_Float();
-}
-TypeInstance& GetType_Float(Params<0>::Type params) {
-  return CreateType_Float(params);
-}
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
-using namespace ZEOLITE_DYNAMIC_NAMESPACE;
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-
-
-// String
-
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-namespace ZEOLITE_DYNAMIC_NAMESPACE {
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-namespace {
-const int collection_String = 0;
-}  // namespace
-const void* const Functions_String = &collection_String;
-const ValueFunction& Function_String_subSequence = (*new ValueFunction{ 0, 2, 1, "String", "subSequence", Functions_String, 0 });
-namespace {
-class Category_String;
-class Type_String;
-Type_String& CreateType_String(Params<0>::Type params);
-class Value_String;
-S<TypeValue> CreateValue(Type_String& parent, const ParamTuple& params, const ValueTuple& args);
-struct Category_String : public TypeCategory {
-  std::string CategoryName() const final { return "String"; }
-  Category_String() {
-    CycleCheck<Category_String>::Check();
-    CycleCheck<Category_String> marker(*this);
-    TRACE_FUNCTION("String (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_String::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_String& CreateCategory_String() {
-  static auto& category = *new Category_String();
-  return category;
-}
-struct Type_String : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_String& parent;
-  bool CanConvertFrom(const TypeInstance& from) const final {
-    std::vector<const TypeInstance*> args;
-    if (!from.TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<const TypeInstance*>& args) const final {
-    if (&category == &GetCategory_String()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_AsBool()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_Formatted()) {
-      args = std::vector<const TypeInstance*>{};
-      return true;
-    }
-    if (&category == &GetCategory_ReadPosition()) {
-      args = std::vector<const TypeInstance*>{&GetType_Char(T_get())};
-      return true;
-    }
-    return false;
-  }
-  Type_String(Category_String& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_String>::Check();
-    CycleCheck<Type_String> marker(*this);
-    TRACE_FUNCTION("String (init @type)")
-  }
-  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_String::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Equals[] = {
-      &Type_String::Call_equals,
-    };
-    static const CallType Table_LessThan[] = {
-      &Type_String::Call_lessThan,
-    };
-    if (label.collection == Functions_Equals) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Equals[label.function_num])(params, args);
-    }
-    if (label.collection == Functions_LessThan) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_LessThan[label.function_num])(params, args);
-    }
-    return TypeInstance::Dispatch(label, params, args);
-  }
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
-};
-Type_String& CreateType_String(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_String>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_String(CreateCategory_String(), params)); }
-  return *cached;
-}
-struct Value_String : public TypeValue {
-  Value_String(Type_String& p, const PrimString& value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_String::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_String::Call_asBool,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_String::Call_formatted,
-    };
-    static const CallType Table_ReadPosition[] = {
-      &Value_String::Call_readPosition,
-      &Value_String::Call_readSize,
-      &Value_String::Call_subSequence,
-    };
-    static const CallType Table_String[] = {
-      &Value_String::Call_subSequence,
-    };
-    if (label.collection == Functions_AsBool) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_AsBool[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_Formatted) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_Formatted[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_ReadPosition) {
-      if (label.function_num < 0 || label.function_num >= 3) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_ReadPosition[label.function_num])(self, params, args);
-    }
-    if (label.collection == Functions_String) {
-      if (label.function_num < 0 || label.function_num >= 1) {
-        FAIL() << "Bad function call " << label;
-      }
-      return (this->*Table_String[label.function_num])(self, params, args);
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  const PrimString& AsString() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_readPosition(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_readSize(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_subSequence(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  Type_String& parent;
-  const PrimString value_;
-};
-ReturnTuple Type_String::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.equals")
-  const S<TypeValue>& Var_arg1 = (args.At(0));
-  const S<TypeValue>& Var_arg2 = (args.At(1));
-  return ReturnTuple(Box_Bool(Var_arg1->AsString()==Var_arg2->AsString()));
-}
-ReturnTuple Type_String::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.lessThan")
-  const S<TypeValue>& Var_arg1 = (args.At(0));
-  const S<TypeValue>& Var_arg2 = (args.At(1));
-  return ReturnTuple(Box_Bool(Var_arg1->AsString()<Var_arg2->AsString()));
-}
-ReturnTuple Value_String::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.asBool")
-  return ReturnTuple(Box_Bool(value_.size() != 0));
-}
-ReturnTuple Value_String::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.formatted")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_String::Call_readPosition(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.readPosition")
-  const PrimInt Var_arg1 = (args.At(0))->AsInt();
-  if (Var_arg1 < 0 || Var_arg1 >= value_.size()) {
-    FAIL() << "Read position " << Var_arg1 << " is out of bounds";
-  }
-  return ReturnTuple(Box_Char(value_[Var_arg1]));
-}
-ReturnTuple Value_String::Call_readSize(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.readSize")
-  return ReturnTuple(Box_Int(value_.size()));
-}
-ReturnTuple Value_String::Call_subSequence(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.subSequence")
-  const PrimInt Var_arg1 = (args.At(0))->AsInt();
-  const PrimInt Var_arg2 = (args.At(1))->AsInt();
-  if (Var_arg1 < 0 || Var_arg1 > value_.size()) {
-    FAIL() << "Subsequence position " << Var_arg1 << " is out of bounds";
-  }
-  if (Var_arg2 < 0 || Var_arg1 + Var_arg2 > value_.size()) {
-    FAIL() << "Subsequence size " << Var_arg2 << " is invalid";
-  }
-  return ReturnTuple(Box_String(value_.substr(Var_arg1,Var_arg2)));
-}
-}  // namespace
-TypeCategory& GetCategory_String() {
-  return CreateCategory_String();
-}
-TypeInstance& GetType_String(Params<0>::Type params) {
-  return CreateType_String(params);
-}
-#ifdef ZEOLITE_DYNAMIC_NAMESPACE
-}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
-using namespace ZEOLITE_DYNAMIC_NAMESPACE;
-#endif  // ZEOLITE_DYNAMIC_NAMESPACE
-
-
-S<TypeValue> Box_Bool(bool value) {
-  return value? Var_true : Var_false;
-}
-
-S<TypeValue> Box_Char(PrimChar value) {
-  return S_get(new Value_Char(CreateType_Char(Params<0>::Type()), value));
-}
-
-S<TypeValue> Box_Int(PrimInt value) {
-  return S_get(new Value_Int(CreateType_Int(Params<0>::Type()), value));
-}
-
-S<TypeValue> Box_Float(PrimFloat value) {
-  return S_get(new Value_Float(CreateType_Float(Params<0>::Type()), value));
-}
-
-S<TypeValue> Box_String(const PrimString& value) {
-  return S_get(new Value_String(CreateType_String(Params<0>::Type()), value));
-}
diff --git a/base/builtin.hpp b/base/builtin.hpp
deleted file mode 100644
--- a/base/builtin.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2020 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#ifndef BUILTIN_HPP_
-#define BUILTIN_HPP_
-
-#include "category-header.hpp"
-
-
-void BuiltinFail(const S<TypeValue>& formatted) __attribute__ ((noreturn));
-
-TypeInstance& Merge_Intersect(L<TypeInstance*> params);
-TypeInstance& Merge_Union(L<TypeInstance*> params);
-
-TypeInstance& GetMerged_Any();
-TypeInstance& GetMerged_All();
-
-S<TypeValue> Box_Bool(bool value);
-S<TypeValue> Box_String(const PrimString& value);
-S<TypeValue> Box_Char(PrimChar value);
-S<TypeValue> Box_Int(PrimInt value);
-S<TypeValue> Box_Float(PrimFloat value);
-
-extern const S<TypeValue>& Var_empty;
-
-#endif  // BUILTIN_HPP_
diff --git a/base/category-header.hpp b/base/category-header.hpp
--- a/base/category-header.hpp
+++ b/base/category-header.hpp
@@ -31,4 +31,12 @@
 class TypeFunction;
 class ValueFunction;
 
+TypeInstance& Merge_Intersect(L<TypeInstance*> params);
+TypeInstance& Merge_Union(L<TypeInstance*> params);
+
+TypeInstance& GetMerged_Any();
+TypeInstance& GetMerged_All();
+
+extern const S<TypeValue>& Var_empty;
+
 #endif  // CATEGORY_HEADER_HPP_
diff --git a/base/category-source.cpp b/base/category-source.cpp
--- a/base/category-source.cpp
+++ b/base/category-source.cpp
@@ -19,7 +19,110 @@
 #include "category-source.hpp"
 
 #include "logging.hpp"
-#include "builtin.hpp"
+
+
+namespace {
+
+struct OptionalEmpty : public TypeValue {
+  ReturnTuple Dispatch(const S<TypeValue>& self,
+                       const ValueFunction& label,
+                       const ParamTuple& params, const ValueTuple& args) final {
+    FAIL() << "Function called on empty value";
+    __builtin_unreachable();
+  }
+
+  std::string CategoryName() const final { return "empty"; }
+
+  bool Present() const final { return false; }
+};
+
+struct Type_Intersect : public TypeInstance {
+  Type_Intersect(L<TypeInstance*> params) : params_(params.begin(), params.end()) {}
+
+  std::string CategoryName() const final { return "(intersection)"; }
+
+  void BuildTypeName(std::ostream& output) const final {
+    if (params_.empty()) {
+      output << "any";
+    } else {
+      output << "[";
+      bool first = true;
+      for (const auto param : params_) {
+        if (!first) output << "&";
+        first = false;
+        param->BuildTypeName(output);
+      }
+      output << "]";
+    }
+  }
+
+  MergeType InstanceMergeType() const final
+  { return MergeType::INTERSECT; }
+
+  std::vector<const TypeInstance*> MergedTypes() const final
+  { return params_; }
+
+  const L<const TypeInstance*> params_;
+};
+
+struct Type_Union : public TypeInstance {
+  Type_Union(L<TypeInstance*> params) : params_(params.begin(), params.end()) {}
+
+  std::string CategoryName() const final { return "(union)"; }
+
+  void BuildTypeName(std::ostream& output) const final {
+    if (params_.empty()) {
+      output << "all";
+    } else {
+      output << "[";
+      bool first = true;
+      for (const auto param : params_) {
+        if (!first) output << "|";
+        first = false;
+        param->BuildTypeName(output);
+      }
+      output << "]";
+    }
+  }
+
+  MergeType InstanceMergeType() const final
+  { return MergeType::UNION; }
+
+  std::vector<const TypeInstance*> MergedTypes() const final
+  { return params_; }
+
+  const L<const TypeInstance*> params_;
+};
+
+}  // namespace
+
+
+TypeInstance& Merge_Intersect(L<TypeInstance*> params) {
+  static auto& cache = *new std::map<L<TypeInstance*>,R<Type_Intersect>>();
+  auto& cached = cache[params];
+  if (!cached) { cached = R_get(new Type_Intersect(params)); }
+  return *cached;
+}
+
+TypeInstance& Merge_Union(L<TypeInstance*> params) {
+  static auto& cache = *new std::map<L<TypeInstance*>,R<Type_Union>>();
+  auto& cached = cache[params];
+  if (!cached) { cached = R_get(new Type_Union(params)); }
+  return *cached;
+}
+
+TypeInstance& GetMerged_Any() {
+  static auto& instance = Merge_Intersect(L_get<TypeInstance*>());
+  return instance;
+}
+
+TypeInstance& GetMerged_All() {
+  static auto& instance = Merge_Union(L_get<TypeInstance*>());
+  return instance;
+}
+
+
+const S<TypeValue>& Var_empty = *new S<TypeValue>(new OptionalEmpty());
 
 
 ReturnTuple TypeCategory::Dispatch(const CategoryFunction& label,
diff --git a/base/category-source.hpp b/base/category-source.hpp
--- a/base/category-source.hpp
+++ b/base/category-source.hpp
@@ -25,9 +25,23 @@
 
 #include "types.hpp"
 #include "function.hpp"
-#include "builtin.hpp"
 #include "argv.hpp"
 #include "cycle-check.hpp"
+
+
+#define BUILTIN_FAIL(e) { \
+  FAIL() << TypeValue::Call((e), Function_Formatted_formatted, \
+                            ParamTuple(), ArgTuple()).Only()->AsString(); \
+  __builtin_unreachable(); \
+  }
+
+extern const S<TypeValue>& Var_empty;
+
+S<TypeValue> Box_Bool(bool value);
+S<TypeValue> Box_String(const PrimString& value);
+S<TypeValue> Box_Char(PrimChar value);
+S<TypeValue> Box_Int(PrimInt value);
+S<TypeValue> Box_Float(PrimFloat value);
 
 
 class TypeCategory {
diff --git a/base/thread-capture.h b/base/thread-capture.h
new file mode 100644
--- /dev/null
+++ b/base/thread-capture.h
@@ -0,0 +1,217 @@
+/* -----------------------------------------------------------------------------
+Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
+
+#ifndef THREAD_CAPTURE_H_
+#define THREAD_CAPTURE_H_
+
+#include <cassert>
+
+#include "thread-crosser.h"
+
+namespace capture_thread {
+
+// Base class for instrumentation classes that need to be made available within
+// a single thread, and possibly shared across threads. Derive a new class Type
+// from ThreadCapture<Type> to define a new instrumentation type. Scoping is
+// managed by adding a ScopedCapture *or* an AutoThreadCrosser member to
+// instantiable implementations of the instrumentation. (If Type is abstract,
+// add the member to the instantiable subclasses.) See ThreadCrosser for info
+// about *automatic* sharing across threads, and ThreadBridge (below) for info
+// about *manually* sharing across threads.
+template <class Type>
+class ThreadCapture {
+ protected:
+  ThreadCapture() = default;
+  virtual ~ThreadCapture() = default;
+
+  class CrossThreads;
+
+  // Manages scoping of instrumentation within a single thread. Use ThreadBridge
+  // + CrossThreads to share instrumentation across threads. Alternatively, use
+  // AutoThreadCrosser *instead* of ScopedCapture to allow ThreadCrosser to
+  // handle this automatically.
+  class ScopedCapture {
+   public:
+    explicit inline ScopedCapture(Type* capture)
+        : previous_(GetCurrent()), current_(capture) {
+      SetCurrent(current_);
+    }
+
+    inline ~ScopedCapture() { SetCurrent(Previous()); }
+
+    inline Type* Previous() const { return previous_; }
+
+   private:
+    ScopedCapture(const ScopedCapture&) = delete;
+    ScopedCapture(ScopedCapture&&) = delete;
+    ScopedCapture& operator=(const ScopedCapture&) = delete;
+    ScopedCapture& operator=(ScopedCapture&&) = delete;
+    void* operator new(std::size_t size) = delete;
+
+    friend class CrossThreads;
+    Type* const previous_;
+    Type* const current_;
+  };
+
+  // Creates a bridge point for sharing a single type of instrumentation between
+  // threads. Create this in the main thread, then enable crossing in the worker
+  // thread with CrossThreads. This is only needed if the instrumentation uses
+  // ScopedCapture instead of AutoThreadCrosser.
+  //
+  // NOTE: This isn't visible by default. If you want to make this functionality
+  // available for a specific instrumentation class, add the following with
+  // public visibility within the instrumentation class:
+  //
+  //   using ThreadCapture<Type>::ThreadBridge;
+  class ThreadBridge {
+   public:
+    inline ThreadBridge() : capture_(GetCurrent()) {}
+
+   private:
+    ThreadBridge(const ThreadBridge&) = delete;
+    ThreadBridge(ThreadBridge&&) = delete;
+    ThreadBridge& operator=(const ThreadBridge&) = delete;
+    ThreadBridge& operator=(ThreadBridge&&) = delete;
+    void* operator new(std::size_t size) = delete;
+
+    friend class CrossThreads;
+    Type* const capture_;
+  };
+
+  class AutoThreadCrosser;
+
+  // Connects a worker thread to the main thread via a ThreadBridge for manually
+  // sharing of a single type of instrumentation.
+  //
+  // NOTE: This isn't visible by default. If you want to make this functionality
+  // available for a specific instrumentation class, add the following with
+  // public visibility within the instrumentation class:
+  //
+  //   using ThreadCapture<Type>::CrossThreads;
+  class CrossThreads {
+   public:
+    explicit inline CrossThreads(const ThreadBridge& bridge)
+        : previous_(GetCurrent()) {
+      SetCurrent(bridge.capture_);
+    }
+
+    inline ~CrossThreads() { SetCurrent(previous_); }
+
+   private:
+    explicit inline CrossThreads(const ScopedCapture& capture)
+        : previous_(GetCurrent()) {
+      SetCurrent(capture.current_);
+    }
+
+    CrossThreads(const CrossThreads&) = delete;
+    CrossThreads(CrossThreads&&) = delete;
+    CrossThreads& operator=(const CrossThreads&) = delete;
+    CrossThreads& operator=(CrossThreads&&) = delete;
+    void* operator new(std::size_t size) = delete;
+
+    friend class AutoThreadCrosser;
+    Type* const previous_;
+  };
+
+  // Enables automatic crossing of threads for a specific instrumentation type
+  // when ThreadCrosser::WrapCall or ThreadCrosser::WrapFunction are used. Add
+  // this as a member of the implementation that you want to automatically
+  // share. Use ScopedCapture *instead* if you don't want ThreadCrosser to
+  // automatically share instrumentation.
+  class AutoThreadCrosser : public ThreadCrosser {
+   public:
+    AutoThreadCrosser(Type* capture)
+        : cross_with_(this), capture_to_(capture) {}
+
+    inline Type* Previous() const { return capture_to_.Previous(); }
+
+   private:
+    AutoThreadCrosser(const AutoThreadCrosser&) = delete;
+    AutoThreadCrosser(AutoThreadCrosser&&) = delete;
+    AutoThreadCrosser& operator=(const AutoThreadCrosser&) = delete;
+    AutoThreadCrosser& operator=(AutoThreadCrosser&&) = delete;
+    void* operator new(std::size_t size) = delete;
+
+    void FindTopAndCall(const std::function<void()>& call,
+                        const ReverseScope& reverse_scope) const final;
+
+    void ReconstructContextAndCall(
+        const std::function<void()>& call,
+        const ReverseScope& reverse_scope) const final;
+
+    const ScopedCrosser cross_with_;
+    const ScopedCapture capture_to_;
+  };
+
+  // Gets the most-recent object from the stack of the current thread.
+  static inline Type* GetCurrent() { return current_; }
+
+ private:
+  ThreadCapture(const ThreadCapture&) = delete;
+  ThreadCapture(ThreadCapture&&) = delete;
+  ThreadCapture& operator=(const ThreadCapture&) = delete;
+  ThreadCapture& operator=(ThreadCapture&&) = delete;
+  void* operator new(std::size_t size) = delete;
+
+  static inline void SetCurrent(Type* value) { current_ = value; }
+
+  static thread_local Type* current_;
+};
+
+template <class Type>
+thread_local Type* ThreadCapture<Type>::current_(nullptr);
+
+template <class Type>
+void ThreadCapture<Type>::AutoThreadCrosser::FindTopAndCall(
+    const std::function<void()>& call,
+    const ReverseScope& reverse_scope) const {
+  if (cross_with_.Parent()) {
+    cross_with_.Parent()->FindTopAndCall(
+        call, {cross_with_.Parent(), &reverse_scope});
+  } else {
+    ReconstructContextAndCall(call, reverse_scope);
+  }
+}
+
+template <class Type>
+void ThreadCapture<Type>::AutoThreadCrosser::ReconstructContextAndCall(
+    const std::function<void()>& call,
+    const ReverseScope& reverse_scope) const {
+  // Makes the ThreadCapture available in this scope so that it overrides the
+  // default behavior of instrumentation calls made in call.
+  const CrossThreads capture(capture_to_);
+  if (reverse_scope.previous) {
+    const auto current = reverse_scope.previous->current;
+    assert(current);
+    if (current) {
+      current->ReconstructContextAndCall(call, *reverse_scope.previous);
+    }
+  } else {
+    // Makes the ThreadCrosser available in this scope so that call itself can
+    // cross threads again.
+    const DelegateCrosser crosser(cross_with_);
+    assert(call);
+    if (call) {
+      call();
+    }
+  }
+}
+
+}  // namespace capture_thread
+
+#endif  // THREAD_CAPTURE_H_
diff --git a/base/thread-crosser.cc b/base/thread-crosser.cc
new file mode 100644
--- /dev/null
+++ b/base/thread-crosser.cc
@@ -0,0 +1,33 @@
+/* -----------------------------------------------------------------------------
+Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
+
+#include "thread-crosser.h"
+
+namespace capture_thread {
+
+namespace {
+thread_local ThreadCrosser* current(nullptr);
+}
+
+// static
+ThreadCrosser* ThreadCrosser::GetCurrent() { return current; }
+
+// static
+void ThreadCrosser::SetCurrent(ThreadCrosser* value) { current = value; }
+
+}  // namespace capture_thread
diff --git a/base/thread-crosser.h b/base/thread-crosser.h
new file mode 100644
--- /dev/null
+++ b/base/thread-crosser.h
@@ -0,0 +1,254 @@
+/* -----------------------------------------------------------------------------
+Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
+
+#ifndef THREAD_CROSSER_H_
+#define THREAD_CROSSER_H_
+
+#include <functional>
+#include <type_traits>
+
+#include <cassert>
+
+namespace capture_thread {
+
+// Manages automatic thread-crossing for sharing instrumentation classes derived
+// from ThreadCapture. The static API allows the caller to automatically share
+// all instrumentation types that are in scope, provided they use
+// AutoThreadCrosser to manage scoping. Not all classes will have this enabled,
+// since it can cause unexpected results.
+class ThreadCrosser {
+ public:
+  // Wraps a callback to share instrumentation that's currently in scope with a
+  // worker thread. Call this in the main thread, then pass the returned value
+  // to the worker thread.
+  //
+  // NOTE: The returned function will be invalidated if any instrumentation goes
+  // out of scope; therefore, the main thread must wait for the worker thread to
+  // call it before continuing.
+  static inline std::function<void()> WrapCall(std::function<void()> call) {
+    return WrapFunction(std::move(call));
+  }
+
+  // Wraps an arbitrary function to share instrumentation that's currently in
+  // scope with a worker thread. Call this in the main thread, then pass the
+  // returned value to the worker thread.
+  //
+  // NOTE: The returned function will be invalidated if any instrumentation goes
+  // out of scope; therefore, the main thread must wait for the worker thread to
+  // call it before continuing.
+  template <class Return, class... Args>
+  static inline std::function<Return(Args...)> WrapFunction(
+      Return (*function)(Args...)) {
+    return WrapFunction(std::function<Return(Args...)>(function));
+  }
+
+  // Wraps an arbitrary function to share instrumentation that's currently in
+  // scope with a worker thread. Call this in the main thread, then pass the
+  // returned value to the worker thread.
+  //
+  // NOTE: The returned function will be invalidated if any instrumentation goes
+  // out of scope; therefore, the main thread must wait for the worker thread to
+  // call it before continuing.
+  template <class Return, class... Args>
+  static std::function<Return(Args...)> WrapFunction(
+      std::function<Return(Args...)> function);
+
+ private:
+  ThreadCrosser(const ThreadCrosser&) = delete;
+  ThreadCrosser(ThreadCrosser&&) = delete;
+  ThreadCrosser& operator=(const ThreadCrosser&) = delete;
+  ThreadCrosser& operator=(ThreadCrosser&&) = delete;
+  void* operator new(std::size_t size) = delete;
+
+  ThreadCrosser() = default;
+  virtual ~ThreadCrosser() = default;
+
+  // Keeps track of a reverse call stack when wrapping a callback. (This is used
+  // to create a linked-list of ThreadCrosser on the stack.)
+  struct ReverseScope {
+    const ThreadCrosser* const current;
+    const ReverseScope* const previous;
+  };
+
+  // Performs the function call in the full ThreadCapture context above this
+  // ThreadCrosser.
+  inline void CallInFullContext(const std::function<void()>& call) const {
+    FindTopAndCall(call, {this, nullptr});
+  }
+
+  // Traverses to the top of the ThreadCrosser stack to recursively rebuild the
+  // stack of ThreadCapture, then calls the callback.
+  virtual void FindTopAndCall(const std::function<void()>& call,
+                              const ReverseScope& reverse_scope) const = 0;
+
+  // Instantiates the ThreadCapture context associated with this ThreadCrosser,
+  // then recursively calls the next ThreadCrosser.
+  virtual void ReconstructContextAndCall(
+      const std::function<void()>& call,
+      const ReverseScope& reverse_scope) const = 0;
+
+  class ScopedCrosser;
+
+  // Makes a captured ThreadCrosser available within the current thread. This is
+  // only to allow thread-crossing to happen again within that thread.
+  class DelegateCrosser {
+   public:
+    explicit inline DelegateCrosser(const ScopedCrosser& crosser)
+        : parent_(GetCurrent()) {
+      SetCurrent(crosser.current_);
+    }
+
+    inline ~DelegateCrosser() { SetCurrent(parent_); }
+
+   private:
+    DelegateCrosser(const DelegateCrosser&) = delete;
+    DelegateCrosser(DelegateCrosser&&) = delete;
+    DelegateCrosser& operator=(const DelegateCrosser&) = delete;
+    DelegateCrosser& operator=(DelegateCrosser&&) = delete;
+    void* operator new(std::size_t size) = delete;
+
+    ThreadCrosser* const parent_;
+  };
+
+  // Captures the ThreadCrosser so it can be used by DelegateCrosser, which will
+  // make it available in another thread.
+  class ScopedCrosser {
+   public:
+    explicit inline ScopedCrosser(ThreadCrosser* capture)
+        : parent_(GetCurrent()), current_(capture) {
+      SetCurrent(capture);
+    }
+
+    inline ~ScopedCrosser() { SetCurrent(Parent()); }
+
+    inline ThreadCrosser* Parent() const { return parent_; }
+
+   private:
+    ScopedCrosser(const ScopedCrosser&) = delete;
+    ScopedCrosser(ScopedCrosser&&) = delete;
+    ScopedCrosser& operator=(const ScopedCrosser&) = delete;
+    ScopedCrosser& operator=(ScopedCrosser&&) = delete;
+    void* operator new(std::size_t size) = delete;
+
+    friend class DelegateCrosser;
+    ThreadCrosser* const parent_;
+    ThreadCrosser* const current_;
+  };
+
+  // AutoMove (and its specializations) ensure that argument-passing when
+  // calling a std::function doesn't make copies when it would be inappropriate,
+  // e.g., when Type cannot be copied or moved. This is a class instead of a
+  // function so that Type can be made explicit, which is necessary for type-
+  // checking to work.
+
+  template <class Type>
+  struct AutoMove;
+
+  // AutoCall (and its specializations) ensure that return values are not
+  // inappropriately copied, e.g., when returning by reference, or when the
+  // return type is void. This is a class instead of a function so that the
+  // types can be made explicit, which is necessary for type-checking to work.
+
+  template <class Return, class... Args>
+  struct AutoCall;
+
+  static ThreadCrosser* GetCurrent();
+  static void SetCurrent(ThreadCrosser* value);
+
+  template <class Type>
+  friend class ThreadCapture;
+};
+
+template <class Return, class... Args>
+std::function<Return(Args...)> ThreadCrosser::WrapFunction(
+    std::function<Return(Args...)> function) {
+  const auto current = GetCurrent();
+  if (function && current) {
+    return [current, function](Args... args) -> Return {
+      return AutoCall<Return, Args...>::Execute(*current, function,
+                                                AutoMove<Args>::Pass(args)...);
+    };
+  } else {
+    return function;
+  }
+}
+
+// Handles pass-by-value.
+template <class Type>
+struct ThreadCrosser::AutoMove {
+  static Type&& Pass(Type& value) { return std::move(value); }
+};
+
+// Handles pass-by-reference.
+template <class Type>
+struct ThreadCrosser::AutoMove<Type&> {
+  static Type& Pass(Type& value) { return value; }
+};
+
+// Handles return-by-value.
+template <class Return, class... Args>
+struct ThreadCrosser::AutoCall {
+  static Return Execute(const ThreadCrosser& current,
+                        const std::function<Return(Args...)>& function,
+                        Args... args) {
+    typename std::remove_cv<Return>::type value = Return();
+    current.CallInFullContext([&value, &function, &args...] {
+      value = function(AutoMove<Args>::Pass(args)...);
+    });
+    return value;
+  }
+};
+
+// Handles return-by-reference.
+template <class Return, class... Args>
+struct ThreadCrosser::AutoCall<Return&, Args...> {
+  static Return& Execute(const ThreadCrosser& current,
+                         const std::function<Return&(Args...)>& function,
+                         Args... args) {
+    Return* value(nullptr);
+    current.CallInFullContext([&value, &function, &args...] {
+      value = &function(AutoMove<Args>::Pass(args)...);
+    });
+    assert(value);
+    return *value;
+  }
+};
+
+// Handles void return type.
+template <class... Args>
+struct ThreadCrosser::AutoCall<void, Args...> {
+  static void Execute(const ThreadCrosser& current,
+                      const std::function<void(Args...)>& function,
+                      Args... args) {
+    current.CallInFullContext(
+        [&function, &args...] { function(AutoMove<Args>::Pass(args)...); });
+  }
+};
+
+// Handles callback type.
+template <>
+struct ThreadCrosser::AutoCall<void> {
+  static void Execute(const ThreadCrosser& current,
+                      const std::function<void()>& function) {
+    current.CallInFullContext(function);
+  }
+};
+
+}  // namespace capture_thread
+
+#endif  // THREAD_CROSSER_H_
diff --git a/bin/zeolite-setup.hs b/bin/zeolite-setup.hs
--- a/bin/zeolite-setup.hs
+++ b/bin/zeolite-setup.hs
@@ -124,11 +124,8 @@
       coSources = libraries,
       coExtraFiles = [],
       coExtraPaths = [],
-      coExtraRequires = [],
       coSourcePrefix = path,
-      coExternalDefs = [],
       coMode = CompileRecompile,
-      coOutputName = "",
       coForce = ForceAll
     }
   runCompiler options
diff --git a/capture-thread/include/thread-capture.h b/capture-thread/include/thread-capture.h
deleted file mode 100644
--- a/capture-thread/include/thread-capture.h
+++ /dev/null
@@ -1,217 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2017 Google Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
-
-#ifndef THREAD_CAPTURE_H_
-#define THREAD_CAPTURE_H_
-
-#include <cassert>
-
-#include "thread-crosser.h"
-
-namespace capture_thread {
-
-// Base class for instrumentation classes that need to be made available within
-// a single thread, and possibly shared across threads. Derive a new class Type
-// from ThreadCapture<Type> to define a new instrumentation type. Scoping is
-// managed by adding a ScopedCapture *or* an AutoThreadCrosser member to
-// instantiable implementations of the instrumentation. (If Type is abstract,
-// add the member to the instantiable subclasses.) See ThreadCrosser for info
-// about *automatic* sharing across threads, and ThreadBridge (below) for info
-// about *manually* sharing across threads.
-template <class Type>
-class ThreadCapture {
- protected:
-  ThreadCapture() = default;
-  virtual ~ThreadCapture() = default;
-
-  class CrossThreads;
-
-  // Manages scoping of instrumentation within a single thread. Use ThreadBridge
-  // + CrossThreads to share instrumentation across threads. Alternatively, use
-  // AutoThreadCrosser *instead* of ScopedCapture to allow ThreadCrosser to
-  // handle this automatically.
-  class ScopedCapture {
-   public:
-    explicit inline ScopedCapture(Type* capture)
-        : previous_(GetCurrent()), current_(capture) {
-      SetCurrent(current_);
-    }
-
-    inline ~ScopedCapture() { SetCurrent(Previous()); }
-
-    inline Type* Previous() const { return previous_; }
-
-   private:
-    ScopedCapture(const ScopedCapture&) = delete;
-    ScopedCapture(ScopedCapture&&) = delete;
-    ScopedCapture& operator=(const ScopedCapture&) = delete;
-    ScopedCapture& operator=(ScopedCapture&&) = delete;
-    void* operator new(std::size_t size) = delete;
-
-    friend class CrossThreads;
-    Type* const previous_;
-    Type* const current_;
-  };
-
-  // Creates a bridge point for sharing a single type of instrumentation between
-  // threads. Create this in the main thread, then enable crossing in the worker
-  // thread with CrossThreads. This is only needed if the instrumentation uses
-  // ScopedCapture instead of AutoThreadCrosser.
-  //
-  // NOTE: This isn't visible by default. If you want to make this functionality
-  // available for a specific instrumentation class, add the following with
-  // public visibility within the instrumentation class:
-  //
-  //   using ThreadCapture<Type>::ThreadBridge;
-  class ThreadBridge {
-   public:
-    inline ThreadBridge() : capture_(GetCurrent()) {}
-
-   private:
-    ThreadBridge(const ThreadBridge&) = delete;
-    ThreadBridge(ThreadBridge&&) = delete;
-    ThreadBridge& operator=(const ThreadBridge&) = delete;
-    ThreadBridge& operator=(ThreadBridge&&) = delete;
-    void* operator new(std::size_t size) = delete;
-
-    friend class CrossThreads;
-    Type* const capture_;
-  };
-
-  class AutoThreadCrosser;
-
-  // Connects a worker thread to the main thread via a ThreadBridge for manually
-  // sharing of a single type of instrumentation.
-  //
-  // NOTE: This isn't visible by default. If you want to make this functionality
-  // available for a specific instrumentation class, add the following with
-  // public visibility within the instrumentation class:
-  //
-  //   using ThreadCapture<Type>::CrossThreads;
-  class CrossThreads {
-   public:
-    explicit inline CrossThreads(const ThreadBridge& bridge)
-        : previous_(GetCurrent()) {
-      SetCurrent(bridge.capture_);
-    }
-
-    inline ~CrossThreads() { SetCurrent(previous_); }
-
-   private:
-    explicit inline CrossThreads(const ScopedCapture& capture)
-        : previous_(GetCurrent()) {
-      SetCurrent(capture.current_);
-    }
-
-    CrossThreads(const CrossThreads&) = delete;
-    CrossThreads(CrossThreads&&) = delete;
-    CrossThreads& operator=(const CrossThreads&) = delete;
-    CrossThreads& operator=(CrossThreads&&) = delete;
-    void* operator new(std::size_t size) = delete;
-
-    friend class AutoThreadCrosser;
-    Type* const previous_;
-  };
-
-  // Enables automatic crossing of threads for a specific instrumentation type
-  // when ThreadCrosser::WrapCall or ThreadCrosser::WrapFunction are used. Add
-  // this as a member of the implementation that you want to automatically
-  // share. Use ScopedCapture *instead* if you don't want ThreadCrosser to
-  // automatically share instrumentation.
-  class AutoThreadCrosser : public ThreadCrosser {
-   public:
-    AutoThreadCrosser(Type* capture)
-        : cross_with_(this), capture_to_(capture) {}
-
-    inline Type* Previous() const { return capture_to_.Previous(); }
-
-   private:
-    AutoThreadCrosser(const AutoThreadCrosser&) = delete;
-    AutoThreadCrosser(AutoThreadCrosser&&) = delete;
-    AutoThreadCrosser& operator=(const AutoThreadCrosser&) = delete;
-    AutoThreadCrosser& operator=(AutoThreadCrosser&&) = delete;
-    void* operator new(std::size_t size) = delete;
-
-    void FindTopAndCall(const std::function<void()>& call,
-                        const ReverseScope& reverse_scope) const final;
-
-    void ReconstructContextAndCall(
-        const std::function<void()>& call,
-        const ReverseScope& reverse_scope) const final;
-
-    const ScopedCrosser cross_with_;
-    const ScopedCapture capture_to_;
-  };
-
-  // Gets the most-recent object from the stack of the current thread.
-  static inline Type* GetCurrent() { return current_; }
-
- private:
-  ThreadCapture(const ThreadCapture&) = delete;
-  ThreadCapture(ThreadCapture&&) = delete;
-  ThreadCapture& operator=(const ThreadCapture&) = delete;
-  ThreadCapture& operator=(ThreadCapture&&) = delete;
-  void* operator new(std::size_t size) = delete;
-
-  static inline void SetCurrent(Type* value) { current_ = value; }
-
-  static thread_local Type* current_;
-};
-
-template <class Type>
-thread_local Type* ThreadCapture<Type>::current_(nullptr);
-
-template <class Type>
-void ThreadCapture<Type>::AutoThreadCrosser::FindTopAndCall(
-    const std::function<void()>& call,
-    const ReverseScope& reverse_scope) const {
-  if (cross_with_.Parent()) {
-    cross_with_.Parent()->FindTopAndCall(
-        call, {cross_with_.Parent(), &reverse_scope});
-  } else {
-    ReconstructContextAndCall(call, reverse_scope);
-  }
-}
-
-template <class Type>
-void ThreadCapture<Type>::AutoThreadCrosser::ReconstructContextAndCall(
-    const std::function<void()>& call,
-    const ReverseScope& reverse_scope) const {
-  // Makes the ThreadCapture available in this scope so that it overrides the
-  // default behavior of instrumentation calls made in call.
-  const CrossThreads capture(capture_to_);
-  if (reverse_scope.previous) {
-    const auto current = reverse_scope.previous->current;
-    assert(current);
-    if (current) {
-      current->ReconstructContextAndCall(call, *reverse_scope.previous);
-    }
-  } else {
-    // Makes the ThreadCrosser available in this scope so that call itself can
-    // cross threads again.
-    const DelegateCrosser crosser(cross_with_);
-    assert(call);
-    if (call) {
-      call();
-    }
-  }
-}
-
-}  // namespace capture_thread
-
-#endif  // THREAD_CAPTURE_H_
diff --git a/capture-thread/include/thread-crosser.h b/capture-thread/include/thread-crosser.h
deleted file mode 100644
--- a/capture-thread/include/thread-crosser.h
+++ /dev/null
@@ -1,254 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2017 Google Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
-
-#ifndef THREAD_CROSSER_H_
-#define THREAD_CROSSER_H_
-
-#include <functional>
-#include <type_traits>
-
-#include <cassert>
-
-namespace capture_thread {
-
-// Manages automatic thread-crossing for sharing instrumentation classes derived
-// from ThreadCapture. The static API allows the caller to automatically share
-// all instrumentation types that are in scope, provided they use
-// AutoThreadCrosser to manage scoping. Not all classes will have this enabled,
-// since it can cause unexpected results.
-class ThreadCrosser {
- public:
-  // Wraps a callback to share instrumentation that's currently in scope with a
-  // worker thread. Call this in the main thread, then pass the returned value
-  // to the worker thread.
-  //
-  // NOTE: The returned function will be invalidated if any instrumentation goes
-  // out of scope; therefore, the main thread must wait for the worker thread to
-  // call it before continuing.
-  static inline std::function<void()> WrapCall(std::function<void()> call) {
-    return WrapFunction(std::move(call));
-  }
-
-  // Wraps an arbitrary function to share instrumentation that's currently in
-  // scope with a worker thread. Call this in the main thread, then pass the
-  // returned value to the worker thread.
-  //
-  // NOTE: The returned function will be invalidated if any instrumentation goes
-  // out of scope; therefore, the main thread must wait for the worker thread to
-  // call it before continuing.
-  template <class Return, class... Args>
-  static inline std::function<Return(Args...)> WrapFunction(
-      Return (*function)(Args...)) {
-    return WrapFunction(std::function<Return(Args...)>(function));
-  }
-
-  // Wraps an arbitrary function to share instrumentation that's currently in
-  // scope with a worker thread. Call this in the main thread, then pass the
-  // returned value to the worker thread.
-  //
-  // NOTE: The returned function will be invalidated if any instrumentation goes
-  // out of scope; therefore, the main thread must wait for the worker thread to
-  // call it before continuing.
-  template <class Return, class... Args>
-  static std::function<Return(Args...)> WrapFunction(
-      std::function<Return(Args...)> function);
-
- private:
-  ThreadCrosser(const ThreadCrosser&) = delete;
-  ThreadCrosser(ThreadCrosser&&) = delete;
-  ThreadCrosser& operator=(const ThreadCrosser&) = delete;
-  ThreadCrosser& operator=(ThreadCrosser&&) = delete;
-  void* operator new(std::size_t size) = delete;
-
-  ThreadCrosser() = default;
-  virtual ~ThreadCrosser() = default;
-
-  // Keeps track of a reverse call stack when wrapping a callback. (This is used
-  // to create a linked-list of ThreadCrosser on the stack.)
-  struct ReverseScope {
-    const ThreadCrosser* const current;
-    const ReverseScope* const previous;
-  };
-
-  // Performs the function call in the full ThreadCapture context above this
-  // ThreadCrosser.
-  inline void CallInFullContext(const std::function<void()>& call) const {
-    FindTopAndCall(call, {this, nullptr});
-  }
-
-  // Traverses to the top of the ThreadCrosser stack to recursively rebuild the
-  // stack of ThreadCapture, then calls the callback.
-  virtual void FindTopAndCall(const std::function<void()>& call,
-                              const ReverseScope& reverse_scope) const = 0;
-
-  // Instantiates the ThreadCapture context associated with this ThreadCrosser,
-  // then recursively calls the next ThreadCrosser.
-  virtual void ReconstructContextAndCall(
-      const std::function<void()>& call,
-      const ReverseScope& reverse_scope) const = 0;
-
-  class ScopedCrosser;
-
-  // Makes a captured ThreadCrosser available within the current thread. This is
-  // only to allow thread-crossing to happen again within that thread.
-  class DelegateCrosser {
-   public:
-    explicit inline DelegateCrosser(const ScopedCrosser& crosser)
-        : parent_(GetCurrent()) {
-      SetCurrent(crosser.current_);
-    }
-
-    inline ~DelegateCrosser() { SetCurrent(parent_); }
-
-   private:
-    DelegateCrosser(const DelegateCrosser&) = delete;
-    DelegateCrosser(DelegateCrosser&&) = delete;
-    DelegateCrosser& operator=(const DelegateCrosser&) = delete;
-    DelegateCrosser& operator=(DelegateCrosser&&) = delete;
-    void* operator new(std::size_t size) = delete;
-
-    ThreadCrosser* const parent_;
-  };
-
-  // Captures the ThreadCrosser so it can be used by DelegateCrosser, which will
-  // make it available in another thread.
-  class ScopedCrosser {
-   public:
-    explicit inline ScopedCrosser(ThreadCrosser* capture)
-        : parent_(GetCurrent()), current_(capture) {
-      SetCurrent(capture);
-    }
-
-    inline ~ScopedCrosser() { SetCurrent(Parent()); }
-
-    inline ThreadCrosser* Parent() const { return parent_; }
-
-   private:
-    ScopedCrosser(const ScopedCrosser&) = delete;
-    ScopedCrosser(ScopedCrosser&&) = delete;
-    ScopedCrosser& operator=(const ScopedCrosser&) = delete;
-    ScopedCrosser& operator=(ScopedCrosser&&) = delete;
-    void* operator new(std::size_t size) = delete;
-
-    friend class DelegateCrosser;
-    ThreadCrosser* const parent_;
-    ThreadCrosser* const current_;
-  };
-
-  // AutoMove (and its specializations) ensure that argument-passing when
-  // calling a std::function doesn't make copies when it would be inappropriate,
-  // e.g., when Type cannot be copied or moved. This is a class instead of a
-  // function so that Type can be made explicit, which is necessary for type-
-  // checking to work.
-
-  template <class Type>
-  struct AutoMove;
-
-  // AutoCall (and its specializations) ensure that return values are not
-  // inappropriately copied, e.g., when returning by reference, or when the
-  // return type is void. This is a class instead of a function so that the
-  // types can be made explicit, which is necessary for type-checking to work.
-
-  template <class Return, class... Args>
-  struct AutoCall;
-
-  static ThreadCrosser* GetCurrent();
-  static void SetCurrent(ThreadCrosser* value);
-
-  template <class Type>
-  friend class ThreadCapture;
-};
-
-template <class Return, class... Args>
-std::function<Return(Args...)> ThreadCrosser::WrapFunction(
-    std::function<Return(Args...)> function) {
-  const auto current = GetCurrent();
-  if (function && current) {
-    return [current, function](Args... args) -> Return {
-      return AutoCall<Return, Args...>::Execute(*current, function,
-                                                AutoMove<Args>::Pass(args)...);
-    };
-  } else {
-    return function;
-  }
-}
-
-// Handles pass-by-value.
-template <class Type>
-struct ThreadCrosser::AutoMove {
-  static Type&& Pass(Type& value) { return std::move(value); }
-};
-
-// Handles pass-by-reference.
-template <class Type>
-struct ThreadCrosser::AutoMove<Type&> {
-  static Type& Pass(Type& value) { return value; }
-};
-
-// Handles return-by-value.
-template <class Return, class... Args>
-struct ThreadCrosser::AutoCall {
-  static Return Execute(const ThreadCrosser& current,
-                        const std::function<Return(Args...)>& function,
-                        Args... args) {
-    typename std::remove_cv<Return>::type value = Return();
-    current.CallInFullContext([&value, &function, &args...] {
-      value = function(AutoMove<Args>::Pass(args)...);
-    });
-    return value;
-  }
-};
-
-// Handles return-by-reference.
-template <class Return, class... Args>
-struct ThreadCrosser::AutoCall<Return&, Args...> {
-  static Return& Execute(const ThreadCrosser& current,
-                         const std::function<Return&(Args...)>& function,
-                         Args... args) {
-    Return* value(nullptr);
-    current.CallInFullContext([&value, &function, &args...] {
-      value = &function(AutoMove<Args>::Pass(args)...);
-    });
-    assert(value);
-    return *value;
-  }
-};
-
-// Handles void return type.
-template <class... Args>
-struct ThreadCrosser::AutoCall<void, Args...> {
-  static void Execute(const ThreadCrosser& current,
-                      const std::function<void(Args...)>& function,
-                      Args... args) {
-    current.CallInFullContext(
-        [&function, &args...] { function(AutoMove<Args>::Pass(args)...); });
-  }
-};
-
-// Handles callback type.
-template <>
-struct ThreadCrosser::AutoCall<void> {
-  static void Execute(const ThreadCrosser& current,
-                      const std::function<void()>& function) {
-    current.CallInFullContext(function);
-  }
-};
-
-}  // namespace capture_thread
-
-#endif  // THREAD_CROSSER_H_
diff --git a/capture-thread/src/thread-crosser.cc b/capture-thread/src/thread-crosser.cc
deleted file mode 100644
--- a/capture-thread/src/thread-crosser.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2017 Google Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
-
-#include "thread-crosser.h"
-
-namespace capture_thread {
-
-namespace {
-thread_local ThreadCrosser* current(nullptr);
-}
-
-// static
-ThreadCrosser* ThreadCrosser::GetCurrent() { return current; }
-
-// static
-void ThreadCrosser::SetCurrent(ThreadCrosser* value) { current = value; }
-
-}  // namespace capture_thread
diff --git a/lib/file/.zeolite-module b/lib/file/.zeolite-module
--- a/lib/file/.zeolite-module
+++ b/lib/file/.zeolite-module
@@ -4,11 +4,13 @@
   "../util"
 ]
 extra_files: [
-  "file/Category_RawFileReader.cpp"
-  "file/Category_RawFileWriter.cpp"
-]
-external: [
-  RawFileReader
-  RawFileWriter
+  category_source {
+    source: "file/Category_RawFileReader.cpp"
+    categories: [RawFileReader]
+  }
+  category_source {
+    source: "file/Category_RawFileWriter.cpp"
+    categories: [RawFileWriter]
+  }
 ]
-mode: incremental
+mode: incremental {}
diff --git a/lib/util/.zeolite-module b/lib/util/.zeolite-module
--- a/lib/util/.zeolite-module
+++ b/lib/util/.zeolite-module
@@ -1,13 +1,17 @@
 root: ".."
 path: "util"
 extra_files: [
-  "util/Category_Argv.cpp"
-  "util/Category_SimpleInput.cpp"
-  "util/Category_SimpleOutput.cpp"
-]
-external: [
-  Argv
-  SimpleInput
-  SimpleOutput
+  category_source {
+    source: "util/Category_Argv.cpp"
+    categories: [Argv]
+  }
+  category_source {
+    source: "util/Category_SimpleInput.cpp"
+    categories: [SimpleInput]
+  }
+  category_source {
+    source: "util/Category_SimpleOutput.cpp"
+    categories: [SimpleOutput]
+  }
 ]
-mode: incremental
+mode: incremental {}
diff --git a/src/Cli/CompileMetadata.hs b/src/Cli/CompileMetadata.hs
--- a/src/Cli/CompileMetadata.hs
+++ b/src/Cli/CompileMetadata.hs
@@ -39,7 +39,6 @@
     cmNamespace :: String, -- TODO: Use Namespace here?
     cmPublicDeps :: [String],
     cmPrivateDeps :: [String],
-    cmExtraRequires :: [CategoryIdentifier],
     cmCategories :: [String],
     cmSubdirs :: [String],
     cmPublicFiles :: [String],
@@ -47,6 +46,7 @@
     cmTestFiles :: [String],
     cmHxxFiles :: [String],
     cmCxxFiles :: [String],
+    cmLinkFlags :: [String],
     cmObjectFiles :: [ObjectFile]
   }
   deriving (Eq,Show)
@@ -88,11 +88,8 @@
     rmPath :: String,
     rmPublicDeps :: [String],
     rmPrivateDeps :: [String],
-    rmExtraFiles :: [String],
+    rmExtraFiles :: [ExtraSource],
     rmExtraPaths :: [String],
-    rmExtraRequires :: [String],
-    rmExternalDefs :: [String],
-    rmMode :: CompileMode,
-    rmOutputName :: String
+    rmMode :: CompileMode
   }
   deriving (Eq,Show)
diff --git a/src/Cli/CompileOptions.hs b/src/Cli/CompileOptions.hs
--- a/src/Cli/CompileOptions.hs
+++ b/src/Cli/CompileOptions.hs
@@ -21,9 +21,14 @@
 module Cli.CompileOptions (
   CompileOptions(..),
   CompileMode(..),
+  ExtraSource(..),
   ForceMode(..),
   HelpMode(..),
   emptyCompileOptions,
+  getLinkFlags,
+  getSourceCategories,
+  getSourceDeps,
+  getSourceFile,
   isCompileBinary,
   isCompileIncremental,
   isCompileRecompile,
@@ -39,13 +44,10 @@
     coPublicDeps :: [String],
     coPrivateDeps :: [String],
     coSources :: [String],
-    coExtraFiles :: [String],
+    coExtraFiles :: [ExtraSource],
     coExtraPaths :: [String],
-    coExtraRequires :: [String],
     coSourcePrefix :: String,
-    coExternalDefs :: [String],
     coMode :: CompileMode,
-    coOutputName :: String,
     coForce :: ForceMode
   } deriving (Show)
 
@@ -58,14 +60,34 @@
     coSources = [],
     coExtraFiles = [],
     coExtraPaths = [],
-    coExtraRequires = [],
     coSourcePrefix = "",
-    coExternalDefs = [],
     coMode = CompileUnspecified,
-    coOutputName = "",
     coForce = DoNotForce
   }
 
+data ExtraSource =
+  CategorySource {
+    csSource :: String,
+    csCategories :: [String],
+    csDepCategories :: [String]
+  } |
+  OtherSource {
+    osSource :: String
+  }
+  deriving (Eq,Show)
+
+getSourceFile :: ExtraSource -> String
+getSourceFile (CategorySource s _ _) = s
+getSourceFile (OtherSource s)        = s
+
+getSourceCategories :: ExtraSource -> [String]
+getSourceCategories (CategorySource _ cs _) = cs
+getSourceCategories (OtherSource _)         = []
+
+getSourceDeps :: ExtraSource -> [String]
+getSourceDeps (CategorySource _ _ ds) = ds
+getSourceDeps (OtherSource _)         = []
+
 data HelpMode = HelpNeeded | HelpNotNeeded | HelpUnspecified deriving (Eq,Show)
 
 data ForceMode = DoNotForce | AllowRecompile | ForceRecompile | ForceAll deriving (Eq,Ord,Show)
@@ -73,24 +95,28 @@
 data CompileMode =
   CompileBinary {
     cbCategory :: String,
-    cbFunction :: String
+    cbFunction :: String,
+    cbOutputName :: String,
+    cbLinkFlags :: [String]
   } |
   ExecuteTests {
     etInclude :: [String]
   } |
-  CompileIncremental |
+  CompileIncremental {
+    ciLinkFlags :: [String]
+  } |
   CompileRecompile |
   CreateTemplates |
   CompileUnspecified
   deriving (Eq,Show)
 
 isCompileBinary :: CompileMode -> Bool
-isCompileBinary (CompileBinary _ _) = True
-isCompileBinary _                   = False
+isCompileBinary (CompileBinary _ _ _ _) = True
+isCompileBinary _                       = False
 
 isCompileIncremental :: CompileMode -> Bool
-isCompileIncremental CompileIncremental = True
-isCompileIncremental _                  = False
+isCompileIncremental (CompileIncremental _) = True
+isCompileIncremental _                      = False
 
 isCompileRecompile :: CompileMode -> Bool
 isCompileRecompile CompileRecompile = True
@@ -107,3 +133,8 @@
 maybeDisableHelp :: HelpMode -> HelpMode
 maybeDisableHelp HelpUnspecified = HelpNotNeeded
 maybeDisableHelp h               = h
+
+getLinkFlags :: CompileMode -> [String]
+getLinkFlags (CompileBinary _ _ _ lf) = lf
+getLinkFlags (CompileIncremental lf)  = lf
+getLinkFlags _                        = []
diff --git a/src/Cli/Compiler.hs b/src/Cli/Compiler.hs
--- a/src/Cli/Compiler.hs
+++ b/src/Cli/Compiler.hs
@@ -21,6 +21,7 @@
 ) where
 
 import Control.Monad (when)
+import Data.Either (partitionEithers)
 import Data.List (intercalate,isSuffixOf,nub,sort)
 import System.Directory
 import System.Exit
@@ -49,9 +50,10 @@
 
 
 runCompiler :: CompileOptions -> IO ()
-runCompiler (CompileOptions _ _ _ ds _ _ _ p _ (ExecuteTests tp) _ f) = do
+runCompiler (CompileOptions _ _ _ ds _ _ p (ExecuteTests tp) f) = do
   (backend,resolver) <- loadConfig
-  ds' <- sequence $ map (preloadModule backend resolver) ds
+  base <- resolveBaseModule resolver
+  ds' <- sequence $ map (preloadModule backend base) ds
   let possibleTests = Set.fromList $ concat $ map getTestsFromPreload ds'
   case Set.toList $ allowTests `Set.difference` possibleTests of
        [] -> return ()
@@ -59,13 +61,12 @@
          hPutStr stderr $ "Some test files do not occur in the selected modules: " ++
                           intercalate ", " (map show ts) ++ "\n"
          exitFailure
-  allResults <- fmap concat $ sequence $ map (runTests backend) ds'
+  allResults <- fmap concat $ sequence $ map (runTests backend base) ds'
   let passed = sum $ map (fst . fst) allResults
   let failed = sum $ map (snd . fst) allResults
   processResults passed failed (mergeAllM $ map snd allResults) where
-    preloadModule b r d = do
+    preloadModule b base d = do
       m <- loadMetadata (p </> d)
-      base <- resolveBaseModule r
       (fr1,deps1) <- loadPublicDeps (getCompilerHash b) [base,p </> d]
       checkAllowedStale fr1 f
       (fr2,deps2) <- loadPrivateDeps (getCompilerHash b) deps1
@@ -74,11 +75,11 @@
     getTestsFromPreload (_,m,_,_) = cmTestFiles m
     allowTests = Set.fromList tp
     isTestAllowed t = if null allowTests then True else t `Set.member` allowTests
-    runTests :: CompilerBackend b => b ->
+    runTests :: CompilerBackend b => b -> String ->
                 (String,CompileMetadata,[CompileMetadata],[CompileMetadata]) ->
                 IO [((Int,Int),CompileInfo ())]
-    runTests b (d,m,deps1,deps2) = do
-      let paths = getIncludePathsForDeps deps1
+    runTests b base (d,m,deps1,deps2) = do
+      let paths = base:(getIncludePathsForDeps deps1)
       let ss = fixPaths $ getSourceFilesForDeps deps1
       let os = getObjectFilesForDeps deps2
       ss' <- zipWithContents p ss
@@ -98,7 +99,7 @@
       | otherwise = do
           hPutStrLn stderr $ "\nPassed: " ++ show passed ++ " test(s), Failed: " ++ show failed ++ " test(s)"
           hPutStrLn stderr $ "Zeolite tests passed."
-runCompiler (CompileOptions h _ _ ds _ _ _ p _ CompileRecompile _ f) = do
+runCompiler (CompileOptions h _ _ ds _ _ p CompileRecompile f) = do
   (backend,_) <- loadConfig
   fmap mergeAll $ sequence $ map (recompileSingle $ getCompilerHash backend) ds where
     recompileSingle h2 d0 = do
@@ -112,7 +113,7 @@
         maybeCompile (Just rm') upToDate
           | f < ForceAll && upToDate = hPutStrLn stderr $ "Path " ++ d0 ++ " is up to date."
           | otherwise = do
-              let (ModuleConfig p2 d is is2 es ep ec ex m o) = rm'
+              let (ModuleConfig p2 d is is2 es ep m) = rm'
               -- In case the module is manually configured with a p such as "..",
               -- since the absolute path might not be known ahead of time.
               absolute <- canonicalizePath (p </> d0)
@@ -124,15 +125,12 @@
                   coSources = [d],
                   coExtraFiles = es,
                   coExtraPaths = ep,
-                  coExtraRequires = ec,
                   coSourcePrefix = fixed,
-                  coExternalDefs = ex,
                   coMode = m,
-                  coOutputName = o,
                   coForce = if f == ForceAll then ForceRecompile else AllowRecompile
                 }
               runCompiler recompile
-runCompiler (CompileOptions _ is is2 ds es ep ec p ex m o f) = do
+runCompiler (CompileOptions _ is is2 ds es ep p m f) = do
   (backend,resolver) <- loadConfig
   as  <- fmap fixPaths $ sequence $ map (resolveModule resolver p) is
   as2 <- fmap fixPaths $ sequence $ map (resolveModule resolver p) is2
@@ -147,7 +145,6 @@
         createBinary backend resolver (deps ++ deps2) m ms
   hPutStrLn stderr $ "Zeolite compilation succeeded." where
     ep' = fixPaths $ map (p </>) ep
-    es' = fixPaths $ map (p </>) es
     processPath b r deps as as2 d = do
       isConfigured <- isPathConfigured d
       when (isConfigured && f == DoNotForce) $ do
@@ -161,12 +158,9 @@
         rmPath = d,
         rmPublicDeps = as,
         rmPrivateDeps = as2,
-        rmExtraFiles = sort es,
-        rmExtraPaths = sort ep,
-        rmExtraRequires = sort ec,
-        rmExternalDefs = ex,
-        rmMode = m,
-        rmOutputName = o
+        rmExtraFiles = es,
+        rmExtraPaths = ep,
+        rmMode = m
       }
       when (f == DoNotForce || f == ForceAll) $ writeRecompile (p </> d) rm
       (ps,xs,ts) <- findSourceFiles p d
@@ -182,17 +176,17 @@
                     return $ bpDeps ++ deps
       let ss = fixPaths $ getSourceFilesForDeps deps2
       ss' <- zipWithContents p ss
-      let paths = getIncludePathsForDeps deps2
+      let paths = base:(getIncludePathsForDeps deps2)
       ps' <- zipWithContents p ps
       xs' <- zipWithContents p xs
       ns0 <- canonicalizePath (p </> d) >>= return . StaticNamespace . publicNamespace
       let ns2 = map StaticNamespace $ filter (not . null) $ getNamespacesForDeps deps
       let fs = compileAll ns0 ns2 ss' ps' xs'
-      writeOutput b r paths ns0 deps2 d as as2
+      writeOutput b paths ns0 deps2 d as as2
                   (map takeFileName ps)
                   (map takeFileName xs)
                   (map takeFileName ts) fs
-    writeOutput b r paths ns0 deps d as as2 ps xs ts fs
+    writeOutput b paths ns0 deps d as as2 ps xs ts fs
       | isCompileError fs = do
           formatWarnings fs
           hPutStr stderr $ "Compiler errors:\n" ++ (show $ getCompileError fs)
@@ -207,48 +201,29 @@
           let hxx   = filter (isSuffixOf ".hpp" . coFilename)       fs'
           let other = filter (not . isSuffixOf ".hpp" . coFilename) fs'
           os1 <- sequence $ map (writeOutputFile b (show ns0) paths' d) $ hxx ++ other
-          actual <- resolveModule r p d
-          isBase <- isBaseModule r actual
-          -- Base files should be compiled to .o and not .a.
-          let extraComp = if isBase
-                             then compileBuiltinFile
-                             else compileExtraFile
-          os2 <- fmap concat $ sequence $ map (extraComp b (show ns0) paths' d) es'
-          let (hxx',cxx,os') = sortCompiledFiles $ map (\f2 -> show (coNamespace f2) </> coFilename f2) fs' ++ es'
+          let files = map (\f2 -> getCachedPath (p </> d) (show $ coNamespace f2) (coFilename f2)) fs' ++
+                      map (\f2 -> p </> getSourceFile f2) es
+          files' <- sequence $ map checkOwnedFile files
+          os2 <- fmap concat $ sequence $ map (compileExtraSource b (show ns0) paths' d) es
+          let (hxx',cxx,os') = sortCompiledFiles files'
+          let (osCat,osOther) = partitionEithers os2
           path <- canonicalizePath $ p </> d
-          let os1' = resolveObjectDeps path os1 deps
-          let cm0 = CompileMetadata {
+          let os1' = resolveObjectDeps path (os1 ++ osCat) deps
+          let cm = CompileMetadata {
               cmVersionHash = getCompilerHash b,
               cmPath = path,
               cmNamespace = show ns0,
               cmPublicDeps = as,
               cmPrivateDeps = as2,
-              cmExtraRequires = [],
               cmCategories = sort $ map show pc,
-              cmSubdirs = sort $ ss' ++ ep',
+              cmSubdirs = sort $ ss',
               cmPublicFiles = sort ps,
               cmPrivateFiles = sort xs,
               cmTestFiles = sort ts,
               cmHxxFiles = sort hxx',
               cmCxxFiles = sort cxx,
-              cmObjectFiles = os1' ++ os2 ++ map OtherObjectFile os'
-            }
-          let ec' = resolveCategoryDeps ec (cm0:deps)
-          let cm = CompileMetadata {
-              cmVersionHash = cmVersionHash cm0,
-              cmPath = cmPath cm0,
-              cmNamespace = cmNamespace cm0,
-              cmPublicDeps = cmPublicDeps cm0,
-              cmPrivateDeps = cmPrivateDeps cm0,
-              cmExtraRequires = ec',
-              cmCategories = cmCategories cm0,
-              cmSubdirs = cmSubdirs cm0,
-              cmPublicFiles = cmPublicFiles cm0,
-              cmPrivateFiles = cmPrivateFiles cm0,
-              cmTestFiles = cmTestFiles cm0,
-              cmHxxFiles = cmHxxFiles cm0,
-              cmCxxFiles = cmCxxFiles cm0,
-              cmObjectFiles = cmObjectFiles cm0
+              cmLinkFlags = getLinkFlags m,
+              cmObjectFiles = os1' ++ osOther ++ map OtherObjectFile os'
             }
           when (not $ isCreateTemplates m) $ writeMetadata (p </> d) cm
           return (cm,mf)
@@ -269,16 +244,41 @@
            o2 <- runCxxCommand b command
            return $ ([o2],ca)
          else return ([],ca)
-    compileExtraFile = compileExtraCommon True
-    compileBuiltinFile = compileExtraCommon False
-    compileExtraCommon e b ns0 paths d f2
+    compileExtraSource b ns0 paths d (CategorySource f2 cs ds2) = do
+      f2' <- compileExtraFile False b ns0 paths d f2
+      let ds2' = nub $ cs ++ ds2
+      case f2' of
+           Nothing -> return []
+           Just o  -> return $ map (\c -> Left $ ([o],fakeCxxForSource ns0 ds2' c)) cs
+    compileExtraSource b ns0 paths d (OtherSource f2) = do
+      f2' <- compileExtraFile True b ns0 paths d f2
+      case f2' of
+           Just o  -> return [Right $ OtherObjectFile o]
+           Nothing -> return []
+    fakeCxxForSource ns ds2 c = CxxOutput {
+        coCategory = Just (CategoryName c),
+        coFilename = "",
+        coNamespace = ns',
+        coUsesNamespace = [ns'],
+        coUsesCategory = map CategoryName ds2,
+        coOutput = []
+      } where
+        ns' = if null ns then NoNamespace else StaticNamespace ns
+    checkOwnedFile f2 = do
+      exists <- doesFileExist f2
+      when (not exists) $ do
+        hPutStrLn stderr $ "Owned file \"" ++ f2 ++ "\" does not exist."
+        hPutStrLn stderr $ "Zeolite compilation failed."
+        exitFailure
+      canonicalizePath f2
+    compileExtraFile e b ns0 paths d f2
       | isSuffixOf ".cpp" f2 || isSuffixOf ".cc" f2 = do
-          let f2' = p </> d </> f2
+          let f2' = p </> f2
           createCachePath (p </> d)
           let command = CompileToObject f2' (getCachedPath (p </> d) "" "") dynamicNamespaceName ns0 paths e
-          o2 <- runCxxCommand b command
-          return [OtherObjectFile o2]
-      | otherwise = return []
+          fmap Just $ runCxxCommand b command
+      | isSuffixOf ".a" f2 || isSuffixOf ".o" f2 = return (Just f2)
+      | otherwise = return Nothing
     processTemplates deps d = do
       (ps,xs,_) <- findSourceFiles p d
       ps' <- zipWithContents p ps
@@ -325,7 +325,7 @@
           cnNamespaces = ns0:ns2,
           cnPublic = cs'',
           cnPrivate = xa,
-          cnExternal = ex
+          cnExternal = concat $ map getSourceCategories es
         }
       xx <- compileCategoryModule cm
       let pc = map getCategoryName cs''
@@ -339,11 +339,7 @@
     addIncludes tm fs = do
       cs <- fmap concat $ collectAllOrErrorM $ map parsePublicSource fs
       includeNewTypes tm cs
-    getBinaryName (CompileBinary n _)
-      | null o    = canonicalizePath $ p </> head ds </> n
-      | otherwise = canonicalizePath $ p </> head ds </> o
-    getBinaryName _ = return ""
-    createBinary b r deps ma@(CompileBinary n _) ms
+    createBinary b r deps (CompileBinary n _ o lf) ms
       | length ms > 1 = do
         hPutStrLn stderr $ "Multiple matches for main category " ++ n ++ "."
         exitFailure
@@ -351,7 +347,9 @@
         hPutStrLn stderr $ "Main category " ++ n ++ " not found."
         exitFailure
       | otherwise = do
-          f0 <- getBinaryName ma
+          f0 <- if null o
+                   then canonicalizePath $ p </> head ds </> n
+                   else canonicalizePath $ p </> head ds </> o
           let (CxxOutput _ _ _ ns2 req content) = head ms
           -- TODO: Create a helper or a constant or something.
           (o',h) <- mkstemps "/tmp/zmain_" ".cpp"
@@ -360,17 +358,17 @@
           base <- resolveBaseModule r
           (_,bpDeps) <- loadPublicDeps (getCompilerHash b) [base]
           (_,deps2) <- loadPrivateDeps (getCompilerHash b) (bpDeps ++ deps)
-          let paths = fixPaths $ getIncludePathsForDeps deps2
+          let lf' = lf ++ getLinkFlagsForDeps deps2
+          let paths = fixPaths $ base:(getIncludePathsForDeps deps2)
           let os    = getObjectFilesForDeps deps2
-          let req2 = getRequiresFromDeps deps2
-          let ofr = getObjectFileResolver req2 os
+          let ofr = getObjectFileResolver os
           let os' = ofr ns2 req
-          let command = CompileToBinary o' os' f0 paths
+          let command = CompileToBinary o' os' f0 paths lf'
           hPutStrLn stderr $ "Creating binary " ++ f0
           _ <- runCxxCommand b command
           removeFile o'
     createBinary _ _ _ _ _ = return ()
-    maybeCreateMain cm (CompileBinary n f2) =
+    maybeCreateMain cm (CompileBinary n f2 _ _) =
       fmap (:[]) $ compileModuleMain cm (CategoryName n) (FunctionName f2)
     maybeCreateMain _ _ = return []
 
diff --git a/src/Cli/ParseCompileOptions.hs b/src/Cli/ParseCompileOptions.hs
--- a/src/Cli/ParseCompileOptions.hs
+++ b/src/Cli/ParseCompileOptions.hs
@@ -24,16 +24,14 @@
 ) where
 
 import Control.Monad (when)
-import Data.List (intercalate,isSuffixOf)
+import Data.List (isSuffixOf)
 import System.Directory
 import System.Exit
-import System.FilePath (takeExtension)
 import System.IO
 import Text.Regex.TDFA -- Not safe!
 
 import Base.CompileError
 import Cli.CompileOptions
-import Cli.ProcessMetadata (allowedExtraTypes)
 import Config.LoadConfig (compilerVersion,rootPath)
 
 
@@ -58,7 +56,6 @@
     "  --version: Show the compiler version and immediately exit.",
     "",
     "Options:",
-    "  -e [path|file]: Include an extra source file or path during compilation.",
     "  -f: Force compilation instead of recompiling with -r.",
     "  -i [path]: A single source path to include as a *public* dependency.",
     "  -I [path]: A single source path to include as a *private* dependency.",
@@ -103,101 +100,88 @@
     | otherwise = argError n f $ "Invalid file path for " ++ o ++ "."
   checkCategoryName n c o
     | c =~ "^[A-Z][A-Za-z0-9]+$" = return ()
-    | null o    = argError n c "Invalid category name."
     | otherwise = argError n c $ "Invalid category name for " ++ o ++ "."
   checkFunctionName n d o
     | d =~ "^[a-z][A-Za-z0-9]+$" = return ()
-    | null d    = argError n d "Invalid function name."
     | otherwise = argError n d $ "Invalid function name for " ++ o ++ "."
 
   parseSingle _ [] = undefined
 
-  parseSingle (CompileOptions _ is is2 ds es ep ec p ex m o f) ((_,"-h"):os) =
-    return (os,CompileOptions HelpNeeded is is2 ds es ep ec p ex m o f)
+  parseSingle (CompileOptions _ is is2 ds es ep p m f) ((_,"-h"):os) =
+    return (os,CompileOptions HelpNeeded is is2 ds es ep p m f)
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o _) ((_,"-f"):os) =
-    return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p ex m o ForceAll)
+  parseSingle (CompileOptions h is is2 ds es ep p m _) ((_,"-f"):os) =
+    return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep p m ForceAll)
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-c"):os)
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"-c"):os)
     | m /= CompileUnspecified = argError n "-c" "Compiler mode already set."
-    | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p ex CompileIncremental o f)
+    | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep p (CompileIncremental []) f)
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-r"):os)
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"-r"):os)
     | m /= CompileUnspecified = argError n "-r" "Compiler mode already set."
-    | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p ex CompileRecompile o f)
+    | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep p CompileRecompile f)
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-t"):os)
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"-t"):os)
     | m /= CompileUnspecified = argError n "-t" "Compiler mode already set."
-    | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p ex (ExecuteTests []) o f)
+    | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep p (ExecuteTests []) f)
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"--templates"):os)
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"--templates"):os)
     | m /= CompileUnspecified = argError n "-t" "Compiler mode already set."
-    | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p ex CreateTemplates o f)
+    | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep p CreateTemplates f)
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-m"):os)
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"-m"):os)
     | m /= CompileUnspecified = argError n "-m" "Compiler mode already set."
     | otherwise = update os where
       update ((n2,c):os2) =  do
         (t,fn) <- check $ break (== '.') c
         checkCategoryName n2 t  "-m"
         checkFunctionName n2 fn "-m"
-        return (os2,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p ex (CompileBinary t fn) o f) where
+        return (os2,CompileOptions (maybeDisableHelp h) is is2 ds es ep p (CompileBinary t fn "" []) f) where
           check (t,"")     = return (t,defaultMainFunc)
           check (t,'.':fn) = return (t,fn)
           check _          = argError n2 "-m" $ "Invalid entry point \"" ++ c ++ "\"."
       update _ = argError n "-m" "Requires a category name."
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-o"):os)
+  parseSingle (CompileOptions h is is2 ds es ep p (CompileBinary t fn o lf) f) ((n,"-o"):os)
     | not $ null o = argError n "-o" "Output name already set."
     | otherwise = update os where
       update ((n2,o2):os2) = do
         checkPathName n2 o2 "-o"
-        return (os2,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p ex m o2 f)
+        return (os2,CompileOptions (maybeDisableHelp h) is is2 ds es ep p (CompileBinary t fn o2 lf) f)
       update _ = argError n "-o" "Requires an output name."
+  parseSingle _ ((n,"-o"):_) = argError n "-o" "Set mode to binary (-m) first"
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-i"):os) = update os where
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"-i"):os) = update os where
     update ((n2,d):os2)
       | isSuffixOf ".0rp" d = argError n2 d "Cannot directly include .0rp source files."
       | isSuffixOf ".0rx" d = argError n2 d "Cannot directly include .0rx source files."
       | isSuffixOf ".0rt" d = argError n2 d "Cannot directly include .0rt test files."
       | otherwise = do
           checkPathName n2 d "-i"
-          return (os2,CompileOptions (maybeDisableHelp h) (is ++ [d]) is2 ds es ep ec p ex m o f)
+          return (os2,CompileOptions (maybeDisableHelp h) (is ++ [d]) is2 ds es ep p m f)
     update _ = argError n "-i" "Requires a source path."
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-I"):os) = update os where
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"-I"):os) = update os where
     update ((n2,d):os2)
       | isSuffixOf ".0rp" d = argError n2 d "Cannot directly include .0rp source files."
       | isSuffixOf ".0rx" d = argError n2 d "Cannot directly include .0rx source files."
       | isSuffixOf ".0rt" d = argError n2 d "Cannot directly include .0rt test files."
       | otherwise = do
           checkPathName n2 d "-i"
-          return (os2,CompileOptions (maybeDisableHelp h) is (is2 ++ [d]) ds es ep ec p ex m o f)
+          return (os2,CompileOptions (maybeDisableHelp h) is (is2 ++ [d]) ds es ep p m f)
     update _ = argError n "-I" "Requires a source path."
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-e"):os) = update os where
-    update ((n2,e):os2)
-      | any (flip isSuffixOf e) allowedExtraTypes = do
-          checkPathName n2 e "-e"
-          return (os2,CompileOptions (maybeDisableHelp h) is is2 ds (es ++ [e]) ep ec p ex m o f)
-      | takeExtension e == "" || e == "." = do
-          checkPathName n2 e "-e"
-          return (os2,CompileOptions (maybeDisableHelp h) is is2 ds es (ep ++ [e]) ec p ex m o f)
-      | otherwise = argError n2 "-e" $ "Only " ++ intercalate ", " allowedExtraTypes ++
-                                       " and directory sources are allowed."
-    update _ = argError n "-e" "Requires a source filename."
-
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,"-p"):os)
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"-p"):os)
     | not $ null p = argError n "-p" "Path prefix already set."
     | otherwise = update os where
       update ((n2,p2):os2) = do
         checkPathName n2 p2 "-p"
-        return (os2,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p2 ex m o f)
+        return (os2,CompileOptions (maybeDisableHelp h) is is2 ds es ep p2 m f)
       update _ = argError n "-p" "Requires a path prefix."
 
   parseSingle _ ((n,o@('-':_)):_) = argError n o "Unknown option."
 
-  parseSingle (CompileOptions h is is2 ds es ep ec p ex m o f) ((n,d):os)
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,d):os)
       | isSuffixOf ".0rp" d = argError n d "Cannot directly include .0rp source files."
       | isSuffixOf ".0rx" d = argError n d "Cannot directly include .0rx source files."
       | isSuffixOf ".0rt" d = do
@@ -205,39 +189,20 @@
           argError n d "Test mode (-t) must be enabled before listing any .0rt test files."
         checkPathName n d ""
         let (ExecuteTests tp) = m
-        return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep ec p ex (ExecuteTests $ tp ++ [d]) o f)
+        return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep p (ExecuteTests $ tp ++ [d]) f)
       | otherwise = do
         checkPathName n d ""
-        return (os,CompileOptions (maybeDisableHelp h) is is2 (ds ++ [d]) es ep ec p ex m o f)
+        return (os,CompileOptions (maybeDisableHelp h) is is2 (ds ++ [d]) es ep p m f)
 
 validateCompileOptions :: CompileErrorM m => CompileOptions -> m CompileOptions
-validateCompileOptions co@(CompileOptions h is is2 ds es ep _ _ _ m o _)
+validateCompileOptions co@(CompileOptions h is is2 ds _ _ _ m _)
   | h /= HelpNotNeeded = return co
 
-  | (not $ null o) && (isCompileIncremental m) =
-    compileError "Output filename (-o) is not allowed in compile-only mode (-c)."
-
-  | (not $ null o) && (isExecuteTests m) =
-    compileError "Output filename (-o) is not allowed in test mode (-t)."
   | (not $ null $ is ++ is2) && (isExecuteTests m) =
     compileError "Include paths (-i/-I) are not allowed in test mode (-t)."
-  | (not $ null $ es ++ ep) && (isExecuteTests m) =
-    compileError "Extra files (-e) are not allowed in test mode (-t)."
 
-  | (not $ null o) && (isCreateTemplates m) =
-    compileError "Output filename (-o) is not allowed in template mode (--templates)."
-  | (not $ null $ es ++ ep) && (isCreateTemplates m) =
-    compileError "Extra files (-e) are not allowed in template mode (--templates)."
-
-  | (not $ null o) && (isCompileRecompile m) =
-    compileError "Output filename (-o) is not allowed in recompile mode (-r)."
   | (not $ null $ is ++ is2) && (isCompileRecompile m) =
     compileError "Include paths (-i/-I) are not allowed in recompile mode (-r)."
-  | (not $ null $ es ++ ep) && (isCompileRecompile m) =
-    compileError "Extra files (-e) are not allowed in recompile mode (-r)."
-
-  | length ds > 1 && length (es ++ ep) > 0 =
-    compileError "Extra files and paths (-e) cannot be used with multiple input paths, to avoid ambiguity."
 
   | null ds =
     compileError "Please specify at least one input path."
diff --git a/src/Cli/ParseMetadata.hs b/src/Cli/ParseMetadata.hs
--- a/src/Cli/ParseMetadata.hs
+++ b/src/Cli/ParseMetadata.hs
@@ -130,7 +130,6 @@
     ns <-  parseRequired "namespace:"     parseNamespace
     is <-  parseRequired "public_deps:"   (parseList parseQuoted)
     is2 <- parseRequired "private_deps:"  (parseList parseQuoted)
-    es <-  parseRequired "extra:"         (parseList readConfig)
     cs <-  parseRequired "categories:"    (parseList parseCategoryName)
     ds <-  parseRequired "subdirs:"       (parseList parseQuoted)
     ps <-  parseRequired "public_files:"  (parseList parseQuoted)
@@ -138,13 +137,13 @@
     ts <-  parseRequired "test_files:"    (parseList parseQuoted)
     hxx <- parseRequired "hxx_files:"     (parseList parseQuoted)
     cxx <- parseRequired "cxx_files:"     (parseList parseQuoted)
+    lf  <- parseRequired "link_flags:"    (parseList parseQuoted)
     os <-  parseRequired "object_files:"  (parseList readConfig)
-    return (CompileMetadata h p ns is is2 es cs ds ps xs ts hxx cxx os)
+    return (CompileMetadata h p ns is is2 cs ds ps xs ts hxx cxx lf os)
   writeConfig m = do
     validateHash (cmVersionHash m)
     validateNamespace (cmNamespace m)
     _ <- collectAllOrErrorM $ map validateCategoryName (cmCategories m)
-    extra   <- fmap concat $ collectAllOrErrorM $ map writeConfig $ cmExtraRequires m
     objects <- fmap concat $ collectAllOrErrorM $ map writeConfig $ cmObjectFiles m
     return $ [
         "version_hash: " ++ (cmVersionHash m),
@@ -156,9 +155,6 @@
         "private_deps: ["
       ] ++ indents (map show $ cmPrivateDeps m) ++ [
         "]",
-        "extra: ["
-      ] ++ indents extra ++ [
-        "]",
         "categories: ["
       ] ++ indents (cmCategories m) ++ [
         "]",
@@ -180,6 +176,9 @@
         "cxx_files: ["
       ] ++ indents (map show $ cmCxxFiles m) ++ [
         "]",
+        "link_flags: ["
+      ] ++ indents (map show $ cmLinkFlags m) ++ [
+        "]",
         "object_files: ["
       ] ++ indents objects ++ [
         "]"
@@ -254,20 +253,16 @@
 
 instance ConfigFormat ModuleConfig where
   readConfig = do
-      p <-   parseOptional "root:"           "" parseQuoted
-      d <-   parseRequired "path:"              parseQuoted
-      is <-  parseOptional "public_deps:"    [] (parseList parseQuoted)
-      is2 <- parseOptional "private_deps:"   [] (parseList parseQuoted)
-      es <-  parseOptional "extra_files:"    [] (parseList parseQuoted)
-      ep <-  parseOptional "extra_paths:"    [] (parseList parseQuoted)
-      ec <-  parseOptional "always_include:" [] (parseList parseCategoryName)
-      ex <-  parseOptional "external:"       [] (parseList parseCategoryName)
-      m <-   parseRequired "mode:"              readConfig
-      o <-   parseOptional "output:"         "" parseQuoted
-      return (ModuleConfig p d is is2 es ep ec ex m o)
+      p <-   parseOptional "root:"          "" parseQuoted
+      d <-   parseRequired "path:"             parseQuoted
+      is <-  parseOptional "public_deps:"   [] (parseList parseQuoted)
+      is2 <- parseOptional "private_deps:"  [] (parseList parseQuoted)
+      es <-  parseOptional "extra_files:"   [] (parseList readConfig)
+      ep <-  parseOptional "include_paths:" [] (parseList parseQuoted)
+      m <-   parseRequired "mode:"             readConfig
+      return (ModuleConfig p d is is2 es ep m)
   writeConfig m = do
-    _ <- collectAllOrErrorM $ map validateCategoryName (rmExtraRequires m)
-    _ <- collectAllOrErrorM $ map validateCategoryName (rmExternalDefs m)
+    extra    <- fmap concat $ collectAllOrErrorM $ map writeConfig $ rmExtraFiles m
     mode <- writeConfig (rmMode m)
     return $ [
         "root: " ++ show (rmRoot m),
@@ -279,42 +274,78 @@
       ] ++ indents (map show $ rmPrivateDeps m) ++ [
         "]",
         "extra_files: ["
-      ] ++ indents (map show $ rmExtraFiles m) ++ [
+      ] ++ indents extra ++ [
         "]",
-        "extra_paths: ["
+        "include_paths: ["
       ] ++ indents (map show $ rmExtraPaths m) ++ [
-        "]",
-        "always_include: ["
-      ] ++ indents (rmExtraRequires m) ++ [
-        "]",
-        "external: ["
-      ] ++ indents (rmExternalDefs m) ++ [
         "]"
-      ] ++ "mode: " `prependFirst` mode ++ output where
-      output = if null (rmOutputName m)
-                  then []
-                  else ["output: " ++ show (rmOutputName m)]
+      ] ++ "mode: " `prependFirst` mode
 
+instance ConfigFormat ExtraSource where
+  readConfig = category <|> other where
+    category = do
+      sepAfter (string_ "category_source")
+      structOpen
+      f <-  parseRequired "source:"        parseQuoted
+      cs <- parseOptional "categories:" [] (parseList parseCategoryName)
+      ds <- parseOptional "requires:"   [] (parseList parseCategoryName)
+      structClose
+      return (CategorySource f cs ds)
+    other = do
+      f <- parseQuoted
+      return (OtherSource f)
+  writeConfig (CategorySource f cs ds) = do
+    _ <- collectAllOrErrorM $ map validateCategoryName cs
+    _ <- collectAllOrErrorM $ map validateCategoryName ds
+    return $ [
+        "category_source {",
+        indent ("source: " ++ show f),
+        indent "categories: ["
+      ] ++ (indents . indents) cs ++ [
+        indent "]",
+        indent "requires: ["
+      ] ++ (indents . indents) ds ++ [
+        indent "]",
+        "}"
+      ]
+  writeConfig (OtherSource f) = return [show f]
+
 instance ConfigFormat CompileMode where
   readConfig = labeled "compile mode" $ binary <|> incremental where
     binary = do
       sepAfter (string_ "binary")
       structOpen
-      c <- parseRequired "category:" parseCategoryName
-      f <- parseRequired "function:" parseFunctionName
+      c <-  parseRequired "category:"      parseCategoryName
+      f <-  parseRequired "function:"      parseFunctionName
+      o <-  parseOptional "output:"     "" parseQuoted
+      lf <- parseOptional "link_flags:" [] (parseList parseQuoted)
       structClose
-      return (CompileBinary c f)
+      return (CompileBinary c f o lf)
     incremental = do
       sepAfter (string_ "incremental")
-      return CompileIncremental
-  writeConfig (CompileBinary c f) = do
+      structOpen
+      lf <- parseOptional "link_flags:" [] (parseList parseQuoted)
+      structClose
+      return (CompileIncremental lf)
+  writeConfig (CompileBinary c f o lf) = do
     validateCategoryName c
     validateFunctionName f
     return $ [
         "binary {",
         indent ("category: " ++ c),
         indent ("function: " ++ f),
+        indent ("output: " ++ show o),
+        indent ("link_flags: [")
+      ] ++ (indents . indents) (map show lf) ++ [
+        indent "]",
         "}"
       ]
-  writeConfig CompileIncremental = return ["incremental"]
+  writeConfig (CompileIncremental lf) = do
+    return $ [
+        "incremental {",
+        indent ("link_flags: [")
+      ] ++ (indents . indents) (map show lf) ++ [
+        indent "]",
+        "}"
+      ]
   writeConfig _ = compileError "Invalid compile mode"
diff --git a/src/Cli/ProcessMetadata.hs b/src/Cli/ProcessMetadata.hs
--- a/src/Cli/ProcessMetadata.hs
+++ b/src/Cli/ProcessMetadata.hs
@@ -25,11 +25,11 @@
   getCachedPath,
   getCacheRelativePath,
   getIncludePathsForDeps,
+  getLinkFlagsForDeps,
   getNamespacesForDeps,
   getObjectFilesForDeps,
   getObjectFileResolver,
   getRealPathsForDeps,
-  getRequiresFromDeps,
   getSourceFilesForDeps,
   isPathConfigured,
   isPathUpToDate,
@@ -149,6 +149,7 @@
 writeRecompile :: String -> ModuleConfig -> IO ()
 writeRecompile p m = do
   p' <- canonicalizePath p
+  let f = p </> recompileFilename
   hPutStrLn stderr $ "Updating config for \"" ++ p' ++ "\"."
   let m' = autoWriteConfig m
   if isCompileError m'
@@ -156,7 +157,10 @@
        hPutStrLn stderr $ "Could not serialize module config."
        hPutStrLn stderr $ show (getCompileError m')
        exitFailure
-     else writeFile (p </> recompileFilename) (getCompileSuccess m')
+     else do
+       exists <- doesFileExist f
+       when exists $ removeFile f
+       writeFile f (getCompileSuccess m')
 
 eraseCachedData :: String -> IO ()
 eraseCachedData p = do
@@ -206,15 +210,15 @@
 getSourceFilesForDeps = concat . map extract where
   extract m = map (cmPath m </>) (cmPublicFiles m)
 
-getRequiresFromDeps :: [CompileMetadata] -> [CategoryIdentifier]
-getRequiresFromDeps = concat . map cmExtraRequires
-
 getNamespacesForDeps :: [CompileMetadata] -> [String]
 getNamespacesForDeps = filter (not . null) . map cmNamespace
 
 getIncludePathsForDeps :: [CompileMetadata] -> [String]
 getIncludePathsForDeps = concat . map cmSubdirs
 
+getLinkFlagsForDeps :: [CompileMetadata] -> [String]
+getLinkFlagsForDeps = concat . map cmLinkFlags
+
 getObjectFilesForDeps :: [CompileMetadata] -> [ObjectFile]
 getObjectFilesForDeps = concat . map cmObjectFiles
 
@@ -241,7 +245,7 @@
     | otherwise = do
         when (not s) $ hPutStrLn stderr $ "Loading metadata for dependency \"" ++ p ++ "\"."
         m <- loadMetadata p
-        fresh <- checkModuleFreshness p m
+        fresh <- checkModuleFreshness s p m
         when (not s && not fresh) $
           hPutStrLn stderr $ "Module \"" ++ p ++ "\" is out of date and should be recompiled."
         let sameVersion = checkModuleVersionHash h m
@@ -277,8 +281,8 @@
 checkModuleVersionHash :: String -> CompileMetadata -> Bool
 checkModuleVersionHash h m = cmVersionHash m == h
 
-checkModuleFreshness :: String -> CompileMetadata -> IO Bool
-checkModuleFreshness p (CompileMetadata _ p2 _ is is2 _ _ _ ps xs ts hxx cxx _) = do
+checkModuleFreshness :: Bool -> String -> CompileMetadata -> IO Bool
+checkModuleFreshness s p (CompileMetadata _ p2 _ is is2 _ _ ps xs ts hxx cxx _ _) = do
   time <- getModificationTime $ getCachedPath p "" metadataFilename
   (ps2,xs2,ts2) <- findSourceFiles p ""
   let e1 = checkMissing ps ps2
@@ -293,10 +297,16 @@
     check time f = do
       exists <- doesFileOrDirExist f
       if not exists
-         then return True
+         then do
+           when (not s) $ hPutStrLn stderr $ "Required path \"" ++ f ++ "\" is missing."
+           return True
          else do
            time2 <- getModificationTime f
-           return (time2 > time)
+           if time2 > time
+              then do
+                when (not s) $ hPutStrLn stderr $ "Required path \"" ++ f ++ "\" is out of date."
+                return True
+              else return False
     checkMissing s0 s1 = not $ null $ (Set.fromList s1) `Set.difference` (Set.fromList s0)
     doesFileOrDirExist f2 = do
       existF <- doesFileExist f2
@@ -304,8 +314,8 @@
         then return True
         else doesDirectoryExist f2
 
-getObjectFileResolver :: [CategoryIdentifier] -> [ObjectFile] -> [Namespace] -> [CategoryName] -> [String]
-getObjectFileResolver ce os ns ds = resolved ++ nonCategories where
+getObjectFileResolver :: [ObjectFile] -> [Namespace] -> [CategoryName] -> [String]
+getObjectFileResolver os ns ds = resolved ++ nonCategories where
   categories    = filter isCategoryObjectFile os
   nonCategories = map oofFile $ filter (not . isCategoryObjectFile) os
   categoryMap = Map.fromList $ map keyByCategory2 categories
@@ -313,7 +323,7 @@
   objectMap = Map.fromList $ map keyBySpec categories
   keyBySpec o = (cofCategory o,o)
   directDeps = concat $ map (resolveDep2 . show) ds
-  directResolved = map cofCategory directDeps ++ ce
+  directResolved = map cofCategory directDeps
   resolveDep2 d = unwrap $ foldl (<|>) Nothing allChecks <|> Just [] where
     allChecks = map (\n -> (d,n) `Map.lookup` categoryMap >>= return . (:[])) (map show ns ++ [""])
     unwrap (Just xs) = xs
diff --git a/src/Cli/TestRunner.hs b/src/Cli/TestRunner.hs
--- a/src/Cli/TestRunner.hs
+++ b/src/Cli/TestRunner.hs
@@ -158,11 +158,11 @@
       let main   = dir </> "testcase.cpp"
       let binary = dir </> "testcase"
       writeFile main $ concat $ map (++ "\n") c
+      let lf = getLinkFlagsForDeps deps
       let paths' = nub $ map fixPath (dir:paths)
-      let req2 = getRequiresFromDeps deps
-      let ofr = getObjectFileResolver req2 (sources' ++ os)
+      let ofr = getObjectFileResolver (sources' ++ os)
       let os' = ofr ns req
-      let command = CompileToBinary main os' binary paths'
+      let command = CompileToBinary main os' binary paths' lf
       runCxxCommand b command
     writeSingleFile d ca@(CxxOutput _ f2 _ _ _ content) = do
       writeFile (d </> f2) $ concat $ map (++ "\n") content
diff --git a/src/CompilerCxx/Category.hs b/src/CompilerCxx/Category.hs
--- a/src/CompilerCxx/Category.hs
+++ b/src/CompilerCxx/Category.hs
@@ -84,7 +84,7 @@
 compileCategoryModule :: (Show c, CompileErrorM m, MergeableM m) =>
   CategoryModule c -> m [CxxOutput]
 compileCategoryModule (CategoryModule tm ns cs xa ex) = do
-  checkSupefluous $ Set.toList $ es `Set.difference` ca
+  checkSupefluous $ Set.toList $ (Set.fromList ex) `Set.difference` ca
   tm' <- includeNewTypes tm cs
   hxx <- collectAllOrErrorM $ map (compileCategoryDeclaration tm' ns) cs
   let interfaces = filter (not . isValueConcrete) cs
@@ -92,26 +92,33 @@
   xa2 <- collectAllOrErrorM $ map (compileInternal ns) xa
   let xx = concat $ map snd xa2
   let dm = mapByName $ concat $ map fst xa2
-  checkDefined dm $ filter isValueConcrete cs
+  checkDefined dm ex $ filter isValueConcrete cs
   return $ hxx ++ cxx ++ xx where
     compileInternal ns0 (PrivateSource ns1 cs2 ds) = do
       let cs' = cs++cs2
+      checkLocals ds (ex ++ map (show . getCategoryName) cs')
+      let dm = mapByName ds
+      checkDefined dm [] $ filter isValueConcrete cs2
       tm' <- includeNewTypes tm cs'
       hxx <- collectAllOrErrorM $ map (compileCategoryDeclaration tm' ns0) cs2
       let interfaces = filter (not . isValueConcrete) cs2
       cxx1 <- collectAllOrErrorM $ map compileInterfaceDefinition interfaces
       cxx2 <- collectAllOrErrorM $ map (compileDefinition tm' (ns1:ns)) ds
-      let dm = mapByName ds
-      checkDefined dm $ filter isValueConcrete cs2
       return (ds,hxx ++ cxx1 ++ cxx2)
     compileDefinition tm2 ns2 d = do
       tm2' <- mergeInternalInheritance tm2 d
       compileConcreteDefinition tm2' ns2 d
     mapByName = Map.fromListWith (++) . map (\d -> (dcName d,[d]))
     ca = Set.fromList $ map (show . getCategoryName) $ filter isValueConcrete cs
-    es = Set.fromList ex
-    checkDefined dm = mergeAllM . map (checkSingle dm)
-    checkSingle dm t =
+    checkLocals ds cs2 = mergeAllM $ map (checkLocal $ Set.fromList cs2) ds
+    checkLocal cs2 d =
+      if (show $ dcName d) `Set.member` cs2
+         then return ()
+         else compileError ("Definition for " ++ show (dcName d) ++
+                            formatFullContextBrace (dcContext d) ++
+                            " does not correspond to a category in this module")
+    checkDefined dm ex2 = mergeAllM . map (checkSingle dm (Set.fromList ex2))
+    checkSingle dm es t =
       case ((show $ getCategoryName t) `Set.member` es, getCategoryName t `Map.lookup` dm) of
            (False,Just [_]) -> return ()
            (True,Nothing)   -> return ()
@@ -158,7 +165,7 @@
                      (headerFilename name)
                      (getCategoryNamespace t)
                      (ns ++ [getCategoryNamespace t])
-                     (filter (not . isBuiltinCategory) $ Set.toList $ cdRequired file)
+                     (Set.toList $ cdRequired file)
                      (cdOutput file) where
     file = mergeAll $ [
         CompiledData depends [],
@@ -389,18 +396,18 @@
 commonDefineAll t ns top bottom ce te fe = do
   let filename = sourceFilename name
   (CompiledData req out) <- fmap (addNamespace t) $ mergeAllM $ [
-      return $ CompiledData (Set.fromList [name]) [],
+      return $ CompiledData (Set.fromList (name:getCategoryMentions t)) [],
       return $ mergeAll [createCollection,createAllLabels]
     ] ++ conditionalContent
   let inherited = Set.fromList $ (map (tiName . vrType) $ getCategoryRefines t) ++
                                  (map (diName . vdType) $ getCategoryDefines t)
   let includes = map (\i -> "#include \"" ++ headerFilename i ++ "\"") $
-                   filter (not . isBuiltinCategory) $ Set.toList $ Set.union req inherited
+                   Set.toList $ Set.union req inherited
   return $ CxxOutput (Just $ getCategoryName t)
                      filename
                      (getCategoryNamespace t)
                      ((getCategoryNamespace t):ns)
-                     (filter (not . isBuiltinCategory) $ Set.toList req)
+                     (Set.toList req)
                      (baseSourceIncludes ++ includes ++ out)
   where
     conditionalContent
@@ -686,7 +693,7 @@
       "  " ++ startFunctionTracing n
     ] ++ out ++ ["}"] where
       depIncludes req2 = map (\i -> "#include \"" ++ headerFilename i ++ "\"") $
-                           filter (not . isBuiltinCategory) $ Set.toList req2
+                           Set.toList req2
 
 createMainFile :: (Show c, CompileErrorM m, MergeableM m) =>
   CategoryMap c -> CategoryName -> FunctionName -> m (Namespace,[String])
@@ -704,4 +711,20 @@
 createTestFile tm e = flip reviseError ("In the creation of the test binary procedure") $ do
   ca@(CompiledData req _) <- fmap indentCompiled (compileMainProcedure tm e)
   let file = createMainCommon "main" ca
-  return (filter (not . isBuiltinCategory) $ Set.toList req,file)
+  return (Set.toList req,file)
+
+getCategoryMentions :: AnyCategory c -> [CategoryName]
+getCategoryMentions t = fromRefines (getCategoryRefines t) ++
+                        fromDefines (getCategoryDefines t) ++
+                        fromFunctions (getCategoryFunctions t) ++
+                        fromFilters (getCategoryFilters t) where
+  fromRefines rs = fromGenerals $ map (SingleType . JustTypeInstance . vrType) rs
+  fromDefines ds = concat $ map (fromDefine . vdType) ds
+  fromDefine (DefinesInstance d ps) = d:(fromGenerals $ pValues ps)
+  fromFunctions fs = concat $ map fromFunction fs
+  fromFunction (ScopedFunction _ _ t2 _ as rs _ fs _) =
+    [t2] ++ (fromGenerals $ map (vtType . pvType) (pValues as ++ pValues rs)) ++ fromFilters fs
+  fromFilters fs = concat $ map (fromFilter . pfFilter) fs
+  fromFilter (TypeFilter _ t2)  = Set.toList $ categoriesFromTypes $ SingleType t2
+  fromFilter (DefinesFilter t2) = fromDefine t2
+  fromGenerals = Set.toList . Set.unions . map categoriesFromTypes
diff --git a/src/CompilerCxx/Procedure.hs b/src/CompilerCxx/Procedure.hs
--- a/src/CompilerCxx/Procedure.hs
+++ b/src/CompilerCxx/Procedure.hs
@@ -22,6 +22,7 @@
 {-# LANGUAGE Safe #-}
 
 module CompilerCxx.Procedure (
+  categoriesFromTypes,
   compileExecutableProcedure,
   compileMainProcedure,
   compileExpression,
@@ -189,6 +190,7 @@
        _ -> return ()
   csWrite $ ["{"] ++ lsUpdate loop ++ ["}","continue;"]
 compileStatement (FailCall c e) = do
+  csRequiresTypes (Set.fromList [BuiltinFormatted,BuiltinString])
   e' <- compileExpression e
   when (length (pValues $ fst e') /= 1) $
     lift $ compileError $ "Expected single return in argument" ++ formatFullContextBrace c
@@ -199,7 +201,7 @@
     ("In fail call at " ++ formatFullContext c)
   csSetNoReturn
   csWrite $ setTraceContext c
-  csWrite ["BuiltinFail(" ++ useAsUnwrapped e0 ++ ");"]
+  csWrite ["BUILTIN_FAIL(" ++ useAsUnwrapped e0 ++ ")"]
 compileStatement (IgnoreValues c e) = do
   (_,e') <- compileExpression e
   csWrite $ setTraceContext c
@@ -416,26 +418,33 @@
   Expression c -> CompilerState a m (ExpressionType,ExprValue)
 compileExpression = compile where
   compile (Literal (StringLiteral _ l)) = do
+    csRequiresTypes (Set.fromList [BuiltinString])
     return (Positional [stringRequiredValue],UnboxedPrimitive PrimString $ "PrimString_FromLiteral(" ++ escapeChars l ++ ")")
   compile (Literal (CharLiteral _ l)) = do
+    csRequiresTypes (Set.fromList [BuiltinChar])
     return (Positional [charRequiredValue],UnboxedPrimitive PrimChar $ "PrimChar('" ++ escapeChar l ++ "')")
   compile (Literal (IntegerLiteral c True l)) = do
+    csRequiresTypes (Set.fromList [BuiltinInt])
     when (l > 2^(64 :: Integer) - 1) $ lift $ compileError $
       "Literal " ++ show l ++ formatFullContextBrace c ++ " is greater than the max value for 64-bit unsigned"
     let l' = if l > 2^(63 :: Integer) - 1 then l - 2^(64 :: Integer) else l
     return (Positional [intRequiredValue],UnboxedPrimitive PrimInt $ "PrimInt(" ++ show l' ++ ")")
   compile (Literal (IntegerLiteral c False l)) = do
+    csRequiresTypes (Set.fromList [BuiltinInt])
     when (l > 2^(63 :: Integer) - 1) $ lift $ compileError $
       "Literal " ++ show l ++ formatFullContextBrace c ++ " is greater than the max value for 64-bit signed"
     when ((-l) > (2^(63 :: Integer) - 2)) $ lift $ compileError $
       "Literal " ++ show l ++ formatFullContextBrace c ++ " is less than the min value for 64-bit signed"
     return (Positional [intRequiredValue],UnboxedPrimitive PrimInt $ "PrimInt(" ++ show l ++ ")")
   compile (Literal (DecimalLiteral _ l e)) = do
+    csRequiresTypes (Set.fromList [BuiltinFloat])
     -- TODO: Check bounds.
     return (Positional [floatRequiredValue],UnboxedPrimitive PrimFloat $ "PrimFloat(" ++ show l ++ "E" ++ show e ++ ")")
   compile (Literal (BoolLiteral _ True)) = do
+    csRequiresTypes (Set.fromList [BuiltinBool])
     return (Positional [boolRequiredValue],UnboxedPrimitive PrimBool "true")
   compile (Literal (BoolLiteral _ False)) = do
+    csRequiresTypes (Set.fromList [BuiltinBool])
     return (Positional [boolRequiredValue],UnboxedPrimitive PrimBool "false")
   compile (Literal (EmptyLiteral _)) = do
     return (Positional [emptyValue],UnwrappedSingle "Var_empty")
@@ -641,6 +650,7 @@
       return f'
 -- TODO: Compile BuiltinCall like regular functions, for consistent validation.
 compileExpressionStart (BuiltinCall c (FunctionCall _ BuiltinPresent ps es)) = do
+  csRequiresTypes (Set.fromList [BuiltinBool])
   when (length (pValues ps) /= 0) $
     lift $ compileError $ "Expected 0 type parameters" ++ formatFullContextBrace c
   when (length (pValues es) /= 1) $
diff --git a/src/Config/LoadConfig.hs b/src/Config/LoadConfig.hs
--- a/src/Config/LoadConfig.hs
+++ b/src/Config/LoadConfig.hs
@@ -108,12 +108,12 @@
       nsFlag
         | null ns = []
         | otherwise = ["-D" ++ nm ++ "=" ++ ns]
-  runCxxCommand (UnixBackend cb co _) (CompileToBinary m ss o ps) = do
+  runCxxCommand (UnixBackend cb co _) (CompileToBinary m ss o ps lf) = do
     let arFiles    = filter (isSuffixOf ".a")       ss
     let otherFiles = filter (not . isSuffixOf ".a") ss
     executeProcess cb $ co ++ otherOptions ++ m:otherFiles ++ arFiles ++ ["-o", o]
     return o where
-      otherOptions = map ("-I" ++) $ map normalise ps
+      otherOptions = lf ++ map ("-I" ++) (map normalise ps)
   runTestCommand _ (TestCommand b p) = do
     (outF,outH) <- mkstemps "/tmp/ztest_" ".txt"
     (errF,errH) <- mkstemps "/tmp/ztest_" ".txt"
diff --git a/src/Config/Programs.hs b/src/Config/Programs.hs
--- a/src/Config/Programs.hs
+++ b/src/Config/Programs.hs
@@ -44,7 +44,8 @@
     ctbMain :: String,
     ctbSources :: [String],
     ctbOutput :: String,
-    ctoPaths :: [String]
+    ctbPaths :: [String],
+    ctbLinkFlags :: [String]
   }
   deriving (Show)
 
diff --git a/src/Test/ParseMetadata.hs b/src/Test/ParseMetadata.hs
--- a/src/Test/ParseMetadata.hs
+++ b/src/Test/ParseMetadata.hs
@@ -41,16 +41,6 @@
         "/home/project/private-dep1",
         "/home/project/private-dep2"
       ],
-      cmExtraRequires = [
-        CategoryIdentifier {
-          ciPath = "/home/project/private-dep1",
-          ciCategory = "PrivateCategory",
-          ciNamespace = "private_123456"
-        },
-        UnresolvedCategory {
-          ucCategory = "UnresolvedCategory"
-        }
-      ],
       cmCategories = [
         "MyCategory",
         "MyOtherCategory"
@@ -79,6 +69,10 @@
         "/home/project/special/category1.cpp",
         "/home/project/special/category2.cpp"
       ],
+      cmLinkFlags = [
+        "-lm",
+        "-ldl"
+      ],
       cmObjectFiles = [
         CategoryObjectFile {
           cofCategory = CategoryIdentifier {
@@ -110,7 +104,6 @@
       cmNamespace = "public_ABCDEF",
       cmPublicDeps = [],
       cmPrivateDeps = [],
-      cmExtraRequires = [],
       cmCategories = [],
       cmSubdirs = [],
       cmPublicFiles = [],
@@ -118,6 +111,7 @@
       cmTestFiles = [],
       cmHxxFiles = [],
       cmCxxFiles = [],
+      cmLinkFlags = [],
       cmObjectFiles = []
     },
 
@@ -127,7 +121,6 @@
       cmNamespace = "bad namespace",
       cmPublicDeps = [],
       cmPrivateDeps = [],
-      cmExtraRequires = [],
       cmCategories = [],
       cmSubdirs = [],
       cmPublicFiles = [],
@@ -135,6 +128,7 @@
       cmTestFiles = [],
       cmHxxFiles = [],
       cmCxxFiles = [],
+      cmLinkFlags = [],
       cmObjectFiles = []
     },
 
@@ -144,7 +138,6 @@
       cmNamespace = "public_ABCDEF",
       cmPublicDeps = [],
       cmPrivateDeps = [],
-      cmExtraRequires = [],
       cmCategories = [
         "bad category"
       ],
@@ -154,6 +147,7 @@
       cmTestFiles = [],
       cmHxxFiles = [],
       cmCxxFiles = [],
+      cmLinkFlags = [],
       cmObjectFiles = []
     },
 
@@ -169,53 +163,47 @@
         "/home/project/private-dep2"
       ],
       rmExtraFiles = [
-        "extra1.cpp",
-        "extra2.cpp"
+        CategorySource {
+          csSource = "extra1.cpp",
+          csCategories = [
+            "Category1",
+            "Category2"
+          ],
+          csDepCategories = [
+            "DepCategory1",
+            "DepCategory2"
+          ]
+        },
+        OtherSource {
+          osSource = "extra2.cpp"
+        }
       ],
       rmExtraPaths = [
         "extra1",
         "extra2"
       ],
-      rmExtraRequires = [
-        "Extra1",
-        "Extra2"
-      ],
-      rmExternalDefs = [
-        "External1",
-        "External1"
-      ],
-      rmMode = CompileIncremental,
-      rmOutputName = "binary"
+      rmMode = CompileIncremental {
+        ciLinkFlags = [
+          "-lm",
+          "-ldl"
+        ]
+      }
     },
 
-    checkWriteFail "bad category" $ ModuleConfig {
-      rmRoot = "/home/projects",
-      rmPath = "special",
-      rmPublicDeps = [],
-      rmPrivateDeps = [],
-      rmExtraFiles = [],
-      rmExtraPaths = [],
-      rmExtraRequires = [
+    checkWriteFail "bad category" $ CategorySource {
+      csSource = "extra1.cpp",
+      csCategories = [
         "bad category"
       ],
-      rmExternalDefs = [],
-      rmMode = CompileIncremental,
-      rmOutputName = ""
+      csDepCategories = []
     },
 
-    checkWriteFail "bad category" $ ModuleConfig {
-      rmRoot = "/home/projects",
-      rmPath = "special",
-      rmPublicDeps = [],
-      rmPrivateDeps = [],
-      rmExtraFiles = [],
-      rmExtraPaths = [],
-      rmExtraRequires = [],
-      rmExternalDefs = [
+    checkWriteFail "bad category" $ CategorySource {
+      csSource = "extra1.cpp",
+      csCategories = [],
+      csDepCategories = [
         "bad category"
-      ],
-      rmMode = CompileIncremental,
-      rmOutputName = ""
+      ]
     },
 
     checkWriteFail "bad category" $ CategoryIdentifier {
@@ -236,20 +224,31 @@
 
     checkWriteThenRead $ CompileBinary {
       cbCategory = "SpecialCategory",
-      cbFunction = "specialFunction"
+      cbFunction = "specialFunction",
+      cbOutputName = "binary",
+      cbLinkFlags = []
     },
 
     checkWriteFail "bad category" $ CompileBinary {
       cbCategory = "bad category",
-      cbFunction = "specialFunction"
+      cbFunction = "specialFunction",
+      cbOutputName = "binary",
+      cbLinkFlags = []
     },
 
     checkWriteFail "bad function" $ CompileBinary {
       cbCategory = "SpecialCategory",
-      cbFunction = "bad function"
+      cbFunction = "bad function",
+      cbOutputName = "binary",
+      cbLinkFlags = []
     },
 
-    checkWriteThenRead $ CompileIncremental,
+    checkWriteThenRead $ CompileIncremental {
+      ciLinkFlags = [
+        "-lm",
+        "-ldl"
+      ]
+    },
 
     checkWriteFail "compile mode" $ ExecuteTests { etInclude = [] },
     checkWriteFail "compile mode" $ CompileRecompile,
@@ -260,7 +259,7 @@
 checkWriteThenRead :: (Eq a, Show a, ConfigFormat a) => a -> IO (CompileInfo ())
 checkWriteThenRead m = return $ do
   text <- fmap spamComments $ autoWriteConfig m
-  m' <- autoReadConfig "(string)" text
+  m' <- autoReadConfig "(string)" text `reviseError` ("Serialized >>>\n\n" ++ text ++ "\n<<< Serialized\n\n")
   when (m' /= m) $
     compileError $ "Failed to match after write/read\n" ++
                    "Before:\n" ++ show m ++ "\n" ++
diff --git a/tests/.zeolite-module b/tests/.zeolite-module
--- a/tests/.zeolite-module
+++ b/tests/.zeolite-module
@@ -4,4 +4,4 @@
   "visibility"
   "visibility2"
 ]
-mode: incremental
+mode: incremental {}
diff --git a/tests/check-defs/.zeolite-module b/tests/check-defs/.zeolite-module
--- a/tests/check-defs/.zeolite-module
+++ b/tests/check-defs/.zeolite-module
@@ -1,6 +1,3 @@
 root: "../.."
 path: "tests/check-defs"
-external: [
-  External
-]
-mode: incremental
+mode: incremental {}
diff --git a/tests/check-defs/public.0rp b/tests/check-defs/public.0rp
--- a/tests/check-defs/public.0rp
+++ b/tests/check-defs/public.0rp
@@ -19,5 +19,3 @@
 concrete Type {}
 
 concrete Undefined {}
-
-concrete External {}
diff --git a/tests/function-merging.0rt b/tests/function-merging.0rt
--- a/tests/function-merging.0rt
+++ b/tests/function-merging.0rt
@@ -141,6 +141,16 @@
   @value call () -> (Interface)
 }
 
+define Value {
+  create () {
+    return Value{}
+  }
+
+  call () {
+    return self
+  }
+}
+
 define Test {
   run () {}
 }
diff --git a/tests/simple.0rt b/tests/simple.0rt
--- a/tests/simple.0rt
+++ b/tests/simple.0rt
@@ -71,3 +71,14 @@
 testcase "minimal linking works properly" {
   success empty
 }
+
+
+testcase "attempt to define outside of this module" {
+  error
+  require "String"
+  require "module"
+  exclude "procedure definition"
+}
+
+// The declaration for String is in scope, but it lives outside of this module.
+define String {}
diff --git a/tests/visibility/.zeolite-module b/tests/visibility/.zeolite-module
--- a/tests/visibility/.zeolite-module
+++ b/tests/visibility/.zeolite-module
@@ -3,4 +3,4 @@
 private_deps: [
   "internal"
 ]
-mode: incremental
+mode: incremental {}
diff --git a/tests/visibility/internal/.zeolite-module b/tests/visibility/internal/.zeolite-module
--- a/tests/visibility/internal/.zeolite-module
+++ b/tests/visibility/internal/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../.."
 path: "visibility/internal"
-mode: incremental
+mode: incremental {}
diff --git a/tests/visibility2/.zeolite-module b/tests/visibility2/.zeolite-module
--- a/tests/visibility2/.zeolite-module
+++ b/tests/visibility2/.zeolite-module
@@ -3,4 +3,4 @@
 private_deps: [
   "internal"
 ]
-mode: incremental
+mode: incremental {}
diff --git a/tests/visibility2/internal/.zeolite-module b/tests/visibility2/internal/.zeolite-module
--- a/tests/visibility2/internal/.zeolite-module
+++ b/tests/visibility2/internal/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../.."
 path: "visibility2/internal"
-mode: incremental
+mode: incremental {}
diff --git a/zeolite-lang.cabal b/zeolite-lang.cabal
--- a/zeolite-lang.cabal
+++ b/zeolite-lang.cabal
@@ -1,7 +1,7 @@
 cabal-version:       2.2
 
 name:                zeolite-lang
-version:             0.3.0.0
+version:             0.4.0.0
 synopsis:            Zeolite is a statically-typed, general-purpose programming language.
 
 description:
@@ -59,16 +59,16 @@
                      GHC == 8.2.2,
                      GHC == 8.0.2
 
-extra-source-files:  ChangeLog.md
-extra-source-files:  src/Test/testfiles/*.0rt,
+extra-source-files:  ChangeLog.md,
+                     src/Test/testfiles/*.0rt,
                      src/Test/testfiles/*.0rx
 
 data-files:          base/.zeolite-module,
                      base/*.0rp,
+                     base/*.cc,
                      base/*.cpp,
                      base/*.hpp,
-                     capture-thread/include/*.h,
-                     capture-thread/src/*.cc,
+                     base/*.h,
                      example/hello/README.md,
                      example/hello/*.0rx,
                      example/regex/README.md,
