diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,28 @@
 # Revision history for zeolite-lang
 
+## 0.4.1.0  -- 2020-05-05
+
+* **[new]** Adds a compiler mode (`--show-deps`) to display the symbolic
+  dependencies of a module.
+
+* **[new]** Adds a compiler mode (`--fast`) to quickly compile a binary from a
+  single source file without needing to create a module.
+
+* **[new]** Adds a compiler mode (`-R`) to recursively recompile modules.
+
+* **[new]** Improves thread-safety of internal code so that thread support can
+  be safely added later. (Probably via a library.)
+
+* **[new]** Adds the `Builder` interface to make `String` concatenation more
+  efficient.
+
+* **[new]** Adds support for basic mathematical `Float` operations, such as
+  `sin`, `exp`, and `sqrt`.
+
+* **[new]** Adds support for bitwise `Int` operations.
+
+* **[fix]** Fixes broken `--templates` mode when builtin types are used.
+
 ## 0.4.0.0  -- 2020-05-05
 
 * **[new]** Allows modules to specify custom linker flags and private include
diff --git a/base/.zeolite-module b/base/.zeolite-module
--- a/base/.zeolite-module
+++ b/base/.zeolite-module
@@ -21,7 +21,6 @@
     source: "base/Category_String.cpp"
     categories: [String]
   }
-  "base/argv.cpp"
   "base/category-header.hpp"
   "base/category-source.cpp"
   "base/category-source.hpp"
diff --git a/base/Category_Bool.cpp b/base/Category_Bool.cpp
--- a/base/Category_Bool.cpp
+++ b/base/Category_Bool.cpp
@@ -116,10 +116,8 @@
   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;
+  static auto& cached = *new Type_Bool(CreateCategory_Bool(), Params<0>::Type());
+  return cached;
 }
 struct Value_Bool : public TypeValue {
   Value_Bool(Type_Bool& p, bool value) : parent(p), value_(value) {}
diff --git a/base/Category_Char.cpp b/base/Category_Char.cpp
--- a/base/Category_Char.cpp
+++ b/base/Category_Char.cpp
@@ -132,10 +132,8 @@
   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;
+  static auto& cached = *new Type_Char(CreateCategory_Char(), Params<0>::Type());
+  return cached;
 }
 struct Value_Char : public TypeValue {
   Value_Char(Type_Char& p, PrimChar value) : parent(p), value_(value) {}
diff --git a/base/Category_Float.cpp b/base/Category_Float.cpp
--- a/base/Category_Float.cpp
+++ b/base/Category_Float.cpp
@@ -127,10 +127,8 @@
   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;
+  static auto& cached = *new Type_Float(CreateCategory_Float(), Params<0>::Type());
+  return cached;
 }
 struct Value_Float : public TypeValue {
   Value_Float(Type_Float& p, PrimFloat value) : parent(p), value_(value) {}
diff --git a/base/Category_Int.cpp b/base/Category_Int.cpp
--- a/base/Category_Int.cpp
+++ b/base/Category_Int.cpp
@@ -132,10 +132,8 @@
   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;
+  static auto& cached = *new Type_Int(CreateCategory_Int(), Params<0>::Type());
+  return cached;
 }
 struct Value_Int : public TypeValue {
   Value_Int(Type_Int& p, PrimInt value) : parent(p), value_(value) {}
diff --git a/base/Category_String.cpp b/base/Category_String.cpp
--- a/base/Category_String.cpp
+++ b/base/Category_String.cpp
@@ -28,6 +28,7 @@
 #include "Category_Char.hpp"
 #include "Category_Equals.hpp"
 #include "Category_LessThan.hpp"
+#include "Category_Builder.hpp"
 
 
 #ifdef ZEOLITE_DYNAMIC_NAMESPACE
@@ -38,6 +39,7 @@
 }  // namespace
 const void* const Functions_String = &collection_String;
 const ValueFunction& Function_String_subSequence = (*new ValueFunction{ 0, 2, 1, "String", "subSequence", Functions_String, 0 });
+const TypeFunction& Function_String_builder = (*new TypeFunction{ 0, 0, 1, "String", "builder", Functions_String, 0 });
 namespace {
 class Category_String;
 class Type_String;
@@ -106,6 +108,9 @@
     static const CallType Table_LessThan[] = {
       &Type_String::Call_lessThan,
     };
+    static const CallType Table_String[] = {
+      &Type_String::Call_builder,
+    };
     if (label.collection == Functions_Equals) {
       if (label.function_num < 0 || label.function_num >= 1) {
         FAIL() << "Bad function call " << label;
@@ -118,16 +123,21 @@
       }
       return (this->*Table_LessThan[label.function_num])(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])(params, args);
+    }
     return TypeInstance::Dispatch(label, params, args);
   }
+  ReturnTuple Call_builder(const ParamTuple& params, const ValueTuple& 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;
+  static auto& cached = *new Type_String(CreateCategory_String(), Params<0>::Type());
+  return cached;
 }
 struct Value_String : public TypeValue {
   Value_String(Type_String& p, const PrimString& value) : parent(p), value_(value) {}
@@ -183,6 +193,43 @@
   Type_String& parent;
   const PrimString value_;
 };
+
+class Value_StringBuilder : public TypeValue {
+ public:
+  std::string CategoryName() const final { return "StringBuilder"; }
+
+  ReturnTuple Dispatch(const S<TypeValue>& self,
+                       const ValueFunction& label,
+                       const ParamTuple& params, const ValueTuple& args) final {
+    if (args.Size() != label.arg_count) {
+      FAIL() << "Wrong number of args";
+    }
+    if (params.Size() != label.param_count){
+      FAIL() << "Wrong number of params";
+    }
+    if (&label == &Function_Builder_append) {
+      TRACE_FUNCTION("StringBuilder.append")
+      std::lock_guard<std::mutex> lock(mutex);
+      output_ << args.At(0)->AsString();
+      return ReturnTuple(self);
+    }
+    if (&label == &Function_Builder_build) {
+      TRACE_FUNCTION("SimpleOutput.build")
+      std::lock_guard<std::mutex> lock(mutex);
+      return ReturnTuple(Box_String(output_.str()));
+    }
+    return TypeValue::Dispatch(self, label, params, args);
+  }
+
+ private:
+  std::mutex mutex;
+  std::ostringstream output_;
+};
+
+ReturnTuple Type_String::Call_builder(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("String.builder")
+  return ReturnTuple(S<TypeValue>(new Value_StringBuilder));
+}
 ReturnTuple Type_String::Call_equals(const ParamTuple& params, const ValueTuple& args) {
   TRACE_FUNCTION("String.equals")
   const S<TypeValue>& Var_arg1 = (args.At(0));
diff --git a/base/argv.cpp b/base/argv.cpp
deleted file mode 100644
--- a/base/argv.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 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 "argv.hpp"
-#include "logging.hpp"
-
-
-int Argv::ArgCount() {
-  if (GetCurrent()) {
-    return GetCurrent()->GetArgs().size();
-  } else {
-    return 0;
-  }
-}
-
-const std::string& Argv::GetArgAt(int pos) {
-  if (pos < 0 || pos >= ArgCount()) {
-    FAIL() << "Argv index " << pos << " is out of bounds";
-    __builtin_unreachable();
-  } else {
-    return GetCurrent()->GetArgs()[pos];
-  }
-}
-
-const std::vector<std::string>& ProgramArgv::GetArgs() const {
-  return argv_;
-}
diff --git a/base/argv.hpp b/base/argv.hpp
deleted file mode 100644
--- a/base/argv.hpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 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 ARGV_HPP_
-#define ARGV_HPP_
-
-#include <string>
-#include <vector>
-
-#include "thread-capture.h"
-
-
-class Argv : public capture_thread::ThreadCapture<Argv> {
- public:
-  static int ArgCount();
-  static const std::string& GetArgAt(int pos);
-
- protected:
-  virtual ~Argv() = default;
-
- private:
-  virtual const std::vector<std::string>& GetArgs() const = 0;
-};
-
-class ProgramArgv : public Argv {
- public:
-  inline ProgramArgv(int argc, const char** argv)
-    : argv_(argv, argv + argc), capture_to_(this) {}
-
- private:
-  const std::vector<std::string>& GetArgs() const final;
-
-  const std::vector<std::string> argv_;
-  const ScopedCapture capture_to_;
-};
-
-#endif  // ARGV_HPP_
diff --git a/base/builtin.0rp b/base/builtin.0rp
--- a/base/builtin.0rp
+++ b/base/builtin.0rp
@@ -66,6 +66,7 @@
   defines LessThan<String>
 
   @value subSequence (Int,Int) -> (String)
+  @type builder () -> (Builder<String>)
 }
 
 @value interface AsBool {
@@ -92,6 +93,11 @@
   readPosition (Int) -> (#x)
   readSize () -> (Int)
   subSequence (Int,Int) -> (ReadPosition<#x>)
+}
+
+@value interface Builder<#x> {
+  append (#x) -> (Builder<#x>)
+  build () -> (#x)
 }
 
 @type interface Equals<#x|> {
diff --git a/base/category-source.hpp b/base/category-source.hpp
--- a/base/category-source.hpp
+++ b/base/category-source.hpp
@@ -20,12 +20,12 @@
 #define CATEGORY_SOURCE_HPP_
 
 #include <map>
+#include <mutex>
 #include <sstream>
 #include <vector>
 
 #include "types.hpp"
 #include "function.hpp"
-#include "argv.hpp"
 #include "cycle-check.hpp"
 
 
diff --git a/base/logging.cpp b/base/logging.cpp
--- a/base/logging.cpp
+++ b/base/logging.cpp
@@ -22,9 +22,7 @@
 #include <csignal>
 #include <iostream>
 
-#include "argv.hpp"
 
-
 LogThenCrash::LogThenCrash(bool fail, const std::string& condition)
     : fail_(fail), signal_(SIGABRT), condition_(condition) {}
 
@@ -120,7 +118,7 @@
 }
 
 const TraceContext* SourceContext::GetNext() const {
-  return cross_and_capture_to_.Previous();
+  return capture_to_.Previous();
 }
 
 
@@ -139,5 +137,26 @@
 }
 
 const TraceContext* CleanupContext::GetNext() const {
-  return cross_and_capture_to_.Previous();
+  return capture_to_.Previous();
+}
+
+int Argv::ArgCount() {
+  if (GetCurrent()) {
+    return GetCurrent()->GetArgs().size();
+  } else {
+    return 0;
+  }
+}
+
+const std::string& Argv::GetArgAt(int pos) {
+  if (pos < 0 || pos >= ArgCount()) {
+    FAIL() << "Argv index " << pos << " is out of bounds";
+    __builtin_unreachable();
+  } else {
+    return GetCurrent()->GetArgs()[pos];
+  }
+}
+
+const std::vector<std::string>& ProgramArgv::GetArgs() const {
+  return argv_;
 }
diff --git a/base/logging.hpp b/base/logging.hpp
--- a/base/logging.hpp
+++ b/base/logging.hpp
@@ -22,6 +22,7 @@
 #include <list>
 #include <sstream>
 #include <string>
+#include <vector>
 
 #include "thread-capture.h"
 
@@ -108,7 +109,7 @@
  public:
   template<int S>
   inline SourceContext(const char(&name)[S])
-    : at_(nullptr), name_(name), cross_and_capture_to_(this) {}
+    : at_(nullptr), name_(name), capture_to_(this) {}
 
  private:
   void SetLocal(const char*) final;
@@ -117,13 +118,13 @@
 
   const char* at_;
   const char* const name_;
-  const AutoThreadCrosser cross_and_capture_to_;
+  const ScopedCapture capture_to_;
 };
 
 class CleanupContext : public TraceContext {
  public:
   inline CleanupContext()
-    : at_(nullptr), cross_and_capture_to_(this) {}
+    : at_(nullptr), capture_to_(this) {}
 
  private:
   void SetLocal(const char*) final;
@@ -131,7 +132,31 @@
   const TraceContext* GetNext() const final;
 
   const char* at_;
-  const AutoThreadCrosser cross_and_capture_to_;
+  const ScopedCapture capture_to_;
+};
+
+class Argv : public capture_thread::ThreadCapture<Argv> {
+ public:
+  static int ArgCount();
+  static const std::string& GetArgAt(int pos);
+
+ protected:
+  virtual ~Argv() = default;
+
+ private:
+  virtual const std::vector<std::string>& GetArgs() const = 0;
+};
+
+class ProgramArgv : public Argv {
+ public:
+  inline ProgramArgv(int argc, const char** argv)
+    : argv_(argv, argv + argc), capture_to_(this) {}
+
+ private:
+  const std::vector<std::string>& GetArgs() const final;
+
+  const std::vector<std::string> argv_;
+  const ScopedCapture capture_to_;
 };
 
 #endif  // LOGGING_HPP_
diff --git a/bin/zeolite-setup.hs b/bin/zeolite-setup.hs
--- a/bin/zeolite-setup.hs
+++ b/bin/zeolite-setup.hs
@@ -53,13 +53,14 @@
 libraries :: [String]
 libraries = [
     "base",
-    "lib/util",
-    "lib/file",
     "tests/visibility/internal",
     "tests/visibility",
     "tests/visibility2/internal",
     "tests/visibility2",
-    "tests"
+    "tests",
+    "lib/util",
+    "lib/file",
+    "lib/math"
   ]
 
 createConfig :: IO LocalConfig
diff --git a/lib/file/Category_RawFileReader.cpp b/lib/file/Category_RawFileReader.cpp
--- a/lib/file/Category_RawFileReader.cpp
+++ b/lib/file/Category_RawFileReader.cpp
@@ -109,10 +109,8 @@
   ReturnTuple Call_open(const ParamTuple& params, const ValueTuple& args);
 };
 Type_RawFileReader& CreateType(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_RawFileReader>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_RawFileReader(CreateCategory(), params)); }
-  return *cached;
+  static auto& cached = *new Type_RawFileReader(CreateCategory(), Params<0>::Type());
+  return cached;
 }
 struct Value_RawFileReader : public TypeValue {
   Value_RawFileReader(Type_RawFileReader& p, const ParamTuple& params, const ValueTuple& args)
@@ -157,6 +155,7 @@
   ReturnTuple Call_readBlock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
   Type_RawFileReader& parent;
 
+  std::mutex mutex;
   R<std::istream> file;
 };
 S<TypeValue> CreateValue(Type_RawFileReader& parent, const ParamTuple& params, const ValueTuple& args) {
@@ -170,6 +169,7 @@
 
 ReturnTuple Value_RawFileReader::Call_freeResource(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
   TRACE_FUNCTION("RawFileReader.freeResource")
+  std::lock_guard<std::mutex> lock(mutex);
   if (file) {
     file = nullptr;
   }
@@ -178,6 +178,7 @@
 
 ReturnTuple Value_RawFileReader::Call_getFileError(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
   TRACE_FUNCTION("RawFileReader.getFileError")
+  std::lock_guard<std::mutex> lock(mutex);
   if (!file) {
     return ReturnTuple(Box_String(PrimString_FromLiteral("file has already been closed")));
   }
@@ -192,11 +193,13 @@
 
 ReturnTuple Value_RawFileReader::Call_pastEnd(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
   TRACE_FUNCTION("RawFileReader.pastEnd")
+  std::lock_guard<std::mutex> lock(mutex);
   return ReturnTuple(Box_Bool(!file || file->fail() || file->eof()));
 }
 
 ReturnTuple Value_RawFileReader::Call_readBlock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
   TRACE_FUNCTION("RawFileReader.readBlock")
+  std::lock_guard<std::mutex> lock(mutex);
   const PrimInt Var_arg1 = (args.At(0))->AsInt();
   if (Var_arg1 < 0) {
     FAIL() << "Read size " << Var_arg1 << " is invalid";
diff --git a/lib/file/Category_RawFileWriter.cpp b/lib/file/Category_RawFileWriter.cpp
--- a/lib/file/Category_RawFileWriter.cpp
+++ b/lib/file/Category_RawFileWriter.cpp
@@ -109,10 +109,8 @@
   ReturnTuple Call_open(const ParamTuple& params, const ValueTuple& args);
 };
 Type_RawFileWriter& CreateType(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_RawFileWriter>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_RawFileWriter(CreateCategory(), params)); }
-  return *cached;
+  static auto& cached = *new Type_RawFileWriter(CreateCategory(), Params<0>::Type());
+  return cached;
 }
 struct Value_RawFileWriter : public TypeValue {
   Value_RawFileWriter(Type_RawFileWriter& p, const ParamTuple& params, const ValueTuple& args)
@@ -155,6 +153,7 @@
   ReturnTuple Call_writeBlock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
   Type_RawFileWriter& parent;
 
+  std::mutex mutex;
   R<std::ostream> file;
 };
 S<TypeValue> CreateValue(Type_RawFileWriter& parent, const ParamTuple& params, const ValueTuple& args) {
@@ -167,6 +166,7 @@
 }
 ReturnTuple Value_RawFileWriter::Call_freeResource(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
   TRACE_FUNCTION("RawFileWriter.freeResource")
+  std::lock_guard<std::mutex> lock(mutex);
   if (file) {
     file = nullptr;
   }
@@ -174,6 +174,7 @@
 }
 ReturnTuple Value_RawFileWriter::Call_getFileError(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
   TRACE_FUNCTION("RawFileWriter.getFileError")
+  std::lock_guard<std::mutex> lock(mutex);
   if (!file) {
     return ReturnTuple(Box_String(PrimString_FromLiteral("file has already been closed")));
   }
@@ -187,6 +188,7 @@
 }
 ReturnTuple Value_RawFileWriter::Call_writeBlock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
   TRACE_FUNCTION("RawFileWriter.writeBlock")
+  std::lock_guard<std::mutex> lock(mutex);
   const PrimString& Var_arg1 = args.At(0)->AsString();
   int write_size = 0;
   if (file) {
diff --git a/lib/math/.zeolite-module b/lib/math/.zeolite-module
new file mode 100644
--- /dev/null
+++ b/lib/math/.zeolite-module
@@ -0,0 +1,12 @@
+root: "../.."
+path: "lib/math"
+private_deps: [
+  "../../tests"
+]
+extra_files: [
+  category_source {
+    source: "lib/math/Category_Math.cpp"
+    categories: [Math]
+  }
+]
+mode: incremental {}
diff --git a/lib/math/Category_Math.cpp b/lib/math/Category_Math.cpp
new file mode 100644
--- /dev/null
+++ b/lib/math/Category_Math.cpp
@@ -0,0 +1,361 @@
+/* -----------------------------------------------------------------------------
+Copyright 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 <cmath>
+
+#include "category-source.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Math.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+namespace ZEOLITE_DYNAMIC_NAMESPACE {
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
+
+namespace {
+const int collection_Math = 0;
+}  // namespace
+const void* const Functions_Math = &collection_Math;
+const TypeFunction& Function_Math_acos = (*new TypeFunction{ 0, 1, 1, "Math", "acos", Functions_Math, 0 });
+const TypeFunction& Function_Math_acosh = (*new TypeFunction{ 0, 1, 1, "Math", "acosh", Functions_Math, 1 });
+const TypeFunction& Function_Math_asin = (*new TypeFunction{ 0, 1, 1, "Math", "asin", Functions_Math, 2 });
+const TypeFunction& Function_Math_asinh = (*new TypeFunction{ 0, 1, 1, "Math", "asinh", Functions_Math, 3 });
+const TypeFunction& Function_Math_atan = (*new TypeFunction{ 0, 1, 1, "Math", "atan", Functions_Math, 4 });
+const TypeFunction& Function_Math_atanh = (*new TypeFunction{ 0, 1, 1, "Math", "atanh", Functions_Math, 5 });
+const TypeFunction& Function_Math_ceil = (*new TypeFunction{ 0, 1, 1, "Math", "ceil", Functions_Math, 6 });
+const TypeFunction& Function_Math_cos = (*new TypeFunction{ 0, 1, 1, "Math", "cos", Functions_Math, 7 });
+const TypeFunction& Function_Math_cosh = (*new TypeFunction{ 0, 1, 1, "Math", "cosh", Functions_Math, 8 });
+const TypeFunction& Function_Math_exp = (*new TypeFunction{ 0, 1, 1, "Math", "exp", Functions_Math, 9 });
+const TypeFunction& Function_Math_fabs = (*new TypeFunction{ 0, 1, 1, "Math", "fabs", Functions_Math, 10 });
+const TypeFunction& Function_Math_floor = (*new TypeFunction{ 0, 1, 1, "Math", "floor", Functions_Math, 11 });
+const TypeFunction& Function_Math_fmod = (*new TypeFunction{ 0, 2, 1, "Math", "fmod", Functions_Math, 12 });
+const TypeFunction& Function_Math_isinf = (*new TypeFunction{ 0, 1, 1, "Math", "isinf", Functions_Math, 13 });
+const TypeFunction& Function_Math_isnan = (*new TypeFunction{ 0, 1, 1, "Math", "isnan", Functions_Math, 14 });
+const TypeFunction& Function_Math_log = (*new TypeFunction{ 0, 1, 1, "Math", "log", Functions_Math, 15 });
+const TypeFunction& Function_Math_log10 = (*new TypeFunction{ 0, 1, 1, "Math", "log10", Functions_Math, 16 });
+const TypeFunction& Function_Math_log2 = (*new TypeFunction{ 0, 1, 1, "Math", "log2", Functions_Math, 17 });
+const TypeFunction& Function_Math_pow = (*new TypeFunction{ 0, 2, 1, "Math", "pow", Functions_Math, 18 });
+const TypeFunction& Function_Math_round = (*new TypeFunction{ 0, 1, 1, "Math", "round", Functions_Math, 19 });
+const TypeFunction& Function_Math_sin = (*new TypeFunction{ 0, 1, 1, "Math", "sin", Functions_Math, 20 });
+const TypeFunction& Function_Math_sinh = (*new TypeFunction{ 0, 1, 1, "Math", "sinh", Functions_Math, 21 });
+const TypeFunction& Function_Math_sqrt = (*new TypeFunction{ 0, 1, 1, "Math", "sqrt", Functions_Math, 22 });
+const TypeFunction& Function_Math_tan = (*new TypeFunction{ 0, 1, 1, "Math", "tan", Functions_Math, 23 });
+const TypeFunction& Function_Math_tanh = (*new TypeFunction{ 0, 1, 1, "Math", "tanh", Functions_Math, 24 });
+const TypeFunction& Function_Math_trunc = (*new TypeFunction{ 0, 1, 1, "Math", "trunc", Functions_Math, 25 });
+namespace {
+class Category_Math;
+class Type_Math;
+Type_Math& CreateType_Math(Params<0>::Type params);
+class Value_Math;
+S<TypeValue> CreateValue_Math(Type_Math& parent, const ParamTuple& params, const ValueTuple& args);
+struct Category_Math : public TypeCategory {
+  std::string CategoryName() const final { return "Math"; }
+  Category_Math() {
+    CycleCheck<Category_Math>::Check();
+    CycleCheck<Category_Math> marker(*this);
+    TRACE_FUNCTION("Math (init @category)")
+  }
+  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Category_Math::*)(const ParamTuple&, const ValueTuple&);
+    return TypeCategory::Dispatch(label, params, args);
+  }
+};
+Category_Math& CreateCategory_Math() {
+  static auto& category = *new Category_Math();
+  return category;
+}
+struct Type_Math : public TypeInstance {
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  void BuildTypeName(std::ostream& output) const final {
+    return TypeInstance::TypeNameFrom(output, parent);
+  }
+  Category_Math& 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_Math()) {
+      args = std::vector<const TypeInstance*>{};
+      return true;
+    }
+    return false;
+  }
+  Type_Math(Category_Math& p, Params<0>::Type params) : parent(p) {
+    CycleCheck<Type_Math>::Check();
+    CycleCheck<Type_Math> marker(*this);
+    TRACE_FUNCTION("Math (init @type)")
+  }
+  ReturnTuple Dispatch(const TypeFunction& label, const ParamTuple& params, const ValueTuple& args) final {
+    using CallType = ReturnTuple(Type_Math::*)(const ParamTuple&, const ValueTuple&);
+    static const CallType Table_Math[] = {
+      &Type_Math::Call_acos,
+      &Type_Math::Call_acosh,
+      &Type_Math::Call_asin,
+      &Type_Math::Call_asinh,
+      &Type_Math::Call_atan,
+      &Type_Math::Call_atanh,
+      &Type_Math::Call_ceil,
+      &Type_Math::Call_cos,
+      &Type_Math::Call_cosh,
+      &Type_Math::Call_exp,
+      &Type_Math::Call_fabs,
+      &Type_Math::Call_floor,
+      &Type_Math::Call_fmod,
+      &Type_Math::Call_isinf,
+      &Type_Math::Call_isnan,
+      &Type_Math::Call_log,
+      &Type_Math::Call_log10,
+      &Type_Math::Call_log2,
+      &Type_Math::Call_pow,
+      &Type_Math::Call_round,
+      &Type_Math::Call_sin,
+      &Type_Math::Call_sinh,
+      &Type_Math::Call_sqrt,
+      &Type_Math::Call_tan,
+      &Type_Math::Call_tanh,
+      &Type_Math::Call_trunc,
+    };
+    if (label.collection == Functions_Math) {
+      if (label.function_num < 0 || label.function_num >= 26) {
+        FAIL() << "Bad function call " << label;
+      }
+      return (this->*Table_Math[label.function_num])(params, args);
+    }
+    return TypeInstance::Dispatch(label, params, args);
+  }
+  ReturnTuple Call_acos(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_acosh(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asin(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_asinh(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_atan(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_atanh(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_ceil(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_cos(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_cosh(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_exp(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_fabs(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_floor(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_fmod(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_isinf(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_isnan(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_log(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_log10(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_log2(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_pow(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_round(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_sin(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_sinh(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_sqrt(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_tan(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_tanh(const ParamTuple& params, const ValueTuple& args);
+  ReturnTuple Call_trunc(const ParamTuple& params, const ValueTuple& args);
+};
+Type_Math& CreateType_Math(Params<0>::Type params) {
+  static auto& cached = *new Type_Math(CreateCategory_Math(), Params<0>::Type());
+  return cached;
+}
+struct Value_Math : public TypeValue {
+  Value_Math(Type_Math& p, const ParamTuple& params, const ValueTuple& args) : parent(p) {}
+  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
+    using CallType = ReturnTuple(Value_Math::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
+    return TypeValue::Dispatch(self, label, params, args);
+  }
+  std::string CategoryName() const final { return parent.CategoryName(); }
+  Type_Math& parent;
+};
+S<TypeValue> CreateValue_Math(Type_Math& parent, const ParamTuple& params, const ValueTuple& args) {
+  return S_get(new Value_Math(parent, params, args));
+}
+
+ReturnTuple Type_Math::Call_acos(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.acos")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::acos(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_acosh(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.acosh")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::acosh(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_asin(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.asin")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::asin(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_asinh(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.asinh")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::asinh(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_atan(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.atan")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::atan(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_atanh(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.atanh")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::atanh(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_ceil(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.ceil")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::ceil(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_cos(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.cos")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::cos(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_cosh(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.cosh")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::cosh(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_exp(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.exp")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::exp(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_fabs(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.fabs")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::fabs(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_floor(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.floor")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::floor(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_fmod(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.fmod")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
+  return ReturnTuple(Box_Float(std::fmod(Var_arg1,Var_arg2)));
+}
+
+ReturnTuple Type_Math::Call_isinf(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.isinf")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Bool(std::isinf(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_isnan(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.isnan")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Bool(std::isnan(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_log(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.log")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::log(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_log10(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.log10")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::log10(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_log2(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.log2")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::log2(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_pow(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.pow")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
+  return ReturnTuple(Box_Float(std::pow(Var_arg1,Var_arg2)));
+}
+
+ReturnTuple Type_Math::Call_round(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.round")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::round(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_sin(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.sin")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::sin(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_sinh(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.sinh")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::sinh(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_sqrt(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.sqrt")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::sqrt(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_tan(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.tan")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::tan(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_tanh(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.tanh")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::tanh(Var_arg1)));
+}
+
+ReturnTuple Type_Math::Call_trunc(const ParamTuple& params, const ValueTuple& args) {
+  TRACE_FUNCTION("Math.trunc")
+  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+  return ReturnTuple(Box_Float(std::trunc(Var_arg1)));
+}
+
+}  // namespace
+
+TypeCategory& GetCategory_Math() {
+  return CreateCategory_Math();
+}
+TypeInstance& GetType_Math(Params<0>::Type params) {
+  return CreateType_Math(params);
+}
+
+#ifdef ZEOLITE_DYNAMIC_NAMESPACE
+}  // namespace ZEOLITE_DYNAMIC_NAMESPACE
+using namespace ZEOLITE_DYNAMIC_NAMESPACE;
+#endif  // ZEOLITE_DYNAMIC_NAMESPACE
diff --git a/lib/math/math.0rp b/lib/math/math.0rp
new file mode 100644
--- /dev/null
+++ b/lib/math/math.0rp
@@ -0,0 +1,50 @@
+/* -----------------------------------------------------------------------------
+Copyright 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]
+
+concrete Math {
+  @type cos (Float) -> (Float)
+  @type sin (Float) -> (Float)
+  @type tan (Float) -> (Float)
+  @type acos (Float) -> (Float)
+  @type asin (Float) -> (Float)
+  @type atan (Float) -> (Float)
+
+  @type cosh (Float) -> (Float)
+  @type sinh (Float) -> (Float)
+  @type tanh (Float) -> (Float)
+  @type acosh (Float) -> (Float)
+  @type asinh (Float) -> (Float)
+  @type atanh (Float) -> (Float)
+
+  @type exp (Float) -> (Float)
+  @type log (Float) -> (Float)
+  @type log10 (Float) -> (Float)
+  @type log2 (Float) -> (Float)
+  @type pow (Float,Float) -> (Float)
+  @type sqrt (Float) -> (Float)
+
+  @type ceil (Float) -> (Float)
+  @type floor (Float) -> (Float)
+  @type fmod (Float,Float) -> (Float)
+  @type trunc (Float) -> (Float)
+  @type round (Float) -> (Float)
+  @type fabs (Float) -> (Float)
+
+  @type isinf (Float) -> (Bool)
+  @type isnan (Float) -> (Bool)
+}
diff --git a/lib/math/tests.0rt b/lib/math/tests.0rt
new file mode 100644
--- /dev/null
+++ b/lib/math/tests.0rt
@@ -0,0 +1,410 @@
+/* -----------------------------------------------------------------------------
+Copyright 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]
+
+testcase "sanity check cos" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$cos(2.0),-0.42,-0.41)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check sin" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$sin(2.0),0.90,0.91)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check tan" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$tan(2.0),-2.19,-2.18)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check acos" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$acos(0.5),1.04,1.05)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check asin" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$asin(0.5),0.52,0.53)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check atan" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$atan(0.5),0.46,0.47)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check cosh" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$cosh(2.0),3.76,3.77)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check sinh" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$sinh(2.0),3.62,3.63)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check tanh" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$tanh(2.0),0.96,0.97)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check acosh" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$acosh(2.0),1.31,1.32)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check asinh" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$asinh(2.0),1.44,1.45)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check atanh" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$atanh(0.5),0.54,0.55)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check exp" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$exp(1.0),2.71,2.72)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check log" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$log(9.0),2.19,2.20)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check log10" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$log10(100.0),1.99,2.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check log2" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$log2(8.0),2.99,3.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check pow" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$pow(2.0,3.0),7.99,8.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check sqrt" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$sqrt(4.0),1.99,2.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check ceil" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$ceil(2.2),2.99,3.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check floor" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$floor(2.2),1.99,2.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check fmod" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$fmod(7.0,4.0),2.99,3.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check trunc" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$trunc(2.2),1.99,2.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check round" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$round(2.7),2.99,3.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check fabs" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+   \ Testing$checkBetween<Float>(Math$fabs(-10.0),9.99,10.01)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check isinf" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+    if (!Math$isinf(Math$log(0.0))) {
+      fail("Failed")
+    }
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "sanity check isnan" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+    if (!Math$isnan(Math$sqrt(-1.0))) {
+      fail("Failed")
+    }
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
diff --git a/lib/util/Category_Argv.cpp b/lib/util/Category_Argv.cpp
--- a/lib/util/Category_Argv.cpp
+++ b/lib/util/Category_Argv.cpp
@@ -107,10 +107,8 @@
   }
 };
 Type_Argv& CreateType(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_Argv>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_Argv(CreateCategory(), params)); }
-  return *cached;
+  static auto& cached = *new Type_Argv(CreateCategory(), Params<0>::Type());
+  return cached;
 }
 struct Value_Argv : public TypeValue {
   Value_Argv(Type_Argv& p, const ParamTuple& params, const ValueTuple& args) : parent(p) {}
diff --git a/lib/util/Category_SimpleInput.cpp b/lib/util/Category_SimpleInput.cpp
--- a/lib/util/Category_SimpleInput.cpp
+++ b/lib/util/Category_SimpleInput.cpp
@@ -104,10 +104,8 @@
   }
 };
 Type_SimpleInput& CreateType(Params<0>::Type params) {
-  static auto& cache = *new InstanceMap<0,Type_SimpleInput>();
-  auto& cached = cache[params];
-  if (!cached) { cached = R_get(new Type_SimpleInput(CreateCategory(), params)); }
-  return *cached;
+  static auto& cached = *new Type_SimpleInput(CreateCategory(), Params<0>::Type());
+  return cached;
 }
 struct Value_SimpleInput : public TypeValue {
   Value_SimpleInput(Type_SimpleInput& p, const ParamTuple& params, const ValueTuple& args) : parent(p) {}
@@ -123,6 +121,7 @@
     }
     if (&label == &Function_BlockReader_readBlock) {
       TRACE_FUNCTION("SimpleInput.readBlock")
+      std::lock_guard<std::mutex> lock(mutex);
       const int size = args.At(0)->AsInt();
       if (size < 0) {
         FAIL() << "Read size " << size << " is invalid";
@@ -137,6 +136,8 @@
       }
     }
     if (&label == &Function_BlockReader_pastEnd) {
+      TRACE_FUNCTION("SimpleInput.pastEnd")
+      std::lock_guard<std::mutex> lock(mutex);
       return ReturnTuple(Box_Bool(zero_read));
     }
     return TypeValue::Dispatch(self, label, params, args);
@@ -144,6 +145,7 @@
 
   std::string CategoryName() const final { return parent.CategoryName(); }
   bool zero_read = false;
+  std::mutex mutex;
   Type_SimpleInput& parent;
 };
 S<TypeValue> CreateValue(Type_SimpleInput& parent, const ParamTuple& params, const ValueTuple& args) {
diff --git a/lib/util/Category_SimpleOutput.cpp b/lib/util/Category_SimpleOutput.cpp
--- a/lib/util/Category_SimpleOutput.cpp
+++ b/lib/util/Category_SimpleOutput.cpp
@@ -98,6 +98,7 @@
     }
     if (&label == &Function_Writer_write) {
       TRACE_FUNCTION("SimpleOutput.write")
+      std::lock_guard<std::mutex> lock(mutex_);
       output_ << TypeValue::Call(args.At(0), Function_Formatted_formatted,
                                  ParamTuple(), ArgTuple()).Only()->AsString();
       return ReturnTuple();
@@ -109,6 +110,7 @@
   }
 
  private:
+  std::mutex mutex_;
   std::ostream& output_;
 };
 
@@ -127,12 +129,14 @@
     }
     if (&label == &Function_Writer_write) {
       TRACE_FUNCTION("SimpleOutput.write")
+      std::lock_guard<std::mutex> lock(mutex_);
       output_ << TypeValue::Call(args.At(0), Function_Formatted_formatted,
                                  ParamTuple(), ArgTuple()).Only()->AsString();
       return ReturnTuple();
     }
     if (&label == &Function_BufferedWriter_flush) {
       TRACE_FUNCTION("SimpleOutput.flush")
+      std::lock_guard<std::mutex> lock(mutex_);
       FAIL() << output_.str();
       return ReturnTuple();
     }
@@ -140,6 +144,7 @@
   }
 
  private:
+  std::mutex mutex_;
   std::ostringstream output_;
 };
 
diff --git a/src/Cli/CompileOptions.hs b/src/Cli/CompileOptions.hs
--- a/src/Cli/CompileOptions.hs
+++ b/src/Cli/CompileOptions.hs
@@ -30,6 +30,7 @@
   getSourceDeps,
   getSourceFile,
   isCompileBinary,
+  isCompileFast,
   isCompileIncremental,
   isCompileRecompile,
   isCreateTemplates,
@@ -99,6 +100,11 @@
     cbOutputName :: String,
     cbLinkFlags :: [String]
   } |
+  CompileFast {
+    cfCategory :: String,
+    cfFunction :: String,
+    cfSource :: String
+  } |
   ExecuteTests {
     etInclude :: [String]
   } |
@@ -106,6 +112,7 @@
     ciLinkFlags :: [String]
   } |
   CompileRecompile |
+  CompileRecompileRecursive |
   CreateTemplates |
   CompileUnspecified
   deriving (Eq,Show)
@@ -114,13 +121,18 @@
 isCompileBinary (CompileBinary _ _ _ _) = True
 isCompileBinary _                       = False
 
+isCompileFast :: CompileMode -> Bool
+isCompileFast (CompileFast _ _ _) = True
+isCompileFast _                   = False
+
 isCompileIncremental :: CompileMode -> Bool
 isCompileIncremental (CompileIncremental _) = True
 isCompileIncremental _                      = False
 
 isCompileRecompile :: CompileMode -> Bool
-isCompileRecompile CompileRecompile = True
-isCompileRecompile _                = False
+isCompileRecompile CompileRecompile          = True
+isCompileRecompile CompileRecompileRecursive = True
+isCompileRecompile _                         = False
 
 isExecuteTests :: CompileMode -> Bool
 isExecuteTests (ExecuteTests _) = True
diff --git a/src/Cli/Compiler.hs b/src/Cli/Compiler.hs
--- a/src/Cli/Compiler.hs
+++ b/src/Cli/Compiler.hs
@@ -26,7 +26,7 @@
 import System.Directory
 import System.Exit
 import System.FilePath
-import System.Posix.Temp (mkstemps)
+import System.Posix.Temp (mkdtemp,mkstemps)
 import System.IO
 import qualified Data.Set as Set
 
@@ -67,11 +67,13 @@
   processResults passed failed (mergeAllM $ map snd allResults) where
     preloadModule b base d = do
       m <- loadMetadata (p </> d)
-      (fr1,deps1) <- loadPublicDeps (getCompilerHash b) [base,p </> d]
+      (fr0,deps0) <- loadPublicDeps (getCompilerHash b) [base]
+      checkAllowedStale fr0 f
+      (fr1,deps1) <- loadTestingDeps (getCompilerHash b) (p </> d)
       checkAllowedStale fr1 f
-      (fr2,deps2) <- loadPrivateDeps (getCompilerHash b) deps1
+      (fr2,deps2) <- loadPrivateDeps (getCompilerHash b) (deps0++deps1)
       checkAllowedStale fr2 f
-      return (d,m,deps1,deps2)
+      return (d,m,deps0++deps1,deps2)
     getTestsFromPreload (_,m,_,_) = cmTestFiles m
     allowTests = Set.fromList tp
     isTestAllowed t = if null allowTests then True else t `Set.member` allowTests
@@ -99,6 +101,39 @@
       | otherwise = do
           hPutStrLn stderr $ "\nPassed: " ++ show passed ++ " test(s), Failed: " ++ show failed ++ " test(s)"
           hPutStrLn stderr $ "Zeolite tests passed."
+runCompiler (CompileOptions h is is2 _ _ _ p (CompileFast c fn f2) f) = do
+  dir <- mkdtemp "/tmp/zfast_"
+  absolute <- canonicalizePath p
+  is' <- sequence $ map (canonicalizePath . (p </>)) is
+  is2' <- sequence $ map (canonicalizePath . (p </>)) is2
+  f2' <- canonicalizePath (p </> f2)
+  let config = ModuleConfig {
+      rmRoot = absolute,
+      rmPath = dir,
+      rmPublicDeps = is',
+      rmPrivateDeps = is2',
+      rmExtraFiles = [OtherSource f2'],
+      rmExtraPaths = [],
+      rmMode = (CompileBinary c fn (absolute </> c) [])
+    }
+  writeRecompile dir config
+  runCompiler (CompileOptions h [] [] [dir] [] [] "" CompileRecompile f)
+  removeDirectoryRecursive dir
+runCompiler (CompileOptions h _ _ ds _ _ p CompileRecompileRecursive f) = do
+  recursiveSequence Set.empty ds where
+    recursiveSequence da (d0:ds2) = do
+      d <- canonicalizePath (p </> d0)
+      rm <- tryLoadRecompile d
+      case rm of
+           Nothing -> do
+             hPutStrLn stderr $ "Path " ++ d ++ " does not have a valid configuration."
+             exitFailure
+           Just m -> when (not $ rmPath m `Set.member` da) $ do
+             let ds3 = map (\d2 -> d </> d2) (rmPublicDeps m ++ rmPrivateDeps m)
+             let da' = rmPath m `Set.insert` da
+             recursiveSequence da' (ds3 ++ ds2)
+             runCompiler (CompileOptions h [] [] [d] [] [] "" CompileRecompile f)
+    recursiveSequence _ _ = return ()
 runCompiler (CompileOptions h _ _ ds _ _ p CompileRecompile f) = do
   (backend,_) <- loadConfig
   fmap mergeAll $ sequence $ map (recompileSingle $ getCompilerHash backend) ds where
@@ -108,7 +143,7 @@
       upToDate <- isPathUpToDate h2 d
       maybeCompile rm upToDate where
         maybeCompile Nothing _ = do
-          hPutStrLn stderr $ "Path " ++ d0 ++ " has not been configured or compiled yet."
+          hPutStrLn stderr $ "Path " ++ d0 ++ " does not have a valid configuration."
           exitFailure
         maybeCompile (Just rm') upToDate
           | f < ForceAll && upToDate = hPutStrLn stderr $ "Path " ++ d0 ++ " is up to date."
@@ -137,18 +172,23 @@
   (fr,deps) <- loadPublicDeps (getCompilerHash backend) (as ++ as2)
   checkAllowedStale fr f
   if isCreateTemplates m
-      then sequence_ $ map (processTemplates deps) ds
+      then do
+        base <- resolveBaseModule resolver
+        (fr2,bpDeps) <- loadPublicDeps (getCompilerHash backend) [base]
+        checkAllowedStale fr2 f
+        sequence_ $ map (processTemplates $ bpDeps ++ deps) ds
       else do
         ma <- sequence $ map (processPath backend resolver deps as as2) ds
         let ms = concat $ map snd ma
-        let deps2 = map fst ma
-        createBinary backend resolver (deps ++ deps2) m ms
+        let deps2 = map (snd . fst) ma
+        let paths2 = concat $ map (fst . fst) ma
+        createBinary backend resolver paths2 (deps ++ deps2) m ms
   hPutStrLn stderr $ "Zeolite compilation succeeded." where
     ep' = fixPaths $ map (p </>) ep
     processPath b r deps as as2 d = do
       isConfigured <- isPathConfigured d
       when (isConfigured && f == DoNotForce) $ do
-        hPutStrLn stderr $ "Module " ++ d ++ " has already been configured. " ++
+        hPutStrLn stderr $ "Module " ++ d ++ " has an existing configuration. " ++
                            "Recompile with -r or use -f to overwrite the config."
         exitFailure
       eraseCachedData (p </> d)
@@ -164,6 +204,9 @@
       }
       when (f == DoNotForce || f == ForceAll) $ writeRecompile (p </> d) rm
       (ps,xs,ts) <- findSourceFiles p d
+      ps2 <- sequence $ map canonicalizePath $ filter (isSuffixOf ".0rp") $ map ((p </>) . getSourceFile) es
+      xs2 <- sequence $ map canonicalizePath $ filter (isSuffixOf ".0rx") $ map ((p </>) . getSourceFile) es
+      ts2 <- sequence $ map canonicalizePath $ filter (isSuffixOf ".0rt") $ map ((p </>) . getSourceFile) es
       base <- resolveBaseModule r
       actual <- resolveModule r p d
       isBase <- isBaseModule r actual
@@ -177,15 +220,15 @@
       let ss = fixPaths $ getSourceFilesForDeps deps2
       ss' <- zipWithContents p ss
       let paths = base:(getIncludePathsForDeps deps2)
-      ps' <- zipWithContents p ps
-      xs' <- zipWithContents p xs
+      ps' <- zipWithContents p (ps ++ ps2)
+      xs' <- zipWithContents p (xs ++ xs2)
       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 paths ns0 deps2 d as as2
-                  (map takeFileName ps)
-                  (map takeFileName xs)
-                  (map takeFileName ts) fs
+                  (map takeFileName ps ++ ps2)
+                  (map takeFileName xs ++ xs2)
+                  (map takeFileName ts ++ ts2) fs
     writeOutput b paths ns0 deps d as as2 ps xs ts fs
       | isCompileError fs = do
           formatWarnings fs
@@ -197,6 +240,7 @@
           let (pc,mf,fs') = getCompileSuccess fs
           let ss = map (\ns -> getCachedPath (p </> d) ns "") $ nub $ filter (not . null) $ map show $ [ns0] ++ map coNamespace fs'
           ss' <- sequence $ map canonicalizePath ss
+          s0 <- canonicalizePath $ getCachedPath (p </> d) (show ns0) ""
           let paths' = paths ++ ep' ++ ss'
           let hxx   = filter (isSuffixOf ".hpp" . coFilename)       fs'
           let other = filter (not . isSuffixOf ".hpp" . coFilename) fs'
@@ -216,7 +260,7 @@
               cmPublicDeps = as,
               cmPrivateDeps = as2,
               cmCategories = sort $ map show pc,
-              cmSubdirs = sort $ ss',
+              cmSubdirs = [s0],
               cmPublicFiles = sort ps,
               cmPrivateFiles = sort xs,
               cmTestFiles = sort ts,
@@ -226,7 +270,7 @@
               cmObjectFiles = os1' ++ osOther ++ map OtherObjectFile os'
             }
           when (not $ isCreateTemplates m) $ writeMetadata (p </> d) cm
-          return (cm,mf)
+          return ((ss',cm),mf)
     formatWarnings c
       | null $ getCompileWarnings c = return ()
       | otherwise = hPutStr stderr $ "Compiler warnings:\n" ++ (concat $ map (++ "\n") (getCompileWarnings c))
@@ -339,7 +383,7 @@
     addIncludes tm fs = do
       cs <- fmap concat $ collectAllOrErrorM $ map parsePublicSource fs
       includeNewTypes tm cs
-    createBinary b r deps (CompileBinary n _ o lf) ms
+    createBinary b r paths deps (CompileBinary n _ o lf) ms
       | length ms > 1 = do
         hPutStrLn stderr $ "Multiple matches for main category " ++ n ++ "."
         exitFailure
@@ -359,15 +403,15 @@
           (_,bpDeps) <- loadPublicDeps (getCompilerHash b) [base]
           (_,deps2) <- loadPrivateDeps (getCompilerHash b) (bpDeps ++ deps)
           let lf' = lf ++ getLinkFlagsForDeps deps2
-          let paths = fixPaths $ base:(getIncludePathsForDeps deps2)
+          let paths' = fixPaths $ paths ++ base:(getIncludePathsForDeps deps2)
           let os    = getObjectFilesForDeps deps2
           let ofr = getObjectFileResolver os
           let os' = ofr ns2 req
-          let command = CompileToBinary o' os' f0 paths lf'
+          let command = CompileToBinary o' os' f0 paths' lf'
           hPutStrLn stderr $ "Creating binary " ++ f0
           _ <- runCxxCommand b command
           removeFile o'
-    createBinary _ _ _ _ _ = return ()
+    createBinary _ _ _ _ _ _ = return ()
     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
@@ -31,7 +31,9 @@
 import Text.Regex.TDFA -- Not safe!
 
 import Base.CompileError
+import Cli.CompileMetadata
 import Cli.CompileOptions
+import Cli.ProcessMetadata
 import Config.LoadConfig (compilerVersion,rootPath)
 
 
@@ -39,24 +41,33 @@
 optionHelpText = [
     "",
     "zeolite [options...] -m [category(.function)] -o [binary] [path]",
+    "zeolite [options...] --fast [category(.function)] [.0rx source]",
     "zeolite [options...] -c [paths...]",
     "zeolite [options...] -r [paths...]",
+    "zeolite [options...] -R [paths...]",
     "zeolite [options...] -t [paths...]",
+    "",
     "zeolite [options...] --templates [paths...]",
     "zeolite --get-path",
+    "zeolite --show-deps [paths...]",
     "zeolite --version",
     "",
-    "Modes:",
+    "Normal Modes:",
     "  -c: Only compile the individual files. (default)",
     "  -m [category(.function)]: Create a binary that executes the function.",
+    "  --fast [category(.function)] [.0rx source]: Create a binary without needing a module.",
     "  -r: Recompile using the previous compilation options.",
+    "  -R: Recursively recompile using the previous compilation options.",
     "  -t: Only execute tests, without other compilation.",
+    "",
+    "Special Modes:",
     "  --templates: Only create C++ templates for undefined categories in .0rp sources.",
     "  --get-path: Show the data path and immediately exit.",
+    "  --show-deps: Show category dependencies for the modules.",
     "  --version: Show the compiler version and immediately exit.",
     "",
     "Options:",
-    "  -f: Force compilation instead of recompiling with -r.",
+    "  -f: Force compilation instead of recompiling with -r/-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.",
     "  -o [binary]: The name of the binary file to create with -m.",
@@ -85,6 +96,17 @@
   if null os
      then exitSuccess
      else exitFailure
+tryFastModes ("--show-deps":ps) = do
+  mapM_ showDeps ps
+  exitSuccess where
+    showDeps p = do
+      p' <- canonicalizePath p
+      m <- loadMetadata p'
+      hPutStrLn stdout $ show p'
+      mapM_ showDep (cmObjectFiles m)
+    showDep (CategoryObjectFile c ds _) = do
+      mapM_ (\d -> hPutStrLn stdout $ "  " ++ ciCategory c ++ " -> " ++ ciCategory d ++ " " ++ show (ciPath d)) ds
+    showDep _ = return ()
 tryFastModes _ = return ()
 
 parseCompileOptions :: CompileErrorM m => [String] -> m CompileOptions
@@ -121,6 +143,10 @@
     | m /= CompileUnspecified = argError n "-r" "Compiler mode already set."
     | otherwise = return (os,CompileOptions (maybeDisableHelp h) is is2 ds es ep p CompileRecompile f)
 
+  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 p CompileRecompileRecursive f)
+
   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 p (ExecuteTests []) f)
@@ -139,9 +165,23 @@
         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 ++ "\"."
+          check _          = argError n2 c $ "Invalid entry point."
       update _ = argError n "-m" "Requires a category name."
 
+  parseSingle (CompileOptions h is is2 ds es ep p m f) ((n,"--fast"):os)
+    | m /= CompileUnspecified = argError n "--fast" "Compiler mode already set."
+    | otherwise = update os where
+      update ((n2,c):(n3,f2):os2) =  do
+        (t,fn) <- check $ break (== '.') c
+        checkCategoryName n2 t  "--fast"
+        checkFunctionName n2 fn "--fast"
+        when (not $ isSuffixOf ".0rx" f2) $ argError n3 f2 $ "Must specify a .0rx source file."
+        return (os2,CompileOptions (maybeDisableHelp h) is is2 ds es ep p (CompileFast t fn f2) f) where
+          check (t,"")     = return (t,defaultMainFunc)
+          check (t,'.':fn) = return (t,fn)
+          check _          = argError n2 c $ "Invalid entry point."
+      update _ = argError n "--fast" "Requires a category name and a .0rx file."
+
   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
@@ -202,10 +242,13 @@
     compileError "Include paths (-i/-I) are not allowed in test mode (-t)."
 
   | (not $ null $ is ++ is2) && (isCompileRecompile m) =
-    compileError "Include paths (-i/-I) are not allowed in recompile mode (-r)."
+    compileError "Include paths (-i/-I) are not allowed in recompile mode (-r/-R)."
 
-  | null ds =
+  | null ds && (not $ isCompileFast m) =
     compileError "Please specify at least one input path."
   | (length ds /= 1) && (isCompileBinary m) =
     compileError "Specify exactly one input path for binary mode (-m)."
+  | (length ds /= 0) && (isCompileFast m) =
+    compileError "Specify exactly one input path for fast mode (--fast)."
+
   | otherwise = return co
diff --git a/src/Cli/ProcessMetadata.hs b/src/Cli/ProcessMetadata.hs
--- a/src/Cli/ProcessMetadata.hs
+++ b/src/Cli/ProcessMetadata.hs
@@ -35,6 +35,7 @@
   isPathUpToDate,
   loadPrivateDeps,
   loadPublicDeps,
+  loadTestingDeps,
   loadMetadata,
   resolveCategoryDeps,
   resolveObjectDeps,
@@ -47,7 +48,7 @@
 
 import Control.Applicative ((<|>))
 import Control.Monad (when)
-import Data.List (nub,isSuffixOf)
+import Data.List (isSuffixOf)
 import Data.Maybe (isJust)
 import System.Directory
 import System.Exit (exitFailure)
@@ -118,7 +119,7 @@
       if isCompileError m
          then do
            hPutStrLn stderr $ "Could not parse config file:"
-           hPutStrLn stderr $ show (getCompileError m)
+           hPutStr stderr $ show (getCompileError m)
            return Nothing
          else return $ Just (getCompileSuccess m)
 
@@ -128,11 +129,14 @@
   case m of
        Nothing -> return False
        Just _ -> do
-         (fr,_) <- loadDepsCommon True h (\m2 -> cmPublicDeps m2 ++ cmPrivateDeps m2) [p]
+         (fr,_) <- loadDepsCommon True h Set.empty (\m2 -> cmPublicDeps m2 ++ cmPrivateDeps m2) [p]
          return fr
 
 isPathConfigured :: String -> IO Bool
-isPathConfigured p = tryLoadRecompile p >>= return . isJust
+isPathConfigured p = do
+  -- Just for error messages.
+  _ <- tryLoadRecompile p
+  doesFileExist (p </> recompileFilename)
 
 writeMetadata :: String -> CompileMetadata -> IO ()
 writeMetadata p m = do
@@ -223,22 +227,23 @@
 getObjectFilesForDeps = concat . map cmObjectFiles
 
 loadPublicDeps :: String -> [String] -> IO (Bool,[CompileMetadata])
-loadPublicDeps h = loadDepsCommon False h cmPublicDeps
+loadPublicDeps h = loadDepsCommon False h Set.empty cmPublicDeps
 
+loadTestingDeps :: String -> String -> IO (Bool,[CompileMetadata])
+loadTestingDeps h p = do
+  m <- loadMetadata p
+  loadDepsCommon False h Set.empty cmPublicDeps (p:cmPrivateDeps m)
+
 loadPrivateDeps :: String -> [CompileMetadata] -> IO (Bool,[CompileMetadata])
 loadPrivateDeps h ms = do
-  (fr,new) <- loadDepsCommon False h (\m -> cmPublicDeps m ++ cmPrivateDeps m) toFind
-  return (fr,ms ++ existing ++ new) where
+  (fr,new) <- loadDepsCommon False h pa (\m -> cmPublicDeps m ++ cmPrivateDeps m) paths
+  return (fr,ms ++ new) where
     paths = concat $ map (\m -> cmPublicDeps m ++ cmPrivateDeps m) ms
-    (existing,toFind) = foldl splitByExisting ([],[]) $ nub paths
-    byPath = Map.fromList $ map (\m -> (cmPath m,m)) ms
-    splitByExisting (es,fs) p =
-      case p `Map.lookup` byPath of
-          Just m  -> (es ++ [m],fs)
-          Nothing -> (es,fs ++ [p])
+    pa = Set.fromList $ map cmPath ms
 
-loadDepsCommon :: Bool -> String -> (CompileMetadata -> [String]) -> [String] -> IO (Bool,[CompileMetadata])
-loadDepsCommon s h f ps = fmap snd $ fixedPaths >>= collect (Set.empty,(True,[])) where
+loadDepsCommon :: Bool -> String -> Set.Set String->
+  (CompileMetadata -> [String]) -> [String] -> IO (Bool,[CompileMetadata])
+loadDepsCommon s h pa0 f ps = fmap snd $ fixedPaths >>= collect (pa0,(True,[])) where
   fixedPaths = sequence $ map canonicalizePath ps
   collect xa@(pa,(fr,xs)) (p:ps2)
     | p `Set.member` pa = collect xa ps2
diff --git a/src/Cli/TestRunner.hs b/src/Cli/TestRunner.hs
--- a/src/Cli/TestRunner.hs
+++ b/src/Cli/TestRunner.hs
@@ -23,6 +23,7 @@
 import Control.Arrow (second)
 import Control.Monad (when)
 import Data.List (isSuffixOf,nub)
+import System.Directory
 import System.IO
 import System.Posix.Temp (mkdtemp)
 import System.FilePath
@@ -105,13 +106,16 @@
          else do
            let warnings = getCompileWarnings result
            let (req,main,ns,fs) = getCompileSuccess result
-           binaryName <- createBinary main req [ns] fs
+           (dir,binaryName) <- createBinary main req [ns] fs
            let command = TestCommand binaryName (takeDirectory binaryName)
            (TestCommandResult s2' out err) <- runTestCommand b command
            case (s2,s2') of
                 (True,False) -> return $ mergeAllM $ map compileError $ warnings ++ err ++ out
                 (False,True) -> return $ compileError "Expected runtime failure"
-                _ -> return $ checkContent rs es warnings err out
+                _ -> do
+                  let result2 = checkContent rs es warnings err out
+                  when (not $ isCompileError result) $ removeDirectoryRecursive dir
+                  return result2
 
     compileAll e cs ds = do
       let ns0 = map (StaticNamespace . cmNamespace) deps
@@ -163,7 +167,8 @@
       let ofr = getObjectFileResolver (sources' ++ os)
       let os' = ofr ns req
       let command = CompileToBinary main os' binary paths' lf
-      runCxxCommand b command
+      file <- runCxxCommand b command
+      return (dir,file)
     writeSingleFile d ca@(CxxOutput _ f2 _ _ _ content) = do
       writeFile (d </> f2) $ concat $ map (++ "\n") content
       if isSuffixOf ".cpp" f2
diff --git a/src/CompilerCxx/Category.hs b/src/CompilerCxx/Category.hs
--- a/src/CompilerCxx/Category.hs
+++ b/src/CompilerCxx/Category.hs
@@ -236,7 +236,7 @@
       }
     createArg = InputValue [] . VariableName . ("arg" ++) . show
     failProcedure f = Procedure [] [
-        NoValueExpression [] $ LineComment $ "// TODO: Implement " ++ funcName f ++ ".",
+        NoValueExpression [] $ LineComment $ "TODO: Implement " ++ funcName f ++ ".",
         FailCall [] (Literal (StringLiteral [] $ funcName f ++ " is not implemented"))
       ]
     funcName f = show (sfType f) ++ "." ++ show (sfName f)
@@ -656,14 +656,25 @@
 
 defineInternalType :: Monad m =>
   CategoryName -> Int -> m (CompiledData [String])
-defineInternalType t n = return $ onlyCodes [
-    typeName t ++ "& " ++ typeCreator t ++ "(Params<" ++ show n ++ ">::Type params) {",
-    "  static auto& cache = *new InstanceMap<" ++ show n ++ "," ++ typeName t ++ ">();",
-    "  auto& cached = cache[params];",
-    "  if (!cached) { cached = R_get(new " ++ typeName t ++ "(" ++ categoryCreator t ++ "(), params)); }",
-    "  return *cached;",
-    "}"
-  ]
+defineInternalType t n
+  | n < 1 =
+      return $ onlyCodes [
+        typeName t ++ "& " ++ typeCreator t ++ "(Params<" ++ show n ++ ">::Type params) {",
+        "  static auto& cached = *new " ++ typeName t ++ "(" ++ categoryCreator t ++ "(), Params<" ++ show n ++ ">::Type());",
+        "  return cached;",
+        "}"
+      ]
+  | otherwise =
+      return $ onlyCodes [
+        typeName t ++ "& " ++ typeCreator t ++ "(Params<" ++ show n ++ ">::Type params) {",
+        "  static auto& cache = *new InstanceMap<" ++ show n ++ "," ++ typeName t ++ ">();",
+        "  static auto& cache_mutex = *new std::mutex;",
+        "  std::lock_guard<std::mutex> lock(cache_mutex);",
+        "  auto& cached = cache[params];",
+        "  if (!cached) { cached = R_get(new " ++ typeName t ++ "(" ++ categoryCreator t ++ "(), params)); }",
+        "  return *cached;",
+        "}"
+      ]
 
 declareInternalValue :: Monad m =>
   CategoryName -> Int -> Int -> m (CompiledData [String])
diff --git a/src/CompilerCxx/Naming.hs b/src/CompilerCxx/Naming.hs
--- a/src/CompilerCxx/Naming.hs
+++ b/src/CompilerCxx/Naming.hs
@@ -74,7 +74,7 @@
 baseSourceIncludes = ["#include \"category-source.hpp\""]
 
 mainSourceIncludes :: [String]
-mainSourceIncludes = ["#include \"argv.hpp\""]
+mainSourceIncludes = ["#include \"logging.hpp\""]
 
 paramName :: ParamName -> String
 paramName p = "Param_" ++ tail (pnName p) -- Remove leading '#'.
diff --git a/src/CompilerCxx/Procedure.hs b/src/CompilerCxx/Procedure.hs
--- a/src/CompilerCxx/Procedure.hs
+++ b/src/CompilerCxx/Procedure.hs
@@ -470,6 +470,7 @@
       doUnary t e2
         | o == "!" = doNot t e2
         | o == "-" = doNeg t e2
+        | o == "~" = doComp t e2
         | otherwise = lift $ compileError $ "Unknown unary operator \"" ++ o ++ "\" " ++
                                             formatFullContextBrace c
       doNot t e2 = do
@@ -484,6 +485,11 @@
                                              UnboxedPrimitive PrimFloat $ "-" ++ useAsUnboxed PrimFloat e2)
         | otherwise = lift $ compileError $ "Cannot use " ++ show t ++ " with unary - operator" ++
                                             formatFullContextBrace c
+      doComp t e2
+        | t == intRequiredValue = return $ (Positional [intRequiredValue],
+                                            UnboxedPrimitive PrimInt $ "~" ++ useAsUnboxed PrimInt e2)
+        | otherwise = lift $ compileError $ "Cannot use " ++ show t ++ " with unary ~ operator" ++
+                                            formatFullContextBrace c
   compile (InitializeValue c t ps es) = do
     es' <- sequence $ map compileExpression $ pValues es
     (ts,es'') <- getValues es'
@@ -536,6 +542,7 @@
   equals = Set.fromList ["==","!="]
   comparison = Set.fromList ["==","!=","<","<=",">",">="]
   logical = Set.fromList ["&&","||"]
+  bitwise = Set.fromList ["&","|","^",">>","<<"]
   bindInfix c (Positional ts1,e1) o (Positional ts2,e2) = do
     -- TODO: Needs better error messages.
     t1' <- requireSingle c ts1
@@ -555,6 +562,8 @@
         | o `Set.member` comparison && t1 == charRequiredValue = do
           return (Positional [boolRequiredValue],glueInfix PrimChar PrimBool e1 o e2)
         | o `Set.member` arithmetic1 && t1 == intRequiredValue = do
+          return (Positional [intRequiredValue],glueInfix PrimInt PrimInt e1 o e2)
+        | o `Set.member` bitwise && t1 == intRequiredValue = do
           return (Positional [intRequiredValue],glueInfix PrimInt PrimInt e1 o e2)
         | o `Set.member` arithmetic2 && t1 == intRequiredValue = do
           return (Positional [intRequiredValue],glueInfix PrimInt PrimInt e1 o e2)
diff --git a/src/Config/LoadConfig.hs b/src/Config/LoadConfig.hs
--- a/src/Config/LoadConfig.hs
+++ b/src/Config/LoadConfig.hs
@@ -122,7 +122,9 @@
     hClose errH
     status <- getProcessStatus True True pid
     out <- readFile outF
+    removeFile outF
     err <- readFile errF
+    removeFile errF
     let success = case status of
                       Just (Exited ExitSuccess) -> True
                       _ -> False
diff --git a/src/Parser/Procedure.hs b/src/Parser/Procedure.hs
--- a/src/Parser/Procedure.hs
+++ b/src/Parser/Procedure.hs
@@ -240,24 +240,52 @@
 
 unaryOperator :: Parser (Operator c)
 unaryOperator = op >>= return . NamedOperator where
-  op = labeled "unary operator" $ foldr (<|>) (fail "empty") $ map (try . operator) [
-      "!", "-"
-    ]
+  op = labeled "unary operator" $ foldr (<|>) (fail "empty") $ map (try . operator) ops
+  ops = logicalUnary ++ arithUnary ++ bitwiseUnary
 
+logicalUnary :: [String]
+logicalUnary = ["!"]
+
+arithUnary :: [String]
+arithUnary = ["-"]
+
+bitwiseUnary :: [String]
+bitwiseUnary = ["~"]
+
 infixOperator :: Parser (Operator c)
 infixOperator = op >>= return . NamedOperator where
-  op = labeled "binary operator" $ foldr (<|>) (fail "empty") $ map (try . operator) [
-      "+","-","*","/","%","==","!=","<","<=",">",">=","&&","||"
-    ]
+  op = labeled "binary operator" $ foldr (<|>) (fail "empty") $ map (try . operator) ops
+  ops = compareInfix ++ logicalInfix ++ addInfix ++ subInfix ++ multInfix ++ bitwiseInfix ++ bitshiftInfix
 
+compareInfix :: [String]
+compareInfix = ["==","!=","<","<=",">",">="]
+
+logicalInfix :: [String]
+logicalInfix = ["&&","||"]
+
+addInfix :: [String]
+addInfix = ["+"]
+
+subInfix :: [String]
+subInfix = ["-"]
+
+multInfix :: [String]
+multInfix = ["*","/","%"]
+
+bitwiseInfix :: [String]
+bitwiseInfix = ["&","|","^"]
+
+bitshiftInfix :: [String]
+bitshiftInfix = [">>","<<"]
+
 infixBefore :: Operator c -> Operator c -> Bool
 infixBefore o1 o2 = (infixOrder o1 :: Int) <= (infixOrder o2 :: Int) where
   infixOrder (NamedOperator o)
     -- TODO: Don't hard-code this.
-    | o `Set.member` Set.fromList ["*","/","%"] = 1
-    | o `Set.member` Set.fromList ["+","-"] = 2
-    | o `Set.member` Set.fromList ["==","!=","<","<=",">",">="] = 4
-    | o `Set.member` Set.fromList ["&&","||"] = 5
+    | o `Set.member` Set.fromList (multInfix ++ bitshiftInfix) = 1
+    | o `Set.member` Set.fromList (addInfix ++ subInfix ++ bitwiseInfix) = 2
+    | o `Set.member` Set.fromList compareInfix = 4
+    | o `Set.member` Set.fromList logicalInfix = 5
   infixOrder _ = 3
 
 functionOperator :: Parser (Operator SourcePos)
diff --git a/src/Test/ParseMetadata.hs b/src/Test/ParseMetadata.hs
--- a/src/Test/ParseMetadata.hs
+++ b/src/Test/ParseMetadata.hs
@@ -243,6 +243,12 @@
       cbLinkFlags = []
     },
 
+    checkWriteFail "compile mode" $ CompileFast {
+      cfCategory = "SpecialCategory",
+      cfFunction = "specialFunction",
+      cfSource = "source.0rx"
+    },
+
     checkWriteThenRead $ CompileIncremental {
       ciLinkFlags = [
         "-lm",
@@ -252,6 +258,7 @@
 
     checkWriteFail "compile mode" $ ExecuteTests { etInclude = [] },
     checkWriteFail "compile mode" $ CompileRecompile,
+    checkWriteFail "compile mode" $ CompileRecompileRecursive,
     checkWriteFail "compile mode" $ CreateTemplates,
     checkWriteFail "compile mode" $ CompileUnspecified
   ]
diff --git a/src/Test/Procedure.hs b/src/Test/Procedure.hs
--- a/src/Test/Procedure.hs
+++ b/src/Test/Procedure.hs
@@ -170,6 +170,11 @@
     checkShortParseSuccess "\\ !x >= !y",
     checkShortParseSuccess "\\ !x && !y",
     checkShortParseSuccess "\\ !x || !y",
+    checkShortParseSuccess "\\ ~x >> ~y",
+    checkShortParseSuccess "\\ ~x << ~y",
+    checkShortParseSuccess "\\ ~x & ~y",
+    checkShortParseSuccess "\\ ~x | ~y",
+    checkShortParseSuccess "\\ ~x ^ ~y",
 
     checkShortParseSuccess "x <- y + z",
     checkShortParseSuccess "x <- !y == !z",
diff --git a/tests/builtin-types.0rt b/tests/builtin-types.0rt
--- a/tests/builtin-types.0rt
+++ b/tests/builtin-types.0rt
@@ -278,6 +278,24 @@
 }
 
 
+testcase "String Builder" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+    Builder<String> builder <- String$builder()
+    \ Testing$check<String>(builder.build(),"")
+    \ Testing$check<String>(builder.append("xyz").build(),"xyz")
+    \ Testing$check<String>(builder.append("123").build(),"xyz123")
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
 testcase "Char LessThan" {
   success Test$run()
 }
@@ -409,6 +427,30 @@
     String value1 <- "x"
     weak String value2 <- value1
     if (!present(strong(value2))) {
+      fail("Failed")
+    }
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "String Builder is not lazy" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+    optional String value1 <- "value"
+    weak String value2 <- value1
+    if (!present(strong(value2))) {
+      fail("Failed")
+    }
+    Builder<String> builder <- String$builder().append(require(value1))
+    value1 <- empty
+    if (present(strong(value2))) {
       fail("Failed")
     }
   }
diff --git a/tests/helpers.0rp b/tests/helpers.0rp
new file mode 100644
--- /dev/null
+++ b/tests/helpers.0rp
@@ -0,0 +1,30 @@
+/* -----------------------------------------------------------------------------
+Copyright 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]
+
+concrete Testing {
+  @type check<#x>
+    #x requires Formatted
+    #x defines Equals<#x>
+  (#x /*actual*/, #x /*expected*/) -> ()
+
+  @type checkBetween<#x>
+    #x requires Formatted
+    #x defines Equals<#x>
+    #x defines LessThan<#x>
+  (#x /*actual*/, #x /*lower*/, #x /*upper*/) -> ()
+}
diff --git a/tests/helpers.0rx b/tests/helpers.0rx
new file mode 100644
--- /dev/null
+++ b/tests/helpers.0rx
@@ -0,0 +1,43 @@
+/* -----------------------------------------------------------------------------
+Copyright 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]
+
+define Testing {
+  check (x,y) {
+    if (!#x$equals(x,y)) {
+      fail(x)
+    }
+  }
+
+  checkBetween (x,l,h) {
+    if (!lessEquals<#x>(l,h)) {
+      fail("Invalid range")
+    }
+    if (!lessEquals<#x>(l,x) || !lessEquals<#x>(x,h)) {
+      fail(x)
+    }
+  }
+
+  @type lessEquals<#x>
+    #x defines Equals<#x>
+    #x defines LessThan<#x>
+  (#x,#x) -> (Bool)
+  lessEquals (x,y) {
+    // Using !#x$lessThan(y,x) wouldn't account for NaNs.
+    return #x$lessThan(x,y) || #x$equals(x,y)
+  }
+}
diff --git a/tests/infix-operations.0rt b/tests/infix-operations.0rt
--- a/tests/infix-operations.0rt
+++ b/tests/infix-operations.0rt
@@ -22,11 +22,7 @@
 
 define Test {
   run () {
-    scoped {
-      Int x <- \x10 + 1 * 2 - 8 / 2 - 3 % 2
-    } in if (x != 13) {
-      fail(x)
-    }
+    \ Testing$check<Int>(\x10 + 1 * 2 - 8 / 2 - 3 % 2,13)
   }
 }
 
@@ -35,17 +31,66 @@
 }
 
 
+testcase "Int arithmetic" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+    \ Testing$check<Int>(8 + 2,10)
+    \ Testing$check<Int>(8 - 2,6)
+    \ Testing$check<Int>(8 * 2,16)
+    \ Testing$check<Int>(8 / 2,4)
+    \ Testing$check<Int>(8 % 2,0)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "Int bitwise" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+    \ Testing$check<Int>(1 << 2,4)
+    \ Testing$check<Int>(7 >> 1,3)
+    \ Testing$check<Int>(7 ^ 2 ,5)
+    \ Testing$check<Int>(7 & ~2,5)
+    \ Testing$check<Int>(5 | 2 ,7)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "Int bitwise with precedence" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+    \ Testing$check<Int>(1 << 4 | 7 >> 1 & ~2,17)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
 testcase "same operators applied left to right" {
   success Test$run()
 }
 
 define Test {
   run () {
-    scoped {
-      Int x <- 2 - 1 - 1
-    } in if (x != 0) {
-      fail(x)
-    }
+    \ Testing$check<Int>(2 - 1 - 1,0)
   }
 }
 
@@ -92,11 +137,7 @@
 
 define Test {
   run () {
-    scoped {
-      String x <- "x" + "y" + "z"
-    } in if (x != "xyz") {
-      fail(x)
-    }
+    \ Testing$check<String>("x" + "y" + "z","xyz")
   }
 }
 
@@ -111,11 +152,22 @@
 
 define Test {
   run () {
-    scoped {
-      Float x <- 16.0 + 1.0 * 2.0 - 8.0 / 2.0 - 3.0 / 3.0
-    } in if (x != 13.0) {
-      fail(x)
-    }
+    \ Testing$check<Float>(16.0 + 1.0 * 2.0 - 8.0 / 2.0 - 3.0 / 3.0,13.0)
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
+
+
+testcase "Char minus" {
+  success Test$run()
+}
+
+define Test {
+  run () {
+    \ Testing$check<Int>('z' - 'a',25)
   }
 }
 
diff --git a/tests/simple.0rt b/tests/simple.0rt
--- a/tests/simple.0rt
+++ b/tests/simple.0rt
@@ -82,3 +82,26 @@
 
 // The declaration for String is in scope, but it lives outside of this module.
 define String {}
+
+
+testcase "no deadlock with recursive type" {
+  success Test$execute()
+}
+
+concrete Test {
+  @type execute () -> ()
+}
+
+concrete Type<#x> {
+  @type call () -> ()
+}
+
+define Type {
+  call () {}
+}
+
+define Test {
+  execute () {
+    \ Type<Type<Type<Int>>>$call()
+  }
+}
diff --git a/tests/templates/.zeolite-module b/tests/templates/.zeolite-module
new file mode 100644
--- /dev/null
+++ b/tests/templates/.zeolite-module
@@ -0,0 +1,9 @@
+root: "../.."
+path: "tests/templates"
+extra_files: [
+  category_source {
+    source: "tests/templates/Category_Templated.cpp"
+    categories: [Templated]
+  }
+]
+mode: incremental {}
diff --git a/tests/templates/README.md b/tests/templates/README.md
new file mode 100644
--- /dev/null
+++ b/tests/templates/README.md
@@ -0,0 +1,15 @@
+# Template Generation Test
+
+This module tests `--templates` mode for generating `.cpp` templates.
+
+To compile and execute:
+
+```shell
+ZEOLITE_PATH=$(zeolite --get-path)
+rm -f $ZEOLITE_PATH/tests/templates/Category_Templated.cpp  # Remove the old template.
+zeolite -p $ZEOLITE_PATH --templates tests/templates        # Create a new template.
+zeolite -p $ZEOLITE_PATH -r tests/templates                 # Recompile.
+zeolite -p $ZEOLITE_PATH -t tests/templates                 # Run the test.
+```
+
+The most important step above is to delete the *old* template first.
diff --git a/tests/templates/templates.0rp b/tests/templates/templates.0rp
new file mode 100644
--- /dev/null
+++ b/tests/templates/templates.0rp
@@ -0,0 +1,22 @@
+/* -----------------------------------------------------------------------------
+Copyright 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]
+
+concrete Templated<#x> {
+  @type create () -> (Templated<#x>)
+  @value get () -> (#x)
+}
diff --git a/tests/templates/tests.0rt b/tests/templates/tests.0rt
new file mode 100644
--- /dev/null
+++ b/tests/templates/tests.0rt
@@ -0,0 +1,32 @@
+/* -----------------------------------------------------------------------------
+Copyright 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]
+
+testcase "template compiles to binary" {
+  crash Test$run()
+  require "Templated\.create is not implemented"
+}
+
+define Test {
+  run () {
+    \ Templated<Int>$create()
+  }
+}
+
+concrete Test {
+  @type run () -> ()
+}
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.4.0.0
+version:             0.4.1.0
 synopsis:            Zeolite is a statically-typed, general-purpose programming language.
 
 description:
@@ -36,7 +36,7 @@
     .
     @
     ZEOLITE_PATH=$(zeolite --get-path)
-    zeolite -p "$ZEOLITE_PATH" -t tests lib\/file lib\/util
+    zeolite -p "$ZEOLITE_PATH" -t tests lib\/file lib\/math lib\/util
     @
   .
   The <https://github.com/ta0kira/zeolite/tree/master/example code examples> are
@@ -83,17 +83,27 @@
                      lib/file/*.0rp,
                      lib/file/*.0rt,
                      lib/file/*.cpp,
+                     lib/math/.zeolite-module,
+                     lib/math/*.0rp,
+                     lib/math/*.0rt,
+                     lib/math/*.cpp,
                      lib/util/.zeolite-module,
                      lib/util/*.0rp,
                      lib/util/*.0rt,
                      lib/util/*.0rx,
                      lib/util/*.cpp,
                      tests/.zeolite-module,
+                     tests/*.0rp,
                      tests/*.0rt,
+                     tests/*.0rx,
                      tests/check-defs/README.md,
                      tests/check-defs/.zeolite-module,
                      tests/check-defs/*.0rp,
                      tests/check-defs/*.0rx,
+                     tests/templates/README.md,
+                     tests/templates/.zeolite-module,
+                     tests/templates/*.0rp,
+                     tests/templates/*.0rt,
                      tests/visibility/.zeolite-module,
                      tests/visibility/*.0rp,
                      tests/visibility/*.0rx,
