diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,206 @@
 # Revision history for zeolite-lang
 
+## 0.24.0.0  -- 2023-12-09
+
+### Language
+
+* **[breaking]** In `testcase`, renames `crash` to `failure`.
+
+* **[breaking]** Adds the `visibility` keyword to limit calling of
+  `@value`/`@type` functions to specific type contexts. This is breaking because
+  `visibility` is now reserved.
+
+  ```text
+  concrete Foo {
+    visibility Bar, Baz  // Functions below here are only visible from Bar or Baz.
+
+    @type call () -> ()
+  }
+  ```
+
+* **[breaking]** Adds function delegation with `delegate` keyword.
+
+  ```text
+  concrete Foo {
+    @type new1 (Int) -> (Foo)
+    @type new2 (Int) -> (Foo)
+  }
+
+  define Foo {
+    @value Int value
+
+    new1 (value) {
+      // Same as Foo{ value }.
+      return delegate -> Foo
+    }
+
+    new2 (value) {
+      // Same as new1(value).
+      return delegate -> `new1`
+    }
+  }
+  ```
+
+  This is a breaking change because `delegate` is now reserved.
+
+* **[breaking]** In `testcase`, you can now optionally include a type name that
+  `defines Testcase` after `success`/`failure`. The test will automatically call
+  `start()` and `finish()`, which is meant to allow custom checks upon test
+  exit. This is breaking because `Testcase` is now reserved.
+
+* **[breaking]** Adds instance identification with the `identify` keyword. This
+  is breaking because `identify` is now reserved.
+
+* **[fix]** Fixes parsing of comments that immediately follow operators, e.g.,
+  the `/*b+*/` in `a+/*b+*/c`.
+
+* **[fix]** Fixes checking that a `@type` function isn't called on a `@value`.
+
+* **[fix]** Fixes cross-module visibility checks with `$TestsOnly$` in `.0rp`
+  without `$ModuleOnly$`. This is primarily to detect if there's usage of a
+  `$TestsOnly$` category from a `private_dep` in a `.0rp`.
+
+* **[fix]** Updates parsing of `empty` to allow explicit type conversion, e.g.,
+  `empty?String`.
+
+* **[new]** Adds the option to provide _enforced_ labels for function args.
+
+  ```text
+  concrete Foo {
+    // First arg has the label value:.
+    @type bar (Int value:) -> ()
+  }
+
+  // ...
+
+  // Callers _must_ use the label value:.
+  \ Foo.bar(value: 1)
+  ```
+
+  Unlike with languages like C++, where the argument name in the function
+  _declaration_ is irrelevant, this is primarily meant to clarify calls to
+  functions with arguments whose purposes are ambiguous.
+
+  Note that this _doesn't_ make arguments optional, nor does it allow reordering
+  of arguments. Additionally, there's no requirement that the labels match the
+  argument names, nor that the labels are even unique.
+
+* **[new]** Adds the `<-|` operator to conditionally overwrite an `optional`
+  variable if it's empty.
+
+  ```text
+  optional Int value <- empty
+  value <-| 123  // Assigned, because value was empty.
+  value <-| 456  // Not assigned, because value wasn't empty.
+  ```
+
+* **[new]** Adds the `&.` operator to conditionally call a function on an
+  `optional` value.
+
+  ```text
+  optional Int value <- 123
+  optional Formatted formatted <- value&.formatted()
+  ```
+
+* **[new]** Updates the return type of inline `<-` to use the type on the right
+  side of the expression, rather than the variable's type.
+
+  ```text
+  optional Formatted value <- empty
+  // Previously, the return type would've been optional Formatted.
+  Int value2 <- (value <- 123)
+  ```
+
+* **[new]** Updates type inference to arbitrarily choose the lower bound when a
+  param is bounded above and below with different types. Previously, the
+  compiler would require specifying the param explicitly. This will make param
+  inference succeed in more situations than it did previously.
+
+* **[new]** Adds the `<->` operator to swap variable values. This can be used to
+  swap the values of writable variables of the same type, i.e., _not_ marked as
+  read-only via a pragma and not a function argument.
+
+  ```text
+  Int foo <- 123
+  Int bar <- 456
+  foo <-> bar
+  ```
+
+* **[new]** Allows hex, octal, and binary floating-point literals. (Exponents
+  not allowed due to ambiguity during hex parsing.)
+
+  ```text
+  Float value1 <- \xFED.CBA
+  Float value2 <- \o765.432
+  Float value3 <- \b101.010
+  ```
+
+* **[new]** Adds the `$CallTrace$` macro to get a call trace for testing
+  purposes.
+
+### Libraries
+
+* **[breaking]** Removes `Testing` from `lib/testing` entirely. Use the new
+  testing approach instead. (More details in items further down.)
+
+  ```text
+  // Old way.
+
+  testcase "my tests" {
+    success
+  }
+
+  unittest test {
+    \ Testing.checkEquals("something", "other")
+    \ Testing.checkBetween(3, 2, 7)
+  }
+  ```
+
+  ```text
+  // New way.
+
+  testcase "my tests" {
+    success TestChecker  // <- allows lib/testing to manage test results
+  }
+
+  unittest test {
+    \ "something" `Matches:with` CheckValue:equals("other")
+    \ 3 `Matches:with` CheckValue:betweenEquals(2, 7)
+  }
+  ```
+
+  Also see the `.0rp` files in `lib/testing`.
+
+* **[breaking]** Removes `UtilTesting` from `lib/util` entirely. Use the new
+  testing approach instead.
+
+* **[new]** Adds the `TestChecker` `testcase` checker (see previous section) to
+  `lib/testing` to help support custom matchers.
+
+* **[new]** Adds support for custom value matchers in tests to `lib/testing`, as
+  well as matchers for value comparisons, substring checks, floating-point
+  epsilon, and container comparisons in `lib/testing`, `lib/util`, and
+  `lib/container`. User-defined matchers can compose other matchers to
+  customize matching of more complex value types.
+
+* **[new]** Implements `SetReader<#k>` for `HashedMap<#k, #v>` and
+  `SortedMap<#k, #v>` in `lib/container`.
+
+* **[new]** Adds `tryValue` to `ErrorOr` in `lib/util`.
+
+* **[fix]** Updates `BarrierWait` C++ implementation to compile for MacOS by
+  adding runtime failures if the functionality is used. MacOS doesn't support
+  thread barriers, so `lib/thread` previously didn't compile for MacOS.
+
+### Compiler CLI
+
+* **[new]** Allows single-`-` CLI options to be clustered, e.g., `-rfj8`, which
+  is expanded to `-r`, `-f`, and `-j` `8`.
+
+* **[behavior]** Clearer error messages for categories hidden by `$ModuleOnly$`,
+  `$TestsOnly$`, and `private_deps`. Previously, the compiler would just say
+  that the category wasn't found.
+
 ## 0.23.0.0  -- 2022-10-08
 
 ### Language
diff --git a/base/.zeolite-module b/base/.zeolite-module
--- a/base/.zeolite-module
+++ b/base/.zeolite-module
@@ -2,36 +2,41 @@
 path: "base"
 extra_files: [
   category_source {
-    source: "base/src/Extension_Bool.cpp"
+    source: "base/src/Builtin_Bool.cpp"
     categories: [Bool]
   }
   category_source {
-    source: "base/src/Extension_Char.cpp"
+    source: "base/src/Builtin_Char.cpp"
     categories: [Char]
   }
   category_source {
-    source: "base/src/Extension_CharBuffer.cpp"
+    source: "base/src/Builtin_CharBuffer.cpp"
     categories: [CharBuffer]
   }
   category_source {
-    source: "base/src/Extension_Float.cpp"
+    source: "base/src/Builtin_Float.cpp"
     categories: [Float]
   }
   category_source {
-    source: "base/src/Extension_Int.cpp"
+    source: "base/src/Builtin_Identifier.cpp"
+    categories: [Identifier]
+  }
+  category_source {
+    source: "base/src/Builtin_Int.cpp"
     categories: [Int]
   }
   category_source {
-    source: "base/src/Extension_String.cpp"
+    source: "base/src/Builtin_String.cpp"
     categories: [String]
   }
   category_source {
-    source: "base/src/Extension_Pointer.cpp"
+    source: "base/src/Builtin_Pointer.cpp"
     categories: [Pointer]
   }
   "base/boxed.hpp"
   "base/category-header.hpp"
   "base/category-source.hpp"
+  "base/cleanup.hpp"
   "base/cycle-check.hpp"
   "base/function.hpp"
   "base/logging.hpp"
@@ -42,8 +47,9 @@
   "base/types.hpp"
   "base/src/boxed.cpp"
   "base/src/category-source.cpp"
+  "base/src/cleanup.cpp"
   "base/src/logging.cpp"
   "base/src/returns.cpp"
   "base/src/thread-crosser.cc"
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/base/boxed.hpp b/base/boxed.hpp
--- a/base/boxed.hpp
+++ b/base/boxed.hpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021-2022 Kevin P. Barry
+Copyright 2021-2023 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.
@@ -30,7 +30,15 @@
 class ValueFunction;
 
 namespace zeolite_internal {
+class WeakValue;
+class BoxedValue;
+}  // namespace zeolite_internal
 
+void SwapValues(zeolite_internal::WeakValue&, zeolite_internal::WeakValue&);
+void SwapValues(zeolite_internal::BoxedValue&, zeolite_internal::BoxedValue&);
+
+namespace zeolite_internal {
+
 struct UnionValue {
   enum class Type : char {
     EMPTY,
@@ -39,6 +47,7 @@
     INT,
     FLOAT,
     POINTER,
+    IDENTIFIER,
     BOXED,
   };
 
@@ -46,6 +55,8 @@
     std::atomic_flag lock_;
     std::atomic_int strong_;
     std::atomic_int weak_;
+    // NOTE: This can't be optimized out because it might differ from the
+    // pointer to the underlying object, e.g., if TypeValue is a virtual base.
     TypeValue* object_;
   };
 
@@ -55,16 +66,16 @@
 
   union {
     unsigned char* as_bytes_;
-    Pointer*    as_boxed_;
-    PrimBool    as_bool_;
-    PrimChar    as_char_;
-    PrimInt     as_int_;
-    PrimFloat   as_float_;
-    PrimPointer as_pointer_;
+    Pointer*       as_boxed_;
+    PrimBool       as_bool_;
+    PrimChar       as_char_;
+    PrimInt        as_int_;
+    PrimFloat      as_float_;
+    PrimPointer    as_pointer_;
+    PrimIdentifier as_identifier_;
   } __attribute__((packed)) value_;
 } __attribute__((packed));
 
-
 class BoxedValue {
  public:
   constexpr BoxedValue()
@@ -116,27 +127,28 @@
     return *this;
   }
 
-  inline BoxedValue(PrimBool value)
+  inline explicit BoxedValue(PrimBool value)
     : union_{ .type_ = UnionValue::Type::BOOL, .value_ = { .as_bool_ = value } } {}
 
-  inline BoxedValue(PrimChar value)
+  inline explicit BoxedValue(PrimChar value)
     : union_{ .type_ = UnionValue::Type::CHAR, .value_ = { .as_char_ = value } } {}
 
-  inline BoxedValue(PrimInt value)
+  inline explicit BoxedValue(PrimInt value)
     : union_{ .type_ = UnionValue::Type::INT, .value_ = { .as_int_ = value } } {}
 
-  inline BoxedValue(PrimFloat value)
+  inline explicit BoxedValue(PrimFloat value)
     : union_{ .type_ = UnionValue::Type::FLOAT, .value_ = { .as_float_ = value } } {}
 
-  inline BoxedValue(PrimPointer value)
+  inline explicit BoxedValue(PrimPointer value)
     : union_{ .type_ = UnionValue::Type::POINTER, .value_ = { .as_pointer_ = value } } {}
 
+  inline explicit BoxedValue(PrimIdentifier value)
+    : union_{ .type_ = UnionValue::Type::IDENTIFIER, .value_ = { .as_identifier_ = value } } {}
+
   template<class T, class... As>
   static inline BoxedValue New(const As&... args) {
     using Pointer = UnionValue::Pointer;
-    BoxedValue new_value;
-    new_value.union_.type_ = UnionValue::Type::BOXED;
-    new_value.union_.value_.as_bytes_ = (unsigned char*) malloc(sizeof(Pointer) + sizeof(T));
+    BoxedValue new_value(reinterpret_cast<unsigned char*>(malloc(sizeof(Pointer) + sizeof(T))));
     new (new_value.union_.value_.as_bytes_)
       Pointer{ ATOMIC_FLAG_INIT, {1}, {1},
                {new (new_value.union_.value_.as_bytes_ + sizeof(Pointer)) T(args...)} };
@@ -197,7 +209,7 @@
   inline T* AsPointer() const {
     switch (union_.type_) {
       case UnionValue::Type::POINTER:
-        return reinterpret_cast<T*>(union_.value_.as_boxed_);
+        return reinterpret_cast<T*>(union_.value_.as_pointer_);
       default:
         FAIL() << union_.CategoryName() << " is not a Pointer value";
         __builtin_unreachable();
@@ -205,6 +217,17 @@
     }
   }
 
+  inline PrimIdentifier AsIdentifier() const {
+    switch (union_.type_) {
+      case UnionValue::Type::IDENTIFIER:
+        return union_.value_.as_identifier_;
+      default:
+        FAIL() << union_.CategoryName() << " is not an Identifier value";
+        __builtin_unreachable();
+        break;
+    }
+  }
+
   inline static bool Present(const BoxedValue& target) {
     return target.union_.type_ != UnionValue::Type::EMPTY;
   }
@@ -224,6 +247,8 @@
     return BoxedValue(target);
   }
 
+  static PrimIdentifier Identify(const BoxedValue& target);
+
   void Validate(const std::string& name) const;
 
   const PrimString& AsString() const;
@@ -232,7 +257,11 @@
  private:
   friend class ::TypeValue;
   friend class WeakValue;
+  friend void ::SwapValues(BoxedValue&, BoxedValue&);
 
+  inline explicit BoxedValue(unsigned char* value)
+    : union_{ .type_ = UnionValue::Type::BOXED, .value_ = { .as_bytes_ = value } } {}
+
   // Intentionally break old calls that used new.
   inline explicit constexpr BoxedValue(void*) : BoxedValue() {}
 
@@ -242,10 +271,7 @@
 
   template<class T>
   static inline BoxedValue FromPointer(const T* pointer) {
-    BoxedValue value;
-    value.union_.type_ = UnionValue::Type::BOXED;
-    value.union_.value_.as_bytes_ =
-      reinterpret_cast<unsigned char*>(const_cast<T*>(pointer))-sizeof(UnionValue::Pointer);
+    BoxedValue value(reinterpret_cast<unsigned char*>(const_cast<T*>(pointer))-sizeof(UnionValue::Pointer));
     if (value.union_.value_.as_boxed_->object_ != pointer ||
         ++value.union_.value_.as_boxed_->strong_ == 1) {
       FAIL() << "Bad VAR_SELF pointer " << pointer << " in " << pointer->CategoryName();
@@ -279,6 +305,7 @@
 
  private:
   friend class BoxedValue;
+  friend void ::SwapValues(WeakValue&, WeakValue&);
 
   void Cleanup();
 
@@ -286,6 +313,19 @@
 } __attribute__((packed));
 
 }  // namespace zeolite_internal
+
+
+inline void SwapValues(BoxedValue& left, BoxedValue& right) {
+  zeolite_internal::UnionValue temp = right.union_;
+  right.union_ = left.union_;
+  left.union_ = temp;
+}
+
+inline void SwapValues(WeakValue& left, WeakValue& right) {
+  zeolite_internal::UnionValue temp = right.union_;
+  right.union_ = left.union_;
+  left.union_ = temp;
+}
 
 using zeolite_internal::BoxedValue;
 using zeolite_internal::WeakValue;
diff --git a/base/builtin.0rp b/base/builtin.0rp
--- a/base/builtin.0rp
+++ b/base/builtin.0rp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -23,6 +23,7 @@
 // Notes:
 // - You do not need () to make a function call on a literal; something like
 //   true.asInt() is totally fine.
+// - Values are unboxed when storing in variables.
 concrete Bool {
   immutable
 
@@ -51,6 +52,7 @@
 //   'a'.asInt() is totally fine.
 // - When converting to Int with asInt(), the value will always be unsigned,
 //   i.e., beween 0-255. ASCII characters will be in the 0-127 range.
+// - Values are unboxed when storing in variables.
 concrete Char {
   immutable
 
@@ -73,9 +75,13 @@
 // Literals:
 // - . required, with at least one digit on either side, e.g., 0.1
 // - Scientific notation: 0.1E-10, etc.
+// - Hex:    \xABC.DEF, etc.
+// - Octal:  \o777.266, etc.
+// - Binary: \b011.011, etc.
 //
 // Notes:
 // - You must use () to make a function call on a literal, e.g, (1.1).asInt().
+// - Values are unboxed when storing in variables.
 concrete Float {
   immutable
 
@@ -103,11 +109,10 @@
 // - Overflow will cause a compilation error.
 // - Hex, oct, and binary escapes can go up to 2^64-1 to allow use of all 64
 //   bits, but they will still be used as signed values.
-//
-// Notes:
 // - You must use () to make a function call on a literal, e.g, (100).asFloat().
 // - When converting to Char with asChar(), the value will always be treated as
 //   unsigned, i.e., beween 0-255 mod 256.
+// - Values are unboxed when storing in variables.
 concrete Int {
   immutable
 
@@ -160,7 +165,7 @@
   // Example:
   //
   //   String.builder().append("foo").append("bar").build()
-  @type builder () -> ([Append<Formatted>&Build<String>])
+  @type builder () -> ([Append<Formatted> & Build<String>])
 }
 
 // Built-in fixed-size buffer of byte data.
@@ -194,10 +199,33 @@
 //   values with incompatible types.
 // - This is category is only meant for passing C++ data between extensions when
 //   it would otherwise require marshaling, which is why it has no functions.
+// - Values are unboxed when storing in variables.
 concrete Pointer<#x> {
   immutable
 }
 
+// An identifier for a value instance.
+//
+// Literals: None.
+//
+// Notes:
+// - Get an Identifier<#x> from optional #x value with instance(value).
+// - Also supports comparisons, e.g., <, ==.
+// - Values are unboxed when storing in variables.
+// - Identifiers will be unique for different concrete values if:
+//   - Both underlying concrete types are unboxed, which is the case with all
+//     custom concrete types.
+//   - Both values existed at the same time at some point.
+concrete Identifier<|#x> {
+  immutable
+
+  refines Formatted
+  refines Hashed
+
+  defines Equals<Identifier<any>>
+  defines LessThan<Identifier<any>>
+}
+
 // Convertible to Bool.
 @value interface AsBool {
   // Convert the value to Bool.
@@ -330,7 +358,7 @@
   refines Container
 
   // Write the value at the given index and return self.
-  writeAt (Int,#x) -> (#self)
+  writeAt (Int, #x) -> (#self)
 }
 
 // Sequence that can be subsequenced.
@@ -338,7 +366,7 @@
   refines Container
 
   // Return a copy of a subsequence.
-  subSequence (Int,Int) -> (#self)
+  subSequence (Int, Int) -> (#self)
 }
 
 // The type has a default value.
@@ -365,7 +393,7 @@
 // - #x: The value type to be compared.
 @type interface Equals<#x|> {
   // Returns true iff the two values are equal. Must be symmetric.
-  equals (#x,#x) -> (Bool)
+  equals (#x, #x) -> (Bool)
 }
 
 // The type can compare values for less-than.
@@ -374,5 +402,5 @@
 // - #x: The value type to be compared.
 @type interface LessThan<#x|> {
   // Returns true iff the first value is less than the second.
-  lessThan (#x,#x) -> (Bool)
+  lessThan (#x, #x) -> (Bool)
 }
diff --git a/base/category-source.hpp b/base/category-source.hpp
--- a/base/category-source.hpp
+++ b/base/category-source.hpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include "boxed.hpp"
+#include "cleanup.hpp"
 #include "cycle-check.hpp"
 #include "function.hpp"
 #include "returns.hpp"
@@ -39,7 +40,9 @@
   }
 
 #define BUILTIN_EXIT(e) { \
-    std::exit(e); \
+    int code = e; \
+    GlobalCleanup::Finish(code); \
+    std::exit(code); \
     __builtin_unreachable(); \
   }
 
@@ -73,6 +76,10 @@
   return BoxedValue(reinterpret_cast<PrimPointer>(static_cast<T1*>(value)));
 }
 
+inline BoxedValue Box_Identifier(PrimIdentifier value) {
+  return BoxedValue(value);
+}
+
 BoxedValue Box_String(const PrimString& value);
 
 S<const TypeInstance> Merge_Intersect(const L<S<const TypeInstance>>& params);
@@ -178,6 +185,13 @@
   }
 };
 
+#define TYPE_VALUE_CALL_UNLESS_EMPTY(expr, func, args, count) ({ \
+    const BoxedValue result = expr; \
+    BoxedValue::Present(result) \
+        ? TypeValue::Call(result, func, args) \
+        : ReturnTuple(count); \
+    })
+
 class TypeValue {
  public:
   inline static ReturnTuple Call(const BoxedValue& target, const ValueFunction& label,
@@ -254,5 +268,9 @@
   std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
   std::map<typename ParamsKey<P>::Type, W<T>> cache_;
 };
+
+#ifdef ZEOLITE_TESTS_ONLY__YOUR_MODULE_IS_BROKEN_IF_YOU_USE_THIS_IN_HAND_WRITTEN_CODE
+BoxedValue GetCallTrace(const ValueFunction& get_func, const ValueFunction& next_func);
+#endif
 
 #endif  // CATEGORY_SOURCE_HPP_
diff --git a/base/cleanup.hpp b/base/cleanup.hpp
new file mode 100644
--- /dev/null
+++ b/base/cleanup.hpp
@@ -0,0 +1,56 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 CLEANUP_HPP_
+#define CLEANUP_HPP_
+
+#include "thread-capture.h"
+#include "category-header.hpp"
+
+
+class GlobalCleanup : public capture_thread::ThreadCapture<GlobalCleanup> {
+ public:
+  // Executes cleanup routines in reverse order.
+  static void Finish(int code);
+
+ protected:
+  // NOTE: Each destructor should call Cleanup().
+  virtual ~GlobalCleanup() = default;
+  virtual GlobalCleanup* GetNext() const = 0;
+  virtual void Cleanup(int code) = 0;
+};
+
+// Executes type functions upon construction and destruction.
+class WrapTypeCall : public GlobalCleanup {
+ public:
+  // Both start and finish take no args. finish is only called once, whether
+  // it's via Finish() or the destructor.
+  WrapTypeCall(S<const TypeInstance> type, const TypeFunction* start, const TypeFunction* finish);
+  ~WrapTypeCall();
+
+ protected:
+  GlobalCleanup* GetNext() const override;
+  void Cleanup(int code) override;
+
+ private:
+  const S<const TypeInstance> type_;
+  const TypeFunction* finish_;
+  const AutoThreadCrosser cross_and_capture_to_;
+};
+
+#endif  // CLEANUP_HPP_
diff --git a/base/src/Builtin_Bool.cpp b/base/src/Builtin_Bool.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Builtin_Bool.cpp
@@ -0,0 +1,100 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2022 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-source.hpp"
+#include "Streamlined_Bool.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Default.hpp"
+#include "Category_Duplicate.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
+#include "Category_Int.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Bool : public Category_Bool {
+};
+
+struct ExtType_Bool : public Type_Bool {
+  inline ExtType_Bool(Category_Bool& p, Params<0>::Type params) : Type_Bool(p, params) {}
+
+  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Bool.default")
+    return ReturnTuple(Box_Bool(false));
+  }
+
+  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Bool.equals")
+    const PrimBool Var_arg1 = (params_args.GetArg(0)).AsBool();
+    const PrimBool Var_arg2 = (params_args.GetArg(1)).AsBool();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+
+  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Bool.lessThan")
+    const PrimBool Var_arg1 = (params_args.GetArg(0)).AsBool();
+    const PrimBool Var_arg2 = (params_args.GetArg(1)).AsBool();
+    return ReturnTuple(Box_Bool(!Var_arg1 && Var_arg2));
+  }
+};
+
+ReturnTuple DispatchBool(PrimBool value, const ValueFunction& label,
+                         const ParamsArgs& params_args) {
+  switch (label.collection) {
+    case CategoryId_AsBool:
+      return ReturnTuple(Box_Bool(value));
+    case CategoryId_AsFloat:
+      return ReturnTuple(Box_Float(value ? 1.0 : 0.0));
+    case CategoryId_AsInt:
+      return ReturnTuple(Box_Int(value? 1 : 0));
+    case CategoryId_Duplicate:
+      return ReturnTuple(Box_Bool(value));
+    case CategoryId_Formatted:
+      return ReturnTuple(Box_String(value? "true" : "false"));
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(value ? 1000000009ULL : 1000000007ULL));
+    default:
+      FAIL() << "Bool does not implement " << label;
+      __builtin_unreachable();
+  }
+}
+
+Category_Bool& CreateCategory_Bool() {
+  static auto& category = *new ExtCategory_Bool();
+  return category;
+}
+
+S<const Type_Bool> CreateType_Bool(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_Bool(CreateCategory_Bool(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_Bool(const Params<0>::Type& params) {}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Builtin_Char.cpp b/base/src/Builtin_Char.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Builtin_Char.cpp
@@ -0,0 +1,115 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2022 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-source.hpp"
+#include "Streamlined_Char.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsChar.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Char.hpp"
+#include "Category_Default.hpp"
+#include "Category_Duplicate.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
+#include "Category_Int.hpp"
+#include "Category_LessThan.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Char : public Category_Char {
+};
+
+struct ExtType_Char : public Type_Char {
+  inline ExtType_Char(Category_Char& p, Params<0>::Type params) : Type_Char(p, params) {}
+
+  ReturnTuple Call_maxBound(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Char.maxBound")
+    return ReturnTuple(Box_Char('\xff'));
+  }
+
+  ReturnTuple Call_minBound(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Char.minBound")
+    return ReturnTuple(Box_Char('\0'));
+  }
+
+  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Char.default")
+    return ReturnTuple(Box_Char('\0'));
+  }
+
+  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Char.equals")
+    const PrimChar Var_arg1 = (params_args.GetArg(0)).AsChar();
+    const PrimChar Var_arg2 = (params_args.GetArg(1)).AsChar();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+
+  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Char.lessThan")
+    const PrimChar Var_arg1 = (params_args.GetArg(0)).AsChar();
+    const PrimChar Var_arg2 = (params_args.GetArg(1)).AsChar();
+    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+  }
+};
+
+ReturnTuple DispatchChar(PrimChar value, const ValueFunction& label,
+                         const ParamsArgs& params_args) {
+  switch (label.collection) {
+    case CategoryId_AsBool:
+      return ReturnTuple(Box_Bool(value != '\0'));
+    case CategoryId_AsChar:
+      return ReturnTuple(Box_Char(value));
+    case CategoryId_AsFloat:
+      return ReturnTuple(Box_Float(((int) value + 256) % 256));
+    case CategoryId_AsInt:
+      return ReturnTuple(Box_Int(((int) value + 256) % 256));
+    case CategoryId_Duplicate:
+      return ReturnTuple(Box_Char(value));
+    case CategoryId_Formatted:
+      return ReturnTuple(Box_String(PrimString(1, value)));
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(1000000009ULL * ((unsigned long long) value + 1000000007ULL)));
+    default:
+      FAIL() << "Char does not implement " << label;
+      __builtin_unreachable();
+  }
+}
+
+Category_Char& CreateCategory_Char() {
+  static auto& category = *new ExtCategory_Char();
+  return category;
+}
+
+S<const Type_Char> CreateType_Char(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_Char(CreateCategory_Char(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_Char(const Params<0>::Type& params) {}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Builtin_CharBuffer.cpp b/base/src/Builtin_CharBuffer.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Builtin_CharBuffer.cpp
@@ -0,0 +1,115 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 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-source.hpp"
+#include "Streamlined_CharBuffer.hpp"
+#include "Category_Char.hpp"
+#include "Category_CharBuffer.hpp"
+#include "Category_Container.hpp"
+#include "Category_Int.hpp"
+#include "Category_ReadAt.hpp"
+#include "Category_WriteAt.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+BoxedValue CreateValue_CharBuffer(S<const Type_CharBuffer> parent, PrimCharBuffer buffer);
+
+struct ExtCategory_CharBuffer : public Category_CharBuffer {
+};
+
+struct ExtType_CharBuffer : public Type_CharBuffer {
+  inline ExtType_CharBuffer(Category_CharBuffer& p, Params<0>::Type params) : Type_CharBuffer(p, params) {}
+
+  ReturnTuple Call_new(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("CharBuffer.new")
+    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
+    if (Var_arg1 < 0) {
+      FAIL() << "Buffer size " << Var_arg1 << " is invalid";
+    }
+    return ReturnTuple(CreateValue_CharBuffer(CreateType_CharBuffer(Params<0>::Type()), PrimCharBuffer(Var_arg1,'\0')));
+  }
+};
+
+struct ExtValue_CharBuffer : public Value_CharBuffer {
+  inline ExtValue_CharBuffer(S<const Type_CharBuffer> p, PrimCharBuffer value)
+    : Value_CharBuffer(std::move(p)), value_(std::move(value)) {}
+
+  ReturnTuple Call_readAt(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("CharBuffer.readAt")
+    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
+    if (Var_arg1 < 0 || Var_arg1 >= AsCharBuffer().size()) {
+      FAIL() << "Read position " << Var_arg1 << " is out of bounds";
+    }
+    return ReturnTuple(Box_Char(AsCharBuffer()[Var_arg1]));
+  }
+
+  ReturnTuple Call_resize(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("CharBuffer.resize")
+    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
+    if (Var_arg1 < 0) {
+      FAIL() << "Buffer size " << Var_arg1 << " is invalid";
+    } else {
+      value_.resize(Var_arg1);
+    }
+    return ReturnTuple(VAR_SELF);
+  }
+
+  ReturnTuple Call_size(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("CharBuffer.size")
+    return ReturnTuple(Box_Int(value_.size()));
+  }
+
+  ReturnTuple Call_writeAt(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("CharBuffer.writeAt")
+    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
+    const PrimChar Var_arg2 = (params_args.GetArg(1)).AsChar();
+    if (Var_arg1 < 0 || Var_arg1 >= value_.size()) {
+      FAIL() << "Write position " << Var_arg1 << " is out of bounds";
+    } else {
+      value_[Var_arg1] = Var_arg2;
+    }
+    return ReturnTuple(VAR_SELF);
+  }
+
+  PrimCharBuffer& AsCharBuffer() final { return value_; }
+
+  PrimCharBuffer value_;
+};
+
+Category_CharBuffer& CreateCategory_CharBuffer() {
+  static auto& category = *new ExtCategory_CharBuffer();
+  return category;
+}
+
+S<const Type_CharBuffer> CreateType_CharBuffer(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_CharBuffer(CreateCategory_CharBuffer(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_CharBuffer(const Params<0>::Type& params) {}
+
+BoxedValue CreateValue_CharBuffer(S<const Type_CharBuffer> parent, PrimCharBuffer value) {
+  return BoxedValue::New<ExtValue_CharBuffer>(std::move(parent), std::move(value));
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Builtin_Float.cpp b/base/src/Builtin_Float.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Builtin_Float.cpp
@@ -0,0 +1,107 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2022 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 <sstream>
+
+#include "category-source.hpp"
+#include "Streamlined_Float.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Default.hpp"
+#include "Category_Duplicate.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
+#include "Category_Int.hpp"
+#include "Category_LessThan.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Float : public Category_Float {
+};
+
+struct ExtType_Float : public Type_Float {
+  inline ExtType_Float(Category_Float& p, Params<0>::Type params) : Type_Float(p, params) {}
+
+  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Float.default")
+    return ReturnTuple(Box_Float(0.0));
+  }
+
+  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Float.equals")
+    const PrimFloat Var_arg1 = (params_args.GetArg(0)).AsFloat();
+    const PrimFloat Var_arg2 = (params_args.GetArg(1)).AsFloat();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+
+  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Float.lessThan")
+    const PrimFloat Var_arg1 = (params_args.GetArg(0)).AsFloat();
+    const PrimFloat Var_arg2 = (params_args.GetArg(1)).AsFloat();
+    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+  }
+};
+
+ReturnTuple DispatchFloat(PrimFloat value, const ValueFunction& label,
+                          const ParamsArgs& params_args) {
+  switch (label.collection) {
+    case CategoryId_AsBool:
+      return ReturnTuple(Box_Bool(value != 0.0));
+    case CategoryId_AsFloat:
+      return ReturnTuple(Box_Float(value));
+    case CategoryId_AsInt:
+      return ReturnTuple(Box_Int(value));
+    case CategoryId_Duplicate:
+      return ReturnTuple(Box_Float(value));
+    case CategoryId_Formatted: {
+      // NOTE: std::to_string does weird things with significant digits.
+      std::ostringstream output;
+      output << value;
+      return ReturnTuple(Box_String(output.str()));
+    }
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(1000000009ULL * reinterpret_cast<uint64_t&>(value)));
+    default:
+      FAIL() << "Float does not implement " << label;
+      __builtin_unreachable();
+  }
+}
+
+Category_Float& CreateCategory_Float() {
+  static auto& category = *new ExtCategory_Float();
+  return category;
+}
+
+S<const Type_Float> CreateType_Float(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_Float(CreateCategory_Float(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_Float(const Params<0>::Type& params) {}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Builtin_Identifier.cpp b/base/src/Builtin_Identifier.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Builtin_Identifier.cpp
@@ -0,0 +1,98 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 <iomanip>
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Streamlined_Identifier.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
+#include "Category_LessThan.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Identifier : public Category_Identifier {
+};
+
+struct ExtType_Identifier : public Type_Identifier {
+  inline ExtType_Identifier(Category_Identifier& p, Params<1>::Type params) : Type_Identifier(p, params) {}
+
+  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Identifier.equals")
+    const PrimIdentifier Var_arg1 = (params_args.GetArg(0)).AsIdentifier();
+    const PrimIdentifier Var_arg2 = (params_args.GetArg(1)).AsIdentifier();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+
+  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Identifier.lessThan")
+    const PrimIdentifier Var_arg1 = (params_args.GetArg(0)).AsIdentifier();
+    const PrimIdentifier Var_arg2 = (params_args.GetArg(1)).AsIdentifier();
+    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+  }
+};
+
+PrimIdentifier RandomizeIdentifier(PrimIdentifier identifier) {
+  // This is random enough. It only needs to be odd.
+  const static auto MULTIPLIER =
+    ((unsigned long long) &CreateCategory_Identifier())
+    + 1 - (1 & (unsigned long long) &CreateCategory_Identifier());
+  return reinterpret_cast<PrimIdentifier>(MULTIPLIER * MULTIPLIER * ((unsigned long long) identifier));
+}
+
+ReturnTuple DispatchIdentifier(PrimIdentifier value, const ValueFunction& label,
+                               const ParamsArgs& params_args) {
+  switch (label.collection) {
+    case CategoryId_Formatted: {
+      std::ostringstream output;
+      output << std::hex << std::setfill('0') << std::setw(16) << (unsigned long long) value;
+      return ReturnTuple(Box_String(output.str()));
+    }
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(1000000009ULL * ((unsigned long long) value + 1000000007ULL)));
+    default:
+      FAIL() << "Identifier does not implement " << label;
+      __builtin_unreachable();
+  }
+}
+
+Category_Identifier& CreateCategory_Identifier() {
+  static auto& category = *new ExtCategory_Identifier();
+  return category;
+}
+
+static auto& Identifier_instance_cache = *new InstanceCache<1, Type_Identifier>([](const Params<1>::Type& params) {
+    return S_get(new ExtType_Identifier(CreateCategory_Identifier(), params));
+  });
+
+S<const Type_Identifier> CreateType_Identifier(const Params<1>::Type& params) {
+  return Identifier_instance_cache.GetOrCreate(params);
+}
+
+void RemoveType_Identifier(const Params<1>::Type& params) {
+  Identifier_instance_cache.Remove(params);
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Builtin_Int.cpp b/base/src/Builtin_Int.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Builtin_Int.cpp
@@ -0,0 +1,118 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2022 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 <climits>
+#include <string>
+
+#include "category-source.hpp"
+#include "Streamlined_Int.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsChar.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Char.hpp"
+#include "Category_Default.hpp"
+#include "Category_Duplicate.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
+#include "Category_Int.hpp"
+#include "Category_LessThan.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Int : public Category_Int {
+};
+
+struct ExtType_Int : public Type_Int {
+  inline ExtType_Int(Category_Int& p, Params<0>::Type params) : Type_Int(p, params) {}
+
+  ReturnTuple Call_maxBound(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Int.maxBound")
+    return ReturnTuple(Box_Int(9223372036854775807LL));
+  }
+
+  ReturnTuple Call_minBound(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Int.minBound")
+    return ReturnTuple(Box_Int(-9223372036854775807LL - 1LL));
+  }
+
+  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Int.default")
+    return ReturnTuple(Box_Int(0));
+  }
+
+  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Int.equals")
+    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
+    const PrimInt Var_arg2 = (params_args.GetArg(1)).AsInt();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+
+  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Int.lessThan")
+    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
+    const PrimInt Var_arg2 = (params_args.GetArg(1)).AsInt();
+    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+  }
+};
+
+ReturnTuple DispatchInt(PrimInt value, const ValueFunction& label,
+                        const ParamsArgs& params_args) {
+  switch (label.collection) {
+    case CategoryId_AsBool:
+      return ReturnTuple(Box_Bool(value != 0));
+    case CategoryId_AsChar:
+      return ReturnTuple(Box_Char(PrimChar(((value % 256) + 256) % 256)));
+    case CategoryId_AsFloat:
+      return ReturnTuple(Box_Float(value));
+    case CategoryId_AsInt:
+      return ReturnTuple(Box_Int(value));
+    case CategoryId_Duplicate:
+      return ReturnTuple(Box_Int(value));
+    case CategoryId_Formatted:
+      return ReturnTuple(Box_String(std::to_string(value)));
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(1000000007ULL * value));
+    default:
+      FAIL() << "Int does not implement " << label;
+      __builtin_unreachable();
+  }
+}
+
+Category_Int& CreateCategory_Int() {
+  static auto& category = *new ExtCategory_Int();
+  return category;
+}
+
+S<const Type_Int> CreateType_Int(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_Int(CreateCategory_Int(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_Int(const Params<0>::Type& params) {}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Builtin_Pointer.cpp b/base/src/Builtin_Pointer.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Builtin_Pointer.cpp
@@ -0,0 +1,62 @@
+/* -----------------------------------------------------------------------------
+Copyright 2022 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-source.hpp"
+#include "Streamlined_Pointer.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Pointer : public Category_Pointer {
+};
+
+struct ExtType_Pointer : public Type_Pointer {
+  inline ExtType_Pointer(Category_Pointer& p, Params<1>::Type params) : Type_Pointer(p, params) {}
+};
+
+ReturnTuple DispatchPointer(PrimPointer value, const ValueFunction& label,
+                            const ParamsArgs& params_args) {
+  switch (label.collection) {
+    default:
+      FAIL() << "Pointer does not implement " << label;
+      __builtin_unreachable();
+  }
+}
+
+Category_Pointer& CreateCategory_Pointer() {
+  static auto& category = *new ExtCategory_Pointer();
+  return category;
+}
+
+static auto& Pointer_instance_cache = *new InstanceCache<1, Type_Pointer>([](const Params<1>::Type& params) {
+    return S_get(new ExtType_Pointer(CreateCategory_Pointer(), params));
+  });
+
+S<const Type_Pointer> CreateType_Pointer(const Params<1>::Type& params) {
+  return Pointer_instance_cache.GetOrCreate(params);
+}
+
+void RemoveType_Pointer(const Params<1>::Type& params) {
+  Pointer_instance_cache.Remove(params);
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Builtin_String.cpp b/base/src/Builtin_String.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Builtin_String.cpp
@@ -0,0 +1,228 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 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-source.hpp"
+#include "Streamlined_String.hpp"
+#include "Category_Append.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Build.hpp"
+#include "Category_Char.hpp"
+#include "Category_CharBuffer.hpp"
+#include "Category_Container.hpp"
+#include "Category_Default.hpp"
+#include "Category_DefaultOrder.hpp"
+#include "Category_Duplicate.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
+#include "Category_Int.hpp"
+#include "Category_LessThan.hpp"
+#include "Category_Order.hpp"
+#include "Category_ReadAt.hpp"
+#include "Category_String.hpp"
+#include "Category_SubSequence.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_String : public Category_String {
+};
+
+class Value_StringBuilder : public TypeValue {
+ public:
+  std::string CategoryName() const final { return "StringBuilder"; }
+
+  ReturnTuple Dispatch(const ValueFunction& label,
+                       const ParamsArgs& params_args) final {
+    if (&label == &Function_Append_append) {
+      TRACE_FUNCTION("StringBuilder.append")
+      std::lock_guard<std::mutex> lock(mutex);
+      output_ << TypeValue::Call(params_args.GetArg(0), Function_Formatted_formatted, PassParamsArgs()).At(0).AsString();
+      return ReturnTuple(VAR_SELF);
+    }
+    if (&label == &Function_Build_build) {
+      TRACE_FUNCTION("StringBuilder.build")
+      std::lock_guard<std::mutex> lock(mutex);
+      return ReturnTuple(Box_String(output_.str()));
+    }
+    return TypeValue::Dispatch(label, params_args);
+  }
+
+ private:
+  std::mutex mutex;
+  std::ostringstream output_;
+};
+
+struct ExtType_String : public Type_String {
+  inline ExtType_String(Category_String& p, Params<0>::Type params) : Type_String(p, params) {}
+
+  ReturnTuple Call_builder(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.builder")
+    return ReturnTuple(BoxedValue::New<Value_StringBuilder>());
+  }
+
+  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.default")
+    return ReturnTuple(Box_String(""));
+  }
+
+  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.equals")
+    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
+    const BoxedValue& Var_arg2 = (params_args.GetArg(1));
+    return ReturnTuple(Box_Bool(Var_arg1.AsString()==Var_arg2.AsString()));
+  }
+
+  ReturnTuple Call_fromCharBuffer(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.fromCharBuffer")
+    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
+    return ReturnTuple(Box_String(PrimString(Var_arg1.AsCharBuffer())));
+  }
+
+  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.lessThan")
+    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
+    const BoxedValue& Var_arg2 = (params_args.GetArg(1));
+    return ReturnTuple(Box_Bool(Var_arg1.AsString()<Var_arg2.AsString()));
+  }
+};
+
+class StringOrder : public TypeValue {
+ public:
+  StringOrder(BoxedValue container, const std::string& s)
+    : container_(container), value_(s) {}
+
+  std::string CategoryName() const final { return "StringOrder"; }
+
+  ReturnTuple Dispatch(const ValueFunction& label, const ParamsArgs& params_args) final {
+    if (&label == &Function_Order_next) {
+      TRACE_FUNCTION("StringOrder.next")
+      if (index_+1 >= value_.size()) {
+        return ReturnTuple(Var_empty);
+      } else {
+        ++index_;
+        return ReturnTuple(VAR_SELF);
+      }
+    }
+    if (&label == &Function_Order_get) {
+      TRACE_FUNCTION("StringOrder.get")
+      if (index_ >= value_.size()) {
+        FAIL() << "Iterated past end of String";
+      }
+      return ReturnTuple(Box_Char(value_[index_]));
+    }
+    return TypeValue::Dispatch(label, params_args);
+  }
+
+ private:
+  const BoxedValue container_;
+  const std::string& value_;
+  int index_ = 0;
+};
+
+struct ExtValue_String : public Value_String {
+  inline ExtValue_String(S<const Type_String> p, const PrimString& value)
+    : Value_String(std::move(p)), value_(value) {}
+
+  ReturnTuple Call_asBool(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.asBool")
+    return ReturnTuple(Box_Bool(value_.size() != 0));
+  }
+
+  ReturnTuple Call_defaultOrder(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.defaultOrder")
+    if (value_.empty()) {
+      return ReturnTuple(Var_empty);
+    } else {
+      return ReturnTuple(BoxedValue::New<StringOrder>(VAR_SELF, value_));
+    }
+  }
+
+  ReturnTuple Call_duplicate(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.duplicate")
+    return ReturnTuple(VAR_SELF);
+  }
+
+  ReturnTuple Call_formatted(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.formatted")
+    return ReturnTuple(VAR_SELF);
+  }
+
+  ReturnTuple Call_hashed(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.hashed")
+    PrimInt hash = 1000000009ULL;
+    for (char c : value_) {
+      hash = hash * 1000000009ULL + (unsigned long long) c * 1000000007ULL;
+    }
+    return ReturnTuple(Box_Int(hash));
+  }
+
+  ReturnTuple Call_readAt(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.readAt")
+    const PrimInt Var_arg1 = (params_args.GetArg(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 Call_size(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.size")
+    return ReturnTuple(Box_Int(value_.size()));
+  }
+
+  ReturnTuple Call_subSequence(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.subSequence")
+    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
+    const PrimInt Var_arg2 = (params_args.GetArg(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)));
+  }
+
+  const PrimString& AsString() const final { return value_; }
+
+  const PrimString value_;
+};
+
+Category_String& CreateCategory_String() {
+  static auto& category = *new ExtCategory_String();
+  return category;
+}
+
+S<const Type_String> CreateType_String(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_String(CreateCategory_String(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_String(const Params<0>::Type& params) {}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+BoxedValue Box_String(const PrimString& value) {
+  return BoxedValue::New<ExtValue_String>(CreateType_String(Params<0>::Type()), value);
+}
diff --git a/base/src/Extension_Bool.cpp b/base/src/Extension_Bool.cpp
deleted file mode 100644
--- a/base/src/Extension_Bool.cpp
+++ /dev/null
@@ -1,100 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2022 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-source.hpp"
-#include "Streamlined_Bool.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_Bool.hpp"
-#include "Category_Default.hpp"
-#include "Category_Duplicate.hpp"
-#include "Category_Equals.hpp"
-#include "Category_Float.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Hashed.hpp"
-#include "Category_Int.hpp"
-#include "Category_String.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-struct ExtCategory_Bool : public Category_Bool {
-};
-
-struct ExtType_Bool : public Type_Bool {
-  inline ExtType_Bool(Category_Bool& p, Params<0>::Type params) : Type_Bool(p, params) {}
-
-  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Bool.default")
-    return ReturnTuple(Box_Bool(false));
-  }
-
-  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Bool.equals")
-    const PrimBool Var_arg1 = (params_args.GetArg(0)).AsBool();
-    const PrimBool Var_arg2 = (params_args.GetArg(1)).AsBool();
-    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-  }
-
-  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Bool.lessThan")
-    const PrimBool Var_arg1 = (params_args.GetArg(0)).AsBool();
-    const PrimBool Var_arg2 = (params_args.GetArg(1)).AsBool();
-    return ReturnTuple(Box_Bool(!Var_arg1 && Var_arg2));
-  }
-};
-
-ReturnTuple DispatchBool(PrimBool value, const ValueFunction& label,
-                         const ParamsArgs& params_args) {
-  switch (label.collection) {
-    case CategoryId_AsBool:
-      return ReturnTuple(Box_Bool(value));
-    case CategoryId_AsFloat:
-      return ReturnTuple(Box_Float(value ? 1.0 : 0.0));
-    case CategoryId_AsInt:
-      return ReturnTuple(Box_Int(value? 1 : 0));
-    case CategoryId_Duplicate:
-      return ReturnTuple(Box_Bool(value));
-    case CategoryId_Formatted:
-      return ReturnTuple(Box_String(value? "true" : "false"));
-    case CategoryId_Hashed:
-      return ReturnTuple(Box_Int(value ? 1000000009ULL : 1000000007ULL));
-    default:
-      FAIL() << "Bool does not implement " << label;
-      __builtin_unreachable();
-  }
-}
-
-Category_Bool& CreateCategory_Bool() {
-  static auto& category = *new ExtCategory_Bool();
-  return category;
-}
-
-S<const Type_Bool> CreateType_Bool(const Params<0>::Type& params) {
-  static const auto cached = S_get(new ExtType_Bool(CreateCategory_Bool(), Params<0>::Type()));
-  return cached;
-}
-
-void RemoveType_Bool(const Params<0>::Type& params) {}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Extension_Char.cpp b/base/src/Extension_Char.cpp
deleted file mode 100644
--- a/base/src/Extension_Char.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2022 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-source.hpp"
-#include "Streamlined_Char.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsChar.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_Bool.hpp"
-#include "Category_Char.hpp"
-#include "Category_Default.hpp"
-#include "Category_Duplicate.hpp"
-#include "Category_Equals.hpp"
-#include "Category_Float.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Hashed.hpp"
-#include "Category_Int.hpp"
-#include "Category_LessThan.hpp"
-#include "Category_String.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-struct ExtCategory_Char : public Category_Char {
-};
-
-struct ExtType_Char : public Type_Char {
-  inline ExtType_Char(Category_Char& p, Params<0>::Type params) : Type_Char(p, params) {}
-
-  ReturnTuple Call_maxBound(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Char.maxBound")
-    return ReturnTuple(Box_Char('\xff'));
-  }
-
-  ReturnTuple Call_minBound(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Char.minBound")
-    return ReturnTuple(Box_Char('\0'));
-  }
-
-  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Char.default")
-    return ReturnTuple(Box_Char('\0'));
-  }
-
-  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Char.equals")
-    const PrimChar Var_arg1 = (params_args.GetArg(0)).AsChar();
-    const PrimChar Var_arg2 = (params_args.GetArg(1)).AsChar();
-    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-  }
-
-  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Char.lessThan")
-    const PrimChar Var_arg1 = (params_args.GetArg(0)).AsChar();
-    const PrimChar Var_arg2 = (params_args.GetArg(1)).AsChar();
-    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-  }
-};
-
-ReturnTuple DispatchChar(PrimChar value, const ValueFunction& label,
-                         const ParamsArgs& params_args) {
-  switch (label.collection) {
-    case CategoryId_AsBool:
-      return ReturnTuple(Box_Bool(value != '\0'));
-    case CategoryId_AsChar:
-      return ReturnTuple(Box_Char(value));
-    case CategoryId_AsFloat:
-      return ReturnTuple(Box_Float(((int) value + 256) % 256));
-    case CategoryId_AsInt:
-      return ReturnTuple(Box_Int(((int) value + 256) % 256));
-    case CategoryId_Duplicate:
-      return ReturnTuple(Box_Char(value));
-    case CategoryId_Formatted:
-      return ReturnTuple(Box_String(PrimString(1, value)));
-    case CategoryId_Hashed:
-      return ReturnTuple(Box_Int(1000000009ULL * ((unsigned long long) value + 1000000007ULL)));
-    default:
-      FAIL() << "Char does not implement " << label;
-      __builtin_unreachable();
-  }
-}
-
-Category_Char& CreateCategory_Char() {
-  static auto& category = *new ExtCategory_Char();
-  return category;
-}
-
-S<const Type_Char> CreateType_Char(const Params<0>::Type& params) {
-  static const auto cached = S_get(new ExtType_Char(CreateCategory_Char(), Params<0>::Type()));
-  return cached;
-}
-
-void RemoveType_Char(const Params<0>::Type& params) {}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Extension_CharBuffer.cpp b/base/src/Extension_CharBuffer.cpp
deleted file mode 100644
--- a/base/src/Extension_CharBuffer.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2021 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-source.hpp"
-#include "Streamlined_CharBuffer.hpp"
-#include "Category_Char.hpp"
-#include "Category_CharBuffer.hpp"
-#include "Category_Container.hpp"
-#include "Category_Int.hpp"
-#include "Category_ReadAt.hpp"
-#include "Category_WriteAt.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-BoxedValue CreateValue_CharBuffer(S<const Type_CharBuffer> parent, PrimCharBuffer buffer);
-
-struct ExtCategory_CharBuffer : public Category_CharBuffer {
-};
-
-struct ExtType_CharBuffer : public Type_CharBuffer {
-  inline ExtType_CharBuffer(Category_CharBuffer& p, Params<0>::Type params) : Type_CharBuffer(p, params) {}
-
-  ReturnTuple Call_new(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("CharBuffer.new")
-    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
-    if (Var_arg1 < 0) {
-      FAIL() << "Buffer size " << Var_arg1 << " is invalid";
-    }
-    return ReturnTuple(CreateValue_CharBuffer(CreateType_CharBuffer(Params<0>::Type()), PrimCharBuffer(Var_arg1,'\0')));
-  }
-};
-
-struct ExtValue_CharBuffer : public Value_CharBuffer {
-  inline ExtValue_CharBuffer(S<const Type_CharBuffer> p, PrimCharBuffer value)
-    : Value_CharBuffer(std::move(p)), value_(std::move(value)) {}
-
-  ReturnTuple Call_readAt(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("CharBuffer.readAt")
-    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
-    if (Var_arg1 < 0 || Var_arg1 >= AsCharBuffer().size()) {
-      FAIL() << "Read position " << Var_arg1 << " is out of bounds";
-    }
-    return ReturnTuple(Box_Char(AsCharBuffer()[Var_arg1]));
-  }
-
-  ReturnTuple Call_resize(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("CharBuffer.resize")
-    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
-    if (Var_arg1 < 0) {
-      FAIL() << "Buffer size " << Var_arg1 << " is invalid";
-    } else {
-      value_.resize(Var_arg1);
-    }
-    return ReturnTuple(VAR_SELF);
-  }
-
-  ReturnTuple Call_size(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("CharBuffer.size")
-    return ReturnTuple(Box_Int(value_.size()));
-  }
-
-  ReturnTuple Call_writeAt(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("CharBuffer.writeAt")
-    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
-    const PrimChar Var_arg2 = (params_args.GetArg(1)).AsChar();
-    if (Var_arg1 < 0 || Var_arg1 >= value_.size()) {
-      FAIL() << "Write position " << Var_arg1 << " is out of bounds";
-    } else {
-      value_[Var_arg1] = Var_arg2;
-    }
-    return ReturnTuple(VAR_SELF);
-  }
-
-  PrimCharBuffer& AsCharBuffer() final { return value_; }
-
-  PrimCharBuffer value_;
-};
-
-Category_CharBuffer& CreateCategory_CharBuffer() {
-  static auto& category = *new ExtCategory_CharBuffer();
-  return category;
-}
-
-S<const Type_CharBuffer> CreateType_CharBuffer(const Params<0>::Type& params) {
-  static const auto cached = S_get(new ExtType_CharBuffer(CreateCategory_CharBuffer(), Params<0>::Type()));
-  return cached;
-}
-
-void RemoveType_CharBuffer(const Params<0>::Type& params) {}
-
-BoxedValue CreateValue_CharBuffer(S<const Type_CharBuffer> parent, PrimCharBuffer value) {
-  return BoxedValue::New<ExtValue_CharBuffer>(std::move(parent), std::move(value));
-}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Extension_Float.cpp b/base/src/Extension_Float.cpp
deleted file mode 100644
--- a/base/src/Extension_Float.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2022 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 <sstream>
-
-#include "category-source.hpp"
-#include "Streamlined_Float.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_Bool.hpp"
-#include "Category_Default.hpp"
-#include "Category_Duplicate.hpp"
-#include "Category_Equals.hpp"
-#include "Category_Float.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Hashed.hpp"
-#include "Category_Int.hpp"
-#include "Category_LessThan.hpp"
-#include "Category_String.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-struct ExtCategory_Float : public Category_Float {
-};
-
-struct ExtType_Float : public Type_Float {
-  inline ExtType_Float(Category_Float& p, Params<0>::Type params) : Type_Float(p, params) {}
-
-  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Float.default")
-    return ReturnTuple(Box_Float(0.0));
-  }
-
-  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Float.equals")
-    const PrimFloat Var_arg1 = (params_args.GetArg(0)).AsFloat();
-    const PrimFloat Var_arg2 = (params_args.GetArg(1)).AsFloat();
-    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-  }
-
-  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Float.lessThan")
-    const PrimFloat Var_arg1 = (params_args.GetArg(0)).AsFloat();
-    const PrimFloat Var_arg2 = (params_args.GetArg(1)).AsFloat();
-    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-  }
-};
-
-ReturnTuple DispatchFloat(PrimFloat value, const ValueFunction& label,
-                          const ParamsArgs& params_args) {
-  switch (label.collection) {
-    case CategoryId_AsBool:
-      return ReturnTuple(Box_Bool(value != 0.0));
-    case CategoryId_AsFloat:
-      return ReturnTuple(Box_Float(value));
-    case CategoryId_AsInt:
-      return ReturnTuple(Box_Int(value));
-    case CategoryId_Duplicate:
-      return ReturnTuple(Box_Float(value));
-    case CategoryId_Formatted: {
-      // NOTE: std::to_string does weird things with significant digits.
-      std::ostringstream output;
-      output << value;
-      return ReturnTuple(Box_String(output.str()));
-    }
-    case CategoryId_Hashed:
-      return ReturnTuple(Box_Int(1000000009ULL * reinterpret_cast<uint64_t&>(value)));
-    default:
-      FAIL() << "Float does not implement " << label;
-      __builtin_unreachable();
-  }
-}
-
-Category_Float& CreateCategory_Float() {
-  static auto& category = *new ExtCategory_Float();
-  return category;
-}
-
-S<const Type_Float> CreateType_Float(const Params<0>::Type& params) {
-  static const auto cached = S_get(new ExtType_Float(CreateCategory_Float(), Params<0>::Type()));
-  return cached;
-}
-
-void RemoveType_Float(const Params<0>::Type& params) {}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Extension_Int.cpp b/base/src/Extension_Int.cpp
deleted file mode 100644
--- a/base/src/Extension_Int.cpp
+++ /dev/null
@@ -1,118 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2022 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 <climits>
-#include <string>
-
-#include "category-source.hpp"
-#include "Streamlined_Int.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsChar.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_Bool.hpp"
-#include "Category_Char.hpp"
-#include "Category_Default.hpp"
-#include "Category_Duplicate.hpp"
-#include "Category_Equals.hpp"
-#include "Category_Float.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Hashed.hpp"
-#include "Category_Int.hpp"
-#include "Category_LessThan.hpp"
-#include "Category_String.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-struct ExtCategory_Int : public Category_Int {
-};
-
-struct ExtType_Int : public Type_Int {
-  inline ExtType_Int(Category_Int& p, Params<0>::Type params) : Type_Int(p, params) {}
-
-  ReturnTuple Call_maxBound(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Int.maxBound")
-    return ReturnTuple(Box_Int(9223372036854775807LL));
-  }
-
-  ReturnTuple Call_minBound(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Int.minBound")
-    return ReturnTuple(Box_Int(-9223372036854775807LL - 1LL));
-  }
-
-  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Int.default")
-    return ReturnTuple(Box_Int(0));
-  }
-
-  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Int.equals")
-    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
-    const PrimInt Var_arg2 = (params_args.GetArg(1)).AsInt();
-    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-  }
-
-  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("Int.lessThan")
-    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
-    const PrimInt Var_arg2 = (params_args.GetArg(1)).AsInt();
-    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-  }
-};
-
-ReturnTuple DispatchInt(PrimInt value, const ValueFunction& label,
-                        const ParamsArgs& params_args) {
-  switch (label.collection) {
-    case CategoryId_AsBool:
-      return ReturnTuple(Box_Bool(value != 0));
-    case CategoryId_AsChar:
-      return ReturnTuple(Box_Char(PrimChar(((value % 256) + 256) % 256)));
-    case CategoryId_AsFloat:
-      return ReturnTuple(Box_Float(value));
-    case CategoryId_AsInt:
-      return ReturnTuple(Box_Int(value));
-    case CategoryId_Duplicate:
-      return ReturnTuple(Box_Int(value));
-    case CategoryId_Formatted:
-      return ReturnTuple(Box_String(std::to_string(value)));
-    case CategoryId_Hashed:
-      return ReturnTuple(Box_Int(1000000007ULL * value));
-    default:
-      FAIL() << "Int does not implement " << label;
-      __builtin_unreachable();
-  }
-}
-
-Category_Int& CreateCategory_Int() {
-  static auto& category = *new ExtCategory_Int();
-  return category;
-}
-
-S<const Type_Int> CreateType_Int(const Params<0>::Type& params) {
-  static const auto cached = S_get(new ExtType_Int(CreateCategory_Int(), Params<0>::Type()));
-  return cached;
-}
-
-void RemoveType_Int(const Params<0>::Type& params) {}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Extension_Pointer.cpp b/base/src/Extension_Pointer.cpp
deleted file mode 100644
--- a/base/src/Extension_Pointer.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2022 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-source.hpp"
-#include "Streamlined_Pointer.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-struct ExtCategory_Pointer : public Category_Pointer {
-};
-
-struct ExtType_Pointer : public Type_Pointer {
-  inline ExtType_Pointer(Category_Pointer& p, Params<1>::Type params) : Type_Pointer(p, params) {}
-};
-
-ReturnTuple DispatchPointer(PrimPointer value, const ValueFunction& label,
-                            const ParamsArgs& params_args) {
-  switch (label.collection) {
-    default:
-      FAIL() << "Pointer does not implement " << label;
-      __builtin_unreachable();
-  }
-}
-
-Category_Pointer& CreateCategory_Pointer() {
-  static auto& category = *new ExtCategory_Pointer();
-  return category;
-}
-
-static auto& Pointer_instance_cache = *new InstanceCache<1, Type_Pointer>([](const Params<1>::Type& params) {
-    return S_get(new ExtType_Pointer(CreateCategory_Pointer(), params));
-  });
-
-S<const Type_Pointer> CreateType_Pointer(const Params<1>::Type& params) {
-  return Pointer_instance_cache.GetOrCreate(params);
-}
-
-void RemoveType_Pointer(const Params<1>::Type& params) {
-  Pointer_instance_cache.Remove(params);
-}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Extension_String.cpp b/base/src/Extension_String.cpp
deleted file mode 100644
--- a/base/src/Extension_String.cpp
+++ /dev/null
@@ -1,228 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2021 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-source.hpp"
-#include "Streamlined_String.hpp"
-#include "Category_Append.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_Bool.hpp"
-#include "Category_Build.hpp"
-#include "Category_Char.hpp"
-#include "Category_CharBuffer.hpp"
-#include "Category_Container.hpp"
-#include "Category_Default.hpp"
-#include "Category_DefaultOrder.hpp"
-#include "Category_Duplicate.hpp"
-#include "Category_Equals.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Hashed.hpp"
-#include "Category_Int.hpp"
-#include "Category_LessThan.hpp"
-#include "Category_Order.hpp"
-#include "Category_ReadAt.hpp"
-#include "Category_String.hpp"
-#include "Category_SubSequence.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-struct ExtCategory_String : public Category_String {
-};
-
-class Value_StringBuilder : public TypeValue {
- public:
-  std::string CategoryName() const final { return "StringBuilder"; }
-
-  ReturnTuple Dispatch(const ValueFunction& label,
-                       const ParamsArgs& params_args) final {
-    if (&label == &Function_Append_append) {
-      TRACE_FUNCTION("StringBuilder.append")
-      std::lock_guard<std::mutex> lock(mutex);
-      output_ << TypeValue::Call(params_args.GetArg(0), Function_Formatted_formatted, PassParamsArgs()).At(0).AsString();
-      return ReturnTuple(VAR_SELF);
-    }
-    if (&label == &Function_Build_build) {
-      TRACE_FUNCTION("StringBuilder.build")
-      std::lock_guard<std::mutex> lock(mutex);
-      return ReturnTuple(Box_String(output_.str()));
-    }
-    return TypeValue::Dispatch(label, params_args);
-  }
-
- private:
-  std::mutex mutex;
-  std::ostringstream output_;
-};
-
-struct ExtType_String : public Type_String {
-  inline ExtType_String(Category_String& p, Params<0>::Type params) : Type_String(p, params) {}
-
-  ReturnTuple Call_builder(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.builder")
-    return ReturnTuple(BoxedValue::New<Value_StringBuilder>());
-  }
-
-  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.default")
-    return ReturnTuple(Box_String(""));
-  }
-
-  ReturnTuple Call_equals(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.equals")
-    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
-    const BoxedValue& Var_arg2 = (params_args.GetArg(1));
-    return ReturnTuple(Box_Bool(Var_arg1.AsString()==Var_arg2.AsString()));
-  }
-
-  ReturnTuple Call_fromCharBuffer(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.fromCharBuffer")
-    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
-    return ReturnTuple(Box_String(PrimString(Var_arg1.AsCharBuffer())));
-  }
-
-  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.lessThan")
-    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
-    const BoxedValue& Var_arg2 = (params_args.GetArg(1));
-    return ReturnTuple(Box_Bool(Var_arg1.AsString()<Var_arg2.AsString()));
-  }
-};
-
-class StringOrder : public TypeValue {
- public:
-  StringOrder(BoxedValue container, const std::string& s)
-    : container_(container), value_(s) {}
-
-  std::string CategoryName() const final { return "StringOrder"; }
-
-  ReturnTuple Dispatch(const ValueFunction& label, const ParamsArgs& params_args) final {
-    if (&label == &Function_Order_next) {
-      TRACE_FUNCTION("StringOrder.next")
-      if (index_+1 >= value_.size()) {
-        return ReturnTuple(Var_empty);
-      } else {
-        ++index_;
-        return ReturnTuple(VAR_SELF);
-      }
-    }
-    if (&label == &Function_Order_get) {
-      TRACE_FUNCTION("StringOrder.get")
-      if (index_ >= value_.size()) {
-        FAIL() << "Iterated past end of String";
-      }
-      return ReturnTuple(Box_Char(value_[index_]));
-    }
-    return TypeValue::Dispatch(label, params_args);
-  }
-
- private:
-  const BoxedValue container_;
-  const std::string& value_;
-  int index_ = 0;
-};
-
-struct ExtValue_String : public Value_String {
-  inline ExtValue_String(S<const Type_String> p, const PrimString& value)
-    : Value_String(std::move(p)), value_(value) {}
-
-  ReturnTuple Call_asBool(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.asBool")
-    return ReturnTuple(Box_Bool(value_.size() != 0));
-  }
-
-  ReturnTuple Call_defaultOrder(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.defaultOrder")
-    if (value_.empty()) {
-      return ReturnTuple(Var_empty);
-    } else {
-      return ReturnTuple(BoxedValue::New<StringOrder>(VAR_SELF, value_));
-    }
-  }
-
-  ReturnTuple Call_duplicate(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.duplicate")
-    return ReturnTuple(VAR_SELF);
-  }
-
-  ReturnTuple Call_formatted(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.formatted")
-    return ReturnTuple(VAR_SELF);
-  }
-
-  ReturnTuple Call_hashed(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.hashed")
-    PrimInt hash = 1000000009ULL;
-    for (char c : value_) {
-      hash = hash * 1000000009ULL + (unsigned long long) c * 1000000007ULL;
-    }
-    return ReturnTuple(Box_Int(hash));
-  }
-
-  ReturnTuple Call_readAt(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.readAt")
-    const PrimInt Var_arg1 = (params_args.GetArg(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 Call_size(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.size")
-    return ReturnTuple(Box_Int(value_.size()));
-  }
-
-  ReturnTuple Call_subSequence(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("String.subSequence")
-    const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
-    const PrimInt Var_arg2 = (params_args.GetArg(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)));
-  }
-
-  const PrimString& AsString() const final { return value_; }
-
-  const PrimString value_;
-};
-
-Category_String& CreateCategory_String() {
-  static auto& category = *new ExtCategory_String();
-  return category;
-}
-
-S<const Type_String> CreateType_String(const Params<0>::Type& params) {
-  static const auto cached = S_get(new ExtType_String(CreateCategory_String(), Params<0>::Type()));
-  return cached;
-}
-
-void RemoveType_String(const Params<0>::Type& params) {}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-BoxedValue Box_String(const PrimString& value) {
-  return BoxedValue::New<ExtValue_String>(CreateType_String(Params<0>::Type()), value);
-}
diff --git a/base/src/boxed.cpp b/base/src/boxed.cpp
--- a/base/src/boxed.cpp
+++ b/base/src/boxed.cpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021-2022 Kevin P. Barry
+Copyright 2021-2023 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.
@@ -42,6 +42,11 @@
 ReturnTuple DispatchPointer(PrimPointer value, const ValueFunction& label,
                             const ParamsArgs& params_args) __attribute__((weak));
 
+ReturnTuple DispatchIdentifier(PrimIdentifier value, const ValueFunction& label,
+                             const ParamsArgs& params_args) __attribute__((weak));
+
+PrimIdentifier RandomizeIdentifier(PrimIdentifier identifier) __attribute__((weak));
+
 #ifdef ZEOLITE_PUBLIC_NAMESPACE
 }  // namespace ZEOLITE_PUBLIC_NAMESPACE
 using namespace ZEOLITE_PUBLIC_NAMESPACE;
@@ -83,12 +88,13 @@
 
 std::string UnionValue::CategoryName() const {
   switch (type_) {
-    case UnionValue::Type::EMPTY:   return "empty";
-    case UnionValue::Type::BOOL:    return "Bool";
-    case UnionValue::Type::CHAR:    return "Char";
-    case UnionValue::Type::INT:     return "Int";
-    case UnionValue::Type::FLOAT:   return "Float";
-    case UnionValue::Type::POINTER: return "Pointer";
+    case UnionValue::Type::EMPTY:      return "empty";
+    case UnionValue::Type::BOOL:       return "Bool";
+    case UnionValue::Type::CHAR:       return "Char";
+    case UnionValue::Type::INT:        return "Int";
+    case UnionValue::Type::FLOAT:      return "Float";
+    case UnionValue::Type::POINTER:    return "Pointer";
+    case UnionValue::Type::IDENTIFIER: return "Identifier";
     case UnionValue::Type::BOXED:
       if (!value_.as_boxed_ || !value_.as_boxed_->object_) {
         FAIL() << "Function called on null pointer";
@@ -120,6 +126,16 @@
   zeolite_internal::Validate(name, union_);
 }
 
+// static
+PrimIdentifier BoxedValue::Identify(const BoxedValue& target) {
+  switch (target.union_.type_) {
+    case UnionValue::Type::IDENTIFIER:
+      return target.union_.value_.as_identifier_;
+    default:
+      return RandomizeIdentifier(target.union_.value_.as_identifier_);
+  }
+}
+
 const PrimString& BoxedValue::AsString() const {
   switch (union_.type_) {
     case UnionValue::Type::BOXED:
@@ -165,6 +181,8 @@
       return DispatchFloat(union_.value_.as_float_, label, params_args);
     case UnionValue::Type::POINTER:
       return DispatchPointer(union_.value_.as_pointer_, label, params_args);
+    case UnionValue::Type::IDENTIFIER:
+      return DispatchIdentifier(union_.value_.as_identifier_, label, params_args);
     case UnionValue::Type::BOXED:
       if (!union_.value_.as_boxed_ || !union_.value_.as_boxed_->object_) {
         FAIL() << "Function called on null pointer";
diff --git a/base/src/category-source.cpp b/base/src/category-source.cpp
--- a/base/src/category-source.cpp
+++ b/base/src/category-source.cpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -269,4 +269,43 @@
 PrimCharBuffer& TypeValue::AsCharBuffer() {
   FAIL() << CategoryName() << " is not a CharBuffer value";
   __builtin_unreachable();
+}
+
+namespace {
+
+class CallTrace : public TypeValue {
+ public:
+  CallTrace(BoxedValue next, std::string trace, const ValueFunction& get_func, const ValueFunction& next_func)
+    : next_(std::move(next)), trace_(std::move(trace)), next_func_(next_func), get_func_(get_func) {}
+
+  std::string CategoryName() const override {
+    return "CallTrace";
+  }
+
+  ReturnTuple Dispatch(const ValueFunction& label, const ParamsArgs& params_args) override {
+    if (&label == &next_func_) {
+      return ReturnTuple(next_);
+    }
+    if (&label == &get_func_) {
+      return ReturnTuple(Box_String(trace_));
+    }
+    return TypeValue::Dispatch(label, params_args);
+  }
+
+ private:
+  const BoxedValue next_;
+  const std::string trace_;
+  const ValueFunction& get_func_;
+  const ValueFunction& next_func_;
+};
+
+}  // namespace
+
+BoxedValue GetCallTrace(const ValueFunction& get_func, const ValueFunction& next_func) {
+  const TraceList trace = TraceContext::GetTrace();
+  BoxedValue head;
+  for (auto current = trace.rbegin(), end = trace.rend(); current != end; ++current) {
+    head = BoxedValue::New<CallTrace>(head, (*current)(), get_func, next_func);
+  }
+  return head;
 }
diff --git a/base/src/cleanup.cpp b/base/src/cleanup.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/cleanup.cpp
@@ -0,0 +1,53 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "cleanup.hpp"
+
+#include "category-source.hpp"
+
+
+// static
+void GlobalCleanup::Finish(int code) {
+  GlobalCleanup* current = GetCurrent();
+  while (current) {
+    current->Cleanup(code);
+    current = current->GetNext();
+  }
+}
+
+WrapTypeCall::WrapTypeCall(S<const TypeInstance> type, const TypeFunction* start, const TypeFunction* finish)
+  : type_(std::move(type)), finish_(finish), cross_and_capture_to_(this) {
+  if (start) {
+    (void) TypeInstance::Call(type_, *start, PassParamsArgs());
+  }
+}
+
+WrapTypeCall::~WrapTypeCall() {
+  Cleanup(0);
+}
+
+GlobalCleanup* WrapTypeCall::GetNext() const {
+  return cross_and_capture_to_.Previous();
+}
+
+void WrapTypeCall::Cleanup(int code) {
+  if (finish_) {
+    (void) TypeInstance::Call(type_, *finish_, PassParamsArgs());
+    finish_ = nullptr;
+  }
+}
diff --git a/base/testing.0rp b/base/testing.0rp
new file mode 100644
--- /dev/null
+++ b/base/testing.0rp
@@ -0,0 +1,25 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$TestsOnly$
+
+// Used for automating checks for a testcase.
+@type interface Testcase {
+  start () -> ()
+  finish () -> ()
+}
diff --git a/base/types.hpp b/base/types.hpp
--- a/base/types.hpp
+++ b/base/types.hpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -47,6 +47,45 @@
 class OpaqueObject { ~OpaqueObject() = default; };
 using PrimPointer = OpaqueObject*;
 
+class OpaqueInstance { ~OpaqueInstance() = default; };
+using PrimIdentifier = OpaqueInstance*;
+
+inline void SwapValues(PrimBool& left, PrimBool& right) {
+  PrimBool temp = right;
+  right = left;
+  left = temp;
+}
+
+inline void SwapValues(PrimInt& left, PrimInt& right) {
+  PrimInt temp = right;
+  right = left;
+  left = temp;
+}
+
+inline void SwapValues(PrimChar& left, PrimChar& right) {
+  PrimChar temp = right;
+  right = left;
+  left = temp;
+}
+
+inline void SwapValues(PrimFloat& left, PrimFloat& right) {
+  PrimFloat temp = right;
+  right = left;
+  left = temp;
+}
+
+inline void SwapValues(PrimPointer& left, PrimPointer& right) {
+  PrimPointer temp = right;
+  right = left;
+  left = temp;
+}
+
+inline void SwapValues(PrimIdentifier& left, PrimIdentifier& right) {
+  PrimIdentifier temp = right;
+  right = left;
+  left = temp;
+}
+
 template<int S>
 inline PrimString PrimString_FromLiteral(const char(&literal)[S]) {
   return PrimString(literal, literal + (S - 1));
@@ -96,6 +135,12 @@
     return *this;
   }
 
+  LazyInit& operator = (T&& value) {
+    InitValue();
+    value_ = value;
+    return *this;
+  }
+
  private:
   LazyInit(const LazyInit&) = delete;
   LazyInit(LazyInit&&) = delete;
@@ -118,6 +163,27 @@
   T value_;
   const std::function<T()> create_;
 };
+
+template<class T>
+inline void SwapValues(LazyInit<T>& left, LazyInit<T>& right) {
+  T temp = right.Get();
+  right = left.Get();
+  left = std::move(temp);
+}
+
+template<class T>
+inline void SwapValues(T& left, LazyInit<T>& right) {
+  T temp = right.Get();
+  right = std::move(left);
+  left = std::move(temp);
+}
+
+template<class T>
+inline void SwapValues(LazyInit<T>& left, T& right) {
+  T temp = std::move(right);
+  right = left.Get();
+  left = std::move(temp);
+}
 
 
 class TypeCategory;
diff --git a/bin/zeolite-setup.hs b/bin/zeolite-setup.hs
--- a/bin/zeolite-setup.hs
+++ b/bin/zeolite-setup.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2020-2021 Kevin P. Barry
+Copyright 2020-2021,2023 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.
@@ -40,6 +40,7 @@
                           then handle pn os
                           else compilerErrorM "Parallel processes (-j) must be > 0."
           _ -> compilerErrorM "Parallel processes (-j) must be > 0."
+  handle pn (('-':'j':n@(_:_)):os) = handle pn ("-j":n:os)
   handle pn ("--reuse":_) = do
     config <- loadConfig
     runWith pn config
@@ -68,13 +69,13 @@
 libraries :: [String]
 libraries = [
     "base",
+    "tests",
     "lib/testing",
     "lib/util",
     "lib/container",
     "lib/file",
     "lib/math",
-    "lib/thread",
-    "tests"
+    "lib/thread"
   ]
 
 optionalLibraries :: [String]
@@ -173,6 +174,8 @@
       _coForce = ForceAll,
       _coParallel = pn
     }
-  runCompiler resolver backend options
+  -- The 2 lines below suppress warnings if there were no errors.
+  result <- lift $ toTrackedErrors $ runCompiler resolver backend options
+  when (isCompilerError result) (fromTrackedErrors result)
   mapM_ optionalWarning optionalLibraries where
   optionalWarning library = compilerWarningM $ "Optional library " ++ library ++ " must be built manually if needed"
diff --git a/example/parser/.zeolite-module b/example/parser/.zeolite-module
--- a/example/parser/.zeolite-module
+++ b/example/parser/.zeolite-module
@@ -7,4 +7,4 @@
   "lib/file"
   "lib/testing"
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/example/parser/parse-text.0rp b/example/parser/parse-text.0rp
--- a/example/parser/parse-text.0rp
+++ b/example/parser/parse-text.0rp
@@ -5,7 +5,7 @@
 
 // Parses a string containing a limited set of characters.
 concrete SequenceOfParser {
-  @type create (String, Int /*min*/, Int /*max*/) -> (Parser<String>)
+  @type create (String, Int min:, Int max:) -> (Parser<String>)
 }
 
 // Parses a fixed character.
@@ -21,7 +21,7 @@
 
   @type error     (Formatted)              -> (Parser<all>)
   @type try<#x>   (Parser<#x>)             -> (Parser<#x>)
-  @type or<#x>    (Parser<#x>,Parser<#x>)  -> (Parser<#x>)
-  @type left<#x>  (Parser<#x>,Parser<any>) -> (Parser<#x>)
-  @type right<#x> (Parser<any>,Parser<#x>) -> (Parser<#x>)
+  @type or<#x>    (Parser<#x>, Parser<#x>)  -> (Parser<#x>)
+  @type left<#x>  (Parser<#x>, Parser<any>) -> (Parser<#x>)
+  @type right<#x> (Parser<any>, Parser<#x>) -> (Parser<#x>)
 }
diff --git a/example/parser/parse-text.0rx b/example/parser/parse-text.0rx
--- a/example/parser/parse-text.0rx
+++ b/example/parser/parse-text.0rx
@@ -25,7 +25,7 @@
   }
 
   create (match) {
-    return StringParser{ match }
+    return delegate -> #self
   }
 }
 
@@ -44,7 +44,7 @@
                       min.formatted() + "," + max.formatted() + "} at " +
                       contextOld.getPosition()
     ParseContext<any> context <- contextOld
-    [Append<Formatted>&Build<String>] builder <- String.builder()
+    [Append<Formatted> & Build<String>] builder <- String.builder()
 
     optional Order<Int> counter <- defer
     if (max == 0) {
@@ -55,13 +55,13 @@
 
     Int count <- 0
     traverse (counter -> count) {
-      $Hidden[count,counter]$
+      $Hidden[count, counter]$
       if (context.atEof()) {
         break
       }
       Bool found <- false
       traverse (Counter.zeroIndexed(matches.size()) -> Int index) {
-        $ReadOnly[context,index]$
+        $ReadOnly[context, index]$
         if (context.current() == matches.readAt(index)) {
           \ builder.append(matches.readAt(index))
           found <- true
@@ -83,8 +83,8 @@
     }
   }
 
-  create (matches,min,max) {
-    return SequenceOfParser{ matches, min, max }
+  create (matches, min, max) {
+    return delegate -> #self
   }
 }
 
@@ -106,7 +106,7 @@
   }
 
   create (match) {
-    return CharParser{ match }
+    return delegate -> #self
   }
 }
 
@@ -126,7 +126,7 @@
   }
 
   create (value) {
-    return #self{ value }
+    return delegate -> #self
   }
 }
 
@@ -170,12 +170,12 @@
   }
 
   create (parser) {
-    return #self{ parser }
+    return delegate -> #self
   }
 }
 
 concrete OrParser<#x> {
-  @type create (Parser<#x>,Parser<#x>) -> (Parser<#x>)
+  @type create (Parser<#x>, Parser<#x>) -> (Parser<#x>)
 }
 
 define OrParser {
@@ -196,14 +196,14 @@
     }
   }
 
-  create (parser1,parser2) {
-    return #self{ parser1, parser2 }
+  create (parser1, parser2) {
+    return delegate -> #self
   }
 }
 
 concrete LeftParser<#x> {
 
-  @type create (Parser<#x>,Parser<any>) -> (Parser<#x>)
+  @type create (Parser<#x>, Parser<any>) -> (Parser<#x>)
 }
 
 define LeftParser {
@@ -229,13 +229,13 @@
     }
   }
 
-  create (parser1,parser2) {
-    return #self{ parser1, parser2 }
+  create (parser1, parser2) {
+    return delegate -> #self
   }
 }
 
 concrete RightParser<#x> {
-  @type create (Parser<any>,Parser<#x>) -> (Parser<#x>)
+  @type create (Parser<any>, Parser<#x>) -> (Parser<#x>)
 }
 
 define RightParser {
@@ -256,33 +256,33 @@
     }
   }
 
-  create (parser1,parser2) {
-    return #self{ parser1, parser2 }
+  create (parser1, parser2) {
+    return delegate -> #self
   }
 }
 
 define Parse {
   error (message) {
-    return ErrorParser.create(message)
+    return delegate -> `ErrorParser.create`
   }
 
   const (value) {
-    return ConstParser<#x>.create(value)
+    return delegate -> `ConstParser<#x>.create`
   }
 
   try (parser) {
-    return TryParser<#x>.create(parser)
+    return delegate -> `TryParser<#x>.create`
   }
 
-  or (parser1,parser2) {
-    return OrParser<#x>.create(parser1,parser2)
+  or (parser1, parser2) {
+    return delegate -> `OrParser<#x>.create`
   }
 
-  left (parser1,parser2) {
-    return LeftParser<#x>.create(parser1,parser2)
+  left (parser1, parser2) {
+    return delegate -> `LeftParser<#x>.create`
   }
 
-  right (parser1,parser2) {
-    return RightParser<#x>.create(parser1,parser2)
+  right (parser1, parser2) {
+    return delegate -> `RightParser<#x>.create`
   }
 }
diff --git a/example/parser/parser-test.0rt b/example/parser/parser-test.0rt
--- a/example/parser/parser-test.0rt
+++ b/example/parser/parser-test.0rt
@@ -1,13 +1,30 @@
 testcase "parse data from a file" {
-  success
+  // We need to specify TestChecker here in order to use lib/testing.
+  success TestChecker
 }
 
 unittest test {
   String raw <- FileTesting.forceReadFile($ExprLookup[MODULE_PATH]$ + "/test-data.txt")
-  ErrorOr<TestData> errorOrData <- TestDataParser.create() `ParseState:consumeAll` raw
-  TestData data <- errorOrData.getValue()
+  ErrorOr<TestData> actual <- TestDataParser.create() `ParseState:consumeAll` raw
 
-  \ Testing.checkEquals(data.getName(),"example data")
-  \ Testing.checkEquals(data.getDescription(),"THIS_IS_A_TOKEN")
-  \ Testing.checkFalse(data.getBoolean())
+  // Since TestData.create specifies labels, we _must_ use them here.
+  TestData expected <- TestData.create(
+      name:        "example data",
+      description: "THIS_IS_A_TOKEN",
+      boolean:     false)
+
+  // Since TestData refines TestCompare<TestData>, we can use Matches:value.
+  \ actual.tryValue() `Matches:value` expected
+
+  // If we want to check the ErrorOr<TestData> itself, we need a
+  // ValueMatcher<TestData> to pass to CheckErrorOr:value. We can do this with
+  // CheckValue:using to create one from TestCompare<TestData>.
+  \ actual `Matches:with` CheckErrorOr:value(CheckValue:using(expected))
+
+  // Matches:with uses more general matchers such as CheckValue:equals. &. below
+  // will call the function iff the value isn't empty. This is needed in order
+  // to call functions on optional values.
+  \ actual.tryValue()&.name()        `Matches:with` CheckValue:equals("example data")
+  \ actual.tryValue()&.description() `Matches:with` CheckValue:equals("THIS_IS_A_TOKEN")
+  \ actual.tryValue()&.boolean()     `Matches:with` CheckValue:equals(false)
 }
diff --git a/example/parser/parser.0rp b/example/parser/parser.0rp
--- a/example/parser/parser.0rp
+++ b/example/parser/parser.0rp
@@ -6,7 +6,7 @@
 // ParseState<all> can convert to all other ParseState.
 concrete ParseState<|#x> {
   // Consumes all input and returns the result.
-  @category consumeAll<#y> (Parser<#y>,String) -> (ErrorOr<#y>)
+  @category consumeAll<#y> (Parser<#y>, String) -> (ErrorOr<#y>)
 }
 
 // A self-contained parser operation.
@@ -26,7 +26,7 @@
 @value interface ParseContext<|#x> {
   // Continue computation.
   run<#y>       (Parser<#y>) -> (ParseContext<#y>)
-  runAndGet<#y> (Parser<#y>) -> (ParseContext<any>,ErrorOr<#y>)
+  runAndGet<#y> (Parser<#y>) -> (ParseContext<any>, ErrorOr<#y>)
   getValue      ()           -> (ErrorOr<#x>)
 
   // End computation and pass on the next state.
diff --git a/example/parser/parser.0rx b/example/parser/parser.0rx
--- a/example/parser/parser.0rx
+++ b/example/parser/parser.0rx
@@ -8,7 +8,7 @@
   @value ErrorOr<#x>        value
   @value optional Formatted error
 
-  consumeAll (parser,data) {
+  consumeAll (parser, data) {
     ParseContext<#y> context <- parser.run(new(data))
     if (context.hasAnyError()) {
       return context.getValue()
diff --git a/example/parser/test-data.0rp b/example/parser/test-data.0rp
--- a/example/parser/test-data.0rp
+++ b/example/parser/test-data.0rp
@@ -1,12 +1,23 @@
+// $TestsOnly$ means that categories in this file are only available in .0rt and
+// in other files that use $TestsOnly$.
 $TestsOnly$
 
 // A contrived object for test data.
 concrete TestData {
-  @type create (String,String,Bool) -> (TestData)
+  // This is used in tests. This allows fine-grained error information, vs. just
+  // comparing the entire object for equality. (Also see parser-test.0rt.)
+  refines TestCompare<TestData>
 
-  @value getName        () -> (String)
-  @value getDescription () -> (String)
-  @value getBoolean     () -> (Bool)
+  @value name        () -> (String)
+  @value description () -> (String)
+  @value boolean     () -> (Bool)
+
+  // This limits visibility of everything below. In this case, we don't want
+  // anyone besides TestDataParser (and unittest) to construct TestData.
+  visibility TestDataParser
+
+  // We can give arguments labels to require calls to be clearer.
+  @type create (String name:, String description:, Bool boolean:) -> (TestData)
 }
 
 
diff --git a/example/parser/test-data.0rx b/example/parser/test-data.0rx
--- a/example/parser/test-data.0rx
+++ b/example/parser/test-data.0rx
@@ -5,21 +5,38 @@
   @value String description
   @value Bool   boolean
 
-  create (name,description,boolean) {
+  create (name, description, boolean) {
     return TestData{ name, description, boolean }
   }
 
-  getName () {
+  name () {
     return name
   }
 
-  getDescription () {
+  description () {
     return description
   }
 
-  getBoolean () {
+  boolean () {
     return boolean
   }
+
+  // From TestCompare<TestData>.
+  testCompare (actual, report) {
+    \ MultiChecker.new(report)
+        .tryCheck(
+            title: "name",
+            actual.name(),
+            CheckValue:equals(name))
+        .tryCheck(
+            title: "description",
+            actual.description(),
+            CheckValue:equals(description))
+        .tryCheck(
+            title: "boolean",
+            actual.boolean(),
+            CheckValue:equals(boolean))
+  }
 }
 
 define TestDataParser {
@@ -38,16 +55,16 @@
 
   refines Parser<TestData>
 
-  @category Parser<any> whitespace <- SequenceOfParser.create(" \n\t",1,0) `Parse.or` Parse.error("Expected whitespace")
+  @category Parser<any> whitespace <- SequenceOfParser.create(" \n\t", min: 1, max: 0) `Parse.or` Parse.error("Expected whitespace")
 
   @category String sentenceChars <- "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
                                     "abcdefghijklmnopqrstuvwxyz" +
                                     "., !?-"
-  @category Parser<String> sentence <- SequenceOfParser.create(sentenceChars,0,0)
+  @category Parser<String> sentence <- SequenceOfParser.create(sentenceChars, min: 0, max: 0)
   @category Parser<any>    quote    <- CharParser.create('"')
 
   @category Parser<String> quotedSentence  <- quote `Parse.right` sentence `Parse.left` quote
-  @category Parser<String> token           <- SequenceOfParser.create("ABCDEFGHIJKLMNOPQRSTUVWXYZ_",1,0)
+  @category Parser<String> token           <- SequenceOfParser.create("ABCDEFGHIJKLMNOPQRSTUVWXYZ_", min: 1, max: 0)
   @category Parser<String> sentenceOrToken <- quotedSentence `Parse.or` token `Parse.left` whitespace
 
   @category Parser<Bool> acronym           <- StringParser.create("acronym")  `Parse.right` Parse.const(true)
@@ -75,13 +92,15 @@
       return context.convertError()
     } else {
       return context.setValue(
-        ErrorOr:value(TestData.create(name.getValue(),
-                                         description.getValue(),
-                                         boolean.getValue())))
+        // Since TestData.create specifies labels, we _must_ use them here.
+        ErrorOr:value(TestData.create(
+            name:        name.getValue(),
+            description: description.getValue(),
+            boolean:     boolean.getValue())))
     }
   }
 
   create ()  {
-    return TestDataParser{ }
+    return delegate -> #self
   }
 }
diff --git a/example/primes/flag.0rx b/example/primes/flag.0rx
--- a/example/primes/flag.0rx
+++ b/example/primes/flag.0rx
@@ -1,5 +1,5 @@
 define ThreadFlag {
-  @value [ConditionWait&ConditionResume] cond
+  @value [ConditionWait & ConditionResume] cond
   @value Bool enabled
   @value Bool canceled
 
diff --git a/example/primes/prime-thread.0rp b/example/primes/prime-thread.0rp
--- a/example/primes/prime-thread.0rp
+++ b/example/primes/prime-thread.0rp
@@ -4,5 +4,5 @@
 concrete PrimeThread {
   refines Routine
 
-  @type create (ThreadContinue,PrimeTracker) -> (PrimeThread)
+  @type create (ThreadContinue, PrimeTracker) -> (PrimeThread)
 }
diff --git a/example/primes/prime-thread.0rx b/example/primes/prime-thread.0rx
--- a/example/primes/prime-thread.0rx
+++ b/example/primes/prime-thread.0rx
@@ -2,8 +2,8 @@
   @value ThreadContinue flag
   @value PrimeTracker   tracker
 
-  create (flag,tracker) {
-    return PrimeThread{ flag, tracker }
+  create (flag, tracker) {
+    return delegate -> #self
   }
 
   run () {
diff --git a/example/primes/primes-demo.0rx b/example/primes/primes-demo.0rx
--- a/example/primes/primes-demo.0rx
+++ b/example/primes/primes-demo.0rx
@@ -6,14 +6,14 @@
   run () {
     PrimeTracker tracker <- PrimeTracker.create()
     ThreadFlag flag <- ThreadFlag.new()
-    Thread thread <- ProcessThread.from(PrimeThread.create(flag,tracker)).start()
+    Thread thread <- ProcessThread.from(PrimeThread.create(flag, tracker)).start()
 
     // Interactive input loop.
 
     scoped {
       TextReader reader <- TextReader.fromBlockReader(BasicInput.stdin())
     } in while (!reader.pastEnd()) {
-      $Hidden[tracker,thread]$
+      $Hidden[tracker, thread]$
 
       \ BasicOutput.stderr()
           .writeNow("Press [Enter] to toggle start/stop computation thread. Type \"exit\" to exit.\n")
diff --git a/example/random/random-demo.0rx b/example/random/random-demo.0rx
--- a/example/random/random-demo.0rx
+++ b/example/random/random-demo.0rx
@@ -15,35 +15,35 @@
     // NOTE: The overall scale here doesn't matter; it just matters what the
     // relative weights are. CategoricalTree only supports positive Int weights.
     CategoricalTree<Char> weights <- CategoricalTree<Char>.new()
-        .setWeight('a',8167)
-        .setWeight('b',1492)
-        .setWeight('c',2782)
-        .setWeight('d',4253)
-        .setWeight('e',12702)
-        .setWeight('f',2228)
-        .setWeight('g',2015)
-        .setWeight('h',6094)
-        .setWeight('i',6966)
-        .setWeight('j',153)
-        .setWeight('k',772)
-        .setWeight('l',4025)
-        .setWeight('m',2406)
-        .setWeight('n',6749)
-        .setWeight('o',7507)
-        .setWeight('p',1929)
-        .setWeight('q',95)
-        .setWeight('r',5987)
-        .setWeight('s',6327)
-        .setWeight('t',9056)
-        .setWeight('u',2758)
-        .setWeight('v',978)
-        .setWeight('w',2361)
-        .setWeight('x',150)
-        .setWeight('y',1974)
-        .setWeight('z',74)
+        .setWeight('a', 8167)
+        .setWeight('b', 1492)
+        .setWeight('c', 2782)
+        .setWeight('d', 4253)
+        .setWeight('e', 12702)
+        .setWeight('f', 2228)
+        .setWeight('g', 2015)
+        .setWeight('h', 6094)
+        .setWeight('i', 6966)
+        .setWeight('j', 153)
+        .setWeight('k', 772)
+        .setWeight('l', 4025)
+        .setWeight('m', 2406)
+        .setWeight('n', 6749)
+        .setWeight('o', 7507)
+        .setWeight('p', 1929)
+        .setWeight('q', 95)
+        .setWeight('r', 5987)
+        .setWeight('s', 6327)
+        .setWeight('t', 9056)
+        .setWeight('u', 2758)
+        .setWeight('v', 978)
+        .setWeight('w', 2361)
+        .setWeight('x', 150)
+        .setWeight('y', 1974)
+        .setWeight('z', 74)
 
     Generator<Char>  letter <- weights `RandomCategorical:sampleWith` RandomUniform.probability()
-    Generator<Float> length <- RandomGaussian.new(5.0,3.0)
+    Generator<Float> length <- RandomGaussian.new(5.0, 3.0)
     Generator<Float> timing <- RandomExponential.new(4.0)
 
     while (timeLimit <= 0.0 || Realtime.monoSeconds() < startTime+timeLimit) {
@@ -56,7 +56,7 @@
 
       // Populate the letters using their relative frequencies.
       traverse (Counter.zeroIndexed(buffer.size()) -> Int pos) {
-        \ buffer.writeAt(pos,letter.generate())
+        \ buffer.writeAt(pos, letter.generate())
       }
 
       // Print the word.
diff --git a/lib/container/.zeolite-module b/lib/container/.zeolite-module
--- a/lib/container/.zeolite-module
+++ b/lib/container/.zeolite-module
@@ -34,4 +34,4 @@
     categories: [Vector]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/lib/container/auto-tree.0rp b/lib/container/auto-tree.0rp
--- a/lib/container/auto-tree.0rp
+++ b/lib/container/auto-tree.0rp
@@ -33,12 +33,12 @@
 //   of the tree. It is provided as a filter for the return of getRoot() so that
 //   a container can implement custom functionality using the tree's structure
 //   without risking modification of the tree.
-concrete AutoBinaryTree<|#n,#k,#v|#r> {
+concrete AutoBinaryTree<|#n, #k, #v|#r> {
   refines Container
   refines Duplicate
   #k immutable
-  #n defines  KVFactory<#k,#v>
-  #n requires BalancedTreeNode<#n,#k,#v>
+  #n defines  KVFactory<#k, #v>
+  #n requires BalancedTreeNode<#n, #k, #v>
   #n requires Duplicate
   #k defines  LessThan<#k>
   #r allows   #n
@@ -57,21 +57,21 @@
   // Notes:
   // - updateNode() will be called on every affected node, starting with the
   //   deepest node. It might be called an arbitrary number of times per node.
-  @value weakSet (#k,#v) -> (#v)
+  @value weakSet (#k, #v) -> (#v)
 
   // Swap the value associated with the key if it is not present.
   //
   // Notes:
   // - updateNode() will be called on every affected node, starting with the
   //   deepest node. It might be called an arbitrary number of times per node.
-  @value swap (#k,optional #v) -> (optional #v)
+  @value swap (#k, optional #v) -> (optional #v)
 }
 
 // An interface for reading the state of a BST node.
 //
 // Notes:
 // - This is intended for internal use in BST-based categories.
-@value interface BinaryTreeNode<|#k,#v> {
+@value interface BinaryTreeNode<|#k, #v> {
   getLower  () -> (optional #self)
   getHigher () -> (optional #self)
   getKey    () -> (#k)
@@ -83,8 +83,8 @@
 //
 // Notes:
 // - This is intended for internal use in BST-based categories.
-@value interface BalancedTreeNode<#n|#k,#v|> {
-  refines BinaryTreeNode<#k,#v>
+@value interface BalancedTreeNode<#n|#k, #v|> {
+  refines BinaryTreeNode<#k, #v>
 
   // Set the lower child of the node.
   //
@@ -119,26 +119,26 @@
 //
 // Notes:
 // - This is intended for internal use in BST-based categories.
-concrete ForwardTreeOrder<|#k,#v> {
-  refines Order<KeyValue<#k,#v>>
+concrete ForwardTreeOrder<|#k, #v> {
+  refines Order<KeyValue<#k, #v>>
 
-  @category create<#k,#v> (optional BinaryTreeNode<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>)
+  @category create<#k, #v> (optional BinaryTreeNode<#k, #v>) -> (optional ForwardTreeOrder<#k, #v>)
 
-  @category seek<#k,#v>
+  @category seek<#k, #v>
     #k defines LessThan<#k>
-  (#k,optional BinaryTreeNode<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>)
+  (#k, optional BinaryTreeNode<#k, #v>) -> (optional ForwardTreeOrder<#k, #v>)
 }
 
 // Provides reverse iteration of the nodes in a BST.
 //
 // Notes:
 // - This is intended for internal use in BST-based categories.
-concrete ReverseTreeOrder<|#k,#v> {
-  refines Order<KeyValue<#k,#v>>
+concrete ReverseTreeOrder<|#k, #v> {
+  refines Order<KeyValue<#k, #v>>
 
-  @category create<#k,#v> (optional BinaryTreeNode<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>)
+  @category create<#k, #v> (optional BinaryTreeNode<#k, #v>) -> (optional ReverseTreeOrder<#k, #v>)
 
-  @category seek<#k,#v>
+  @category seek<#k, #v>
     #k defines LessThan<#k>
-  (#k,optional BinaryTreeNode<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>)
+  (#k, optional BinaryTreeNode<#k, #v>) -> (optional ReverseTreeOrder<#k, #v>)
 }
diff --git a/lib/container/hashed-map.0rp b/lib/container/hashed-map.0rp
--- a/lib/container/hashed-map.0rp
+++ b/lib/container/hashed-map.0rp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -21,16 +21,17 @@
 // Params:
 // - #k: The key type.
 // - #v: The value type.
-concrete HashedMap<#k,#v> {
+concrete HashedMap<#k, #v> {
   defines Default
   refines Container
-  refines DefaultOrder<KeyValue<#k,#v>>
+  refines DefaultOrder<KeyValue<#k, #v>>
   refines Duplicate
   refines KeyOrder<#k>
-  refines KVExchange<#k,#v>
-  refines KVWriter<#k,#v>
-  refines KVReader<#k,#v>
+  refines KVExchange<#k, #v>
+  refines KVWriter<#k, #v>
+  refines KVReader<#k, #v>
   refines ValueOrder<#v>
+  refines SetReader<#k>
   #k immutable
   #k defines Equals<#k>
   #k requires Hashed
diff --git a/lib/container/helpers.0rp b/lib/container/helpers.0rp
--- a/lib/container/helpers.0rp
+++ b/lib/container/helpers.0rp
@@ -30,10 +30,10 @@
   // Example:
   //
   //   Bool lt <- x `KeyValueH:lessThan` y
-  @category lessThan<#k,#v>
+  @category lessThan<#k, #v>
     #k defines LessThan<#k>
     #v defines LessThan<#v>
-  (KeyValue<#k,#v>,KeyValue<#k,#v>) -> (Bool)
+  (KeyValue<#k, #v>, KeyValue<#k, #v>) -> (Bool)
 
   // The same as lessThan, but with custom comparators.
   //
@@ -49,11 +49,11 @@
   // Example:
   //
   //   // Ignore the value and compare Int keys.
-  //   Bool lt <- x `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` y
-  @category lessThanWith<#k,#v,#kk,#vv>
+  //   Bool lt <- x `KeyValueH:lessThanWith<?, ?, Int, AlwaysEqual>` y
+  @category lessThanWith<#k, #v, #kk, #vv>
     #kk defines LessThan<#k>
     #vv defines LessThan<#v>
-  (KeyValue<#k,#v>,KeyValue<#k,#v>) -> (Bool)
+  (KeyValue<#k, #v>, KeyValue<#k, #v>) -> (Bool)
 
   // Compare for equals.
   //
@@ -64,10 +64,10 @@
   // Example:
   //
   //   Bool eq <- x `KeyValueH:equals` y
-  @category equals<#k,#v>
+  @category equals<#k, #v>
     #k defines Equals<#k>
     #v defines Equals<#v>
-  (KeyValue<#k,#v>,KeyValue<#k,#v>) -> (Bool)
+  (KeyValue<#k, #v>, KeyValue<#k, #v>) -> (Bool)
 
   // The same as equals, but with custom comparators.
   //
@@ -80,9 +80,9 @@
   // Example:
   //
   //   // Ignore the key and compare Int values.
-  //   Bool eq <- x `KeyValueH:equalsWith<?,?,AlwaysEqual,Int>` y
-  @category equalsWith<#k,#v,#kk,#vv>
+  //   Bool eq <- x `KeyValueH:equalsWith<?, ?, AlwaysEqual, Int>` y
+  @category equalsWith<#k, #v, #kk, #vv>
     #kk defines Equals<#k>
     #vv defines Equals<#v>
-  (KeyValue<#k,#v>,KeyValue<#k,#v>) -> (Bool)
+  (KeyValue<#k, #v>, KeyValue<#k, #v>) -> (Bool)
 }
diff --git a/lib/container/interfaces.0rp b/lib/container/interfaces.0rp
--- a/lib/container/interfaces.0rp
+++ b/lib/container/interfaces.0rp
@@ -48,9 +48,9 @@
 // Params:
 // - #k: The key type.
 // - #v: The value type.
-@value interface KVWriter<#k,#v|> {
+@value interface KVWriter<#k, #v|> {
   // Sets or replaces the value associated with the key and returns self.
-  set (#k,#v) -> (#self)
+  set (#k, #v) -> (#self)
   // Removes the value associated with the key if present and returns self.
   remove (#k) -> (#self)
 }
@@ -84,7 +84,7 @@
 // - #v: The value type.
 @value interface KVExchange<#k|#v|> {
   // Replaces the value if it does not exist and returns the final value.
-  weakSet (#k,#v) -> (#v)
+  weakSet (#k, #v) -> (#v)
 
   // Swap the value associated with the key.
   //
@@ -94,7 +94,7 @@
   //
   // Returns:
   // - optional #v: Previous value, or empty if none existed.
-  swap (#k,optional #v) -> (optional #v)
+  swap (#k, optional #v) -> (optional #v)
 }
 
 // Writing to a set of values.
@@ -118,13 +118,13 @@
 }
 
 // Factory for creating a new key-value pair.
-@type interface KVFactory<#k,#v|> {
+@type interface KVFactory<#k, #v|> {
   // Create a new pair from the provided key and value.
-  newNode (#k,#v) -> (#self)
+  newNode (#k, #v) -> (#self)
 }
 
 // A single key-value pair from a KVReader.
-@value interface KeyValue<|#k,#v> {
+@value interface KeyValue<|#k, #v> {
   // Get the key.
   //
   // Notes:
diff --git a/lib/container/list.0rp b/lib/container/list.0rp
--- a/lib/container/list.0rp
+++ b/lib/container/list.0rp
@@ -38,7 +38,7 @@
 
 // List element that supports reverse iteration.
 @value interface DoubleNode<|#n|#x> {
-  refines ListNode<#n,#x>
+  refines ListNode<#n, #x>
 
   // Return the previous element.
   prev () -> (optional #self)
@@ -63,10 +63,10 @@
   defines NewNode<#x>
   // Duplication happens only in the forward direction.
   refines Duplicate
-  refines DoubleNode<LinkedNode<#x>,#x>
+  refines DoubleNode<LinkedNode<#x>, #x>
 
   // Create a new builder.
-  @type builder () -> (ListBuilder<#x,#self>)
+  @type builder () -> (ListBuilder<#x, #self>)
 
   // Set the value held by the node.
   @value set (#x) -> (#self)
@@ -80,10 +80,10 @@
 concrete ForwardNode<#x> {
   defines NewNode<#x>
   refines Duplicate
-  refines ListNode<ForwardNode<#x>,#x>
+  refines ListNode<ForwardNode<#x>, #x>
 
   // Create a new builder.
-  @type builder () -> (ListBuilder<#x,#self>)
+  @type builder () -> (ListBuilder<#x, #self>)
 
   // Set the value held by the node.
   @value set (#x) -> (#self)
@@ -93,7 +93,7 @@
 concrete ListBuilder<#x|#n> {
   refines Append<#x>
   #n defines NewNode<#x>
-  #n requires ListNode<#n,#x>
+  #n requires ListNode<#n, #x>
 
   // Create a new builder.
   @type new () -> (#self)
@@ -112,5 +112,5 @@
   //   head/tail, but that tail will no longer be the end.
   // - Calling mutating functions (e.g., setNext, setPrev) on any of the
   //   elements between the returned head/tail will invalidate the builder.
-  @value build () -> (optional #n,optional #n)
+  @value build () -> (optional #n, optional #n)
 }
diff --git a/lib/container/sorted-map.0rp b/lib/container/sorted-map.0rp
--- a/lib/container/sorted-map.0rp
+++ b/lib/container/sorted-map.0rp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -21,15 +21,16 @@
 // Params:
 // - #k: The key type.
 // - #v: The value type.
-concrete SortedMap<#k,#v> {
+concrete SortedMap<#k, #v> {
   defines Default
   refines Container
-  refines DefaultOrder<KeyValue<#k,#v>>
+  refines DefaultOrder<KeyValue<#k, #v>>
   refines Duplicate
   refines KeyOrder<#k>
-  refines KVExchange<#k,#v>
-  refines KVWriter<#k,#v>
-  refines KVReader<#k,#v>
+  refines KVExchange<#k, #v>
+  refines KVWriter<#k, #v>
+  refines KVReader<#k, #v>
+  refines SetReader<#k>
   refines ValueOrder<#v>
   #k immutable
   #k defines LessThan<#k>
@@ -44,7 +45,7 @@
   // - Traversal of the entire map is amortized O(n); however, the cost of any
   //   particular iteration could be up to O(log n).
   // - The overall memory cost is O(log n).
-  @value defaultOrder () -> (optional Order<KeyValue<#k,#v>>)
+  @value defaultOrder () -> (optional Order<KeyValue<#k, #v>>)
 
   // Traverse the map in the reverse order of defaultOrder().
   //
@@ -52,19 +53,19 @@
   // - Traversal of the entire map is amortized O(n); however, the cost of any
   //   particular iteration could be up to O(log n).
   // - The overall memory cost is O(log n).
-  @value reverseOrder () -> (optional Order<KeyValue<#k,#v>>)
+  @value reverseOrder () -> (optional Order<KeyValue<#k, #v>>)
 
   // Start forward traversal (same as defaultOrder()) from the specified key.
   //
   // Notes:
   // - If the key does not exist in the SortedMap, the position right after
   //   where it would be (in the forward direction) is returned.
-  @value getForward (#k) -> (optional Order<KeyValue<#k,#v>>)
+  @value getForward (#k) -> (optional Order<KeyValue<#k, #v>>)
 
   // Start reverse traversal (same as reverseOrder()) from the specified key.
   //
   // Notes:
   // - If the key does not exist in the SortedMap, the position right after
   //   where it would be (in the reverse direction) is returned.
-  @value getReverse (#k) -> (optional Order<KeyValue<#k,#v>>)
+  @value getReverse (#k) -> (optional Order<KeyValue<#k, #v>>)
 }
diff --git a/lib/container/sorting.0rp b/lib/container/sorting.0rp
--- a/lib/container/sorting.0rp
+++ b/lib/container/sorting.0rp
@@ -28,7 +28,7 @@
   // - Worst-case storage is O(1).
   @category sort<#x>
     #x defines LessThan<#x>
-  ([ReadAt<#x>&WriteAt<#x>]) -> ()
+  ([ReadAt<#x> & WriteAt<#x>]) -> ()
 
   // In-place unstable sorting of a random-access container.
   //
@@ -43,17 +43,17 @@
   // Example:
   //
   //   // Sort myIntContainer in reverse order. (Reversed is from lib/util.)
-  //   \ Sorting:sortWith<?,Reversed<Int>>(myIntContainer)
-  @category sortWith<#x,#xx>
+  //   \ Sorting:sortWith<?, Reversed<Int>>(myIntContainer)
+  @category sortWith<#x, #xx>
     #xx defines LessThan<#x>
-  ([ReadAt<#x>&WriteAt<#x>]) -> ()
+  ([ReadAt<#x> & WriteAt<#x>]) -> ()
 
   // The same as sortWith, but with LessThan2 instead of LessThan.
   @category sortWith2<#x>
-  ([ReadAt<#x>&WriteAt<#x>],LessThan2<#x>) -> ()
+  ([ReadAt<#x> & WriteAt<#x>], LessThan2<#x>) -> ()
 
   // In-place order reversal of a random-access container.
-  @category reverse<#x> ([ReadAt<#x>&WriteAt<#x>]) -> ()
+  @category reverse<#x> ([ReadAt<#x> & WriteAt<#x>]) -> ()
 
   // In-place stable sorting of an iterable list.
   //
@@ -69,9 +69,9 @@
   // - Worst-case storage is O(1).
   // - If the node is not the actual head, the part of the list being sorted
   //   might become detached from the earlier part of the list.
-  @category sortList<#n,#x>
+  @category sortList<#n, #x>
     #x defines LessThan<#x>
-    #n requires ListNode<#n,#x>
+    #n requires ListNode<#n, #x>
   (optional #n) -> (optional #n)
 
   // In-place stable sorting of an iterable list.
@@ -93,16 +93,16 @@
   // Example:
   //
   //   // Sort myIntContainer in reverse order. (Reversed is from lib/util.)
-  //   optional ListNode<#n,#x> newHead <- Sorting:sortListWith<?,?,Reversed<Int>>(oldHead)
-  @category sortListWith<#n,#x,#xx>
+  //   optional ListNode<#n, #x> newHead <- Sorting:sortListWith<?, ?, Reversed<Int>>(oldHead)
+  @category sortListWith<#n, #x, #xx>
     #xx defines LessThan<#x>
-    #n requires ListNode<#n,#x>
+    #n requires ListNode<#n, #x>
   (optional #n) -> (optional #n)
 
   // The same as sortListWith, but with LessThan2 instead of LessThan.
-  @category sortListWith2<#n,#x>
-    #n requires ListNode<#n,#x>
-  (optional #n,LessThan2<#x>) -> (optional #n)
+  @category sortListWith2<#n, #x>
+    #n requires ListNode<#n, #x>
+  (optional #n, LessThan2<#x>) -> (optional #n)
 
   // Reverses the list in place and returns the new head.
   //
@@ -112,7 +112,7 @@
   // Notes:
   // - If the node is not the actual head, the part of the list being reversed
   //   might become detached from the earlier part of the list.
-  @category reverseList<#n,#x>
-    #n requires ListNode<#n,#x>
+  @category reverseList<#n, #x>
+    #n requires ListNode<#n, #x>
   (optional #n) -> (optional #n)
 }
diff --git a/lib/container/src/auto-tree.0rx b/lib/container/src/auto-tree.0rx
--- a/lib/container/src/auto-tree.0rx
+++ b/lib/container/src/auto-tree.0rx
@@ -41,11 +41,11 @@
   }
 
   get (k) {
-    return find(root,k)
+    return find(root, k)
   }
 
-  weakSet (k,v) {
-    root, optional #v v2 <- insertWeak(root,k,v)
+  weakSet (k, v) {
+    root, optional #v v2 <- insertWeak(root, k, v)
     if (`present` v2) {
       return `require` v2
     } else {
@@ -54,8 +54,8 @@
     }
   }
 
-  swap (k,v) (old) {
-    root, old <- exchange(root,k,v)
+  swap (k, v) (old) {
+    root, old <- exchange(root, k, v)
     if (!present(v) && present(old)) {
       treeSize <- treeSize-1
     } elif (present(v) && !present(old)) {
@@ -63,22 +63,22 @@
     }
   }
 
-  @type exchange (optional #n,#k,optional #v) -> (optional #n,optional #v)
-  exchange (node,k,v) (newRoot,old) {
+  @type exchange (optional #n, #k, optional #v) -> (optional #n, optional #v)
+  exchange (node, k, v) (newRoot, old) {
     if (!present(node)) {
       if (present(v)) {
-        return #n.newNode(k,require(v)), empty
+        return #n.newNode(k, require(v)), empty
       } else {
         return empty, empty
       }
     }
     #n node2 <- require(node)
     if (k `#k.lessThan` node2.getKey()) {
-      newRoot, old <- exchange(node2.getLower(),k,v)
+      newRoot, old <- exchange(node2.getLower(), k, v)
       \ node2.setLower(newRoot)
       newRoot <- rebalance(node2)
     } elif (node2.getKey() `#k.lessThan` k) {
-      newRoot, old <- exchange(node2.getHigher(),k,v)
+      newRoot, old <- exchange(node2.getHigher(), k, v)
       \ node2.setHigher(newRoot)
       newRoot <- rebalance(node2)
     } elif (present(v)) {
@@ -93,18 +93,18 @@
   }
 
 
-  @type insertWeak (optional #n,#k,#v) -> (optional #n,optional #v)
-  insertWeak (node,k,v) (newRoot,value) {
+  @type insertWeak (optional #n, #k, #v) -> (optional #n, optional #v)
+  insertWeak (node, k, v) (newRoot, value) {
     if (!present(node)) {
-      return #n.newNode(k,v), empty
+      return #n.newNode(k, v), empty
     }
     #n node2 <- require(node)
     if (k `#k.lessThan` node2.getKey()) {
-      newRoot, value <- insertWeak(node2.getLower(),k,v)
+      newRoot, value <- insertWeak(node2.getLower(), k, v)
       \ node2.setLower(newRoot)
       newRoot <- rebalance(node2)
     } elif (node2.getKey() `#k.lessThan` k) {
-      newRoot, value <- insertWeak(node2.getHigher(),k,v)
+      newRoot, value <- insertWeak(node2.getHigher(), k, v)
       \ node2.setHigher(newRoot)
       newRoot <- rebalance(node2)
     } else {
@@ -112,15 +112,15 @@
     }
   }
 
-  @type find (optional #n,#k) -> (optional #v)
-  find (node,k) {
+  @type find (optional #n, #k) -> (optional #v)
+  find (node, k) {
     if (present(node)) {
       scoped {
         #n node2 <- require(node)
       } in if (k `#k.lessThan` node2.getKey()) {
-        return find(node2.getLower(),k)
+        return find(node2.getLower(), k)
       } elif (node2.getKey() `#k.lessThan` k) {
-        return find(node2.getHigher(),k)
+        return find(node2.getHigher(), k)
       } else {
         return node2.getValue()
       }
@@ -195,13 +195,13 @@
       \ node.setHigher(temp)
     }
     if (present(newNode)) {
-      \ swapChildren(node,require(newNode))
+      \ swapChildren(node, require(newNode))
       \ require(newNode).updateNode()
     }
   }
 
-  @type removeHighest (optional #n) -> (optional #n,optional #n)
-  removeHighest (node) (newNode,removed) {
+  @type removeHighest (optional #n) -> (optional #n, optional #n)
+  removeHighest (node) (newNode, removed) {
     if (!present(node)) {
       return empty, empty
     }
@@ -217,8 +217,8 @@
     }
   }
 
-  @type removeLowest (optional #n) -> (optional #n,optional #n)
-  removeLowest (node) (newNode,removed) {
+  @type removeLowest (optional #n) -> (optional #n, optional #n)
+  removeLowest (node) (newNode, removed) {
     if (!present(node)) {
       return empty, empty
     }
@@ -234,8 +234,8 @@
     }
   }
 
-  @type swapChildren (#n,#n) -> ()
-  swapChildren (l,r) {
+  @type swapChildren (#n, #n) -> ()
+  swapChildren (l, r) {
     scoped {
       optional #n temp <- l.getLower()
       \ l.setLower(r.getLower())
@@ -250,30 +250,30 @@
 define ForwardTreeOrder {
   $ReadOnlyExcept[]$
 
-  @value BinaryTreeNode<#k,#v> node
-  @value optional ForwardTreeOrder<#k,#v> prev
+  @value BinaryTreeNode<#k, #v> node
+  @value optional ForwardTreeOrder<#k, #v> prev
 
   create (node) (current) {
-    optional BinaryTreeNode<#k,#v> node2 <- node
+    optional BinaryTreeNode<#k, #v> node2 <- node
     current <- empty
     while (present(node2)) {
-      current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
+      current <- ForwardTreeOrder<#k, #v>{ require(node2), current }
       node2 <- require(node2).getLower()
     }
   }
 
-  seek (key,node) (current) {
-    optional BinaryTreeNode<#k,#v> node2 <- node
+  seek (key, node) (current) {
+    optional BinaryTreeNode<#k, #v> node2 <- node
     current <- empty
     while (present(node2)) {
       if (require(node2).getKey() `#k.lessThan` key) {
         // Skip node2 in the traversal, since it's before key.
         node2 <- require(node2).getHigher()
       } elif (key `#k.lessThan` require(node2).getKey()) {
-        current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
+        current <- ForwardTreeOrder<#k, #v>{ require(node2), current }
         node2 <- require(node2).getLower()
       } else {
-        current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
+        current <- ForwardTreeOrder<#k, #v>{ require(node2), current }
         break
       }
     }
@@ -283,46 +283,46 @@
     // Algorithm:
     // 1. Pop self from the stack.
     // 2. Traverse lower to the bottom starting from the higher child of self.
-    optional BinaryTreeNode<#k,#v> node2 <- node.getHigher()
+    optional BinaryTreeNode<#k, #v> node2 <- node.getHigher()
     current <- prev
     while (present(node2)) {
-      current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
+      current <- ForwardTreeOrder<#k, #v>{ require(node2), current }
       node2 <- require(node2).getLower()
     }
   }
 
   get () {
-    return SimpleKeyValue:new(node.getKey(),node.getValue())
+    return SimpleKeyValue:new(node.getKey(), node.getValue())
   }
 }
 
 define ReverseTreeOrder {
   $ReadOnlyExcept[]$
 
-  @value BinaryTreeNode<#k,#v> node
-  @value optional ReverseTreeOrder<#k,#v> prev
+  @value BinaryTreeNode<#k, #v> node
+  @value optional ReverseTreeOrder<#k, #v> prev
 
   create (node) (current) {
-    optional BinaryTreeNode<#k,#v> node2 <- node
+    optional BinaryTreeNode<#k, #v> node2 <- node
     current <- empty
     while (present(node2)) {
-      current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
+      current <- ReverseTreeOrder<#k, #v>{ require(node2), current }
       node2 <- require(node2).getHigher()
     }
   }
 
-  seek (key,node) (current) {
-    optional BinaryTreeNode<#k,#v> node2 <- node
+  seek (key, node) (current) {
+    optional BinaryTreeNode<#k, #v> node2 <- node
     current <- empty
     while (present(node2)) {
       if (require(node2).getKey() `#k.lessThan` key) {
-        current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
+        current <- ReverseTreeOrder<#k, #v>{ require(node2), current }
         node2 <- require(node2).getHigher()
       } elif (key `#k.lessThan` require(node2).getKey()) {
         // Skip node2 in the traversal, since it's after key.
         node2 <- require(node2).getLower()
       } else {
-        current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
+        current <- ReverseTreeOrder<#k, #v>{ require(node2), current }
         break
       }
     }
@@ -332,15 +332,15 @@
     // Algorithm:
     // 1. Pop self from the stack.
     // 2. Traverse higher to the bottom starting from the lower child of self.
-    optional BinaryTreeNode<#k,#v> node2 <- node.getLower()
+    optional BinaryTreeNode<#k, #v> node2 <- node.getLower()
     current <- prev
     while (present(node2)) {
-      current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
+      current <- ReverseTreeOrder<#k, #v>{ require(node2), current }
       node2 <- require(node2).getHigher()
     }
   }
 
   get () {
-    return SimpleKeyValue:new(node.getKey(),node.getValue())
+    return SimpleKeyValue:new(node.getKey(), node.getValue())
   }
 }
diff --git a/lib/container/src/hashed-map.0rx b/lib/container/src/hashed-map.0rx
--- a/lib/container/src/hashed-map.0rx
+++ b/lib/container/src/hashed-map.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -18,10 +18,10 @@
 
 define HashedMap {
   @value Int itemCount
-  @value [DefaultOrder<HashRoot<#k,#v>>&ReadAt<HashRoot<#k,#v>>] table
+  @value [DefaultOrder<HashRoot<#k, #v>> & ReadAt<HashRoot<#k, #v>>] table
 
   new () {
-    return #self{ 0, Vector:createSize<HashRoot<#k,#v>>($ExprLookup[HASH_TABLE_MIN_SIZE]$) }
+    return #self{ 0, Vector:createSize<HashRoot<#k, #v>>($ExprLookup[HASH_TABLE_MIN_SIZE]$) }
   }
 
   default () {
@@ -29,29 +29,33 @@
   }
 
   duplicate () {
-    return #self{ itemCount, table.defaultOrder() `OrderH:duplicateTo` Vector<HashRoot<#k,#v>>.new() }
+    return #self{ itemCount, table.defaultOrder() `OrderH:duplicateTo` Vector<HashRoot<#k, #v>>.new() }
   }
 
   size () {
     return itemCount
   }
 
+  member (k) {
+    return `present` delegate -> `get`
+  }
+
   get (k) {
     return table.readAt(getBin(k.hashed())).find(k)
   }
 
-  set (k,v) {
-    \ swap(k,v)
+  set (k, v) {
+    \ swap(k, v)
     return self
   }
 
   remove (k) {
-    \ swap(k,empty)
+    \ swap(k, empty)
     return self
   }
 
-  weakSet (k,v) {
-    optional #v value <- table.readAt(getBin(k.hashed())).weakReplace(k,v)
+  weakSet (k, v) {
+    optional #v value <- table.readAt(getBin(k.hashed())).weakReplace(k, v)
     if (`present` value) {
       return `require` value
     } else {
@@ -61,8 +65,8 @@
     }
   }
 
-  swap (k,v) (old) {
-    old <- table.readAt(getBin(k.hashed())).exchange(k,v)
+  swap (k, v) (old) {
+    old <- table.readAt(getBin(k.hashed())).exchange(k, v)
     if (!present(v) && present(old)) {
       itemCount <- itemCount-1
       \ maybeResize()
@@ -73,7 +77,7 @@
   }
 
   defaultOrder () {
-    return HashedMapOrder<#k,#v>.new(itemCount,table)
+    return HashedMapOrder<#k, #v>.new(itemCount, table)
   }
 
   keyOrder () {
@@ -95,34 +99,34 @@
 
   @value maybeResize () -> ()
   maybeResize () {
-    optional [DefaultOrder<HashRoot<#k,#v>>&ReadAt<HashRoot<#k,#v>>] newTable <- empty
+    optional [DefaultOrder<HashRoot<#k, #v>> & ReadAt<HashRoot<#k, #v>>] newTable <- empty
     if (itemCount >= $ExprLookup[HASH_TABLE_EXPAND_RATIO]$*table.size()) {
-      newTable <- Vector:createSize<HashRoot<#k,#v>>(2*table.size())
+      newTable <- Vector:createSize<HashRoot<#k, #v>>(2*table.size())
     } elif ($ExprLookup[HASH_TABLE_CONTRACT_RATIO]$*itemCount < table.size() && table.size() > $ExprLookup[HASH_TABLE_MIN_SIZE]$) {
-      newTable <- Vector:createSize<HashRoot<#k,#v>>(table.size()/2)
+      newTable <- Vector:createSize<HashRoot<#k, #v>>(table.size()/2)
     }
     if (`present` newTable) {
-      [DefaultOrder<HashRoot<#k,#v>>&ReadAt<HashRoot<#k,#v>>] oldTable <- table
+      [DefaultOrder<HashRoot<#k, #v>> & ReadAt<HashRoot<#k, #v>>] oldTable <- table
       table <- `require` newTable
       $Hidden[newTable]$
-      traverse (oldTable.defaultOrder() -> HashRoot<#k,#v> root) {
-        traverse (root.defaultOrder() -> HashNode<#k,#v> node) {
-          \ table.readAt(getBin(node.getKey().hashed())).exchange(node.getKey(),node.getValue())
+      traverse (oldTable.defaultOrder() -> HashRoot<#k, #v> root) {
+        traverse (root.defaultOrder() -> HashNode<#k, #v> node) {
+          \ table.readAt(getBin(node.getKey().hashed())).exchange(node.getKey(), node.getValue())
         }
       }
     }
   }
 }
 
-concrete HashNode<#k,#v> {
+concrete HashNode<#k, #v> {
   refines Duplicate
   refines Order<#self>
 
-  @type new (#k,#v,optional HashNode<#k,#v>) -> (#self)
+  @type new (#k, #v, optional HashNode<#k, #v>) -> (#self)
   @value getKey () -> (#k)
   @value getValue () -> (#v)
   @value setValue (#v) -> ()
-  @value setNext (optional HashNode<#k,#v>) -> (optional #self)
+  @value setNext (optional HashNode<#k, #v>) -> (optional #self)
 }
 
 define HashNode {
@@ -130,14 +134,14 @@
 
   @value #k key
   @value #v value
-  @value optional HashNode<#k,#v> next
+  @value optional HashNode<#k, #v> next
 
-  new (key,value,next) {
-    return #self{ key, value, next }
+  new (key, value, next) {
+    return delegate -> #self
   }
 
   duplicate () {
-    optional HashNode<#k,#v> next2 <- empty
+    optional HashNode<#k, #v> next2 <- empty
     if (`present` next) {
       next2 <- require(next).duplicate()
     }
@@ -156,21 +160,21 @@
   }
 }
 
-concrete HashRoot<#k,#v> {
+concrete HashRoot<#k, #v> {
   defines Default
-  refines DefaultOrder<HashNode<#k,#v>>
+  refines DefaultOrder<HashNode<#k, #v>>
   refines Duplicate
   #k defines Equals<#k>
 
-  @value exchange (#k,optional #v) -> (optional #v)
-  @value weakReplace (#k,#v) -> (optional #v)
+  @value exchange (#k, optional #v) -> (optional #v)
+  @value weakReplace (#k, #v) -> (optional #v)
   @value find (#k) -> (optional #v)
   // Override to allow returning HashNode directly.
-  @value defaultOrder () -> (optional HashNode<#k,#v>)
+  @value defaultOrder () -> (optional HashNode<#k, #v>)
 }
 
 define HashRoot {
-  @value optional HashNode<#k,#v> root
+  @value optional HashNode<#k, #v> root
 
   default () {
     return #self{ empty }
@@ -188,17 +192,17 @@
     }
   }
 
-  exchange (k,v) (old) {
+  exchange (k, v) (old) {
     old <- empty
     scoped {
-      optional HashNode<#k,#v> prev <- empty
-    } in traverse (root -> HashNode<#k,#v> node) {
+      optional HashNode<#k, #v> prev <- empty
+    } in traverse (root -> HashNode<#k, #v> node) {
       if (node.getKey() `#k.equals` k) {
         old <- node.getValue()
         if (`present` v) {
           \ node.setValue(`require` v)
         } else {
-          optional HashNode<#k,#v> next <- node.setNext(empty)
+          optional HashNode<#k, #v> next <- node.setNext(empty)
           if (`present` prev) {
             \ require(prev).setNext(next)
           } else {
@@ -211,23 +215,23 @@
       prev <- node
     }
     if (`present` v) {
-      root <- HashNode<#k,#v>.new(k,`require` v,root)
+      root <- HashNode<#k, #v>.new(k, `require` v, root)
     }
   }
 
-  weakReplace (k,v) {
-    traverse (root -> HashNode<#k,#v> node) {
+  weakReplace (k, v) {
+    traverse (root -> HashNode<#k, #v> node) {
       if (node.getKey() `#k.equals` k) {
         return node.getValue()
       }
     }
-    root <- HashNode<#k,#v>.new(k,v,root)
+    root <- HashNode<#k, #v>.new(k, v, root)
     return empty
   }
 
   find (k) (v) {
     v <- empty
-    traverse (root -> HashNode<#k,#v> node) {
+    traverse (root -> HashNode<#k, #v> node) {
       if (node.getKey() `#k.equals` k) {
         return node.getValue()
       }
@@ -235,22 +239,22 @@
   }
 }
 
-concrete HashedMapOrder<#k,#v> {
-  refines Order<KeyValue<#k,#v>>
+concrete HashedMapOrder<#k, #v> {
+  refines Order<KeyValue<#k, #v>>
 
-  @type new (Int,DefaultOrder<HashRoot<#k,#v>>) -> (optional #self)
+  @type new (Int, DefaultOrder<HashRoot<#k, #v>>) -> (optional #self)
 }
 
 define HashedMapOrder {
   @value Int count
-  @value optional Order<HashRoot<#k,#v>> root
-  @value HashNode<#k,#v> node
+  @value optional Order<HashRoot<#k, #v>> root
+  @value HashNode<#k, #v> node
 
-  new (count,table) (next) {
+  new (count, table) (next) {
     next <- empty
     if (count > 0) {
       scoped {
-        optional Order<HashRoot<#k,#v>> root, optional HashNode<#k,#v> node <- seekNext(table.defaultOrder())
+        optional Order<HashRoot<#k, #v>> root, optional HashNode<#k, #v> node <- seekNext(table.defaultOrder())
       } in if (`present` node) {
         next <- #self{ count, root, `require` node }
       }
@@ -258,20 +262,20 @@
   }
 
   get () {
-    return SimpleKeyValue:new(node.getKey(),node.getValue())
+    return SimpleKeyValue:new(node.getKey(), node.getValue())
   }
 
   next () (next) {
     next <- empty
     if ((count <- count-1) > 0) {
       scoped {
-        optional HashNode<#k,#v> newNode <- node.next()
+        optional HashNode<#k, #v> newNode <- node.next()
       } in if (`present` newNode) {
         node <- `require` newNode
         return self
       }
       scoped {
-        root, optional HashNode<#k,#v> newNode <- seekNext(root)
+        root, optional HashNode<#k, #v> newNode <- seekNext(root)
       } in if (`present` newNode) {
         node <- `require` newNode
         return self
@@ -279,8 +283,8 @@
     }
   }
 
-  @type seekNext (optional Order<HashRoot<#k,#v>>) -> (optional Order<HashRoot<#k,#v>>,optional HashNode<#k,#v>)
-  seekNext (start) (root,node) {
+  @type seekNext (optional Order<HashRoot<#k, #v>>) -> (optional Order<HashRoot<#k, #v>>, optional HashNode<#k, #v>)
+  seekNext (start) (root, node) {
     root <- start
     node <- empty
     $Hidden[start]$
diff --git a/lib/container/src/hashed-set.0rx b/lib/container/src/hashed-set.0rx
--- a/lib/container/src/hashed-set.0rx
+++ b/lib/container/src/hashed-set.0rx
@@ -17,14 +17,14 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define HashedSet {
-  @value HashedMap<#k,any> map
+  @value HashedMap<#k, any> map
 
   default () {
     return new()
   }
 
   new () {
-    return #self{ HashedMap<#k,any>.new() }
+    return #self{ HashedMap<#k, any>.new() }
   }
 
   size () {
@@ -36,7 +36,7 @@
   }
 
   add (k) {
-    \ map.set(k,Void.default())
+    \ map.set(k, Void.default())
     return self
   }
 
diff --git a/lib/container/src/helpers.0rx b/lib/container/src/helpers.0rx
--- a/lib/container/src/helpers.0rx
+++ b/lib/container/src/helpers.0rx
@@ -17,11 +17,11 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define KeyValueH {
-  lessThan (x,y) {
-    return lessThanWith<#k,#v,#k,#v>(x,y)
+  lessThan (x, y) {
+    return lessThanWith<#k, #v, #k, #v>(x, y)
   }
 
-  lessThanWith (x,y) {
+  lessThanWith (x, y) {
     if (x.getKey() `#kk.lessThan` y.getKey()) {
       return true
     } elif (y.getKey() `#kk.lessThan` x.getKey()) {
@@ -31,11 +31,11 @@
     }
   }
 
-  equals (x,y) {
-    return equalsWith<#k,#v,#k,#v>(x,y)
+  equals (x, y) {
+    return equalsWith<#k, #v, #k, #v>(x, y)
   }
 
-  equalsWith (x,y) {
+  equalsWith (x, y) {
     if (!(x.getKey() `#kk.equals` y.getKey())) {
       return false
     } else {
diff --git a/lib/container/src/list.0rx b/lib/container/src/list.0rx
--- a/lib/container/src/list.0rx
+++ b/lib/container/src/list.0rx
@@ -35,7 +35,7 @@
   }
 
   builder () {
-    return ListBuilder<#x,#self>.new()
+    return ListBuilder<#x, #self>.new()
   }
 
   next () {
@@ -47,7 +47,7 @@
   }
 
   duplicate () {
-    $Hidden[prev,next,value]$
+    $Hidden[prev, next, value]$
     return require(builder().appendAll(self).build(){0})
   }
 
@@ -104,7 +104,7 @@
   }
 
   builder () {
-    return ListBuilder<#x,#self>.new()
+    return ListBuilder<#x, #self>.new()
   }
 
   next () {
@@ -112,7 +112,7 @@
   }
 
   duplicate () {
-    $Hidden[next,value]$
+    $Hidden[next, value]$
     return require(builder().appendAll(self).build(){0})
   }
 
diff --git a/lib/container/src/queue.0rx b/lib/container/src/queue.0rx
--- a/lib/container/src/queue.0rx
+++ b/lib/container/src/queue.0rx
@@ -30,7 +30,7 @@
   }
 
   duplicate () {
-    $ReadOnly[size,head]$
+    $ReadOnly[size, head]$
     $Hidden[tail]$
     scoped {
       optional ForwardNode<#x> head2, optional ForwardNode<#x> tail2 <-
diff --git a/lib/container/src/sorted-map.0rx b/lib/container/src/sorted-map.0rx
--- a/lib/container/src/sorted-map.0rx
+++ b/lib/container/src/sorted-map.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -17,20 +17,24 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define SortedMap {
-  @value AutoBinaryTree<SortedMapNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>> map
+  @value AutoBinaryTree<SortedMapNode<#k, #v>, #k, #v, BinaryTreeNode<#k, #v>> map
 
   default () {
     return new()
   }
 
   new () {
-    return #self{ AutoBinaryTree<SortedMapNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>>.new() }
+    return #self{ AutoBinaryTree<SortedMapNode<#k, #v>, #k, #v, BinaryTreeNode<#k, #v>>.new() }
   }
 
   size () {
     return map.size()
   }
 
+  member (k) {
+    return `present` delegate -> `get`
+  }
+
   duplicate () {
     return #self{ map.duplicate() }
   }
@@ -39,22 +43,22 @@
     return map.get(k)
   }
 
-  set (k,v) {
-    \ swap(k,v)
+  set (k, v) {
+    \ swap(k, v)
     return self
   }
 
   remove (k) {
-    \ swap(k,empty)
+    \ swap(k, empty)
     return self
   }
 
-  weakSet (k,v) {
-    return map.weakSet(k,v)
+  weakSet (k, v) {
+    return map.weakSet(k, v)
   }
 
-  swap (k,v) {
-    return map.swap(k,v)
+  swap (k, v) {
+    return map.swap(k, v)
   }
 
   defaultOrder () {
@@ -66,11 +70,11 @@
   }
 
   getForward (k) {
-    return ForwardTreeOrder:seek(k,map.getRoot())
+    return ForwardTreeOrder:seek(k, map.getRoot())
   }
 
   getReverse (k) {
-    return ReverseTreeOrder:seek(k,map.getRoot())
+    return ReverseTreeOrder:seek(k, map.getRoot())
   }
 
   keyOrder () {
@@ -83,24 +87,24 @@
 }
 
 define ValidatedTree {
-  @value AutoBinaryTree<SortedMapNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>> map
+  @value AutoBinaryTree<SortedMapNode<#k, #v>, #k, #v, BinaryTreeNode<#k, #v>> map
 
   new () { $NoTrace$
-    return #self{ AutoBinaryTree<SortedMapNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>>.new() }
+    return #self{ AutoBinaryTree<SortedMapNode<#k, #v>, #k, #v, BinaryTreeNode<#k, #v>>.new() }
   }
 
   size () { $NoTrace$
     return map.size()
   }
 
-  set (k,v) { $NoTrace$
-    \ map.swap(k,v)
+  set (k, v) { $NoTrace$
+    \ map.swap(k, v)
     \ validate(map.getRoot())
     return self
   }
 
   remove (k) { $NoTrace$
-    \ map.swap(k,empty)
+    \ map.swap(k, empty)
     \ validate(map.getRoot())
     return self
   }
@@ -109,7 +113,7 @@
     return map.get(k)
   }
 
-  @type validate (optional BinaryTreeNode<#k,#v>) -> ()
+  @type validate (optional BinaryTreeNode<#k, #v>) -> ()
   validate (node) { $NoTrace$
     if (present(node)) {
       \ validateOrder(require(node))
@@ -117,7 +121,7 @@
     }
   }
 
-  @type validateOrder (BinaryTreeNode<#k,#v>) -> ()
+  @type validateOrder (BinaryTreeNode<#k, #v>) -> ()
   validateOrder (node) { $NoTrace$
     if (present(node.getLower())) {
       if (!(require(node.getLower()).getKey() `#k.lessThan` node.getKey())) {
@@ -133,7 +137,7 @@
     }
   }
 
-  @type validateBalance (BinaryTreeNode<#k,#v>) -> ()
+  @type validateBalance (BinaryTreeNode<#k, #v>) -> ()
   validateBalance (node) { $NoTrace$
     scoped {
       Int balance <- 0
@@ -156,9 +160,9 @@
   }
 }
 
-concrete SortedMapNode<#k,#v> {
-  defines KVFactory<#k,#v>
-  refines BalancedTreeNode<SortedMapNode<#k,#v>,#k,#v>
+concrete SortedMapNode<#k, #v> {
+  defines KVFactory<#k, #v>
+  refines BalancedTreeNode<SortedMapNode<#k, #v>, #k, #v>
   refines Duplicate
 }
 
@@ -171,7 +175,7 @@
   @value optional #self lower
   @value optional #self higher
 
-  newNode (k,v) {
+  newNode (k, v) {
     return #self{ 1, k, v, empty, empty }
   }
 
diff --git a/lib/container/src/sorted-set.0rx b/lib/container/src/sorted-set.0rx
--- a/lib/container/src/sorted-set.0rx
+++ b/lib/container/src/sorted-set.0rx
@@ -17,14 +17,14 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define SortedSet {
-  @value SortedMap<#k,any> map
+  @value SortedMap<#k, any> map
 
   default () {
     return new()
   }
 
   new () {
-    return #self{ SortedMap<#k,any>.new() }
+    return #self{ SortedMap<#k, any>.new() }
   }
 
   size () {
@@ -36,7 +36,7 @@
   }
 
   add (k) {
-    \ map.set(k,Void.default())
+    \ map.set(k, Void.default())
     return self
   }
 
diff --git a/lib/container/src/sorting.0rx b/lib/container/src/sorting.0rx
--- a/lib/container/src/sorting.0rx
+++ b/lib/container/src/sorting.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -18,15 +18,15 @@
 
 define Sorting {
   sort (seq) {
-    \ sortWith<#x,#x>(seq)
+    \ sortWith<#x, #x>(seq)
   }
 
   sortWith (seq) {
-    \ seq `sortWith2` AsLessThan2<#x,#xx>.new()
+    \ seq `sortWith2` AsLessThan2<#x, #xx>.new()
   }
 
-  sortWith2 (seq,compare) {
-    \ HeapSort<#x>.inPlace(seq,compare)
+  sortWith2 (seq, compare) {
+    \ HeapSort<#x>.inPlace(seq, compare)
   }
 
   reverse (seq) {
@@ -36,23 +36,23 @@
       $ReadOnly[i]$
       Int j <- seq.size()-i-1
       #x temp <- seq.readAt(i)
-      \ seq.writeAt(i,seq.readAt(j))
-      \ seq.writeAt(j,temp)
+      \ seq.writeAt(i, seq.readAt(j))
+      \ seq.writeAt(j, temp)
     } update {
       i <- i-1
     }
   }
 
   sortList (head) {
-    return sortListWith<#n,#x,#x>(head)
+    return sortListWith<#n, #x, #x>(head)
   }
 
   sortListWith (head) {
-    return head `sortListWith2` AsLessThan2<#x,#xx>.new()
+    return head `sortListWith2` AsLessThan2<#x, #xx>.new()
   }
 
-  sortListWith2 (head,compare) {
-    return MergeSort<#n,#x>.sort(head,compare)
+  sortListWith2 (head, compare) {
+    return MergeSort<#n, #x>.sort(head, compare)
   }
 
   reverseList (head) (head2) {
@@ -80,17 +80,17 @@
 // Putting the params at the top level allows helpers to be called without
 // needing to pass the params every time.
 concrete HeapSort<#x> {
-  @type inPlace ([ReadAt<#x>&WriteAt<#x>],LessThan2<#x>) -> ()
+  @type inPlace ([ReadAt<#x> & WriteAt<#x>], LessThan2<#x>) -> ()
 }
 
 define HeapSort {
   $ReadOnlyExcept[]$
 
-  @value [ReadAt<#x>&WriteAt<#x>] seq
+  @value [ReadAt<#x> & WriteAt<#x>] seq
   @value LessThan2<#x> compare
 
-  inPlace (seq,compare) {
-    \ #self{ seq, compare }.execute()
+  inPlace (seq, compare) {
+    \ (delegate -> #self).execute()
   }
 
   @value execute () -> ()
@@ -100,7 +100,7 @@
       Int i <- seq.size()/2-1
     } in while (i >= 0) {
       $ReadOnly[i]$
-      \ sift(i,seq.size())
+      \ sift(i, seq.size())
     } update {
       i <- i-1
     }
@@ -110,15 +110,15 @@
       Int i <- seq.size()-1
     } in while (i >= 0) {
       $ReadOnly[i]$
-      \ swap(0,i)
-      \ sift(0,i)
+      \ swap(0, i)
+      \ sift(0, i)
     } update {
       i <- i-1
     }
   }
 
-  @value sift (Int,Int) -> ()
-  sift (start,size) { $NoTrace$
+  @value sift (Int, Int) -> ()
+  sift (start, size) { $NoTrace$
     scoped {
       Int last         <- start
       Int indexLargest <- last
@@ -127,7 +127,7 @@
       $ReadOnly[last]$
       Int left  <- 2*last+1
       Int right <- 2*last+2
-      $ReadOnly[left,right]$
+      $ReadOnly[left, right]$
       if (seq.readAt(indexLargest) `compare.lessThan2` seq.readAt(left)) {
         indexLargest <- left
       }
@@ -138,33 +138,33 @@
         break
       }
     } update {
-      \ swap(last,indexLargest)
+      \ swap(last, indexLargest)
       last <- indexLargest
     }
   }
 
-  @value swap (Int,Int) -> ()
-  swap (i,j) { $NoTrace$
+  @value swap (Int, Int) -> ()
+  swap (i, j) { $NoTrace$
     if (i != j) {
       #x temp <- seq.readAt(i)
-      \ seq.writeAt(i,seq.readAt(j))
-      \ seq.writeAt(j,temp)
+      \ seq.writeAt(i, seq.readAt(j))
+      \ seq.writeAt(j, temp)
     }
   }
 }
 
 // Putting the params at the top level allows helpers to be called without
 // needing to pass the params every time.
-concrete MergeSort<#n,#x> {
-  #n requires ListNode<#n,#x>
+concrete MergeSort<#n, #x> {
+  #n requires ListNode<#n, #x>
 
-  @type sort (optional #n,LessThan2<#x>) -> (optional #n)
+  @type sort (optional #n, LessThan2<#x>) -> (optional #n)
 }
 
 define MergeSort {
   @value LessThan2<#x> compare
 
-  sort (head,compare) {
+  sort (head, compare) {
     return #self{ compare }.iterated(head)
   }
 
@@ -188,7 +188,7 @@
         optional #n right <- left  `splitAt` chunk
         next              <- right `splitAt` chunk
         $Hidden[next]$
-        $ReadOnly[left,right]$
+        $ReadOnly[left, right]$
         dirty <- dirty || present(right)
         optional #n newHead, optional #n newTail <- left `merge` right
         if (!present(head2)) {
@@ -203,8 +203,8 @@
     }
   }
 
-  @value splitAt (optional #n,Int) -> (optional #n)
-  splitAt (head,n) {
+  @value splitAt (optional #n, Int) -> (optional #n)
+  splitAt (head, n) {
     optional #n head2 <- head
     $Hidden[head]$
 
@@ -223,14 +223,14 @@
     return empty
   }
 
-  @value merge (optional #n,optional #n) -> (optional #n,optional #n)
-  merge (left,right) (head,tail) {
+  @value merge (optional #n, optional #n) -> (optional #n, optional #n)
+  merge (left, right) (head, tail) {
     head <- empty
     tail <- empty
 
     optional #n left2  <- left
     optional #n right2 <- right
-    $Hidden[left,right]$
+    $Hidden[left, right]$
 
     while (present(left2) && present(right2)) {
       #n append <- defer
diff --git a/lib/container/src/testing.0rx b/lib/container/src/testing.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/src/testing.0rx
@@ -0,0 +1,233 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 CheckSet {
+  equals (expected) {
+    return SetEquals<#k>.new(expected)
+  }
+}
+
+define CheckMap {
+  using (expected) {
+    return MapAdapter<#k, #v>.new(expected)
+  }
+
+  equals (expected) {
+    return MapEquals<#k, #v>.new(expected)
+  }
+
+  matches (expected) {
+    return MapMatches<#k, #v>.new(expected)
+  }
+}
+
+concrete MapAdapter<#k, #v> {
+  #v requires TestCompare<#v>
+
+  @type new ([SetReader<#k> & DefaultOrder<KeyValue<#k, #v>>]) -> ([SetReader<#k> & DefaultOrder<KeyValue<#k, ValueMatcher<#v>>>])
+}
+
+define MapAdapter {
+  $ReadOnlyExcept[current]$
+
+  refines SetReader<#k>
+  refines DefaultOrder<KeyValue<#k, ValueMatcher<#v>>>
+  refines Order<KeyValue<#k, ValueMatcher<#v>>>
+
+  @value [SetReader<#k> & DefaultOrder<KeyValue<#k, #v>>] original
+  @value optional Order<KeyValue<#k, #v>> current
+
+  new (original) {
+    return #self{ original, empty }
+  }
+
+  defaultOrder () {
+    return #self{ original, original.defaultOrder() }
+  }
+
+  get () {
+    scoped {
+      KeyValue<#k, #v> keyValue <- `require` current&.get()
+    } in return SimpleKeyValue:new(keyValue.getKey(), CheckValue:using(keyValue.getValue()))
+  }
+
+  next () {
+    if (`present` (current <- current&.next())) {
+      return self
+    } else {
+      return empty
+    }
+  }
+
+  member (k) {
+    return delegate -> `original.member`
+  }
+}
+
+concrete SetEquals<#k> {
+  #k requires Formatted
+
+  @type new ([SetReader<#k> & DefaultOrder<#k>]) -> (ValueMatcher<[SetReader<#k> & DefaultOrder<#k>]>)
+}
+
+define SetEquals {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<[SetReader<#k> & DefaultOrder<#k>]>
+
+  @value [SetReader<#k> & DefaultOrder<#k>] expected
+
+  new (expected) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    traverse (expected.defaultOrder() -> #k currentExpected) {
+      if (`present` actual && ! `require(actual).member` currentExpected) {
+        \ report.addError(message: String.builder()
+            .append("\"")
+            .append(currentExpected)
+            .append("\" is missing")
+            .build())
+      }
+    }
+    traverse (actual&.defaultOrder() -> #k currentActual) {
+      if (! `expected.member` currentActual) {
+        \ report.addError(message: String.builder()
+            .append("\"")
+            .append(currentActual)
+            .append("\" is unexpected")
+            .build())
+      }
+    }
+  }
+
+  summary () {
+    return "set equals"
+  }
+}
+
+concrete MapEquals<#k, #v> {
+  #k requires Formatted
+  #v requires Formatted
+  #v defines Equals<#v>
+
+  @type new ([SetReader<#k> & DefaultOrder<KeyValue<#k, #v>>]) -> (ValueMatcher<[KVReader<#k, #v> & KeyOrder<#k>]>)
+}
+
+define MapEquals {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<[KVReader<#k, #v> & KeyOrder<#k>]>
+
+  @value [SetReader<#k> & DefaultOrder<KeyValue<#k, #v>>] expected
+
+  new (expected) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    traverse (expected.defaultOrder() -> KeyValue<#k, #v> current) {
+      optional #v value <- actual&.get(current.getKey())
+      $Hidden[actual, expected]$
+      if (! `present` value) {
+        \ report.addError(message: String.builder()
+            .append("Key ")
+            .append(`Format:autoFormat` current.getKey())
+            .append(" is missing")
+            .build())
+      } elif (!(require(value) `#v.equals` current.getValue())) {
+        TestReport newReport <- report.newSection(title: String.builder()
+            .append("Key ")
+            .append(`Format:autoFormat` current.getKey())
+            .build())
+        $Hidden[report]$
+        \ newReport.addError(message: String.builder()
+            .append(`Format:autoFormat` value)
+            .append(" does not equal ")
+            .append(`Format:autoFormat` current.getValue())
+            .build())
+      }
+    }
+    traverse (actual&.keyOrder() -> #k current) {
+      if (! `expected.member` current) {
+        \ report.addError(message: String.builder()
+            .append("Key ")
+            .append(`Format:autoFormat` current)
+            .append(" is unexpected")
+            .build())
+      }
+    }
+  }
+
+  summary () {
+    return "map equals"
+  }
+}
+
+concrete MapMatches<#k, #v> {
+  #k requires Formatted
+
+  @type new ([SetReader<#k> & DefaultOrder<KeyValue<#k, ValueMatcher<#v>>>]) -> (ValueMatcher<[KVReader<#k, #v> & KeyOrder<#k>]>)
+}
+
+define MapMatches {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<[KVReader<#k, #v> & KeyOrder<#k>]>
+
+  @value [SetReader<#k> & DefaultOrder<KeyValue<#k, ValueMatcher<#v>>>] expected
+
+  new (expected) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    traverse (expected.defaultOrder() -> KeyValue<#k, ValueMatcher<#v>> current) {
+      optional #v value <- actual&.get(current.getKey())
+      $Hidden[actual, expected]$
+      if (! `present` value) {
+        \ report.addError(message: String.builder()
+            .append("Key ")
+            .append(`Format:autoFormat` current.getKey())
+            .append(" is missing")
+            .build())
+      } else {
+        TestReport newReport <- report.newSection(title: String.builder()
+            .append("Key ")
+            .append(`Format:autoFormat` current.getKey())
+            .build())
+        $Hidden[report]$
+        \ require(value) `current.getValue().check` newReport
+      }
+    }
+    traverse (actual&.keyOrder() -> #k current) {
+      if (! `expected.member` current) {
+        \ report.addError(message: String.builder()
+            .append("Key ")
+            .append(`Format:autoFormat` current)
+            .append(" is unexpected")
+            .build())
+      }
+    }
+  }
+
+  summary () {
+    return "map matches"
+  }
+}
diff --git a/lib/container/src/type-map.0rx b/lib/container/src/type-map.0rx
--- a/lib/container/src/type-map.0rx
+++ b/lib/container/src/type-map.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -17,22 +17,22 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define TypeMap {
-  @value SortedMap<TypeKey<any>,GenericValue<any>> map
+  @value SortedMap<TypeKey<any>, GenericValue<any>> map
 
   default () {
     return new()
   }
 
   new () {
-    return TypeMap{ SortedMap<TypeKey<any>,GenericValue<any>>.new() }
+    return TypeMap{ SortedMap<TypeKey<any>, GenericValue<any>>.new() }
   }
 
   duplicate () {
     return #self{ map.duplicate() }
   }
 
-  set (k,v) {
-    \ map.set(k,GenericValue:create(v))
+  set (k, v) {
+    \ map.set(k, GenericValue:create(v))
     return self
   }
 
@@ -52,7 +52,7 @@
   }
 
   getAll (output) {
-    traverse (map.defaultOrder() -> KeyValue<any,GenericValue<any>> pair) {
+    traverse (map.defaultOrder() -> KeyValue<any, GenericValue<any>> pair) {
       scoped {
         optional #x value <- pair.getValue().check<#x>()
       } in if (present(value)) {
@@ -64,44 +64,29 @@
 }
 
 define TypeKey {
-  $ReadOnlyExcept[counter]$
-
-  @category Mutex counterMutex <- SpinlockMutex.new()
-  @category Int   counter      <- 0
-  @value Int index
-
   new () {
-    scoped {
-      \ counterMutex.lock()
-    } cleanup {
-      \ counterMutex.unlock()
-    } in return TypeKey<#x>{ (counter <- counter+1) }
+    return #self{ }
   }
 
   formatted () {
     return String.builder()
         .append(typename<#self>())
         .append("{")
-        .append(index)
+        .append( `identify` self)
         .append("}")
         .build()
   }
 
   hashed () {
-    return index.hashed()
-  }
-
-  lessThan (l,r) {
-    return l.get() < r.get()
+    return identify(self).hashed()
   }
 
-  equals (l,r) {
-    return l.get() == r.get()
+  lessThan (l, r) {
+    return identify(l) < identify(r)
   }
 
-  @value get () -> (Int)
-  get () {
-    return index
+  equals (l, r) {
+    return identify(l) == identify(r)
   }
 }
 
@@ -116,10 +101,10 @@
   @value #x value
 
   create (v) {
-    return GenericValue<#x>{ v }
+    return delegate -> GenericValue<#x>
   }
 
   check () {
-    return reduce<#x,#y>(value)
+    return reduce<#x, #y>(value)
   }
 }
diff --git a/lib/container/src/util.0rp b/lib/container/src/util.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/src/util.0rp
@@ -0,0 +1,32 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$ModuleOnly$
+
+concrete Format {
+  @category autoFormat<#x>
+    #x requires Formatted
+  (optional #x) -> (Formatted)
+}
+
+concrete TestValue {
+  immutable
+  refines TestCompare<TestValue>
+
+  @type new (String) -> (TestValue)
+}
diff --git a/lib/container/src/util.0rx b/lib/container/src/util.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/src/util.0rx
@@ -0,0 +1,54 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 Format {
+  autoFormat (value) {
+    if (`present` value) {
+      return String.builder()
+          .append("\"")
+          .append(`require` value)
+          .append("\"")
+          .build()
+    } else {
+      return "empty"
+    }
+  }
+}
+
+define TestValue {
+  @value String message
+
+  new (message) {
+    return delegate -> #self
+  }
+
+  testCompare (actual, report) {
+    if (actual.message() != message) {
+      \ report.addError(message: String.builder()
+          .append(`Format:autoFormat` actual.message())
+          .append(" does not equal ")
+          .append(`Format:autoFormat` message)
+          .build())
+    }
+  }
+
+  @value message () -> (String)
+  message () {
+    return message
+  }
+}
diff --git a/lib/container/src/validated-tree.0rp b/lib/container/src/validated-tree.0rp
--- a/lib/container/src/validated-tree.0rp
+++ b/lib/container/src/validated-tree.0rp
@@ -26,8 +26,8 @@
 //   therefore only be used in tests.
 concrete ValidatedTree<#k|#v|> {
   refines Container
-  refines KVWriter<#k,#v>
-  refines KVReader<#k,#v>
+  refines KVWriter<#k, #v>
+  refines KVReader<#k, #v>
   #k immutable
   #k defines LessThan<#k>
 
diff --git a/lib/container/src/wrappers.0rp b/lib/container/src/wrappers.0rp
--- a/lib/container/src/wrappers.0rp
+++ b/lib/container/src/wrappers.0rp
@@ -18,14 +18,14 @@
 
 $ModuleOnly$
 
-concrete SimpleKeyValue<|#k,#v> {
-  @category new<#k,#v> (#k,#v) -> (KeyValue<#k,#v>)
+concrete SimpleKeyValue<|#k, #v> {
+  @category new<#k, #v> (#k, #v) -> (KeyValue<#k, #v>)
 }
 
 concrete MapKeyOrder<#k> {
-  @category new<#k> (optional Order<KeyValue<#k,any>>) -> (optional Order<#k>)
+  @category new<#k> (optional Order<KeyValue<#k, any>>) -> (optional Order<#k>)
 }
 
 concrete MapValueOrder<#v> {
-  @category new<#v> (optional Order<KeyValue<any,#v>>) -> (optional Order<#v>)
+  @category new<#v> (optional Order<KeyValue<any, #v>>) -> (optional Order<#v>)
 }
diff --git a/lib/container/src/wrappers.0rx b/lib/container/src/wrappers.0rx
--- a/lib/container/src/wrappers.0rx
+++ b/lib/container/src/wrappers.0rx
@@ -19,13 +19,13 @@
 define SimpleKeyValue {
   $ReadOnlyExcept[]$
 
-  refines KeyValue<#k,#v>
+  refines KeyValue<#k, #v>
 
   @value #k key
   @value #v value
 
-  new (key,value) {
-    return SimpleKeyValue<#k,#v>{ key, value }
+  new (key, value) {
+    return delegate -> SimpleKeyValue<#k, #v>
   }
 
   getKey () {
@@ -40,7 +40,7 @@
 define MapKeyOrder {
   refines Order<#k>
 
-  @value Order<KeyValue<#k,any>> order
+  @value Order<KeyValue<#k, any>> order
 
   new (order) {
     if (!present(order)) {
@@ -52,7 +52,7 @@
 
   next () {
     scoped {
-      optional Order<KeyValue<#k,any>> order2 <- order.next()
+      optional Order<KeyValue<#k, any>> order2 <- order.next()
     } in if (present(order2)) {
       order <- require(order2)
       return self
@@ -69,7 +69,7 @@
 define MapValueOrder {
   refines Order<#v>
 
-  @value Order<KeyValue<any,#v>> order
+  @value Order<KeyValue<any, #v>> order
 
   new (order) {
     if (!present(order)) {
@@ -81,7 +81,7 @@
 
   next () {
     scoped {
-      optional Order<KeyValue<any,#v>> order2 <- order.next()
+      optional Order<KeyValue<any, #v>> order2 <- order.next()
     } in if (present(order2)) {
       order <- require(order2)
       return self
diff --git a/lib/container/test/hashed-map.0rt b/lib/container/test/hashed-map.0rt
--- a/lib/container/test/hashed-map.0rt
+++ b/lib/container/test/hashed-map.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,19 +17,19 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "HashedMap tests" {
-  success
+  success TestChecker
 }
 
 unittest integrationTest {
-  HashedMap<Int,Int> map <- HashedMap<Int,Int>.default()
+  HashedMap<Int, Int> map <- HashedMap<Int, Int>.default()
   Int count <- 33
   $ReadOnly[count]$
 
   // Insert values.
   traverse (Counter.zeroIndexed(count) -> Int i) {
     Int new <- ((i + 13) * 3547) % count
-    \ map.set(new,i)
-    \ Testing.checkEquals(map.size(),i+1)
+    \ map.set(new, i)
+    \ map.size() `Matches:with` CheckValue:equals(i+1)
   }
 
   // Check and remove values.
@@ -55,21 +55,21 @@
           .flush()
     }
     \ map.remove(new)
-    \ Testing.checkEquals(map.size(),count-i-1)
+    \ map.size() `Matches:with` CheckValue:equals(count-i-1)
   }
 }
 
 unittest removeNotPresent {
-  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new().set(1,1).set(2,2).set(4,4)
+  HashedMap<Int, Int> map <- HashedMap<Int, Int>.new().set(1, 1).set(2, 2).set(4, 4)
   \ map.remove(3)
-  \ Testing.checkEquals(map.size(),3)
+  \ map.size() `Matches:with` CheckValue:equals(3)
 }
 
 unittest defaultOrder {
-  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new()
+  HashedMap<Int, Int> map <- HashedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -80,27 +80,27 @@
 
   // Validate traversal coverage.
   Int index <- 0
-  traverse (map.defaultOrder() -> KeyValue<Int,Int> entry) {
-    \ Testing.checkFalse(visited.readAt(entry.getKey()))
-    \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
-    \ visited.writeAt(entry.getKey(),true)
+  traverse (map.defaultOrder() -> KeyValue<Int, Int> entry) {
+    \ visited.readAt(entry.getKey()) `Matches:with` CheckValue:equals(false)
+    \ (entry.getValue()*hash)%max `Matches:with` CheckValue:equals(entry.getKey())
+    \ visited.writeAt(entry.getKey(), true)
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,max)
+  \ index `Matches:with` CheckValue:equals(max)
 }
 
 unittest defaultOrderEmpty {
-  traverse (HashedMap<Int,Int>.new().defaultOrder() -> _) {
+  traverse (HashedMap<Int, Int>.new().defaultOrder() -> _) {
     fail("not empty")
   }
 }
 
 unittest keyOrder {
-  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new()
+  HashedMap<Int, Int> map <- HashedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -112,19 +112,19 @@
   // Validate traversal coverage.
   Int index <- 0
   traverse (map.keyOrder() -> Int key) {
-    \ Testing.checkFalse(visited.readAt(key))
-    \ visited.writeAt(key,true)
+    \ visited.readAt(key) `Matches:with` CheckValue:equals(false)
+    \ visited.writeAt(key, true)
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,max)
+  \ index `Matches:with` CheckValue:equals(max)
 }
 
 unittest valueOrder {
-  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new()
+  HashedMap<Int, Int> map <- HashedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -136,83 +136,92 @@
   // Validate traversal coverage.
   Int index <- 0
   traverse (map.valueOrder() -> Int value) {
-    \ Testing.checkFalse(visited.readAt(value))
-    \ visited.writeAt(value,true)
+    \ visited.readAt(value) `Matches:with` CheckValue:equals(false)
+    \ visited.writeAt(value, true)
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,max)
+  \ index `Matches:with` CheckValue:equals(max)
 }
 
 unittest duplicate {
-  HashedMap<Int,Int> map <- HashedMap<Int,Int>.default()
+  HashedMap<Int, Int> map <- HashedMap<Int, Int>.default()
   Int max <- 31
   $ReadOnly[max]$
 
   traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ map.set(i,i)
+    \ map.set(i, i)
   }
 
-  HashedMap<Int,Int> copy <- map.duplicate()
-  \ Testing.checkEquals(copy.size(),map.size())
+  HashedMap<Int, Int> copy <- map.duplicate()
+  \ copy.size() `Matches:with` CheckValue:equals(map.size())
   traverse (Counter.zeroIndexed(max) -> Int i) {
     $Hidden[map]$
-    \ Testing.checkOptional(copy.get(i),i)
+    \ copy.get(i) `Matches:with` CheckValue:equals(i)
   }
 
-  \ copy.set(2,5)
+  \ copy.set(2, 5)
   \ copy.remove(7)
   $Hidden[copy]$
 
-  \ Testing.checkEquals(map.size(),max)
+  \ map.size() `Matches:with` CheckValue:equals(max)
   traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ Testing.checkOptional(map.get(i),i)
+    \ map.get(i) `Matches:with` CheckValue:equals(i)
   }
 }
 
 unittest duplicateEmpty {
-  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new()
-  HashedMap<Int,Int> copy <- map.duplicate()
-  \ map.set(1,1)
-  \ Testing.checkEquals(copy.size(),0)
+  HashedMap<Int, Int> map <- HashedMap<Int, Int>.new()
+  HashedMap<Int, Int> copy <- map.duplicate()
+  \ map.set(1, 1)
+  \ copy.size() `Matches:with` CheckValue:equals(0)
 }
 
 unittest weakSetExists {
-  HashedMap<Int,Value> map <- HashedMap<Int,Value>.default()
-      .set(1,Value.new(1))
-      .set(2,Value.new(2))
-      .set(3,Value.new(3))
+  HashedMap<Int, Value> map <- HashedMap<Int, Value>.default()
+      .set(1, Value.new(1))
+      .set(2, Value.new(2))
+      .set(3, Value.new(3))
 
   Value value0 <- Value.new(5)
-  Value value1 <- map.weakSet(1,value0)
-  \ Testing.checkEquals(map.size(),3)
+  Value value1 <- map.weakSet(1, value0)
+  \ map.size() `Matches:with` CheckValue:equals(3)
 
-  \ Testing.checkEquals(value0.get(),5)
-  \ Testing.checkEquals(value1.get(),1)
+  \ value0.get() `Matches:with` CheckValue:equals(5)
+  \ value1.get() `Matches:with` CheckValue:equals(1)
 
   \ value0.set(10)
-  \ Testing.checkEquals(value1.get(),1)
-  \ Testing.checkOptional(map.get(1),Value.new(1))
+  \ value1.get() `Matches:with` CheckValue:equals(1)
+  \ map.get(1) `Matches:with` CheckValue:equals(Value.new(1))
 }
 
 unittest weakSetMissing {
-  HashedMap<Int,Value> map <- HashedMap<Int,Value>.default()
-      .set(1,Value.new(1))
-      .set(2,Value.new(2))
-      .set(3,Value.new(3))
+  HashedMap<Int, Value> map <- HashedMap<Int, Value>.default()
+      .set(1, Value.new(1))
+      .set(2, Value.new(2))
+      .set(3, Value.new(3))
 
   Value value0 <- Value.new(5)
-  Value value1 <- map.weakSet(7,value0)
-  \ Testing.checkEquals(map.size(),4)
+  Value value1 <- map.weakSet(7, value0)
+  \ map.size() `Matches:with` CheckValue:equals(4)
 
-  \ Testing.checkEquals(value0.get(),5)
-  \ Testing.checkEquals(value1.get(),5)
+  \ value0.get() `Matches:with` CheckValue:equals(5)
+  \ value1.get() `Matches:with` CheckValue:equals(5)
 
   \ value0.set(10)
-  \ Testing.checkEquals(value1.get(),10)
-  \ Testing.checkOptional(map.get(7),Value.new(10))
+  \ value1.get() `Matches:with` CheckValue:equals(10)
+  \ map.get(7) `Matches:with` CheckValue:equals(Value.new(10))
 }
 
+unittest setMember {
+  HashedMap<Int, Value> map <- HashedMap<Int, Value>.default()
+      .set(1, Value.new(1))
+      .set(2, Value.new(2))
+      .set(3, Value.new(3))
+  \ map.member(1) `Matches:with` CheckValue:equals(true)
+  \ map.member(5) `Matches:with` CheckValue:equals(false)
+}
+
 concrete Value {
   defines Equals<Value>
   refines Formatted
@@ -233,7 +242,7 @@
     return value.formatted()
   }
 
-  equals (x,y) {
+  equals (x, y) {
     return x.get() == y.get()
   }
 }
diff --git a/lib/container/test/hashed-set.0rt b/lib/container/test/hashed-set.0rt
--- a/lib/container/test/hashed-set.0rt
+++ b/lib/container/test/hashed-set.0rt
@@ -17,19 +17,19 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "HashedSet tests" {
-  success
+  success TestChecker
 }
 
 unittest addAndRemove {
   HashedSet<Int> set <- HashedSet<Int>.default()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the set in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
     \ set.add((i*hash)%max)
-    \ Testing.checkEquals(set.size(),i+1)
+    \ set.size() `Matches:with` CheckValue:equals(i+1)
   }
 
   // Remove only the odd elements.
@@ -38,33 +38,33 @@
       // Remove it twice to ensure idempotence.
       \ set.remove(i).remove(i)
     }
-    \ Testing.checkEquals(set.size(),max-(i+1)/2)
+    \ set.size() `Matches:with` CheckValue:equals(max-(i+1)/2)
   }
 
   // Validate set membership.
   traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ Testing.checkEquals(set.member(i),i%2 == 0)
+    \ set.member(i) `Matches:with` CheckValue:equals(i%2 == 0)
   }
 }
 
 unittest append {
   HashedSet<Int> set <- HashedSet<Int>.new()
   \ set.append(3)
-  \ Testing.checkEquals(set.size(),1)
-  \ Testing.checkTrue(set.member(3))
+  \ set.size() `Matches:with` CheckValue:equals(1)
+  \ set.member(3) `Matches:with` CheckValue:equals(true)
 }
 
 unittest removeNotPresent {
   HashedSet<Int> set <- HashedSet<Int>.new().add(1).add(2).add(4)
   \ set.remove(3)
-  \ Testing.checkEquals(set.size(),3)
+  \ set.size() `Matches:with` CheckValue:equals(3)
 }
 
 unittest defaultOrder {
   HashedSet<Int> set <- HashedSet<Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the set in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -76,12 +76,12 @@
   // Validate traversal coverage.
   Int index <- 0
   traverse (set.defaultOrder() -> Int entry) {
-    \ Testing.checkFalse(visited.readAt(entry))
-    \ visited.writeAt(entry,true)
+    \ visited.readAt(entry) `Matches:with` CheckValue:equals(false)
+    \ visited.writeAt(entry, true)
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,max)
+  \ index `Matches:with` CheckValue:equals(max)
 }
 
 unittest defaultOrderEmpty {
@@ -94,12 +94,12 @@
   HashedSet<Int> set <- HashedSet<Int>.new().add(1).add(2)
 
   HashedSet<Int> copy <- set.duplicate()
-  \ Testing.checkEquals(copy.size(),2)
-  \ Testing.checkTrue(copy.member(1))
-  \ Testing.checkTrue(copy.member(1))
+  \ copy.size() `Matches:with` CheckValue:equals(2)
+  \ copy.member(1) `Matches:with` CheckValue:equals(true)
+  \ copy.member(1) `Matches:with` CheckValue:equals(true)
 
   \ copy.remove(2)
-  \ Testing.checkEquals(copy.size(),1)
-  \ Testing.checkTrue(copy.member(1))
-  \ Testing.checkTrue(set.member(2))
+  \ copy.size() `Matches:with` CheckValue:equals(1)
+  \ copy.member(1) `Matches:with` CheckValue:equals(true)
+  \ set.member(2) `Matches:with` CheckValue:equals(true)
 }
diff --git a/lib/container/test/helpers.0rt b/lib/container/test/helpers.0rt
--- a/lib/container/test/helpers.0rt
+++ b/lib/container/test/helpers.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,83 +17,83 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "comparator helper tests" {
-  success
+  success TestChecker
 }
 
 unittest keyValueHLessThan {
-  \ Testing.checkFalse(KV.new(1,"a") `KeyValueH:lessThan` KV.new(1,"a"))
-  \ Testing.checkTrue(KV.new(1,"a") `KeyValueH:lessThan` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(2,"a") `KeyValueH:lessThan` KV.new(1,"b"))
-  \ Testing.checkTrue(KV.new(1,"b") `KeyValueH:lessThan` KV.new(2,"a"))
+  \ (KV.new(1, "a") `KeyValueH:lessThan` KV.new(1, "a")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(1, "a") `KeyValueH:lessThan` KV.new(1, "b")) `Matches:with` CheckValue:equals(true)
+  \ (KV.new(2, "a") `KeyValueH:lessThan` KV.new(1, "b")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(1, "b") `KeyValueH:lessThan` KV.new(2, "a")) `Matches:with` CheckValue:equals(true)
 }
 
 unittest keyValueHLessThanWith {
-  \ Testing.checkFalse(KV.new(1,"a") `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` KV.new(1,"a"))
-  \ Testing.checkFalse(KV.new(1,"a") `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(2,"a") `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` KV.new(1,"b"))
-  \ Testing.checkTrue(KV.new(1,"b") `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` KV.new(2,"a"))
+  \ (KV.new(1, "a") `KeyValueH:lessThanWith<?, ?, Int, AlwaysEqual>` KV.new(1, "a")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(1, "a") `KeyValueH:lessThanWith<?, ?, Int, AlwaysEqual>` KV.new(1, "b")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(2, "a") `KeyValueH:lessThanWith<?, ?, Int, AlwaysEqual>` KV.new(1, "b")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(1, "b") `KeyValueH:lessThanWith<?, ?, Int, AlwaysEqual>` KV.new(2, "a")) `Matches:with` CheckValue:equals(true)
 
-  \ Testing.checkFalse(KV.new(1,"a") `KeyValueH:lessThanWith<?,?,AlwaysEqual,String>` KV.new(1,"a"))
-  \ Testing.checkTrue(KV.new(1,"a") `KeyValueH:lessThanWith<?,?,AlwaysEqual,String>` KV.new(1,"b"))
-  \ Testing.checkTrue(KV.new(2,"a") `KeyValueH:lessThanWith<?,?,AlwaysEqual,String>` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(1,"b") `KeyValueH:lessThanWith<?,?,AlwaysEqual,String>` KV.new(2,"a"))
+  \ (KV.new(1, "a") `KeyValueH:lessThanWith<?, ?, AlwaysEqual, String>` KV.new(1, "a")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(1, "a") `KeyValueH:lessThanWith<?, ?, AlwaysEqual, String>` KV.new(1, "b")) `Matches:with` CheckValue:equals(true)
+  \ (KV.new(2, "a") `KeyValueH:lessThanWith<?, ?, AlwaysEqual, String>` KV.new(1, "b")) `Matches:with` CheckValue:equals(true)
+  \ (KV.new(1, "b") `KeyValueH:lessThanWith<?, ?, AlwaysEqual, String>` KV.new(2, "a")) `Matches:with` CheckValue:equals(false)
 }
 
 unittest keyValueHEquals {
-  \ Testing.checkTrue(KV.new(1,"a") `KeyValueH:equals` KV.new(1,"a"))
-  \ Testing.checkFalse(KV.new(1,"a") `KeyValueH:equals` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(2,"a") `KeyValueH:equals` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(1,"b") `KeyValueH:equals` KV.new(2,"a"))
+  \ (KV.new(1, "a") `KeyValueH:equals` KV.new(1, "a")) `Matches:with` CheckValue:equals(true)
+  \ (KV.new(1, "a") `KeyValueH:equals` KV.new(1, "b")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(2, "a") `KeyValueH:equals` KV.new(1, "b")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(1, "b") `KeyValueH:equals` KV.new(2, "a")) `Matches:with` CheckValue:equals(false)
 }
 
 unittest keyValueHEqualsWith {
-  \ Testing.checkTrue(KV.new(1,"a") `KeyValueH:equalsWith<?,?,Int,AlwaysEqual>` KV.new(1,"a"))
-  \ Testing.checkTrue(KV.new(1,"a") `KeyValueH:equalsWith<?,?,Int,AlwaysEqual>` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(2,"a") `KeyValueH:equalsWith<?,?,Int,AlwaysEqual>` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(1,"b") `KeyValueH:equalsWith<?,?,Int,AlwaysEqual>` KV.new(2,"a"))
+  \ (KV.new(1, "a") `KeyValueH:equalsWith<?, ?, Int, AlwaysEqual>` KV.new(1, "a")) `Matches:with` CheckValue:equals(true)
+  \ (KV.new(1, "a") `KeyValueH:equalsWith<?, ?, Int, AlwaysEqual>` KV.new(1, "b")) `Matches:with` CheckValue:equals(true)
+  \ (KV.new(2, "a") `KeyValueH:equalsWith<?, ?, Int, AlwaysEqual>` KV.new(1, "b")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(1, "b") `KeyValueH:equalsWith<?, ?, Int, AlwaysEqual>` KV.new(2, "a")) `Matches:with` CheckValue:equals(false)
 
-  \ Testing.checkTrue(KV.new(1,"a") `KeyValueH:equalsWith<?,?,AlwaysEqual,String>` KV.new(1,"a"))
-  \ Testing.checkFalse(KV.new(1,"a") `KeyValueH:equalsWith<?,?,AlwaysEqual,String>` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(2,"a") `KeyValueH:equalsWith<?,?,AlwaysEqual,String>` KV.new(1,"b"))
-  \ Testing.checkFalse(KV.new(1,"b") `KeyValueH:equalsWith<?,?,AlwaysEqual,String>` KV.new(2,"a"))
+  \ (KV.new(1, "a") `KeyValueH:equalsWith<?, ?, AlwaysEqual, String>` KV.new(1, "a")) `Matches:with` CheckValue:equals(true)
+  \ (KV.new(1, "a") `KeyValueH:equalsWith<?, ?, AlwaysEqual, String>` KV.new(1, "b")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(2, "a") `KeyValueH:equalsWith<?, ?, AlwaysEqual, String>` KV.new(1, "b")) `Matches:with` CheckValue:equals(false)
+  \ (KV.new(1, "b") `KeyValueH:equalsWith<?, ?, AlwaysEqual, String>` KV.new(2, "a")) `Matches:with` CheckValue:equals(false)
 }
 
 unittest integrationTest {
-  SortedMap<Int,String> map1 <- SortedMap<Int,String>.new()
-      .set(1,"d").set(2,"c").set(3,"b").set(4,"a")
-  SortedMap<Int,String> map2 <- SortedMap<Int,String>.new()
-      .set(1,"a").set(2,"b").set(3,"c").set(4,"d")
+  SortedMap<Int, String> map1 <- SortedMap<Int, String>.new()
+      .set(1, "d").set(2, "c").set(3, "b").set(4, "a")
+  SortedMap<Int, String> map2 <- SortedMap<Int, String>.new()
+      .set(1, "a").set(2, "b").set(3, "c").set(4, "d")
 
-  \ Testing.checkTrue(map1.defaultOrder() `OrderH:equalsWith<?,KVEquals<Int,AlwaysEqual>>`    map2.defaultOrder())
-  \ Testing.checkFalse(map1.defaultOrder() `OrderH:equalsWith<?,KVEquals<Int,String>>`         map2.defaultOrder())
-  \ Testing.checkFalse(map1.defaultOrder() `OrderH:equalsWith<?,KVEquals<AlwaysEqual,String>>` map2.defaultOrder())
+  \ (map1.defaultOrder() `OrderH:equalsWith<?, KVEquals<Int, AlwaysEqual>>`    map2.defaultOrder()) `Matches:with` CheckValue:equals(true)
+  \ (map1.defaultOrder() `OrderH:equalsWith<?, KVEquals<Int, String>>`         map2.defaultOrder()) `Matches:with` CheckValue:equals(false)
+  \ (map1.defaultOrder() `OrderH:equalsWith<?, KVEquals<AlwaysEqual, String>>` map2.defaultOrder()) `Matches:with` CheckValue:equals(false)
 }
 
-concrete KVEquals<#k,#v> {
-  defines Equals<KeyValue<Int,String>>
+concrete KVEquals<#k, #v> {
+  defines Equals<KeyValue<Int, String>>
   #k defines Equals<Int>
   #v defines Equals<String>
 }
 
 define KVEquals {
-  equals (x,y) {
-    return x `KeyValueH:equalsWith<?,?,#k,#v>` y
+  equals (x, y) {
+    return x `KeyValueH:equalsWith<?, ?, #k, #v>` y
   }
 }
 
 concrete KV {
-  refines KeyValue<Int,String>
+  refines KeyValue<Int, String>
 
-  @type new (Int,String) -> (KV)
+  @type new (Int, String) -> (KV)
 }
 
 define KV {
-  $ReadOnly[key,value]$
+  $ReadOnly[key, value]$
 
   @value Int key
   @value String value
 
-  new (k,v) {
+  new (k, v) {
     return KV{ k, v }
   }
 
diff --git a/lib/container/test/list.0rt b/lib/container/test/list.0rt
--- a/lib/container/test/list.0rt
+++ b/lib/container/test/list.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "basic list tests" {
-  success
+  success TestChecker
   timeout 5
 }
 
@@ -40,11 +40,11 @@
       .append(4)
 
   traverse (expected.defaultOrder() -> Int value) {
-    \ Testing.checkEquals(require(head).get(),value)
+    \ require(head).get() `Matches:with` CheckValue:equals(value)
   } update {
     head <- require(head).next()
   }
-  \ Testing.checkFalse(present(head))
+  \ present(head) `Matches:with` CheckValue:equals(false)
 }
 
 unittest reverseTraverse {
@@ -67,11 +67,11 @@
       .append(3)
 
   traverse (expected.defaultOrder() -> Int value) {
-    \ Testing.checkEquals(require(tail).get(),value)
+    \ require(tail).get() `Matches:with` CheckValue:equals(value)
   } update {
     tail <- require(tail).prev()
   }
-  \ Testing.checkFalse(present(tail))
+  \ present(tail) `Matches:with` CheckValue:equals(false)
 }
 
 unittest setNext {
@@ -84,7 +84,7 @@
       .append(4)
       .build()
 
-  optional LinkedNode<Int> after1  <- Helper.jumpBy(3,head1)
+  optional LinkedNode<Int> after1  <- Helper.jumpBy(3, head1)
   optional LinkedNode<Int> before1 <- require(after1).prev()
 
   optional LinkedNode<Int> head2, _ <- LinkedNode<Int>.builder()
@@ -96,15 +96,15 @@
       .append(14)
       .build()
 
-  optional LinkedNode<Int> after2  <- Helper.jumpBy(3,head2)
+  optional LinkedNode<Int> after2  <- Helper.jumpBy(3, head2)
   optional LinkedNode<Int> before2 <- require(after2).prev()
 
-  \ Testing.checkEquals(require(require(before1).setNext(after2)).get(),1)
+  \ require(require(before1).setNext(after2)).get() `Matches:with` CheckValue:equals(1)
 
-  \ Testing.checkFalse(present(require(after1).prev()))
-  \ Testing.checkEquals(require(require(before1).next()).get(),11)
-  \ Testing.checkEquals(require(require(after2).prev()).get(),5)
-  \ Testing.checkFalse(present(require(before2).next()))
+  \ present(require(after1).prev()) `Matches:with` CheckValue:equals(false)
+  \ require(require(before1).next()).get() `Matches:with` CheckValue:equals(11)
+  \ require(require(after2).prev()).get() `Matches:with` CheckValue:equals(5)
+  \ present(require(before2).next()) `Matches:with` CheckValue:equals(false)
 }
 
 unittest setPrev {
@@ -117,7 +117,7 @@
       .append(4)
       .build()
 
-  optional LinkedNode<Int> after1  <- Helper.jumpBy(3,head1)
+  optional LinkedNode<Int> after1  <- Helper.jumpBy(3, head1)
   optional LinkedNode<Int> before1 <- require(after1).prev()
 
   optional LinkedNode<Int> head2, _ <- LinkedNode<Int>.builder()
@@ -129,15 +129,15 @@
       .append(14)
       .build()
 
-  optional LinkedNode<Int> after2  <- Helper.jumpBy(3,head2)
+  optional LinkedNode<Int> after2  <- Helper.jumpBy(3, head2)
   optional LinkedNode<Int> before2 <- require(after2).prev()
 
-  \ Testing.checkEquals(require(require(after1).setPrev(before2)).get(),5)
+  \ require(require(after1).setPrev(before2)).get() `Matches:with` CheckValue:equals(5)
 
-  \ Testing.checkEquals(require(require(after1).prev()).get(),15)
-  \ Testing.checkFalse(present(require(before1).next()))
-  \ Testing.checkFalse(present(require(after2).prev()))
-  \ Testing.checkEquals(require(require(before2).next()).get(),1)
+  \ require(require(after1).prev()).get() `Matches:with` CheckValue:equals(15)
+  \ present(require(before1).next()) `Matches:with` CheckValue:equals(false)
+  \ present(require(after2).prev()) `Matches:with` CheckValue:equals(false)
+  \ require(require(before2).next()).get() `Matches:with` CheckValue:equals(1)
 }
 
 unittest setNextSame {
@@ -150,13 +150,13 @@
       .append(4)
       .build()
 
-  optional LinkedNode<Int> after  <- Helper.jumpBy(3,head)
+  optional LinkedNode<Int> after  <- Helper.jumpBy(3, head)
   optional LinkedNode<Int> before <- require(after).prev()
 
   \ require(before).setNext(after)
 
-  \ Testing.checkEquals(require(require(after).prev()).get(),5)
-  \ Testing.checkEquals(require(require(before).next()).get(),1)
+  \ require(require(after).prev()).get() `Matches:with` CheckValue:equals(5)
+  \ require(require(before).next()).get() `Matches:with` CheckValue:equals(1)
 }
 
 unittest setPrevSame {
@@ -169,13 +169,13 @@
       .append(4)
       .build()
 
-  optional LinkedNode<Int> after  <- Helper.jumpBy(3,head)
+  optional LinkedNode<Int> after  <- Helper.jumpBy(3, head)
   optional LinkedNode<Int> before <- require(after).prev()
 
   \ require(after).setPrev(before)
 
-  \ Testing.checkEquals(require(require(after).prev()).get(),5)
-  \ Testing.checkEquals(require(require(before).next()).get(),1)
+  \ require(require(after).prev()).get() `Matches:with` CheckValue:equals(5)
+  \ require(require(before).next()).get() `Matches:with` CheckValue:equals(1)
 }
 
 unittest setNextSelf {
@@ -188,14 +188,14 @@
       .append(4)
       .build()
 
-  optional LinkedNode<Int> after  <- Helper.jumpBy(3,head)
+  optional LinkedNode<Int> after  <- Helper.jumpBy(3, head)
   optional LinkedNode<Int> before <- require(after).prev()
 
   \ require(before).setNext(before)
 
-  \ Testing.checkFalse(present(require(after).prev()))
-  \ Testing.checkEquals(require(require(before).prev()).get(),5)
-  \ Testing.checkEquals(require(require(before).next()).get(),5)
+  \ present(require(after).prev()) `Matches:with` CheckValue:equals(false)
+  \ require(require(before).prev()).get() `Matches:with` CheckValue:equals(5)
+  \ require(require(before).next()).get() `Matches:with` CheckValue:equals(5)
 }
 
 unittest setPrevSelf {
@@ -208,14 +208,14 @@
       .append(4)
       .build()
 
-  optional LinkedNode<Int> after  <- Helper.jumpBy(3,head)
+  optional LinkedNode<Int> after  <- Helper.jumpBy(3, head)
   optional LinkedNode<Int> before <- require(after).prev()
 
   \ require(after).setPrev(after)
 
-  \ Testing.checkFalse(present(require(before).next()))
-  \ Testing.checkEquals(require(require(after).prev()).get(),1)
-  \ Testing.checkEquals(require(require(after).next()).get(),1)
+  \ present(require(before).next()) `Matches:with` CheckValue:equals(false)
+  \ require(require(after).prev()).get() `Matches:with` CheckValue:equals(1)
+  \ require(require(after).next()).get() `Matches:with` CheckValue:equals(1)
 }
 
 unittest duplicate {
@@ -237,20 +237,20 @@
       .append(4)
 
   optional LinkedNode<Int> head2 <- require(head).duplicate()
-  \ require(Helper.jumpBy(3,head)).set(7).setNext(empty)
+  \ require(Helper.jumpBy(3, head)).set(7).setNext(empty)
   $Hidden[head]$
 
   traverse (expected.defaultOrder() -> Int value) {
-    \ Testing.checkEquals(require(head2).get(),value)
+    \ require(head2).get() `Matches:with` CheckValue:equals(value)
   } update {
     head2 <- require(head2).next()
   }
-  \ Testing.checkFalse(present(head2))
+  \ present(head2) `Matches:with` CheckValue:equals(false)
 }
 
 unittest hugeCleanup { $DisableCoverage$
   scoped {
-    ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
+    ListBuilder<Int, LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
   } in traverse (Counter.zeroIndexed(1000000) -> _) {
     \ builder.append(0)
   }
@@ -275,11 +275,11 @@
       .append(4)
 
   traverse (expected.defaultOrder() -> Int value) {
-    \ Testing.checkEquals(require(head).get(),value)
+    \ require(head).get() `Matches:with` CheckValue:equals(value)
   } update {
     head <- require(head).next()
   }
-  \ Testing.checkFalse(present(head))
+  \ present(head) `Matches:with` CheckValue:equals(false)
 }
 
 unittest duplicateSingle {
@@ -301,20 +301,20 @@
       .append(4)
 
   optional ForwardNode<Int> head2 <- require(head).duplicate()
-  \ require(Helper.jumpBy(3,head)).set(7).setNext(empty)
+  \ require(Helper.jumpBy(3, head)).set(7).setNext(empty)
   $Hidden[head]$
 
   traverse (expected.defaultOrder() -> Int value) {
-    \ Testing.checkEquals(require(head2).get(),value)
+    \ require(head2).get() `Matches:with` CheckValue:equals(value)
   } update {
     head2 <- require(head2).next()
   }
-  \ Testing.checkFalse(present(head2))
+  \ present(head2) `Matches:with` CheckValue:equals(false)
 }
 
 unittest hugeCleanupSingle { $DisableCoverage$
   scoped {
-    ListBuilder<Int,ForwardNode<Int>> builder <- ForwardNode<Int>.builder()
+    ListBuilder<Int, ForwardNode<Int>> builder <- ForwardNode<Int>.builder()
   } in traverse (Counter.zeroIndexed(1000000) -> _) {
     \ builder.append(0)
   }
@@ -323,11 +323,11 @@
 concrete Helper {
   @type jumpBy<#x>
     #x requires Order<any>
-  (Int,optional #x) -> (optional #x)
+  (Int, optional #x) -> (optional #x)
 }
 
 define Helper {
-  jumpBy (n,x) (y) {
+  jumpBy (n, x) (y) {
     y <- x
     traverse (Counter.zeroIndexed(n) -> _) {
       y <- require(y).next()
diff --git a/lib/container/test/queue.0rt b/lib/container/test/queue.0rt
--- a/lib/container/test/queue.0rt
+++ b/lib/container/test/queue.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "SimpleQueue tests" {
-  success
+  success TestChecker
 }
 
 unittest integrationTest {
@@ -25,14 +25,14 @@
   $ReadOnly[max]$
   SimpleQueue<Int> queue <- SimpleQueue<Int>.default()
   traverse (`Counter.zeroIndexed` max -> Int value) {
-    \ Testing.checkEquals(queue.size(),value)
+    \ queue.size() `Matches:with` CheckValue:equals(value)
     \ queue.append(value)
   }
   traverse (`Counter.zeroIndexed` max -> Int value) {
-    \ Testing.checkEquals(queue.size(),max-value)
-    \ Testing.checkEquals(queue.pop(),value)
+    \ queue.size() `Matches:with` CheckValue:equals(max-value)
+    \ queue.pop() `Matches:with` CheckValue:equals(value)
   }
-  \ Testing.checkEquals(queue.size(),0)
+  \ queue.size() `Matches:with` CheckValue:equals(0)
 }
 
 unittest duplicate {
@@ -45,8 +45,8 @@
   \ queue.pop()
   $Hidden[queue]$
   traverse (`Counter.zeroIndexed` max -> Int value) {
-    \ Testing.checkEquals(copy.size(),max-value)
-    \ Testing.checkEquals(copy.pop(),value)
+    \ copy.size() `Matches:with` CheckValue:equals(max-value)
+    \ copy.pop() `Matches:with` CheckValue:equals(value)
   }
-  \ Testing.checkEquals(copy.size(),0)
+  \ copy.size() `Matches:with` CheckValue:equals(0)
 }
diff --git a/lib/container/test/sorted-map.0rt b/lib/container/test/sorted-map.0rt
--- a/lib/container/test/sorted-map.0rt
+++ b/lib/container/test/sorted-map.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -17,19 +17,19 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "SortedMap tests" {
-  success
+  success TestChecker
 }
 
 unittest integrationTest {
-  ValidatedTree<Int,Int> map <- ValidatedTree<Int,Int>.new()
+  ValidatedTree<Int, Int> map <- ValidatedTree<Int, Int>.new()
   Int count <- 33
   $ReadOnly[count]$
 
   // Insert values.
   traverse (Counter.zeroIndexed(count) -> Int i) {
     Int new <- ((i + 13) * 3547) % count
-    \ map.set(new,i)
-    \ Testing.checkEquals(map.size(),i+1)
+    \ map.set(new, i)
+    \ map.size() `Matches:with` CheckValue:equals(i+1)
   }
 
   // Check and remove values.
@@ -55,21 +55,21 @@
           .flush()
     }
     \ map.remove(new)
-    \ Testing.checkEquals(map.size(),count-i-1)
+    \ map.size() `Matches:with` CheckValue:equals(count-i-1)
   }
 }
 
 unittest removeNotPresent {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new().set(1,1).set(2,2).set(4,4)
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new().set(1, 1).set(2, 2).set(4, 4)
   \ map.remove(3)
-  \ Testing.checkEquals(map.size(),3)
+  \ map.size() `Matches:with` CheckValue:equals(3)
 }
 
 unittest defaultOrder {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -78,26 +78,26 @@
 
   // Validate the traversal order.
   Int index <- 0
-  traverse (map.defaultOrder() -> KeyValue<Int,Int> entry) {
-    \ Testing.checkEquals(entry.getKey(),index)
-    \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+  traverse (map.defaultOrder() -> KeyValue<Int, Int> entry) {
+    \ entry.getKey() `Matches:with` CheckValue:equals(index)
+    \ (entry.getValue()*hash)%max `Matches:with` CheckValue:equals(entry.getKey())
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,max)
+  \ index `Matches:with` CheckValue:equals(max)
 }
 
 unittest defaultOrderEmpty {
-  traverse (SortedMap<Int,Int>.new().defaultOrder() -> _) {
+  traverse (SortedMap<Int, Int>.new().defaultOrder() -> _) {
     fail("not empty")
   }
 }
 
 unittest keyOrder {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -107,18 +107,18 @@
   // Validate the traversal order.
   Int index <- 0
   traverse (map.keyOrder() -> Int key) {
-    \ Testing.checkEquals(key,index)
+    \ key `Matches:with` CheckValue:equals(index)
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,max)
+  \ index `Matches:with` CheckValue:equals(max)
 }
 
 unittest valueOrder {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -128,18 +128,18 @@
   // Validate the traversal order.
   Int index <- 0
   traverse (map.valueOrder() -> Int value) {
-    \ Testing.checkEquals((value*hash)%max,index)
+    \ (value*hash)%max `Matches:with` CheckValue:equals(index)
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,max)
+  \ index `Matches:with` CheckValue:equals(max)
 }
 
 unittest reverseOrder {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -148,20 +148,20 @@
 
   // Validate the traversal order.
   Int index <- max
-  traverse (map.reverseOrder() -> KeyValue<Int,Int> entry) {
+  traverse (map.reverseOrder() -> KeyValue<Int, Int> entry) {
     index <- index-1
-    \ Testing.checkEquals(entry.getKey(),index)
-    \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+    \ entry.getKey() `Matches:with` CheckValue:equals(index)
+    \ (entry.getValue()*hash)%max `Matches:with` CheckValue:equals(entry.getKey())
   }
 
-  \ Testing.checkEquals(index,0)
+  \ index `Matches:with` CheckValue:equals(0)
 }
 
 unittest getForward {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -171,39 +171,39 @@
   // Validate the traversal order from every starting point.
   traverse (Counter.zeroIndexed(max) -> Int i) {
     Int index <- i
-    traverse (map.getForward(index) -> KeyValue<Int,Int> entry) {
-      \ Testing.checkEquals(entry.getKey(),index)
-      \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+    traverse (map.getForward(index) -> KeyValue<Int, Int> entry) {
+      \ entry.getKey() `Matches:with` CheckValue:equals(index)
+      \ (entry.getValue()*hash)%max `Matches:with` CheckValue:equals(entry.getKey())
       index <- index+1
     }
-    \ Testing.checkEquals(index,max)
+    \ index `Matches:with` CheckValue:equals(max)
   }
 }
 
 unittest getForwardNotFound {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
-  \ map.set(1,1).set(3,3).set(5,5)
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
+  \ map.set(1, 1).set(3, 3).set(5, 5)
 
   scoped {
-    optional Order<KeyValue<Int,Int>> start <- map.getForward(4)
+    optional Order<KeyValue<Int, Int>> start <- map.getForward(4)
   } in if (!present(start)) {
     fail("Failed")
   } else {
-    \ Testing.checkEquals(require(start).get().getKey(),5)
+    \ require(start).get().getKey() `Matches:with` CheckValue:equals(5)
   }
 
   scoped {
-    optional Order<KeyValue<Int,Int>> start <- map.getForward(6)
+    optional Order<KeyValue<Int, Int>> start <- map.getForward(6)
   } in if (present(start)) {
     fail("Failed")
   }
 }
 
 unittest getReverse {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the map in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -213,103 +213,112 @@
   // Validate the traversal order from every starting point.
   traverse (Counter.zeroIndexed(max) -> Int i) {
     Int index <- i
-    traverse (map.getReverse(index) -> KeyValue<Int,Int> entry) {
-      \ Testing.checkEquals(entry.getKey(),index)
-      \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+    traverse (map.getReverse(index) -> KeyValue<Int, Int> entry) {
+      \ entry.getKey() `Matches:with` CheckValue:equals(index)
+      \ (entry.getValue()*hash)%max `Matches:with` CheckValue:equals(entry.getKey())
       index <- index-1
     }
-    \ Testing.checkEquals(index,-1)
+    \ index `Matches:with` CheckValue:equals(-1)
   }
 }
 
 unittest getReverseNotFound {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
-  \ map.set(1,1).set(3,3).set(5,5)
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
+  \ map.set(1, 1).set(3, 3).set(5, 5)
 
   scoped {
-    optional Order<KeyValue<Int,Int>> start <- map.getReverse(2)
+    optional Order<KeyValue<Int, Int>> start <- map.getReverse(2)
   } in if (!present(start)) {
     fail("Failed")
   } else {
-    \ Testing.checkEquals(require(start).get().getKey(),1)
+    \ require(start).get().getKey() `Matches:with` CheckValue:equals(1)
   }
 
   scoped {
-    optional Order<KeyValue<Int,Int>> start <- map.getReverse(0)
+    optional Order<KeyValue<Int, Int>> start <- map.getReverse(0)
   } in if (present(start)) {
     fail("Failed")
   }
 }
 
 unittest duplicate {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.default()
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.default()
   Int max <- 31
   $ReadOnly[max]$
 
   traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ map.set(i,i)
+    \ map.set(i, i)
   }
 
-  SortedMap<Int,Int> copy <- map.duplicate()
-  \ Testing.checkEquals(copy.size(),map.size())
+  SortedMap<Int, Int> copy <- map.duplicate()
+  \ copy.size() `Matches:with` CheckValue:equals(map.size())
   traverse (Counter.zeroIndexed(max) -> Int i) {
     $Hidden[map]$
-    \ Testing.checkOptional(copy.get(i),i)
+    \ copy.get(i) `Matches:with` CheckValue:equals(i)
   }
 
-  \ copy.set(2,5)
+  \ copy.set(2, 5)
   \ copy.remove(7)
   $Hidden[copy]$
 
-  \ Testing.checkEquals(map.size(),max)
+  \ map.size() `Matches:with` CheckValue:equals(max)
   traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ Testing.checkOptional(map.get(i),i)
+    \ map.get(i) `Matches:with` CheckValue:equals(i)
   }
 }
 
 unittest duplicateEmpty {
-  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
-  SortedMap<Int,Int> copy <- map.duplicate()
-  \ map.set(1,1)
-  \ Testing.checkEquals(copy.size(),0)
+  SortedMap<Int, Int> map <- SortedMap<Int, Int>.new()
+  SortedMap<Int, Int> copy <- map.duplicate()
+  \ map.set(1, 1)
+  \ copy.size() `Matches:with` CheckValue:equals(0)
 }
 
 unittest weakSetExists {
-  SortedMap<Int,Value> map <- SortedMap<Int,Value>.default()
-      .set(1,Value.new(1))
-      .set(2,Value.new(2))
-      .set(3,Value.new(3))
+  SortedMap<Int, Value> map <- SortedMap<Int, Value>.default()
+      .set(1, Value.new(1))
+      .set(2, Value.new(2))
+      .set(3, Value.new(3))
 
   Value value0 <- Value.new(5)
-  Value value1 <- map.weakSet(1,value0)
-  \ Testing.checkEquals(map.size(),3)
+  Value value1 <- map.weakSet(1, value0)
+  \ map.size() `Matches:with` CheckValue:equals(3)
 
-  \ Testing.checkEquals(value0.get(),5)
-  \ Testing.checkEquals(value1.get(),1)
+  \ value0.get() `Matches:with` CheckValue:equals(5)
+  \ value1.get() `Matches:with` CheckValue:equals(1)
 
   \ value0.set(10)
-  \ Testing.checkEquals(value1.get(),1)
-  \ Testing.checkOptional(map.get(1),Value.new(1))
+  \ value1.get() `Matches:with` CheckValue:equals(1)
+  \ map.get(1) `Matches:with` CheckValue:equals(Value.new(1))
 }
 
 unittest weakSetMissing {
-  SortedMap<Int,Value> map <- SortedMap<Int,Value>.default()
-      .set(1,Value.new(1))
-      .set(2,Value.new(2))
-      .set(3,Value.new(3))
+  SortedMap<Int, Value> map <- SortedMap<Int, Value>.default()
+      .set(1, Value.new(1))
+      .set(2, Value.new(2))
+      .set(3, Value.new(3))
 
   Value value0 <- Value.new(5)
-  Value value1 <- map.weakSet(7,value0)
-  \ Testing.checkEquals(map.size(),4)
+  Value value1 <- map.weakSet(7, value0)
+  \ map.size() `Matches:with` CheckValue:equals(4)
 
-  \ Testing.checkEquals(value0.get(),5)
-  \ Testing.checkEquals(value1.get(),5)
+  \ value0.get() `Matches:with` CheckValue:equals(5)
+  \ value1.get() `Matches:with` CheckValue:equals(5)
 
   \ value0.set(10)
-  \ Testing.checkEquals(value1.get(),10)
-  \ Testing.checkOptional(map.get(7),Value.new(10))
+  \ value1.get() `Matches:with` CheckValue:equals(10)
+  \ map.get(7) `Matches:with` CheckValue:equals(Value.new(10))
 }
 
+unittest setMember {
+  SortedMap<Int, Value> map <- SortedMap<Int, Value>.default()
+      .set(1, Value.new(1))
+      .set(2, Value.new(2))
+      .set(3, Value.new(3))
+  \ map.member(1) `Matches:with` CheckValue:equals(true)
+  \ map.member(5) `Matches:with` CheckValue:equals(false)
+}
+
 concrete Value {
   defines Equals<Value>
   refines Formatted
@@ -330,7 +339,7 @@
     return value.formatted()
   }
 
-  equals (x,y) {
+  equals (x, y) {
     return x.get() == y.get()
   }
 }
diff --git a/lib/container/test/sorted-set.0rt b/lib/container/test/sorted-set.0rt
--- a/lib/container/test/sorted-set.0rt
+++ b/lib/container/test/sorted-set.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,19 +17,19 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "SortedSet tests" {
-  success
+  success TestChecker
 }
 
 unittest addAndRemove {
   SortedSet<Int> set <- SortedSet<Int>.default()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the set in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
     \ set.add((i*hash)%max)
-    \ Testing.checkEquals(set.size(),i+1)
+    \ set.size() `Matches:with` CheckValue:equals(i+1)
   }
 
   // Remove only the odd elements.
@@ -38,33 +38,33 @@
       // Remove it twice to ensure idempotence.
       \ set.remove(i).remove(i)
     }
-    \ Testing.checkEquals(set.size(),max-(i+1)/2)
+    \ set.size() `Matches:with` CheckValue:equals(max-(i+1)/2)
   }
 
   // Validate set membership.
   traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ Testing.checkEquals(set.member(i),i%2 == 0)
+    \ set.member(i) `Matches:with` CheckValue:equals(i%2 == 0)
   }
 }
 
 unittest append {
   SortedSet<Int> set <- SortedSet<Int>.new()
   \ set.append(3)
-  \ Testing.checkEquals(set.size(),1)
-  \ Testing.checkTrue(set.member(3))
+  \ set.size() `Matches:with` CheckValue:equals(1)
+  \ set.member(3) `Matches:with` CheckValue:equals(true)
 }
 
 unittest removeNotPresent {
   SortedSet<Int> set <- SortedSet<Int>.new().add(1).add(2).add(4)
   \ set.remove(3)
-  \ Testing.checkEquals(set.size(),3)
+  \ set.size() `Matches:with` CheckValue:equals(3)
 }
 
 unittest defaultOrder {
   SortedSet<Int> set <- SortedSet<Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the set in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -74,11 +74,11 @@
   // Validate the traversal order.
   Int index <- 0
   traverse (set.defaultOrder() -> Int entry) {
-    \ Testing.checkEquals(entry,index)
+    \ entry `Matches:with` CheckValue:equals(index)
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,max)
+  \ index `Matches:with` CheckValue:equals(max)
 }
 
 unittest defaultOrderEmpty {
@@ -91,7 +91,7 @@
   SortedSet<Int> set <- SortedSet<Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the set in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -102,17 +102,17 @@
   Int index <- max
   traverse (set.reverseOrder() -> Int entry) {
     index <- index-1
-    \ Testing.checkEquals(entry,index)
+    \ entry `Matches:with` CheckValue:equals(index)
   }
 
-  \ Testing.checkEquals(index,0)
+  \ index `Matches:with` CheckValue:equals(0)
 }
 
 unittest getForward {
   SortedSet<Int> set <- SortedSet<Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the set in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -123,10 +123,10 @@
   traverse (Counter.zeroIndexed(max) -> Int i) {
     Int index <- i
     traverse (set.getForward(index) -> Int entry) {
-      \ Testing.checkEquals(entry,index)
+      \ entry `Matches:with` CheckValue:equals(index)
       index <- index+1
     }
-    \ Testing.checkEquals(index,max)
+    \ index `Matches:with` CheckValue:equals(max)
   }
 }
 
@@ -139,7 +139,7 @@
   } in if (!present(start)) {
     fail("Failed")
   } else {
-    \ Testing.checkEquals(require(start).get(),5)
+    \ require(start).get() `Matches:with` CheckValue:equals(5)
   }
 
   scoped {
@@ -153,7 +153,7 @@
   SortedSet<Int> set <- SortedSet<Int>.new()
   Int hash <- 13
   Int max  <- 20
-  $ReadOnly[hash,max]$
+  $ReadOnly[hash, max]$
 
   // Populate the set in a pseudo-random order.
   traverse (Counter.zeroIndexed(max) -> Int i) {
@@ -164,10 +164,10 @@
   traverse (Counter.zeroIndexed(max) -> Int i) {
     Int index <- i
     traverse (set.getReverse(index) -> Int entry) {
-      \ Testing.checkEquals(entry,index)
+      \ entry `Matches:with` CheckValue:equals(index)
       index <- index-1
     }
-    \ Testing.checkEquals(index,-1)
+    \ index `Matches:with` CheckValue:equals(-1)
   }
 }
 
@@ -180,7 +180,7 @@
   } in if (!present(start)) {
     fail("Failed")
   } else {
-    \ Testing.checkEquals(require(start).get(),1)
+    \ require(start).get() `Matches:with` CheckValue:equals(1)
   }
 
   scoped {
@@ -194,12 +194,12 @@
   SortedSet<Int> set <- SortedSet<Int>.new().add(1).add(2)
 
   SortedSet<Int> copy <- set.duplicate()
-  \ Testing.checkEquals(copy.size(),2)
-  \ Testing.checkTrue(copy.member(1))
-  \ Testing.checkTrue(copy.member(1))
+  \ copy.size() `Matches:with` CheckValue:equals(2)
+  \ copy.member(1) `Matches:with` CheckValue:equals(true)
+  \ copy.member(1) `Matches:with` CheckValue:equals(true)
 
   \ copy.remove(2)
-  \ Testing.checkEquals(copy.size(),1)
-  \ Testing.checkTrue(copy.member(1))
-  \ Testing.checkTrue(set.member(2))
+  \ copy.size() `Matches:with` CheckValue:equals(1)
+  \ copy.member(1) `Matches:with` CheckValue:equals(true)
+  \ set.member(2) `Matches:with` CheckValue:equals(true)
 }
diff --git a/lib/container/test/sorting.0rt b/lib/container/test/sorting.0rt
--- a/lib/container/test/sorting.0rt
+++ b/lib/container/test/sorting.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "Sorting tests" {
-  success
+  success TestChecker
   timeout 5
 }
 
@@ -26,7 +26,7 @@
   TestSequence expected <- TestSequence.create()
   Int hash <- 269
   Int size <- 137
-  $ReadOnly[hash,size]$
+  $ReadOnly[hash, size]$
 
   traverse (Counter.zeroIndexed(size) -> Int i) {
     \ original.append((i*hash)%size/3)
@@ -34,7 +34,7 @@
   }
 
   \ Sorting:sort(original)
-  \ Testing.checkEquals(original,expected)
+  \ original `Matches:with` CheckValue:equals(expected)
 }
 
 unittest sortWith { $DisableCoverage$
@@ -42,15 +42,15 @@
   TestSequence expected <- TestSequence.create()
   Int hash <- 313
   Int size <- 197
-  $ReadOnly[hash,size]$
+  $ReadOnly[hash, size]$
 
   traverse (Counter.revZeroIndexed(size) -> Int i) {
     \ original.append((i*hash)%size/3)
     \ expected.append(i/3)
   }
 
-  \ Sorting:sortWith<?,Reversed<Int>>(original)
-  \ Testing.checkEquals(original,expected)
+  \ Sorting:sortWith<?, Reversed<Int>>(original)
+  \ original `Matches:with` CheckValue:equals(expected)
 }
 
 unittest sortEmpty {
@@ -58,7 +58,7 @@
   TestSequence expected <- TestSequence.create()
 
   \ Sorting:sort(original)
-  \ Testing.checkEquals(original,expected)
+  \ original `Matches:with` CheckValue:equals(expected)
 }
 
 unittest reverseEvenSize {
@@ -76,7 +76,7 @@
   }
 
   \ Sorting:reverse(original)
-  \ Testing.checkEquals(original,expected)
+  \ original `Matches:with` CheckValue:equals(expected)
 }
 
 unittest reverseOddSize { $DisableCoverage$
@@ -94,7 +94,7 @@
   }
 
   \ Sorting:reverse(original)
-  \ Testing.checkEquals(original,expected)
+  \ original `Matches:with` CheckValue:equals(expected)
 }
 
 unittest reverseEmpty {
@@ -102,15 +102,15 @@
   TestSequence expected <- TestSequence.create()
 
   \ Sorting:reverse(original)
-  \ Testing.checkEquals(original,expected)
+  \ original `Matches:with` CheckValue:equals(expected)
 }
 
 unittest sortList {
-  ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
+  ListBuilder<Int, LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
   Vector<Int> expected <- Vector<Int>.new()
   Int hash <- 269
   Int size <- 137
-  $ReadOnly[hash,size]$
+  $ReadOnly[hash, size]$
 
   traverse (Counter.zeroIndexed(size) -> Int i) {
     \ builder.append((i*hash)%size/3)
@@ -120,19 +120,19 @@
   optional LinkedNode<Int> actual <- Sorting:sortList(builder.build(){0})
 
   traverse (expected.defaultOrder() -> Int n) {
-    \ Testing.checkEquals(require(actual).get(),n)
+    \ require(actual).get() `Matches:with` CheckValue:equals(n)
   } update {
     actual <- require(actual).next()
   }
-  \ Testing.checkFalse(present(actual))
+  \ present(actual) `Matches:with` CheckValue:equals(false)
 }
 
 unittest sortListSingle { $DisableCoverage$
-  ListBuilder<Int,ForwardNode<Int>> builder <- ForwardNode<Int>.builder()
+  ListBuilder<Int, ForwardNode<Int>> builder <- ForwardNode<Int>.builder()
   Vector<Int> expected <- Vector<Int>.new()
   Int hash <- 269
   Int size <- 137
-  $ReadOnly[hash,size]$
+  $ReadOnly[hash, size]$
 
   traverse (Counter.zeroIndexed(size) -> Int i) {
     \ builder.append((i*hash)%size/3)
@@ -142,23 +142,23 @@
   optional ForwardNode<Int> actual <- Sorting:sortList(builder.build(){0})
 
   traverse (expected.defaultOrder() -> Int n) {
-    \ Testing.checkEquals(require(actual).get(),n)
+    \ require(actual).get() `Matches:with` CheckValue:equals(n)
   } update {
     actual <- require(actual).next()
   }
-  \ Testing.checkFalse(present(actual))
+  \ present(actual) `Matches:with` CheckValue:equals(false)
 }
 
 unittest sortListEmpty {
-  \ Sorting:sortList<LinkedNode<Int>,Int>(empty)
+  \ Sorting:sortList<LinkedNode<Int>, Int>(empty)
 }
 
 unittest sortListPow2 { $DisableCoverage$
-  ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
+  ListBuilder<Int, LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
   Vector<Int> expected <- Vector<Int>.new()
   Int hash <- 269
   Int size <- 256
-  $ReadOnly[hash,size]$
+  $ReadOnly[hash, size]$
 
   traverse (Counter.zeroIndexed(size) -> Int i) {
     \ builder.append((i*hash)%size/3)
@@ -168,37 +168,37 @@
   optional LinkedNode<Int> actual <- Sorting:sortList(builder.build(){0})
 
   traverse (expected.defaultOrder() -> Int n) {
-    \ Testing.checkEquals(require(actual).get(),n)
+    \ require(actual).get() `Matches:with` CheckValue:equals(n)
   } update {
     actual <- require(actual).next()
   }
-  \ Testing.checkFalse(present(actual))
+  \ present(actual) `Matches:with` CheckValue:equals(false)
 }
 
 unittest sortListWith { $DisableCoverage$
-  ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
+  ListBuilder<Int, LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
   Vector<Int> expected <- Vector<Int>.new()
   Int hash <- 269
   Int size <- 137
-  $ReadOnly[hash,size]$
+  $ReadOnly[hash, size]$
 
   traverse (Counter.revZeroIndexed(size) -> Int i) {
     \ builder.append((i*hash)%size/3)
     \ expected.append(i/3)
   }
 
-  optional LinkedNode<Int> actual <- Sorting:sortListWith<?,?,Reversed<Int>>(builder.build(){0})
+  optional LinkedNode<Int> actual <- Sorting:sortListWith<?, ?, Reversed<Int>>(builder.build(){0})
 
   traverse (expected.defaultOrder() -> Int n) {
-    \ Testing.checkEquals(require(actual).get(),n)
+    \ require(actual).get() `Matches:with` CheckValue:equals(n)
   } update {
     actual <- require(actual).next()
   }
-  \ Testing.checkFalse(present(actual))
+  \ present(actual) `Matches:with` CheckValue:equals(false)
 }
 
 unittest reverseList {
-  ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
+  ListBuilder<Int, LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
   Vector<Int> expected <- Vector<Int>.new()
   Int size <- 101
   $ReadOnly[size]$
@@ -214,15 +214,15 @@
   optional LinkedNode<Int> reversed <- Sorting:reverseList(builder.build(){0})
 
   traverse (expected.defaultOrder() -> Int value) {
-    \ Testing.checkEquals(require(reversed).get(),value)
+    \ require(reversed).get() `Matches:with` CheckValue:equals(value)
   } update {
     reversed <- require(reversed).next()
   }
-  \ Testing.checkFalse(present(reversed))
+  \ present(reversed) `Matches:with` CheckValue:equals(false)
 }
 
 unittest reverseListSingle { $DisableCoverage$
-  ListBuilder<Int,ForwardNode<Int>> builder <- ForwardNode<Int>.builder()
+  ListBuilder<Int, ForwardNode<Int>> builder <- ForwardNode<Int>.builder()
   Vector<Int> expected <- Vector<Int>.new()
   Int size <- 101
   $ReadOnly[size]$
@@ -238,15 +238,15 @@
   optional ForwardNode<Int> reversed <- Sorting:reverseList(builder.build(){0})
 
   traverse (expected.defaultOrder() -> Int value) {
-    \ Testing.checkEquals(require(reversed).get(),value)
+    \ require(reversed).get() `Matches:with` CheckValue:equals(value)
   } update {
     reversed <- require(reversed).next()
   }
-  \ Testing.checkFalse(present(reversed))
+  \ present(reversed) `Matches:with` CheckValue:equals(false)
 }
 
 unittest reverseListEmpty {
-  \ Sorting:reverseList<LinkedNode<Int>,Int>(empty)
+  \ Sorting:reverseList<LinkedNode<Int>, Int>(empty)
 }
 
 concrete TestSequence {
@@ -267,7 +267,7 @@
   }
 
   formatted () {
-    [Append<Formatted>&Build<String>] builder <- String.builder()
+    [Append<Formatted> & Build<String>] builder <- String.builder()
     \ builder.append("{")
     traverse (seq.defaultOrder() -> Formatted x) {
       \ builder.append(" ").append(x)
@@ -283,8 +283,8 @@
     return seq.readAt(i)
   }
 
-  writeAt (i,x) {
-    \ seq.writeAt(i,x)
+  writeAt (i, x) {
+    \ seq.writeAt(i, x)
     return self
   }
 
@@ -293,7 +293,7 @@
     return self
   }
 
-  equals (x,y) {
+  equals (x, y) {
     return x `ReadAtH:equals` y
   }
 }
diff --git a/lib/container/test/testing.0rt b/lib/container/test/testing.0rt
new file mode 100644
--- /dev/null
+++ b/lib/container/test/testing.0rt
@@ -0,0 +1,257 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "CheckSet equality success" {
+  success TestChecker
+}
+
+unittest equals {
+  SortedSet<Int> actual <- SortedSet<Int>.new().append(1).append(2).append(3)
+  SortedSet<Int> expected <- SortedSet<Int>.new().append(1).append(2).append(3)
+  \ actual `Matches:with` CheckSet:equals(expected)
+}
+
+
+testcase "CheckSet:equals different" {
+  failure TestChecker
+  require "\"10\""
+  require "\"20\""
+  require "\"30\""
+  require "\"40\""
+  require "\"50\""
+  require "\"60\""
+}
+
+unittest test {
+  SortedSet<Int> actual <- SortedSet<Int>.new().append(10).append(20).append(30)
+  SortedSet<Int> expected <- SortedSet<Int>.new().append(40).append(50).append(60)
+  \ actual `Matches:with` CheckSet:equals(expected)
+}
+
+
+testcase "CheckSet:equals extra" {
+  failure TestChecker
+  require "\"40\""
+  exclude "\"10\""
+  exclude "\"20\""
+  exclude "\"30\""
+}
+
+unittest test {
+  SortedSet<Int> actual <- SortedSet<Int>.new().append(10).append(20).append(30).append(40)
+  SortedSet<Int> expected <- SortedSet<Int>.new().append(10).append(20).append(30)
+  \ actual `Matches:with` CheckSet:equals(expected)
+}
+
+
+testcase "CheckSet:equals missing" {
+  failure TestChecker
+  require "\"40\""
+  exclude "\"10\""
+  exclude "\"20\""
+  exclude "\"30\""
+}
+
+unittest test {
+  SortedSet<Int> actual <- SortedSet<Int>.new().append(10).append(20).append(30)
+  SortedSet<Int> expected <- SortedSet<Int>.new().append(10).append(20).append(30).append(40)
+  \ actual `Matches:with` CheckSet:equals(expected)
+}
+
+
+testcase "CheckMap equality success" {
+  success TestChecker
+}
+
+unittest equals {
+  SortedMap<String, Int> actual <- SortedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+  HashedMap<String, Int> expected <- HashedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+  \ actual `Matches:with` CheckMap:equals(expected)
+}
+
+
+testcase "CheckMap:equals different" {
+  failure TestChecker
+  require "two"
+  require "2900.+2"
+  require "three"
+  require "3.+3900"
+}
+
+unittest test {
+  SortedMap<String, Int> actual <- SortedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2900)
+      .set("three", 3)
+  HashedMap<String, Int> expected <- HashedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3900)
+  \ actual `Matches:with` CheckMap:equals(expected)
+}
+
+
+testcase "CheckMap:equals extra" {
+  failure TestChecker
+  require "zero"
+}
+
+unittest test {
+  SortedMap<String, Int> actual <- SortedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+  HashedMap<String, Int> expected <- HashedMap<String, Int>.new()
+      .set("zero", 0)
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+  \ actual `Matches:with` CheckMap:equals(expected)
+}
+
+
+testcase "CheckMap:equals missing" {
+  failure TestChecker
+  require "four"
+}
+
+unittest test {
+  SortedMap<String, Int> actual <- SortedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+      .set("four", 4)
+  HashedMap<String, Int> expected <- HashedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+  \ actual `Matches:with` CheckMap:equals(expected)
+}
+
+
+testcase "CheckMap equality success" {
+  success TestChecker
+}
+
+unittest matches {
+  SortedMap<String, Int> actual <- SortedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+  HashedMap<String, ValueMatcher<Int>> expected <- HashedMap<String, ValueMatcher<Int>>.new()
+      .set("one", CheckValue:equals(1))
+      .set("two", CheckValue:equals(2))
+      .set("three", CheckValue:equals(3))
+  \ actual `Matches:with` CheckMap:matches(expected)
+}
+
+
+testcase "CheckMap:matches different" {
+  failure TestChecker
+  require "two"
+  require "2900.+2"
+  require "three"
+  require "3.+3900"
+}
+
+unittest test {
+  SortedMap<String, Int> actual <- SortedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2900)
+      .set("three", 3)
+  HashedMap<String, ValueMatcher<Int>> expected <- HashedMap<String, ValueMatcher<Int>>.new()
+      .set("one", CheckValue:equals(1))
+      .set("two", CheckValue:equals(2))
+      .set("three", CheckValue:equals(3900))
+  \ actual `Matches:with` CheckMap:matches(expected)
+}
+
+
+testcase "CheckMap:matches extra" {
+  failure TestChecker
+  require "zero"
+}
+
+unittest test {
+  SortedMap<String, Int> actual <- SortedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+  HashedMap<String, ValueMatcher<Int>> expected <- HashedMap<String, ValueMatcher<Int>>.new()
+      .set("zero", CheckValue:equals(0))
+      .set("one", CheckValue:equals(1))
+      .set("two", CheckValue:equals(2))
+      .set("three", CheckValue:equals(3))
+  \ actual `Matches:with` CheckMap:matches(expected)
+}
+
+
+testcase "CheckMap:matches missing" {
+  failure TestChecker
+  require "four"
+}
+
+unittest test {
+  SortedMap<String, Int> actual <- SortedMap<String, Int>.new()
+      .set("one", 1)
+      .set("two", 2)
+      .set("three", 3)
+      .set("four", 4)
+  HashedMap<String, ValueMatcher<Int>> expected <- HashedMap<String, ValueMatcher<Int>>.new()
+      .set("one", CheckValue:equals(1))
+      .set("two", CheckValue:equals(2))
+      .set("three", CheckValue:equals(3))
+  \ actual `Matches:with` CheckMap:matches(expected)
+}
+
+
+testcase "CheckMap:using success" {
+  success TestChecker
+}
+
+unittest test {
+  SortedMap<String, TestValue> actual <- SortedMap<String, TestValue>.new()
+      .set("one", TestValue.new("one"))
+      .set("two", TestValue.new("two"))
+  SortedMap<String, TestValue> expected <- SortedMap<String, TestValue>.new()
+      .set("one", TestValue.new("one"))
+      .set("two", TestValue.new("two"))
+  \ actual `Matches:with` CheckMap:matches(CheckMap:using(expected))
+}
+
+
+testcase "CheckMap:using failure" {
+  failure TestChecker
+  require "TWO.+two"
+}
+
+unittest test {
+  SortedMap<String, TestValue> actual <- SortedMap<String, TestValue>.new()
+      .set("one", TestValue.new("one"))
+      .set("two", TestValue.new("TWO"))
+  SortedMap<String, TestValue> expected <- SortedMap<String, TestValue>.new()
+      .set("one", TestValue.new("one"))
+      .set("two", TestValue.new("two"))
+  \ actual `Matches:with` CheckMap:matches(CheckMap:using(expected))
+}
diff --git a/lib/container/test/type-map.0rt b/lib/container/test/type-map.0rt
--- a/lib/container/test/type-map.0rt
+++ b/lib/container/test/type-map.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "TypeMap tests" {
-  success
+  success TestChecker
 }
 
 unittest basicOperations {
@@ -27,21 +27,21 @@
   TypeKey<String> keyString <- TypeKey<String>.new()
   TypeKey<Value>  keyValue  <- TypeKey<Value>.new()
 
-  \ Testing.checkOptional(map.get(keyInt),   empty)
-  \ Testing.checkOptional(map.get(keyString),empty)
-  \ Testing.checkOptional(map.get(keyValue), empty)
+  \ map.get(keyInt)    `Matches:with` CheckValue:equals(empty?Int)
+  \ map.get(keyString) `Matches:with` CheckValue:equals(empty?String)
+  \ map.get(keyValue)  `Matches:with` CheckValue:equals(empty?Value)
 
-  \ map.set(keyInt,1)
-  \ map.set(keyString,"a")
-  \ map.set(keyValue,Value.new())
+  \ map.set(keyInt, 1)
+  \ map.set(keyString, "a")
+  \ map.set(keyValue, Value.new())
 
-  \ Testing.checkOptional(map.get(keyInt),   1)
-  \ Testing.checkOptional(map.get(keyString),"a")
-  \ Testing.checkOptional(map.get(keyValue), Value.new())
+  \ map.get(keyInt) `Matches:with` CheckValue:equals(   1)
+  \ map.get(keyString) `Matches:with` CheckValue:equals("a")
+  \ map.get(keyValue) `Matches:with` CheckValue:equals( Value.new())
 
   \ map.remove(keyString)
 
-  \ Testing.checkOptional(map.get(keyString),empty)
+  \ map.get(keyString) `Matches:with` CheckValue:equals(empty?String)
 }
 
 unittest wrongReturnType {
@@ -50,56 +50,57 @@
   TypeKey<String> keyString <- TypeKey<String>.new()
 
   // Added as Formatted, which means it cannot be retrieved as String later.
-  \ map.set<Formatted>(keyString,"a")
-  \ Testing.checkOptional(map.get<String>(keyString),empty)
+  \ map.set<Formatted>(keyString, "a")
+  \ map.get<String>(keyString) `Matches:with` CheckValue:equals(empty?String)
 
-  \ map.set<String>(keyString,"a")
-  \ Testing.checkOptional(map.get<String>(keyString),"a")
+  \ map.set<String>(keyString, "a")
+  \ map.get<String>(keyString) `Matches:with` CheckValue:equals("a")
 }
 
 unittest duplicate {
   TypeKey<Int>    keyInt    <- TypeKey<Int>.new()
   TypeKey<String> keyString <- TypeKey<String>.new()
 
-  TypeMap map <- TypeMap.new().set(keyInt,1).set(keyString,"a")
+  TypeMap map <- TypeMap.new().set(keyInt, 1).set(keyString, "a")
 
   TypeMap copy <- map.duplicate()
-  \ Testing.checkOptional(copy.get(keyInt),1)
-  \ Testing.checkOptional(copy.get(keyString),"a")
+  \ copy.get(keyInt) `Matches:with` CheckValue:equals(1)
+  \ copy.get(keyString) `Matches:with` CheckValue:equals("a")
 
   \ copy.remove(keyString)
-  \ Testing.checkOptional(copy.get(keyInt),1)
-  \ Testing.checkOptional(copy.get(keyString),empty)
-  \ Testing.checkOptional(map.get(keyString),"a")
+  \ copy.get(keyInt)    `Matches:with` CheckValue:equals(1)
+  \ copy.get(keyString) `Matches:with` CheckValue:equals(empty?String)
+  \ map.get(keyString)  `Matches:with` CheckValue:equals("a")
 }
 
 unittest getValues {
   TypeMap map <- TypeMap.new()
-      .set(TypeKey<String>.new(),"one")
-      .set(TypeKey<Int>.new(),2)
-      .set(TypeKey<String>.new(),"two")
-      .set(TypeKey<Int>.new(),1)
+      .set(TypeKey<String>.new(), "one")
+      .set(TypeKey<Int>.new(), 2)
+      .set(TypeKey<String>.new(), "two")
+      .set(TypeKey<Int>.new(), 1)
 
-  [Container&SetReader<String>] actual <- map.getAll(SortedSet<String>.new())
+  [Container & SetReader<String>] actual <- map.getAll(SortedSet<String>.new())
 
-  \ Testing.checkEquals(actual.size(),2)
-  \ Testing.checkTrue(actual.member("one"))
-  \ Testing.checkTrue(actual.member("two"))
+  \ actual.size() `Matches:with` CheckValue:equals(2)
+  \ actual.member("one") `Matches:with` CheckValue:equals(true)
+  \ actual.member("two") `Matches:with` CheckValue:equals(true)
 }
 
 unittest keyEquals {
   TypeKey<Int>    key1 <- TypeKey<Int>.new()
   TypeKey<Int>    key2 <- TypeKey<Int>.new()
   TypeKey<String> key3 <- TypeKey<String>.new()
-  \ Testing.checkEquals<TypeKey<Int>>(key1,key1)
-  \ Testing.checkFalse(key1 `TypeKey<any>.equals` key2)
-  \ Testing.checkFalse(key1 `TypeKey<any>.equals` key3)
+  \ key1 `Matches:with` CheckValue:equals(key1)
+  \ (key1 `TypeKey<any>.equals` key2) `Matches:with` CheckValue:equals(false)
+  \ (key1 `TypeKey<any>.equals` key2) `Matches:with` CheckValue:equals(false)
+  \ (key1 `TypeKey<any>.equals` key3) `Matches:with` CheckValue:equals(false)
 }
 
 unittest keyHashed {
   TypeKey<Int> key1 <- TypeKey<Int>.new()
   TypeKey<Int> key2 <- TypeKey<Int>.new()
-  \ Testing.checkNotEquals(key1.hashed(),key2.hashed())
+  \ key1.hashed() `Matches:with` CheckValue:notEquals(key2.hashed())
 }
 
 concrete Value {
@@ -118,15 +119,15 @@
     return "Value"
   }
 
-  equals (_,_) {
+  equals (_, _) {
     return true
   }
 }
 
 
 testcase "TypeKey formatted" {
-  crash
-  require "TypeKey<Int>\{[0-9]+\}"
+  failure
+  require "TypeKey<Int>\{[0-9a-fA-F]+\}"
 }
 
 unittest test {
diff --git a/lib/container/test/vector.0rt b/lib/container/test/vector.0rt
--- a/lib/container/test/vector.0rt
+++ b/lib/container/test/vector.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,17 +17,17 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "Vector tests" {
-  success
+  success TestChecker
 }
 
 unittest createSize {
   Vector<Int> values <- Vector:createSize<Int>(10)
-  \ Testing.checkEquals(values.size(),10)
+  \ values.size() `Matches:with` CheckValue:equals(10)
 
   scoped {
     Int i <- 0
   } in while (i < values.size()) {
-    \ Testing.checkEquals(values.readAt(i),Int.default())
+    \ values.readAt(i) `Matches:with` CheckValue:equals(Int.default())
   } update {
     i <- i+1
   }
@@ -40,16 +40,16 @@
     Int i <- 0
   } in while (i < 10) {
     \ values.append(i)
-    \ Testing.checkEquals(values.size(),i+1)
+    \ values.size() `Matches:with` CheckValue:equals(i+1)
   } update {
     i <- i+1
   }
-  \ Testing.checkEquals(values.size(),10)
+  \ values.size() `Matches:with` CheckValue:equals(10)
 
   scoped {
     Int i <- 0
   } in while (i < values.size()) {
-    \ Testing.checkEquals(values.readAt(i),i)
+    \ values.readAt(i) `Matches:with` CheckValue:equals(i)
   } update {
     i <- i+1
   }
@@ -62,21 +62,21 @@
     Int i <- 0
   } in while (i < 10) {
     \ values.push(i)
-    \ Testing.checkEquals(values.size(),i+1)
+    \ values.size() `Matches:with` CheckValue:equals(i+1)
   } update {
     i <- i+1
   }
-  \ Testing.checkEquals(values.size(),10)
+  \ values.size() `Matches:with` CheckValue:equals(10)
 
   scoped {
     Int i <- 0
   } in while (i < 10) {
-    \ Testing.checkEquals(values.pop(),10-i-1)
-    \ Testing.checkEquals(values.size(),10-i-1)
+    \ values.pop() `Matches:with` CheckValue:equals(10-i-1)
+    \ values.size() `Matches:with` CheckValue:equals(10-i-1)
   } update {
     i <- i+1
   }
-  \ Testing.checkEquals(values.size(),0)
+  \ values.size() `Matches:with` CheckValue:equals(0)
 }
 
 unittest writeAt {
@@ -85,7 +85,7 @@
   scoped {
     Int i <- 0
   } in while (i < values.size()) {
-    \ values.writeAt(i,2*i)
+    \ values.writeAt(i, 2*i)
   } update {
     i <- i+1
   }
@@ -93,7 +93,7 @@
   scoped {
     Int i <- 0
   } in while (i < values.size()) {
-    \ Testing.checkEquals(values.readAt(i),2*i)
+    \ values.readAt(i) `Matches:with` CheckValue:equals(2*i)
   } update {
     i <- i+1
   }
@@ -110,26 +110,26 @@
   \ vector.push(1)
 
   traverse (vector.defaultOrder() -> Int i) {
-    \ Testing.checkEquals(i,vector.readAt(index))
+    \ i `Matches:with` CheckValue:equals(vector.readAt(index))
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,6)
+  \ index `Matches:with` CheckValue:equals(6)
 }
 
 unittest duplicate {
   Vector<Int> vector <- Vector<Int>.default().push(1).push(2)
 
   Vector<Int> copy <- vector.duplicate()
-  \ Testing.checkEquals(copy.size(),2)
-  \ Testing.checkEquals(copy.readAt(0),1)
-  \ Testing.checkEquals(copy.readAt(1),2)
+  \ copy.size() `Matches:with` CheckValue:equals(2)
+  \ copy.readAt(0) `Matches:with` CheckValue:equals(1)
+  \ copy.readAt(1) `Matches:with` CheckValue:equals(2)
 
-  \ copy.writeAt(1,3)
+  \ copy.writeAt(1, 3)
   \ copy.pop()
-  \ Testing.checkEquals(copy.size(),1)
-  \ Testing.checkEquals(copy.readAt(0),1)
-  \ Testing.checkEquals(vector.readAt(1),2)
+  \ copy.size() `Matches:with` CheckValue:equals(1)
+  \ copy.readAt(0) `Matches:with` CheckValue:equals(1)
+  \ vector.readAt(1) `Matches:with` CheckValue:equals(2)
 }
 
 concrete Value {
@@ -151,20 +151,20 @@
 
 
 testcase "distinct default values in pre-sized Vector" {
-  success
+  success TestChecker
 }
 
 unittest test {
   Vector<Type> values <- Vector:createSize<Type>(10)
   \ values.readAt(3).set(7)
 
-  \ Testing.checkEquals(values.readAt(3),Type.create(7))
+  \ values.readAt(3) `Matches:with` CheckValue:equals(Type.create(7))
 
   scoped {
     Int i <- 0
   } in while (i < values.size()) {
     if (i != 3) {
-      \ Testing.checkEquals(values.readAt(i),Type.create(0))
+      \ values.readAt(i) `Matches:with` CheckValue:equals(Type.create(0))
     }
   } update {
     i <- i+1
@@ -195,7 +195,7 @@
     return value.formatted()
   }
 
-  equals (x,y) {
+  equals (x, y) {
     return x.get() == y.get()
   }
 
@@ -211,7 +211,7 @@
 
 
 testcase "negative Vector index" {
-  crash
+  failure
 }
 
 unittest test {
@@ -221,7 +221,7 @@
 
 
 testcase "Vector index out of bounds" {
-  crash
+  failure
 }
 
 unittest test {
diff --git a/lib/container/testing.0rp b/lib/container/testing.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/testing.0rp
@@ -0,0 +1,39 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 CheckSet {
+  @category equals<#k>
+    #k requires Formatted
+  ([SetReader<#k> & DefaultOrder<#k>]) -> (ValueMatcher<[SetReader<#k> & DefaultOrder<#k>]>)
+}
+
+concrete CheckMap {
+  @category using<#k, #v>
+    #v requires TestCompare<#v>
+  ([SetReader<#k> & DefaultOrder<KeyValue<#k, #v>>]) -> ([SetReader<#k> & DefaultOrder<KeyValue<#k, ValueMatcher<#v>>>])
+
+  @category equals<#k, #v>
+    #k requires Formatted
+    #v requires Formatted
+    #v defines Equals<#v>
+  ([SetReader<#k> & DefaultOrder<KeyValue<#k, #v>>]) -> (ValueMatcher<[KVReader<#k, #v> & KeyOrder<#k>]>)
+
+  @category matches<#k, #v>
+    #k requires Formatted
+  ([SetReader<#k> & DefaultOrder<KeyValue<#k, ValueMatcher<#v>>>]) -> (ValueMatcher<[KVReader<#k, #v> & KeyOrder<#k>]>)
+}
diff --git a/lib/container/type-map.0rp b/lib/container/type-map.0rp
--- a/lib/container/type-map.0rp
+++ b/lib/container/type-map.0rp
@@ -28,7 +28,7 @@
   //
   // Params:
   // - #x: The value type being stored.
-  @value set<#x> (TypeKey<#x>,#x) -> (TypeMap)
+  @value set<#x> (TypeKey<#x>, #x) -> (TypeMap)
 
   // Remove the value associated with the key if present and return self.
   @value remove (TypeKey<any>) -> (TypeMap)
@@ -47,7 +47,7 @@
   @value get<#x> (TypeKey<#x>) -> (optional #x)
 
   // Get all values of type #x.
-  @value getAll<#c,#x>
+  @value getAll<#c, #x>
     #c requires Append<#x>
   (#c) -> (#c)
 }
diff --git a/lib/file/.zeolite-module b/lib/file/.zeolite-module
--- a/lib/file/.zeolite-module
+++ b/lib/file/.zeolite-module
@@ -20,4 +20,4 @@
     categories: [RawFileWriter]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/lib/file/test/tests.0rt b/lib/file/test/tests.0rt
--- a/lib/file/test/tests.0rt
+++ b/lib/file/test/tests.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
+Copyright 2020,2023 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.
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "read and write" {
-  success
+  success TestChecker
 }
 
 unittest readWrite {
@@ -28,12 +28,12 @@
   } cleanup {
     \ writer.freeResource()
   } in {
-    \ Testing.checkEmpty(writer.getFileError())
+    \ present(writer.getFileError()) `Matches:with` CheckValue:equals(false)
     Int writeSize <- writer.writeBlock(data)
     if (writeSize != data.size()) {
       fail(writeSize)
     }
-    \ Testing.checkEmpty(writer.getFileError())
+    \ present(writer.getFileError()) `Matches:with` CheckValue:equals(false)
   }
 
   scoped {
@@ -41,7 +41,7 @@
   } cleanup {
     \ reader.freeResource()
   } in {
-    \ Testing.checkEmpty(reader.getFileError())
+    \ present(reader.getFileError()) `Matches:with` CheckValue:equals(false)
 
     scoped {
       String data2 <- reader.readBlock(8)
@@ -53,7 +53,7 @@
     } in if (data2 != "some\x00data") {
       fail("\"" + data2 + "\"")
     }
-    \ Testing.checkEmpty(reader.getFileError())
+    \ present(reader.getFileError()) `Matches:with` CheckValue:equals(false)
 
     if (!reader.pastEnd()) {
       fail("more data in file")
@@ -70,7 +70,7 @@
     \ writer.freeResource()
   } in \ writer.writeBlock(data)
 
-  \ Testing.checkEquals(FileTesting.forceReadFile("testfile"),data)
+  \ FileTesting.forceReadFile("testfile") `Matches:with` CheckValue:equals(data)
 }
 
 unittest readEmpty {
@@ -81,7 +81,7 @@
   if (data != "") {
     fail("\"" + data + "\"")
   }
-  \ Testing.checkEmpty(reader.getFileError())
+  \ present(reader.getFileError()) `Matches:with` CheckValue:equals(false)
   if (!reader.pastEnd()) {
     fail("more data in file")
   }
@@ -89,18 +89,18 @@
 
 unittest readMissing {
   RawFileReader reader <- RawFileReader.open("do-not-create-this-file")
-  \ Testing.checkPresent(reader.getFileError())
+  \ present(reader.getFileError()) `Matches:with` CheckValue:equals(true)
 }
 
 unittest unwritable {
   RawFileWriter writer <- RawFileWriter.open("testfile")
   \ writer.freeResource()
-  \ Testing.checkPresent(writer.getFileError())
+  \ present(writer.getFileError()) `Matches:with` CheckValue:equals(true)
 }
 
 
 testcase "read missing file crash" {
-  crash
+  failure
   require "do-not-create-this-file"
   require "RawFileReader .*creation"
 }
@@ -112,7 +112,7 @@
 
 
 testcase "unwritable file crash" {
-  crash
+  failure
   require "testfile"
   require "RawFileWriter .*creation"
 }
diff --git a/lib/math/.zeolite-module b/lib/math/.zeolite-module
--- a/lib/math/.zeolite-module
+++ b/lib/math/.zeolite-module
@@ -29,4 +29,4 @@
     categories: [RandomUniform]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/lib/math/categorical-tree.0rp b/lib/math/categorical-tree.0rp
--- a/lib/math/categorical-tree.0rp
+++ b/lib/math/categorical-tree.0rp
@@ -33,11 +33,11 @@
   // Return the value at the given offset.
   //
   // Notes:
-  // - The offset must be within [0,getTotal()). A uniform selection in that
+  // - The offset must be within [0, getTotal()). A uniform selection in that
   //   range will provide samples that follow the categorical distribution
   //   corresponding to the relative weights of the respective #c.
   // - The return value is deterministic. If you were to iterate over
-  //   [0,getTotal()), you'd get an increasing sequence of all #c in the
+  //   [0, getTotal()), you'd get an increasing sequence of all #c in the
   //   CategoricalReader, each repeated the number of times indicated by its
   //   respective weight.
   // - Also see RandomCategorical in random.0rp.
@@ -74,7 +74,7 @@
   // Notes:
   // - The weight must not be negative.
   // - The sum of all weights (see getTotal()) must not exceed Int.maxBound().
-  @value setWeight (#c,Int) -> (#self)
+  @value setWeight (#c, Int) -> (#self)
 
   // Increments the weight of the value by 1.
   @value incrWeight (#c) -> (#self)
diff --git a/lib/math/math.0rp b/lib/math/math.0rp
--- a/lib/math/math.0rp
+++ b/lib/math/math.0rp
@@ -36,12 +36,12 @@
   @type log (Float) -> (Float)
   @type log10 (Float) -> (Float)
   @type log2 (Float) -> (Float)
-  @type pow (Float,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 fmod (Float, Float) -> (Float)
   @type trunc (Float) -> (Float)
   @type round (Float) -> (Float)
   @type fabs (Float) -> (Float)
diff --git a/lib/math/random.0rp b/lib/math/random.0rp
--- a/lib/math/random.0rp
+++ b/lib/math/random.0rp
@@ -37,7 +37,7 @@
   refines Generator<Float>
 
   // Creates a new generator with the specified mean and standard deviation.
-  @type new (Float,Float) -> (#self)
+  @type new (Float, Float) -> (#self)
 
   // Resets the seed for RNG.
   @value setSeed (Int) -> (#self)
@@ -48,9 +48,9 @@
   refines Generator<Float>
 
   // Creates a new generator with the specified min and max values.
-  @type new (Float,Float) -> (#self)
+  @type new (Float, Float) -> (#self)
 
-  // Creates a new generator for the range [0.0,1.0).
+  // Creates a new generator for the range [0.0, 1.0).
   @type probability () -> (#self)
 
   // Resets the seed for RNG.
@@ -72,10 +72,10 @@
   // Creates a generator for the provided distribution.
   //
   // Notes:
-  // - The Generator<Float> *must* return values in the range [0,1).
+  // - The Generator<Float> *must* return values in the range [0, 1).
   // - The distribution is not copied; therefore, changes to the distribution
   //   will affect the returned generator.
-  @category sampleWith<#c> (CategoricalReader<#c>,Generator<Float>) -> (RandomCategorical<#c>)
+  @category sampleWith<#c> (CategoricalReader<#c>, Generator<Float>) -> (RandomCategorical<#c>)
 
   // Returns true if the distribution is empty.
   //
@@ -93,9 +93,9 @@
   // 2. Samples from the copy *without* replacement until the tree is empty.
   //
   // Notes:
-  // - The Generator must only return values in [0,1).
+  // - The Generator must only return values in [0, 1).
   // - If category c has a weight of n, it will occur n times in the output.
-  @category permuteFrom<#c> (CategoricalTree<#c>,Generator<Float>,Append<#c>) -> ()
+  @category permuteFrom<#c> (CategoricalTree<#c>, Generator<Float>, Append<#c>) -> ()
 
   // Creates a permutation from the CategoricalTree.
   //
@@ -103,8 +103,8 @@
   // 2. Samples from the copy *without* replacement until the tree is empty.
   //
   // Notes:
-  // - The Generator must only return values in [0,1).
+  // - The Generator must only return values in [0, 1).
   // - If category c has a weight of n, it will occur *once* in the output, but
   //   it will have a relative chance of n to be chosen ahead of others.
-  @category permuteFromWeight<#c> (CategoricalTree<#c>,Generator<Float>,Append<#c>) -> ()
+  @category permuteFromWeight<#c> (CategoricalTree<#c>, Generator<Float>, Append<#c>) -> ()
 }
diff --git a/lib/math/src/categorical-tree.0rx b/lib/math/src/categorical-tree.0rx
--- a/lib/math/src/categorical-tree.0rx
+++ b/lib/math/src/categorical-tree.0rx
@@ -17,14 +17,14 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define CategoricalTree {
-  @value AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>> tree
+  @value AutoBinaryTree<CategoricalTreeNode<#c>, #c, Int, CategoricalSearch<#c>> tree
 
   default () {
     return new()
   }
 
   new () {
-    return #self{ AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>>.new() }
+    return #self{ AutoBinaryTree<CategoricalTreeNode<#c>, #c, Int, CategoricalSearch<#c>>.new() }
   }
 
   duplicate () {
@@ -35,13 +35,13 @@
     return CategoricalTreeNode<#c>.tryTotal(tree.getRoot())
   }
 
-  setWeight (cat,size) {
+  setWeight (cat, size) {
     if (size < 0) {
       fail("size must not be negative")
     } elif (size > 0) {
-      \ tree.swap(cat,size)
+      \ tree.swap(cat, size)
     } else {
-      \ tree.swap(cat,empty)
+      \ tree.swap(cat, empty)
     }
     return self
   }
@@ -57,12 +57,12 @@
   }
 
   incrWeight (cat) {
-    \ setWeight(cat,getWeight(cat)+1)
+    \ setWeight(cat, getWeight(cat)+1)
     return self
   }
 
   decrWeight (cat) {
-    \ setWeight(cat,getWeight(cat)-1)
+    \ setWeight(cat, getWeight(cat)-1)
     return self
   }
 
@@ -72,7 +72,7 @@
     } elif (pos >= getTotal()) {
       fail("position must be strictly less than the total")
     } else {
-      return require(CategoricalTreeNode<#c>.findPosition(pos,tree.getRoot()))
+      return require(CategoricalTreeNode<#c>.findPosition(pos, tree.getRoot()))
     }
   }
 
@@ -86,23 +86,23 @@
 }
 
 define ValidatedTree {
-  @value AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>> tree
+  @value AutoBinaryTree<CategoricalTreeNode<#c>, #c, Int, CategoricalSearch<#c>> tree
 
   new () { $NoTrace$
-    return #self{ AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>>.new() }
+    return #self{ AutoBinaryTree<CategoricalTreeNode<#c>, #c, Int, CategoricalSearch<#c>>.new() }
   }
 
   getTotal () { $NoTrace$
     return CategoricalTreeNode<#c>.tryTotal(tree.getRoot())
   }
 
-  setWeight (cat,size) { $NoTrace$
+  setWeight (cat, size) { $NoTrace$
     if (size < 0) {
       fail("size must not be negative")
     } elif (size > 0) {
-      \ tree.swap(cat,size)
+      \ tree.swap(cat, size)
     } else {
-      \ tree.swap(cat,empty)
+      \ tree.swap(cat, empty)
     }
     \ validateTotal(tree.getRoot())
     return self
@@ -124,7 +124,7 @@
     } elif (pos >= getTotal()) {
       fail("position must be strictly less than the total")
     } else {
-      return require(CategoricalTreeNode<#c>.findPosition(pos,tree.getRoot()))
+      return require(CategoricalTreeNode<#c>.findPosition(pos, tree.getRoot()))
     }
   }
 
@@ -154,18 +154,18 @@
 }
 
 @value interface CategoricalSearch<|#c> {
-  refines BinaryTreeNode<#c,Int>
+  refines BinaryTreeNode<#c, Int>
 
   getTotal () -> (Int)
 }
 
 concrete CategoricalTreeNode<#c> {
-  defines KVFactory<#c,Int>
+  defines KVFactory<#c, Int>
   refines CategoricalSearch<#c>
-  refines BalancedTreeNode<CategoricalTreeNode<#c>,#c,Int>
+  refines BalancedTreeNode<CategoricalTreeNode<#c>, #c, Int>
   refines Duplicate
 
-  @type findPosition (Int,optional CategoricalSearch<#c>) -> (optional #c)
+  @type findPosition (Int, optional CategoricalSearch<#c>) -> (optional #c)
   @type tryTotal (optional CategoricalSearch<#c>) -> (Int)
 
   @value getTotal () -> (Int)
@@ -193,7 +193,7 @@
     return #self{ height, key, size, total, lower2, higher2 }
   }
 
-  findPosition (pos,node) (cat) {
+  findPosition (pos, node) (cat) {
     cat <- empty
     if (present(node)) {
       scoped {
@@ -202,11 +202,11 @@
         CategoricalSearch<#c> node2 <- require(node)
         Int lower <- tryTotal(node2.getLower())
         Int size <- node2.getValue()
-        $ReadOnly[node2,lower,size]$
+        $ReadOnly[node2, lower, size]$
       } in if (pos2 < node2.getTotal()) {
         if (pos2 < lower) {
           // pos2 is in the 1st (lower) of the 3 sections.
-          return findPosition(pos2,node2.getLower())
+          return findPosition(pos2, node2.getLower())
         }
         pos2 <- pos2-lower
         if (pos2 < size) {
@@ -215,7 +215,7 @@
         }
         pos2 <- pos2-size
           // pos2 is in the 3rd (higher) of the 3 sections.
-        return findPosition(pos2,node2.getHigher())
+        return findPosition(pos2, node2.getHigher())
       }
     }
   }
@@ -228,7 +228,7 @@
     }
   }
 
-  newNode (k,v) {
+  newNode (k, v) {
     return #self{ 1, k, v, v, empty, empty }
   }
 
diff --git a/lib/math/src/random.0rx b/lib/math/src/random.0rx
--- a/lib/math/src/random.0rx
+++ b/lib/math/src/random.0rx
@@ -17,10 +17,10 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define Randomize {
-  permuteFrom (tree,random,output) {
+  permuteFrom (tree, random, output) {
     CategoricalTree<#c> copy <- tree.duplicate()
     Generator<#c> generator <- copy `RandomCategorical:sampleWith` random
-    $Hidden[tree,random]$
+    $Hidden[tree, random]$
     while (copy.getTotal() > 0) {
       #c cat <- generator.generate()
       \ output.append(cat)
@@ -28,14 +28,14 @@
     }
   }
 
-  permuteFromWeight (tree,random,output) {
+  permuteFromWeight (tree, random, output) {
     CategoricalTree<#c> copy <- tree.duplicate()
     Generator<#c> generator <- copy `RandomCategorical:sampleWith` random
-    $Hidden[tree,random]$
+    $Hidden[tree, random]$
     while (copy.getTotal() > 0) {
       #c cat <- generator.generate()
       \ output.append(cat)
-      \ copy.setWeight(cat,0)
+      \ copy.setWeight(cat, 0)
     }
   }
 }
@@ -46,7 +46,7 @@
   @value Float constant
 
   new (constant) {
-    return GenerateConstant{ constant }
+    return delegate -> #self
   }
 
   generate () {
@@ -60,8 +60,8 @@
   @value CategoricalReader<#c> categorical
   @value Generator<Float> random
 
-  sampleWith (categorical,random) {
-    return RandomCategorical<#c>{ categorical, random }
+  sampleWith (categorical, random) {
+    return delegate -> RandomCategorical<#c>
   }
 
   generate () {
diff --git a/lib/math/src/token.0rx b/lib/math/src/token.0rx
--- a/lib/math/src/token.0rx
+++ b/lib/math/src/token.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,14 +17,12 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define Token {
-  $ReadOnlyExcept[counter]$
+  $ReadOnlyExcept[]$
 
   @category Mutex mutex <- SpinlockMutex.new()
-  @category HashedMap<String,Token> toToken <- HashedMap<String,Token>.new()
-  @category Int counter <- 0
+  @category HashedMap<String, Token> toToken <- HashedMap<String, Token>.new()
 
   @value String string
-  @value Int    value
 
   from (string) (token) {
     scoped {
@@ -32,10 +30,10 @@
       optional Token existing <- toToken.get(string)
     } cleanup {
       \ mutex.unlock()
-    } in if (present(existing)) {
-      return require(existing)
+    } in if (`present` existing) {
+      token <- `require` existing
     } else {
-      \ toToken.set(string,(token <- Token{ string, (counter <- counter+1) }))
+      \ string `toToken.set` (token <- Token{ string })
     }
   }
 
@@ -44,17 +42,14 @@
   }
 
   hashed () {
-    return value.hashed()
+    return identify(self).hashed()
   }
 
-  equals (x,y) {
-    return x.get() == y.get()
+  equals (x, y) {
+    return identify(x) == identify(y)
   }
 
-  lessThan (x,y) {
-    return x.get() < y.get()
+  lessThan (x, y) {
+    return identify(x) < identify(y)
   }
-
-  @value get () -> (Int)
-  get () { return value }
 }
diff --git a/lib/math/src/validated-tree.0rp b/lib/math/src/validated-tree.0rp
--- a/lib/math/src/validated-tree.0rp
+++ b/lib/math/src/validated-tree.0rp
@@ -31,7 +31,7 @@
   @type new () -> (#self)
 
   @value getTotal  ()       -> (Int)
-  @value setWeight (#c,Int) -> (#self)
+  @value setWeight (#c, Int) -> (#self)
   @value getWeight (#c)     -> (Int)
   @value locate    (Int)    -> (#c)
 }
diff --git a/lib/math/test/categorical-tree.0rt b/lib/math/test/categorical-tree.0rt
--- a/lib/math/test/categorical-tree.0rt
+++ b/lib/math/test/categorical-tree.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,17 +17,17 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "CategoricalTree tests" {
-  success
+  success TestChecker
 }
 
 unittest simpleInsertion {
   CategoricalTree<Int> tree <- CategoricalTree<Int>.default()
-      .setWeight(1,3)
-      .setWeight(6,2)
-      .setWeight(3,1)
-      .setWeight(5,4)
-      .setWeight(4,3)
-      .setWeight(2,7)
+      .setWeight(1, 3)
+      .setWeight(6, 2)
+      .setWeight(3, 1)
+      .setWeight(5, 4)
+      .setWeight(4, 3)
+      .setWeight(2, 7)
 
   Vector<Int> expected <- Vector<Int>.new()
       .push(1).push(1).push(1)
@@ -37,31 +37,31 @@
       .push(5).push(5).push(5).push(5)
       .push(6).push(6)
 
-  \ Testing.checkEquals(tree.getTotal(),expected.size())
-  \ Testing.checkEquals(tree.getWeight(1),3)
-  \ Testing.checkEquals(tree.getWeight(2),7)
-  \ Testing.checkEquals(tree.getWeight(3),1)
-  \ Testing.checkEquals(tree.getWeight(4),3)
-  \ Testing.checkEquals(tree.getWeight(5),4)
-  \ Testing.checkEquals(tree.getWeight(6),2)
+  \ tree.getTotal() `Matches:with` CheckValue:equals(expected.size())
+  \ tree.getWeight(1) `Matches:with` CheckValue:equals(3)
+  \ tree.getWeight(2) `Matches:with` CheckValue:equals(7)
+  \ tree.getWeight(3) `Matches:with` CheckValue:equals(1)
+  \ tree.getWeight(4) `Matches:with` CheckValue:equals(3)
+  \ tree.getWeight(5) `Matches:with` CheckValue:equals(4)
+  \ tree.getWeight(6) `Matches:with` CheckValue:equals(2)
 
   traverse (Counter.zeroIndexed(tree.getTotal()) -> Int pos) {
-    \ Testing.checkEquals(tree.locate(pos),expected.readAt(pos))
+    \ tree.locate(pos) `Matches:with` CheckValue:equals(expected.readAt(pos))
   }
 }
 
 unittest modifySizes {
   CategoricalTree<Int> tree <- CategoricalTree<Int>.new()
-      .setWeight(1,3)
-      .setWeight(6,2)
-      .setWeight(3,1)
-      .setWeight(5,4)
-      .setWeight(4,3)
-      .setWeight(2,7)
-      .setWeight(7,3)
+      .setWeight(1, 3)
+      .setWeight(6, 2)
+      .setWeight(3, 1)
+      .setWeight(5, 4)
+      .setWeight(4, 3)
+      .setWeight(2, 7)
+      .setWeight(7, 3)
       // modifications start here
-      .setWeight(5,1)
-      .setWeight(3,0)
+      .setWeight(5, 1)
+      .setWeight(3, 0)
 
   Vector<Int> expected <- Vector<Int>.new()
       .push(1).push(1).push(1)
@@ -71,22 +71,22 @@
       .push(6).push(6)
       .push(7).push(7).push(7)
 
-  \ Testing.checkEquals(tree.getTotal(),expected.size())
-  \ Testing.checkEquals(tree.getWeight(1),3)
-  \ Testing.checkEquals(tree.getWeight(2),7)
-  \ Testing.checkEquals(tree.getWeight(3),0)
-  \ Testing.checkEquals(tree.getWeight(4),3)
-  \ Testing.checkEquals(tree.getWeight(5),1)
-  \ Testing.checkEquals(tree.getWeight(6),2)
-  \ Testing.checkEquals(tree.getWeight(7),3)
+  \ tree.getTotal() `Matches:with` CheckValue:equals(expected.size())
+  \ tree.getWeight(1) `Matches:with` CheckValue:equals(3)
+  \ tree.getWeight(2) `Matches:with` CheckValue:equals(7)
+  \ tree.getWeight(3) `Matches:with` CheckValue:equals(0)
+  \ tree.getWeight(4) `Matches:with` CheckValue:equals(3)
+  \ tree.getWeight(5) `Matches:with` CheckValue:equals(1)
+  \ tree.getWeight(6) `Matches:with` CheckValue:equals(2)
+  \ tree.getWeight(7) `Matches:with` CheckValue:equals(3)
 
   traverse (Counter.zeroIndexed(tree.getTotal()) -> Int pos) {
-    \ Testing.checkEquals(tree.locate(pos),expected.readAt(pos))
+    \ tree.locate(pos) `Matches:with` CheckValue:equals(expected.readAt(pos))
   }
 
   // Use ReadAt aliases for the same functionality.
   traverse (Counter.zeroIndexed(tree.size()) -> Int pos) {
-    \ Testing.checkEquals(tree.readAt(pos),expected.readAt(pos))
+    \ tree.readAt(pos) `Matches:with` CheckValue:equals(expected.readAt(pos))
   }
 }
 
@@ -96,59 +96,59 @@
     .incrWeight(1)
     .incrWeight(1)
 
-  \ Testing.checkEquals(tree.getTotal(),3)
-  \ Testing.checkEquals(tree.getWeight(1),3)
+  \ tree.getTotal() `Matches:with` CheckValue:equals(3)
+  \ tree.getWeight(1) `Matches:with` CheckValue:equals(3)
 }
 
 unittest decrement {
   CategoricalTree<Int> tree <- CategoricalTree<Int>.new()
-    .setWeight(1,10)
+    .setWeight(1, 10)
     .decrWeight(1)
     .decrWeight(1)
     .decrWeight(1)
 
-  \ Testing.checkEquals(tree.getTotal(),7)
-  \ Testing.checkEquals(tree.getWeight(1),7)
+  \ tree.getTotal() `Matches:with` CheckValue:equals(7)
+  \ tree.getWeight(1) `Matches:with` CheckValue:equals(7)
 }
 
 unittest duplicate {
-  CategoricalTree<Int> tree <- CategoricalTree<Int>.new().setWeight(1,1).setWeight(2,2)
+  CategoricalTree<Int> tree <- CategoricalTree<Int>.new().setWeight(1, 1).setWeight(2, 2)
 
   CategoricalTree<Int> copy <- tree.duplicate()
-  \ Testing.checkEquals(copy.size(),3)
-  \ Testing.checkEquals(copy.getWeight(1),1)
-  \ Testing.checkEquals(copy.getWeight(2),2)
+  \ copy.size() `Matches:with` CheckValue:equals(3)
+  \ copy.getWeight(1) `Matches:with` CheckValue:equals(1)
+  \ copy.getWeight(2) `Matches:with` CheckValue:equals(2)
 
-  \ copy.setWeight(2,0)
-  \ Testing.checkEquals(copy.size(),1)
-  \ Testing.checkEquals(copy.getWeight(1),1)
-  \ Testing.checkEquals(tree.getWeight(2),2)
+  \ copy.setWeight(2, 0)
+  \ copy.size() `Matches:with` CheckValue:equals(1)
+  \ copy.getWeight(1) `Matches:with` CheckValue:equals(1)
+  \ tree.getWeight(2) `Matches:with` CheckValue:equals(2)
 }
 
 unittest integrationTest { $DisableCoverage$
   ValidatedTree<Int> tree     <- ValidatedTree<Int>.new()
-  SortedMap<Int,Int> expected <- SortedMap<Int,Int>.new()
+  SortedMap<Int, Int> expected <- SortedMap<Int, Int>.new()
 
   Int count   <- 137
   Int maxSize <- 15
   Int hash1   <- 379
   Int hash2   <- 457
-  $ReadOnly[count,maxSize,hash1,hash2]$
+  $ReadOnly[count, maxSize, hash1, hash2]$
 
   // Populate the tree with arbitrary data.
   traverse (Counter.zeroIndexed(count) -> Int i) {
-    \ tree.setWeight((i*hash1)%count,(i*hash2)%maxSize)
+    \ tree.setWeight((i*hash1)%count, (i*hash2)%maxSize)
   }
 
   // Overwrite the tree with real data.
   traverse (Counter.zeroIndexed(count) -> Int i) {
     Int key <- (i*hash2)%count
     Int size <- (i*hash1)%maxSize
-    \ tree.setWeight(key,size)
-    \ expected.set(key,size)
+    \ tree.setWeight(key, size)
+    \ expected.set(key, size)
   }
 
-  optional Order<KeyValue<Int,Int>> node <- expected.defaultOrder()
+  optional Order<KeyValue<Int, Int>> node <- expected.defaultOrder()
   Int key       <- 0
   Int remaining <- 0
   traverse (Counter.zeroIndexed(tree.getTotal()) -> Int pos) {
@@ -159,27 +159,27 @@
     }
     $ReadOnly[key]$
     $Hidden[node]$
-    \ Testing.checkEquals(tree.locate(pos),key)
+    \ tree.locate(pos) `Matches:with` CheckValue:equals(key)
     remaining <- remaining-1
   }
 
-  \ Testing.checkEmpty(node)
-  \ Testing.checkEquals(remaining,0)
+  \ present(node) `Matches:with` CheckValue:equals(false)
+  \ remaining `Matches:with` CheckValue:equals(0)
 }
 
 
 testcase "negative size crashes" {
-  crash
+  failure
   require "size.+negative"
 }
 
 unittest test {
-  \ CategoricalTree<Int>.new().setWeight(0,-10)
+  \ CategoricalTree<Int>.new().setWeight(0, -10)
 }
 
 
 testcase "negative position crashes" {
-  crash
+  failure
   require "position.+negative"
 }
 
@@ -189,10 +189,10 @@
 
 
 testcase "position past end crashes" {
-  crash
+  failure
   require "position.+total"
 }
 
 unittest test {
-  \ CategoricalTree<Int>.new().setWeight(0,1).locate(1)
+  \ CategoricalTree<Int>.new().setWeight(0, 1).locate(1)
 }
diff --git a/lib/math/test/math.0rt b/lib/math/test/math.0rt
--- a/lib/math/test/math.0rt
+++ b/lib/math/test/math.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
+Copyright 2020,2023 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.
@@ -17,103 +17,103 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "sanity check all functions" {
-  success
+  success TestChecker
 }
 
 unittest cos {
-  \ Testing.checkBetween(Math.cos(2.0),-0.42,-0.41)
+  \ Math.cos(2.0) `Matches:with` CheckValue:betweenEquals(-0.42, -0.41)
 }
 
 unittest sin {
-  \ Testing.checkBetween(Math.sin(2.0),0.90,0.91)
+  \ Math.sin(2.0) `Matches:with` CheckValue:betweenEquals(0.90, 0.91)
 }
 
 unittest tan {
-  \ Testing.checkBetween(Math.tan(2.0),-2.19,-2.18)
+  \ Math.tan(2.0) `Matches:with` CheckValue:betweenEquals(-2.19, -2.18)
 }
 
 unittest acos {
-  \ Testing.checkBetween(Math.acos(0.5),1.04,1.05)
+  \ Math.acos(0.5) `Matches:with` CheckValue:betweenEquals(1.04, 1.05)
 }
 
 unittest asin {
-  \ Testing.checkBetween(Math.asin(0.5),0.52,0.53)
+  \ Math.asin(0.5) `Matches:with` CheckValue:betweenEquals(0.52, 0.53)
 }
 
 unittest atan {
-  \ Testing.checkBetween(Math.atan(0.5),0.46,0.47)
+  \ Math.atan(0.5) `Matches:with` CheckValue:betweenEquals(0.46, 0.47)
 }
 
 unittest cosh {
-  \ Testing.checkBetween(Math.cosh(2.0),3.76,3.77)
+  \ Math.cosh(2.0) `Matches:with` CheckValue:betweenEquals(3.76, 3.77)
 }
 
 unittest sinh {
-  \ Testing.checkBetween(Math.sinh(2.0),3.62,3.63)
+  \ Math.sinh(2.0) `Matches:with` CheckValue:betweenEquals(3.62, 3.63)
 }
 
 unittest tanh {
-  \ Testing.checkBetween(Math.tanh(2.0),0.96,0.97)
+  \ Math.tanh(2.0) `Matches:with` CheckValue:betweenEquals(0.96, 0.97)
 }
 
 unittest acosh {
-  \ Testing.checkBetween(Math.acosh(2.0),1.31,1.32)
+  \ Math.acosh(2.0) `Matches:with` CheckValue:betweenEquals(1.31, 1.32)
 }
 
 unittest asinh {
-  \ Testing.checkBetween(Math.asinh(2.0),1.44,1.45)
+  \ Math.asinh(2.0) `Matches:with` CheckValue:betweenEquals(1.44, 1.45)
 }
 
 unittest atanh {
-  \ Testing.checkBetween(Math.atanh(0.5),0.54,0.55)
+  \ Math.atanh(0.5) `Matches:with` CheckValue:betweenEquals(0.54, 0.55)
 }
 
 unittest exp {
-  \ Testing.checkBetween(Math.exp(1.0),2.71,2.72)
+  \ Math.exp(1.0) `Matches:with` CheckValue:betweenEquals(2.71, 2.72)
 }
 
 unittest log {
-  \ Testing.checkBetween(Math.log(9.0),2.19,2.20)
+  \ Math.log(9.0) `Matches:with` CheckValue:betweenEquals(2.19, 2.20)
 }
 
 unittest log10 {
-  \ Testing.checkBetween(Math.log10(100.0),1.99,2.01)
+  \ Math.log10(100.0) `Matches:with` CheckValue:betweenEquals(1.99, 2.01)
 }
 
 unittest log2 {
-  \ Testing.checkBetween(Math.log2(8.0),2.99,3.01)
+  \ Math.log2(8.0) `Matches:with` CheckValue:betweenEquals(2.99, 3.01)
 }
 
 unittest pow {
-  \ Testing.checkBetween(Math.pow(2.0,3.0),7.99,8.01)
+  \ Math.pow(2.0, 3.0) `Matches:with` CheckValue:betweenEquals(7.99, 8.01)
 }
 
 unittest sqrt {
-  \ Testing.checkBetween(Math.sqrt(4.0),1.99,2.01)
+  \ Math.sqrt(4.0) `Matches:with` CheckValue:betweenEquals(1.99, 2.01)
 }
 
 unittest ceil {
-  \ Testing.checkBetween(Math.ceil(2.2),2.99,3.01)
+  \ Math.ceil(2.2) `Matches:with` CheckValue:betweenEquals(2.99, 3.01)
 }
 
 unittest floor {
-  \ Testing.checkBetween(Math.floor(2.2),1.99,2.01)
+  \ Math.floor(2.2) `Matches:with` CheckValue:betweenEquals(1.99, 2.01)
 }
 
 unittest fmod {
-  \ Testing.checkBetween(Math.fmod(7.0,4.0),2.99,3.01)
+  \ Math.fmod(7.0, 4.0) `Matches:with` CheckValue:betweenEquals(2.99, 3.01)
 }
 
 unittest trunc {
-  \ Testing.checkBetween(Math.trunc(2.2),1.99,2.01)
+  \ Math.trunc(2.2) `Matches:with` CheckValue:betweenEquals(1.99, 2.01)
 }
 
 unittest round {
-  \ Testing.checkBetween(Math.round(2.7),2.99,3.01)
+  \ Math.round(2.7) `Matches:with` CheckValue:betweenEquals(2.99, 3.01)
 }
 
 unittest fabs {
-  \ Testing.checkBetween(Math.fabs(-10.0),9.99,10.01)
+  \ Math.fabs(-10.0) `Matches:with` CheckValue:betweenEquals(9.99, 10.01)
 }
 
 unittest isinf {
@@ -129,7 +129,7 @@
 }
 
 unittest abs {
-  \ Testing.checkEquals(Math.abs(-10),10)
-  \ Testing.checkEquals(Math.abs(10),10)
-  \ Testing.checkEquals(Math.abs(0),0)
+  \ Math.abs(-10) `Matches:with` CheckValue:equals(10)
+  \ Math.abs(10) `Matches:with` CheckValue:equals(10)
+  \ Math.abs(0) `Matches:with` CheckValue:equals(0)
 }
diff --git a/lib/math/test/random.0rt b/lib/math/test/random.0rt
--- a/lib/math/test/random.0rt
+++ b/lib/math/test/random.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "valid instantiations" {
-  success
+  success TestChecker
 }
 
 unittest exponential {
@@ -26,12 +26,12 @@
 }
 
 unittest gaussian {
-  Generator<Float> random <- RandomGaussian.new(0.0,1.0)
+  Generator<Float> random <- RandomGaussian.new(0.0, 1.0)
   Float value <- random.generate()
 }
 
 unittest uniform {
-  Generator<Float> random <- RandomUniform.new(0.0,1.0)
+  Generator<Float> random <- RandomUniform.new(0.0, 1.0)
   Float value <- random.generate()
 }
 
@@ -47,19 +47,19 @@
 
 unittest categorical {
   CategoricalTree<Float> tree <- CategoricalTree<Float>.new()
-      .setWeight(0.0,1)
-      .setWeight(1.0,1)
-      .setWeight(2.0,1)
-      .setWeight(3.0,1)
-      .setWeight(4.0,1)
+      .setWeight(0.0, 1)
+      .setWeight(1.0, 1)
+      .setWeight(2.0, 1)
+      .setWeight(3.0, 1)
+      .setWeight(4.0, 1)
   RandomCategorical<Float> random <- tree `RandomCategorical:sampleWith` RandomUniform.probability()
-  \ Testing.checkFalse(random.isEmpty())
+  \ random.isEmpty() `Matches:with` CheckValue:equals(false)
   Float value <- random.generate()
 }
 
 
 testcase "negative lambda in exponential" {
-  crash
+  failure
   require "lambda"
   require "-1"
 }
@@ -70,93 +70,93 @@
 
 
 testcase "negative standard deviation in gaussian" {
-  crash
+  failure
   require "standard deviation"
   require "-1"
 }
 
 unittest test {
-  Generator<Float> random <- RandomGaussian.new(0.0,-1.0)
+  Generator<Float> random <- RandomGaussian.new(0.0, -1.0)
 }
 
 
 testcase "empty range in uniform" {
-  crash
+  failure
   require "range"
   require "0,-1"
 }
 
 unittest test {
-  Generator<Float> random <- RandomUniform.new(0.0,-1.0)
+  Generator<Float> random <- RandomUniform.new(0.0, -1.0)
 }
 
 
 testcase "sampling with empty CategoricalTree" {
-  crash
+  failure TestChecker
   require "less than the total"
 }
 
 unittest categorical {
   CategoricalTree<Float> tree <- CategoricalTree<Float>.new()
   RandomCategorical<Float> random <- tree `RandomCategorical:sampleWith` RandomUniform.probability()
-  \ Testing.checkTrue(random.isEmpty())
+  \ random.isEmpty() `Matches:with` CheckValue:equals(true)
   Float value <- random.generate()
 }
 
 
 testcase "distribution sanity checks" {
-  success
+  success TestChecker
 }
 
 unittest exponential { $DisableCoverage$
   Int count <- 10000
   Float lambda <- 10.0
-  $ReadOnly[count,lambda]$
+  $ReadOnly[count, lambda]$
 
   Generator<Float> random <- RandomExponential.new(lambda)
 
   Float sum <- 0.0
   traverse (Counter.zeroIndexed(count) -> _) {
     Float value <- random.generate()
-    \ Testing.checkGreaterThan(value,0.0)
+    \ value `Matches:with` CheckValue:greaterThanNotEquals(0.0)
     sum <- sum+value
   }
 
-  \ Testing.checkBetween(sum/count.asFloat(),0.9/lambda,1.1/lambda)
+  \ sum/count.asFloat() `Matches:with` CheckValue:betweenEquals(0.9/lambda, 1.1/lambda)
 }
 
 unittest gaussian { $DisableCoverage$
   Int count <- 10000
   Float mean <- 100.0
   Float sd <- 1.0
-  $ReadOnly[count,mean,sd]$
+  $ReadOnly[count, mean, sd]$
 
-  Generator<Float> random <- RandomGaussian.new(mean,sd)
+  Generator<Float> random <- RandomGaussian.new(mean, sd)
 
   Float sum <- 0.0
   traverse (Counter.zeroIndexed(count) -> _) {
     sum <- sum+random.generate()
   }
 
-  \ Testing.checkBetween(sum/count.asFloat(),mean-0.1*sd,mean+0.1*sd)
+  \ sum/count.asFloat() `Matches:with` CheckValue:betweenEquals(mean-0.1*sd, mean+0.1*sd)
 }
 
 unittest uniform { $DisableCoverage$
   Int count <- 10000
   Float min <- -11.0
   Float max <- 5.0
-  $ReadOnly[count,min,max]$
+  $ReadOnly[count, min, max]$
 
-  Generator<Float> random <- RandomUniform.new(min,max)
+  Generator<Float> random <- RandomUniform.new(min, max)
 
   Float sum <- 0.0
   traverse (Counter.zeroIndexed(count) -> _) {
     Float value <- random.generate()
-    \ Testing.checkBetween(value,min,max)
+    \ value `Matches:with` CheckValue:betweenEquals(min, max)
     sum <- sum+value
   }
 
-  \ Testing.checkBetween(sum/count.asFloat(),(min+max)/2.0-0.1*(max-min),(min+max)/2.0+0.1*(max-min))
+  \ sum/count.asFloat() `Matches:with` CheckValue:betweenEquals((min+max)/2.0-0.1*(max-min), (min+max)/2.0+0.1*(max-min))
 }
 
 unittest probability { $DisableCoverage$
@@ -168,51 +168,51 @@
   Float sum <- 0.0
   traverse (Counter.zeroIndexed(count) -> _) {
     Float value <- random.generate()
-    \ Testing.checkBetween(value,0.0,1.0)
+    \ value `Matches:with` CheckValue:betweenEquals(0.0, 1.0)
     sum <- sum+value
   }
 
-  \ Testing.checkBetween(sum/count.asFloat(),0.4,0.6)
+  \ sum/count.asFloat() `Matches:with` CheckValue:betweenEquals(0.4, 0.6)
 }
 
 unittest constant { $DisableCoverage$
   Int count <- 10000
   Float constant <- 12345.0
-  $ReadOnly[count,constant]$
+  $ReadOnly[count, constant]$
 
   Generator<Float> random <- GenerateConstant.new(constant)
 
   traverse (Counter.zeroIndexed(count) -> _) {
     Float value <- random.generate()
-    \ Testing.checkEquals(constant,value)
+    \ constant `Matches:with` CheckValue:equals(value)
   }
 }
 
 unittest categorical { $DisableCoverage$
   Int count <- 10000
   CategoricalTree<Float> tree <- CategoricalTree<Float>.new()
-      .setWeight(0.0,1)
-      .setWeight(1.0,1)
-      .setWeight(2.0,1)
-      .setWeight(3.0,1)
-      .setWeight(4.0,1)
-  $ReadOnly[count,tree]$
+      .setWeight(0.0, 1)
+      .setWeight(1.0, 1)
+      .setWeight(2.0, 1)
+      .setWeight(3.0, 1)
+      .setWeight(4.0, 1)
+  $ReadOnly[count, tree]$
 
   Generator<Float> random <- tree `RandomCategorical:sampleWith` RandomUniform.probability()
 
   Float sum <- 0.0
   traverse (Counter.zeroIndexed(count) -> _) {
     Float value <- random.generate()
-    \ Testing.checkBetween(value,0.0,4.0)
+    \ value `Matches:with` CheckValue:betweenEquals(0.0, 4.0)
     sum <- sum+value
   }
 
-  \ Testing.checkBetween(sum/count.asFloat(),1.9,2.1)
+  \ sum/count.asFloat() `Matches:with` CheckValue:betweenEquals(1.9, 2.1)
 }
 
 
 testcase "distribution seed checks" {
-  success
+  success TestChecker
 }
 
 unittest exponential {
@@ -229,18 +229,18 @@
   random2 <- random2.setSeed(123)
   Float v4 <- random2.generate()
 
-  \ Testing.checkNotEquals(v1,v2)
-  \ Testing.checkNotEquals(v1,v3)
-  \ Testing.checkEquals(v3,v4)
+  \ v1 `Matches:with` CheckValue:notEquals(v2)
+  \ v1 `Matches:with` CheckValue:notEquals(v3)
+  \ v3 `Matches:with` CheckValue:equals(v4)
 }
 
 unittest gaussian {
   Float mean <- 100.0
   Float sd <- 1.0
-  $ReadOnly[mean,sd]$
+  $ReadOnly[mean, sd]$
 
-  RandomGaussian random1 <- RandomGaussian.new(mean,sd)
-  RandomGaussian random2 <- RandomGaussian.new(mean,sd)
+  RandomGaussian random1 <- RandomGaussian.new(mean, sd)
+  RandomGaussian random2 <- RandomGaussian.new(mean, sd)
 
   Float v1 <- random1.generate()
   Float v2 <- random2.generate()
@@ -249,18 +249,18 @@
   random2 <- random2.setSeed(123)
   Float v4 <- random2.generate()
 
-  \ Testing.checkNotEquals(v1,v2)
-  \ Testing.checkNotEquals(v1,v3)
-  \ Testing.checkEquals(v3,v4)
+  \ v1 `Matches:with` CheckValue:notEquals(v2)
+  \ v1 `Matches:with` CheckValue:notEquals(v3)
+  \ v3 `Matches:with` CheckValue:equals(v4)
 }
 
 unittest uniform {
   Float min <- -11.0
   Float max <- 5.0
-  $ReadOnly[min,max]$
+  $ReadOnly[min, max]$
 
-  RandomUniform random1 <- RandomUniform.new(min,max)
-  RandomUniform random2 <- RandomUniform.new(min,max)
+  RandomUniform random1 <- RandomUniform.new(min, max)
+  RandomUniform random2 <- RandomUniform.new(min, max)
 
   Float v1 <- random1.generate()
   Float v2 <- random2.generate()
@@ -269,21 +269,21 @@
   random2 <- random2.setSeed(123)
   Float v4 <- random2.generate()
 
-  \ Testing.checkNotEquals(v1,v2)
-  \ Testing.checkNotEquals(v1,v3)
-  \ Testing.checkEquals(v3,v4)
+  \ v1 `Matches:with` CheckValue:notEquals(v2)
+  \ v1 `Matches:with` CheckValue:notEquals(v3)
+  \ v3 `Matches:with` CheckValue:equals(v4)
 }
 
 
 testcase "Randomize tests" {
-  success
+  success TestChecker
 }
 
 unittest permuteFrom {
   CategoricalTree<String> tree <- CategoricalTree<String>.new()
-      .setWeight("a",2)
-      .setWeight("b",3)
-      .setWeight("c",1)
+      .setWeight("a", 2)
+      .setWeight("b", 3)
+      .setWeight("c", 1)
 
   Vector<String> expected <- Vector<String>.new()
       .append("a")
@@ -294,24 +294,24 @@
       .append("c")
 
   Vector<String> output <- Vector<String>.new()
-  \ Randomize:permuteFrom(tree,RandomUniform.new(0.0,1.0),output)
+  \ Randomize:permuteFrom(tree, RandomUniform.new(0.0, 1.0), output)
   \ Sorting:sort(output)
 
-  \ Testing.checkEquals(output.size(),expected.size())
+  \ output.size() `Matches:with` CheckValue:equals(expected.size())
   traverse (Counter.zeroIndexed(output.size()) -> Int pos) {
-    \ Testing.checkEquals(output.readAt(pos),expected.readAt(pos))
+    \ output.readAt(pos) `Matches:with` CheckValue:equals(expected.readAt(pos))
   }
 
-  \ Testing.checkEquals(tree.getWeight("a"),2)
-  \ Testing.checkEquals(tree.getWeight("b"),3)
-  \ Testing.checkEquals(tree.getWeight("c"),1)
+  \ tree.getWeight("a") `Matches:with` CheckValue:equals(2)
+  \ tree.getWeight("b") `Matches:with` CheckValue:equals(3)
+  \ tree.getWeight("c") `Matches:with` CheckValue:equals(1)
 }
 
 unittest permuteFromWeight {
   CategoricalTree<String> tree <- CategoricalTree<String>.new()
-      .setWeight("a",2)
-      .setWeight("b",3)
-      .setWeight("c",1)
+      .setWeight("a", 2)
+      .setWeight("b", 3)
+      .setWeight("c", 1)
 
   Vector<String> expected <- Vector<String>.new()
       .append("a")
@@ -319,15 +319,15 @@
       .append("c")
 
   Vector<String> output <- Vector<String>.new()
-  \ Randomize:permuteFromWeight(tree,RandomUniform.new(0.0,1.0),output)
+  \ Randomize:permuteFromWeight(tree, RandomUniform.new(0.0, 1.0), output)
   \ Sorting:sort(output)
 
-  \ Testing.checkEquals(output.size(),expected.size())
+  \ output.size() `Matches:with` CheckValue:equals(expected.size())
   traverse (Counter.zeroIndexed(output.size()) -> Int pos) {
-    \ Testing.checkEquals(output.readAt(pos),expected.readAt(pos))
+    \ output.readAt(pos) `Matches:with` CheckValue:equals(expected.readAt(pos))
   }
 
-  \ Testing.checkEquals(tree.getWeight("a"),2)
-  \ Testing.checkEquals(tree.getWeight("b"),3)
-  \ Testing.checkEquals(tree.getWeight("c"),1)
+  \ tree.getWeight("a") `Matches:with` CheckValue:equals(2)
+  \ tree.getWeight("b") `Matches:with` CheckValue:equals(3)
+  \ tree.getWeight("c") `Matches:with` CheckValue:equals(1)
 }
diff --git a/lib/math/test/token.0rt b/lib/math/test/token.0rt
--- a/lib/math/test/token.0rt
+++ b/lib/math/test/token.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,26 +17,26 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "basic Token tests" {
-  success
+  success TestChecker
 }
 
 unittest equals {
-  \ Testing.checkEquals(Token.from("message"),Token.from("message"))
-  \ Testing.checkFalse(Token.from("message") `Token.equals` Token.from("other"))
+  \ Token.from("message") `Matches:with` CheckValue:equals(Token.from("message"))
+  \ Token.from("message") `Token.equals` Token.from("other") `Matches:with` CheckValue:equals(false)
 }
 
 unittest lessThan {
-  \ Testing.checkFalse(Token.from("message") `Token.lessThan` Token.from("message"))
-  \ Testing.checkTrue((Token.from("message") `Token.lessThan` Token.from("other")) ||
-                      (Token.from("other") `Token.lessThan` Token.from("message")))
+  \ Token.from("message") `Token.lessThan` Token.from("message") `Matches:with` CheckValue:equals(false)
+  \ ((Token.from("message") `Token.lessThan` Token.from("other")) ^
+     (Token.from("other") `Token.lessThan` Token.from("message"))) `Matches:with` CheckValue:equals(true)
 }
 
 unittest formatted {
-  \ Testing.checkEquals(Token.from("message").formatted(),"message")
+  \ Token.from("message").formatted() `Matches:with` CheckValue:equals("message")
 }
 
 unittest hashed {
-  \ Testing.checkNotEquals(Token.from("message").hashed(),Token.from("other").hashed())
-  \ Testing.checkNotEquals(Token.from("message").hashed(),"message".hashed())
-  \ Testing.checkEquals(Token.from("message").hashed(),Token.from("message").hashed())
+  \ Token.from("message").hashed() `Matches:with` CheckValue:notEquals(Token.from("other").hashed())
+  \ Token.from("message").hashed() `Matches:with` CheckValue:notEquals("message".hashed())
+  \ Token.from("message").hashed() `Matches:with` CheckValue:equals(Token.from("message").hashed())
 }
diff --git a/lib/testing/.zeolite-module b/lib/testing/.zeolite-module
--- a/lib/testing/.zeolite-module
+++ b/lib/testing/.zeolite-module
@@ -1,7 +1,19 @@
 root: "../.."
 path: "lib/testing"
+expression_map: [
+  expression_macro {
+    name: FLOAT_RELATIVE_THRESHOLD
+    expression: 1.0E-7
+  }
+]
 extra_paths: [
   "lib/testing/src"
   "lib/testing/test"
 ]
-mode: incremental {}
+extra_files: [
+  category_source {
+    source: "lib/testing/src/Extension_TestHandler.cpp"
+    categories: [TestHandler]
+  }
+]
+mode: incremental { }
diff --git a/lib/testing/check-float.0rp b/lib/testing/check-float.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/check-float.0rp
@@ -0,0 +1,28 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+// Simple Float checks.
+concrete CheckFloat {
+  // Uses relative threshold of $ExprLookup[FLOAT_RELATIVE_THRESHOLD]$ times the
+  // max absolute value of the values being compared.
+  // NOTE: This will never match 0 unless exactly 0 is expected.
+  @category almostEquals (AsFloat) -> (ValueMatcher<[AsFloat & Formatted]>)
+
+  // Uses absolute threshold.
+  @category closeTo (AsFloat, Float epsilon:) -> (ValueMatcher<[AsFloat & Formatted]>)
+}
diff --git a/lib/testing/check-sequence.0rp b/lib/testing/check-sequence.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/check-sequence.0rp
@@ -0,0 +1,33 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+// Checks sequences of values.
+concrete CheckSequence {
+  @category using<#x>
+    #x requires TestCompare<#x>
+  (DefaultOrder<#x>) -> (DefaultOrder<ValueMatcher<#x>>)
+
+  @category equals<#x>
+    #x requires Formatted
+    #x defines Equals<#x>
+  (DefaultOrder<#x>) -> (ValueMatcher<DefaultOrder<#x>>)
+
+  @category matches<#x> (DefaultOrder<ValueMatcher<#x>>) -> (ValueMatcher<DefaultOrder<#x>>)
+
+  @category allMatch<#x> (ValueMatcher<#x>) -> (ValueMatcher<DefaultOrder<#x>>)
+}
diff --git a/lib/testing/check-string.0rp b/lib/testing/check-string.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/check-string.0rp
@@ -0,0 +1,24 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+// Simple sub-string checks.
+concrete CheckString {
+  @category contains (Formatted) -> (ValueMatcher<Formatted>)
+  @category startsWith (Formatted) -> (ValueMatcher<Formatted>)
+  @category endsWith (Formatted) -> (ValueMatcher<Formatted>)
+}
diff --git a/lib/testing/check-value.0rp b/lib/testing/check-value.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/check-value.0rp
@@ -0,0 +1,67 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+// Simple equality-based value checks.
+concrete CheckValue<#x|> {
+  @category using<#x>
+    #x requires TestCompare<#x>
+  (#x) -> (ValueMatcher<#x>)
+
+  @category equals<#x>
+    #x requires Formatted
+    #x defines Equals<#x>
+  (optional #x) -> (ValueMatcher<#x>)
+
+  @category notEquals<#x>
+    #x requires Formatted
+    #x defines Equals<#x>
+  (optional #x) -> (ValueMatcher<#x>)
+
+  // Value has the same Identifier.
+  @category is<#x> (optional #x) -> (ValueMatcher<#x>)
+
+  @category lessThanNotEquals<#x>
+    #x requires Formatted
+    #x defines LessThan<#x>
+  (#x) -> (ValueMatcher<#x>)
+
+  @category greaterThanNotEquals<#x>
+    #x requires Formatted
+    #x defines LessThan<#x>
+  (#x) -> (ValueMatcher<#x>)
+
+  @category betweenNotEquals<#x>
+    #x requires Formatted
+    #x defines LessThan<#x>
+  (#x, #x) -> (ValueMatcher<#x>)
+
+  @category lessThanEquals<#x>
+    #x requires Formatted
+    #x defines LessThan<#x>
+  (#x) -> (ValueMatcher<#x>)
+
+  @category greaterThanEquals<#x>
+    #x requires Formatted
+    #x defines LessThan<#x>
+  (#x) -> (ValueMatcher<#x>)
+
+  @category betweenEquals<#x>
+    #x requires Formatted
+    #x defines LessThan<#x>
+  (#x, #x) -> (ValueMatcher<#x>)
+}
diff --git a/lib/testing/checker.0rp b/lib/testing/checker.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/checker.0rp
@@ -0,0 +1,32 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$TestsOnly$
+
+// NOTE: You must set the testcase checker in order to use this.
+//
+// testcase "my testcase" {
+//    success TestChecker
+// }
+concrete TestChecker {
+  defines Testcase
+
+  @type isFailing () -> (Bool)
+  @type checkNow () -> ()
+  @type newReport (Formatted title:, optional Order<Formatted> context:) -> (TestReport)
+}
diff --git a/lib/testing/evaluation.0rp b/lib/testing/evaluation.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/evaluation.0rp
@@ -0,0 +1,54 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$TestsOnly$
+
+// NOTE: You must specify TestChecker in order to use this.
+//
+// testcase "my testcase" {
+//    success TestChecker
+// }
+concrete Matches {
+  // Fails immediately if match fails.
+  @category with<#x> (optional #x, ValueMatcher<#x>) -> ()
+
+  // Fails later if match fails.
+  @category tryWith<#x> (optional #x, ValueMatcher<#x>) -> ()
+
+  // Fails immediately if match fails.
+  @category value<#x>
+    #x requires TestCompare<#x>
+  (optional #x, #x) -> ()
+
+  // Fails later if match fails.
+  @category tryValue<#x>
+    #x requires TestCompare<#x>
+  (optional #x, #x) -> ()
+}
+
+// Unconditional outcomes regardless of the actual value.
+concrete CheckAlways {
+  // Unconditionally match.
+  @category match () -> (ValueMatcher<any>)
+
+  // Unconditional error.
+  @category error (Formatted) -> (ValueMatcher<any>)
+
+  // Immediate crash if the matcher is used to check a value.
+  @category die (Formatted) -> (ValueMatcher<any>)
+}
diff --git a/lib/testing/helpers.0rp b/lib/testing/helpers.0rp
deleted file mode 100644
--- a/lib/testing/helpers.0rp
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2020-2021 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]
-
-$TestsOnly$
-
-// General helpers for use in unit tests.
-concrete Testing {
-  // Check the values for equality. Crashes if they are not equal.
-  //
-  // Args:
-  // - #x: Actual value.
-  // - #x: Expected value.
-  @type checkEquals<#x>
-    #x requires Formatted
-    #x defines Equals<#x>
-  (#x,#x) -> ()
-
-  // Check the values for inequality. Crashes if they are equal.
-  //
-  // Args:
-  // - #x: Actual value.
-  // - #x: Expected value.
-  @type checkNotEquals<#x>
-    #x requires Formatted
-    #x defines Equals<#x>
-  (#x,#x) -> ()
-
-  // Check the value for empty. Crashes if present.
-  //
-  // Args:
-  // - #x: Actual value.
-  @type checkEmpty<#x> (optional #x) -> ()
-
-  // Check the value for present. Crashes if empty.
-  //
-  // Args:
-  // - optional any: Actual value.
-  @type checkPresent (optional any) -> ()
-
-  // Check the optional values for equality. Crashes if they are not equal.
-  //
-  // Args:
-  // - optional #x: Actual value.
-  // - optional #x: Expected value.
-  @type checkOptional<#x>
-    #x requires Formatted
-    #x defines Equals<#x>
-  (optional #x,optional #x) -> ()
-
-  // Check the value for true. Crashes if false.
-  //
-  // Args:
-  // - AsBool: Actual value.
-  @type checkTrue (AsBool) -> ()
-
-  // Check the value for false. Crashes if true.
-  //
-  // Args:
-  // - AsBool: Actual value.
-  @type checkFalse (AsBool) -> ()
-
-  // Check that the value is within a range. Crashes if it is not in the range.
-  //
-  // Args:
-  // - #x: Actual value.
-  // - #x: Lower bound.
-  // - #x: Upper bound.
-  //
-  // Notes:
-  // - This could be done without Equals, except that some types have
-  //   "undefined" values (e.g., NaN, for Float) that prevent inferring equality
-  //   from less-than comparisons.
-  @type checkBetween<#x>
-    #x requires Formatted
-    #x defines Equals<#x>
-    #x defines LessThan<#x>
-  (#x,#x,#x) -> ()
-
-  // Check that the value is above the limit. Crashes if it is not in the range.
-  //
-  // Args:
-  // - #x: Actual value.
-  // - #x: Lower bound.
-  //
-  // Notes:
-  // - This could be done without Equals, except that some types have
-  //   "undefined" values (e.g., NaN, for Float) that prevent inferring equality
-  //   from less-than comparisons.
-  // - This comparison doesn't allow the value to equal the bound.
-  @type checkGreaterThan<#x>
-    #x requires Formatted
-    #x defines Equals<#x>
-    #x defines LessThan<#x>
-  (#x,#x) -> ()
-
-  // Check that the value is below the limit. Crashes if it is not in the range.
-  //
-  // Args:
-  // - #x: Actual value.
-  // - #x: Upper bound.
-  //
-  // Notes:
-  // - This could be done without Equals, except that some types have
-  //   "undefined" values (e.g., NaN, for Float) that prevent inferring equality
-  //   from less-than comparisons.
-  // - This comparison doesn't allow the value to equal the bound.
-  @type checkLessThan<#x>
-    #x requires Formatted
-    #x defines Equals<#x>
-    #x defines LessThan<#x>
-  (#x,#x) -> ()
-}
diff --git a/lib/testing/matching.0rp b/lib/testing/matching.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/matching.0rp
@@ -0,0 +1,66 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+// Supports test comparisons.
+// NOTE: This should be refined in #x.
+@value interface TestCompare<#x|> {
+  testCompare (#x actual:, TestReport) -> ()
+}
+
+// Base interface for custom value matchers.
+@value interface ValueMatcher<#x|> {
+  check<#y>
+    #y requires #x
+  (optional #y, TestReport) -> ()
+
+  summary () -> (Formatted)
+}
+
+// Interface used by predicate testers and matchers to report errors.
+@value interface TestReport {
+  // Adds a new error message.
+  addError (Formatted message:) -> (#self)
+
+  // Adds a child section without changing the error status.
+  newSection (Formatted title:) -> (#self)
+
+  // Discards error messages and child sections.
+  discardReport () -> (#self)
+
+  // Returns true if there are error messages, or if a child has an error.
+  hasError () -> (Bool)
+}
+
+// Helper to check multiple members of a single object.
+concrete MultiChecker {
+  @value check<#x> (Formatted title:, optional #x, ValueMatcher<#x>) -> (optional #self)
+  @value tryCheck<#x> (Formatted title:, optional #x, ValueMatcher<#x>) -> (#self)
+
+  // Can only be constructed by value checkers.
+  visibility ValueMatcher<all>, TestCompare<all>
+
+  @type new (TestReport) -> (#self)
+}
+
+concrete MatcherCompose {
+  @category not<#x> (ValueMatcher<#x>) -> (ValueMatcher<#x>)
+  @category or<#x> (ValueMatcher<#x>, ValueMatcher<#x>) -> (ValueMatcher<#x>)
+  @category and<#x> (ValueMatcher<#x>, ValueMatcher<#x>) -> (ValueMatcher<#x>)
+  @category anyOf<#x> (DefaultOrder<ValueMatcher<#x>>) -> (ValueMatcher<#x>)
+  @category allOf<#x> (DefaultOrder<ValueMatcher<#x>>) -> (ValueMatcher<#x>)
+}
diff --git a/lib/testing/src/Extension_TestHandler.cpp b/lib/testing/src/Extension_TestHandler.cpp
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/Extension_TestHandler.cpp
@@ -0,0 +1,59 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 <cstdlib>
+#include <iostream>
+
+#include "category-source.hpp"
+#include "Streamlined_TestHandler.hpp"
+#include "Category_String.hpp"
+#include "Category_TestHandler.hpp"
+
+#ifdef ZEOLITE_PRIVATE_NAMESPACE
+namespace ZEOLITE_PRIVATE_NAMESPACE {
+#endif  // ZEOLITE_PRIVATE_NAMESPACE
+
+struct ExtCategory_TestHandler : public Category_TestHandler {
+  ReturnTuple Call_failAndExit(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("TestHandler:failAndExit")
+    const BoxedValue& Var_arg1 = params_args.GetArg(0);
+    std::cerr << Var_arg1.AsString();
+    std::exit(1);
+  }
+};
+
+struct ExtType_TestHandler : public Type_TestHandler {
+  inline ExtType_TestHandler(Category_TestHandler& p, Params<0>::Type params) : Type_TestHandler(p, params) {}
+};
+
+Category_TestHandler& CreateCategory_TestHandler() {
+  static auto& category = *new ExtCategory_TestHandler();
+  return category;
+}
+
+S<const Type_TestHandler> CreateType_TestHandler(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_TestHandler(CreateCategory_TestHandler(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_TestHandler(const Params<0>::Type& params) {}
+
+#ifdef ZEOLITE_PRIVATE_NAMESPACE
+}  // namespace ZEOLITE_PRIVATE_NAMESPACE
+using namespace ZEOLITE_PRIVATE_NAMESPACE;
+#endif  // ZEOLITE_PRIVATE_NAMESPACE
diff --git a/lib/testing/src/check-float.0rx b/lib/testing/src/check-float.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/check-float.0rx
@@ -0,0 +1,92 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 CheckFloat {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<[AsFloat & Formatted]>
+
+  @value optional Float epsilon
+  @value Float expected
+
+  almostEquals (expected) {
+    return CheckFloat{ empty, expected.asFloat() }
+  }
+
+  closeTo (expected, epsilon) {
+    return CheckFloat{ epsilon, expected.asFloat() }
+  }
+
+  check (actual, report) {
+    optional Float threshold <- epsilon
+    if (`present` actual) {
+      Float value <- require(actual).asFloat()
+      $Hidden[actual]$
+      if (`present` epsilon) {
+        threshold <- `require` epsilon
+      } else {
+        threshold <- $ExprLookup[FLOAT_RELATIVE_THRESHOLD]$ * getMaxAbs(value, expected)
+      }
+      $Hidden[epsilon]$
+      if (abs(value - expected) <= `require` threshold) {
+        return _
+      }
+    }
+    $Hidden[epsilon]$
+    if (`present` threshold) {
+      \ report.addError(message: String.builder()
+          .append(`Format:autoFormat` actual)
+          .append(" is not within ")
+          .append(`require` threshold)
+          .append(" of ")
+          .append(expected)
+          .build())
+    } else {
+      \ report.addError(message: String.builder()
+          .append(`Format:autoFormat` actual)
+          .append(" is not close to ")
+          .append(expected)
+          .build())
+    }
+  }
+
+  summary () {
+    if (`present` epsilon) {
+      return "value is close to"
+    } else {
+      return "value is almost equal to"
+    }
+  }
+
+  @type abs (Float) -> (Float)
+  abs (x) {
+    if (x < 0.0) {
+      return -x
+    } else {
+      return x
+    }
+  }
+
+  @type getMaxAbs (Float, Float) -> (Float)
+  getMaxAbs (x, y) (max) {
+    max <- abs(x)
+    if (abs(y) > max) {
+      max <- abs(y)
+    }
+  }
+}
diff --git a/lib/testing/src/check-sequence.0rx b/lib/testing/src/check-sequence.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/check-sequence.0rx
@@ -0,0 +1,219 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 CheckSequence {
+  using (expected) {
+    return SequenceAdapter<#x>.new(expected)
+  }
+
+  equals (expected) {
+    return SequenceEquals<#x>.new(expected)
+  }
+
+  matches (expected) {
+    return SequenceMatches<#x>.new(expected)
+  }
+
+  allMatch (expected) {
+    return SequenceAllMatch<#x>.new(expected)
+  }
+}
+
+concrete SequenceAdapter<#x> {
+  #x requires TestCompare<#x>
+
+  @type new (DefaultOrder<#x>) -> (DefaultOrder<ValueMatcher<#x>>)
+}
+
+define SequenceAdapter {
+  $ReadOnlyExcept[current]$
+
+  refines DefaultOrder<ValueMatcher<#x>>
+  refines Order<ValueMatcher<#x>>
+
+  @value DefaultOrder<#x> original
+  @value optional Order<#x> current
+
+  new (original) {
+    return #self{ original, empty }
+  }
+
+  defaultOrder () {
+    return #self{ original, original.defaultOrder() }
+  }
+
+  get () {
+    return CheckValue:using(`require` current&.get())
+  }
+
+  next () {
+    if (`present` (current <- current&.next())) {
+      return self
+    } else {
+      return empty
+    }
+  }
+}
+
+concrete SequenceEquals<#x> {
+  #x requires Formatted
+  #x defines Equals<#x>
+
+  @type new (DefaultOrder<#x>) -> (ValueMatcher<DefaultOrder<#x>>)
+}
+
+define SequenceEquals {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<DefaultOrder<#x>>
+
+  @value DefaultOrder<#x> expected
+
+  new (expected) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    optional Order<#x> currentActual   <- actual&.defaultOrder()
+    optional Order<#x> currentExpected <- expected.defaultOrder()
+    Int index <- 0
+    $Hidden[actual, expected]$
+    while (`present` currentActual || `present` currentExpected) {
+      optional #x valueActual   <- currentActual&.get()
+      optional #x valueExpected <- currentExpected&.get()
+      TestReport newReport <- report.newSection(title: String.builder()
+          .append("Item #")
+          .append(index)
+          .build())
+      $Hidden[report]$
+      if (! `present` valueActual) {
+        \ newReport.addError(message: String.builder()
+            .append(`Format:autoFormat` valueExpected)
+            .append(" is missing")
+            .build())
+      } elif (! `present` valueExpected) {
+        \ newReport.addError(message: String.builder()
+            .append(`Format:autoFormat` valueActual)
+            .append(" is unexpected")
+            .build())
+      } elif (!(require(valueActual) `#x.equals` require(valueExpected))) {
+        \ newReport.addError(message: String.builder()
+            .append(`Format:autoFormat` valueActual)
+            .append(" does not equal ")
+            .append(`Format:autoFormat` valueExpected)
+            .build())
+      }
+    } update {
+      currentActual <- currentActual&.next()
+      currentExpected <- currentExpected&.next()
+      index <- index+1
+    }
+  }
+
+  summary () {
+    return "sequence has equal values"
+  }
+}
+
+concrete SequenceMatches<#x> {
+  @type new (DefaultOrder<ValueMatcher<#x>>) -> (ValueMatcher<DefaultOrder<#x>>)
+}
+
+define SequenceMatches {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<DefaultOrder<#x>>
+
+  @value DefaultOrder<ValueMatcher<#x>> expected
+
+  new (expected) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    optional Order<#x> currentActual <- actual&.defaultOrder()
+    optional Order<ValueMatcher<#x>> currentExpected <- expected.defaultOrder()
+    Int index <- 0
+    $Hidden[actual, expected]$
+    while (`present` currentActual || `present` currentExpected) {
+      optional #x valueActual <- currentActual&.get()
+      optional ValueMatcher<#x> valueExpected <- currentExpected&.get()
+      TestReport newReport <- report.newSection(title: String.builder()
+          .append("Item #")
+          .append(index)
+          .build())
+      $Hidden[report]$
+      if (! `present` valueActual) {
+        \ newReport.addError(message: String.builder()
+            .append("Item #")
+            .append(index)
+            .append(" is missing")
+            .build())
+      } elif (! `present` valueExpected) {
+        \ newReport.addError(message: String.builder()
+            .append("Item #")
+            .append(index)
+            .append(" is unexpected")
+            .build())
+      } else {
+        \ valueActual `require(valueExpected).check` newReport
+      }
+    } update {
+      currentActual <- currentActual&.next()
+      currentExpected <- currentExpected&.next()
+      index <- index+1
+    }
+  }
+
+  summary () {
+    return "sequence values separately match"
+  }
+}
+
+concrete SequenceAllMatch<#x> {
+  @type new (ValueMatcher<#x>) -> (ValueMatcher<DefaultOrder<#x>>)
+}
+
+define SequenceAllMatch {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<DefaultOrder<#x>>
+
+  @value ValueMatcher<#x> expected
+
+  new (expected) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    scoped {
+      Int index <- 0
+    } in traverse (actual&.defaultOrder() -> #x value) {
+      TestReport newReport <- report.newSection(title: String.builder()
+          .append("Item #")
+          .append(index)
+          .build())
+      $Hidden[report]$
+      \ value `expected.check` newReport
+    }
+  }
+
+  summary () {
+    return "sequence values all match"
+  }
+}
diff --git a/lib/testing/src/check-string.0rx b/lib/testing/src/check-string.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/check-string.0rx
@@ -0,0 +1,96 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 CheckString {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<Formatted>
+
+  @value String expected
+  @value Bool fromStart
+  @value Bool toEnd
+
+  contains (expected) {
+    return CheckString{ expected.formatted(), false, false }
+  }
+
+  startsWith (expected) {
+    return CheckString{ expected.formatted(), true, false }
+  }
+
+  endsWith (expected) {
+    return CheckString{ expected.formatted(), false, true }
+  }
+
+  check (actual, report) {
+    if (! `present` actual) {
+      \ report.addError(message: appendErrorAndBuild(String.builder().append("empty")))
+      return _
+    }
+    String value <- require(actual).formatted()
+    $Hidden[actual]$
+    Int startIndex <- 0
+    if (toEnd) {
+      startIndex <- value.size()-expected.size()
+    }
+    while (startIndex >= 0 && startIndex+expected.size() <= value.size()) {
+      Bool matches <- true
+      scoped {
+        Int index <- startIndex
+        $Hidden[startIndex]$
+      } in traverse (expected.defaultOrder() -> Char char) {
+        $Hidden[expected]$
+        if (value.readAt(index) != char) {
+          matches <- false
+          break
+        }
+      } update {
+        index <- index+1
+      }
+      if (matches) {
+        return _
+      } elif (fromStart || toEnd) {
+        break
+      }
+    } update {
+      startIndex <- startIndex+1
+    }
+    \ report.addError(message: appendErrorAndBuild(String.builder().append(`Format:autoFormat` value)))
+  }
+
+  summary () {
+    if (fromStart) {
+      return "String starts with"
+    } elif (toEnd) {
+      return "String ends with"
+    } else {
+      return "String contains"
+    }
+  }
+
+  @value appendErrorAndBuild ([Append<Formatted> & Build<String>]) -> (String)
+  appendErrorAndBuild (output) {
+    if (fromStart) {
+      return output.append(" does not start with ").append(`Format:autoFormat` expected).build()
+    } elif (toEnd) {
+      return output.append(" does not end with ").append(`Format:autoFormat` expected).build()
+    } else {
+      return output.append(" does not contain ").append(`Format:autoFormat` expected).build()
+    }
+  }
+}
diff --git a/lib/testing/src/check-value.0rx b/lib/testing/src/check-value.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/check-value.0rx
@@ -0,0 +1,269 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 CheckValue {
+  using (expected) {
+    return ValueTester<#x>.new(expected)
+  }
+
+  equals (expected) {
+    return ValueEquals<#x>.new(false, expected)
+  }
+
+  notEquals (expected) {
+    return ValueEquals<#x>.new(true, expected)
+  }
+
+  is (expected) {
+    return ValueIdentifier<#x>.new(expected)
+  }
+
+  lessThanNotEquals (upper) {
+    return ValueBounds<#x>.new(false, empty, upper)
+  }
+
+  greaterThanNotEquals (lower) {
+    return ValueBounds<#x>.new(false, lower, empty)
+  }
+
+  betweenNotEquals (lower, upper) {
+    return ValueBounds<#x>.new(false, lower, upper)
+  }
+
+  lessThanEquals (upper) {
+    return ValueBounds<#x>.new(true, empty, upper)
+  }
+
+  greaterThanEquals (lower) {
+    return ValueBounds<#x>.new(true, lower, empty)
+  }
+
+  betweenEquals (lower, upper) {
+    return ValueBounds<#x>.new(true, lower, upper)
+  }
+}
+
+concrete ValueTester<#x> {
+  #x requires TestCompare<#x>
+
+  @type new (#x) -> (ValueMatcher<#x>)
+}
+
+define ValueTester {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<#x>
+
+  @value #x expected
+
+  new (expected) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    if (! `present` actual) {
+      \ report.addError(message: "expected value but got empty")
+    } else {
+      \ expected.testCompare(actual: require(actual), report)
+    }
+  }
+
+  summary () {
+    return String.builder()
+        .append("matches ")
+        .append(typename<#x>())
+        .build()
+  }
+}
+
+concrete ValueEquals<#x> {
+  #x requires Formatted
+  #x defines Equals<#x>
+
+  @type new (Bool, optional #x) -> (ValueMatcher<#x>)
+}
+
+define ValueEquals {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<#x>
+
+  @value Bool invert
+  @value optional #x expected
+
+  new (invert, expected) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    Bool match <- defer
+    if (`present` actual && `present` expected) {
+      match <- require(actual) `#x.equals` require(expected)
+    } else {
+      match <- ! `present` actual && ! `present` expected
+    }
+    if (invert && match) {
+      \ report.addError(message: String.builder()
+          .append(`Format:autoFormat` actual)
+          .append(" equals ")
+          .append(`Format:autoFormat` expected)
+          .build())
+    } elif (!invert && !match) {
+      \ report.addError(message: String.builder()
+          .append(`Format:autoFormat` actual)
+          .append(" does not equal ")
+          .append(`Format:autoFormat` expected)
+          .build())
+    }
+  }
+
+  summary () {
+    if (invert) {
+      return "value not equals"
+    } else {
+      return "value equals"
+    }
+  }
+}
+
+concrete ValueIdentifier<#x> {
+  @type new (optional #x) -> (ValueMatcher<#x>)
+}
+
+define ValueIdentifier {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<#x>
+
+  // NOTE: Don't use #x here so that it doesn't create a reference.
+  @value Identifier<#x> expected
+
+  new (expected) {
+    return #self{ `identify` expected }
+  }
+
+  check (actual, report) {
+    if (`identify` actual != expected) {
+      \ report.addError(message: String.builder()
+          .append(`format` `identify` actual)
+          .append(" does not equal ")
+          .append(`format` expected)
+          .build())
+    }
+  }
+
+  summary () {
+    return "value is same instance as"
+  }
+
+  @type format (Identifier<#x>) -> (Formatted)
+  format (id) {
+    return String.builder()
+        .append(typename<#x>())
+        .append("@")
+        .append(id)
+        .build()
+  }
+}
+
+concrete ValueBounds<#x> {
+  #x requires Formatted
+  #x defines LessThan<#x>
+
+  @type new (Bool, optional #x, optional #x) -> (ValueMatcher<#x>)
+}
+
+define ValueBounds {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<#x>
+
+  @value Bool inclusive
+  @value optional #x lower
+  @value optional #x upper
+
+  new (inclusive, lower, upper) {
+    return delegate -> #self
+  }
+
+  check (actual, report) {
+    Bool match <- `present` actual
+    if (match && `present` lower) {
+      match <- require(lower) `compare` require(actual)
+    }
+    if (match && `present` upper) {
+      match <- require(actual) `compare` require(upper)
+    }
+    if (!match) {
+      if (`present` lower && `present` upper) {
+        \ report.addError(message: String.builder()
+            .append(`Format:autoFormat` actual)
+            .append(" is not between ")
+            .append(`Format:autoFormat` lower)
+            .append(" and ")
+            .append(`Format:autoFormat` upper)
+            .build())
+      } elif (`present` lower) {
+        \ report.addError(message: String.builder()
+            .append(`Format:autoFormat` actual)
+            .append(" is not greater than ")
+            .append(`Format:autoFormat` lower)
+            .build())
+      } elif (`present` upper) {
+        \ report.addError(message: String.builder()
+            .append(`Format:autoFormat` actual)
+            .append(" is not less than ")
+            .append(`Format:autoFormat` upper)
+            .build())
+      }
+    }
+  }
+
+  summary () {
+    if (inclusive) {
+      if (`present` lower && `present` upper) {
+        return "value is between or equal"
+      } elif (`present` lower) {
+        return "value is greater than or equal"
+      } elif (`present` upper) {
+        return "value is less than or equal"
+      } else {
+        return "value is present"
+      }
+    } else {
+      if (`present` lower && `present` upper) {
+        return "value is between but not equal"
+      } elif (`present` lower) {
+        return "value is greater than but not equal"
+      } elif (`present` upper) {
+        return "value is less than but not equal"
+      } else {
+        return "value is present"
+      }
+    }
+  }
+
+  @value compare (#x, #x) -> (Bool)
+  compare (x, y) {
+    if (inclusive) {
+      return !(y `#x.lessThan` x)
+    } else {
+      return x `#x.lessThan` y
+    }
+  }
+}
diff --git a/lib/testing/src/checker.0rx b/lib/testing/src/checker.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/checker.0rx
@@ -0,0 +1,110 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$TestsOnly$
+
+define TestChecker {
+  @category Bool started <- false
+  @category Bool inProgress <- false
+  @category Int testCount <- 0
+  @category ValueList<TestReportTree> reports <- ValueList<TestReportTree>.new()
+
+  start () { $NoTrace$
+    if (started) {
+      fail("Testcase already started.")
+    }
+    started <- true
+    inProgress <- true
+  }
+
+  finish () { $NoTrace$
+    \ checkTestingStarted()
+    if (inProgress) {
+      inProgress <- false
+      \ checkNow()
+    }
+  }
+
+  isFailing () { $NoTrace$
+    \ checkTestingStarted()
+    traverse (reports.defaultOrder() -> TestReportTree report) {
+      if (report.hasError()) {
+        return true
+      }
+    }
+    return false
+  }
+
+  checkNow () { $NoTrace$
+    \ checkTestingStarted()
+    if (isFailing()) {
+      \ TestHandler:failAndExit(formatErrors().formatted())
+    }
+  }
+
+  newReport (title, context) { $NoTrace$
+    \ checkTestingAllowed()
+    ValueList<Formatted> contextList <- ValueList<Formatted>.new()
+    traverse (context -> Formatted item) {
+      String stringItem <- item.formatted()
+      // TODO: Get rid of this hard coding.
+      if (stringItem == "From testcase") {
+        break
+      } else {
+        \ contextList.append(stringItem)
+      }
+    }
+    String numberedTitle <- String.builder()
+        .append("Check #")
+        .append((testCount <- testCount+1))
+        .append(": ")
+        .append(title)
+        .build()
+    TestReportTree report <- TestReportTree.new(numberedTitle, contextList)
+    \ reports.append(report)
+    return report
+  }
+
+  @category formatErrors () -> (Formatted)
+  formatErrors () {
+    IndentAppend output <- IndentAppend.new(String.builder())
+    \ output.append("TestChecker failures:")
+    scoped {
+      \ output.pushIndent()
+    } cleanup {
+      \ output.popIndent()
+    } in traverse (reports.defaultOrder() -> TestReportTree report) {
+      \ report.prune()&.writeTo(output)&.addNewline()
+    }
+    return output.build()
+  }
+
+  @category checkTestingStarted () -> ()
+  checkTestingStarted () {
+    if (!started) {
+      fail("Testcase was not started.")
+    }
+  }
+
+  @category checkTestingAllowed () -> ()
+  checkTestingAllowed () {
+    if (!started || !inProgress) {
+      fail("Testcase not started or no longer in progress.")
+    }
+  }
+}
diff --git a/lib/testing/src/evaluation.0rx b/lib/testing/src/evaluation.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/evaluation.0rx
@@ -0,0 +1,109 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$TestsOnly$
+
+define Matches {
+  with (value, matcher) { $NoTrace$
+    if (delegate -> `executeMatch`) {
+      \ TestChecker.checkNow()
+    }
+  }
+
+  tryWith (value, matcher) { $NoTrace$
+    \ delegate -> `executeMatch`
+  }
+
+  value (actual, expected) { $NoTrace$
+    if (delegate -> `executeValue`) {
+      \ TestChecker.checkNow()
+    }
+  }
+
+  tryValue (actual, expected) { $NoTrace$
+    \ delegate -> `executeValue`
+  }
+
+  @category executeMatch<#x> (optional #x, ValueMatcher<#x>) -> (Bool)
+  executeMatch (value, matcher) { $NoTrace$
+    String title <- String.builder()
+        .append("Matches with ")
+        .append(matcher.summary())
+        .build()
+    TestReport report <- TestChecker.newReport(title: title, context: $CallTrace$)
+    \ value `matcher.check` report
+    return report.hasError()
+  }
+
+  @category executeValue<#x>
+    #x requires TestCompare<#x>
+  (optional #x, #x) -> (Bool)
+  executeValue (actual, expected) { $NoTrace$
+    String title <- String.builder()
+        .append("Matches ")
+        .append(typename<#x>())
+        .build()
+    TestReport report <- TestChecker.newReport(title: title, context: $CallTrace$)
+    if (! `present` actual) {
+      \ report.addError(message: "expected value but got empty")
+    } else {
+      \ expected.testCompare(actual: require(actual), report)
+    }
+    return report.hasError()
+  }
+}
+
+define CheckAlways {
+  $ReadOnlyExcept[]$
+
+  refines ValueMatcher<any>
+
+  @value Bool die
+  @value optional Formatted message
+
+  match () {
+    return CheckAlways{ false, empty }
+  }
+
+  error (message) {
+    return CheckAlways{ false, message }
+  }
+
+  die (message) {
+    return CheckAlways{ true, message }
+  }
+
+  check (_, report) {
+    if (! `present` message) {
+    } elif (die) {
+      fail(`require` message)
+    } else {
+      \ report.addError(message: `require` message)
+    }
+  }
+
+  summary () {
+    if (die) {
+      return "always die"
+    } elif (`present` message) {
+      return "always error"
+    } else {
+      return "always pass"
+    }
+  }
+}
diff --git a/lib/testing/src/helpers.0rx b/lib/testing/src/helpers.0rx
deleted file mode 100644
--- a/lib/testing/src/helpers.0rx
+++ /dev/null
@@ -1,135 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2020-2021 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]
-
-$TestsOnly$
-
-define Testing {
-  checkEquals (x,y) { $NoTrace$
-    if (!#x.equals(x,y)) {
-      fail(String.builder()
-          .append(x)
-          .append(" is not equal to ")
-          .append(y)
-          .build())
-    }
-  }
-
-  checkNotEquals (x,y) { $NoTrace$
-    if (#x.equals(x,y)) {
-      fail(String.builder()
-          .append(x)
-          .append(" is equal to ")
-          .append(y)
-          .build())
-    }
-  }
-
-  checkEmpty (x) { $NoTrace$
-    if (present(x)) {
-      scoped {
-        optional Formatted formatted <- reduce<#x,Formatted>(require(x))
-      } in if (present(formatted)) {
-        fail(String.builder()
-            .append(require(formatted))
-            .append(" is not empty")
-            .build())
-      } else {
-        fail("(unformatted value) is not empty")
-      }
-    }
-  }
-
-  checkPresent (x) { $NoTrace$
-    if (!present(x)) {
-      fail(String.builder()
-          .append("expected non-empty value")
-          .build())
-    }
-  }
-
-  checkOptional (x,y) { $NoTrace$
-    if (present(x) && present(y)) {
-      \ checkEquals(require(x),require(y))
-    } elif (!present(x) && present(y)) {
-      fail("empty is not equal to " + require(y).formatted())
-    } elif (present(x) && !present(y)) {
-      fail(require(x).formatted() + " is not equal to empty")
-    }
-  }
-
-  checkTrue (x) { $NoTrace$
-    if (!x.asBool()) {
-      fail("expected true")
-    }
-  }
-
-  checkFalse (x) { $NoTrace$
-    if (x.asBool()) {
-      fail("expected false")
-    }
-  }
-
-  checkBetween (x,l,h) { $NoTrace$
-    if (!lessEquals(l,h)) {
-      fail(String.builder()
-          .append("Upper limit ")
-          .append(l)
-          .append(" is not at or above lower limit ")
-          .append(h)
-          .build())
-    }
-    if (!lessEquals(l,x) || !lessEquals(x,h)) {
-      fail(String.builder()
-          .append(x)
-          .append(" is not between ")
-          .append(l)
-          .append(" and ")
-          .append(h)
-          .build())
-    }
-  }
-
-  checkGreaterThan (x,l) { $NoTrace$
-    if (lessEquals(x,l)) {
-      fail(String.builder()
-          .append(x)
-          .append(" is not greater than ")
-          .append(l)
-          .build())
-    }
-  }
-
-  checkLessThan (x,h) { $NoTrace$
-    if (lessEquals(h,x)) {
-      fail(String.builder()
-          .append(x)
-          .append(" is not less than ")
-          .append(h)
-          .build())
-    }
-  }
-
-  @type lessEquals<#x>
-    #x defines Equals<#x>
-    #x defines LessThan<#x>
-  (#x,#x) -> (Bool)
-  lessEquals (x,y) { $NoTrace$
-    // Using !#x.lessThan(y,x) wouldn't account for NaNs.
-    return #x.lessThan(x,y) || #x.equals(x,y)
-  }
-}
diff --git a/lib/testing/src/matching.0rx b/lib/testing/src/matching.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/matching.0rx
@@ -0,0 +1,241 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 MultiChecker {
+  $ReadOnlyExcept[]$
+
+  @value TestReport report
+
+  new (report) {
+    return delegate -> #self
+  }
+
+  check (title, value, matcher) {
+    if (delegate -> `executeMatch`) {
+      return empty
+    } else {
+      return self
+    }
+  }
+
+  tryCheck (title, value, matcher) {
+    \ delegate -> `executeMatch`
+    return self
+  }
+
+  @value executeMatch<#x> (Formatted title:, optional #x, ValueMatcher<#x>) -> (Bool)
+  executeMatch (title, value, matcher) {
+    String fullTitle <- String.builder()
+        .append(title)
+        .append(": ")
+        .append(matcher.summary())
+        .build()
+    TestReport newReport <- report.newSection(title: fullTitle)
+    $Hidden[report]$
+    \ value `matcher.check` newReport
+    return newReport.hasError()
+  }
+}
+
+define MatcherCompose {
+  or (left, right) {
+    return delegate -> `OrMatcher:new`
+  }
+
+  not (matcher) {
+    return delegate -> `NotMatcher:new`
+  }
+
+  and (left, right) {
+    return delegate -> `AndMatcher:new`
+  }
+
+  anyOf (matchers) {
+    return delegate -> `AnyOfMatcher:new`
+  }
+
+  allOf (matchers) {
+    return delegate -> `AllOfMatcher:new`
+  }
+}
+
+concrete OrMatcher<#x|> {
+  refines ValueMatcher<#x>
+
+  @category new<#x> (ValueMatcher<#x>, ValueMatcher<#x>) -> (ValueMatcher<#x>)
+}
+
+define OrMatcher {
+  @value ValueMatcher<#x> left
+  @value ValueMatcher<#x> right
+
+  new (left, right) {
+    return delegate -> OrMatcher<#x>
+  }
+
+  check (actual, report) {
+    TestReport leftReport <- report.newSection(title: left.summary())
+    TestReport rightReport <- report.newSection(title: right.summary())
+    \ actual `left.check` leftReport
+    if (!leftReport.hasError()) {
+      return _
+    }
+    $Hidden[left]$
+    \ actual `right.check` rightReport
+    if (!rightReport.hasError()) {
+      \ leftReport.discardReport()
+    }
+  }
+
+  summary () {
+    return String.builder()
+        .append("either (")
+        .append(left.summary())
+        .append(") or (")
+        .append(right.summary())
+        .append(")")
+        .build()
+  }
+}
+
+concrete NotMatcher<#x|> {
+  refines ValueMatcher<#x>
+
+  @category new<#x> (ValueMatcher<#x>) -> (ValueMatcher<#x>)
+}
+
+define NotMatcher {
+  @value ValueMatcher<#x> matcher
+
+  new (matcher) {
+    return delegate -> NotMatcher<#x>
+  }
+
+  check (actual, report) {
+    \ delegate -> `matcher.check`
+    if (report.hasError()) {
+      \ report.discardReport()
+    } else {
+      \ report.addError(message: String.builder()
+          .append("expected failure to match with ")
+          .append(matcher.summary())
+          .build())
+    }
+  }
+
+  summary () {
+    return String.builder()
+        .append("not (")
+        .append(matcher.summary())
+        .append(")")
+        .build()
+  }
+}
+
+concrete AndMatcher<#x|> {
+  refines ValueMatcher<#x>
+
+  @category new<#x> (ValueMatcher<#x>, ValueMatcher<#x>) -> (ValueMatcher<#x>)
+}
+
+define AndMatcher {
+  @value ValueMatcher<#x> left
+  @value ValueMatcher<#x> right
+
+  new (left, right) {
+    return delegate -> AndMatcher<#x>
+  }
+
+  check (actual, report) {
+    TestReport leftReport <- report.newSection(title: left.summary())
+    TestReport rightReport <- report.newSection(title: right.summary())
+    \ actual `left.check` leftReport
+    \ actual `right.check` rightReport
+  }
+
+  summary () {
+    return String.builder()
+        .append("both (")
+        .append(left.summary())
+        .append(") and (")
+        .append(right.summary())
+        .append(")")
+        .build()
+  }
+}
+
+concrete AnyOfMatcher<#x|> {
+  refines ValueMatcher<#x>
+
+  @category new<#x> (DefaultOrder<ValueMatcher<#x>>) -> (ValueMatcher<#x>)
+}
+
+define AnyOfMatcher {
+  @value DefaultOrder<ValueMatcher<#x>> matchers
+
+  new (matchers) {
+    return delegate -> AnyOfMatcher<#x>
+  }
+
+  check (actual, report) {
+    ValueList<TestReport> reports <- ValueList<TestReport>.new()
+    traverse (matchers.defaultOrder() -> ValueMatcher<#x> matcher) {
+      TestReport subReport <- report.newSection(title: matcher.summary())
+      $Hidden[report]$
+      \ actual `matcher.check` subReport
+      if (subReport.hasError()) {
+        \ reports.append(subReport)
+      } else {
+        traverse (reports.defaultOrder() -> TestReport failed) {
+          \ failed.discardReport()
+        }
+        break
+      }
+    }
+  }
+
+  summary () {
+    return "match any of"
+  }
+}
+
+concrete AllOfMatcher<#x|> {
+  refines ValueMatcher<#x>
+
+  @category new<#x> (DefaultOrder<ValueMatcher<#x>>) -> (ValueMatcher<#x>)
+}
+
+define AllOfMatcher {
+  @value DefaultOrder<ValueMatcher<#x>> matchers
+
+  new (matchers) {
+    return delegate -> AllOfMatcher<#x>
+  }
+
+  check (actual, report) {
+    traverse (matchers.defaultOrder() -> ValueMatcher<#x> matcher) {
+      TestReport subReport <- report.newSection(title: matcher.summary())
+      $Hidden[report]$
+      \ actual `matcher.check` subReport
+    }
+  }
+
+  summary () {
+    return "match all of"
+  }
+}
diff --git a/lib/testing/src/util.0rp b/lib/testing/src/util.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/util.0rp
@@ -0,0 +1,59 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$ModuleOnly$
+
+concrete IndentAppend {
+  refines Append<Formatted>
+  refines Build<Formatted>
+
+  @type new ([Append<Formatted> & Build<Formatted>]) -> (#self)
+
+  @value pushIndent () -> (#self)
+  @value popIndent () -> (#self)
+  @value addNewline () -> (#self)
+}
+
+concrete ValueList<#x> {
+  refines Append<#x>
+  refines DefaultOrder<#x>
+
+  @type new () -> (#self)
+  @value isEmpty () -> (Bool)
+}
+
+// See Extension_TestHandler.cpp.
+concrete TestHandler {
+  @category failAndExit (String) -> ()
+}
+
+concrete TestReportTree {
+  refines TestReport
+
+  @type new (Formatted, ValueList<Formatted>) -> (#self)
+
+  // NOTE: This will write passing sections unless you prune() first.
+  @value writeTo (IndentAppend) -> (IndentAppend)
+  @value prune () -> (optional #self)
+}
+
+concrete Format {
+  @category autoFormat<#x>
+    #x requires Formatted
+  (optional #x) -> (Formatted)
+}
diff --git a/lib/testing/src/util.0rx b/lib/testing/src/util.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/src/util.0rx
@@ -0,0 +1,239 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 IndentAppend {
+  $ReadOnlyExcept[indentCount]$
+
+  @value [Append<Formatted> & Build<Formatted>] output
+  @value Int indentCount
+
+  new (output) {
+    return #self{ output, 0 }
+  }
+
+  pushIndent () {
+    indentCount <- indentCount+1
+    return self
+  }
+
+  popIndent () {
+    indentCount <- indentCount-1
+    return self
+  }
+
+  addNewline () {
+    \ output.append("\n")
+    return self
+  }
+
+  append (value) {
+    scoped {
+      Int count <- 0
+    } in while (count < indentCount) {
+      \ output.append("  ")
+    } update {
+      count <- count+1
+    }
+    \ output.append(value).append("\n")
+    return self
+  }
+
+  build () {
+    return output.build()
+  }
+}
+
+concrete ListNode<#x> {
+  refines Order<#x>
+
+  @type new (#x) -> (#self)
+  @value setNext (optional ListNode<#x>) -> (#self)
+}
+
+define ListNode {
+  $FlatCleanup[next]$
+  $ReadOnly[value]$
+
+  @value #x value
+  @value optional #self next
+
+  new (value) {
+    return #self{ value, empty }
+  }
+
+  setNext (newNext) {
+    next <- newNext
+    return self
+  }
+
+  next () {
+    return next
+  }
+
+  get () {
+    return value
+  }
+}
+
+define ValueList {
+  @value optional ListNode<#x> head
+  @value optional ListNode<#x> tail
+
+  new () {
+    return #self{ empty, empty }
+  }
+
+  isEmpty () {
+    return ! `present` head
+  }
+
+  append (value) {
+    scoped {
+      ListNode<#x> node <- ListNode<#x>.new(value)
+    } in {
+      head <-| node
+      \ tail&.setNext(node)
+      tail <- node
+    }
+    return self
+  }
+
+  defaultOrder () {
+    return head
+  }
+}
+
+define TestReportTree {
+  $ReadOnlyExcept[messages, children]$
+
+  @value Formatted title
+  @value ValueList<Formatted> context
+  @value ValueList<Formatted> messages
+  @value ValueList<TestReportTree> children
+
+  new (title, context) {
+    return #self{ title, context, ValueList<Formatted>.new(), ValueList<TestReportTree>.new() }
+  }
+
+  writeTo (output) {
+    \ output.append(title)
+    scoped {
+      \ output.pushIndent()
+    } cleanup {
+      \ output.popIndent()
+    } in {
+      \ writeMessagesTo(output)
+      \ writeContextTo(output)
+      \ writeChildrenTo(output)
+    }
+    return output
+  }
+
+  addError (message) {
+    \ messages.append(message)
+    return self
+  }
+
+  newSection (newTitle) {
+    #self new <- new(newTitle, ValueList<Formatted>.new())
+    \ children.append(new)
+    return new
+  }
+
+  discardReport () {
+    messages <- ValueList<Formatted>.new()
+    children <- ValueList<TestReportTree>.new()
+    return self
+  }
+
+  hasError () {
+    if (!messages.isEmpty()) {
+      return true
+    }
+    traverse (children.defaultOrder() -> TestReportTree child) {
+      if (child.hasError()) {
+        return true
+      }
+    }
+    return false
+  }
+
+  prune () {
+    optional ValueList<TestReportTree> newChildren <- empty
+    traverse (children.defaultOrder() -> TestReportTree child) {
+      $Hidden[children]$
+      optional TestReportTree newChild <- child.prune()
+      $Hidden[child]$
+      if (`present` newChild) {
+        \ (newChildren <-| ValueList<TestReportTree>.new()).append(`require` newChild)
+      }
+    }
+    if (! `present` newChildren && messages.isEmpty()) {
+      children <- ValueList<TestReportTree>.new()
+      return empty
+    } else {
+      children <- (newChildren <-| ValueList<TestReportTree>.new())
+      return self
+    }
+  }
+
+  @value writeContextTo (IndentAppend) -> ()
+  writeContextTo (output) {
+    $Hidden[messages, children]$
+      scoped {
+      \ output.pushIndent()
+    } cleanup {
+      \ output.popIndent()
+    } in traverse (context.defaultOrder() -> Formatted line) {
+      \ output.append(line)
+    }
+  }
+
+  @value writeMessagesTo (IndentAppend) -> ()
+  writeMessagesTo (output) {
+    $Hidden[context, children]$
+    if (!messages.isEmpty()) {
+      traverse (messages.defaultOrder() -> Formatted line) {
+        \ output.append(line)
+      }
+    }
+  }
+
+  @value writeChildrenTo (IndentAppend) -> ()
+  writeChildrenTo (output) {
+    $Hidden[messages, context]$
+    traverse (children.defaultOrder() -> TestReportTree child) {
+      \ output.append("---")
+      \ child.writeTo(output)
+    }
+  }
+}
+
+define Format {
+  autoFormat (value) {
+    if (`present` value) {
+      return String.builder()
+          .append("\"")
+          .append(`require` value)
+          .append("\"")
+          .build()
+    } else {
+      return "empty"
+    }
+  }
+}
diff --git a/lib/testing/test/check-float.0rt b/lib/testing/test/check-float.0rt
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/check-float.0rt
@@ -0,0 +1,106 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "CheckFloat success" {
+  success TestChecker
+}
+
+unittest almostEquals {
+  \ 1.230000000001 `Matches:with` CheckFloat:almostEquals(1.23)
+  \ 1.229999999999 `Matches:with` CheckFloat:almostEquals(1.23)
+  \ -1.230000000001 `Matches:with` CheckFloat:almostEquals(-1.23)
+  \ -1.229999999999 `Matches:with` CheckFloat:almostEquals(-1.23)
+  \ 1000000001.0 `Matches:with` CheckFloat:almostEquals(1000000002.0)
+}
+
+unittest closeTo {
+  \ 1.24 `Matches:with` CheckFloat:closeTo(1.23, epsilon: 0.011)
+  \ 1.22 `Matches:with` CheckFloat:closeTo(1.23, epsilon: 0.011)
+  \ -1.24 `Matches:with` CheckFloat:closeTo(-1.23, epsilon: 0.011)
+  \ -1.22 `Matches:with` CheckFloat:closeTo(-1.23, epsilon: 0.011)
+}
+
+
+testcase "CheckFloat:almostEquals failure empty" {
+  failure TestChecker
+  require "empty .+1.23"
+}
+
+unittest test {
+  \ empty `Matches:with` CheckFloat:almostEquals(1.23)
+}
+
+
+testcase "CheckFloat:almostEquals failure above" {
+  failure TestChecker
+  require "1.24.+ 1.23"
+}
+
+unittest test {
+  \ 1.24 `Matches:with` CheckFloat:almostEquals(1.23)
+}
+
+
+testcase "CheckFloat:almostEquals failure expect 0" {
+  failure TestChecker
+  require "not within"
+}
+
+unittest test {
+  \ 0.000000001 `Matches:with` CheckFloat:almostEquals(0.0)
+}
+
+
+testcase "CheckFloat:almostEquals failure below" {
+  failure TestChecker
+  require "1.22.+ 1.23"
+}
+
+unittest test {
+  \ 1.22 `Matches:with` CheckFloat:almostEquals(1.23)
+}
+
+
+testcase "CheckFloat:closeTo failure empty" {
+  failure TestChecker
+  require "empty .+0.001 .+1.23"
+}
+
+unittest test {
+  \ empty `Matches:with` CheckFloat:closeTo(1.23, epsilon: 0.001)
+}
+
+
+testcase "CheckFloat:closeTo failure above" {
+  failure TestChecker
+  require "1.24.+ 0.001.+ 1.23"
+}
+
+unittest test {
+  \ 1.24 `Matches:with` CheckFloat:closeTo(1.23, epsilon: 0.001)
+}
+
+
+testcase "CheckFloat:closeTo failure below" {
+  failure TestChecker
+  require "1.22.+ 0.001.+ 1.23"
+}
+
+unittest test {
+  \ 1.22 `Matches:with` CheckFloat:closeTo(1.23, epsilon: 0.001)
+}
diff --git a/lib/testing/test/check-sequence.0rt b/lib/testing/test/check-sequence.0rt
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/check-sequence.0rt
@@ -0,0 +1,203 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "CheckSequence:equals success" {
+  success TestChecker
+}
+
+unittest equals {
+  ValueList<Int> actual <- ValueList<Int>.new().append(10).append(20).append(30)
+  ValueList<Int> expected <- ValueList<Int>.new().append(10).append(20).append(30)
+  \ actual `Matches:with` CheckSequence:equals(expected)
+}
+
+unittest equalsWithEmpty {
+  ValueList<Int> expected <- ValueList<Int>.new()
+  \ empty `Matches:with` CheckSequence:equals(expected)
+}
+
+
+testcase "CheckSequence:equals different" {
+  failure TestChecker
+  require "10.+40"
+  require "20.+50"
+  require "30.+60"
+}
+
+unittest test {
+  ValueList<Int> actual <- ValueList<Int>.new().append(10).append(20).append(30)
+  ValueList<Int> expected <- ValueList<Int>.new().append(40).append(50).append(60)
+  \ actual `Matches:with` CheckSequence:equals(expected)
+}
+
+
+testcase "CheckSequence:equals extra" {
+  failure TestChecker
+  require "40.+unexpected"
+}
+
+unittest test {
+  ValueList<Int> actual <- ValueList<Int>.new().append(10).append(20).append(30).append(40)
+  ValueList<Int> expected <- ValueList<Int>.new().append(10).append(20).append(30)
+  \ actual `Matches:with` CheckSequence:equals(expected)
+}
+
+
+testcase "CheckSequence:equals missing" {
+  failure TestChecker
+  require "40.+missing"
+}
+
+unittest test {
+  ValueList<Int> actual <- ValueList<Int>.new().append(10).append(20).append(30)
+  ValueList<Int> expected <- ValueList<Int>.new().append(10).append(20).append(30).append(40)
+  \ actual `Matches:with` CheckSequence:equals(expected)
+}
+
+
+testcase "CheckSequence:matches success" {
+  success TestChecker
+}
+
+unittest matches {
+  ValueList<Int> actual <- ValueList<Int>.new().append(1).append(2).append(3)
+  ValueList<ValueMatcher<Int>> expected <- ValueList<ValueMatcher<Int>>.new()
+      .append(`CheckValue:equals` 1)
+      .append(`CheckValue:equals` 2)
+      .append(`CheckValue:equals` 3)
+  \ actual `Matches:with` CheckSequence:matches(expected)
+}
+
+unittest matchesWithEmpty {
+  ValueList<ValueMatcher<Int>> expected <- ValueList<ValueMatcher<Int>>.new()
+  \ empty `Matches:with` CheckSequence:matches(expected)
+}
+
+
+testcase "CheckSequence:matches different" {
+  failure TestChecker
+  require "10.+40"
+  require "20.+50"
+  require "30.+60"
+}
+
+unittest test {
+  ValueList<Int> actual <- ValueList<Int>.new().append(10).append(20).append(30)
+  ValueList<ValueMatcher<Int>> expected <- ValueList<ValueMatcher<Int>>.new()
+      .append(`CheckValue:equals` 40)
+      .append(`CheckValue:equals` 50)
+      .append(`CheckValue:equals` 60)
+  \ actual `Matches:with` CheckSequence:matches(expected)
+}
+
+
+testcase "CheckSequence:matches extra" {
+  failure TestChecker
+  require "unexpected"
+  require "[Ii]tem #3"
+  exclude "[Ii]tem #0"
+  exclude "[Ii]tem #1"
+  exclude "[Ii]tem #2"
+}
+
+unittest test {
+  ValueList<Int> actual <- ValueList<Int>.new().append(10).append(20).append(30).append(40)
+  ValueList<ValueMatcher<Int>> expected <- ValueList<ValueMatcher<Int>>.new()
+      .append(`CheckValue:equals` 10)
+      .append(`CheckValue:equals` 20)
+      .append(`CheckValue:equals` 30)
+  \ actual `Matches:with` CheckSequence:matches(expected)
+}
+
+
+testcase "CheckSequence:matches missing" {
+  failure TestChecker
+  require "missing"
+  require "[Ii]tem #3"
+  exclude "[Ii]tem #0"
+  exclude "[Ii]tem #1"
+  exclude "[Ii]tem #2"
+}
+
+unittest test {
+  ValueList<Int> actual <- ValueList<Int>.new().append(10).append(20).append(30)
+  ValueList<ValueMatcher<Int>> expected <- ValueList<ValueMatcher<Int>>.new()
+      .append(`CheckValue:equals` 10)
+      .append(`CheckValue:equals` 20)
+      .append(`CheckValue:equals` 30)
+      .append(`CheckValue:equals` 40)
+  \ actual `Matches:with` CheckSequence:matches(expected)
+}
+
+
+testcase "CheckSequence:allMatch success" {
+  success TestChecker
+}
+
+unittest allMatch {
+  ValueList<Int> actual <- ValueList<Int>.new().append(1).append(2).append(3)
+  \ actual `Matches:with` CheckSequence:allMatch(CheckValue:greaterThanEquals(0))
+}
+
+unittest allMatchWithEmpty {
+  \ empty `Matches:with` CheckSequence:allMatch(CheckValue:greaterThanEquals(0))
+}
+
+
+testcase "CheckSequence:allMatch failure" {
+  failure TestChecker
+  require "10.+25"
+  require "20.+25"
+  exclude "30.+25"
+}
+
+unittest test {
+  ValueList<Int> actual <- ValueList<Int>.new().append(10).append(20).append(30)
+  \ actual `Matches:with` CheckSequence:allMatch(CheckValue:greaterThanEquals(25))
+}
+
+
+testcase "CheckSequence:using success" {
+  success TestChecker
+}
+
+unittest test {
+  ValueList<TestValue> actual <- ValueList<TestValue>.new()
+      .append(TestValue.default())
+      .append(TestValue.new(456, "something", empty))
+  ValueList<TestValue> expected <- ValueList<TestValue>.new()
+      .append(TestValue.default())
+      .append(TestValue.new(456, "something", empty))
+  \ actual `Matches:with` CheckSequence:matches(CheckSequence:using(expected))
+}
+
+
+testcase "CheckSequence:using failure" {
+  failure TestChecker
+  require "456.+321"
+}
+
+unittest test {
+  ValueList<TestValue> actual <- ValueList<TestValue>.new()
+      .append(TestValue.default())
+      .append(TestValue.new(456, "something", empty))
+  ValueList<TestValue> expected <- ValueList<TestValue>.new()
+      .append(TestValue.default())
+      .append(TestValue.new(321, "something", empty))
+  \ actual `Matches:with` CheckSequence:matches(CheckSequence:using(expected))
+}
diff --git a/lib/testing/test/check-string.0rt b/lib/testing/test/check-string.0rt
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/check-string.0rt
@@ -0,0 +1,162 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "CheckString:contains success" {
+  success TestChecker
+}
+
+unittest entireString {
+  \ "1234567890" `Matches:with` CheckString:contains("1234567890")
+}
+
+unittest beginning {
+  \ "1234567890" `Matches:with` CheckString:contains("12345")
+}
+
+unittest end {
+  \ "1234567890" `Matches:with` CheckString:contains("67890")
+}
+
+unittest middle {
+  \ "1234567890" `Matches:with` CheckString:contains("4567")
+}
+
+
+testcase "CheckString:contains failure empty" {
+  failure TestChecker
+  require "does not contain"
+  require "empty"
+}
+
+unittest test {
+  \ empty `Matches:with` CheckString:contains("567890")
+}
+
+
+testcase "CheckString:contains failure partial" {
+  failure TestChecker
+  require "does not contain"
+  require "567890"
+}
+
+unittest test {
+  \ "123456789" `Matches:with` CheckString:contains("567890")
+}
+
+
+testcase "CheckString:contains failure too long" {
+  failure TestChecker
+  require "does not contain"
+  require "123"
+}
+
+unittest test {
+  \ "123" `Matches:with` CheckString:contains("12345")
+}
+
+
+testcase "CheckString:startsWith success" {
+  success TestChecker
+}
+
+unittest entireString {
+  \ "1234567890" `Matches:with` CheckString:startsWith("1234567890")
+}
+
+unittest beginning {
+  \ "1234567890" `Matches:with` CheckString:startsWith("12345")
+}
+
+
+testcase "CheckString:startsWith failure empty" {
+  failure TestChecker
+  require "does not start with"
+  require "empty"
+}
+
+unittest test {
+  \ empty `Matches:with` CheckString:startsWith("567890")
+}
+
+
+testcase "CheckString:startsWith failure not at start" {
+  failure TestChecker
+  require "does not start with"
+  require "567890"
+}
+
+unittest test {
+  \ "1234567890" `Matches:with` CheckString:startsWith("567890")
+}
+
+
+testcase "CheckString:startsWith failure too long" {
+  failure TestChecker
+  require "does not start with"
+  require "123"
+}
+
+unittest test {
+  \ "123" `Matches:with` CheckString:startsWith("12345")
+}
+
+
+testcase "CheckString:endsWith success" {
+  success TestChecker
+}
+
+unittest entireString {
+  \ "1234567890" `Matches:with` CheckString:endsWith("1234567890")
+}
+
+unittest end {
+  \ "1234567890" `Matches:with` CheckString:endsWith("567890")
+}
+
+
+testcase "CheckString:endsWith failure empty" {
+  failure TestChecker
+  require "does not end with"
+  require "empty"
+}
+
+unittest test {
+  \ empty `Matches:with` CheckString:endsWith("567890")
+}
+
+
+testcase "CheckString:endsWith failure not at end" {
+  failure TestChecker
+  require "does not end with"
+  require "123456"
+}
+
+unittest test {
+  \ "1234567890" `Matches:with` CheckString:endsWith("123456")
+}
+
+
+testcase "CheckString:endsWith failure too long" {
+  failure TestChecker
+  require "does not end with"
+  require "345"
+}
+
+unittest test {
+  \ "345" `Matches:with` CheckString:endsWith("12345")
+}
diff --git a/lib/testing/test/check-value.0rt b/lib/testing/test/check-value.0rt
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/check-value.0rt
@@ -0,0 +1,409 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "CheckValue:equals success" {
+  success TestChecker
+}
+
+unittest nonEmpty {
+  \ "foo" `Matches:with` CheckValue:equals("foo")
+}
+
+unittest bothEmpty {
+  \ empty?String `Matches:with` CheckValue:equals(empty?String)
+}
+
+
+testcase "CheckValue:equals failure not equal" {
+  failure TestChecker
+  require "foo"
+  require "bar"
+}
+
+unittest test {
+  \ "foo" `Matches:with` CheckValue:equals("bar")
+}
+
+
+testcase "CheckValue:equals failure left empty" {
+  failure TestChecker
+  require "empty"
+  require "bar"
+}
+
+unittest test {
+  \ empty?String `Matches:with` CheckValue:equals("bar")
+}
+
+
+testcase "CheckValue:equals failure right empty" {
+  failure TestChecker
+  require "foo"
+  require "empty"
+}
+
+unittest test {
+  \ "foo" `Matches:with` CheckValue:equals(empty?String)
+}
+
+
+testcase "CheckValue:notEquals success" {
+  success TestChecker
+}
+
+unittest nonEmpty {
+  \ "foo" `Matches:with` CheckValue:notEquals("bar")
+}
+
+unittest leftEmpty {
+  \ empty?String `Matches:with` CheckValue:notEquals("bar")
+}
+
+unittest rightEmpty {
+  \ "foo" `Matches:with` CheckValue:notEquals(empty?String)
+}
+
+
+testcase "CheckValue:notEquals failure non-empty" {
+  failure TestChecker
+  require "foo"
+}
+
+unittest test {
+  \ "foo" `Matches:with` CheckValue:notEquals("foo")
+}
+
+
+testcase "CheckValue:notEquals failure empty" {
+  failure TestChecker
+  require "empty"
+}
+
+unittest test {
+  \ empty?String `Matches:with` CheckValue:notEquals(empty?String)
+}
+
+
+testcase "CheckValue:is success" {
+  success TestChecker
+}
+
+unittest test {
+  String value <- "foo"
+  \ value `Matches:with` CheckValue:is(value)
+}
+
+
+testcase "CheckValue:is failure same value" {
+  failure TestChecker
+  require "String@[a-f0-9]+"
+  exclude "foo"
+}
+
+unittest test {
+  \ "foo" `Matches:with` CheckValue:is("foo")
+}
+
+
+testcase "CheckValue:using success" {
+  success TestChecker
+}
+
+unittest test {
+  \ TestValue.default() `Matches:with` `CheckValue:using` TestValue.default()
+}
+
+
+testcase "CheckValue:using failure not equal" {
+  failure TestChecker
+  require "message1"
+}
+
+unittest test {
+  \ TestValue.default() `Matches:with` `CheckValue:using` TestValue.new(123, "message1", empty)
+}
+
+
+testcase "CheckValue:using failure left empty" {
+  failure TestChecker
+  require "empty"
+}
+
+unittest test {
+  \ empty?TestValue `Matches:with` `CheckValue:using` TestValue.new(123, "message1", empty)
+}
+
+
+testcase "CheckValue:lessThanNotEqual success" {
+  success TestChecker
+}
+
+unittest test {
+  \ 0 `Matches:with` CheckValue:lessThanNotEquals(1)
+}
+
+
+testcase "CheckValue:lessThanNotEqual failure non-empty" {
+  failure TestChecker
+  require "100"
+  require "20"
+}
+
+unittest test {
+  \ 100 `Matches:with` CheckValue:lessThanNotEquals(20)
+}
+
+
+testcase "CheckValue:lessThanNotEqual failure endpoint" {
+  failure TestChecker
+  require "100"
+}
+
+unittest test {
+  \ 100 `Matches:with` CheckValue:lessThanNotEquals(100)
+}
+
+
+testcase "CheckValue:lessThanNotEqual failure empty" {
+  failure TestChecker
+  require "empty"
+  require "20"
+}
+
+unittest test {
+  \ empty?Int `Matches:with` CheckValue:lessThanNotEquals(20)
+}
+
+
+testcase "CheckValue:greaterThanNotEqual success" {
+  success TestChecker
+}
+
+unittest test {
+  \ 1 `Matches:with` CheckValue:greaterThanNotEquals(0)
+}
+
+
+testcase "CheckValue:greaterThanNotEqual failure non-empty" {
+  failure TestChecker
+  require "10"
+  require "200"
+}
+
+unittest test {
+  \ 10 `Matches:with` CheckValue:greaterThanNotEquals(200)
+}
+
+
+testcase "CheckValue:greaterThanNotEqual failure endpoint" {
+  failure TestChecker
+  require "10"
+}
+
+unittest test {
+  \ 10 `Matches:with` CheckValue:greaterThanNotEquals(10)
+}
+
+
+testcase "CheckValue:greaterThanNotEqual failure empty" {
+  failure TestChecker
+  require "empty"
+  require "20"
+}
+
+unittest test {
+  \ empty?Int `Matches:with` CheckValue:greaterThanNotEquals(20)
+}
+
+
+testcase "CheckValue:betweenNotEqual success" {
+  success TestChecker
+}
+
+unittest test {
+  \ 1 `Matches:with` CheckValue:betweenNotEquals(0, 3)
+}
+
+
+testcase "CheckValue:betweenNotEqual failure below" {
+  failure TestChecker
+  require "3"
+  require "5"
+  require "200"
+}
+
+unittest test {
+  \ 3 `Matches:with` CheckValue:betweenNotEquals(5, 200)
+}
+
+
+testcase "CheckValue:betweenNotEqual failure above" {
+  failure TestChecker
+  require "300"
+  require "5"
+  require "200"
+}
+
+unittest test {
+  \ 300 `Matches:with` CheckValue:betweenNotEquals(5, 200)
+}
+
+
+testcase "CheckValue:betweenNotEqual failure endpoint low" {
+  failure TestChecker
+  require "5"
+  require "200"
+}
+
+unittest test {
+  \ 5 `Matches:with` CheckValue:betweenNotEquals(5, 200)
+}
+
+
+testcase "CheckValue:betweenNotEqual failure endpoint high" {
+  failure TestChecker
+  require "5"
+  require "200"
+}
+
+unittest test {
+  \ 200 `Matches:with` CheckValue:betweenNotEquals(5, 200)
+}
+
+
+testcase "CheckValue:betweenNotEqual failure empty" {
+  failure TestChecker
+  require "empty"
+  require "5"
+  require "200"
+}
+
+unittest test {
+  \ empty?Int `Matches:with` CheckValue:betweenNotEquals(5, 200)
+}
+
+
+testcase "CheckValue:lessThanEqual success" {
+  success TestChecker
+}
+
+unittest test {
+  \ 0 `Matches:with` CheckValue:lessThanEquals(1)
+  \ 0 `Matches:with` CheckValue:lessThanEquals(0)
+}
+
+
+testcase "CheckValue:lessThanEqual failure non-empty" {
+  failure TestChecker
+  require "100"
+  require "20"
+}
+
+unittest test {
+  \ 100 `Matches:with` CheckValue:lessThanEquals(20)
+}
+
+
+testcase "CheckValue:lessThanEqual failure empty" {
+  failure TestChecker
+  require "empty"
+  require "20"
+}
+
+unittest test {
+  \ empty?Int `Matches:with` CheckValue:lessThanEquals(20)
+}
+
+
+testcase "CheckValue:greaterThanEqual success" {
+  success TestChecker
+}
+
+unittest test {
+  \ 1 `Matches:with` CheckValue:greaterThanEquals(0)
+  \ 1 `Matches:with` CheckValue:greaterThanEquals(1)
+}
+
+
+testcase "CheckValue:greaterThanEqual failure non-empty" {
+  failure TestChecker
+  require "10"
+  require "200"
+}
+
+unittest test {
+  \ 10 `Matches:with` CheckValue:greaterThanEquals(200)
+}
+
+
+testcase "CheckValue:greaterThanEqual failure empty" {
+  failure TestChecker
+  require "empty"
+  require "20"
+}
+
+unittest test {
+  \ empty?Int `Matches:with` CheckValue:greaterThanEquals(20)
+}
+
+
+testcase "CheckValue:betweenEqual success" {
+  success TestChecker
+}
+
+unittest test {
+  \ 1 `Matches:with` CheckValue:betweenEquals(0, 3)
+  \ 0 `Matches:with` CheckValue:betweenEquals(0, 3)
+  \ 3 `Matches:with` CheckValue:betweenEquals(0, 3)
+}
+
+
+testcase "CheckValue:betweenEqual failure below" {
+  failure TestChecker
+  require "3"
+  require "5"
+  require "200"
+}
+
+unittest test {
+  \ 3 `Matches:with` CheckValue:betweenEquals(5, 200)
+}
+
+
+testcase "CheckValue:betweenEqual failure above" {
+  failure TestChecker
+  require "300"
+  require "5"
+  require "200"
+}
+
+unittest test {
+  \ 300 `Matches:with` CheckValue:betweenEquals(5, 200)
+}
+
+
+testcase "CheckValue:betweenEqual failure empty" {
+  failure TestChecker
+  require "empty"
+  require "5"
+  require "200"
+}
+
+unittest test {
+  \ empty?Int `Matches:with` CheckValue:betweenEquals(5, 200)
+}
diff --git a/lib/testing/test/checker.0rt b/lib/testing/test/checker.0rt
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/checker.0rt
@@ -0,0 +1,154 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "TestChecker.isFailing fails if not started" {
+  failure
+  require "not started"
+}
+
+unittest test {
+  \ TestChecker.isFailing()
+}
+
+
+testcase "TestChecker.finish fails if not started" {
+  failure
+  require "not started"
+}
+
+unittest test {
+  \ TestChecker.finish()
+}
+
+
+testcase "TestChecker.checkNow fails if not started" {
+  failure
+  require "not started"
+}
+
+unittest test {
+  \ TestChecker.checkNow()
+}
+
+
+testcase "TestChecker.start fails if already started" {
+  failure
+  require "already started"
+}
+
+unittest test {
+  \ TestChecker.start()
+  \ TestChecker.start()
+}
+
+
+testcase "TestChecker.newReport fails if not started" {
+  failure
+  require "not started"
+}
+
+unittest test {
+  \ TestChecker.newReport(title: "title", context: empty)
+}
+
+
+testcase "TestChecker explicit start and finish" {
+  success
+}
+
+unittest test {
+  \ TestChecker.start()
+  \ TestChecker.newReport(title: "title", context: empty)
+  \ TestChecker.newReport(title: "title", context: empty).newSection(title: "subtitle")
+  \ Testing.checkEquals(TestChecker.isFailing(), false)
+  \ TestChecker.checkNow()
+  \ TestChecker.finish()
+}
+
+
+testcase "TestChecker succeeds by default" {
+  success TestChecker
+}
+
+unittest test { }
+
+
+testcase "TestChecker fails when error added to report" {
+  failure TestChecker
+  require "context1"
+  require "context2"
+  require "title1"
+  require "message1"
+  require "title2"
+  require "message2"
+  exclude "testError"
+}
+
+unittest testError {
+  ValueList<String> context <- ValueList<String>.new()
+      .append("context1")
+      .append("context2")
+  \ TestChecker.newReport(title: "title1", context: context.defaultOrder()).addError(message: "message1")
+  \ TestChecker.newReport(title: "title2", context: empty).addError(message: "message2")
+}
+
+
+testcase "TestChecker fails when error added to child" {
+  failure TestChecker
+  require "title"
+  require "child"
+  require "message"
+  exclude "testError"
+}
+
+unittest testError {
+  TestReport report <- TestChecker.newReport(title: "title", context: empty)
+  TestReport child <- report.newSection(title: "child")
+  \ child.addError(message: "message")
+}
+
+
+testcase "TestChecker ignores discarded errors" {
+  success TestChecker
+}
+
+unittest topLevel {
+  TestReport report <- TestChecker.newReport(title: "title", context: empty)
+  \ Testing.checkEquals(report.hasError(), false)
+
+  \ report.addError(message: "message")
+  \ Testing.checkEquals(report.hasError(), true)
+
+  \ report.discardReport()
+  \ Testing.checkEquals(report.hasError(), false)
+}
+
+unittest recursive {
+  TestReport report <- TestChecker.newReport(title: "title", context: empty)
+  TestReport child <- report.newSection(title: "child")
+  \ Testing.checkEquals(report.hasError(), false)
+  \ Testing.checkEquals(child.hasError(), false)
+
+  \ child.addError(message: "message")
+  \ Testing.checkEquals(report.hasError(), true)
+  \ Testing.checkEquals(child.hasError(), true)
+
+  \ report.discardReport()
+  \ Testing.checkEquals(report.hasError(), false)
+  \ Testing.checkEquals(child.hasError(), true)
+}
diff --git a/lib/testing/test/evaluation.0rt b/lib/testing/test/evaluation.0rt
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/evaluation.0rt
@@ -0,0 +1,184 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "Matches no errors" {
+  success TestChecker
+}
+
+unittest withOnce {
+  \ "foo" `Matches:with` CheckAlways:match()
+}
+
+unittest withMulti {
+  \ "foo" `Matches:with` CheckAlways:match()
+  \ "foo" `Matches:with` CheckAlways:match()
+}
+
+unittest tryWithOnce {
+  \ "foo" `Matches:tryWith` CheckAlways:match()
+}
+
+unittest tryWithMulti {
+  \ "foo" `Matches:tryWith` CheckAlways:match()
+  \ "foo" `Matches:tryWith` CheckAlways:match()
+}
+
+unittest valueOnce {
+  \ TestValue.default() `Matches:value` TestValue.default()
+}
+
+unittest valueMulti {
+  \ TestValue.default() `Matches:value` TestValue.default()
+  \ TestValue.default() `Matches:value` TestValue.default()
+}
+
+unittest tryValueOnce {
+  \ TestValue.default() `Matches:tryValue` TestValue.default()
+}
+
+unittest tryValueMulti {
+  \ TestValue.default() `Matches:tryValue` TestValue.default()
+  \ TestValue.default() `Matches:tryValue` TestValue.default()
+}
+
+
+testcase "Matches:with fails immediately" {
+  failure TestChecker
+  require "message1"
+  exclude "message2"
+}
+
+unittest test {
+  \ "foo" `Matches:with` CheckAlways:error("message1")
+  \ "foo" `Matches:with` CheckAlways:error("message2")
+}
+
+
+testcase "Matches:tryWith fails later" {
+  failure TestChecker
+  require "message1"
+  require "message2"
+}
+
+unittest test {
+  \ "foo" `Matches:tryWith` CheckAlways:error("message1")
+  \ "foo" `Matches:tryWith` CheckAlways:error("message2")
+}
+
+
+testcase "Matches:value fails immediately" {
+  failure TestChecker
+  require "message1"
+  exclude "321"
+  exclude "message2"
+  exclude "999"
+}
+
+unittest test {
+  \ TestValue.default() `Matches:value` TestValue.new(123, "message1", empty)
+  \ TestValue.default() `Matches:value` TestValue.new(321, "message2", 999)
+}
+
+
+testcase "Matches:value failure with empty" {
+  failure TestChecker
+  require "empty"
+}
+
+unittest test {
+  \ empty?TestValue `Matches:value` TestValue.new(123, "message1", empty)
+}
+
+
+testcase "Matches:tryValue fails later" {
+  failure TestChecker
+  require "message1"
+  require "321"
+  require "message2"
+  require "999"
+}
+
+unittest test {
+  \ TestValue.default() `Matches:tryValue` TestValue.new(123, "message1", empty)
+  \ TestValue.default() `Matches:tryValue` TestValue.new(321, "message2", 999)
+}
+
+
+testcase "Matches:tryValue failure with empty" {
+  failure TestChecker
+  require "empty"
+}
+
+unittest test {
+  \ empty?TestValue `Matches:tryValue` TestValue.new(123, "message1", empty)
+}
+
+
+testcase "Matches:with doesn't evaluate pending errors" {
+  failure TestChecker
+  require "message2"
+  exclude "message1"
+}
+
+unittest test {
+  \ "foo" `Matches:tryWith` CheckAlways:error("message1")
+  \ "foo" `Matches:with` CheckAlways:match()
+  fail("message2")
+}
+
+
+testcase "Matches:value doesn't evaluate pending errors" {
+  failure TestChecker
+  require "message2"
+  exclude "message1"
+}
+
+unittest test {
+  \ TestValue.default() `Matches:tryValue` TestValue.new(123, "message1", empty)
+  \ TestValue.default() `Matches:value` TestValue.default()
+  fail("message2")
+}
+
+
+testcase "CheckAlways" {
+  success
+}
+
+unittest match {
+  TestReportTree report <- TestReportTree.new("title", ValueList<Formatted>.new())
+  \ CheckAlways:match().check(123, report)
+  \ Testing.checkEquals(report.hasError(), false)
+}
+
+unittest fromWithoutError {
+  TestReportTree report <- TestReportTree.new("title", ValueList<Formatted>.new())
+  \ CheckAlways:error("message").check(123, report)
+  \ Testing.checkEquals(report.hasError(), true)
+}
+
+
+testcase "CheckAlways:die" {
+  failure
+  require "something"
+}
+
+unittest test {
+  TestReportTree report <- TestReportTree.new("title", ValueList<Formatted>.new())
+  \ report.addError(message: "message")
+  \ CheckAlways:die("something").check(123, report)
+}
diff --git a/lib/testing/test/helpers.0rp b/lib/testing/test/helpers.0rp
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/helpers.0rp
@@ -0,0 +1,35 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$ModuleOnly$
+$TestsOnly$
+
+concrete TestValue {
+  refines TestCompare<TestValue>
+  // TestValue.new(123, "message", empty)
+  defines Default
+
+  @type new (Int, String, optional Int) -> (#self)
+}
+
+concrete Testing {
+  @type checkEquals<#x>
+    #x requires Formatted
+    #x defines Equals<#x>
+  (optional #x, optional #x) -> ()
+}
diff --git a/lib/testing/test/helpers.0rx b/lib/testing/test/helpers.0rx
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/helpers.0rx
@@ -0,0 +1,69 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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]
+
+$TestsOnly$
+
+define TestValue {
+  $ReadOnlyExcept[]$
+
+  @value Int number
+  @value String message
+  @value optional Int error
+
+  new (number, message, error) {
+    return delegate -> #self
+  }
+
+  default () {
+    return new(123, "message", empty)
+  }
+
+  testCompare (actual, report) {
+    \ MultiChecker.new(report)
+        .tryCheck(title: "number", actual.number(), CheckValue:equals(number))
+        .tryCheck(title: "message", actual.message(), CheckValue:equals(message))
+        .tryCheck(title: "error", actual.error(), CheckValue:equals(error))
+  }
+
+  @value number () -> (Int)
+  number () { return number }
+
+  @value message () -> (String)
+  message () { return message }
+
+  @value error () -> (optional Int)
+  error () { return error }
+}
+
+define Testing {
+  checkEquals (x, y) { $NoTrace$
+    Bool failed <- false
+    if (`present` x && `present` y && !#x.equals(`require` x, `require` y)) {
+      failed <- true
+    } elif (`present` x != `present` y) {
+      failed <- true
+    }
+    if (failed) {
+      fail(String.builder()
+          .append(`Format:autoFormat` x)
+          .append(" is not equal to ")
+          .append(`Format:autoFormat` y)
+          .build())
+    }
+  }
+}
diff --git a/lib/testing/test/matching.0rt b/lib/testing/test/matching.0rt
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/matching.0rt
@@ -0,0 +1,218 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "MultiChecker.check short cicuits" {
+  failure
+  require "title0"
+  require "title2"
+  require "message2"
+  exclude "title1"
+  exclude "title3"
+  exclude "message3"
+}
+
+unittest test {
+  TestReportTree report <- TestReportTree.new("title0", ValueList<Formatted>.new())
+
+  \ MultiChecker.new(report)
+      .check(title: "title1", "foo", CheckAlways:match())
+      &.check(title: "title2", "foo", CheckAlways:error("message2"))
+      &.check(title: "title3", "foo", CheckAlways:error("message3"))
+
+  IndentAppend output <- IndentAppend.new(String.builder())
+  \ report.prune()&.writeTo(output)
+  \ TestHandler:failAndExit(output.build().formatted())
+}
+
+
+testcase "MultiChecker.tryCheck continues" {
+  failure
+  require "title0"
+  require "title2"
+  require "message2"
+  require "title3"
+  require "message3"
+  exclude "title1"
+}
+
+unittest test {
+  TestReportTree report <- TestReportTree.new("title0", ValueList<Formatted>.new())
+
+  \ MultiChecker.new(report)
+      .tryCheck<String>(title: "title1", "foo", CheckAlways:match())
+      .tryCheck<String>(title: "title2", "foo", CheckAlways:error("message2"))
+      .tryCheck<String>(title: "title3", "foo", CheckAlways:error("message3"))
+
+  IndentAppend output <- IndentAppend.new(String.builder())
+  \ report.prune()&.writeTo(output)
+  \ TestHandler:failAndExit(output.build().formatted())
+}
+
+
+testcase "MultiChecker.check doesn't force pending errors" {
+  failure
+  require "title0"
+  require "title1"
+  require "message1"
+  require "title3"
+  require "message3"
+  exclude "title2"
+}
+
+unittest test {
+  TestReportTree report <- TestReportTree.new("title0", ValueList<Formatted>.new())
+
+  \ MultiChecker.new(report)
+      .tryCheck<String>(title: "title1", "foo", CheckAlways:error("message1"))
+      .check(title: "title2", "foo", CheckAlways:match())
+      &.tryCheck<String>(title: "title3", "foo", CheckAlways:error("message3"))
+
+  IndentAppend output <- IndentAppend.new(String.builder())
+  \ report.prune()&.writeTo(output)
+  \ TestHandler:failAndExit(output.build().formatted())
+}
+
+
+testcase "MatcherCompose:not success" {
+  success TestChecker
+}
+
+unittest test {
+  \ "foo" `Matches:with` `MatcherCompose:not` CheckAlways:error("message")
+}
+
+
+testcase "MatcherCompose:not failure" {
+  failure TestChecker
+  require "expected failure"
+}
+
+unittest test {
+  \ "foo" `Matches:with` `MatcherCompose:not` CheckAlways:match()
+}
+
+
+testcase "MatcherCompose:or success" {
+  success TestChecker
+}
+
+unittest test {
+  \ "foo" `Matches:with` (CheckAlways:match() `MatcherCompose:or` CheckAlways:match())
+  \ "foo" `Matches:with` (CheckAlways:match() `MatcherCompose:or` CheckAlways:error("message"))
+  \ "foo" `Matches:with` (CheckAlways:error("message") `MatcherCompose:or` CheckAlways:match())
+}
+
+
+testcase "MatcherCompose:or failure" {
+  failure TestChecker
+  require "message1"
+  require "message2"
+}
+
+unittest test {
+  \ "foo" `Matches:with` (CheckAlways:error("message1") `MatcherCompose:or` CheckAlways:error("message2"))
+}
+
+
+testcase "MatcherCompose:and success" {
+  success TestChecker
+}
+
+unittest test {
+  \ "foo" `Matches:with` (CheckAlways:match() `MatcherCompose:and` CheckAlways:match())
+}
+
+
+testcase "MatcherCompose:and failure both" {
+  failure TestChecker
+  require "message1"
+  require "message2"
+}
+
+unittest test {
+  \ "foo" `Matches:with` (CheckAlways:error("message1") `MatcherCompose:and` CheckAlways:error("message2"))
+}
+
+
+testcase "MatcherCompose:and failure one" {
+  failure TestChecker
+  require "message"
+}
+
+unittest test {
+  \ "foo" `Matches:with` (CheckAlways:match() `MatcherCompose:and` CheckAlways:error("message"))
+}
+
+
+testcase "MatcherCompose:anyOf success" {
+  success TestChecker
+}
+
+unittest someMatching {
+  \ "foo" `Matches:with` MatcherCompose:anyOf(ValueList<ValueMatcher<any>>.new()
+      .append(CheckAlways:error("message"))
+      .append(CheckAlways:match())
+      .append(CheckAlways:die("message")))
+}
+
+unittest emptyList {
+  \ "foo" `Matches:with` MatcherCompose:anyOf(ValueList<ValueMatcher<any>>.new())
+}
+
+
+testcase "MatcherCompose:anyOf failure" {
+  failure TestChecker
+  require "message1"
+  require "message2"
+}
+
+unittest test {
+  \ "foo" `Matches:with` MatcherCompose:anyOf(ValueList<ValueMatcher<any>>.new()
+      .append(CheckAlways:error("message1"))
+      .append(CheckAlways:error("message2")))
+}
+
+
+testcase "MatcherCompose:allOf success" {
+  success TestChecker
+}
+
+unittest allMatching {
+  \ "foo" `Matches:with` MatcherCompose:allOf(ValueList<ValueMatcher<any>>.new()
+      .append(CheckAlways:match())
+      .append(CheckAlways:match()))
+}
+
+unittest emptyList {
+  \ "foo" `Matches:with` MatcherCompose:allOf(ValueList<ValueMatcher<any>>.new())
+}
+
+
+testcase "MatcherCompose:allOf failure" {
+  failure TestChecker
+  require "message1"
+  require "message2"
+}
+
+unittest test {
+  \ "foo" `Matches:with` MatcherCompose:allOf(ValueList<ValueMatcher<any>>.new()
+      .append(CheckAlways:match())
+      .append(CheckAlways:error("message1"))
+      .append(CheckAlways:match())
+      .append(CheckAlways:error("message2")))
+}
diff --git a/lib/testing/test/tests.0rt b/lib/testing/test/tests.0rt
deleted file mode 100644
--- a/lib/testing/test/tests.0rt
+++ /dev/null
@@ -1,254 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2020-2021 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 "checkEquals success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkEquals(13,13)
-}
-
-
-testcase "checkEquals fail" {
-  crash
-  require stderr "13"
-  require stderr "15"
-}
-
-unittest test {
-  \ Testing.checkEquals(13,15)
-}
-
-
-testcase "checkNotEquals success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkNotEquals(12,13)
-}
-
-
-testcase "checkNotEquals fail" {
-  crash
-  require stderr "13.*13"
-}
-
-unittest test {
-  \ Testing.checkNotEquals(13,13)
-}
-
-
-testcase "checkEmpty success" {
-  success
-}
-
-unittest formatted {
-  \ Testing.checkEmpty(empty)
-}
-
-unittest notFormatted {
-  optional CharBuffer value <- empty
-  \ Testing.checkEmpty(value)
-}
-
-
-testcase "checkEmpty fail formatted" {
-  crash
-  require stderr "13"
-  require stderr "empty"
-}
-
-unittest test {
-  \ Testing.checkEmpty(13)
-}
-
-
-testcase "checkEmpty fail unformatted" {
-  crash
-  require stderr "unformatted"
-  require stderr "empty"
-}
-
-unittest test {
-  CharBuffer value <- CharBuffer.new(10)
-  \ Testing.checkEmpty(value)
-}
-
-
-testcase "checkPresent success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkPresent(13)
-}
-
-
-testcase "checkPresent fail" {
-  crash
-  require stderr "empty"
-}
-
-unittest test {
-  \ Testing.checkPresent(empty)
-}
-
-
-testcase "checkOptional success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkOptional<Int>(empty,empty)
-  \ Testing.checkOptional(13,13)
-}
-
-
-testcase "checkOptional fail empty" {
-  crash
-  require stderr "13"
-  require stderr "empty"
-}
-
-unittest test {
-  \ Testing.checkOptional(13,empty)
-}
-
-
-testcase "checkOptional fail present" {
-  crash
-  require stderr "13"
-  require stderr "empty"
-}
-
-unittest test {
-  \ Testing.checkOptional(empty,13)
-}
-
-
-testcase "checkBetween success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkBetween(13,13,15)
-}
-
-
-testcase "checkBetween fail" {
-  crash
-  require stderr "13"
-  require stderr "14"
-  require stderr "15"
-}
-
-unittest test {
-  \ Testing.checkBetween(13,14,15)
-}
-
-
-testcase "checkBetween bad range" {
-  crash
-  exclude stderr "14"
-  require stderr "13"
-  require stderr "15"
-}
-
-unittest test {
-  \ Testing.checkBetween(14,15,13)
-}
-
-
-testcase "checkGreaterThan success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkGreaterThan(13,11)
-}
-
-
-testcase "checkGreaterThan fail" {
-  crash
-  require stderr "13"
-  require stderr "14"
-}
-
-unittest test {
-  \ Testing.checkGreaterThan(13,14)
-}
-
-
-testcase "checkLessThan success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkLessThan(13,14)
-}
-
-
-testcase "checkLessThan fail" {
-  crash
-  require stderr "13"
-  require stderr "11"
-}
-
-unittest test {
-  \ Testing.checkLessThan(13,11)
-}
-
-
-testcase "checkTrue success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkTrue(1)
-}
-
-
-testcase "checkTrue fail" {
-  crash
-  require stderr "true"
-}
-
-unittest test {
-  \ Testing.checkTrue(0)
-}
-
-
-testcase "checkFalse success" {
-  success
-}
-
-unittest test {
-  \ Testing.checkFalse(0)
-}
-
-
-testcase "checkFalse fail" {
-  crash
-  require stderr "false"
-}
-
-unittest test {
-  \ Testing.checkFalse(1)
-}
diff --git a/lib/testing/test/util.0rt b/lib/testing/test/util.0rt
new file mode 100644
--- /dev/null
+++ b/lib/testing/test/util.0rt
@@ -0,0 +1,195 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "IndentAppend" {
+  success
+}
+
+unittest nothingAppended {
+  String result <- IndentAppend.new(String.builder())
+      .build()
+      .formatted()
+  \ Testing.checkEquals(result, "")
+}
+
+unittest addNewline {
+  String result <- IndentAppend.new(String.builder())
+      .addNewline()
+      .build()
+      .formatted()
+  \ Testing.checkEquals(result, "\n")
+}
+
+unittest usesNewlines {
+  String result <- IndentAppend.new(String.builder())
+      .append("message1")
+      .append("message2")
+      .build()
+      .formatted()
+  \ Testing.checkEquals(result, "message1\nmessage2\n")
+}
+
+unittest indents {
+  String result <- IndentAppend.new(String.builder())
+      .pushIndent()
+      .append("message1")
+      .pushIndent()
+      .append("message2")
+      .popIndent()
+      .popIndent()
+      .append("message3")
+      .build()
+      .formatted()
+  \ Testing.checkEquals(result, "  message1\n    message2\nmessage3\n")
+}
+
+
+testcase "ValueList" {
+  success
+}
+
+concrete Helper {
+  @type new (ValueList<Int>) -> (Helper)
+
+  @value checkNext (Int) -> (Helper)
+  @value checkFinished () -> ()
+}
+
+define Helper {
+  @value optional Order<Int> current
+
+  new (list) {
+    return Helper{ list.defaultOrder() }
+  }
+
+  checkNext (value) {
+    \ Testing.checkEquals(current&.get(), value)
+    current <- current&.next()
+    return self
+  }
+
+  checkFinished () {
+    if (`present` current) {
+      fail("list not empty")
+    }
+  }
+}
+
+unittest nothingAdded {
+  ValueList<Int> list <- ValueList<Int>.new()
+  \ Testing.checkEquals(list.isEmpty(), true)
+  \ Helper.new(list)
+      .checkFinished()
+}
+
+unittest withItems {
+  ValueList<Int> list <- ValueList<Int>.new()
+      .append(1)
+      .append(2)
+      .append(3)
+  \ Testing.checkEquals(list.isEmpty(), false)
+  \ Helper.new(list)
+      .checkNext(1)
+      .checkNext(2)
+      .checkNext(3)
+      .checkFinished()
+}
+
+
+testcase "TestHandler skips tracing" {
+  failure
+  require "message"
+  exclude "exitTest"
+}
+
+unittest exitTest {
+  \ TestHandler:failAndExit("message")
+}
+
+
+testcase "TestReportTree success" {
+  success
+}
+
+unittest nothingAdded {
+  TestReportTree report <- TestReportTree.new("title", ValueList<Formatted>.new())
+  IndentAppend output <- IndentAppend.new(String.builder())
+  \ Testing.checkEquals(report.hasError(), false)
+  \ report.prune()&.writeTo(output)
+  \ Testing.checkEquals(output.build().formatted(), "")
+}
+
+unittest nonFailingSections {
+  TestReportTree report <- TestReportTree.new("title", ValueList<Formatted>.new())
+  \ report.newSection(title: "child1")
+  \ report.newSection(title: "child2")
+  \ Testing.checkEquals(report.hasError(), false)
+  IndentAppend output <- IndentAppend.new(String.builder())
+  \ report.prune()&.writeTo(output)
+  \ Testing.checkEquals(output.build().formatted(), "")
+}
+
+
+testcase "TestReportTree prune doesn't include non-failing sections" {
+  failure
+  require "title"
+  require "child2"
+  require "message2"
+  require "child4"
+  require "message4"
+  exclude "child1"
+  exclude "child5"
+}
+
+unittest test {
+  TestReportTree report <- TestReportTree.new("title", ValueList<Formatted>.new())
+  \ report.newSection(title: "child1")
+  \ report.newSection(title: "child2").addError(message: "message2")
+  \ report.newSection(title: "child3").addError(message: "message3").discardReport()
+  \ report.newSection(title: "child4").addError(message: "message4")
+  \ report.newSection(title: "child5")
+  \ Testing.checkEquals(report.hasError(), true)
+  IndentAppend output <- IndentAppend.new(String.builder())
+  \ report.prune()&.writeTo(output)
+  \ TestHandler:failAndExit(output.build().formatted())
+}
+
+
+testcase "TestReportTree unpruned preserves non-failing sections" {
+  failure
+  require "title"
+  require "child1"
+  require "child2"
+  require "message2"
+  require "child4"
+  require "message4"
+  require "child5"
+}
+
+unittest test {
+  TestReportTree report <- TestReportTree.new("title", ValueList<Formatted>.new())
+  \ report.newSection(title: "child1")
+  \ report.newSection(title: "child2").addError(message: "message2")
+  \ report.newSection(title: "child3").addError(message: "message3").discardReport()
+  \ report.newSection(title: "child4").addError(message: "message4")
+  \ report.newSection(title: "child5")
+  \ Testing.checkEquals(report.hasError(), true)
+  IndentAppend output <- IndentAppend.new(String.builder())
+  \ report.writeTo(output)
+  \ TestHandler:failAndExit(output.build().formatted())
+}
diff --git a/lib/thread/.zeolite-module b/lib/thread/.zeolite-module
--- a/lib/thread/.zeolite-module
+++ b/lib/thread/.zeolite-module
@@ -24,6 +24,12 @@
     categories: [EnumeratedBarrier EnumeratedWait]
   }
 ]
+extension_specs: [
+  category {
+    name: EnumeratedBarrier
+    refines: [ReadAt<BarrierWait>]
+  }
+]
 mode: incremental {
   link_flags: ["-lpthread"]
 }
diff --git a/lib/thread/barrier.0rp b/lib/thread/barrier.0rp
--- a/lib/thread/barrier.0rp
+++ b/lib/thread/barrier.0rp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -16,6 +16,9 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
+// NOTE: This functionality isn't available on MacOS because MacOS doesn't
+// support thread barriers.
+
 // Waits for a thread barrier.
 @value interface BarrierWait {
   // Block until the barrier condition is satisfied.
@@ -24,8 +27,6 @@
 
 // A thread barrier validated using thread enumeration.
 concrete EnumeratedBarrier {
-  refines ReadAt<BarrierWait>
-
   // Create a new barrier for a fixed number of threads.
   //
   // Args:
diff --git a/lib/thread/src/Extension_Enumerated.cpp b/lib/thread/src/Extension_Enumerated.cpp
--- a/lib/thread/src/Extension_Enumerated.cpp
+++ b/lib/thread/src/Extension_Enumerated.cpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -19,7 +19,10 @@
 #include <atomic>
 #include <vector>
 
+#ifndef __APPLE__
 #include <pthread.h>
+#endif
+
 #include <string.h>
 
 #include "category-source.hpp"
@@ -29,6 +32,8 @@
 #include "Category_EnumeratedBarrier.hpp"
 #include "Category_Int.hpp"
 
+#ifndef __APPLE__
+
 namespace {
 
 class Barrier {
@@ -97,13 +102,19 @@
 
 }  // namespace
 
+#endif
+
 #ifdef ZEOLITE_PRIVATE_NAMESPACE
 namespace ZEOLITE_PRIVATE_NAMESPACE {
 #endif  // ZEOLITE_PRIVATE_NAMESPACE
 
+#ifndef __APPLE__
+
 BoxedValue CreateValue_EnumeratedWait(
   S<const Type_EnumeratedWait> parent, S<Barrier> b, int i);
 
+#endif
+
 struct ExtCategory_EnumeratedWait : public Category_EnumeratedWait {
 };
 
@@ -111,6 +122,8 @@
   inline ExtType_EnumeratedWait(Category_EnumeratedWait& p, Params<0>::Type params) : Type_EnumeratedWait(p, params) {}
 };
 
+#ifndef __APPLE__
+
 struct ExtValue_EnumeratedWait : public Value_EnumeratedWait {
   inline ExtValue_EnumeratedWait(S<const Type_EnumeratedWait> p, S<Barrier> b, int i)
     : Value_EnumeratedWait(std::move(p)), barrier(b), index(i) {}
@@ -129,6 +142,8 @@
   int index;
 };
 
+#endif
+
 Category_EnumeratedWait& CreateCategory_EnumeratedWait() {
   static auto& category = *new ExtCategory_EnumeratedWait();
   return category;
@@ -141,11 +156,15 @@
 
 void RemoveType_EnumeratedWait(const Params<0>::Type& params) {}
 
+#ifndef __APPLE__
+
 BoxedValue CreateValue_EnumeratedWait(
   S<const Type_EnumeratedWait> parent, S<Barrier> b, int i) {
   return BoxedValue::New<ExtValue_EnumeratedWait>(std::move(parent), std::move(b), i);
 }
 
+#endif
+
 #ifdef ZEOLITE_PRIVATE_NAMESPACE
 }  // namespace ZEOLITE_PRIVATE_NAMESPACE
 using namespace ZEOLITE_PRIVATE_NAMESPACE;
@@ -167,6 +186,9 @@
 
   ReturnTuple Call_new(const ParamsArgs& params_args) const final {
     TRACE_FUNCTION("EnumeratedBarrier.new")
+#ifdef __APPLE__
+    FAIL() << "Error creating barriers: BarrierWait not supported on MacOS";
+#else
     const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
     if (Var_arg1 < 0) {
       FAIL() << "Invalid barrier thread count " << Var_arg1;
@@ -179,6 +201,7 @@
       waits.push_back(wait);
     }
     return ReturnTuple(CreateValue_EnumeratedBarrier(PARAM_SELF, std::move(waits)));
+#endif
   }
 };
 
diff --git a/lib/thread/src/testing.0rx b/lib/thread/src/testing.0rx
--- a/lib/thread/src/testing.0rx
+++ b/lib/thread/src/testing.0rx
@@ -20,15 +20,15 @@
 
 define NoOpRoutine {
   create () {
-    return NoOpRoutine{ }
+    return delegate -> #self
   }
 
-  run () {}
+  run () { }
 }
 
 define CrashRoutine {
   create () {
-    return CrashRoutine{ }
+    return delegate -> #self
   }
 
   run () {
@@ -38,7 +38,7 @@
 
 define InfiniteRoutine {
   create () {
-    return InfiniteRoutine{ }
+    return delegate -> #self
   }
 
   run () { $NoTrace$
diff --git a/lib/thread/test/barrier.0rt b/lib/thread/test/barrier.0rt
--- a/lib/thread/test/barrier.0rt
+++ b/lib/thread/test/barrier.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,12 +17,12 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "EnumeratedBarrier tests" {
-  success
+  success TestChecker
 }
 
 unittest correctCount {
   ReadAt<BarrierWait> barriers <- EnumeratedBarrier.new(13)
-  \ Testing.checkEquals(barriers.size(),13)
+  \ barriers.size() `Matches:with` CheckValue:equals(13)
 }
 
 unittest waitForOneThread {
@@ -32,7 +32,7 @@
 
 unittest zeroAllowed {
   ReadAt<BarrierWait> barriers <- EnumeratedBarrier.new(0)
-  \ Testing.checkEquals(barriers.size(),0)
+  \ barriers.size() `Matches:with` CheckValue:equals(0)
 }
 
 unittest waitForMultipleThreads {
@@ -40,14 +40,14 @@
   Mutex mutex <- SimpleMutex.new()
   ReadAt<BarrierWait> barriers <- EnumeratedBarrier.new(3)
 
-  Thread thread1 <- ProcessThread.from(WaitAndIncrement.create(2,value,mutex,barriers.readAt(1))).start()
-  Thread thread2 <- ProcessThread.from(WaitAndIncrement.create(2,value,mutex,barriers.readAt(2))).start()
+  Thread thread1 <- ProcessThread.from(WaitAndIncrement.create(2, value, mutex, barriers.readAt(1))).start()
+  Thread thread2 <- ProcessThread.from(WaitAndIncrement.create(2, value, mutex, barriers.readAt(2))).start()
 
   \ barriers.readAt(0).wait()
-  \ Testing.checkEquals(value.get(),2)
+  \ value.get() `Matches:with` CheckValue:equals(2)
 
   \ barriers.readAt(0).wait()
-  \ Testing.checkEquals(value.get(),4)
+  \ value.get() `Matches:with` CheckValue:equals(4)
 
   \ thread1.join()
   \ thread2.join()
@@ -56,7 +56,7 @@
 concrete WaitAndIncrement {
   refines Routine
 
-  @type create (Int,Value,Mutex,BarrierWait) -> (WaitAndIncrement)
+  @type create (Int, Value, Mutex, BarrierWait) -> (WaitAndIncrement)
 }
 
 define WaitAndIncrement {
@@ -65,7 +65,7 @@
   @value Mutex mutex
   @value BarrierWait barrier
 
-  create (count,value,mutex,barrier) {
+  create (count, value, mutex, barrier) {
     return WaitAndIncrement{ count, value, mutex, barrier }
   }
 
@@ -73,7 +73,7 @@
     scoped {
       Int i <- 0
     } in while (i < count) {
-      $Hidden[i,count]$
+      $Hidden[i, count]$
       scoped {
         MutexLock lock <- MutexLock.lock(mutex)
       } cleanup {
@@ -92,7 +92,7 @@
 
 
 testcase "EnumeratedBarrier crashes with negative count" {
-  crash
+  failure
   require "-4"
 }
 
@@ -102,7 +102,7 @@
 
 
 testcase "wait() crashes when references are lost" {
-  crash
+  failure
   require "BarrierWait.*destroyed"
 }
 
@@ -115,7 +115,7 @@
 
 
 testcase "references lost during wait() causes crash" {
-  crash
+  failure
   require "BarrierWait.*waiting"
 }
 
@@ -149,7 +149,7 @@
 
 
 testcase "wait() crashes when two threads use same BarrierWait" {
-  crash
+  failure
   require "BarrierWait.*in use"
 }
 
diff --git a/lib/thread/test/condition.0rt b/lib/thread/test/condition.0rt
--- a/lib/thread/test/condition.0rt
+++ b/lib/thread/test/condition.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,15 +17,15 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "ThreadCondition tests" {
-  success
+  success TestChecker
 }
 
 unittest resumeAll {
   Value value <- Value.create()
   ThreadCondition cond <- ThreadCondition.new()
 
-  Thread thread1 <- ProcessThread.from(WaitAndUpdate.create(value,cond)).start()
-  Thread thread2 <- ProcessThread.from(WaitAndUpdate.create(value,cond)).start()
+  Thread thread1 <- ProcessThread.from(WaitAndUpdate.create(value, cond)).start()
+  Thread thread2 <- ProcessThread.from(WaitAndUpdate.create(value, cond)).start()
 
   // Wait for both threads to increment Value once, to indicate readiness.
   while (true) {
@@ -42,15 +42,15 @@
   \ thread1.join()
   \ thread2.join()
 
-  \ Testing.checkEquals(value.get(),4)
+  \ value.get() `Matches:with` CheckValue:equals(4)
 }
 
 unittest resumeOne {
   Value value <- Value.create()
   ThreadCondition cond <- ThreadCondition.new()
 
-  Thread thread1 <- ProcessThread.from(WaitAndUpdate.create(value,cond)).start()
-  Thread thread2 <- ProcessThread.from(WaitAndUpdate.create(value,cond)).start()
+  Thread thread1 <- ProcessThread.from(WaitAndUpdate.create(value, cond)).start()
+  Thread thread2 <- ProcessThread.from(WaitAndUpdate.create(value, cond)).start()
 
   \ Realtime.sleepSeconds(0.01)
 
@@ -69,20 +69,20 @@
   \ thread1.join()
   \ thread2.join()
 
-  \ Testing.checkEquals(value.get(),3)
+  \ value.get() `Matches:with` CheckValue:equals(3)
 }
 
 unittest forceWaitTimeout {
   Value value <- Value.create()
   ThreadCondition cond <- ThreadCondition.new()
 
-  Thread thread <- ProcessThread.from(WaitAndUpdate.create(value,cond)).start()
+  Thread thread <- ProcessThread.from(WaitAndUpdate.create(value, cond)).start()
 
   \ Realtime.sleepSeconds(0.2)
   \ cond.resumeOne()
   \ thread.join()
 
-  \ Testing.checkEquals(value.get(),1)
+  \ value.get() `Matches:with` CheckValue:equals(1)
 }
 
 unittest waitNoTimeout {
@@ -121,14 +121,14 @@
 concrete WaitAndUpdate {
   refines Routine
 
-  @type create (Value,ConditionWait) -> (WaitAndUpdate)
+  @type create (Value, ConditionWait) -> (WaitAndUpdate)
 }
 
 define WaitAndUpdate {
   @value Value value
   @value ConditionWait cond
 
-  create (value,cond) {
+  create (value, cond) {
     return WaitAndUpdate{ value, cond }
   }
 
@@ -150,7 +150,7 @@
 
 
 testcase "wait() crashes if not locked first" {
-  crash
+  failure
   require "waiting for condition"
 }
 
@@ -160,7 +160,7 @@
 
 
 testcase "timedWait() crashes if not locked first" {
-  crash
+  failure
   require "waiting for condition"
 }
 
@@ -170,7 +170,7 @@
 
 
 testcase "timedWait() crashes with negative wait" {
-  crash
+  failure
   require "-0\.1"
 }
 
diff --git a/lib/thread/test/thread.0rt b/lib/thread/test/thread.0rt
--- a/lib/thread/test/thread.0rt
+++ b/lib/thread/test/thread.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021-2022 Kevin P. Barry
+Copyright 2021-2023 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.
@@ -17,16 +17,16 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "simple Thread test" {
-  success
+  success TestChecker
 }
 
 unittest test {
   CountLoop counter <- CountLoop.create(10000)
-  \ Testing.checkEquals(counter.get(),0)
+  \ counter.get() `Matches:with` CheckValue:equals(0)
   \ counter.start()
-  \ Testing.checkEquals(counter.get(),10000)
+  \ counter.get() `Matches:with` CheckValue:equals(10000)
   \ counter.start()
-  \ Testing.checkEquals(counter.get(),20000)
+  \ counter.get() `Matches:with` CheckValue:equals(20000)
 }
 
 concrete CountLoop {
@@ -79,17 +79,17 @@
 
 
 testcase "ProcessThread mutex integration test" {
-  success
+  success TestChecker
 }
 
 unittest test {
   Mutex mutex <- SimpleMutex.new()
   Value value <- Value.create()
-  Thread evenThread <- ProcessThread.from(EvenOrOdd.create(mutex,true, 10,value)).start()
-  Thread oddThread  <- ProcessThread.from(EvenOrOdd.create(mutex,false,10,value)).start()
+  Thread evenThread <- ProcessThread.from(EvenOrOdd.create(mutex, true, 10, value)).start()
+  Thread oddThread  <- ProcessThread.from(EvenOrOdd.create(mutex, false, 10, value)).start()
   \ evenThread.join()
   \ oddThread.join()
-  \ Testing.checkEquals(value.get(),20)
+  \ value.get() `Matches:with` CheckValue:equals(20)
 }
 
 concrete EvenOrOdd {
@@ -100,7 +100,7 @@
   // - Bool:  Increment only on even if true, or on odd if false.
   // - Int:   Number of increment operations.
   // - Value: Value to increment.
-  @type create (Mutex,Bool,Int,Value) -> (Routine)
+  @type create (Mutex, Bool, Int, Value) -> (Routine)
 }
 
 define EvenOrOdd {
@@ -109,7 +109,7 @@
   @value Int count
   @value Value value
 
-  create (m,e,c,v) {
+  create (m, e, c, v) {
     return EvenOrOdd{ m, e, c, v }
   }
 
@@ -143,7 +143,7 @@
 
 
 testcase "Argv available in Thread" {
-  success
+  success TestChecker
   args "arg1"
 }
 
@@ -163,13 +163,13 @@
   }
 
   run () {
-    \ Testing.checkEquals(Argv.global().readAt(1),"arg1")
+    \ Argv.global().readAt(1) `Matches:with` CheckValue:equals("arg1")
   }
 }
 
 
 testcase "join() crashes if Thread not started yet" {
-  crash
+  failure
   require "thread.*started"
 }
 
@@ -188,7 +188,7 @@
 
 
 testcase "join() twice crashes" {
-  crash
+  failure
   require "thread.*started"
 }
 
@@ -198,7 +198,7 @@
 
 
 testcase "detach() crashes if Thread not started yet" {
-  crash
+  failure
   require "thread.*started"
 }
 
@@ -217,7 +217,7 @@
 
 
 testcase "detach() twice crashes" {
-  crash
+  failure
   require "thread.*started"
 }
 
@@ -246,7 +246,7 @@
 
 
 testcase "creation tracing in Thread" {
-  crash
+  failure
   // Thread creation.
   require "CrashThread\.new"
   // Inherited trace from start call.
diff --git a/lib/util/.zeolite-module b/lib/util/.zeolite-module
--- a/lib/util/.zeolite-module
+++ b/lib/util/.zeolite-module
@@ -10,7 +10,7 @@
     expression: \x1000
   }
 ]
-private_deps: [
+public_deps: [
   "lib/testing"
 ]
 extra_files: [
@@ -43,4 +43,4 @@
     categories: [SpinlockMutex]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/lib/util/counters.0rp b/lib/util/counters.0rp
--- a/lib/util/counters.0rp
+++ b/lib/util/counters.0rp
@@ -31,7 +31,7 @@
 // Creates an Order<#x> that repeats a single value, for use with traverse.
 concrete Repeat<#x> {
   // Repeat the value the specified number of times.
-  @category times<#y>     (#y,Int) -> (optional Order<#y>)
+  @category times<#y>     (#y, Int) -> (optional Order<#y>)
   // Repeat the value an unlimited number of times.
   @category unlimited<#y> (#y)     -> (optional Order<#y>)
 }
@@ -41,17 +41,17 @@
   // Get the lower value. (Defaults to the first value.)
   @category min<#x>
     #x defines LessThan<#x>
-  (#x,#x) -> (#x)
+  (#x, #x) -> (#x)
 
   // Get the higher value. (Defaults to the second value.)
   @category max<#x>
     #x defines LessThan<#x>
-  (#x,#x) -> (#x)
+  (#x, #x) -> (#x)
 
   // Determine the lower and higher values. (Defaults to the same order.)
   @category minMax<#x>
     #x defines LessThan<#x>
-  (#x,#x) -> (#x,#x)
+  (#x, #x) -> (#x, #x)
 }
 
 // Builds a sequence of values.
diff --git a/lib/util/extra.0rp b/lib/util/extra.0rp
--- a/lib/util/extra.0rp
+++ b/lib/util/extra.0rp
@@ -63,6 +63,8 @@
 
   // Get the contained value. Crashes if there is no value.
   @value getValue () -> (#x)
+  // Get the contained value. Returns empty if there is no value.
+  @value tryValue () -> (optional #x)
   // Get the error message. Crashes if there is no error.
   @value getError () -> (Formatted)
   // Convert the error to any other ErrorOr type. Crashes if there is no error.
@@ -81,7 +83,7 @@
   // Notes:
   // - When used in unit tests, arg 0 is "testcase" and the remaining args are
   //   set from args in the testcase. (See extra.0rt.)
-  @type global () -> ([ReadAt<String>&SubSequence])
+  @type global () -> ([ReadAt<String> & SubSequence])
 }
 
 // A resource that requires explicit cleanup.
diff --git a/lib/util/helpers.0rp b/lib/util/helpers.0rp
--- a/lib/util/helpers.0rp
+++ b/lib/util/helpers.0rp
@@ -29,7 +29,7 @@
   //   Bool lt <- x.defaultOrder() `OrderH:lessThan` y.defaultOrder()
   @category lessThan<#x>
     #x defines LessThan<#x>
-  (optional Order<#x>,optional Order<#x>) -> (Bool)
+  (optional Order<#x>, optional Order<#x>) -> (Bool)
 
   // The same as lessThan, but with a custom comparator.
   //
@@ -39,10 +39,10 @@
   //
   // Example:
   //
-  //   Bool lt <- x.defaultOrder() `OrderH:lessThanWith<?,Reversed<Int>>` y.defaultOrder()
-  @category lessThanWith<#x,#xx>
+  //   Bool lt <- x.defaultOrder() `OrderH:lessThanWith<?, Reversed<Int>>` y.defaultOrder()
+  @category lessThanWith<#x, #xx>
     #xx defines LessThan<#x>
-  (optional Order<#x>,optional Order<#x>) -> (Bool)
+  (optional Order<#x>, optional Order<#x>) -> (Bool)
 
   // Extend equals, based on position-wise comparisons.
   //
@@ -51,7 +51,7 @@
   //   Bool eq <- x.defaultOrder() `OrderH:equals` y.defaultOrder()
   @category equals<#x>
     #x defines Equals<#x>
-  (optional Order<#x>,optional Order<#x>) -> (Bool)
+  (optional Order<#x>, optional Order<#x>) -> (Bool)
 
   // The same as equals, but with a custom comparator.
   //
@@ -61,10 +61,10 @@
   //
   // Example:
   //
-  //   Bool eq <- x.defaultOrder() `OrderH:equalsWith<?,BySize>` y.defaultOrder()
-  @category equalsWith<#x,#xx>
+  //   Bool eq <- x.defaultOrder() `OrderH:equalsWith<?, BySize>` y.defaultOrder()
+  @category equalsWith<#x, #xx>
     #xx defines Equals<#x>
-  (optional Order<#x>,optional Order<#x>) -> (Bool)
+  (optional Order<#x>, optional Order<#x>) -> (Bool)
 
   // Copies references to all elements to the destination.
   //
@@ -78,9 +78,9 @@
   //
   // Returns:
   // - #c: The container originally passed.
-  @category copyTo<#x,#c>
+  @category copyTo<#x, #c>
     #c requires Append<#x>
-  (optional Order<#x>,#c) -> (#c)
+  (optional Order<#x>, #c) -> (#c)
 
   // Duplicates all elements to the destination.
   //
@@ -94,10 +94,10 @@
   //
   // Returns:
   // - #c: The container originally passed.
-  @category duplicateTo<#x,#c>
+  @category duplicateTo<#x, #c>
     #x requires Duplicate
     #c requires Append<#x>
-  (optional Order<#x>,#c) -> (#c)
+  (optional Order<#x>, #c) -> (#c)
 }
 
 // Helpers to extend functionality to the ReadAt<#x> built-in.
@@ -113,7 +113,7 @@
   //   Bool lt <- x `ReadAtH:lessThan` y
   @category lessThan<#x>
     #x defines LessThan<#x>
-  (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+  (ReadAt<#x>, ReadAt<#x>) -> (Bool)
 
   // The same as lessThan, but with a custom comparator.
   //
@@ -123,10 +123,10 @@
   //
   // Example:
   //
-  //   Bool lt <- x `ReadAtH:lessThanWith<?,Reversed<Int>>` y
-  @category lessThanWith<#x,#xx>
+  //   Bool lt <- x `ReadAtH:lessThanWith<?, Reversed<Int>>` y
+  @category lessThanWith<#x, #xx>
     #xx defines LessThan<#x>
-  (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+  (ReadAt<#x>, ReadAt<#x>) -> (Bool)
 
   // Extend equals, based on position-wise comparisons.
   //
@@ -135,7 +135,7 @@
   //   Bool eq <- x `ReadAtH:equals` y
   @category equals<#x>
     #x defines Equals<#x>
-  (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+  (ReadAt<#x>, ReadAt<#x>) -> (Bool)
 
   // The same as equals, but with a custom comparator.
   //
@@ -145,10 +145,10 @@
   //
   // Example:
   //
-  //   Bool eq <- x `ReadAtH:equalsWith<?,BySize>` y
-  @category equalsWith<#x,#xx>
+  //   Bool eq <- x `ReadAtH:equalsWith<?, BySize>` y
+  @category equalsWith<#x, #xx>
     #xx defines Equals<#x>
-  (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+  (ReadAt<#x>, ReadAt<#x>) -> (Bool)
 
   // Copies references to all elements to the destination.
   //
@@ -162,9 +162,9 @@
   //
   // Returns:
   // - #c: The container originally passed.
-  @category copyTo<#x,#c>
+  @category copyTo<#x, #c>
     #c requires Append<#x>
-  (ReadAt<#x>,#c) -> (#c)
+  (ReadAt<#x>, #c) -> (#c)
 
   // Duplicates all elements to the destination.
   //
@@ -178,10 +178,10 @@
   //
   // Returns:
   // - #c: The container originally passed.
-  @category duplicateTo<#x,#c>
+  @category duplicateTo<#x, #c>
     #x requires Duplicate
     #c requires Append<#x>
-  (ReadAt<#x>,#c) -> (#c)
+  (ReadAt<#x>, #c) -> (#c)
 
   // Iterates over the container from 0 to the end.
   @category forwardOrder<#x> (ReadAt<#x>) -> (optional Order<#x>)
@@ -190,7 +190,7 @@
   @category reverseOrder<#x> (ReadAt<#x>) -> (optional Order<#x>)
 
   // Iterates over the container using the provided index order.
-  @category iterateWith<#x> (ReadAt<#x>,optional Order<Int>) -> (optional Order<#x>)
+  @category iterateWith<#x> (ReadAt<#x>, optional Order<Int>) -> (optional Order<#x>)
 }
 
 // Helpers to extend functionality to the Formatted built-in.
diff --git a/lib/util/interfaces.0rp b/lib/util/interfaces.0rp
--- a/lib/util/interfaces.0rp
+++ b/lib/util/interfaces.0rp
@@ -18,5 +18,5 @@
 
 // A @value version of the LessThan @type interface.
 @value interface LessThan2<#x|> {
-  lessThan2 (#x,#x) -> (Bool)
+  lessThan2 (#x, #x) -> (Bool)
 }
diff --git a/lib/util/src/counters.0rx b/lib/util/src/counters.0rx
--- a/lib/util/src/counters.0rx
+++ b/lib/util/src/counters.0rx
@@ -64,7 +64,7 @@
       actualStart <- start + increment*(require(actualCount)-1)
       actualIncrement <- -increment
     }
-    return IntCounter.new(actualStart,actualIncrement,actualCount)
+    return IntCounter.new(actualStart, actualIncrement, actualCount)
   }
 
   start (x) {
@@ -96,7 +96,7 @@
 }
 
 concrete IntCounter {
-  @type new (Int,Int,optional Int) -> (optional Order<Int>)
+  @type new (Int, Int, optional Int) -> (optional Order<Int>)
 }
 
 define IntCounter {
@@ -108,11 +108,11 @@
   @value Int increment
   @value optional Int remaining
 
-  new (start,increment,count) {
+  new (start, increment, count) {
     if (`present` count && `require` count <= 0) {
       return empty
     } else {
-      return #self{ start, increment, count }
+      return delegate -> #self
     }
   }
 
@@ -141,7 +141,7 @@
   @value #x value
   @value Order<any> counter
 
-  times (value,max) {
+  times (value, max) {
     optional Order<any> counter <- Counter.builder().count(max).done()
     if (! `present` counter) {
       return empty
@@ -170,15 +170,15 @@
 }
 
 define Ranges {
-  min (x,y) {
-    return minMax(x,y){0}
+  min (x, y) {
+    return minMax(x, y){0}
   }
 
-  max (x,y) (z) {
-    return minMax(x,y){1}
+  max (x, y) (z) {
+    return minMax(x, y){1}
   }
 
-  minMax (x,y) {
+  minMax (x, y) {
     // NOTE: Using y < x makes the default x, y when they are equal.
     if (y `#x.lessThan` x) {
       return y, x
diff --git a/lib/util/src/extra.0rx b/lib/util/src/extra.0rx
--- a/lib/util/src/extra.0rx
+++ b/lib/util/src/extra.0rx
@@ -31,11 +31,11 @@
     return "Void"
   }
 
-  equals (_,_) {
+  equals (_, _) {
     return true
   }
 
-  lessThan (_,_) {
+  lessThan (_, _) {
     return false
   }
 
@@ -67,7 +67,7 @@
     fail("container is always empty")
   }
 
-  writeAt (_,_) {
+  writeAt (_, _) {
     fail("container is always empty")
   }
 }
@@ -96,13 +96,17 @@
     }
   }
 
+  tryValue () {
+    return maybeValue
+  }
+
   getError () {
     return require(maybeError)
   }
 
   convertError () {
     scoped {
-      optional ErrorOr<all> error <- reduce<#self,ErrorOr<all>>(self)
+      optional ErrorOr<all> error <- reduce<#self, ErrorOr<all>>(self)
     } in if (present(error)) {
       return require(error)
     } else {
diff --git a/lib/util/src/helpers.0rx b/lib/util/src/helpers.0rx
--- a/lib/util/src/helpers.0rx
+++ b/lib/util/src/helpers.0rx
@@ -17,16 +17,16 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define OrderH {
-  lessThan (x,y) {
-    return lessThanWith<#x,#x>(x,y)
+  lessThan (x, y) {
+    return lessThanWith<#x, #x>(x, y)
   }
 
-  lessThanWith (x,y) {
+  lessThanWith (x, y) {
     optional Order<#x> xx <- x
     optional Order<#x> yy <- y
-    $Hidden[x,y]$
+    $Hidden[x, y]$
     while (present(xx) && present(yy)) {
-      $ReadOnly[xx,yy]$
+      $ReadOnly[xx, yy]$
       if (require(xx).get() `#xx.lessThan` require(yy).get()) {
         return true
       } elif (require(yy).get() `#xx.lessThan` require(xx).get()) {
@@ -39,16 +39,16 @@
     return present(yy)
   }
 
-  equals (x,y) {
-    return equalsWith<#x,#x>(x,y)
+  equals (x, y) {
+    return equalsWith<#x, #x>(x, y)
   }
 
-  equalsWith (x,y) {
+  equalsWith (x, y) {
     optional Order<#x> xx <- x
     optional Order<#x> yy <- y
-    $Hidden[x,y]$
+    $Hidden[x, y]$
     while (present(xx) && present(yy)) {
-      $ReadOnly[xx,yy]$
+      $ReadOnly[xx, yy]$
       if (!(require(xx).get() `#xx.equals` require(yy).get())) {
         return false
       }
@@ -59,7 +59,7 @@
     return !present(xx) && !present(yy)
   }
 
-  copyTo (input,output) (original) {
+  copyTo (input, output) (original) {
     original <- output
     $Hidden[original]$
     traverse (input -> #x value) {
@@ -67,7 +67,7 @@
     }
   }
 
-  duplicateTo (input,output) (original) {
+  duplicateTo (input, output) (original) {
     original <- output
     $Hidden[original]$
     traverse (input -> #x value) {
@@ -77,11 +77,11 @@
 }
 
 define ReadAtH {
-  lessThan (x,y) {
-    return lessThanWith<#x,#x>(x,y)
+  lessThan (x, y) {
+    return lessThanWith<#x, #x>(x, y)
   }
 
-  lessThanWith (x,y) {
+  lessThanWith (x, y) {
     traverse (`Counter.zeroIndexed` (x.size() `Ranges:min` y.size()) -> Int i) {
       $ReadOnly[i]$
       if (x.readAt(i) `#xx.lessThan` y.readAt(i)) {
@@ -93,11 +93,11 @@
     return x.size() < y.size()
   }
 
-  equals (x,y) {
-    return equalsWith<#x,#x>(x,y)
+  equals (x, y) {
+    return equalsWith<#x, #x>(x, y)
   }
 
-  equalsWith (x,y) {
+  equalsWith (x, y) {
     if (x.size() != y.size()) {
       return false
     }
@@ -110,7 +110,7 @@
     return true
   }
 
-  copyTo (input,output) (original) {
+  copyTo (input, output) (original) {
     original <- output
     $Hidden[original]$
     traverse (`Counter.zeroIndexed` input.size() -> Int i) {
@@ -118,7 +118,7 @@
     }
   }
 
-  duplicateTo (input,output) (original) {
+  duplicateTo (input, output) (original) {
     original <- output
     $Hidden[original]$
     traverse (`Counter.zeroIndexed` input.size() -> Int i) {
@@ -134,7 +134,7 @@
     return input `iterateWith` `Counter.revZeroIndexed` input.size()
   }
 
-  iterateWith (input,order) {
+  iterateWith (input, order) {
     return input `IndexOrder:iterateWith` order
   }
 }
@@ -143,7 +143,7 @@
   try (x) {
     if (present(x)) {
       scoped {
-        optional Formatted formatted <- reduce<#x,Formatted>(x)
+        optional Formatted formatted <- reduce<#x, Formatted>(x)
       } in if (present(formatted)) {
         return require(formatted)
       } else {
@@ -164,7 +164,7 @@
 concrete IndexOrder<#x> {
   refines Order<#x>
 
-  @category iterateWith<#x> (ReadAt<#x>,optional Order<Int>) -> (optional Order<#x>)
+  @category iterateWith<#x> (ReadAt<#x>, optional Order<Int>) -> (optional Order<#x>)
 }
 
 define IndexOrder {
@@ -173,7 +173,7 @@
   @value ReadAt<#x> container
   @value Order<Int> index
 
-  iterateWith (container,index) {
+  iterateWith (container, index) {
     if (`present` index) {
       return IndexOrder<#x>{ container, `require` index }
     } else {
@@ -198,27 +198,27 @@
 }
 
 define Reversed {
-  lessThan (x,y) {
+  lessThan (x, y) {
     return y `#x.lessThan` x
   }
 }
 
 define BySize {
-  equals (x,y) {
+  equals (x, y) {
     return x.size() == y.size()
   }
 
-  lessThan (x,y) {
+  lessThan (x, y) {
     return x.size() < y.size()
   }
 }
 
 define AlwaysEqual {
-  equals (_,_) {
+  equals (_, _) {
     return true
   }
 
-  lessThan (_,_) {
+  lessThan (_, _) {
     return false
   }
 }
@@ -227,10 +227,10 @@
   refines LessThan2<#x>
 
   new () {
-    return #self{ }
+    return delegate -> #self
   }
 
-  lessThan2 (x,y) {
+  lessThan2 (x, y) {
     return x `#xx.lessThan` y
   }
 }
diff --git a/lib/util/src/input.0rx b/lib/util/src/input.0rx
--- a/lib/util/src/input.0rx
+++ b/lib/util/src/input.0rx
@@ -25,7 +25,7 @@
   }
 
   readAll (reader) {
-    [Append<Formatted>&Build<String>] builder <- String.builder()
+    [Append<Formatted> & Build<String>] builder <- String.builder()
     while (!reader.pastEnd()) {
       \ builder.append(reader.readBlock($ExprLookup[BLOCK_READ_SIZE]$))
     }
@@ -69,8 +69,8 @@
 
   @value takeLine (Int) -> (String)
   takeLine (position) {
-    String data <- buffer.subSequence(0,position)
-    buffer <- buffer.subSequence(position+1,buffer.size()-position-1)
+    String data <- buffer.subSequence(0, position)
+    buffer <- buffer.subSequence(position+1, buffer.size()-position-1)
     return data
   }
 }
diff --git a/lib/util/src/parse.0rx b/lib/util/src/parse.0rx
--- a/lib/util/src/parse.0rx
+++ b/lib/util/src/parse.0rx
@@ -95,7 +95,7 @@
     }
 
     total <- sign*total
-    $Hidden[sign,hasDigit]$
+    $Hidden[sign, hasDigit]$
 
     Int expSign <- 0
     Int exp <- 0
@@ -107,7 +107,7 @@
         Char c <- require(curr).get()
         curr <- require(curr).next()
         $ReadOnly[c]$
-        $Hidden[curr,total]$
+        $Hidden[curr, total]$
         if (expSign == 0) {
           if (c == '-') {
             expSign <- -1
diff --git a/lib/util/src/testing.0rx b/lib/util/src/testing.0rx
--- a/lib/util/src/testing.0rx
+++ b/lib/util/src/testing.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2023 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.
@@ -16,43 +16,74 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-$TestsOnly$
+define CheckErrorOr {
+  $ReadOnlyExcept[]$
 
-define UtilTesting {
-  checkError (x) {
-    if (!x.isError()) {
-      fail(String.builder()
-          .append("expected an error but got value ")
-          .append(x.getValue())
-          .build())
-    }
+  refines ValueMatcher<ErrorOr<#x>>
+
+  @value optional ValueMatcher<#x> valueMatcher
+  @value optional ValueMatcher<String> errorMatcher
+
+  value (valueMatcher) {
+    return CheckErrorOr<#x>{ valueMatcher, empty }
   }
 
-  checkSuccess (x,y) {
-    if (x.isError()) {
-      fail(String.builder()
-          .append("expected ")
-          .append(y)
-          .append(" but got error ")
-          .append(x.getError())
-          .build())
-    } else {
-      \ Testing.checkEquals(x.getValue(),y)
+  error (errorMatcher) {
+    return CheckErrorOr<any>{ empty, errorMatcher }
+  }
+
+  check (actual, report) {
+    optional #x value <- empty
+    if (`present` actual && !require(actual).isError()) {
+      value <- require(actual).getValue()
     }
+    optional String error <- empty
+    if (`present` actual && require(actual).isError()) {
+      error <- require(actual).getError().formatted()
+    }
+    MultiChecker checker <- MultiChecker.new(report)
+    if (`present` valueMatcher) {
+      \ checker.tryCheck(title: "value", value, require(valueMatcher))
+    } elif (`present` value) {
+      optional Formatted formattedValue <- reduce<#y, ErrorOr<Formatted>>(actual)&.getValue()
+      if (`present` formattedValue) {
+      \ report.newSection(title: "value")
+          .addError(message: String.builder()
+              .append("expected empty but got \"")
+              .append(`require` formattedValue)
+              .append("\"")
+              .build())
+      } else {
+      \ report.newSection(title: "value")
+          .addError(message: String.builder()
+              .append("expected empty but got ")
+              .append(typename<#y>())
+              .build())
+      }
+    }
+    if (`present` errorMatcher) {
+      \ checker.tryCheck(title: "error", error, require(errorMatcher))
+    } elif (`present` error) {
+      \ report.newSection(title: "error")
+          .addError(message: String.builder()
+              .append("expected empty but got \"")
+              .append(require(error).formatted())
+              .append("\"")
+              .build())
+    }
   }
 
-  checkSuccessBetween (x,l,h) {
-    if (x.isError()) {
-      fail(String.builder()
-          .append("expected value between ")
-          .append(l)
-          .append(" and ")
-          .append(h)
-          .append(" but got error ")
-          .append(x.getError())
-          .build())
+  summary () {
+    if (present(valueMatcher)) {
+      return String.builder()
+          .append(typename<ErrorOr<#x>>())
+          .append(" has matching value")
+          .build()
     } else {
-      \ Testing.checkBetween(x.getValue(),l,h)
+      return String.builder()
+          .append(typename<ErrorOr<#x>>())
+          .append(" has error")
+          .build()
     }
   }
 }
diff --git a/lib/util/test/counters.0rt b/lib/util/test/counters.0rt
--- a/lib/util/test/counters.0rt
+++ b/lib/util/test/counters.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021-2022 Kevin P. Barry
+Copyright 2021-2023 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.
@@ -17,18 +17,18 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "counter tests" {
-  success
+  success TestChecker
 }
 
 unittest zeroIndexed {
   Int index <- 0
 
   traverse (Counter.zeroIndexed(10) -> Int i) {
-    \ Testing.checkEquals(i,index)
+    \ i `Matches:with` CheckValue:equals(index)
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,10)
+  \ index `Matches:with` CheckValue:equals(10)
 }
 
 unittest zeroIndexedEmpty {
@@ -41,11 +41,11 @@
   Int index <- 9
 
   traverse (Counter.revZeroIndexed(10) -> Int i) {
-    \ Testing.checkEquals(i,index)
+    \ i `Matches:with` CheckValue:equals(index)
     index <- index-1
   }
 
-  \ Testing.checkEquals(index,-1)
+  \ index `Matches:with` CheckValue:equals(-1)
 }
 
 unittest revZeroIndexedEmpty {
@@ -58,13 +58,13 @@
   Int index <- 0
 
   traverse (Counter.unlimited() -> Int i) {
-    \ Testing.checkEquals(i,index)
+    \ i `Matches:with` CheckValue:equals(index)
     if ((index <- index+1) >= 10) {
       break
     }
   }
 
-  \ Testing.checkEquals(index,10)
+  \ index `Matches:with` CheckValue:equals(10)
 }
 
 unittest builderReversePositive {
@@ -77,11 +77,11 @@
                 .limit(17)
                 .reverse()
                 .done() -> Int i) {
-    \ Testing.checkEquals(i,index)
+    \ i `Matches:with` CheckValue:equals(index)
     index <- index-5
   }
 
-  \ Testing.checkEquals(index,-2)
+  \ index `Matches:with` CheckValue:equals(-2)
 }
 
 unittest builderReverseNegative {
@@ -94,22 +94,22 @@
                 .limit(3)
                 .reverse()
                 .done() -> Int i) {
-    \ Testing.checkEquals(i,index)
+    \ i `Matches:with` CheckValue:equals(index)
     index <- index+5
   }
 
-  \ Testing.checkEquals(index,22)
+  \ index `Matches:with` CheckValue:equals(22)
 }
 
 unittest times {
   Int index <- 0
 
   traverse ("message" `Repeat:times` 10 -> String m) {
-    \ Testing.checkEquals(m,"message")
+    \ m `Matches:with` CheckValue:equals("message")
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,10)
+  \ index `Matches:with` CheckValue:equals(10)
 }
 
 unittest timesEmpty {
@@ -122,47 +122,47 @@
   Int index <- 0
 
   traverse (Repeat:unlimited("message") -> String m) {
-    \ Testing.checkEquals(m,"message")
+    \ m `Matches:with` CheckValue:equals("message")
     if ((index <- index+1) >= 10) {
       break
     }
   }
 
-  \ Testing.checkEquals(index,10)
+  \ index `Matches:with` CheckValue:equals(10)
 }
 
 unittest minMax {
   scoped {
-    Int x, Int y <- Ranges:minMax(10,3)
+    Int x, Int y <- Ranges:minMax(10, 3)
   } in {
-    \ Testing.checkEquals(x,3)
-    \ Testing.checkEquals(y,10)
+    \ x `Matches:with` CheckValue:equals(3)
+    \ y `Matches:with` CheckValue:equals(10)
   }
 
   scoped {
-    Int x, Int y <- Ranges:minMax(3,10)
+    Int x, Int y <- Ranges:minMax(3, 10)
   } in {
-    \ Testing.checkEquals(x,3)
-    \ Testing.checkEquals(y,10)
+    \ x `Matches:with` CheckValue:equals(3)
+    \ y `Matches:with` CheckValue:equals(10)
   }
 }
 
 unittest min {
   scoped {
-    Int x <- Ranges:min(10,3)
-  } in \ Testing.checkEquals(x,3)
+    Int x <- Ranges:min(10, 3)
+  } in \ x `Matches:with` CheckValue:equals(3)
 
   scoped {
-    Int x <- Ranges:min(3,10)
-  } in \ Testing.checkEquals(x,3)
+    Int x <- Ranges:min(3, 10)
+  } in \ x `Matches:with` CheckValue:equals(3)
 }
 
 unittest max {
   scoped {
-    Int x <- Ranges:max(10,3)
-  } in \ Testing.checkEquals(x,10)
+    Int x <- Ranges:max(10, 3)
+  } in \ x `Matches:with` CheckValue:equals(10)
 
   scoped {
-    Int x <- Ranges:max(3,10)
-  } in \ Testing.checkEquals(x,10)
+    Int x <- Ranges:max(3, 10)
+  } in \ x `Matches:with` CheckValue:equals(10)
 }
diff --git a/lib/util/test/extra.0rt b/lib/util/test/extra.0rt
--- a/lib/util/test/extra.0rt
+++ b/lib/util/test/extra.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -17,31 +17,31 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "Argv checks" {
-  success
+  success TestChecker
   args "arg1" "arg2" "arg3" "arg4"
 }
 
 unittest global {
   Int count <- Argv.global().size()
-  \ Testing.checkEquals(count,5)
-  \ Testing.checkEquals(Argv.global().readAt(0),"testcase")
-  \ Testing.checkEquals(Argv.global().readAt(1),"arg1")
-  \ Testing.checkEquals(Argv.global().readAt(2),"arg2")
-  \ Testing.checkEquals(Argv.global().readAt(3),"arg3")
-  \ Testing.checkEquals(Argv.global().readAt(4),"arg4")
+  \ count `Matches:with` CheckValue:equals(5)
+  \ Argv.global().readAt(0) `Matches:with` CheckValue:equals("testcase")
+  \ Argv.global().readAt(1) `Matches:with` CheckValue:equals("arg1")
+  \ Argv.global().readAt(2) `Matches:with` CheckValue:equals("arg2")
+  \ Argv.global().readAt(3) `Matches:with` CheckValue:equals("arg3")
+  \ Argv.global().readAt(4) `Matches:with` CheckValue:equals("arg4")
 }
 
 unittest subSequence {
-  ReadAt<String> argv <- Argv.global().subSequence(2,2)
+  ReadAt<String> argv <- Argv.global().subSequence(2, 2)
   Int count <- argv.size()
-  \ Testing.checkEquals(count,2)
-  \ Testing.checkEquals(argv.readAt(0),"arg2")
-  \ Testing.checkEquals(argv.readAt(1),"arg3")
+  \ count `Matches:with` CheckValue:equals(2)
+  \ argv.readAt(0) `Matches:with` CheckValue:equals("arg2")
+  \ argv.readAt(1) `Matches:with` CheckValue:equals("arg3")
 }
 
 
 testcase "Argv readAt out of bounds" {
-  crash
+  failure
   require "index 5"
 }
 
@@ -51,41 +51,43 @@
 
 
 testcase "Argv subSequence out of bounds" {
-  crash
+  failure
   require "index 5"
 }
 
 unittest test {
-  \ Argv.global().subSequence(5,1)
+  \ Argv.global().subSequence(5, 1)
 }
 
 
 testcase "ErrorOr checks" {
-  success
+  success TestChecker
 }
 
 unittest withValue {
   ErrorOr<Int> value <- ErrorOr:value<Int>(10)
-  \ Testing.checkFalse(value.isError())
-  \ Testing.checkEquals(value.getValue(),10)
+  \ value.isError() `Matches:with` CheckValue:equals(false)
+  \ value.getValue() `Matches:with` CheckValue:equals(10)
+  \ value.tryValue() `Matches:with` CheckValue:equals(10)
 }
 
 unittest withError {
   ErrorOr<String> value <- ErrorOr:error("error message")
-  \ Testing.checkTrue(value.isError())
-  \ Testing.checkEquals(value.getError().formatted(),"error message")
+  \ value.isError() `Matches:with` CheckValue:equals(true)
+  \ value.tryValue() `Matches:with` CheckValue:equals(empty?String)
+  \ value.getError().formatted() `Matches:with` CheckValue:equals("error message")
 }
 
 unittest convertError {
   scoped {
     ErrorOr<String> error <- ErrorOr:error("error message")
   } in ErrorOr<Int> value <- error.convertError()
-  \ Testing.checkEquals(value.getError().formatted(),"error message")
+  \ value.getError().formatted() `Matches:with` CheckValue:equals("error message")
 }
 
 
 testcase "ErrorOr getError() crashes with value" {
-  crash
+  failure
   require "empty"
 }
 
@@ -96,7 +98,7 @@
 
 
 testcase "ErrorOr getValue() crashes with error" {
-  crash
+  failure
   require "error message"
 }
 
@@ -107,7 +109,7 @@
 
 
 testcase "ErrorOr convertError() crashes with no error" {
-  crash
+  failure
   require "no error"
 }
 
@@ -118,28 +120,28 @@
 
 
 testcase "Void tests" {
-  success
+  success TestChecker
 }
 
 unittest formatted {
-  \ Testing.checkEquals(Void.default().formatted(),"Void")
+  \ Void.default().formatted() `Matches:with` CheckValue:equals("Void")
 }
 
 unittest lessThan {
-  \ Testing.checkFalse(Void.default() `Void.lessThan` Void.default())
+  \ Void.default() `Void.lessThan` Void.default() `Matches:with` CheckValue:equals(false)
 }
 
 unittest equals {
-  \ Testing.checkTrue(Void.default() `Void.equals` Void.default())
+  \ Void.default() `Void.equals` Void.default() `Matches:with` CheckValue:equals(true)
 }
 
 unittest hashed {
-  \ Testing.checkEquals(Void.default().duplicate().hashed(),Void.default().hashed())
+  \ Void.default().duplicate().hashed() `Matches:with` CheckValue:equals(Void.default().hashed())
 }
 
 
 testcase "AlwaysEmpty tests" {
-  success
+  success TestChecker
 }
 
 unittest conversions {
@@ -148,11 +150,11 @@
 }
 
 unittest size {
-  \ Testing.checkEquals(AlwaysEmpty.default().size(),0)
+  \ AlwaysEmpty.default().size() `Matches:with` CheckValue:equals(0)
 }
 
 unittest duplicate {
-  \ Testing.checkEquals(AlwaysEmpty.default().duplicate().size(),0)
+  \ AlwaysEmpty.default().duplicate().size() `Matches:with` CheckValue:equals(0)
 }
 
 unittest defaultOrder {
@@ -163,7 +165,7 @@
 
 
 testcase "AlwaysEmpty.readAt fails" {
-  crash
+  failure
   require "empty"
 }
 
@@ -173,10 +175,10 @@
 
 
 testcase "AlwaysEmpty.writeAt fails" {
-  crash
+  failure
   require "empty"
 }
 
 unittest readAt {
-  \ AlwaysEmpty.default().writeAt(0,123)
+  \ AlwaysEmpty.default().writeAt(0, 123)
 }
diff --git a/lib/util/test/helpers.0rt b/lib/util/test/helpers.0rt
--- a/lib/util/test/helpers.0rt
+++ b/lib/util/test/helpers.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,73 +17,73 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "comparator helper tests" {
-  success
+  success TestChecker
 }
 
 unittest orderHLessThan {
-  \ Testing.checkFalse("".defaultOrder()    `OrderH:lessThan` "".defaultOrder())
-  \ Testing.checkTrue("a".defaultOrder()    `OrderH:lessThan` "abc".defaultOrder())
-  \ Testing.checkFalse("b".defaultOrder()   `OrderH:lessThan` "abc".defaultOrder())
-  \ Testing.checkFalse("abc".defaultOrder() `OrderH:lessThan` "abc".defaultOrder())
-  \ Testing.checkFalse("abc".defaultOrder() `OrderH:lessThan` "a".defaultOrder())
-  \ Testing.checkTrue("abc".defaultOrder()  `OrderH:lessThan` "b".defaultOrder())
+  \ "".defaultOrder()    `OrderH:lessThan` "".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "a".defaultOrder()    `OrderH:lessThan` "abc".defaultOrder() `Matches:with` CheckValue:equals(true)
+  \ "b".defaultOrder()   `OrderH:lessThan` "abc".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "abc".defaultOrder() `OrderH:lessThan` "abc".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "abc".defaultOrder() `OrderH:lessThan` "a".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "abc".defaultOrder()  `OrderH:lessThan` "b".defaultOrder() `Matches:with` CheckValue:equals(true)
 }
 
 unittest orderHLessThanWith {
-  \ Testing.checkFalse("".defaultOrder()    `OrderH:lessThanWith<?,Reversed<Char>>` "".defaultOrder())
-  \ Testing.checkTrue("a".defaultOrder()    `OrderH:lessThanWith<?,Reversed<Char>>` "abc".defaultOrder())
-  \ Testing.checkTrue("b".defaultOrder()    `OrderH:lessThanWith<?,Reversed<Char>>` "abc".defaultOrder())
-  \ Testing.checkFalse("abc".defaultOrder() `OrderH:lessThanWith<?,Reversed<Char>>` "abc".defaultOrder())
-  \ Testing.checkFalse("abc".defaultOrder() `OrderH:lessThanWith<?,Reversed<Char>>` "a".defaultOrder())
-  \ Testing.checkFalse("abc".defaultOrder() `OrderH:lessThanWith<?,Reversed<Char>>` "b".defaultOrder())
+  \ "".defaultOrder()    `OrderH:lessThanWith<?, Reversed<Char>>` "".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "a".defaultOrder()    `OrderH:lessThanWith<?, Reversed<Char>>` "abc".defaultOrder() `Matches:with` CheckValue:equals(true)
+  \ "b".defaultOrder()    `OrderH:lessThanWith<?, Reversed<Char>>` "abc".defaultOrder() `Matches:with` CheckValue:equals(true)
+  \ "abc".defaultOrder() `OrderH:lessThanWith<?, Reversed<Char>>` "abc".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "abc".defaultOrder() `OrderH:lessThanWith<?, Reversed<Char>>` "a".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "abc".defaultOrder() `OrderH:lessThanWith<?, Reversed<Char>>` "b".defaultOrder() `Matches:with` CheckValue:equals(false)
 }
 
 unittest orderHEquals {
-  \ Testing.checkTrue("".defaultOrder()     `OrderH:equals` "".defaultOrder())
-  \ Testing.checkFalse("a".defaultOrder()   `OrderH:equals` "abc".defaultOrder())
-  \ Testing.checkTrue("abc".defaultOrder()  `OrderH:equals` "abc".defaultOrder())
-  \ Testing.checkFalse("abc".defaultOrder() `OrderH:equals` "a".defaultOrder())
+  \ "".defaultOrder()     `OrderH:equals` "".defaultOrder() `Matches:with` CheckValue:equals(true)
+  \ "a".defaultOrder()   `OrderH:equals` "abc".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "abc".defaultOrder()  `OrderH:equals` "abc".defaultOrder() `Matches:with` CheckValue:equals(true)
+  \ "abc".defaultOrder() `OrderH:equals` "a".defaultOrder() `Matches:with` CheckValue:equals(false)
 }
 
 unittest orderHEqualsWith {
-  \ Testing.checkTrue("".defaultOrder()     `OrderH:equalsWith<?,IgnoreCase>` "".defaultOrder())
-  \ Testing.checkFalse("a".defaultOrder()   `OrderH:equalsWith<?,IgnoreCase>` "ABC".defaultOrder())
-  \ Testing.checkTrue("abc".defaultOrder()  `OrderH:equalsWith<?,IgnoreCase>` "ABC".defaultOrder())
-  \ Testing.checkFalse("abc".defaultOrder() `OrderH:equalsWith<?,IgnoreCase>` "DEF".defaultOrder())
-  \ Testing.checkFalse("abc".defaultOrder() `OrderH:equalsWith<?,IgnoreCase>` "A".defaultOrder())
+  \ "".defaultOrder()     `OrderH:equalsWith<?, IgnoreCase>` "".defaultOrder() `Matches:with` CheckValue:equals(true)
+  \ "a".defaultOrder()   `OrderH:equalsWith<?, IgnoreCase>` "ABC".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "abc".defaultOrder()  `OrderH:equalsWith<?, IgnoreCase>` "ABC".defaultOrder() `Matches:with` CheckValue:equals(true)
+  \ "abc".defaultOrder() `OrderH:equalsWith<?, IgnoreCase>` "DEF".defaultOrder() `Matches:with` CheckValue:equals(false)
+  \ "abc".defaultOrder() `OrderH:equalsWith<?, IgnoreCase>` "A".defaultOrder() `Matches:with` CheckValue:equals(false)
 }
 
 unittest readAtHLessThan {
-  \ Testing.checkFalse(""    `ReadAtH:lessThan` "")
-  \ Testing.checkTrue("a"    `ReadAtH:lessThan` "abc")
-  \ Testing.checkFalse("b"   `ReadAtH:lessThan` "abc")
-  \ Testing.checkFalse("abc" `ReadAtH:lessThan` "abc")
-  \ Testing.checkFalse("abc" `ReadAtH:lessThan` "a")
-  \ Testing.checkTrue("abc"  `ReadAtH:lessThan` "b")
+  \ ""    `ReadAtH:lessThan` "" `Matches:with` CheckValue:equals(false)
+  \ "a"    `ReadAtH:lessThan` "abc" `Matches:with` CheckValue:equals(true)
+  \ "b"   `ReadAtH:lessThan` "abc" `Matches:with` CheckValue:equals(false)
+  \ "abc" `ReadAtH:lessThan` "abc" `Matches:with` CheckValue:equals(false)
+  \ "abc" `ReadAtH:lessThan` "a" `Matches:with` CheckValue:equals(false)
+  \ "abc"  `ReadAtH:lessThan` "b" `Matches:with` CheckValue:equals(true)
 }
 
 unittest readAtHLessThanWith {
-  \ Testing.checkFalse(""    `ReadAtH:lessThanWith<?,Reversed<Char>>` "")
-  \ Testing.checkTrue("a"    `ReadAtH:lessThanWith<?,Reversed<Char>>` "abc")
-  \ Testing.checkTrue("b"    `ReadAtH:lessThanWith<?,Reversed<Char>>` "abc")
-  \ Testing.checkFalse("abc" `ReadAtH:lessThanWith<?,Reversed<Char>>` "abc")
-  \ Testing.checkFalse("abc" `ReadAtH:lessThanWith<?,Reversed<Char>>` "a")
-  \ Testing.checkFalse("abc" `ReadAtH:lessThanWith<?,Reversed<Char>>` "b")
+  \ ""    `ReadAtH:lessThanWith<?, Reversed<Char>>` "" `Matches:with` CheckValue:equals(false)
+  \ "a"    `ReadAtH:lessThanWith<?, Reversed<Char>>` "abc" `Matches:with` CheckValue:equals(true)
+  \ "b"    `ReadAtH:lessThanWith<?, Reversed<Char>>` "abc" `Matches:with` CheckValue:equals(true)
+  \ "abc" `ReadAtH:lessThanWith<?, Reversed<Char>>` "abc" `Matches:with` CheckValue:equals(false)
+  \ "abc" `ReadAtH:lessThanWith<?, Reversed<Char>>` "a" `Matches:with` CheckValue:equals(false)
+  \ "abc" `ReadAtH:lessThanWith<?, Reversed<Char>>` "b" `Matches:with` CheckValue:equals(false)
 }
 
 unittest readAtHEquals {
-  \ Testing.checkTrue(""     `ReadAtH:equals` "")
-  \ Testing.checkFalse("a"   `ReadAtH:equals` "abc")
-  \ Testing.checkTrue("abc"  `ReadAtH:equals` "abc")
-  \ Testing.checkFalse("abc" `ReadAtH:equals` "a")
+  \ ""     `ReadAtH:equals` "" `Matches:with` CheckValue:equals(true)
+  \ "a"   `ReadAtH:equals` "abc" `Matches:with` CheckValue:equals(false)
+  \ "abc"  `ReadAtH:equals` "abc" `Matches:with` CheckValue:equals(true)
+  \ "abc" `ReadAtH:equals` "a" `Matches:with` CheckValue:equals(false)
 }
 
 unittest readAtHEqualsWith {
-  \ Testing.checkTrue(""     `ReadAtH:equalsWith<?,IgnoreCase>` "")
-  \ Testing.checkFalse("a"   `ReadAtH:equalsWith<?,IgnoreCase>` "ABC")
-  \ Testing.checkTrue("abc"  `ReadAtH:equalsWith<?,IgnoreCase>` "ABC")
-  \ Testing.checkFalse("abc" `ReadAtH:equalsWith<?,IgnoreCase>` "DEF")
-  \ Testing.checkFalse("abc" `ReadAtH:equalsWith<?,IgnoreCase>` "a")
+  \ ""     `ReadAtH:equalsWith<?, IgnoreCase>` "" `Matches:with` CheckValue:equals(true)
+  \ "a"   `ReadAtH:equalsWith<?, IgnoreCase>` "ABC" `Matches:with` CheckValue:equals(false)
+  \ "abc"  `ReadAtH:equalsWith<?, IgnoreCase>` "ABC" `Matches:with` CheckValue:equals(true)
+  \ "abc" `ReadAtH:equalsWith<?, IgnoreCase>` "DEF" `Matches:with` CheckValue:equals(false)
+  \ "abc" `ReadAtH:equalsWith<?, IgnoreCase>` "a" `Matches:with` CheckValue:equals(false)
 }
 
 concrete IgnoreCase {
@@ -91,7 +91,7 @@
 }
 
 define IgnoreCase {
-  equals (x,y) {
+  equals (x, y) {
     return toLower(x) == toLower(y)
   }
 
@@ -106,118 +106,118 @@
 }
 
 unittest reversed {
-  \ Testing.checkFalse(0 `Reversed<Int>.lessThan` 0)
-  \ Testing.checkFalse(0 `Reversed<Int>.lessThan` 1)
-  \ Testing.checkTrue(1  `Reversed<Int>.lessThan` 0)
+  \ 0 `Reversed<Int>.lessThan` 0 `Matches:with` CheckValue:equals(false)
+  \ 0 `Reversed<Int>.lessThan` 1 `Matches:with` CheckValue:equals(false)
+  \ 1  `Reversed<Int>.lessThan` 0 `Matches:with` CheckValue:equals(true)
 }
 
 unittest bySizeLessThan {
-  \ Testing.checkFalse(""   `BySize.lessThan` "")
-  \ Testing.checkTrue("b"   `BySize.lessThan` "ac")
-  \ Testing.checkFalse("ac" `BySize.lessThan` "b")
-  \ Testing.checkFalse("a"  `BySize.lessThan` "b")
+  \ ""   `BySize.lessThan` "" `Matches:with` CheckValue:equals(false)
+  \ "b"   `BySize.lessThan` "ac" `Matches:with` CheckValue:equals(true)
+  \ "ac" `BySize.lessThan` "b" `Matches:with` CheckValue:equals(false)
+  \ "a"  `BySize.lessThan` "b" `Matches:with` CheckValue:equals(false)
 }
 
 unittest bySizeEquals {
-  \ Testing.checkTrue(""    `BySize.equals` "")
-  \ Testing.checkFalse("b"  `BySize.equals` "ac")
-  \ Testing.checkFalse("ac" `BySize.equals` "b")
-  \ Testing.checkTrue("a"   `BySize.equals` "b")
+  \ ""    `BySize.equals` "" `Matches:with` CheckValue:equals(true)
+  \ "b"  `BySize.equals` "ac" `Matches:with` CheckValue:equals(false)
+  \ "ac" `BySize.equals` "b" `Matches:with` CheckValue:equals(false)
+  \ "a"   `BySize.equals` "b" `Matches:with` CheckValue:equals(true)
 }
 
 unittest alwaysEqualLessThan {
-  \ Testing.checkFalse(""   `AlwaysEqual.lessThan` "")
-  \ Testing.checkFalse("b"  `AlwaysEqual.lessThan` "ac")
-  \ Testing.checkFalse("ac" `AlwaysEqual.lessThan` "b")
+  \ ""   `AlwaysEqual.lessThan` "" `Matches:with` CheckValue:equals(false)
+  \ "b"  `AlwaysEqual.lessThan` "ac" `Matches:with` CheckValue:equals(false)
+  \ "ac" `AlwaysEqual.lessThan` "b" `Matches:with` CheckValue:equals(false)
 }
 
 unittest alwaysEqualEquals {
-  \ Testing.checkTrue(""   `AlwaysEqual.equals` "")
-  \ Testing.checkTrue("b"  `AlwaysEqual.equals` "ac")
-  \ Testing.checkTrue("ac" `AlwaysEqual.equals` "b")
-  \ Testing.checkTrue("a"  `AlwaysEqual.equals` "b")
+  \ ""   `AlwaysEqual.equals` "" `Matches:with` CheckValue:equals(true)
+  \ "b"  `AlwaysEqual.equals` "ac" `Matches:with` CheckValue:equals(true)
+  \ "ac" `AlwaysEqual.equals` "b" `Matches:with` CheckValue:equals(true)
+  \ "a"  `AlwaysEqual.equals` "b" `Matches:with` CheckValue:equals(true)
 }
 
 
 testcase "iteration tests" {
-  success
+  success TestChecker
 }
 
 unittest orderHCopyTo {
   ValueCheck output <- ValueCheck.new(10)
-  \ ValueOrder.wrap(false,Counter.zeroIndexed(10)) `OrderH:copyTo` output
+  \ ValueOrder.wrap(false, Counter.zeroIndexed(10)) `OrderH:copyTo` output
   \ output.checkEnd()
 }
 
 unittest orderHDuplicateTo {
   ValueCheck output <- ValueCheck.new(10)
-  \ ValueOrder.wrap(true,Counter.zeroIndexed(10)) `OrderH:duplicateTo` output
+  \ ValueOrder.wrap(true, Counter.zeroIndexed(10)) `OrderH:duplicateTo` output
   \ output.checkEnd()
 }
 
 unittest readAtHCopyTo {
   ValueCheck output <- ValueCheck.new(10)
-  \ ValueSource.new(false,10) `ReadAtH:copyTo` output
+  \ ValueSource.new(false, 10) `ReadAtH:copyTo` output
   \ output.checkEnd()
 }
 
 unittest readAtHDuplicateTo {
   ValueCheck output <- ValueCheck.new(10)
-  \ ValueSource.new(true,10) `ReadAtH:duplicateTo` output
+  \ ValueSource.new(true, 10) `ReadAtH:duplicateTo` output
   \ output.checkEnd()
 }
 
 unittest readAtHForwardOrder {
-  ValueSource source <- ValueSource.new(false,10)
+  ValueSource source <- ValueSource.new(false, 10)
   Int expected <- 0
   traverse (`ReadAtH:forwardOrder` source -> Value actual) {
-    \ Testing.checkEquals(actual.get(),expected)
+    \ actual.get() `Matches:with` CheckValue:equals(expected)
   } update {
     expected <- expected+1
   }
-  \ Testing.checkEquals(expected,10)
+  \ expected `Matches:with` CheckValue:equals(10)
 }
 
 unittest readAtHForwardOrderEmpty {
-  ValueSource source <- ValueSource.new(false,0)
+  ValueSource source <- ValueSource.new(false, 0)
   traverse (`ReadAtH:forwardOrder` source -> _) {
     fail("not empty")
   }
 }
 
 unittest readAtHReverseOrder {
-  ValueSource source <- ValueSource.new(false,10)
+  ValueSource source <- ValueSource.new(false, 10)
   Int expected <- 9
   traverse (`ReadAtH:reverseOrder` source -> Value actual) {
-    \ Testing.checkEquals(actual.get(),expected)
+    \ actual.get() `Matches:with` CheckValue:equals(expected)
   } update {
     expected <- expected-1
   }
-  \ Testing.checkEquals(expected,-1)
+  \ expected `Matches:with` CheckValue:equals(-1)
 }
 
 unittest readAtHReverseOrderEmpty {
-  ValueSource source <- ValueSource.new(false,0)
+  ValueSource source <- ValueSource.new(false, 0)
   traverse (`ReadAtH:reverseOrder` source -> _) {
     fail("not empty")
   }
 }
 
 unittest readAtHIterateWith {
-  ValueSource source <- ValueSource.new(false,10)
+  ValueSource source <- ValueSource.new(false, 10)
   Int expected <- 0
   traverse (source `ReadAtH:iterateWith` `Counter.zeroIndexed` 5 -> Value actual) {
-    \ Testing.checkEquals(actual.get(),expected)
+    \ actual.get() `Matches:with` CheckValue:equals(expected)
   } update {
     expected <- expected+1
   }
-  \ Testing.checkEquals(expected,5)
+  \ expected `Matches:with` CheckValue:equals(5)
 }
 
 concrete ValueOrder {
   refines Order<Value>
 
-  @type wrap (Bool,optional Order<Int>) -> (optional Order<Value>)
+  @type wrap (Bool, optional Order<Int>) -> (optional Order<Value>)
 }
 
 define ValueOrder {
@@ -226,7 +226,7 @@
   @value Bool original
   @value Order<Int> order
 
-  wrap (original,order) {
+  wrap (original, order) {
     if (!present(order)) {
       return empty
     } else {
@@ -245,12 +245,12 @@
   }
 
   get () {
-    return Value.new(original,order.get())
+    return Value.new(original, order.get())
   }
 }
 
 concrete ValueCheck {
-  // Must append the next value in the sequence [0,size).
+  // Must append the next value in the sequence [0, size).
   refines Append<Value>
 
   @type new (Int) -> (ValueCheck)
@@ -268,11 +268,11 @@
   }
 
   checkEnd () {
-    \ Testing.checkEquals(current,target)
+    \ current `Matches:with` CheckValue:equals(target)
   }
 
   append (x) {
-    \ Testing.checkEquals(x.get(),current)
+    \ x.get() `Matches:with` CheckValue:equals(current)
     current <- current+1
     return self
   }
@@ -285,19 +285,19 @@
   // Args:
   // - Bool: Whether or not the value is "original".
   // - Int: Value to return from get().
-  @type new (Bool,Int) -> (Value)
+  @type new (Bool, Int) -> (Value)
 
   // Crashes if "original" is true.
   @value get () -> (Int)
 }
 
 define Value {
-  $ReadOnly[original,value]$
+  $ReadOnly[original, value]$
 
   @value Bool original
   @value Int value
 
-  new (original,value) {
+  new (original, value) {
     return Value{ original, value }
   }
 
@@ -317,22 +317,22 @@
 }
 
 concrete ValueSource {
-  // readAt(i) returns Value.new(original,i).
+  // readAt(i) returns Value.new(original, i).
   refines ReadAt<Value>
 
   // Args:
   // - Bool: Whether or not to mark new values as "original".
   // - Int: Total size.
-  @type new (Bool,Int) -> (ValueSource)
+  @type new (Bool, Int) -> (ValueSource)
 }
 
 define ValueSource {
-  $ReadOnly[original,size]$
+  $ReadOnly[original, size]$
 
   @value Bool original
   @value Int size
 
-  new (original,size) {
+  new (original, size) {
     return ValueSource{ original, size }
   }
 
@@ -341,25 +341,25 @@
   }
 
   readAt (i) {
-    \ Testing.checkBetween(i,0,size-1)
-    return Value.new(original,i)
+    \ i `Matches:with` CheckValue:betweenEquals(0, size-1)
+    return Value.new(original, i)
   }
 }
 
 
 testcase "formatting tests" {
-  success
+  success TestChecker
 }
 
 unittest emptyValue {
-  \ Testing.checkEquals(FormattedH:try<Char>(empty).formatted(),"Char{empty}")
-  \ Testing.checkEquals(FormattedH:try(empty).formatted(),"all{empty}")
+  \ FormattedH:try<Char>(empty).formatted() `Matches:with` CheckValue:equals("Char{empty}")
+  \ FormattedH:try(empty).formatted() `Matches:with` CheckValue:equals("all{empty}")
 }
 
 unittest formattedValue {
-  \ Testing.checkEquals(FormattedH:try(123).formatted(),"123")
+  \ FormattedH:try(123).formatted() `Matches:with` CheckValue:equals("123")
 }
 
 unittest nonFormattedValue {
-  \ Testing.checkEquals(FormattedH:try(String.builder()).formatted(),"[Append<Formatted>&Build<String>]{?}")
+  \ FormattedH:try(String.builder()).formatted() `Matches:with` CheckValue:equals("[Append<Formatted>&Build<String>]{?}")
 }
diff --git a/lib/util/test/input.0rt b/lib/util/test/input.0rt
--- a/lib/util/test/input.0rt
+++ b/lib/util/test/input.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -17,20 +17,20 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "TextReader checks" {
-  success
+  success TestChecker
 }
 
 concrete Helper {
-  @type checkNext (TextReader,String) -> ()
+  @type checkNext (TextReader, String) -> ()
 }
 
 define Helper {
-  checkNext (reader,line) {
+  checkNext (reader, line) {
     if (reader.pastEnd()) {
       fail("past end")
     }
     String value <- reader.readNextLine()
-    \ Testing.checkEquals(value,line)
+    \ value `Matches:with` CheckValue:equals(line)
   }
 }
 
@@ -70,11 +70,11 @@
 
 unittest correctSplit {
     TextReader reader <- TextReader.fromBlockReader(FakeFile.create())
-    \ Helper.checkNext(reader,"this is a partial line")
-    \ Helper.checkNext(reader,"with some more data")
-    \ Helper.checkNext(reader,"and")
-    \ Helper.checkNext(reader,"more lines")
-    \ Helper.checkNext(reader,"unfinished")
+    \ Helper.checkNext(reader, "this is a partial line")
+    \ Helper.checkNext(reader, "with some more data")
+    \ Helper.checkNext(reader, "and")
+    \ Helper.checkNext(reader, "more lines")
+    \ Helper.checkNext(reader, "unfinished")
     if (!reader.pastEnd()) {
       fail("not past end")
     }
@@ -85,15 +85,15 @@
 
 unittest readAll {
   String allContents <- TextReader.readAll(FakeFile.create())
-  \ Testing.checkEquals(allContents,"this is a partial line\nwith some more data\nand\nmore lines\nunfinished")
+  \ allContents `Matches:with` CheckValue:equals("this is a partial line\nwith some more data\nand\nmore lines\nunfinished")
 }
 
 
 testcase "stdin is empty in tests" {
-  success
+  success TestChecker
   timeout 1
 }
 
 unittest test {
-  \ Testing.checkEquals(TextReader.readAll(BasicInput.stdin()),"")
+  \ TextReader.readAll(BasicInput.stdin()) `Matches:with` CheckValue:equals("")
 }
diff --git a/lib/util/test/mutex.0rt b/lib/util/test/mutex.0rt
--- a/lib/util/test/mutex.0rt
+++ b/lib/util/test/mutex.0rt
@@ -32,7 +32,7 @@
 
 
 testcase "MutexLock crashes if not freed" {
-  crash
+  failure
   require "not freed"
 }
 
@@ -43,7 +43,7 @@
 
 
 testcase "MutexLock crashes if freed twice" {
-  crash
+  failure
   require "freed multiple times"
 }
 
diff --git a/lib/util/test/output.0rt b/lib/util/test/output.0rt
--- a/lib/util/test/output.0rt
+++ b/lib/util/test/output.0rt
@@ -47,7 +47,7 @@
 
 
 testcase "error writer" {
-  crash
+  failure
   require "message1"
   require "message2"
   require "message3"
diff --git a/lib/util/test/parse.0rt b/lib/util/test/parse.0rt
--- a/lib/util/test/parse.0rt
+++ b/lib/util/test/parse.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,213 +17,213 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "ParseChars tests" {
-  success
+  success TestChecker
 }
 
 unittest floatNoDecimal {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("123"),122.9,123.1)
+  \ ParseChars.float("123") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.0))
 }
 
 unittest floatDecimalLeft {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float(".123"),0.1229,0.1231)
+  \ ParseChars.float(".123") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(0.123))
 }
 
 unittest floatDecimalRight {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("123."),122.9,123.1)
+  \ ParseChars.float("123.") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.0))
 }
 
 unittest floatDecimalMiddle {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("123.456"),123.4559,123.4561)
+  \ ParseChars.float("123.456") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.456))
 }
 
 unittest floatLeadingTrailingZeros {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("000123.456000"),123.4559,123.4561)
+  \ ParseChars.float("000123.456000") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.456))
 }
 
 unittest floatNegative {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("-123.456"),-123.4561,-123.4559)
+  \ ParseChars.float("-123.456") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(-123.456))
 }
 
 unittest floatPositive {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("+123.456"),123.4559,123.4561)
+  \ ParseChars.float("+123.456") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.456))
 }
 
 unittest floatNegativeDecimalLeft {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("-.456"),-0.4561,-0.4559)
+  \ ParseChars.float("-.456") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(-0.456))
 }
 
 unittest floatExponentNoDecimal {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("123E5"),122.9E5,123.1E5)
+  \ ParseChars.float("123E5") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.0E5))
 }
 
 unittest floatNegativeExponent {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("123E-5"),122.9E-5,123.1E-5)
+  \ ParseChars.float("123E-5") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.0E-5))
 }
 
 unittest floatPositiveExponent {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("123E+5"),122.9E5,123.1E5)
+  \ ParseChars.float("123E+5") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.0E5))
 }
 
 unittest floatExponentDecimalRight {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("123.E5"),122.9E5,123.1E5)
+  \ ParseChars.float("123.E5") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.0E5))
 }
 
 unittest floatExponentDecimalLeft {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float(".123E5"),122.9E2,123.1E2)
+  \ ParseChars.float(".123E5") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.0E2))
 }
 
 unittest floatExponentDecimalMiddle {
-  \ UtilTesting.checkSuccessBetween(ParseChars.float("123.456E5"),123.4559E5,123.4561E5)
+  \ ParseChars.float("123.456E5") `Matches:with` CheckErrorOr:value(CheckFloat:almostEquals(123.456E5))
 }
 
 unittest floatEmpty {
-  \ UtilTesting.checkError(ParseChars.float(""))
+  \ ParseChars.float("") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest floatNoDigits {
-  \ UtilTesting.checkError(ParseChars.float("."))
+  \ ParseChars.float(".") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest floatTwoDecimals {
-  \ UtilTesting.checkError(ParseChars.float("1.1."))
+  \ ParseChars.float("1.1.") `Matches:with` CheckErrorOr:error(CheckString:contains("failed"))
 }
 
 unittest floatExponentNoDigitsNoDecimal {
-  \ UtilTesting.checkError(ParseChars.float("E5"))
+  \ ParseChars.float("E5") `Matches:with` CheckErrorOr:error(CheckString:contains("E"))
 }
 
 unittest floatExponentNoDigitsDecimal {
-  \ UtilTesting.checkError(ParseChars.float(".E5"))
+  \ ParseChars.float(".E5") `Matches:with` CheckErrorOr:error(CheckString:contains("E"))
 }
 
 unittest floatExponentNoDigitsSign {
-  \ UtilTesting.checkError(ParseChars.float("-E5"))
+  \ ParseChars.float("-E5") `Matches:with` CheckErrorOr:error(CheckString:contains("E"))
 }
 
 unittest floatDecimalAfterExponent {
-  \ UtilTesting.checkError(ParseChars.float("1E.5"))
+  \ ParseChars.float("1E.5") `Matches:with` CheckErrorOr:error(CheckString:contains("."))
 }
 
 unittest floatExponentExcessData {
-  \ UtilTesting.checkError(ParseChars.float("123E5Q"))
+  \ ParseChars.float("123E5Q") `Matches:with` CheckErrorOr:error(CheckString:contains("failed"))
 }
 
 unittest floatBadCharAfterDigit {
-  \ UtilTesting.checkError(ParseChars.float("123X"))
+  \ ParseChars.float("123X") `Matches:with` CheckErrorOr:error(CheckString:contains("failed"))
 }
 
 unittest floatMissingExponentDigits {
-  \ UtilTesting.checkError(ParseChars.float("123E"))
+  \ ParseChars.float("123E") `Matches:with` CheckErrorOr:error(CheckString:contains("exponent"))
 }
 
 unittest int {
-  \ UtilTesting.checkSuccess(ParseChars.int("1234"),1234)
+  \ ParseChars.int("1234") `Matches:with` CheckErrorOr:value(CheckValue:equals(1234))
 }
 
 unittest intNegative {
-  \ UtilTesting.checkSuccess(ParseChars.int("-1234"),-1234)
+  \ ParseChars.int("-1234") `Matches:with` CheckErrorOr:value(CheckValue:equals(-1234))
 }
 
 unittest intPositive {
-  \ UtilTesting.checkSuccess(ParseChars.int("+1234"),1234)
+  \ ParseChars.int("+1234") `Matches:with` CheckErrorOr:value(CheckValue:equals(1234))
 }
 
 unittest intLeadingZeros {
-  \ UtilTesting.checkSuccess(ParseChars.int("001234"),1234)
+  \ ParseChars.int("001234") `Matches:with` CheckErrorOr:value(CheckValue:equals(1234))
 }
 
 unittest intEmpty {
-  \ UtilTesting.checkError(ParseChars.int(""))
+  \ ParseChars.int("") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest intJustSign {
-  \ UtilTesting.checkError(ParseChars.int("-"))
+  \ ParseChars.int("-") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest intBadDigit {
-  \ UtilTesting.checkError(ParseChars.int("12A"))
+  \ ParseChars.int("12A") `Matches:with` CheckErrorOr:error(CheckString:contains("failed"))
 }
 
 unittest hexInt {
-  \ UtilTesting.checkSuccess(ParseChars.hexInt("12AbCdEf"),\x12ABCDEF)
+  \ ParseChars.hexInt("12AbCdEf") `Matches:with` CheckErrorOr:value(CheckValue:equals(\x12ABCDEF))
 }
 
 unittest hexIntNegative {
-  \ UtilTesting.checkSuccess(ParseChars.hexInt("-12AbCdEf"),-\x12ABCDEF)
+  \ ParseChars.hexInt("-12AbCdEf") `Matches:with` CheckErrorOr:value(CheckValue:equals(-\x12ABCDEF))
 }
 
 unittest hexIntPositive {
-  \ UtilTesting.checkSuccess(ParseChars.hexInt("+12AbCdEf"),\x12ABCDEF)
+  \ ParseChars.hexInt("+12AbCdEf") `Matches:with` CheckErrorOr:value(CheckValue:equals(\x12ABCDEF))
 }
 
 unittest hexIntLeadingZeros {
-  \ UtilTesting.checkSuccess(ParseChars.hexInt("0012AbCdEf"),\x12ABCDEF)
+  \ ParseChars.hexInt("0012AbCdEf") `Matches:with` CheckErrorOr:value(CheckValue:equals(\x12ABCDEF))
 }
 
 unittest hexIntEmpty {
-  \ UtilTesting.checkError(ParseChars.hexInt(""))
+  \ ParseChars.hexInt("") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest hexIntJustSign {
-  \ UtilTesting.checkError(ParseChars.hexInt("-"))
+  \ ParseChars.hexInt("-") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest hexIntBadDigit {
-  \ UtilTesting.checkError(ParseChars.hexInt("12Q"))
+  \ ParseChars.hexInt("12Q") `Matches:with` CheckErrorOr:error(CheckString:contains("failed"))
 }
 
 unittest octInt {
-  \ UtilTesting.checkSuccess(ParseChars.octInt("127"),\o127)
+  \ ParseChars.octInt("127") `Matches:with` CheckErrorOr:value(CheckValue:equals(\o127))
 }
 
 unittest octIntNegative {
-  \ UtilTesting.checkSuccess(ParseChars.octInt("-127"),-\o127)
+  \ ParseChars.octInt("-127") `Matches:with` CheckErrorOr:value(CheckValue:equals(-\o127))
 }
 
 unittest octIntPositive {
-  \ UtilTesting.checkSuccess(ParseChars.octInt("+127"),\o127)
+  \ ParseChars.octInt("+127") `Matches:with` CheckErrorOr:value(CheckValue:equals(\o127))
 }
 
 unittest octIntLeadingZeros {
-  \ UtilTesting.checkSuccess(ParseChars.octInt("00127"),\o127)
+  \ ParseChars.octInt("00127") `Matches:with` CheckErrorOr:value(CheckValue:equals(\o127))
 }
 
 unittest octIntEmpty {
-  \ UtilTesting.checkError(ParseChars.octInt(""))
+  \ ParseChars.octInt("") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest octIntJustSign {
-  \ UtilTesting.checkError(ParseChars.octInt("-"))
+  \ ParseChars.octInt("-") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest octIntBadDigit {
-  \ UtilTesting.checkError(ParseChars.octInt("19"))
+  \ ParseChars.octInt("19") `Matches:with` CheckErrorOr:error(CheckString:contains("failed"))
 }
 
 unittest binInt {
-  \ UtilTesting.checkSuccess(ParseChars.binInt("100101"),\b100101)
+  \ ParseChars.binInt("100101") `Matches:with` CheckErrorOr:value(CheckValue:equals(\b100101))
 }
 
 unittest binIntNegative {
-  \ UtilTesting.checkSuccess(ParseChars.binInt("-100101"),-\b100101)
+  \ ParseChars.binInt("-100101") `Matches:with` CheckErrorOr:value(CheckValue:equals(-\b100101))
 }
 
 unittest binIntPositive {
-  \ UtilTesting.checkSuccess(ParseChars.binInt("+100101"),\b100101)
+  \ ParseChars.binInt("+100101") `Matches:with` CheckErrorOr:value(CheckValue:equals(\b100101))
 }
 
 unittest binIntLeadingZeros {
-  \ UtilTesting.checkSuccess(ParseChars.binInt("00100101"),\b100101)
+  \ ParseChars.binInt("00100101") `Matches:with` CheckErrorOr:value(CheckValue:equals(\b100101))
 }
 
 unittest binIntEmpty {
-  \ UtilTesting.checkError(ParseChars.binInt(""))
+  \ ParseChars.binInt("") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest binIntJustSign {
-  \ UtilTesting.checkError(ParseChars.binInt("-"))
+  \ ParseChars.binInt("-") `Matches:with` CheckErrorOr:error(CheckString:contains("missing digits"))
 }
 
 unittest binIntBadDigit {
-  \ UtilTesting.checkError(ParseChars.binInt("12"))
+  \ ParseChars.binInt("12") `Matches:with` CheckErrorOr:error(CheckString:contains("failed"))
 }
diff --git a/lib/util/test/testing.0rt b/lib/util/test/testing.0rt
--- a/lib/util/test/testing.0rt
+++ b/lib/util/test/testing.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2023 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.
@@ -16,81 +16,91 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-testcase "UtilTesting.checkError with error" {
-  success
+testcase "CheckErrorOr success" {
+  success TestChecker
 }
 
-unittest test {
-  \ UtilTesting.checkError(ErrorOr:error("failure"))
+unittest value {
+  \ ErrorOr:value(123) `Matches:with` CheckErrorOr:value(CheckValue:equals(123))
 }
 
+unittest error {
+  \ ErrorOr:error("something") `Matches:with` CheckErrorOr:error(CheckValue:equals("something"))
+}
 
-testcase "UtilTesting.checkError with value" {
-  crash
-  require "message"
+
+testcase "CheckErrorOr:value failure with value" {
+  failure TestChecker
+  require "456"
+  require "123"
 }
 
 unittest test {
-  \ UtilTesting.checkError(ErrorOr:value("message"))
+  \ ErrorOr:value(456) `Matches:with` CheckErrorOr:value(CheckValue:equals(123))
 }
 
 
-testcase "UtilTesting.checkSuccess success" {
-  success
+testcase "CheckErrorOr:value failure with error" {
+  failure TestChecker
+  require "something"
+  require "123"
 }
 
 unittest test {
-  \ UtilTesting.checkSuccess(ErrorOr:value("message"),"message")
+  \ ErrorOr:error("something") `Matches:with` CheckErrorOr:value(CheckValue:equals(123))
 }
 
 
-testcase "UtilTesting.checkSuccess bad value" {
-  crash
-  require "message"
-  require "foo"
+testcase "CheckErrorOr:value failure with empty" {
+  failure TestChecker
+  require "empty"
+  require "123"
 }
 
 unittest test {
-  \ UtilTesting.checkSuccess(ErrorOr:value("foo"),"message")
+  \ empty `Matches:with` CheckErrorOr:value(CheckValue:equals(123))
 }
 
 
-testcase "UtilTesting.checkSuccess with error" {
-  crash
-  require "message"
-  require "failure"
+testcase "CheckErrorOr:error failure with value" {
+  failure TestChecker
+  require "456"
+  require "something"
 }
 
 unittest test {
-  \ UtilTesting.checkSuccess(ErrorOr:error("failure"),"message")
+  \ ErrorOr:value(456) `Matches:with` CheckErrorOr:error(CheckValue:equals("something"))
 }
 
 
-testcase "UtilTesting.checkSuccessBetween success" {
-  success
+testcase "CheckErrorOr:error failure with error" {
+  failure TestChecker
+  require "message"
+  require "something"
 }
 
 unittest test {
-  \ UtilTesting.checkSuccessBetween(ErrorOr:value(1),0,2)
+  \ ErrorOr:error("message") `Matches:with` CheckErrorOr:error(CheckValue:equals("something"))
 }
 
 
-testcase "UtilTesting.checkSuccess bad value" {
-  crash
-  require "7.+0.*2"
+testcase "CheckErrorOr:error failure with empty" {
+  failure TestChecker
+  require "empty"
+  require "something"
 }
 
 unittest test {
-  \ UtilTesting.checkSuccessBetween(ErrorOr:value(7),0,2)
+  \ empty `Matches:with` CheckErrorOr:error(CheckValue:equals("something"))
 }
 
 
-testcase "UtilTesting.checkSuccess with error" {
-  crash
-  require "0.*2"
-  require "failure"
+testcase "CheckErrorOr:error failure with non-Formatted" {
+  failure TestChecker
+  require "CharBuffer"
+  require "something"
 }
 
 unittest test {
-  \ UtilTesting.checkSuccessBetween(ErrorOr:error("failure"),0,2)
+  \ ErrorOr:value(CharBuffer.new(0)) `Matches:with` CheckErrorOr:error(CheckValue:equals("something"))
 }
diff --git a/lib/util/test/time.0rt b/lib/util/test/time.0rt
--- a/lib/util/test/time.0rt
+++ b/lib/util/test/time.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -38,7 +38,7 @@
 
 
 testcase "sleepSeconds() does not interfere with test timeout" {
-  crash
+  failure
   require "signal 14"
   timeout 1
 }
@@ -49,7 +49,7 @@
 
 
 testcase "sleepSecondsPrecise() does not interfere with test timeout" {
-  crash
+  failure
   require "signal 14"
   timeout 1
 }
@@ -60,7 +60,7 @@
 
 
 testcase "monoSeconds() diff is somewhat accurate" {
-  success
+  success TestChecker
   timeout 2
 }
 
@@ -68,12 +68,12 @@
   Float start <- Realtime.monoSeconds()
   \ Realtime.sleepSeconds(0.5)
   Float stop <- Realtime.monoSeconds()
-  \ Testing.checkBetween(stop-start,0.5,0.6)
+  \ (stop-start) `Matches:with` CheckValue:betweenEquals(0.5, 0.6)
 }
 
 unittest testPrecise {
   Float start <- Realtime.monoSeconds()
   \ Realtime.sleepSecondsPrecise(0.5)
   Float stop <- Realtime.monoSeconds()
-  \ Testing.checkBetween(stop-start,0.5,0.51)
+  \ (stop-start) `Matches:with` CheckValue:betweenEquals(0.5, 0.51)
 }
diff --git a/lib/util/testing.0rp b/lib/util/testing.0rp
--- a/lib/util/testing.0rp
+++ b/lib/util/testing.0rp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2023 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.
@@ -16,35 +16,8 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-$TestsOnly$
-
-// General lib/util helpers for use in unit tests.
-concrete UtilTesting {
-  // Check the value for an ErrorOr error. Crashes if it is not an error.
-  //
-  // Args:
-  // - ErrorOr<Formatted>: Actual value.
-  @type checkError (ErrorOr<Formatted>) -> ()
-
-  // Check the values for equality. Crashes if they are not equal.
-  //
-  // Args:
-  // - ErrorOr<#x>: Actual value.
-  // - #x: Expected value.
-  @type checkSuccess<#x>
-    #x requires Formatted
-    #x defines Equals<#x>
-  (ErrorOr<#x>,#x) -> ()
-
-  // Check that the value is within a range. Crashes if it is not in the range.
-  //
-  // Args:
-  // - ErrorOr<#x>: Actual value.
-  // - #x: Lower bound.
-  // - #x: Upper bound.
-  @type checkSuccessBetween<#x>
-    #x requires Formatted
-    #x defines Equals<#x>
-    #x defines LessThan<#x>
-  (ErrorOr<#x>,#x,#x) -> ()
+// Checks the contents of an ErrorOr.
+concrete CheckErrorOr<#x> {
+  @category value<#x> (ValueMatcher<#x>) -> (ValueMatcher<ErrorOr<#x>>)
+  @category error (ValueMatcher<String>) -> (ValueMatcher<ErrorOr<any>>)
 }
diff --git a/src/Cli/Compiler.hs b/src/Cli/Compiler.hs
--- a/src/Cli/Compiler.hs
+++ b/src/Cli/Compiler.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2020-2022 Kevin P. Barry
+Copyright 2020-2023 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.
@@ -49,7 +49,7 @@
 import Module.ProcessMetadata
 import Parser.SourceFile
 import Parser.TextParser (SourceContext)
-import Types.Builtin
+import Types.Builtin (requiredStaticTypes)
 import Types.DefinedCategory
 import Types.TypeCategory
 import Types.TypeInstance
@@ -288,7 +288,7 @@
           let lf' = lf ++ getLinkFlagsForDeps deps2
           let os     = getObjectFilesForDeps deps2
           let ofr = getObjectFileResolver os
-          let objects = ofr ns2 req
+          let objects = ofr ns2 (req `Set.union` requiredStaticTypes)
           return $ CompileToBinary mainAbs objects [] f0 paths2 lf'
         getCommand LinkDynamic mainAbs f0 deps2 paths2 = do
           let objects = getLibrariesForDeps deps2
@@ -316,16 +316,16 @@
 createModuleTemplates resolver p d ds cm deps1 deps2 = do
   time <- errorFromIO getCurrentTime
   (ps,xs,_) <- findSourceFiles p (d:ds)
-  (LanguageModule _ _ _ cs0 ps0 ts0 cs1 ps1 ts1 _ _) <-
+  (LanguageModule _ _ _ cs0 ps0 tc0 tp0 cs1 ps1 tc1 tp1 _ _ cm0) <-
     fmap (createLanguageModule [] Map.empty . fst) $ loadModuleGlobals resolver p (PublicNamespace,PrivateNamespace) ps Nothing deps1 deps2
   xs' <- zipWithContents resolver p xs
   ds2 <- mapCompilerM parseInternalSource xs'
   let ds3 = concat $ map (\(_,_,d2) -> d2) ds2
-  tm <- foldM includeNewTypes defaultCategories [cs0,cs1,ps0,ps1,ts0,ts1]
-  let cs = filter isValueConcrete $ cs1++ps1++ts1
+  tm <- foldM includeNewTypes cm0 [cs0,cs1,ps0,ps1,tc0,tp0,tc1,tp1]
+  let cs = filter isValueConcrete $ cs1++ps1++tc1++tp1
   let ca = Set.fromList $ map getCategoryName $ filter isValueConcrete cs
   let ca' = foldr Set.delete ca $ map dcName ds3
-  let testingCats = Set.fromList $ map getCategoryName ts1
+  let testingCats = Set.fromList $ (map getCategoryName tc1) ++ (map getCategoryName tp1)
   ts <- fmap concat $ mapCompilerM (\n -> generate (n `Set.member` testingCats) tm n) $ Set.toList ca'
   mapCompilerM_ (writeTemplate time) ts where
     generate testing tm n = do
@@ -376,18 +376,22 @@
   [WithVisibility (AnyCategory c)] -> LanguageModule c
 createLanguageModule ss em cs = lm where
   lm = LanguageModule {
-      lmPublicNamespaces  = Set.fromList $ map wvData $ apply ns [with    FromDependency,without ModuleOnly],
-      lmPrivateNamespaces = Set.fromList $ map wvData $ apply ns [with    FromDependency,with    ModuleOnly],
-      lmLocalNamespaces   = Set.fromList $ map wvData $ apply ns [without FromDependency],
-      lmPublicDeps        = map wvData $ apply cs [with    FromDependency,without ModuleOnly,without TestsOnly],
-      lmPrivateDeps       = map wvData $ apply cs [with    FromDependency,with    ModuleOnly,without TestsOnly],
-      lmTestingDeps       = map wvData $ apply cs [with    FromDependency,with TestsOnly],
-      lmPublicLocal       = map wvData $ apply cs [without FromDependency,without ModuleOnly,without TestsOnly],
-      lmPrivateLocal      = map wvData $ apply cs [without FromDependency,with    ModuleOnly,without TestsOnly],
-      lmTestingLocal      = map wvData $ apply cs [without FromDependency,with TestsOnly],
+      lmPublicNamespaces    = Set.fromList $ map wvData $ apply ns [with    FromDependency,without ModuleOnly],
+      lmPrivateNamespaces   = Set.fromList $ map wvData $ apply ns [with    FromDependency,with    ModuleOnly],
+      lmLocalNamespaces     = Set.fromList $ map wvData $ apply ns [without FromDependency],
+      lmPublicDeps          = map wvData $ apply cs [with    FromDependency,without ModuleOnly,without TestsOnly],
+      lmPrivateDeps         = map wvData $ apply cs [with    FromDependency,with    ModuleOnly,without TestsOnly],
+      lmPublicTestingDeps   = map wvData $ apply cs [with    FromDependency,without ModuleOnly,with TestsOnly],
+      lmPrivateTestingDeps  = map wvData $ apply cs [with    FromDependency,with    ModuleOnly,with TestsOnly],
+      lmPublicLocal         = map wvData $ apply cs [without FromDependency,without ModuleOnly,without TestsOnly],
+      lmPrivateLocal        = map wvData $ apply cs [without FromDependency,with    ModuleOnly,without TestsOnly],
+      lmPublicTestingLocal  = map wvData $ apply cs [without FromDependency,without ModuleOnly,with TestsOnly],
+      lmPrivateTestingLocal = map wvData $ apply cs [without FromDependency,with    ModuleOnly,with TestsOnly],
       lmStreamlined = ss,
-      lmExprMap  = em
+      lmExprMap  = em,
+      lmEmptyCategories = CategoryMap km Map.empty
     }
+  km = Map.fromList $ map (\(WithVisibility _ t) -> (getCategoryName t,getCategoryContext t)) cs
   ns = map (mapCodeVisibility getCategoryNamespace) cs
   with    v = hasCodeVisibility v
   without v = not . hasCodeVisibility v
diff --git a/src/Cli/ParseCompileOptions.hs b/src/Cli/ParseCompileOptions.hs
--- a/src/Cli/ParseCompileOptions.hs
+++ b/src/Cli/ParseCompileOptions.hs
@@ -25,6 +25,7 @@
 import Control.Monad (when)
 import Lens.Micro
 import Text.Regex.TDFA
+import qualified Data.Set as Set
 
 import Base.CompilerError
 import Cli.CompileOptions
@@ -85,9 +86,9 @@
     "",
     "Options:",
     "  -f: Force an operation that zeolite would otherwise reject.",
-    "  -j [# parallel]: Parallel execution of compilation subprocesses.",
     "  -i [module]: A single source module to include as a public dependency.",
     "  -I [module]: A single source module to include as a private dependency.",
+    "  -j [# parallel]: Parallel execution of compilation subprocesses.",
     "  -o [binary]: The name of the binary file to create with -m.",
     "  -p [path]: Set a path prefix for finding modules or files.",
     "  --log-traces [filename]: Log call traces to a file when running tests.",
@@ -124,8 +125,19 @@
 defaultMainFunc :: String
 defaultMainFunc = "run"
 
+optionsWithArgs :: Set.Set Char
+optionsWithArgs = Set.fromList "iIjmop"
+
+splitOptionClusters :: [String] -> [String]
+splitOptionClusters (o@('-':c:rest):os)
+  | c == '-' || null rest          = o : splitOptionClusters os
+  | c `Set.member` optionsWithArgs = ('-':c:[]) : rest : splitOptionClusters os
+  | otherwise                      = ('-':c:[]) : splitOptionClusters (('-':rest):os)
+splitOptionClusters (o:os) = o : splitOptionClusters os
+splitOptionClusters [] = []
+
 parseCompileOptions :: CollectErrorsM m => [String] -> m CompileOptions
-parseCompileOptions = parseAll emptyCompileOptions . zip ([1..] :: [Int]) where
+parseCompileOptions = parseAll emptyCompileOptions . zip ([1..] :: [Int]) . splitOptionClusters where
   parseAll co [] = return co
   parseAll co os = do
     (os',co') <- parseSingle co os
diff --git a/src/Cli/RunCompiler.hs b/src/Cli/RunCompiler.hs
--- a/src/Cli/RunCompiler.hs
+++ b/src/Cli/RunCompiler.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2020-2021 Kevin P. Barry
+Copyright 2020-2021,2023 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.
@@ -203,9 +203,9 @@
     sequence_ $ intercalate [string_ ","] $ map ((:[]) . parseColTitle) tracesLogHeader
     some (char '\n' <|> char '\r') >> return ()
   parseSingle = do
-    ms <- parseDec
+    ms <- fmap snd parseDec
     string_ ","
-    pid <- parseDec
+    pid <- fmap snd parseDec
     string_ ","
     func <- quotedString
     string_ ","
diff --git a/src/Cli/TestRunner.hs b/src/Cli/TestRunner.hs
--- a/src/Cli/TestRunner.hs
+++ b/src/Cli/TestRunner.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2020-2021 Kevin P. Barry
+Copyright 2020-2021,2023 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.
@@ -108,7 +108,7 @@
              Just  t -> compilerWarningM $ "Explicit timeouts are ignored in " ++ ex2 ++ " tests: " ++ show t
 
     run (ExpectCompilerError _ rs es) _ _ cs ds ts = do
-      result <- toTrackedErrors $ compileAll [] cs ds ts
+      result <- toTrackedErrors $ compileAll Nothing [] cs ds ts
       if not $ isCompilerError result
          then compilerErrorM "Expected compilation failure"
          else fmap (:[]) $ return $ do
@@ -117,22 +117,22 @@
            checkContent rs es (lines warnings ++ lines errors) [] []
 
     run (ExpectCompiles _ rs es) _ _ cs ds ts = do
-      result <- toTrackedErrors $ compileAll [] cs ds ts
+      result <- toTrackedErrors $ compileAll Nothing [] cs ds ts
       if isCompilerError result
          then fromTrackedErrors result >> return []
          else fmap (:[]) $ return $ do
            let warnings = show $ getCompilerWarnings result
            checkContent rs es (lines warnings) [] []
 
-    run (ExpectRuntimeError _ rs es) args timeout cs ds ts = do
-      when (length ts /= 1) $ compilerErrorM "Exactly one unittest is required when crash is expected"
+    run (ExpectRuntimeError _ t rs es) args timeout cs ds ts = do
+      when (length ts /= 1) $ compilerErrorM "Exactly one unittest is required when failure is expected"
       uniqueTestNames ts
-      execute False rs es args timeout cs ds ts
+      execute False t rs es args timeout cs ds ts
 
-    run (ExpectRuntimeSuccess _ rs es) args timeout cs ds ts = do
+    run (ExpectRuntimeSuccess _ t rs es) args timeout cs ds ts = do
       when (null ts) $ compilerErrorM "At least one unittest is required when success is expected"
       uniqueTestNames ts
-      execute True rs es args timeout cs ds ts
+      execute True t rs es args timeout cs ds ts
 
     checkContent rs es comp err out = do
       let cr = checkRequired rs comp err out
@@ -157,8 +157,8 @@
     testClash (n,ts) = "unittest " ++ show n ++ " is defined multiple times" !!>
       (mapCompilerM_ (compilerErrorM . ("Defined at " ++) . formatFullContext) $ sort $ map tpContext ts)
 
-    execute s2 rs es args timeout cs ds ts = do
-      result <- toTrackedErrors $ compileAll args cs ds ts
+    execute s2 t rs es args timeout cs ds ts = do
+      result <- toTrackedErrors $ compileAll t args cs ds ts
       if isCompilerError result
          then return [result >> return ()]
          else do
@@ -185,10 +185,10 @@
             (hPutStrLn stderr $ "--- unittest " ++ show f2 ++ " passed ---")
             (hPutStrLn stderr $ "--- unittest " ++ show f2 ++ " failed ---")
 
-    compileAll args cs ds ts = do
+    compileAll t args cs ds ts = do
       let ns1 = StaticNamespace $ privateNamespace s
       let cs' = map (setCategoryNamespace ns1) cs
-      fromTrackedErrors $ compileTestsModule cm ns1 args cs' ds ts
+      fromTrackedErrors $ compileTestsModule cm ns1 args t cs' ds ts
 
     checkRequired rs comp err out = mapCompilerM_ (checkSubsetForRegex True  comp err out) rs
     checkExcluded es comp err out = mapCompilerM_ (checkSubsetForRegex False comp err out) es
diff --git a/src/Compilation/CompilerState.hs b/src/Compilation/CompilerState.hs
--- a/src/Compilation/CompilerState.hs
+++ b/src/Compilation/CompilerState.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -48,6 +48,7 @@
   csCheckVariableInit,
   csClearOutput,
   csCurrentScope,
+  csDelegateArgs,
   csExprLookup,
   csGetCategoryFunction,
   csGetCleanup,
@@ -55,6 +56,7 @@
   csGetNoTrace,
   csGetOutput,
   csGetParamScope,
+  csGetTestsOnly,
   csGetTypeFunction,
   csGetVariable,
   csInheritDeferred,
@@ -101,6 +103,7 @@
 
 import Base.CompilerError
 import Base.GeneralType
+import Base.Positional
 import Types.DefinedCategory
 import Types.Procedure
 import Types.TypeCategory
@@ -150,9 +153,11 @@
   ccReleaseExprMacro :: a -> [c] -> MacroName -> m a
   ccSetNoTrace :: a -> Bool -> m a
   ccGetNoTrace :: a -> m Bool
+  ccGetTestsOnly :: a -> m Bool
   ccAddTrace :: a -> String -> m a
   ccGetTraces :: a -> m [String]
   ccCanForward :: a -> [ParamName] -> [VariableName] -> m Bool
+  ccDelegateArgs :: a -> m (Positional (Maybe (CallArgLabel c), VariableName))
 
 data MemberValue c =
   MemberValue {
@@ -359,11 +364,17 @@
 csGetNoTrace :: CompilerContext c m s a => CompilerState a m Bool
 csGetNoTrace = fmap ccGetNoTrace get >>= lift
 
+csGetTestsOnly :: CompilerContext c m s a => CompilerState a m Bool
+csGetTestsOnly = fmap ccGetTestsOnly get >>= lift
+
 csAddTrace :: CompilerContext c m s a => String -> CompilerState a m ()
 csAddTrace t = fmap (\x -> ccAddTrace x t) get >>= lift >>= put
 
 csCanForward :: CompilerContext c m s a => [ParamName] -> [VariableName] -> CompilerState a m Bool
 csCanForward ps as = fmap (\x -> ccCanForward x ps as) get >>= lift
+
+csDelegateArgs :: CompilerContext c m s a => CompilerState a m (Positional (Maybe (CallArgLabel c), VariableName))
+csDelegateArgs = fmap ccDelegateArgs get >>= lift
 
 data CompiledData s =
   CompiledData {
diff --git a/src/Compilation/ProcedureContext.hs b/src/Compilation/ProcedureContext.hs
--- a/src/Compilation/ProcedureContext.hs
+++ b/src/Compilation/ProcedureContext.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -89,8 +89,9 @@
     _pcExprMap :: ExprMap c,
     _pcReservedMacros :: [(MacroName,[c])],
     _pcNoTrace :: Bool,
+    _pcTestsOnly :: Bool,
     _pcTraces :: [String],
-    _pcParentCall :: Maybe (Positional ParamName,Positional VariableName)
+    _pcParentCall :: Maybe (Positional ParamName,Positional (Maybe (CallArgLabel c), InputValue c))
   }
 
 $(makeLenses ''ProcedureContext)
@@ -146,7 +147,17 @@
                  case t of
                       Just t0 -> replaceSelfInstance self t0
                       Nothing -> return self
-    getFunction t' t' where
+    f <- getFunction t' t'
+    when (ctx ^. pcType /= sfType f && ctx ^. pcType /= CategoryNone) $
+      "In call to " ++ show (sfName f) ++ formatFullContextBrace c ??> checkVisibility (ctx ^. pcScope) (sfVisibility f) f
+    return f where
+      checkVisibility _ FunctionVisibilityDefault _ = return ()
+      checkVisibility CategoryScope v _ = "Function restricted to @type and @value contexts" ??> compilerErrorM (show v)
+      checkVisibility _ _ f = do
+        r <- ccResolver ctx
+        allFilters <- ccAllFilters ctx
+        self <- fmap (singleType . JustTypeInstance) $ ccSelfType ctx
+        checkFunctionCallVisibility r allFilters f self
       multipleMatchError t2 fs = do
         "Multiple matches for function " ++ show n ++ " called on " ++ show t2 ++ formatFullContextBrace c !!>
           mapErrorsM (map show fs)
@@ -409,19 +420,28 @@
   ccReleaseExprMacro ctx _ n = return $ ctx & pcReservedMacros %~ (filter ((/= n) . fst))
   ccSetNoTrace ctx t = return $ ctx & pcNoTrace .~ t
   ccGetNoTrace = return . (^. pcNoTrace)
+  ccGetTestsOnly = return . (^. pcTestsOnly)
   ccAddTrace ctx "" = return ctx
   ccAddTrace ctx t = return $ ctx & pcTraces <>~ [t]
   ccGetTraces = return . (^. pcTraces)
   ccCanForward ctx ps as = handle (ctx ^. pcParentCall) where
+    nameOrError (InputValue _ n) = return n
+    nameOrError _                = emptyErrorM
     handle Nothing = return False
     handle (Just (ps0,as0)) = collectFirstM [checkMatch ps0 as0,return False]
     checkMatch ps0 as0 = do
+      as0' <- fmap Positional $ mapCompilerM (nameOrError . snd) $ pValues as0
       processPairs_ equalOrError ps0 (Positional ps)
-      processPairs_ equalOrError as0 (Positional as)
+      processPairs_ equalOrError as0' (Positional as)
       return True
     equalOrError x y
       | x == y    = return ()
       | otherwise = emptyErrorM
+  ccDelegateArgs ctx = handle (ctx ^. pcParentCall) where
+    nameOrError (l,InputValue _ n) = return (l,n)
+    nameOrError (_,DiscardInput c) = compilerErrorM $ "Delegation is not allowed with ignored args" ++ formatFullContextBrace c
+    handle Nothing = compilerErrorM "Delegation is only allowed within function calls"
+    handle (Just (_,as0)) = fmap Positional $ mapCompilerM nameOrError $ pValues as0
 
 updateReturnVariables :: (Show c, CollectErrorsM m) =>
   (Map.Map VariableName (VariableValue c)) ->
@@ -444,17 +464,25 @@
 
 updateArgVariables :: (Show c, CollectErrorsM m) =>
   (Map.Map VariableName (VariableValue c)) ->
-  Positional (PassedValue c) -> ArgValues c ->
+  Positional (PassedValue c, Maybe (CallArgLabel c)) -> ArgValues c ->
   m (Map.Map VariableName (VariableValue c))
 updateArgVariables ma as1 as2 = do
   as <- processPairs alwaysPair as1 (avNames as2)
   let as' = filter (not . isDiscardedInput . snd) as
   foldM update ma as' where
-    update va (PassedValue _ t,a) = do
+    checkName (Just l) (InputValue c (VariableName n))
+      | l `matchesCallArgLabel` n = return ()
+      | otherwise = compilerWarningM $ "Variable " ++ n ++
+                                         formatFullContextBrace c ++
+                                         " has a different name than arg label " ++ show l
+    checkName _ _ = return ()
+    update va ((PassedValue _ t,n),a) = do
       let c = ivContext a
       case ivName a `Map.lookup` va of
-            Nothing -> return $ Map.insert (ivName a) (VariableValue c LocalScope t (VariableReadOnly c)) va
+            Nothing -> return ()
             (Just v) -> compilerErrorM $ "Variable " ++ show (ivName a) ++
                                          formatFullContextBrace c ++
                                          " is already defined" ++
                                          formatFullContextBrace (vvContext v)
+      checkName n a
+      return $ Map.insert (ivName a) (VariableValue c LocalScope t (VariableReadOnly c)) va
diff --git a/src/CompilerCxx/CategoryContext.hs b/src/CompilerCxx/CategoryContext.hs
--- a/src/CompilerCxx/CategoryContext.hs
+++ b/src/CompilerCxx/CategoryContext.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -42,9 +42,9 @@
 
 
 getContextForInit :: (Show c, CollectErrorsM m) =>
-  CategoryMap c -> ExprMap c -> AnyCategory c -> DefinedCategory c ->
+  Bool -> CategoryMap c -> ExprMap c -> AnyCategory c -> DefinedCategory c ->
   SymbolScope -> m (ProcedureContext c)
-getContextForInit tm em t d s = do
+getContextForInit to tm em t d s = do
   let ps = Positional $ getCategoryParams t
   -- NOTE: This is always ValueScope for initializer checks.
   let ms = filter ((== ValueScope) . dmScope) $ dcMembers d
@@ -85,14 +85,15 @@
       _pcExprMap = em,
       _pcReservedMacros = [],
       _pcNoTrace = False,
+      _pcTestsOnly = to,
       _pcTraces = [],
       _pcParentCall = Nothing
     }
 
 getProcedureContext :: (Show c, CollectErrorsM m) =>
-  ScopeContext c -> ScopedFunction c -> ExecutableProcedure c -> m (ProcedureContext c)
-getProcedureContext (ScopeContext tm t ps ms pa fa va em)
-                    ff@(ScopedFunction _ _ _ s as1 rs1 ps1 fs _)
+  Bool -> ScopeContext c -> ScopedFunction c -> ExecutableProcedure c -> m (ProcedureContext c)
+getProcedureContext to (ScopeContext tm t ps ms pa fa va em)
+                    ff@(ScopedFunction _ _ _ s _ as1 rs1 ps1 fs _)
                     (ExecutableProcedure _ _ _ _ as2 rs2 _) = do
   rs' <- if isUnnamedReturns rs2
             then return $ ValidatePositions rs1
@@ -146,21 +147,17 @@
       _pcExprMap = em,
       _pcReservedMacros = [],
       _pcNoTrace = False,
+      _pcTestsOnly = to,
       _pcTraces = [],
       _pcParentCall = parentCall
     }
   where
     pairOutput (PassedValue c1 t2) (OutputValue c2 n2) = return $ (n2,PassedValue (c2++c1) t2)
-    psList = map vpParam $ pValues ps1
-    asList = sequence $ map maybeArgName $ pValues $ avNames as2
-    maybeArgName (InputValue _ n) = Just n
-    maybeArgName _                = Nothing
-    parentCall = case asList of
-                      Just as3 -> Just (Positional psList,Positional as3)
-                      Nothing  -> Nothing
+    args = Positional $ zip (map snd $ pValues as1) (pValues $ avNames as2)
+    parentCall = Just (fmap vpParam ps1,args)
 
-getMainContext :: CollectErrorsM m => CategoryMap c -> ExprMap c -> m (ProcedureContext c)
-getMainContext tm em = return $ ProcedureContext {
+getMainContext :: CollectErrorsM m => Bool -> CategoryMap c -> ExprMap c -> m (ProcedureContext c)
+getMainContext to tm em = return $ ProcedureContext {
     _pcScope = LocalScope,
     _pcType = CategoryNone,
     _pcExtParams = Positional [],
@@ -186,6 +183,7 @@
     _pcExprMap = em,
     _pcReservedMacros = [],
     _pcNoTrace = False,
+    _pcTestsOnly = to,
     _pcTraces = [],
     _pcParentCall = Nothing
   }
diff --git a/src/CompilerCxx/Code.hs b/src/CompilerCxx/Code.hs
--- a/src/CompilerCxx/Code.hs
+++ b/src/CompilerCxx/Code.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -125,21 +125,23 @@
 showCreationTrace = "TRACE_CREATION"
 
 hasPrimitiveValue :: CategoryName -> Bool
-hasPrimitiveValue BuiltinBool    = True
-hasPrimitiveValue BuiltinInt     = True
-hasPrimitiveValue BuiltinFloat   = True
-hasPrimitiveValue BuiltinChar    = True
-hasPrimitiveValue BuiltinPointer = True
-hasPrimitiveValue _              = False
+hasPrimitiveValue BuiltinBool       = True
+hasPrimitiveValue BuiltinInt        = True
+hasPrimitiveValue BuiltinFloat      = True
+hasPrimitiveValue BuiltinChar       = True
+hasPrimitiveValue BuiltinPointer    = True
+hasPrimitiveValue BuiltinIdentifier = True
+hasPrimitiveValue _                 = False
 
 isStoredUnboxed :: ValueType -> Bool
 isStoredUnboxed t
-  | t == boolRequiredValue    = True
-  | t == intRequiredValue     = True
-  | t == floatRequiredValue   = True
-  | t == charRequiredValue    = True
-  | isPointerRequiredValue t = True
-  | otherwise                 = False
+  | t == boolRequiredValue      = True
+  | t == intRequiredValue       = True
+  | t == floatRequiredValue     = True
+  | t == charRequiredValue      = True
+  | isPointerRequiredValue t    = True
+  | isIdentifierRequiredValue t = True
+  | otherwise                   = False
 
 expressionFromLiteral :: PrimitiveType -> String -> (ExpressionType,ExpressionValue)
 expressionFromLiteral PrimString e =
@@ -153,13 +155,14 @@
 expressionFromLiteral PrimBool e =
   (Positional [boolRequiredValue],UnboxedPrimitive PrimBool e)
 expressionFromLiteral PrimPointer _ = undefined
+expressionFromLiteral PrimIdentifier _ = undefined
 
 getFromLazy :: ExpressionValue -> ExpressionValue
-getFromLazy (OpaqueMulti e)        = OpaqueMulti $ e ++ ".Get()"
-getFromLazy (WrappedSingle e)      = WrappedSingle $ e ++ ".Get()"
-getFromLazy (UnwrappedSingle e)    = UnwrappedSingle $ e ++ ".Get()"
-getFromLazy (BoxedPrimitive t e)   = BoxedPrimitive t $ e ++ ".Get()"
-getFromLazy (UnboxedPrimitive t e) = UnboxedPrimitive t  $ e ++ ".Get()"
+getFromLazy (OpaqueMulti e)        = OpaqueMulti $ "(" ++ e ++ ").Get()"
+getFromLazy (WrappedSingle e)      = WrappedSingle $ "(" ++ e ++ ").Get()"
+getFromLazy (UnwrappedSingle e)    = UnwrappedSingle $ "(" ++ e ++ ").Get()"
+getFromLazy (BoxedPrimitive t e)   = BoxedPrimitive t $ "(" ++ e ++ ").Get()"
+getFromLazy (UnboxedPrimitive t e) = UnboxedPrimitive t  $ "(" ++ e ++ ").Get()"
 getFromLazy (LazySingle e)         = LazySingle $ getFromLazy e
 
 useAsWhatever :: ExpressionValue -> String
@@ -171,147 +174,162 @@
 useAsWhatever (LazySingle e)         = useAsWhatever $ getFromLazy e
 
 useAsReturns :: ExpressionValue -> String
-useAsReturns (OpaqueMulti e)                  = e
-useAsReturns (WrappedSingle e)                = "ReturnTuple(" ++ e ++ ")"
-useAsReturns (UnwrappedSingle e)              = "ReturnTuple(" ++ e ++ ")"
-useAsReturns (BoxedPrimitive PrimBool e)      = "ReturnTuple(Box_Bool(" ++ e ++ "))"
-useAsReturns (BoxedPrimitive PrimString e)    = "ReturnTuple(Box_String(" ++ e ++ "))"
-useAsReturns (BoxedPrimitive PrimChar e)      = "ReturnTuple(Box_Char(" ++ e ++ "))"
-useAsReturns (BoxedPrimitive PrimInt e)       = "ReturnTuple(Box_Int(" ++ e ++ "))"
-useAsReturns (BoxedPrimitive PrimFloat e)     = "ReturnTuple(Box_Float(" ++ e ++ "))"
-useAsReturns (BoxedPrimitive PrimPointer e)   = "ReturnTuple(Box_Pointer<void>(" ++ e ++ "))"
-useAsReturns (UnboxedPrimitive PrimBool e)    = "ReturnTuple(Box_Bool(" ++ e ++ "))"
-useAsReturns (UnboxedPrimitive PrimString e)  = "ReturnTuple(Box_String(" ++ e ++ "))"
-useAsReturns (UnboxedPrimitive PrimChar e)    = "ReturnTuple(Box_Char(" ++ e ++ "))"
-useAsReturns (UnboxedPrimitive PrimInt e)     = "ReturnTuple(Box_Int(" ++ e ++ "))"
-useAsReturns (UnboxedPrimitive PrimFloat e)   = "ReturnTuple(Box_Float(" ++ e ++ "))"
-useAsReturns (UnboxedPrimitive PrimPointer e) = "ReturnTuple(Box_Pointer<void>(" ++ e ++ "))"
-useAsReturns (LazySingle e)                   = useAsReturns $ getFromLazy e
+useAsReturns (OpaqueMulti e)                     = e
+useAsReturns (WrappedSingle e)                   = "ReturnTuple(" ++ e ++ ")"
+useAsReturns (UnwrappedSingle e)                 = "ReturnTuple(" ++ e ++ ")"
+useAsReturns (BoxedPrimitive PrimBool e)         = "ReturnTuple(Box_Bool(" ++ e ++ "))"
+useAsReturns (BoxedPrimitive PrimString e)       = "ReturnTuple(Box_String(" ++ e ++ "))"
+useAsReturns (BoxedPrimitive PrimChar e)         = "ReturnTuple(Box_Char(" ++ e ++ "))"
+useAsReturns (BoxedPrimitive PrimInt e)          = "ReturnTuple(Box_Int(" ++ e ++ "))"
+useAsReturns (BoxedPrimitive PrimFloat e)        = "ReturnTuple(Box_Float(" ++ e ++ "))"
+useAsReturns (BoxedPrimitive PrimPointer e)      = "ReturnTuple(Box_Pointer<void>(" ++ e ++ "))"
+useAsReturns (BoxedPrimitive PrimIdentifier e)   = "ReturnTuple(Box_Identifier(" ++ e ++ "))"
+useAsReturns (UnboxedPrimitive PrimBool e)       = "ReturnTuple(Box_Bool(" ++ e ++ "))"
+useAsReturns (UnboxedPrimitive PrimString e)     = "ReturnTuple(Box_String(" ++ e ++ "))"
+useAsReturns (UnboxedPrimitive PrimChar e)       = "ReturnTuple(Box_Char(" ++ e ++ "))"
+useAsReturns (UnboxedPrimitive PrimInt e)        = "ReturnTuple(Box_Int(" ++ e ++ "))"
+useAsReturns (UnboxedPrimitive PrimFloat e)      = "ReturnTuple(Box_Float(" ++ e ++ "))"
+useAsReturns (UnboxedPrimitive PrimPointer e)    = "ReturnTuple(Box_Pointer<void>(" ++ e ++ "))"
+useAsReturns (UnboxedPrimitive PrimIdentifier e) = "ReturnTuple(Box_Identifier(" ++ e ++ "))"
+useAsReturns (LazySingle e)                      = useAsReturns $ getFromLazy e
 
 useAsArgs :: ExpressionValue -> String
-useAsArgs (OpaqueMulti e)                  = e
-useAsArgs (WrappedSingle e)                = e
-useAsArgs (UnwrappedSingle e)              = e
-useAsArgs (BoxedPrimitive PrimBool e)      = "Box_Bool(" ++ e ++ ")"
-useAsArgs (BoxedPrimitive PrimString e)    = "Box_String(" ++ e ++ ")"
-useAsArgs (BoxedPrimitive PrimChar e)      = "Box_Char(" ++ e ++ ")"
-useAsArgs (BoxedPrimitive PrimInt e)       = "Box_Int(" ++ e ++ ")"
-useAsArgs (BoxedPrimitive PrimFloat e)     = "Box_Float(" ++ e ++ ")"
-useAsArgs (BoxedPrimitive PrimPointer e)   = "Box_Pointer<void>(" ++ e ++ ")"
-useAsArgs (UnboxedPrimitive PrimBool e)    = "Box_Bool(" ++ e ++ ")"
-useAsArgs (UnboxedPrimitive PrimString e)  = "Box_String(" ++ e ++ ")"
-useAsArgs (UnboxedPrimitive PrimChar e)    = "Box_Char(" ++ e ++ ")"
-useAsArgs (UnboxedPrimitive PrimInt e)     = "Box_Int(" ++ e ++ ")"
-useAsArgs (UnboxedPrimitive PrimFloat e)   = "Box_Float(" ++ e ++ ")"
-useAsArgs (UnboxedPrimitive PrimPointer e) = "Box_Pointer<void>(" ++ e ++ ")"
-useAsArgs (LazySingle e)                   = useAsArgs $ getFromLazy e
+useAsArgs (OpaqueMulti e)                     = e
+useAsArgs (WrappedSingle e)                   = e
+useAsArgs (UnwrappedSingle e)                 = e
+useAsArgs (BoxedPrimitive PrimBool e)         = "Box_Bool(" ++ e ++ ")"
+useAsArgs (BoxedPrimitive PrimString e)       = "Box_String(" ++ e ++ ")"
+useAsArgs (BoxedPrimitive PrimChar e)         = "Box_Char(" ++ e ++ ")"
+useAsArgs (BoxedPrimitive PrimInt e)          = "Box_Int(" ++ e ++ ")"
+useAsArgs (BoxedPrimitive PrimFloat e)        = "Box_Float(" ++ e ++ ")"
+useAsArgs (BoxedPrimitive PrimPointer e)      = "Box_Pointer<void>(" ++ e ++ ")"
+useAsArgs (BoxedPrimitive PrimIdentifier e)   = "Box_Identifier(" ++ e ++ ")"
+useAsArgs (UnboxedPrimitive PrimBool e)       = "Box_Bool(" ++ e ++ ")"
+useAsArgs (UnboxedPrimitive PrimString e)     = "Box_String(" ++ e ++ ")"
+useAsArgs (UnboxedPrimitive PrimChar e)       = "Box_Char(" ++ e ++ ")"
+useAsArgs (UnboxedPrimitive PrimInt e)        = "Box_Int(" ++ e ++ ")"
+useAsArgs (UnboxedPrimitive PrimFloat e)      = "Box_Float(" ++ e ++ ")"
+useAsArgs (UnboxedPrimitive PrimPointer e)    = "Box_Pointer<void>(" ++ e ++ ")"
+useAsArgs (UnboxedPrimitive PrimIdentifier e) = "Box_Identifier(" ++ e ++ ")"
+useAsArgs (LazySingle e)                      = useAsArgs $ getFromLazy e
 
 useAsUnwrapped :: ExpressionValue -> String
-useAsUnwrapped (OpaqueMulti e)                  = "(" ++ e ++ ").At(0)"
-useAsUnwrapped (WrappedSingle e)                = e
-useAsUnwrapped (UnwrappedSingle e)              = e
-useAsUnwrapped (BoxedPrimitive PrimBool e)      = "Box_Bool(" ++ e ++ ")"
-useAsUnwrapped (BoxedPrimitive PrimString e)    = "Box_String(" ++ e ++ ")"
-useAsUnwrapped (BoxedPrimitive PrimChar e)      = "Box_Char(" ++ e ++ ")"
-useAsUnwrapped (BoxedPrimitive PrimInt e)       = "Box_Int(" ++ e ++ ")"
-useAsUnwrapped (BoxedPrimitive PrimFloat e)     = "Box_Float(" ++ e ++ ")"
-useAsUnwrapped (BoxedPrimitive PrimPointer e)   = "Box_Pointer<void>(" ++ e ++ ")"
-useAsUnwrapped (UnboxedPrimitive PrimBool e)    = "Box_Bool(" ++ e ++ ")"
-useAsUnwrapped (UnboxedPrimitive PrimString e)  = "Box_String(" ++ e ++ ")"
-useAsUnwrapped (UnboxedPrimitive PrimChar e)    = "Box_Char(" ++ e ++ ")"
-useAsUnwrapped (UnboxedPrimitive PrimInt e)     = "Box_Int(" ++ e ++ ")"
-useAsUnwrapped (UnboxedPrimitive PrimFloat e)   = "Box_Float(" ++ e ++ ")"
-useAsUnwrapped (UnboxedPrimitive PrimPointer e) = "Box_Pointer<void>(" ++ e ++ ")"
-useAsUnwrapped (LazySingle e)                   = useAsUnwrapped $ getFromLazy e
+useAsUnwrapped (OpaqueMulti e)                     = "(" ++ e ++ ").At(0)"
+useAsUnwrapped (WrappedSingle e)                   = e
+useAsUnwrapped (UnwrappedSingle e)                 = e
+useAsUnwrapped (BoxedPrimitive PrimBool e)         = "Box_Bool(" ++ e ++ ")"
+useAsUnwrapped (BoxedPrimitive PrimString e)       = "Box_String(" ++ e ++ ")"
+useAsUnwrapped (BoxedPrimitive PrimChar e)         = "Box_Char(" ++ e ++ ")"
+useAsUnwrapped (BoxedPrimitive PrimInt e)          = "Box_Int(" ++ e ++ ")"
+useAsUnwrapped (BoxedPrimitive PrimFloat e)        = "Box_Float(" ++ e ++ ")"
+useAsUnwrapped (BoxedPrimitive PrimPointer e)      = "Box_Pointer<void>(" ++ e ++ ")"
+useAsUnwrapped (BoxedPrimitive PrimIdentifier e)   = "Box_Identifier(" ++ e ++ ")"
+useAsUnwrapped (UnboxedPrimitive PrimBool e)       = "Box_Bool(" ++ e ++ ")"
+useAsUnwrapped (UnboxedPrimitive PrimString e)     = "Box_String(" ++ e ++ ")"
+useAsUnwrapped (UnboxedPrimitive PrimChar e)       = "Box_Char(" ++ e ++ ")"
+useAsUnwrapped (UnboxedPrimitive PrimInt e)        = "Box_Int(" ++ e ++ ")"
+useAsUnwrapped (UnboxedPrimitive PrimFloat e)      = "Box_Float(" ++ e ++ ")"
+useAsUnwrapped (UnboxedPrimitive PrimPointer e)    = "Box_Pointer<void>(" ++ e ++ ")"
+useAsUnwrapped (UnboxedPrimitive PrimIdentifier e) = "Box_Identifier(" ++ e ++ ")"
+useAsUnwrapped (LazySingle e)                      = useAsUnwrapped $ getFromLazy e
 
 useAsUnboxed :: PrimitiveType -> ExpressionValue -> String
-useAsUnboxed PrimBool    (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsBool()"
-useAsUnboxed PrimString  (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsString()"
-useAsUnboxed PrimChar    (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsChar()"
-useAsUnboxed PrimInt     (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsInt()"
-useAsUnboxed PrimFloat   (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsFloat()"
-useAsUnboxed PrimPointer (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsPointer<OpaqueObject>()"
-useAsUnboxed PrimBool    (WrappedSingle e)   = "(" ++ e ++ ").AsBool()"
-useAsUnboxed PrimString  (WrappedSingle e)   = "(" ++ e ++ ").AsString()"
-useAsUnboxed PrimChar    (WrappedSingle e)   = "(" ++ e ++ ").AsChar()"
-useAsUnboxed PrimInt     (WrappedSingle e)   = "(" ++ e ++ ").AsInt()"
-useAsUnboxed PrimFloat   (WrappedSingle e)   = "(" ++ e ++ ").AsFloat()"
-useAsUnboxed PrimPointer (WrappedSingle e)   = "(" ++ e ++ ").AsPointer<OpaqueObject>()"
-useAsUnboxed PrimBool    (UnwrappedSingle e) = "(" ++ e ++ ").AsBool()"
-useAsUnboxed PrimString  (UnwrappedSingle e) = "(" ++ e ++ ").AsString()"
-useAsUnboxed PrimChar    (UnwrappedSingle e) = "(" ++ e ++ ").AsChar()"
-useAsUnboxed PrimInt     (UnwrappedSingle e) = "(" ++ e ++ ").AsInt()"
-useAsUnboxed PrimFloat   (UnwrappedSingle e) = "(" ++ e ++ ").AsFloat()"
-useAsUnboxed PrimPointer (UnwrappedSingle e) = "(" ++ e ++ ").AsPointer<OpaqueObject>()"
-useAsUnboxed _ (BoxedPrimitive _ e)         = e
-useAsUnboxed _ (UnboxedPrimitive _ e)       = e
-useAsUnboxed t (LazySingle e)               = useAsUnboxed t $ getFromLazy e
+useAsUnboxed PrimBool       (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsBool()"
+useAsUnboxed PrimString     (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsString()"
+useAsUnboxed PrimChar       (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsChar()"
+useAsUnboxed PrimInt        (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsInt()"
+useAsUnboxed PrimFloat      (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsFloat()"
+useAsUnboxed PrimPointer    (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsPointer<OpaqueObject>()"
+useAsUnboxed PrimIdentifier (OpaqueMulti e)     = "(" ++ e ++ ").At(0).AsIdentifier()"
+useAsUnboxed PrimBool       (WrappedSingle e)   = "(" ++ e ++ ").AsBool()"
+useAsUnboxed PrimString     (WrappedSingle e)   = "(" ++ e ++ ").AsString()"
+useAsUnboxed PrimChar       (WrappedSingle e)   = "(" ++ e ++ ").AsChar()"
+useAsUnboxed PrimInt        (WrappedSingle e)   = "(" ++ e ++ ").AsInt()"
+useAsUnboxed PrimFloat      (WrappedSingle e)   = "(" ++ e ++ ").AsFloat()"
+useAsUnboxed PrimPointer    (WrappedSingle e)   = "(" ++ e ++ ").AsPointer<OpaqueObject>()"
+useAsUnboxed PrimIdentifier (WrappedSingle e)   = "(" ++ e ++ ").AsIdentifier()"
+useAsUnboxed PrimBool       (UnwrappedSingle e) = "(" ++ e ++ ").AsBool()"
+useAsUnboxed PrimString     (UnwrappedSingle e) = "(" ++ e ++ ").AsString()"
+useAsUnboxed PrimChar       (UnwrappedSingle e) = "(" ++ e ++ ").AsChar()"
+useAsUnboxed PrimInt        (UnwrappedSingle e) = "(" ++ e ++ ").AsInt()"
+useAsUnboxed PrimFloat      (UnwrappedSingle e) = "(" ++ e ++ ").AsFloat()"
+useAsUnboxed PrimPointer    (UnwrappedSingle e) = "(" ++ e ++ ").AsPointer<OpaqueObject>()"
+useAsUnboxed PrimIdentifier (UnwrappedSingle e) = "(" ++ e ++ ").AsIdentifier()"
+useAsUnboxed _ (BoxedPrimitive _ e)             = e
+useAsUnboxed _ (UnboxedPrimitive _ e)           = e
+useAsUnboxed t (LazySingle e)                   = useAsUnboxed t $ getFromLazy e
 
 valueAsWrapped :: ExpressionValue -> ExpressionValue
-valueAsWrapped (UnwrappedSingle e)              = WrappedSingle e
-valueAsWrapped (BoxedPrimitive _ e)             = WrappedSingle e
-valueAsWrapped (UnboxedPrimitive PrimBool e)    = WrappedSingle $ "Box_Bool(" ++ e ++ ")"
-valueAsWrapped (UnboxedPrimitive PrimString e)  = WrappedSingle $ "Box_String(" ++ e ++ ")"
-valueAsWrapped (UnboxedPrimitive PrimChar e)    = WrappedSingle $ "Box_Char(" ++ e ++ ")"
-valueAsWrapped (UnboxedPrimitive PrimInt e)     = WrappedSingle $ "Box_Int(" ++ e ++ ")"
-valueAsWrapped (UnboxedPrimitive PrimFloat e)   = WrappedSingle $ "Box_Float(" ++ e ++ ")"
-valueAsWrapped (UnboxedPrimitive PrimPointer e) = WrappedSingle $ "Box_Pointer<void>(" ++ e ++ ")"
-valueAsWrapped (LazySingle e)                   = valueAsWrapped $ getFromLazy e
-valueAsWrapped v                                = v
+valueAsWrapped (UnwrappedSingle e)                 = WrappedSingle e
+valueAsWrapped (BoxedPrimitive _ e)                = WrappedSingle e
+valueAsWrapped (UnboxedPrimitive PrimBool e)       = WrappedSingle $ "Box_Bool(" ++ e ++ ")"
+valueAsWrapped (UnboxedPrimitive PrimString e)     = WrappedSingle $ "Box_String(" ++ e ++ ")"
+valueAsWrapped (UnboxedPrimitive PrimChar e)       = WrappedSingle $ "Box_Char(" ++ e ++ ")"
+valueAsWrapped (UnboxedPrimitive PrimInt e)        = WrappedSingle $ "Box_Int(" ++ e ++ ")"
+valueAsWrapped (UnboxedPrimitive PrimFloat e)      = WrappedSingle $ "Box_Float(" ++ e ++ ")"
+valueAsWrapped (UnboxedPrimitive PrimPointer e)    = WrappedSingle $ "Box_Pointer<void>(" ++ e ++ ")"
+valueAsWrapped (UnboxedPrimitive PrimIdentifier e) = WrappedSingle $ "Box_Identifier(" ++ e ++ ")"
+valueAsWrapped (LazySingle e)                      = valueAsWrapped $ getFromLazy e
+valueAsWrapped v                                   = v
 
 valueAsUnwrapped :: ExpressionValue -> ExpressionValue
-valueAsUnwrapped (OpaqueMulti e)                  = UnwrappedSingle $ "(" ++ e ++ ").At(0)"
-valueAsUnwrapped (WrappedSingle e)                = UnwrappedSingle e
-valueAsUnwrapped (UnboxedPrimitive PrimBool e)    = UnwrappedSingle $ "Box_Bool(" ++ e ++ ")"
-valueAsUnwrapped (UnboxedPrimitive PrimString e)  = UnwrappedSingle $ "Box_String(" ++ e ++ ")"
-valueAsUnwrapped (UnboxedPrimitive PrimChar e)    = UnwrappedSingle $ "Box_Char(" ++ e ++ ")"
-valueAsUnwrapped (UnboxedPrimitive PrimInt e)     = UnwrappedSingle $ "Box_Int(" ++ e ++ ")"
-valueAsUnwrapped (UnboxedPrimitive PrimFloat e)   = UnwrappedSingle $ "Box_Float(" ++ e ++ ")"
-valueAsUnwrapped (UnboxedPrimitive PrimPointer e) = UnwrappedSingle $ "Box_Pointer<void>(" ++ e ++ ")"
-valueAsUnwrapped (LazySingle e)                   = valueAsUnwrapped $ getFromLazy e
-valueAsUnwrapped v                                = v
+valueAsUnwrapped (OpaqueMulti e)                     = UnwrappedSingle $ "(" ++ e ++ ").At(0)"
+valueAsUnwrapped (WrappedSingle e)                   = UnwrappedSingle e
+valueAsUnwrapped (UnboxedPrimitive PrimBool e)       = UnwrappedSingle $ "Box_Bool(" ++ e ++ ")"
+valueAsUnwrapped (UnboxedPrimitive PrimString e)     = UnwrappedSingle $ "Box_String(" ++ e ++ ")"
+valueAsUnwrapped (UnboxedPrimitive PrimChar e)       = UnwrappedSingle $ "Box_Char(" ++ e ++ ")"
+valueAsUnwrapped (UnboxedPrimitive PrimInt e)        = UnwrappedSingle $ "Box_Int(" ++ e ++ ")"
+valueAsUnwrapped (UnboxedPrimitive PrimFloat e)      = UnwrappedSingle $ "Box_Float(" ++ e ++ ")"
+valueAsUnwrapped (UnboxedPrimitive PrimPointer e)    = UnwrappedSingle $ "Box_Pointer<void>(" ++ e ++ ")"
+valueAsUnwrapped (UnboxedPrimitive PrimIdentifier e) = UnwrappedSingle $ "Box_Identifier(" ++ e ++ ")"
+valueAsUnwrapped (LazySingle e)                      = valueAsUnwrapped $ getFromLazy e
+valueAsUnwrapped v                                   = v
 
 variableStoredType :: ValueType -> String
 variableStoredType t
-  | t == boolRequiredValue   = "PrimBool"
-  | t == intRequiredValue    = "PrimInt"
-  | t == floatRequiredValue  = "PrimFloat"
-  | t == charRequiredValue   = "PrimChar"
-  | isPointerRequiredValue t = "PrimPointer"
-  | isWeakValue t            = "WeakValue"
-  | otherwise                = "BoxedValue"
+  | t == boolRequiredValue      = "PrimBool"
+  | t == intRequiredValue       = "PrimInt"
+  | t == floatRequiredValue     = "PrimFloat"
+  | t == charRequiredValue      = "PrimChar"
+  | isPointerRequiredValue t    = "PrimPointer"
+  | isIdentifierRequiredValue t = "PrimIdentifier"
+  | isWeakValue t               = "WeakValue"
+  | otherwise                   = "BoxedValue"
 
 variableLazyType :: ValueType -> String
 variableLazyType t = "LazyInit<" ++ variableStoredType t ++ ">"
 
 variableProxyType :: ValueType -> String
 variableProxyType t
-  | t == boolRequiredValue   = "PrimBool"
-  | t == intRequiredValue    = "PrimInt"
-  | t == floatRequiredValue  = "PrimFloat"
-  | t == charRequiredValue   = "PrimChar"
-  | isPointerRequiredValue t = "PrimPointer"
-  | isWeakValue t            = "WeakValue&"
-  | otherwise                = "BoxedValue&"
+  | t == boolRequiredValue      = "PrimBool"
+  | t == intRequiredValue       = "PrimInt"
+  | t == floatRequiredValue     = "PrimFloat"
+  | t == charRequiredValue      = "PrimChar"
+  | isPointerRequiredValue t    = "PrimPointer"
+  | isIdentifierRequiredValue t = "PrimIdentifier"
+  | isWeakValue t               = "WeakValue&"
+  | otherwise                   = "BoxedValue&"
 
 readStoredVariable :: Bool -> ValueType -> String -> ExpressionValue
 readStoredVariable True t s = LazySingle $ readStoredVariable False t s
 readStoredVariable False t s
-  | t == boolRequiredValue   = UnboxedPrimitive PrimBool    s
-  | t == intRequiredValue    = UnboxedPrimitive PrimInt     s
-  | t == floatRequiredValue  = UnboxedPrimitive PrimFloat   s
-  | t == charRequiredValue   = UnboxedPrimitive PrimChar    s
-  | isPointerRequiredValue t = UnboxedPrimitive PrimPointer s
-  | otherwise                = UnwrappedSingle s
+  | t == boolRequiredValue      = UnboxedPrimitive PrimBool     s
+  | t == intRequiredValue       = UnboxedPrimitive PrimInt      s
+  | t == floatRequiredValue     = UnboxedPrimitive PrimFloat    s
+  | t == charRequiredValue      = UnboxedPrimitive PrimChar     s
+  | isPointerRequiredValue t    = UnboxedPrimitive PrimPointer  s
+  | isIdentifierRequiredValue t = UnboxedPrimitive PrimIdentifier s
+  | otherwise                   = UnwrappedSingle s
 
 writeStoredVariable :: ValueType -> ExpressionValue -> String
 writeStoredVariable t e
-  | t == boolRequiredValue   = useAsUnboxed PrimBool    e
-  | t == intRequiredValue    = useAsUnboxed PrimInt     e
-  | t == floatRequiredValue  = useAsUnboxed PrimFloat   e
-  | t == charRequiredValue   = useAsUnboxed PrimChar    e
-  | isPointerRequiredValue t = useAsUnboxed PrimPointer e
-  | otherwise                = useAsUnwrapped e
+  | t == boolRequiredValue      = useAsUnboxed PrimBool     e
+  | t == intRequiredValue       = useAsUnboxed PrimInt      e
+  | t == floatRequiredValue     = useAsUnboxed PrimFloat    e
+  | t == charRequiredValue      = useAsUnboxed PrimChar     e
+  | isPointerRequiredValue t    = useAsUnboxed PrimPointer  e
+  | isIdentifierRequiredValue t = useAsUnboxed PrimIdentifier e
+  | otherwise                   = useAsUnwrapped e
 
 functionLabelType :: ScopedFunction c -> String
 functionLabelType = getType . sfScope where
diff --git a/src/CompilerCxx/CxxFiles.hs b/src/CompilerCxx/CxxFiles.hs
--- a/src/CompilerCxx/CxxFiles.hs
+++ b/src/CompilerCxx/CxxFiles.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -592,7 +592,7 @@
     createParam p = onlyCode $ paramType ++ " " ++ paramName (vpParam p) ++ ";"
 
   inlineCategoryConstructor t d tm em = do
-    ctx <- getContextForInit tm em t d CategoryScope
+    ctx <- getContextForInit testing tm em t d CategoryScope
     initMembers <- runDataCompiler (sequence $ map compileLazyInit members) ctx
     let initMembersStr = intercalate ", " $ cdOutput initMembers
     let initColon = if null initMembersStr then "" else " : "
@@ -709,20 +709,22 @@
       "CycleCheck<" ++ n2 ++ "> marker(*this);"
     ]
 
-  defineProcedure t = compileExecutableProcedure (isImmutable t)
+  defineProcedure t = compileExecutableProcedure testing (isImmutable t)
   declareProcedure t = procedureDeclaration (isImmutable t)
 
 isImmutable :: AnyCategory c -> Bool
 isImmutable = any isCategoryImmutable . getCategoryPragmas
 
 formatFunctionTypes :: Show c => ScopedFunction c -> [String]
-formatFunctionTypes (ScopedFunction c _ _ s as rs ps fa _) = [location,args,returns,params] ++ filters where
+formatFunctionTypes (ScopedFunction c _ _ s _ as rs ps fa _) = [location,args,returns,params] ++ filters where
   location = show s ++ " function declared at " ++ formatFullContext c
-  args    = "Arg Types:    (" ++ intercalate ", " (map (show . pvType) $ pValues as)  ++ ")"
+  args    = "Arg Types:    (" ++ intercalate ", " (map singleArg $ pValues as)  ++ ")"
   returns = "Return Types: (" ++ intercalate ", " (map (show . pvType) $ pValues rs)  ++ ")"
   params  = "Type Params:  <" ++ intercalate ", " (map (show . vpParam) $ pValues ps) ++ ">"
   filters = map singleFilter fa
   singleFilter (ParamFilter _ n2 f) = "  " ++ show n2 ++ " " ++ show f
+  singleArg (a,Just n) = show (pvType a) ++ " " ++ (calName n)
+  singleArg (a,_)      = show (pvType a)
 
 createMainCommon :: String -> CompiledData [String] -> CompiledData [String] -> [String]
 createMainCommon n (CompiledData req0 _ out0) (CompiledData req1 _ out1) =
@@ -747,13 +749,17 @@
     argv = onlyCode "ProgramArgv program_argv(argc, argv);"
 
 generateTestFile :: (Ord c, Show c, CollectErrorsM m) =>
-  CategoryMap c -> ExprMap c  -> [String] -> [TestProcedure c] -> m (CompiledData [String])
-generateTestFile tm em args ts = "In the creation of the test binary procedure" ??> do
+  CategoryMap c -> ExprMap c  -> [String] -> Maybe ([c],TypeInstance) -> [TestProcedure c] ->
+  m (CompiledData [String])
+generateTestFile tm em args t ts = "In the creation of the test binary procedure" ??> do
+  wrap <- case t of
+               Just t2 -> compileWrapTestcase tm t2
+               Nothing -> return emptyCode
   ts' <- fmap mconcat $ mapCompilerM (compileTestProcedure tm em) ts
   (include,sel) <- selectTestFromArgv1 $ map tpName ts
   let (CompiledData req traces _) = ts' <> sel
   let contentTop = mconcat [timeoutInclude,onlyCodes include,ts']
-  let contentMain = mconcat [setTimeout,argv,callLog,sel]
+  let contentMain = mconcat [setTimeout,argv,callLog,wrap,sel]
   let file = testsOnlySourceGuard ++ createMainCommon "testcase" contentTop contentMain
   return $ CompiledData req traces file where
     args' = map escapeChars args
@@ -1032,8 +1038,8 @@
   fromDefines ds = Set.toList $ Set.unions $ map (categoriesFromDefine . 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
+  fromFunction (ScopedFunction _ _ t2 _ _ as rs _ fs _) =
+    [t2] ++ (fromGenerals $ map (vtType . pvType) (map fst (pValues as) ++ pValues rs)) ++ fromFilters fs
   fromFilters fs = concat $ map (fromFilter . pfFilter) fs
   fromFilter (TypeFilter _ t2)  = Set.toList $ categoriesFromTypes t2
   fromFilter (DefinesFilter t2) = fromDefine t2
diff --git a/src/CompilerCxx/LanguageModule.hs b/src/CompilerCxx/LanguageModule.hs
--- a/src/CompilerCxx/LanguageModule.hs
+++ b/src/CompilerCxx/LanguageModule.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -35,7 +35,6 @@
 import CompilerCxx.CxxFiles
 import CompilerCxx.Naming
 import Module.CompileMetadata (CategorySpec(..))
-import Types.Builtin
 import Types.DefinedCategory
 import Types.Procedure
 import Types.TypeCategory
@@ -49,12 +48,15 @@
     lmLocalNamespaces :: Set.Set Namespace,
     lmPublicDeps :: [AnyCategory c],
     lmPrivateDeps :: [AnyCategory c],
-    lmTestingDeps :: [AnyCategory c],
+    lmPublicTestingDeps :: [AnyCategory c],
+    lmPrivateTestingDeps :: [AnyCategory c],
     lmPublicLocal :: [AnyCategory c],
     lmPrivateLocal :: [AnyCategory c],
-    lmTestingLocal :: [AnyCategory c],
+    lmPublicTestingLocal :: [AnyCategory c],
+    lmPrivateTestingLocal :: [AnyCategory c],
     lmStreamlined :: [CategoryName],
-    lmExprMap :: ExprMap c
+    lmExprMap :: ExprMap c,
+    lmEmptyCategories :: CategoryMap c
   }
 
 data PrivateSource c =
@@ -68,47 +70,49 @@
 compileLanguageModule :: (Ord c, Show c, CollectErrorsM m) =>
   LanguageModule c -> Map.Map CategoryName (CategorySpec c) ->
   [PrivateSource c] -> m [CxxOutput]
-compileLanguageModule (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1  ss em) cm xa = do
+compileLanguageModule (LanguageModule ns0 ns1 ns2 cs0 ps0 tc0 tp0 cs1 ps1 tc1 tp1 ss em cm0) sm xa = do
   let dm = mapDefByName $ concat $ map psDefine xa
-  checkDefined dm extensions allExternal $ filter isValueConcrete (cs1 ++ ps1 ++ ts1)
+  checkDefined dm extensions allExternal $ filter isValueConcrete (cs1 ++ ps1 ++ tc1 ++ tp1)
   checkSupefluous $ Set.toList $ extensions `Set.difference` ca
-  tmPublic  <- foldM includeNewTypes defaultCategories [cs0,cs1]
-  tmPrivate <- foldM includeNewTypes tmPublic          [ps0,ps1]
-  tmTesting <- foldM includeNewTypes tmPrivate         [ts0,ts1]
+  tmPublic         <- foldM includeNewTypes cm0             [cs0,cs1]
+  tmPrivate        <- foldM includeNewTypes tmPublic        [ps0,ps1]
+  tmPublicTesting  <- foldM includeNewTypes tmPublic        [tc0,tc1]
+  tmPrivateTesting <- foldM includeNewTypes tmPublicTesting [ps0,tp0,ps1,tp1]
   xxInterfaces <- fmap concat $ collectAllM $
     map (generateNativeInterface False nsPublic)  (onlyNativeInterfaces cs1) ++
     map (generateNativeInterface False nsPrivate) (onlyNativeInterfaces ps1) ++
-    map (generateNativeInterface True  nsPrivate) (onlyNativeInterfaces ts1)
-  xxPrivate <- fmap concat $ mapCompilerM (compilePrivate tmPrivate tmTesting) xa
-  xxStreamlined <- fmap concat $ mapCompilerM (streamlined tmPrivate tmTesting) $ nub ss
+    map (generateNativeInterface True  nsPublic)  (onlyNativeInterfaces tc1) ++
+    map (generateNativeInterface True  nsPrivate) (onlyNativeInterfaces tp1)
+  xxPrivate <- fmap concat $ mapCompilerM (compilePrivate tmPrivate tmPrivateTesting) xa
+  xxStreamlined <- fmap concat $ mapCompilerM (streamlined tmPrivate tmPrivateTesting) $ nub ss
   let allFiles = xxInterfaces ++ xxPrivate ++ xxStreamlined
   noDuplicateFiles $ map (\f -> (coFilename f,coNamespace f)) allFiles
   return allFiles where
     nsPublic  = ns0 `Set.union` ns2
     nsPrivate = ns1 `Set.union` nsPublic
     extensions = Set.fromList ss
-    allExternal = Set.unions [extensions,Map.keysSet cm]
-    testingCats = Set.fromList $ map getCategoryName ts1
+    allExternal = Set.unions [extensions,Map.keysSet sm]
+    testingCats = Set.fromList $ (map getCategoryName tc1) ++ (map getCategoryName tp1)
     onlyNativeInterfaces = filter (not . (`Set.member` extensions) . getCategoryName) . filter (not . isValueConcrete)
-    localCats = Set.fromList $ map getCategoryName $ cs1 ++ ps1 ++ ts1
+    localCats = Set.fromList $ map getCategoryName $ cs1 ++ ps1 ++ tc1 ++ tp1
     streamlined tm0 tm2 n = do
       checkLocal localCats ([] :: [String]) n
       let testing = n `Set.member` testingCats
       let tm = if testing then tm2 else tm0
       (_,t) <- getConcreteCategory tm ([],n)
       let ctx = FileContext testing tm nsPrivate Map.empty
-      let spec = Map.findWithDefault (CategorySpec [] [] []) (getCategoryName t) cm
+      let spec = Map.findWithDefault (CategorySpec [] [] []) (getCategoryName t) sm
       generateStreamlinedExtension ctx t spec
     compilePrivate tmPrivate tmTesting (PrivateSource ns3 testing cs2 ds) = do
       let tm = if testing
                   then tmTesting
                   else tmPrivate
       let cs = Set.fromList $ map getCategoryName $ if testing
-                                                       then cs2 ++ cs1 ++ ps1 ++ ts1
+                                                       then cs2 ++ cs1 ++ ps1 ++ tc1 ++ tp1
                                                        else cs2 ++ cs1 ++ ps1
       tm' <- includeNewTypes tm cs2
       let ctx = FileContext testing tm' (ns3 `Set.insert` nsPrivate) em
-      checkLocals ds $ Map.keysSet tm'
+      checkLocals ds $ Map.keysSet $ cmAvailable tm'
       when testing $ checkTests ds (cs1 ++ ps1)
       let dm = mapDefByName ds
       checkDefined dm Set.empty Set.empty $ filter isValueConcrete cs2
@@ -123,7 +127,7 @@
       checkLocal cs (dcContext d) (dcName d)
       fmap snd $ getConcreteCategory tm (dcContext d,dcName d)
     mapDefByName = Map.fromListWith (++) . map (\d -> (dcName d,[d]))
-    ca = Set.fromList $ map getCategoryName $ filter isValueConcrete (cs1 ++ ps1 ++ ts1)
+    ca = Set.fromList $ map getCategoryName $ filter isValueConcrete (cs1 ++ ps1 ++ tc1 ++ tp1)
     checkLocals ds tm = mapCompilerM_ (\d -> checkLocal tm (dcContext d) (dcName d)) ds
     checkLocal cs2 c n =
       when (not $ n `Set.member` cs2) $
@@ -170,9 +174,9 @@
       return $ (f,ns3) `Set.insert` used
 
 compileTestsModule :: (Ord c, Show c, CollectErrorsM m) =>
-  LanguageModule c -> Namespace -> [String] -> [AnyCategory c] -> [DefinedCategory c] ->
-  [TestProcedure c] -> m ([CxxOutput],CxxOutput,[(FunctionName,[c])])
-compileTestsModule cm ns args cs ds ts = do
+  LanguageModule c -> Namespace -> [String] -> Maybe ([c],TypeInstance) -> [AnyCategory c] ->
+  [DefinedCategory c] -> [TestProcedure c] -> m ([CxxOutput],CxxOutput,[(FunctionName,[c])])
+compileTestsModule cm ns args t cs ds ts = do
   let xs = PrivateSource {
       psNamespace = ns,
       psTesting = True,
@@ -180,23 +184,23 @@
       psDefine = ds
     }
   xx <- compileLanguageModule cm Map.empty [xs]
-  (main,fs) <- compileTestMain cm args xs ts
+  (main,fs) <- compileTestMain cm args t xs ts
   return (xx,main,fs)
 
 compileTestMain :: (Ord c, Show c, CollectErrorsM m) =>
-  LanguageModule c -> [String] -> PrivateSource c -> [TestProcedure c] ->
+  LanguageModule c -> [String] -> Maybe ([c],TypeInstance) -> PrivateSource c -> [TestProcedure c] ->
   m (CxxOutput,[(FunctionName,[c])])
-compileTestMain (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 _ em) args ts2 tests = do
+compileTestMain (LanguageModule ns0 ns1 ns2 cs0 ps0 tc0 tp0 cs1 ps1 tc1 tp1 _ em cm0) args t ts2 tests = do
   tm' <- tm
-  (CompiledData req traces main) <- generateTestFile tm' em args tests
+  (CompiledData req traces main) <- generateTestFile tm' em args t tests
   let output = CxxOutput Nothing testFilename NoNamespace (psNamespace ts2 `Set.insert` Set.unions [ns0,ns1,ns2]) req traces main
-  let tests' = map (\t -> (tpName t,tpContext t)) tests
+  let tests' = map (\t2 -> (tpName t2,tpContext t2)) tests
   return (output,tests') where
-  tm = foldM includeNewTypes defaultCategories [cs0,cs1,ps0,ps1,ts0,ts1,psCategory ts2]
+  tm = foldM includeNewTypes cm0 [cs0,cs1,ps0,ps1,tc0,tp0,tc1,tp1,psCategory ts2]
 
 compileModuleMain :: (Ord c, Show c, CollectErrorsM m) =>
   LanguageModule c -> [PrivateSource c] -> CategoryName -> FunctionName -> m CxxOutput
-compileModuleMain (LanguageModule ns0 ns1 ns2 cs0 ps0 _ cs1 ps1 _ _ em) xa n f = do
+compileModuleMain (LanguageModule ns0 ns1 ns2 cs0 ps0 _ _ cs1 ps1 _ _ _ em cm0) xa n f = do
   let resolved = filter (\d -> dcName d == n) $ concat $ map psDefine $ filter (not . psTesting) xa
   reconcile resolved
   tm' <- tm
@@ -204,7 +208,7 @@
   tm'' <- includeNewTypes tm' cs
   (ns,main) <- generateMainFile tm'' em n f
   return $ CxxOutput Nothing mainFilename NoNamespace (ns `Set.insert` Set.unions [ns0,ns1,ns2]) (Set.fromList [n]) Set.empty main where
-    tm = foldM includeNewTypes defaultCategories [cs0,cs1,ps0,ps1]
+    tm = foldM includeNewTypes cm0 [cs0,cs1,ps0,ps1]
     reconcile [_] = return ()
     reconcile []  = compilerErrorM $ "No matches for main category " ++ show n ++ " ($TestsOnly$ sources excluded)"
     reconcile ds  =
diff --git a/src/CompilerCxx/Procedure.hs b/src/CompilerCxx/Procedure.hs
--- a/src/CompilerCxx/Procedure.hs
+++ b/src/CompilerCxx/Procedure.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -31,6 +31,7 @@
   compileLazyInit,
   compileRegularInit,
   compileTestProcedure,
+  compileWrapTestcase,
   procedureDeclaration,
   selectTestFromArgv1,
 ) where
@@ -87,12 +88,12 @@
   deriving Show
 
 compileExecutableProcedure :: (Ord c, Show c, CollectErrorsM m) =>
-  Bool -> CxxFunctionType -> ScopeContext c -> ScopedFunction c ->
+  Bool -> Bool -> CxxFunctionType -> ScopeContext c -> ScopedFunction c ->
   ExecutableProcedure c -> m (CompiledData [String])
-compileExecutableProcedure immutable cxxType ctx
-  ff@(ScopedFunction _ _ _ s as1 rs1 ps1 _ _)
+compileExecutableProcedure to immutable cxxType ctx
+  ff@(ScopedFunction _ _ _ s _ as1 rs1 ps1 _ _)
   pp@(ExecutableProcedure c pragmas c2 n as2 rs2 p) = do
-  ctx' <- getProcedureContext ctx ff pp
+  ctx' <- getProcedureContext to ctx ff pp
   output <- runDataCompiler compileWithReturn ctx'
   procedureTrace <- setProcedureTrace
   creationTrace  <- setCreationTrace
@@ -152,7 +153,7 @@
       | otherwise            = ["ReturnTuple returns(" ++ show (length $ pValues rs1) ++ ");"]
     nameParams = flip map (zip ([0..] :: [Int]) $ pValues ps1) $
       (\(i,p2) -> paramType ++ " " ++ paramName (vpParam p2) ++ " = params_args.GetParam(" ++ show i ++ ");")
-    nameArgs = map nameSingleArg (zip ([0..] :: [Int]) $ zip (pValues as1) (pValues $ avNames as2))
+    nameArgs = map nameSingleArg (zip ([0..] :: [Int]) $ zip (map fst $ pValues as1) (pValues $ avNames as2))
     nameSingleArg (i,(t2,n2))
       | isDiscardedInput n2 = "// Arg " ++ show i ++ " (" ++ show (pvType t2) ++ ") is discarded"
       | otherwise = "const " ++ variableProxyType (pvType t2) ++ " " ++ variableName (ivName n2) ++
@@ -307,6 +308,19 @@
       csSetDeferred (UsedVariable c2 n)
     createVariable (ExistingVariable (DiscardInput c2)) =
       compilerErrorM $ "Cannot defer discarded value" ++ formatFullContextBrace c2
+compileStatement (VariableSwap c vl vr) = message ??> handle vl vr where
+  message = "In variable swap at " ++ formatFullContext c
+  handle (OutputValue cl nl) (OutputValue cr nr) = do
+    r <- csResolver
+    fa <- csAllFilters
+    (VariableValue _ sl tl _) <- getWritableVariable cl nl
+    (VariableValue _ sr tr _) <- getWritableVariable cr nr
+    lift $ checkValueAssignment r fa tl tr
+    lift $ checkValueAssignment r fa tr tl
+    csCheckVariableInit [UsedVariable cl nl, UsedVariable cr nr]
+    scopedL <- autoScope sl
+    scopedR <- autoScope sr
+    csWrite ["SwapValues(" ++ scopedL ++ variableName nl ++ ", " ++ scopedR ++ variableName nr ++ ");"]
 compileStatement (Assignment c as e) = message ??> do
   (ts,e') <- compileExpression e
   r <- csResolver
@@ -362,6 +376,9 @@
       csWrite [scoped ++ variableName n ++ " = " ++
                writeStoredVariable t (UnwrappedSingle $ "r.At(" ++ show i ++ ")") ++ ";"]
     assignMulti _ = return ()
+compileStatement (AssignmentEmpty c n e) = do
+  (_,e') <- compileExpressionStart (InlineAssignment c n AssignIfEmpty e)
+  csWrite ["(void) (" ++ useAsWhatever e' ++ ");"]
 compileStatement (NoValueExpression _ v) = compileVoidExpression v
 compileStatement (MarkReadOnly c vs) = mapM_ (\v -> csSetReadOnly (UsedVariable c v)) vs
 compileStatement (MarkHidden   c vs) = mapM_ (\v -> csSetHidden   (UsedVariable c v)) vs
@@ -486,10 +503,10 @@
                                                (Positional [t])
   let currVar = hiddenVariableName $ VariableName "traverse"
   let currType = orderOptionalValue $ fixTypeParams t2
-  let currExpr    = BuiltinCall [] $ FunctionCall [] BuiltinRequire (Positional []) (Positional [RawExpression (Positional [currType]) (UnwrappedSingle currVar)])
-  let currPresent = BuiltinCall [] $ FunctionCall [] BuiltinPresent (Positional []) (Positional [RawExpression (Positional [currType]) (UnwrappedSingle currVar)])
-  let callNext = Expression c1 currExpr [ValueCall c1 $ FunctionCall c1 (FunctionName "next") (Positional []) (Positional [])]
-  let callGet  = Expression c2 currExpr [ValueCall c2 $ FunctionCall c2 (FunctionName "get")  (Positional []) (Positional [])]
+  let currExpr    = BuiltinCall [] $ FunctionCall [] BuiltinRequire (Positional []) (Positional [(Nothing,RawExpression (Positional [currType]) (UnwrappedSingle currVar))])
+  let currPresent = BuiltinCall [] $ FunctionCall [] BuiltinPresent (Positional []) (Positional [(Nothing,RawExpression (Positional [currType]) (UnwrappedSingle currVar))])
+  let callNext = Expression c1 currExpr [ValueCall c1 AlwaysCall $ FunctionCall c1 (FunctionName "next") (Positional []) (Positional [])]
+  let callGet  = Expression c2 currExpr [ValueCall c2 AlwaysCall $ FunctionCall c2 (FunctionName "get")  (Positional []) (Positional [])]
   (Positional [typeGet],exprNext) <- compileExpression callNext
   when (typeGet /= currType) $ compilerErrorM $ "Unexpected return type from next(): " ++ show typeGet ++ " (expected) " ++ show currType ++ " (actual)"
   let assnGet = if isAssignableDiscard a then [] else [Assignment c2 (Positional [a]) callGet]
@@ -601,21 +618,31 @@
                       CompilerContext c m [String] a) =>
   Expression c -> CompilerState a m (ExpressionType,ExpressionValue)
 compileExpression = compile where
+  callFunctionSpec c as (FunctionSpec _ (CategoryFunction c2 cn) fn ps) =
+    compile (Expression c (CategoryCall c2 cn (FunctionCall c fn ps as)) [])
+  callFunctionSpec c as (FunctionSpec _ (TypeFunction c2 tn) fn ps) =
+    compile (Expression c (TypeCall c2 tn (FunctionCall c fn ps as)) [])
+  callFunctionSpec c as (FunctionSpec _ (ValueFunction c2 e0) fn ps) =
+    compile (Expression c (ParensExpression c2 e0) [ValueCall c AlwaysCall (FunctionCall c fn ps as)])
+  callFunctionSpec c as (FunctionSpec c2 UnqualifiedFunction fn ps) =
+    compile (Expression c (UnqualifiedCall c2 (FunctionCall c fn ps as)) [])
   compile (Literal l) = compileValueLiteral l
   compile (Expression _ s os) = do
     foldl transform (compileExpressionStart s) os
-  compile (UnaryExpression c (FunctionOperator _ (FunctionSpec _ (CategoryFunction c2 cn) fn ps)) e) =
-    compile (Expression c (CategoryCall c2 cn (FunctionCall c fn ps (Positional [e]))) [])
-  compile (UnaryExpression c (FunctionOperator _ (FunctionSpec _ (TypeFunction c2 tn) fn ps)) e) =
-    compile (Expression c (TypeCall c2 tn (FunctionCall c fn ps (Positional [e]))) [])
-  compile (UnaryExpression c (FunctionOperator _ (FunctionSpec _ (ValueFunction c2 e0) fn ps)) e) =
-    compile (Expression c (ParensExpression c2 e0) [ValueCall c (FunctionCall c fn ps (Positional [e]))])
-  compile (UnaryExpression c (FunctionOperator _ (FunctionSpec c2 UnqualifiedFunction fn ps)) e) =
-    compile (Expression c (UnqualifiedCall c2 (FunctionCall c fn ps (Positional [e]))) [])
+  compile (DelegatedFunctionCall c f) = "In function delegation at " ++ formatFullContext c ??> do
+    args <- csDelegateArgs
+    let vars = fmap (\(l,v) -> (l,Expression c (NamedVariable (OutputValue c v)) [])) args
+    callFunctionSpec c vars f
+  compile (DelegatedInitializeValue c t) = "In initialization delegation at " ++ formatFullContext c ??> do
+    args <- csDelegateArgs
+    let vars = fmap (\(_,v) -> Expression c (NamedVariable (OutputValue c v)) []) args
+    compile (Expression c  (InitializeValue c t vars) [])
+  compile (UnaryExpression c (FunctionOperator _ fa@(FunctionSpec _ _ _ _)) e) =
+    callFunctionSpec c (Positional [(Nothing,e)]) fa
   compile (UnaryExpression _ (NamedOperator c "-") (Literal (IntegerLiteral _ _ l))) =
     compile (Literal (IntegerLiteral c False (-l)))
-  compile (UnaryExpression _ (NamedOperator c "-") (Literal (DecimalLiteral _ l e))) =
-    compile (Literal (DecimalLiteral c (-l) e))
+  compile (UnaryExpression _ (NamedOperator c "-") (Literal (DecimalLiteral _ l e b))) =
+    compile (Literal (DecimalLiteral c (-l) e b))
   compile (UnaryExpression _ (NamedOperator c o) e) = do
     (Positional ts,e') <- compileExpression e
     t' <- requireSingle c ts
@@ -645,13 +672,13 @@
         | otherwise = compilerErrorM $ "Cannot use " ++ show t ++ " with unary ~ operator" ++
                                        formatFullContextBrace c
   compile (InfixExpression c e1 (FunctionOperator _ (FunctionSpec _ (CategoryFunction c2 cn) fn ps)) e2) =
-    compile (Expression c (CategoryCall c2 cn (FunctionCall c fn ps (Positional [e1,e2]))) [])
+    compile (Expression c (CategoryCall c2 cn (FunctionCall c fn ps (Positional [(Nothing,e1),(Nothing,e2)]))) [])
   compile (InfixExpression c e1 (FunctionOperator _ (FunctionSpec _ (TypeFunction c2 tn) fn ps)) e2) =
-    compile (Expression c (TypeCall c2 tn (FunctionCall c fn ps (Positional [e1,e2]))) [])
+    compile (Expression c (TypeCall c2 tn (FunctionCall c fn ps (Positional [(Nothing,e1),(Nothing,e2)]))) [])
   compile (InfixExpression c e1 (FunctionOperator _ (FunctionSpec _ (ValueFunction c2 e0) fn ps)) e2) =
-    compile (Expression c (ParensExpression c2 e0) [ValueCall c (FunctionCall c fn ps (Positional [e1,e2]))])
+    compile (Expression c (ParensExpression c2 e0) [ValueCall c AlwaysCall (FunctionCall c fn ps (Positional [(Nothing,e1),(Nothing,e2)]))])
   compile (InfixExpression c e1 (FunctionOperator _ (FunctionSpec c2 UnqualifiedFunction fn ps)) e2) =
-    compile (Expression c (UnqualifiedCall c2 (FunctionCall c fn ps (Positional [e1,e2]))) [])
+    compile (Expression c (UnqualifiedCall c2 (FunctionCall c fn ps (Positional [(Nothing,e1),(Nothing,e2)]))) [])
   compile (InfixExpression _ e1 (NamedOperator c o) e2) = do
     e1' <- compileExpression e1
     e2' <- if o `Set.member` logical
@@ -679,6 +706,8 @@
     bind t1' t2'
     where
       bind t1 t2
+        | o `Set.member` comparison && isIdentifierRequiredValue t1 && isIdentifierRequiredValue t2 = do
+          return (Positional [boolRequiredValue],glueInfix PrimIdentifier PrimBool e1 o e2)
         | t1 /= t2 =
           compilerErrorM $ "Cannot " ++ show o ++ " " ++ show t1 ++ " and " ++
                            show t2 ++ formatFullContextBrace c
@@ -726,11 +755,11 @@
     (lift $ checkValueAssignment r fa t' vt) <??
       "In explicit type conversion at " ++ formatFullContext c
     return (Positional [vt],e')
-  transform e (ValueCall c f) = do
+  transform e (ValueCall c o f) = do
     (Positional ts,e') <- e
     t' <- requireSingle c ts
-    f' <- lookupValueFunction t' f
-    compileFunctionCall (Just $ useAsUnwrapped e') f' f
+    f' <- lookupValueFunction t' o f
+    compileFunctionCall (o == CallUnlessEmpty) (Just $ useAsUnwrapped e') f' f
   transform e (SelectReturn c pos) = do
     (Positional ts,e') <- e
     when (not $ isOpaqueMulti e') $
@@ -740,20 +769,37 @@
     return (Positional [ts !! pos],WrappedSingle $ useAsReturns e' ++ ".At(" ++ show pos ++ ")")
   requireSingle _ [t] = return t
   requireSingle c2 ts =
-    compilerErrorM $ "Function call requires 1 return but found but found {" ++
-                            intercalate "," (map show ts) ++ "}" ++ formatFullContextBrace c2
+    compilerErrorM $ "Function call requires one return but got " ++ formatTypes ts ++ formatFullContextBrace c2
+  formatTypes [] = "none"
+  formatTypes ts = intercalate ", " (map show ts)
 
+forceOptionalReturns :: [c] -> ScopedFunction c -> ScopedFunction c
+forceOptionalReturns c0 (ScopedFunction c n t s v as rs ps fs ms) =
+  ScopedFunction c n t s v as rs' ps fs ms where
+    rs' = fmap forceOptional rs
+    forceOptional (PassedValue c2 (ValueType RequiredValue t2)) = (PassedValue (c0 ++ c2) (ValueType OptionalValue t2))
+    forceOptional t2 = t2
+
 lookupValueFunction :: (Ord c, Show c, CollectErrorsM m,
                         CompilerContext c m [String] a) =>
-  ValueType -> FunctionCall c -> CompilerState a m (ScopedFunction c)
-lookupValueFunction (ValueType WeakValue t) (FunctionCall c _ _ _) =
+  ValueType -> ValueCallType -> FunctionCall c -> CompilerState a m (ScopedFunction c)
+lookupValueFunction (ValueType OptionalValue t) CallUnlessEmpty f@(FunctionCall c _ _ _) = do
+  f' <- lookupValueFunction (ValueType RequiredValue t) AlwaysCall f
+  return $ forceOptionalReturns c f'
+lookupValueFunction t CallUnlessEmpty (FunctionCall c _ _ _) =
+  compilerErrorM $ "Optional type required for &. but got " ++ show t ++ formatFullContextBrace c
+lookupValueFunction (ValueType WeakValue t) _ (FunctionCall c _ _ _) =
   compilerErrorM $ "Use strong to convert weak " ++ show t ++
                         " to optional first" ++ formatFullContextBrace c
-lookupValueFunction (ValueType OptionalValue t) (FunctionCall c _ _ _) =
+lookupValueFunction (ValueType OptionalValue t) _ (FunctionCall c _ _ _) =
   compilerErrorM $ "Use require to convert optional " ++ show t ++
                         " to required first" ++ formatFullContextBrace c
-lookupValueFunction (ValueType RequiredValue t) (FunctionCall c n _ _) =
-  csGetTypeFunction c (Just t) n
+lookupValueFunction (ValueType RequiredValue t) _ (FunctionCall c n _ _) = do
+  f' <- csGetTypeFunction c (Just t) n
+  when (sfScope f' /= ValueScope) $ compilerErrorM $ "Function " ++ show n ++
+                                                     " cannot be used as a value function" ++
+                                                     formatFullContextBrace c
+  return f'
 
 compileExpressionStart :: (Ord c, Show c, CollectErrorsM m,
                            CompilerContext c m [String] a) =>
@@ -773,11 +819,21 @@
   -- NOTE: This will be skipped if expression compilation fails.
   csReleaseExprMacro c n
   return e'
+compileExpressionStart (ExpressionMacro c MacroCallTrace) = do
+  to <- csGetTestsOnly
+  when (not to) $ compilerErrorM $ "$CallTrace$ is a $TestsOnly$ macro" ++ formatFullContextBrace c
+  csAddRequired $ Set.fromList [BuiltinOrder,BuiltinFormatted]
+  let formatted = singleType $ JustTypeInstance (TypeInstance BuiltinFormatted (Positional []))
+  let order = singleType $ JustTypeInstance (TypeInstance BuiltinOrder (Positional [formatted]))
+  nextFunc <- csGetTypeFunction c (Just order) (FunctionName "next")
+  getFunc <- csGetTypeFunction c (Just order) (FunctionName "get")
+  let getTrace = "GetCallTrace(" ++ functionName getFunc ++ ", " ++ functionName nextFunc ++ ")"
+  return (Positional [orderOptionalValue formatted],UnwrappedSingle getTrace)
 compileExpressionStart (CategoryCall c t f@(FunctionCall _ n _ _)) = do
   f' <- csGetCategoryFunction c (Just t) n
   csAddRequired $ Set.fromList [t,sfType f']
   t' <- expandCategory t
-  compileFunctionCall (Just t') f' f
+  compileFunctionCall False (Just t') f' f
 compileExpressionStart (TypeCall c t f@(FunctionCall _ n _ _)) = do
   self <- autoSelfType
   t' <- lift $ replaceSelfInstance self (singleType t)
@@ -794,7 +850,7 @@
   t2 <- if same
            then return Nothing
            else fmap Just $ expandGeneralInstance t'
-  compileFunctionCall t2 f' f
+  compileFunctionCall False t2 f' f
   where
     maybeSingleType = lift . tryCompilerM . matchOnlyLeaf
     checkSame (Just (JustTypeInstance t2)) = csSameType t2
@@ -803,7 +859,7 @@
   ctx <- get
   f' <- lift $ collectFirstM [tryCategory ctx,tryNonCategory ctx] <?? "In function call at " ++ formatFullContext c
   csAddRequired $ Set.fromList [sfType f']
-  compileFunctionCall Nothing f' f
+  compileFunctionCall False Nothing f' f
   where
     tryCategory ctx = ccGetCategoryFunction ctx c Nothing n
     tryNonCategory ctx = do
@@ -819,7 +875,7 @@
     compilerErrorM $ "Expected 0 type parameters" ++ formatFullContextBrace c
   when (length (pValues es) /= 1) $
     compilerErrorM $ "Expected 1 argument" ++ formatFullContextBrace c
-  es' <- sequence $ map compileExpression $ pValues es
+  es' <- sequence $ map (compileExpression . snd) $ pValues es
   when (length (pValues $ fst $ head es') /= 1) $
     compilerErrorM $ "Expected single return in argument" ++ formatFullContextBrace c
   let (Positional [t0],e) = head es'
@@ -827,12 +883,26 @@
     compilerErrorM $ "Weak values not allowed here" ++ formatFullContextBrace c
   return $ (Positional [boolRequiredValue],
             UnboxedPrimitive PrimBool $ "BoxedValue::Present(" ++ useAsUnwrapped e ++ ")")
+compileExpressionStart (BuiltinCall c (FunctionCall _ BuiltinIdentify ps es)) = do
+  when (length (pValues ps) /= 0) $
+    compilerErrorM $ "Expected 0 type parameters" ++ formatFullContextBrace c
+  when (length (pValues es) /= 1) $
+    compilerErrorM $ "Expected 1 argument" ++ formatFullContextBrace c
+  es' <- sequence $ map (compileExpression . snd) $ pValues es
+  when (length (pValues $ fst $ head es') /= 1) $
+    compilerErrorM $ "Expected single return in argument" ++ formatFullContextBrace c
+  let (Positional [t0],e) = head es'
+  when (isWeakValue t0) $
+    compilerErrorM $ "Weak values not allowed here" ++ formatFullContextBrace c
+  csAddRequired $ Set.fromList [BuiltinIdentifier]
+  return $ (Positional [ValueType RequiredValue (singleType $ JustTypeInstance (TypeInstance BuiltinIdentifier (Positional [(vtType t0)])))],
+            UnboxedPrimitive PrimIdentifier $ "BoxedValue::Identify(" ++ useAsUnwrapped e ++ ")")
 compileExpressionStart (BuiltinCall c (FunctionCall _ BuiltinReduce ps es)) = do
   when (length (pValues ps) /= 2) $
     compilerErrorM $ "Expected 2 type parameters" ++ formatFullContextBrace c
   when (length (pValues es) /= 1) $
     compilerErrorM $ "Expected 1 argument" ++ formatFullContextBrace c
-  es' <- sequence $ map compileExpression $ pValues es
+  es' <- sequence $ map (compileExpression . snd) $ pValues es
   when (length (pValues $ fst $ head es') /= 1) $
     compilerErrorM $ "Expected single return in argument" ++ formatFullContextBrace c
   let (Positional [t0],e) = head es'
@@ -857,7 +927,7 @@
     compilerErrorM $ "Expected 0 type parameters" ++ formatFullContextBrace c
   when (length (pValues es) /= 1) $
     compilerErrorM $ "Expected 1 argument" ++ formatFullContextBrace c
-  es' <- sequence $ map compileExpression $ pValues es
+  es' <- sequence $ map (compileExpression . snd) $ pValues es
   when (length (pValues $ fst $ head es') /= 1) $
     compilerErrorM $ "Expected single return in argument" ++ formatFullContextBrace c
   let (Positional [t0],e) = head es'
@@ -870,7 +940,7 @@
     compilerErrorM $ "Expected 0 type parameters" ++ formatFullContextBrace c
   when (length (pValues es) /= 1) $
     compilerErrorM $ "Expected 1 argument" ++ formatFullContextBrace c
-  es' <- sequence $ map compileExpression $ pValues es
+  es' <- sequence $ map (compileExpression . snd) $ pValues es
   when (length (pValues $ fst $ head es') /= 1) $
     compilerErrorM $ "Expected single return in argument" ++ formatFullContextBrace c
   let (Positional [t0],e) = head es'
@@ -896,9 +966,15 @@
             valueAsWrapped $ UnboxedPrimitive PrimString $ typeBase ++ "::TypeName(" ++ t' ++ ")")
 compileExpressionStart (BuiltinCall _ _) = undefined
 compileExpressionStart (ParensExpression _ e) = compileExpression e
-compileExpressionStart (InlineAssignment c n e) = do
+compileExpressionStart (InlineAssignment c n o e) = do
   (VariableValue _ s t0 _) <- getWritableVariable c n
-  (Positional [t],e') <- compileExpression e -- TODO: Get rid of the Positional matching here.
+  e2 <- compileExpression e
+  when (length (pValues $ fst $ e2) /= 1) $
+    compilerErrorM $ "Expected single return" ++ formatFullContextBrace c
+  let (Positional [t],e') = e2
+  when (o == AssignIfEmpty && not (isOptionalValue t0)) $
+    compilerErrorM $ "Variable must have an optional type" ++ formatFullContextBrace c
+  when (o == AssignIfEmpty) $ csCheckVariableInit [UsedVariable c n]
   r <- csResolver
   fa <- csAllFilters
   lift $ (checkValueAssignment r fa t t0) <??
@@ -906,8 +982,19 @@
   csUpdateAssigned n
   scoped <- autoScope s
   let lazy = s == CategoryScope
-  return (Positional [t0],readStoredVariable lazy t0 $ "(" ++ scoped ++ variableName n ++
-                                                     " = " ++ writeStoredVariable t0 e' ++ ")")
+  let variable = scoped ++ variableName n
+  let assign = variable ++ " = " ++ writeStoredVariable t0 e'
+  let check = "BoxedValue::Present(" ++ useAsUnwrapped (readStoredVariable lazy t0 variable) ++ ")"
+  let assignAndGet = readStoredVariable lazy t0 assign
+  let alwaysAssign = if isWeakValue t0 && not (isWeakValue t)
+                        then UnwrappedSingle $ "BoxedValue::Strong(" ++ useAsUnwrapped assignAndGet ++ ")"
+                        else assignAndGet
+  let maybeAssign = readStoredVariable lazy t0 $ check ++ " ? " ++ variable ++ " : (" ++ assign ++ ")"
+  case o of
+       AlwaysAssign -> return (Positional [t],alwaysAssign)
+       AssignIfEmpty -> return (Positional [combineTypes t0 t],maybeAssign)
+  where
+    combineTypes (ValueType _ t1) (ValueType s _) = ValueType s t1
 compileExpressionStart (InitializeValue c t es) = do
   scope <- csCurrentScope
   t' <- case scope of
@@ -978,10 +1065,17 @@
   -- written out as a literal looks like an unsigned overflow. Using ULL here
   -- silences that warning.
   return $ expressionFromLiteral PrimInt (show l ++ "ULL")
-compileValueLiteral (DecimalLiteral _ l e) = do
+compileValueLiteral (DecimalLiteral _ l e 10) = do
   csAddRequired (Set.fromList [BuiltinFloat])
   -- TODO: Check bounds.
   return $ expressionFromLiteral PrimFloat (show l ++ "E" ++ show e)
+compileValueLiteral (DecimalLiteral _ l e b) = do
+  csAddRequired (Set.fromList [BuiltinFloat])
+  let scale = if e < 0
+                 then "/" ++ show (b^(-e))
+                 else "*" ++ show (b^e)
+  -- TODO: Check bounds.
+  return $ expressionFromLiteral PrimFloat ("(" ++ show l ++ "E0" ++ scale ++ ")")
 compileValueLiteral (BoolLiteral _ True) = do
   csAddRequired (Set.fromList [BuiltinBool])
   return $ expressionFromLiteral PrimBool "true"
@@ -999,12 +1093,12 @@
 
 compileFunctionCall :: (Ord c, Show c, CollectErrorsM m,
                         CompilerContext c m [String] a) =>
-  Maybe String -> ScopedFunction c -> FunctionCall c ->
+  Bool -> Maybe String -> ScopedFunction c -> FunctionCall c ->
   CompilerState a m (ExpressionType,ExpressionValue)
-compileFunctionCall e f (FunctionCall c _ ps es) = message ??> do
+compileFunctionCall optionalValue e f (FunctionCall c _ ps es) = message ??> do
   r <- csResolver
   fa <- csAllFilters
-  es' <- sequence $ map compileExpression $ pValues es
+  es' <- sequence $ map (compileExpression . snd) $ pValues es
   (ts,es'') <- lift $ getValues es'
   f' <- lift $ parsedToFunctionType f
   let psActual = case ps of
@@ -1017,6 +1111,11 @@
   lift $ mapCompilerM_ backgroundMessage $ zip3 (map vpParam $ pValues $ sfParams f) (pValues ps') (pValues ps2)
   -- Called an extra time so arg count mismatches have reasonable errors.
   lift $ processPairs_ (\_ _ -> return ()) (ftArgs f'') (Positional ts)
+  lift $ if (length ts /= length (pValues es))
+            then do
+              mapCompilerM_ (labelNotAllowedError . fst) $ pValues es
+              mapCompilerM_ (labelNotSetError . snd) $ pValues $ sfArgs f
+            else processPairs_ checkArgLabel (fmap snd $ sfArgs f) (Positional $ zip ([0..] :: [Int]) $ map fst $ pValues es)
   lift $ processPairs_ (checkArg r fa) (ftArgs f'') (Positional $ zip ([0..] :: [Int]) ts)
   csAddRequired $ Set.unions $ map categoriesFromTypes $ pValues ps2
   csAddRequired (Set.fromList [sfType f])
@@ -1027,18 +1126,23 @@
   call <- assemble e scoped scope (sfScope f) paramsArgs
   return $ (ftReturns f'',OpaqueMulti call)
   where
+    labelNotAllowedError (Just l) = compilerErrorM $ "Arg label " ++ show l ++ " not allowed when forwarding multiple returns"
+    labelNotAllowedError _ = return ()
+    labelNotSetError (Just l) = compilerErrorM $ "Arg label " ++ show l ++ " cannot be set when forwarding multiple returns"
+    labelNotSetError _ = return ()
     replaceSelfParam self (AssignedInstance c2 t) = do
       t' <- replaceSelfInstance self t
       return $ AssignedInstance c2 t'
     replaceSelfParam _ t = return t
     message = "In call to " ++ show (sfName f) ++ " at " ++ formatFullContext c
-    backgroundMessage (n,(InferredInstance c2),t) =
-      compilerBackgroundM $ "Parameter " ++ show n ++ " (from " ++ show (sfType f) ++ "." ++
-        show (sfName f) ++ ") inferred as " ++ show t ++ " at " ++ formatFullContext c2
+    backgroundMessage (n,(InferredInstance c2),t) = do
+      let funcName = functionDebugName (sfType f) f
+      compilerBackgroundM $ "Parameter " ++ show n ++ " (from " ++ funcName ++
+        ") inferred as " ++ show t ++ " at " ++ formatFullContext c2
     backgroundMessage _ = return ()
     getParamsArgs ps2 paramEs argEs = do
       psNames <- lift $ collectParamNames ps2
-      asNames <- lift $ collectArgNames $ pValues es
+      asNames <- lift $ collectArgNames $ map snd $ pValues es
       canForward <- case (psNames,asNames) of
                          (Just pn,Just an) -> csCanForward pn an
                          _                 -> return False
@@ -1054,11 +1158,14 @@
     assemble Nothing scoped _ _ paramsArgs =
       return $ scoped ++ callName (sfName f) ++ "(" ++ paramsArgs ++ ")"
     assemble (Just e2) _ _ ValueScope paramsArgs =
-      return $ valueBase ++ "::Call(" ++ e2 ++ ", " ++ functionName f ++ ", " ++ paramsArgs ++ ")"
+      if optionalValue
+         then return $ "TYPE_VALUE_CALL_UNLESS_EMPTY(" ++ e2 ++ ", " ++ functionName f ++ ", " ++ paramsArgs ++ ", " ++ show returnCount ++ ")"
+         else return $ valueBase ++ "::Call(" ++ e2 ++ ", " ++ functionName f ++ ", " ++ paramsArgs ++ ")"
     assemble (Just e2) _ _ TypeScope paramsArgs =
       return $ typeBase ++ "::Call(" ++ e2 ++ ", " ++ functionName f ++ ", " ++ paramsArgs ++ ")"
     assemble (Just e2) _ _ _ paramsArgs =
       return $ e2 ++ ".Call(" ++ functionName f ++ ", " ++ paramsArgs ++ ")"
+    returnCount = length $ pValues $ sfReturns f
     -- TODO: Lots of duplication with assignments and initialization.
     -- Single expression, but possibly multi-return.
     getValues [(Positional ts,e2)] = return (ts,[useAsArgs e2])
@@ -1072,6 +1179,16 @@
       compilerErrorM $ "Return position " ++ show i ++ " has " ++ show (length ts) ++ " values but should have 1"
     checkArg r fa t0 (i,t1) = do
       checkValueAssignment r fa t1 t0 <?? "In argument " ++ show i ++ " to " ++ show (sfName f)
+    checkArgLabel (Just (CallArgLabel _ n1)) (_,Just (CallArgLabel _ n2))
+      | n1 == n2 = return ()
+    checkArgLabel l1 (i,l2) = "In argument " ++ show i ++ " to " ++ show (sfName f) ??> labelError l1 l2
+    labelError (Just l1) (Just l2) =
+      compilerErrorM $ "Expected arg label " ++ show l1 ++ " but got " ++ show l2
+    labelError (Just l1) _ =
+      compilerErrorM $ "Expected arg label " ++ show l1 ++ " but label is missing"
+    labelError _ (Just l2) =
+      compilerErrorM $ "Expected no arg label but got " ++ show l2
+    labelError _ _ = return ()
     collectParamNames = fmap sequence . mapCompilerM collectParamName
     collectParamName = fmap getParamName . tryCompilerM . matchOnlyLeaf
     getParamName (Just (JustParamName _ n)) = Just n
@@ -1087,8 +1204,8 @@
   Positional ValueType -> m (Positional GeneralInstance)
 guessParamsFromArgs r fa f ps ts = do
   fm <- getFunctionFilterMap f
-  args <- processPairs (\t1 t2 -> return $ TypePattern Covariant t1 t2) ts (fmap pvType $ sfArgs f)
-  filts <- fmap concat $ processPairs (guessesFromFilters fm) ts (fmap pvType $ sfArgs f)
+  args <- processPairs (\t1 t2 -> return $ TypePattern Covariant t1 t2) ts (fmap (pvType . fst) $ sfArgs f)
+  filts <- fmap concat $ processPairs (guessesFromFilters fm) ts (fmap (pvType . fst) $ sfArgs f)
   pa <- fmap Map.fromList $ processPairs toInstance (fmap vpParam $ sfParams f) ps
   gs <- inferParamTypes r fa pa (args ++ filts)
   gs' <- mergeInferredTypes r fa fm pa gs
@@ -1096,7 +1213,10 @@
   fmap Positional $ mapCompilerM (subPosition pa3) (pValues $ sfParams f) where
     subPosition pa2 p =
       case (vpParam p) `Map.lookup` pa2 of
-           Just t  -> return t
+           Just t  -> if not (hasInferredParams t)
+                         then return t
+                         else compilerErrorM $ "Could not infer param " ++
+                              show (vpParam p) ++ formatFullContextBrace (vpContext p)
            Nothing -> compilerErrorM $ "Something went wrong inferring " ++
                       show (vpParam p) ++ formatFullContextBrace (vpContext p)
     toInstance p1 (AssignedInstance _ t) = return (p1,t)
@@ -1122,17 +1242,39 @@
 compileMainProcedure :: (Ord c, Show c, CollectErrorsM m) =>
   CategoryMap c -> ExprMap c -> Expression c -> m (CompiledData [String])
 compileMainProcedure tm em e = do
-  ctx <- getMainContext tm em
+  ctx <- getMainContext False tm em
   runDataCompiler compiler ctx where
     procedure = Procedure [] [IgnoreValues [] e]
     compiler = do
       ctx0 <- getCleanContext
       compileProcedure ctx0 procedure >>= put
 
+compileWrapTestcase :: (Ord c, Show c, CollectErrorsM m) =>
+  CategoryMap c -> ([c],TypeInstance) -> m (CompiledData [String])
+compileWrapTestcase tm (c,t) = do
+  ctx <- getMainContext False tm Map.empty
+  runDataCompiler compiler ctx <??
+    "In custom testcase checker at " ++ formatFullContext c
+  where
+    compiler = do
+      ctx0 <- getCleanContext
+      lift (execStateT testcase ctx0) >>= put
+    testcase = do
+      let t2 = singleType $ JustTypeInstance t
+      r <- csResolver
+      lift $ validateGeneralInstanceForCall r Map.empty t2
+      lift $ validateAssignment r Map.empty t2 [DefinesFilter (DefinesInstance BuiltinTestcase (Positional []))]
+      start <- csGetTypeFunction c (Just t2) (FunctionName "start")
+      finish <- csGetTypeFunction c (Just t2) (FunctionName "finish")
+      csAddRequired $ Set.fromList [sfType start,sfType finish]
+      csAddRequired $ categoriesFromTypes t2
+      t2' <- expandGeneralInstance t2
+      csWrite ["WrapTypeCall check_test(" ++ t2' ++ ", &" ++ functionName start ++ ", &" ++ functionName finish ++ ");"]
+
 compileTestProcedure :: (Ord c, Show c, CollectErrorsM m) =>
   CategoryMap c -> ExprMap c -> TestProcedure c -> m (CompiledData [String])
 compileTestProcedure tm em (TestProcedure c n cov p) = do
-  ctx <- getMainContext tm em
+  ctx <- getMainContext True tm em
   p' <- runDataCompiler compiler ctx <??
     "In unittest " ++ show n ++ formatFullContextBrace c
   return $ mconcat [
diff --git a/src/Parser/Common.hs b/src/Parser/Common.hs
--- a/src/Parser/Common.hs
+++ b/src/Parser/Common.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -21,6 +21,7 @@
 module Parser.Common (
   ParseFromSource(..),
   anyComment,
+  assignEmptyOperator,
   assignOperator,
   blockComment,
   builtinValues,
@@ -43,6 +44,7 @@
   kwDefer,
   kwDefine,
   kwDefines,
+  kwDelegate,
   kwElif,
   kwElse,
   kwEmpty,
@@ -51,8 +53,9 @@
   kwFalse,
   kwIf,
   kwIgnore,
-  kwIn,
   kwImmutable,
+  kwIn,
+  kwIdentify,
   kwInterface,
   kwOptional,
   kwPresent,
@@ -72,6 +75,7 @@
   kwUnittest,
   kwUpdate,
   kwValue,
+  kwVisibility,
   kwWeak,
   kwWhile,
   labeled,
@@ -92,7 +96,6 @@
   parseDec,
   parseHex,
   parseOct,
-  parseSubOne,
   pragmaArgsEnd,
   pragmaArgsStart,
   pragmaEnd,
@@ -110,8 +113,10 @@
   statementStart,
   stringChar,
   string_,
+  swapOperator,
   typeSymbolGet,
   valueSymbolGet,
+  valueSymbolMaybeGet,
 ) where
 
 import Data.Char
@@ -145,6 +150,9 @@
 valueSymbolGet :: TextParser ()
 valueSymbolGet = sepAfter (string_ ".")
 
+valueSymbolMaybeGet :: TextParser ()
+valueSymbolMaybeGet = sepAfter (string_ "&.")
+
 categorySymbolGet :: TextParser ()
 categorySymbolGet = labeled ":" $ useNewOperators <|> sepAfter (string_ ":")
 
@@ -164,6 +172,12 @@
 assignOperator :: TextParser ()
 assignOperator = operator "<-" >> return ()
 
+assignEmptyOperator :: TextParser ()
+assignEmptyOperator = operator "<-|" >> return ()
+
+swapOperator :: TextParser ()
+swapOperator = operator "<->" >> return ()
+
 infixFuncStart :: TextParser ()
 infixFuncStart = sepAfter (string_ "`")
 
@@ -209,6 +223,9 @@
 kwDefines :: TextParser ()
 kwDefines = keyword "defines"
 
+kwDelegate :: TextParser ()
+kwDelegate = keyword "delegate"
+
 kwElif :: TextParser ()
 kwElif = keyword "elif"
 
@@ -230,15 +247,18 @@
 kwIf :: TextParser ()
 kwIf = keyword "if"
 
-kwIn :: TextParser ()
-kwIn = keyword "in"
-
 kwIgnore :: TextParser ()
 kwIgnore = keyword "_"
 
 kwImmutable :: TextParser ()
 kwImmutable = keyword "immutable"
 
+kwIn :: TextParser ()
+kwIn = keyword "in"
+
+kwIdentify :: TextParser ()
+kwIdentify = keyword "identify"
+
 kwInterface :: TextParser ()
 kwInterface = keyword "interface"
 
@@ -296,6 +316,9 @@
 kwValue :: TextParser ()
 kwValue = keyword "@value"
 
+kwVisibility :: TextParser ()
+kwVisibility = keyword "visibility"
+
 kwWeak :: TextParser ()
 kwWeak = keyword "weak"
 
@@ -330,6 +353,7 @@
     kwDefer,
     kwDefine,
     kwDefines,
+    kwDelegate,
     kwElif,
     kwElse,
     kwEmpty,
@@ -337,9 +361,10 @@
     kwFail,
     kwFalse,
     kwIf,
-    kwIn,
     kwIgnore,
     kwImmutable,
+    kwIn,
+    kwIdentify,
     kwInterface,
     kwOptional,
     kwPresent,
@@ -359,6 +384,7 @@
     kwUnittest,
     kwUpdate,
     kwValue,
+    kwVisibility,
     kwWeak,
     kwWhile
   ]
@@ -433,7 +459,7 @@
 operator :: String -> TextParser String
 operator o = labeled o $ do
   string_ o
-  notFollowedBy operatorSymbol
+  try anyComment <|> notFollowedBy operatorSymbol
   optionalSpace
   return o
 
@@ -481,20 +507,17 @@
   | c >= 'a' && c <= 'f' = 10 + ord(c) - ord('a')
   | otherwise = undefined
 
-parseDec :: TextParser Integer
-parseDec = labeled "base-10" $ fmap snd $ parseIntCommon 10 digitChar
-
-parseHex :: TextParser Integer
-parseHex = labeled "base-16" $ fmap snd $ parseIntCommon 16 hexDigitChar
+parseDec :: TextParser (Integer,Integer)
+parseDec = labeled "base-10" $ parseIntCommon 10 digitChar
 
-parseOct :: TextParser Integer
-parseOct = labeled "base-8" $ fmap snd $ parseIntCommon 8 octDigitChar
+parseHex :: TextParser (Integer,Integer)
+parseHex = labeled "base-16" $ parseIntCommon 16 hexDigitChar
 
-parseBin :: TextParser Integer
-parseBin = labeled "base-2" $ fmap snd $ parseIntCommon 2 (oneOf "01")
+parseOct :: TextParser (Integer,Integer)
+parseOct = labeled "base-8" $ parseIntCommon 8 octDigitChar
 
-parseSubOne :: TextParser (Integer,Integer)
-parseSubOne = parseIntCommon 10 digitChar
+parseBin :: TextParser (Integer,Integer)
+parseBin = labeled "base-2" $ parseIntCommon 2 (oneOf "01")
 
 parseIntCommon :: Integer -> TextParser Char -> TextParser (Integer,Integer)
 parseIntCommon b p = do
diff --git a/src/Parser/IntegrationTest.hs b/src/Parser/IntegrationTest.hs
--- a/src/Parser/IntegrationTest.hs
+++ b/src/Parser/IntegrationTest.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
+Copyright 2020,2023 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.
@@ -28,6 +28,7 @@
 import Parser.Procedure ()
 import Parser.TextParser
 import Parser.TypeCategory ()
+import Parser.TypeInstance ()
 import Types.IntegrationTest
 
 
@@ -56,20 +57,26 @@
         c <- getSourceContext
         keyword "error"
         return $ ExpectCompilerError [c]
-      resultCrash = labeled "crash expectation" $ do
+      resultCrash = labeled "failure expectation" $ do
         c <- getSourceContext
-        keyword "crash"
-        return $ ExpectRuntimeError [c]
+        keyword "failure"
+        t <- fmap Just parseTestcaseType <|> return Nothing
+        return $ ExpectRuntimeError [c] t
       resultSuccess = labeled "success expectation" $ do
         c <- getSourceContext
         keyword "success"
-        return $ ExpectRuntimeSuccess [c]
+        t <- fmap Just parseTestcaseType <|> return Nothing
+        return $ ExpectRuntimeSuccess [c] t
+      parseTestcaseType = do
+        c <- getSourceContext
+        t <- sourceParser
+        return ([c],t)
       parseArgs = labeled "testcase args" $ do
         keyword "args"
         many (sepAfter quotedString)
       parseTimeout = labeled "testcase timeout" $ do
         keyword "timeout"
-        fmap Just (sepAfter parseDec)
+        fmap (Just . snd) (sepAfter parseDec)
       requireOrExclude = parseAny2 require exclude where
         require = do
           keyword "require"
diff --git a/src/Parser/Procedure.hs b/src/Parser/Procedure.hs
--- a/src/Parser/Procedure.hs
+++ b/src/Parser/Procedure.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -133,14 +133,32 @@
                  parseFailCall <|>
                  parseExitCall <|>
                  parseVoid <|>
+                 parseSwap <|>
                  parseAssign <|>
                  parsePragma <|>
                  parseIgnore where
     parseAssign = labeled "assignment" $ do
       c <- getSourceContext
       as <- sepBy sourceParser (sepAfter $ string_ ",")
+      case as of
+           [ExistingVariable (InputValue _ n)] -> parseAssignEmpty c n <|> parseAlwaysAssign c as
+           _ -> parseAlwaysAssign c as
+    parseAssignEmpty c n = do
+      assignEmptyOperator
+      e <- sourceParser
+      statementEnd
+      return $ AssignmentEmpty [c] n e
+    parseAlwaysAssign c as = do
       assignOperator
       assignExpr c as <|> assignDefer c as
+    parseSwap = labeled "swap" $ do
+      c <- getSourceContext
+      l <- try $ do
+        var <- sourceParser
+        swapOperator
+        return var
+      r <- sourceParser
+      return $ VariableSwap [c] l r
     assignExpr c as = do
       e <- sourceParser
       statementEnd
@@ -411,7 +429,7 @@
     asInfix [e] [] <|> return e
     where
       -- NOTE: InitializeValue is parsed as ExpressionStart.
-      notInfix = literal <|> try unaryBuiltin <|> unary <|> expression
+      notInfix = literal <|> try unaryBuiltin <|> unary <|> delegatedCall <|> expression
       asInfix es os = do
         c <- getSourceContext
         o <- infixOperator <|> functionOperator
@@ -443,16 +461,30 @@
                             (sepBy sourceParser (sepAfter $ string_ ",")) <|> return []
         infixFuncEnd
         e <- notInfix
-        return $ Expression [c] (BuiltinCall [c] $ FunctionCall [c] n (Positional ps) (Positional [e])) []
+        return $ Expression [c] (BuiltinCall [c] $ FunctionCall [c] n (Positional ps) (Positional [(Nothing,e)])) []
       unary = do
         c <- getSourceContext
         o <- unaryOperator <|> functionOperator
         e <- notInfix
         return $ UnaryExpression [c] o e
+      delegatedCall = do
+        kwDelegate
+        sepAfter_ $ string "->"
+        try delegateCall <|> delegateInit
+      delegateInit = do
+        c <- getSourceContext
+        t <- (paramSelf >> return Nothing) <|> fmap Just sourceParser
+        return $ DelegatedInitializeValue [c] t
+      delegateCall = do
+        c <- getSourceContext
+        infixFuncStart
+        f <- sourceParser
+        infixFuncEnd
+        return $ DelegatedFunctionCall [c] f
       expression = labeled "expression" $ do
         c <- getSourceContext
         s <- sourceParser
-        vs <- many sourceParser
+        vs <- many (try sourceParser)
         return $ Expression [c] s vs
 
 instance ParseFromSource (FunctionQualifier SourceContext) where
@@ -514,24 +546,30 @@
                       (sepBy sourceParser (sepAfter $ string_ ",")) <|> return []
   es <- between (sepAfter $ string_ "(")
                 (sepAfter $ string_ ")")
-                (sepBy sourceParser (sepAfter $ string_ ","))
-  return $ FunctionCall [c] n (Positional ps) (Positional es)
+                (sepBy parseArg (sepAfter $ string_ ","))
+  return $ FunctionCall [c] n (Positional ps) (Positional es) where
+    parseArg = do
+      l <- try (fmap Just sourceParser) <|> return Nothing
+      e <- sourceParser
+      return (l,e)
 
 builtinFunction :: TextParser FunctionName
 builtinFunction = foldr (<|>) empty $ map try [
-    kwPresent >> return BuiltinPresent,
-    kwReduce >> return BuiltinReduce,
-    kwRequire >> return BuiltinRequire,
-    kwStrong >> return BuiltinStrong,
+    kwPresent  >> return BuiltinPresent,
+    kwReduce   >> return BuiltinReduce,
+    kwRequire  >> return BuiltinRequire,
+    kwStrong   >> return BuiltinStrong,
+    kwIdentify >> return BuiltinIdentify,
     kwTypename >> return BuiltinTypename
   ]
 
 builtinUnary :: TextParser FunctionName
 builtinUnary = foldr (<|>) empty $ map try [
-    kwPresent >> return BuiltinPresent,
-    kwReduce >> return BuiltinReduce,
-    kwRequire >> return BuiltinRequire,
-    kwStrong >> return BuiltinStrong
+    kwPresent  >> return BuiltinPresent,
+    kwReduce   >> return BuiltinReduce,
+    kwRequire  >> return BuiltinRequire,
+    kwStrong   >> return BuiltinStrong,
+    kwIdentify >> return BuiltinIdentify
   ]
 
 instance ParseFromSource (ExpressionStart SourceContext) where
@@ -542,13 +580,15 @@
                  builtinValue <|>
                  sourceContext <|>
                  exprLookup <|>
+                 exprMacro <|>
                  categoryCall <|>
                  -- Keep this before typeCall, since it does a look-ahead for {.
                  initalize <|>
                  typeCall <|>
                  stringLiteral <|>
                  charLiteral <|>
-                 boolLiteral where
+                 boolLiteral <|>
+                 emptyLiteral where
     parens = do
       c <- getSourceContext
       sepAfter (string_ "(")
@@ -558,9 +598,9 @@
     assign :: SourceContext -> TextParser (ExpressionStart SourceContext)
     assign c = do
       n <- sourceParser
-      assignOperator
+      o <- (assignEmptyOperator >> return AssignIfEmpty) <|> (assignOperator >> return AlwaysAssign)
       e <- sourceParser
-      return $ InlineAssignment [c] n e
+      return $ InlineAssignment [c] n o e
     expr :: SourceContext -> TextParser (ExpressionStart SourceContext)
     expr c = do
       e <- sourceParser
@@ -584,6 +624,9 @@
       case pragma of
            (PragmaExprLookup c name) -> return $ NamedMacro c name
            _ -> undefined  -- Should be caught above.
+    exprMacro = do
+      (c,macro) <- macroExpression
+      return $ ExpressionMacro [c] macro
     variableOrUnqualified = do
       c <- getSourceContext
       n <- sourceParser :: TextParser VariableName
@@ -634,61 +677,62 @@
       c <- getSourceContext
       b <- try $ (kwTrue >> return True) <|> (kwFalse >> return False)
       return $ UnambiguousLiteral $ BoolLiteral [c] b
+    emptyLiteral = do
+      c <- getSourceContext
+      kwEmpty
+      return $ UnambiguousLiteral $ EmptyLiteral [c]
 
 instance ParseFromSource (ValueLiteral SourceContext) where
   sourceParser = labeled "literal" $
-                 -- NOTE: StringLiteral, CharLiteral, and BoolLiteral are parsed
-                 -- as ExpressionStart.
-                 escapedInteger <|>
-                 integerOrDecimal <|>
-                 emptyLiteral where
-    escapedInteger = do
+                 -- NOTE: StringLiteral, CharLiteral, BoolLiteral , and
+                 -- EmptyLiteral are parsed as ExpressionStart.
+                 escapedIntegerOrDecimal <|>
+                 integerOrDecimal where
+    escapedIntegerOrDecimal = do
       c <- getSourceContext
       escapeStart
       b <- oneOf "bBoOdDxX"
-      d <- case b of
-               'b' -> parseBin
-               'B' -> parseBin
-               'o' -> parseOct
-               'O' -> parseOct
-               'd' -> parseDec
-               'D' -> parseDec
-               'x' -> parseHex
-               'X' -> parseHex
-               _ -> undefined
-      optionalSpace
-      return $ IntegerLiteral [c] True d
+      let (digitParser,base) = case b of
+                                    'b' -> (parseBin,2)
+                                    'B' -> (parseBin,2)
+                                    'o' -> (parseOct,8)
+                                    'O' -> (parseOct,8)
+                                    'd' -> (parseDec,10)
+                                    'D' -> (parseDec,10)
+                                    'x' -> (parseHex,16)
+                                    'X' -> (parseHex,16)
+                                    _ -> undefined
+      d <- fmap snd digitParser
+      decimal c d False base digitParser <|> integer c d True
     integerOrDecimal = do
       c <- getSourceContext
-      d <- parseDec
-      decimal c d <|> integer c d
-    decimal c d = do
+      d <- fmap snd parseDec
+      decimal c d True 10 parseDec <|> integer c d False
+    decimal c d allowExp base digitParser = do
       char_ '.'
-      (n,d2) <- parseSubOne
-      e <- decExponent <|> return 0
+      (n,d2) <- digitParser
+      e <- if allowExp
+              then decExponent <|> return 0
+              else return 0
       optionalSpace
-      return $ DecimalLiteral [c] (d*10^n + d2) (e - n)
+      return $ DecimalLiteral [c] (d*base^n + d2) (e - n) base
     decExponent = do
       string_ "e" <|> string_ "E"
       s <- (string_ "+" >> return 1) <|> (string_ "-" >> return (-1)) <|> return 1
-      e <- parseDec
+      e <- fmap snd parseDec
       return (s*e)
-    integer c d = do
+    integer c d unsigned = do
       optionalSpace
-      return $ IntegerLiteral [c] False d
-    emptyLiteral = do
-      c <- getSourceContext
-      kwEmpty
-      return $ EmptyLiteral [c]
+      return $ IntegerLiteral [c] unsigned d
 
 instance ParseFromSource (ValueOperation SourceContext) where
-  sourceParser = try valueCall <|> try conversion <|> selectReturn where
+  sourceParser = valueCall <|> conversion <|> selectReturn where
     valueCall = labeled "function call" $ do
       c <- getSourceContext
-      valueSymbolGet
+      o <- (valueSymbolGet >> return AlwaysCall) <|> (valueSymbolMaybeGet >> return CallUnlessEmpty)
       n <- sourceParser
       f <- parseFunctionCall c n
-      return $ ValueCall [c] f
+      return $ ValueCall [c] o f
     conversion = labeled "type conversion" $ do
       c <- getSourceContext
       inferredParam
@@ -697,7 +741,7 @@
     selectReturn = labeled "return selection" $ do
       c <- getSourceContext
       sepAfter_ (string_ "{")
-      pos <- labeled "return position" parseDec
+      pos <- labeled "return position" $ fmap snd parseDec
       sepAfter_ (string_ "}")
       return $ SelectReturn [c] (fromIntegral pos)
 
@@ -735,6 +779,10 @@
 pragmaSourceContext :: TextParser (PragmaExpr SourceContext)
 pragmaSourceContext = autoPragma "SourceContext" $ Left parseAt where
   parseAt c = PragmaSourceContext c
+
+macroExpression :: TextParser (SourceContext,MacroExpression)
+macroExpression = callTrace where
+  callTrace = autoPragma "CallTrace" $ Left (flip (,) MacroCallTrace)
 
 data MarkType = ReadOnly | Hidden deriving (Show)
 
diff --git a/src/Parser/TypeCategory.hs b/src/Parser/TypeCategory.hs
--- a/src/Parser/TypeCategory.hs
+++ b/src/Parser/TypeCategory.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019,2021 Kevin P. Barry
+Copyright 2019,2021,2023 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.
@@ -69,9 +69,9 @@
       open
       pg <- pragmas
       (rs,ds,vs) <- parseRefinesDefinesFilters
-      fs <- flip sepBy optionalSpace $ parseScopedFunction parseScope (return n)
+      (fs,fv) <- parseFunctionsWithVisibility $ parseScopedFunction parseScope (return n)
       close
-      return $ ValueConcrete [c] NoNamespace n pg ps rs ds vs fs
+      return $ ValueConcrete [c] NoNamespace n pg fv ps rs ds vs fs
     pragmas = fmap (:[]) immutable <|> return []
     immutable = do
       c <- getSourceContext
@@ -161,6 +161,43 @@
     e <- sepAfter $ many alphaNumChar
     return $ FunctionName (b:e)
 
+instance ParseFromSource (CallArgLabel SourceContext) where
+  sourceParser = labeled "arg label" $ do
+    c <- getSourceContext
+    b <- lowerChar
+    e <- many alphaNumChar
+    sepAfter $ char_ ':'
+    return $ CallArgLabel [c] (b:e ++ ":")
+
+instance ParseFromSource (FunctionVisibility SourceContext) where
+  sourceParser = labeled "visibility" $ do
+    c <- getSourceContext
+    kwVisibility
+    visDefault <|> visTypes c where
+      visDefault = do
+        kwIgnore
+        return $ FunctionVisibilityDefault
+      visTypes c = do
+        ts <- sepBy1 singleType (sepAfter $ string ",")
+        return $ FunctionVisibility [c] ts
+      singleType = do
+        c <- getSourceContext
+        t <- sourceParser
+        return ([c],t)
+
+parseFunctionsWithVisibility :: TextParser (ScopedFunction SourceContext) ->
+  TextParser ([ScopedFunction SourceContext],[FunctionVisibility SourceContext])
+parseFunctionsWithVisibility func = labeled "visibility" $ sepBy anyType optionalSpace >>= merge FunctionVisibilityDefault where
+  anyType = fmap Left func <|> fmap Right sourceParser
+  merge v0 (Left f:ps) = do
+    (fs,vs) <- merge v0 ps
+    return (setVis v0 f:fs,vs)
+  merge _ (Right v:ps) = do
+    (fs,vs) <- merge v ps
+    return (fs,v:vs)
+  merge _ _ = return ([],[])
+  setVis v (ScopedFunction c n t s _ as rs ps fs ms) = (ScopedFunction c n t s v as rs ps fs ms)
+
 parseScopedFunction ::
   TextParser SymbolScope -> TextParser CategoryName -> TextParser (ScopedFunction SourceContext)
 parseScopedFunction sp tp = labeled "function" $ do
@@ -168,10 +205,10 @@
   (s,t,n) <- try parseName
   ps <- fmap Positional $ noParams <|> someParams
   fa <- parseFilters
-  as <- fmap Positional $ typeList "argument type"
+  as <- fmap Positional $ typeList "arg label" singleArg
   sepAfter_ (string "->")
-  rs <- fmap Positional $ typeList "return type"
-  return $ ScopedFunction [c] n t s as rs ps fa []
+  rs <- fmap Positional $ typeList "return type" singleReturn
+  return $ ScopedFunction [c] n t s FunctionVisibilityDefault as rs ps fa []
   where
     parseName = do
       s <- sp -- Could be a constant, i.e., nothing consumed.
@@ -187,10 +224,16 @@
       c <- getSourceContext
       n <- sourceParser
       return $ ValueParam [c] n Invariant
-    typeList l = between (sepAfter $ string_ "(")
-                         (sepAfter $ string_ ")")
-                         (sepBy (labeled l $ singleType) (sepAfter $ string ","))
-    singleType = do
+    typeList l parseVal = between (sepAfter $ string_ "(")
+                                  (sepAfter $ string_ ")")
+                                  (sepBy (labeled l $ parseVal) (sepAfter $ string ","))
+    singleArg = do
+      c <- getSourceContext
+      t <- sourceParser
+      optionalSpace
+      n <- fmap Just sourceParser <|> return Nothing
+      return $ (PassedValue [c] t,n)
+    singleReturn = do
       c <- getSourceContext
       t <- sourceParser
       return $ PassedValue [c] t
diff --git a/src/Parser/TypeInstance.hs b/src/Parser/TypeInstance.hs
--- a/src/Parser/TypeInstance.hs
+++ b/src/Parser/TypeInstance.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2020,2022 Kevin P. Barry
+Copyright 2019-2020,2022-2023 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.
@@ -82,7 +82,10 @@
         | n == "Float"      = BuiltinFloat
         | n == "String"     = BuiltinString
         | n == "Pointer"    = BuiltinPointer
+        | n == "Identifier" = BuiltinIdentifier
         | n == "Formatted"  = BuiltinFormatted
+        | n == "Order"      = BuiltinOrder
+        | n == "Testcase"   = BuiltinTestcase
         | otherwise = CategoryName n
 
 instance ParseFromSource ParamName where
diff --git a/src/Test/ParseMetadata.hs b/src/Test/ParseMetadata.hs
--- a/src/Test/ParseMetadata.hs
+++ b/src/Test/ParseMetadata.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2020-2021 Kevin P. Barry
+Copyright 2020-2021,2023 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.
@@ -427,12 +427,12 @@
                   [(MacroName "MY_MACRO",
                     Expression _ (BuiltinCall _
                       (FunctionCall _ BuiltinRequire (Positional [])
-                        (Positional [Literal (EmptyLiteral _)]))) []),
+                        (Positional [(Nothing,Expression _ (UnambiguousLiteral (EmptyLiteral _)) [])]))) []),
                    (MacroName "MY_OTHER_MACRO",
                     Expression _
                       (TypeCall _ _
                         (FunctionCall _ (FunctionName "execute") (Positional [])
-                        (Positional [Expression _ (UnambiguousLiteral (StringLiteral _ "this is a string\n")) []]))) [])
+                        (Positional [(Nothing,Expression _ (UnambiguousLiteral (StringLiteral _ "this is a string\n")) [])]))) [])
                     ] -> True
                   _ -> False),
 
diff --git a/src/Test/Procedure.hs b/src/Test/Procedure.hs
--- a/src/Test/Procedure.hs
+++ b/src/Test/Procedure.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -83,8 +83,11 @@
     checkShortParseSuccess "\\ (false).call()",
     checkShortParseSuccess "\\ false.call()",
     checkShortParseFail "\\ 1.call()",
-    checkShortParseFail "\\ empty.call()",
+    checkShortParseSuccess "\\ empty.call()",
 
+    checkShortParseSuccess "\\ foo(arg1: 123)",
+    checkShortParseFail "\\ foo(arg1 : 123)",
+
     checkShortParseSuccess "x <- var.func()",
     checkShortParseFail "x <- var.func() var.func()",
     checkShortParseFail "x <- y <- var.func()",
@@ -113,6 +116,19 @@
     checkShortParseSuccess "_, weak T<#x> x <- var.func()",
     checkShortParseFail "_, weak T<#x> x <- T<#x> x",
 
+    checkShortParseSuccess "a <-> b",
+    checkShortParseFail "self <-> b",
+    checkShortParseFail "a <-> self",
+    checkShortParseFail "_ <-> b",
+    checkShortParseFail "a <-> _",
+    checkShortParseFail "\\ a <-> b",
+    checkShortParseFail "a, b <-> c",
+    checkShortParseFail "a <-> b, c",
+    checkShortParseFail "#a <-> b",
+    checkShortParseFail "a <-> #b",
+    checkShortParseFail "a.foo() <-> b",
+    checkShortParseFail "a <-> b.foo()",
+
     checkShortParseSuccess "if (var.func()) { \\ val.call() }",
     checkShortParseSuccess "if (present(var)) { \\ val.call() }",
     checkShortParseFail "if (T<#x> x) { \\ val.call() }",
@@ -190,6 +206,15 @@
     checkShortParseSuccess "\\ ~x | ~y",
     checkShortParseSuccess "\\ ~x ^ ~y",
 
+    checkShortParseSuccess "\\ a+b",
+    checkShortParseFail "\\ a++b",
+    checkShortParseFail "\\ a+/b",
+    checkShortParseFail "\\ a/=b",
+    checkShortParseSuccess "\\ a+/*c*//*c*/d",
+    checkShortParseSuccess "\\ a+//c\nd",
+    checkShortParseSuccess "\\ a/*c*//*c*/+d",
+    checkShortParseSuccess "\\ a//c\n+d",
+
     checkShortParseSuccess "x <- y + z",
     checkShortParseSuccess "x <- !y == !z",
     checkShortParseSuccess "x <- (x + y) / z",
@@ -207,7 +232,6 @@
     checkShortParseSuccess "x <- \\x123aBc + \\x123aBc",
     checkShortParseFail "x <- \\x123aQc",
     checkShortParseFail "x <- \\x",
-    checkShortParseFail "x <- \\x1.2",
     checkShortParseSuccess "x <- \" return \\\"\\\" \" + \"1fds\"",
     checkShortParseFail "x <- \"fsdfd",
     checkShortParseFail "x <- \"\"fsdfd",
@@ -242,6 +266,28 @@
     checkShortParseSuccess "x <- `reduce<#x,#y>` y",
     checkShortParseFail "x <- `typename<#y>` y",
 
+    checkShortParseSuccess "x <- 123.456",
+    checkShortParseSuccess "x <- \\d123.456",
+    checkShortParseFail "x <- 123.",
+    checkShortParseFail "x <- \\d123.",
+    checkShortParseSuccess "x <- 123.456E1",
+    checkShortParseFail "x <- \\d123.456E1",
+
+    checkShortParseSuccess "x <- \\b101.101",
+    checkShortParseFail "x <- \\b101.",
+    checkShortParseFail "x <- \\b101.101E1",
+
+    checkShortParseSuccess "x <- \\xABC.DEF",
+    checkShortParseFail "x <- \\xABC.",
+
+    checkShortParseSuccess "x <- \\o123.456",
+    checkShortParseFail "x <- \\o123.",
+    checkShortParseFail "x <- \\o123.456E1",
+
+    checkShortParseFail "Int x <-| 123",
+    checkShortParseFail "_ <-| 123",
+    checkShortParseFail "x, y <-| 123",
+
     checkParsesAs "'\"'"
                   (\e -> case e of
                               (Expression _ (UnambiguousLiteral (CharLiteral _ '"')) []) -> True
@@ -322,7 +368,7 @@
                                     (FunctionSpec _
                                       (ValueFunction _
                                         (Expression _ (BuiltinCall _ (FunctionCall _ BuiltinRequire (Positional [])
-                                          (Positional [Expression _ (NamedVariable (OutputValue _ (VariableName "x"))) []]))) []))
+                                          (Positional [(Nothing,Expression _ (NamedVariable (OutputValue _ (VariableName "x"))) [])]))) []))
                                         (FunctionName "foo") (Positional [])))
                                 (Literal (IntegerLiteral _ False 2)) -> True
                               _ -> False),
@@ -374,7 +420,7 @@
                                     (FunctionSpec _
                                       (ValueFunction _
                                         (Expression _ (BuiltinCall _ (FunctionCall _ BuiltinRequire (Positional [])
-                                          (Positional [Expression _ (NamedVariable (OutputValue _ (VariableName "x"))) []]))) []))
+                                          (Positional [(Nothing,Expression _ (NamedVariable (OutputValue _ (VariableName "x"))) [])]))) []))
                                         (FunctionName "not") (Positional [])))
                                 (Literal (IntegerLiteral _ False 2)) -> True
                               _ -> False),
@@ -424,15 +470,27 @@
                                    _ -> False),
 
     checkParsesAs "1.2345" (\e -> case e of
-                                       (Literal (DecimalLiteral _ 12345 (-4))) -> True
+                                       (Literal (DecimalLiteral _ 12345 (-4) 10)) -> True
                                        _ -> False),
 
     checkParsesAs "1.2345E+4" (\e -> case e of
-                                          (Literal (DecimalLiteral _ 12345 0)) -> True
+                                          (Literal (DecimalLiteral _ 12345 0 10)) -> True
                                           _ -> False),
 
     checkParsesAs "1.2345E-4" (\e -> case e of
-                                          (Literal (DecimalLiteral _ 12345 (-8))) -> True
+                                          (Literal (DecimalLiteral _ 12345 (-8) 10)) -> True
+                                          _ -> False),
+
+    checkParsesAs "\\xF.F" (\e -> case e of
+                                          (Literal (DecimalLiteral _ 255 (-1) 16)) -> True
+                                          _ -> False),
+
+    checkParsesAs "\\o7.7" (\e -> case e of
+                                          (Literal (DecimalLiteral _ 63 (-1) 8)) -> True
+                                          _ -> False),
+
+    checkParsesAs "\\b1.1" (\e -> case e of
+                                          (Literal (DecimalLiteral _ 3 (-1) 2)) -> True
                                           _ -> False),
 
     checkParseMatch "$NoTrace$" pragmaNoTrace
diff --git a/src/Test/TypeCategory.hs b/src/Test/TypeCategory.hs
--- a/src/Test/TypeCategory.hs
+++ b/src/Test/TypeCategory.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -20,8 +20,9 @@
 module Test.TypeCategory (tests) where
 
 import Control.Arrow
-import Control.Monad ((>=>))
+import Control.Monad ((>=>),when)
 import System.FilePath
+import Text.Regex.TDFA
 import qualified Data.Map as Map
 import qualified Data.Set as Set
 
@@ -32,7 +33,6 @@
 import Parser.TextParser (SourceContext)
 import Parser.TypeCategory ()
 import Test.Common
-import Types.Builtin
 import Types.TypeCategory
 import Types.TypeInstance
 import Types.Variance
@@ -84,67 +84,73 @@
     checkShortParseFail "concrete Type<#x> { #x defines #self }",
     checkShortParseFail "@value interface Type { call<#self> () -> () }",
 
-    checkOperationSuccess ("testfiles" </> "value_refines_value.0rx") (checkConnectedTypes defaultCategories),
-    checkOperationFail ("testfiles" </> "value_refines_instance.0rx") (checkConnectedTypes defaultCategories),
-    checkOperationFail ("testfiles" </> "value_refines_concrete.0rx") (checkConnectedTypes defaultCategories),
+    checkOperationSuccess ("testfiles" </> "value_refines_value.0rx") (checkConnectedTypes emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "value_refines_instance.0rx") (checkConnectedTypes emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "value_refines_concrete.0rx") (checkConnectedTypes emptyCategoryMap),
 
-    checkOperationSuccess ("testfiles" </> "concrete_refines_value.0rx") (checkConnectedTypes defaultCategories),
-    checkOperationFail ("testfiles" </> "concrete_refines_instance.0rx") (checkConnectedTypes defaultCategories),
-    checkOperationFail ("testfiles" </> "concrete_refines_concrete.0rx") (checkConnectedTypes defaultCategories),
-    checkOperationSuccess ("testfiles" </> "concrete_defines_instance.0rx") (checkConnectedTypes defaultCategories),
-    checkOperationFail ("testfiles" </> "concrete_defines_value.0rx") (checkConnectedTypes defaultCategories),
-    checkOperationFail ("testfiles" </> "concrete_defines_concrete.0rx") (checkConnectedTypes defaultCategories),
+    checkOperationSuccess ("testfiles" </> "concrete_refines_value.0rx") (checkConnectedTypes emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "concrete_refines_instance.0rx") (checkConnectedTypes emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "concrete_refines_concrete.0rx") (checkConnectedTypes emptyCategoryMap),
+    checkOperationSuccess ("testfiles" </> "concrete_defines_instance.0rx") (checkConnectedTypes emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "concrete_defines_value.0rx") (checkConnectedTypes emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "concrete_defines_concrete.0rx") (checkConnectedTypes emptyCategoryMap),
 
     checkOperationSuccess
       ("testfiles" </> "concrete_refines_value.0rx")
-      (checkConnectedTypes $ Map.fromList [
+      (checkConnectedTypes $ toCategoryMap [
           (CategoryName "Parent2",InstanceInterface [] NoNamespace (CategoryName "Parent2") [] [] [])
         ]),
     checkOperationFail
       ("testfiles" </> "concrete_refines_value.0rx")
-      (checkConnectedTypes $ Map.fromList [
+      (checkConnectedTypes $ toCategoryMap [
           (CategoryName "Parent",InstanceInterface [] NoNamespace (CategoryName "Parent") [] [] [])
         ]),
 
     checkOperationSuccess
       ("testfiles" </> "partial.0rx")
-      (checkConnectedTypes $ Map.fromList [
+      (checkConnectedTypes $ toCategoryMap [
           (CategoryName "Parent",ValueInterface [] NoNamespace (CategoryName "Parent") [] [] [] [])
         ]),
     checkOperationFail
       ("testfiles" </> "partial.0rx")
-      (checkConnectedTypes $ Map.fromList [
+      (checkConnectedTypes $ toCategoryMap [
           (CategoryName "Parent",InstanceInterface [] NoNamespace (CategoryName "Parent") [] [] [])
         ]),
     checkOperationFail
       ("testfiles" </> "partial.0rx")
-      (checkConnectedTypes $ Map.fromList [
-          (CategoryName "Parent",ValueConcrete [] NoNamespace (CategoryName "Parent") [] [] [] [] [] [])
+      (checkConnectedTypes $ toCategoryMap [
+          (CategoryName "Parent",ValueConcrete [] NoNamespace (CategoryName "Parent") [] [] [] [] [] [] [])
         ]),
+    checkOperationFailWith "Parent.+not found"
+      ("testfiles" </> "partial.0rx")
+      (checkConnectedTypes emptyCategoryMap),
+    checkOperationFailWith "Parent.+not visible"
+      ("testfiles" </> "partial.0rx")
+      (checkConnectedTypes $ CategoryMap (Map.fromList [(CategoryName "Parent",[])]) Map.empty),
 
-    checkOperationSuccess ("testfiles" </> "value_refines_value.0rx") (checkConnectionCycles Map.empty),
-    checkOperationSuccess ("testfiles" </> "concrete_refines_value.0rx") (checkConnectionCycles Map.empty),
-    checkOperationSuccess ("testfiles" </> "concrete_defines_instance.0rx") (checkConnectionCycles Map.empty),
-    checkOperationFail ("testfiles" </> "value_cycle.0rx") (checkConnectionCycles Map.empty),
+    checkOperationSuccess ("testfiles" </> "value_refines_value.0rx") (checkConnectionCycles emptyCategoryMap),
+    checkOperationSuccess ("testfiles" </> "concrete_refines_value.0rx") (checkConnectionCycles emptyCategoryMap),
+    checkOperationSuccess ("testfiles" </> "concrete_defines_instance.0rx") (checkConnectionCycles emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "value_cycle.0rx") (checkConnectionCycles emptyCategoryMap),
 
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
+        ts2 <- topoSortCategories emptyCategoryMap ts
         map (show . getCategoryName) ts2 `containsPaired` [
             "Object2","Object3","Object1","Type","Parent","Child"
           ]),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2),
 
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         scrapeAllRefines ts3 `containsExactly` [
             ("Object1","Object3<#y>"),
             ("Object1","Object2"),
@@ -164,7 +170,7 @@
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        existing  <- return $ Map.fromList [
+        existing  <- return $ toCategoryMap [
             (CategoryName "Parent2",InstanceInterface [] NoNamespace (CategoryName "Parent2") [] [] [])
           ]
         ts2 <- topoSortCategories existing ts
@@ -172,7 +178,7 @@
     checkOperationFail
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        existing  <- return $ Map.fromList [
+        existing  <- return $ toCategoryMap [
             (CategoryName "Parent",InstanceInterface [] NoNamespace (CategoryName "Parent") [] [] [])
           ]
         topoSortCategories existing ts),
@@ -180,7 +186,7 @@
     checkOperationSuccess
       ("testfiles" </> "partial.0rx")
       (\ts -> do
-        existing  <- return $ Map.fromList [
+        existing  <- return $ toCategoryMap [
             (CategoryName "Parent",
             ValueInterface [] NoNamespace (CategoryName "Parent") [] []
                            [ValueRefine [] $ TypeInstance (CategoryName "Object1") (Positional []),
@@ -198,33 +204,33 @@
             ("Child","Object2")
           ]),
 
-    checkOperationSuccess ("testfiles" </> "valid_variances.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "contravariant_refines_covariant.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "contravariant_refines_invariant.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "covariant_refines_contravariant.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "covariant_refines_invariant.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "contravariant_defines_covariant.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "contravariant_defines_invariant.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "covariant_defines_contravariant.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "covariant_defines_invariant.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "concrete_duplicate_param.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "type_duplicate_param.0rx") (checkParamVariances defaultCategories),
-    checkOperationFail ("testfiles" </> "value_duplicate_param.0rx") (checkParamVariances defaultCategories),
+    checkOperationSuccess ("testfiles" </> "valid_variances.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "contravariant_refines_covariant.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "contravariant_refines_invariant.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "covariant_refines_contravariant.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "covariant_refines_invariant.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "contravariant_defines_covariant.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "contravariant_defines_invariant.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "covariant_defines_contravariant.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "covariant_defines_invariant.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "concrete_duplicate_param.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "type_duplicate_param.0rx") (checkParamVariances emptyCategoryMap),
+    checkOperationFail ("testfiles" </> "value_duplicate_param.0rx") (checkParamVariances emptyCategoryMap),
 
     checkOperationSuccess
       ("testfiles" </> "concrete_refines_value.0rx")
-      (checkParamVariances $ Map.fromList [
+      (checkParamVariances $ toCategoryMap [
           (CategoryName "Parent2",InstanceInterface [] NoNamespace (CategoryName "Parent2") [] [] [])
         ]),
     checkOperationFail
       ("testfiles" </> "concrete_refines_value.0rx")
-      (checkParamVariances $ Map.fromList [
+      (checkParamVariances $ toCategoryMap [
           (CategoryName "Parent",InstanceInterface [] NoNamespace (CategoryName "Parent") [] [] [])
         ]),
 
     checkOperationSuccess
       ("testfiles" </> "partial_params.0rx")
-      (checkParamVariances $ Map.fromList [
+      (checkParamVariances $ toCategoryMap [
           (CategoryName "Parent",
            ValueInterface [] NoNamespace (CategoryName "Parent") []
                           [ValueParam [] (ParamName "#w") Contravariant,
@@ -232,7 +238,7 @@
       ]),
     checkOperationFail
       ("testfiles" </> "partial_params.0rx")
-      (checkParamVariances $ Map.fromList [
+      (checkParamVariances $ toCategoryMap [
           (CategoryName "Parent",
            ValueInterface [] NoNamespace (CategoryName "Parent") []
                           [ValueParam [] (ParamName "#w") Invariant,
@@ -240,7 +246,7 @@
       ]),
     checkOperationFail
       ("testfiles" </> "partial_params.0rx")
-      (checkParamVariances $ Map.fromList [
+      (checkParamVariances $ toCategoryMap [
           (CategoryName "Parent",
            ValueInterface [] NoNamespace (CategoryName "Parent") []
                           [ValueParam [] (ParamName "#w") Contravariant,
@@ -256,55 +262,55 @@
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         rs <- getTypeRefines ts3 "Object1<#a,#b>" "Object1"
         rs `containsPaired` ["#a","#b"]),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         rs <- getTypeRefines ts3 "Object1<#a,#b>" "Object3"
         rs `containsPaired` ["#b"]),
     checkOperationFail
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         rs <- getTypeRefines ts3 "Undefined<#a,#b>" "Undefined"
         rs `containsPaired` ["#a","#b"]),
     checkOperationFail
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         rs <- getTypeRefines ts3 "Object1<#a>" "Object1"
         rs `containsPaired` ["#a"]),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         rs <- getTypeRefines ts3 "Parent<#t>" "Object1"
         rs `containsPaired` ["#t","Object3<Object2>"]),
     checkOperationFail
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         getTypeRefines ts3 "Parent<#t>" "Child"),
     checkOperationFail
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         getTypeRefines ts3 "Child" "Type"),
     checkOperationFail
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
         getTypeRefines ts3 "Child" "Missing"),
 
     checkOperationSuccess
@@ -362,64 +368,64 @@
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "Child"),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "[Child|Child]"),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "[Child&Child]"),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "Object2"),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "[Object2|Object2]"),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "[Object2&Object2]"),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeFail r [] "Type<Child>"),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeFail r [] "[Type<Child>|Type<Child>]"),
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ta <- flattenAllConnections defaultCategories ts2 >>= declareAllTypes defaultCategories
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeFail r [] "[Type<Child>&Type<Child>]"),
 
@@ -427,29 +433,29 @@
     checkOperationSuccess
       ("testfiles" </> "filters.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories Map.empty ts
-        ta <- flattenAllConnections Map.empty ts2 >>= declareAllTypes Map.empty
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "Value0<Value1,Value2>"),
     checkOperationSuccess
       ("testfiles" </> "filters.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories Map.empty ts
-        ta <- flattenAllConnections Map.empty ts2 >>= declareAllTypes Map.empty
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "Value0<Value1,Value1>"),
     checkOperationSuccess
       ("testfiles" </> "filters.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories Map.empty ts
-        ta <- flattenAllConnections Map.empty ts2 >>= declareAllTypes Map.empty
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r [] "Value0<Value3,Value2>"),
     checkOperationSuccess
       ("testfiles" </> "filters.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories Map.empty ts
-        ta <- flattenAllConnections Map.empty ts2 >>= declareAllTypes Map.empty
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r
           ["#x","#y"]
@@ -457,8 +463,8 @@
     checkOperationSuccess
       ("testfiles" </> "filters.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories Map.empty ts
-        ta <- flattenAllConnections Map.empty ts2 >>= declareAllTypes Map.empty
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r
           ["#x","#y"]
@@ -466,8 +472,8 @@
     checkOperationSuccess
       ("testfiles" </> "filters.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories Map.empty ts
-        ta <- flattenAllConnections Map.empty ts2 >>= declareAllTypes Map.empty
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r
           ["#x","#y"]
@@ -475,8 +481,8 @@
     checkOperationSuccess
       ("testfiles" </> "filters.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories Map.empty ts
-        ta <- flattenAllConnections Map.empty ts2 >>= declareAllTypes Map.empty
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ta <- flattenAllConnections emptyCategoryMap ts2 >>= declareAllTypes emptyCategoryMap
         let r = CategoryResolver ta
         checkTypeSuccess r
           ["#x","#y"]
@@ -485,222 +491,222 @@
     checkOperationSuccess
       ("testfiles" </> "requires_concrete.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
-        checkCategoryInstances defaultCategories ts3),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
+        checkCategoryInstances emptyCategoryMap ts3),
 
     -- TODO: Clean these tests up.
     checkOperationSuccess
       ("testfiles" </> "merged.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        ts3 <- flattenAllConnections defaultCategories ts2
-        tm <- declareAllTypes defaultCategories ts3
-        rs <- getRefines tm "Test"
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        ts3 <- flattenAllConnections emptyCategoryMap ts2
+        tm <- declareAllTypes emptyCategoryMap ts3
+        rs <- getRefines (cmAvailable tm) "Test"
         rs `containsExactly` ["Value0","Value1","Value2","Value3",
                               "Value4<Value1,Value1>","Inherit1","Inherit2"]),
 
     checkOperationSuccess
       ("testfiles" </> "merged.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationSuccess
       ("testfiles" </> "duplicate_refine.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationSuccess
       ("testfiles" </> "duplicate_define.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationFail
       ("testfiles" </> "refine_wrong_direction.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationFail
       ("testfiles" </> "inherit_incompatible.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationSuccess
       ("testfiles" </> "merge_incompatible.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
 
     checkOperationSuccess
       ("testfiles" </> "flatten.0rx")
       (\ts -> do
-        let tm0 = Map.fromList [
+        let tm0 = toCategoryMap [
                     (CategoryName "Parent2",InstanceInterface [] NoNamespace (CategoryName "Parent2") [] [] [])
                   ]
         tm <- includeNewTypes tm0 ts
-        rs <- getRefines tm "Child"
+        rs <- getRefines (cmAvailable tm) "Child"
         rs `containsExactly` ["Parent<Child>","Object2",
                               "Object1<Child,Object3<Object2>>",
                               "Object3<Object3<Object2>>"]),
 
     checkOperationSuccess
       ("testfiles" </> "category_function_param_match.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "function_param_clash.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "function_duplicate_param.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "function_bad_filter_param.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "function_bad_allows_variance.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "function_bad_requires_variance.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "function_bad_defines_variance.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "weak_arg.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "weak_return.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
 
     checkOperationSuccess
       ("testfiles" </> "function_filters_satisfied.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "function_requires_missed.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "function_allows_missed.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "function_defines_missed.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
 
     checkOperationSuccess
       ("testfiles" </> "valid_function_variance.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "bad_value_arg_variance.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "bad_value_return_variance.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "bad_type_arg_variance.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
     checkOperationFail
       ("testfiles" </> "bad_type_return_variance.0rx")
-      (\ts -> checkCategoryInstances defaultCategories ts),
+      (\ts -> checkCategoryInstances emptyCategoryMap ts),
 
     checkOperationSuccess
       ("testfiles" </> "valid_filter_variance.0rx")
-      (\ts -> checkParamVariances defaultCategories ts),
+      (\ts -> checkParamVariances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "allows_variance_right.0rx")
-      (\ts -> checkParamVariances defaultCategories ts),
+      (\ts -> checkParamVariances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "defines_variance_right.0rx")
-      (\ts -> checkParamVariances defaultCategories ts),
+      (\ts -> checkParamVariances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "requires_variance_right.0rx")
-      (\ts -> checkParamVariances defaultCategories ts),
+      (\ts -> checkParamVariances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "allows_variance_left.0rx")
-      (\ts -> checkParamVariances defaultCategories ts),
+      (\ts -> checkParamVariances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "defines_variance_left.0rx")
-      (\ts -> checkParamVariances defaultCategories ts),
+      (\ts -> checkParamVariances emptyCategoryMap ts),
     checkOperationSuccess
       ("testfiles" </> "requires_variance_left.0rx")
-      (\ts -> checkParamVariances defaultCategories ts),
+      (\ts -> checkParamVariances emptyCategoryMap ts),
 
     checkOperationFail
       ("testfiles" </> "conflicting_declaration.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationFail
       ("testfiles" </> "conflicting_inherited.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationSuccess
       ("testfiles" </> "successful_merge.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationSuccess
       ("testfiles" </> "merge_with_refine.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationFail
       ("testfiles" </> "failed_merge.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationFail
       ("testfiles" </> "ambiguous_merge_inherit.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationFail
       ("testfiles" </> "merge_different_scopes.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
 
     checkOperationSuccess
       ("testfiles" </> "successful_merge_params.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationFail
       ("testfiles" </> "failed_merge_params.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationSuccess
       ("testfiles" </> "preserve_merged.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationFail
       ("testfiles" </> "conflict_in_preserved.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
     checkOperationSuccess
       ("testfiles" </> "resolved_in_preserved.0rx")
       (\ts -> do
-        ts2 <- topoSortCategories defaultCategories ts
-        flattenAllConnections defaultCategories ts2 >> return ()),
+        ts2 <- topoSortCategories emptyCategoryMap ts
+        flattenAllConnections emptyCategoryMap ts2 >> return ()),
 
     checkOperationSuccess
       ("testfiles" </> "valid_self.0rx")
-      (includeNewTypes defaultCategories >=> const (return ())),
+      (includeNewTypes emptyCategoryMap >=> const (return ())),
     checkOperationFail
       ("testfiles" </> "bad_merge_self.0rx")
-      (includeNewTypes defaultCategories >=> const (return ())),
+      (includeNewTypes emptyCategoryMap >=> const (return ())),
     checkOperationFail
       ("testfiles" </> "contravariant_self.0rx")
-      (includeNewTypes defaultCategories >=> const (return ())),
+      (includeNewTypes emptyCategoryMap >=> const (return ())),
     checkOperationFail
       ("testfiles" </> "invariant_self.0rx")
-      (includeNewTypes defaultCategories >=> const (return ())),
+      (includeNewTypes emptyCategoryMap >=> const (return ())),
 
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Type1","#x")]
@@ -708,7 +714,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Type2","#x"),("Type1","#x")]
@@ -716,7 +722,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           []
@@ -725,7 +731,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface1<Type2>","#x"),("Interface1<Type1>","#x")]
@@ -734,7 +740,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface2<Type2>","#x"),("Interface2<Type1>","#x")]
@@ -742,7 +748,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface2<Type2>","Interface2<#x>"),
@@ -752,7 +758,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface3<Type1>","#x"),("Interface3<Type1>","#x")]
@@ -760,7 +766,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface3<Type1>","#x"),("Interface3<Type2>","#x")]
@@ -768,7 +774,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface3<Type1>","Interface3<#x>"),
@@ -777,7 +783,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceFail tm
           [("#x",[])] ["#x"]
           [("Interface3<Type1>","Interface3<#x>"),
@@ -786,16 +792,17 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
-        checkInferenceFail tm
+        tm <- includeNewTypes emptyCategoryMap ts
+        checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Type1","#x"),
            ("Interface1<Type2>","Interface1<#x>"),
-           ("Interface2<Type0>","Interface2<#x>")]),
+           ("Interface2<Type0>","Interface2<#x>")]
+           [("#x","Type1")]),
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface3<Type2>","Interface3<#x>"),
@@ -806,7 +813,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface1<Type1>","Interface1<[#x|Interface2<#x>]>")]
@@ -814,7 +821,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface1<Type2>","Interface1<[#x&Type1]>")]
@@ -822,7 +829,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface2<Type1>","Interface2<[#x&Interface2<#x>]>")]
@@ -830,7 +837,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface2<Type0>","Interface2<[#x|Type1]>")]
@@ -839,7 +846,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           -- Guesses are both Type0 and Type1, and Type0 is more general.
@@ -849,7 +856,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           -- An unrelated union shouldn't cause problems.
@@ -859,7 +866,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[]),("#y",[])] ["#x","#y"]
           [("Interface3<Type0>","[Interface1<#x>&Interface3<#x>]"),
@@ -869,7 +876,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface1<any>","Interface1<#x>")]
@@ -877,7 +884,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface2<all>","Interface2<#x>")]
@@ -886,7 +893,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface1<all>","Interface1<#x>")]
@@ -894,7 +901,7 @@
     checkOperationSuccess
       ("testfiles" </> "inference.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("Interface2<any>","Interface2<#x>")]
@@ -903,7 +910,7 @@
     checkOperationSuccess
       ("testfiles" </> "delayed_merging.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceFail tm
           [("#x",[])] ["#x"]
           -- Guesses are either Type1 or Type2.
@@ -911,7 +918,7 @@
     checkOperationSuccess
       ("testfiles" </> "delayed_merging.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           -- Failure to merge Type1 and Type2 is resolved by Base.
@@ -922,7 +929,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("[Interface2<Type1>|Interface3<Type2>]","Interface0<#x>")]
@@ -931,7 +938,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("[Interface2<Type0>|Interface3<Type4>]","Interface0<#x>")]
@@ -940,7 +947,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("[Interface2<Type1>&Interface3<Type2>]","Interface0<#x>")]
@@ -949,7 +956,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceFail tm
           [("#x",[])] ["#x"]
           -- Guesses are Type0 or Type4, which can't be merged.
@@ -957,7 +964,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("[Interface2<Type1>|Interface3<Type2>]","Interface1<#x>")]
@@ -966,7 +973,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("[Interface2<Type0>|Interface3<Type4>]","Interface1<#x>")]
@@ -975,7 +982,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",[])] ["#x"]
           [("[Interface2<Type1>&Interface3<Type2>]","Interface1<#x>")]
@@ -984,7 +991,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceFail tm
           [("#x",[])] ["#x"]
           -- Guesses are Type0 or Type4, which can't be merged.
@@ -993,7 +1000,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",["requires Type0"])] ["#x"]
           -- Guesses are Type1 or Type4, which can't be merged, but the filter
@@ -1003,7 +1010,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",["allows Type2"])] ["#x"]
           -- Guesses are Type1 or Type4, which can't be merged, but the filter
@@ -1013,7 +1020,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",["defines Defined<#x>"])] ["#x"]
           -- Guesses are Type1 or Type4, which can't be merged, but the filter
@@ -1024,7 +1031,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",["requires Type0"])] ["#x"]
           [("[Type1|Type2]","#x")]
@@ -1032,7 +1039,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceSuccess tm
           [("#x",["requires #x"])] ["#x"]
           [("[Type1|Type2]","#x")]
@@ -1040,7 +1047,7 @@
     checkOperationSuccess
       ("testfiles" </> "infer_meta.0rx")
       (\ts -> do
-        tm <- includeNewTypes defaultCategories ts
+        tm <- includeNewTypes emptyCategoryMap ts
         checkInferenceFail tm
           [("#x",["requires Type0"])] ["#x"]
           [("[Type1|Type4]","#x")])
@@ -1060,7 +1067,7 @@
 
 getTypeRefines :: Show c => [AnyCategory c] -> String -> String -> TrackedErrors [String]
 getTypeRefines ts s n = do
-  ta <- declareAllTypes defaultCategories ts
+  ta <- declareAllTypes emptyCategoryMap ts
   let r = CategoryResolver ta
   t <- readSingle "(string)" s
   Positional rs <- trRefines r t (CategoryName n)
@@ -1068,7 +1075,7 @@
 
 getTypeDefines :: Show c => [AnyCategory c] -> String -> String -> TrackedErrors [String]
 getTypeDefines ts s n = do
-  ta <- declareAllTypes defaultCategories ts
+  ta <- declareAllTypes emptyCategoryMap ts
   let r = CategoryResolver ta
   t <- readSingle "(string)" s
   Positional ds <- trDefines r t (CategoryName n)
@@ -1076,14 +1083,14 @@
 
 getTypeVariance :: Show c => [AnyCategory c] -> String -> TrackedErrors [Variance]
 getTypeVariance ts n = do
-  ta <- declareAllTypes defaultCategories ts
+  ta <- declareAllTypes emptyCategoryMap ts
   let r = CategoryResolver ta
   (Positional vs) <- trVariance r (CategoryName n)
   return vs
 
 getTypeFilters :: Show c => [AnyCategory c] -> String -> TrackedErrors [[String]]
 getTypeFilters ts s = do
-  ta <- declareAllTypes defaultCategories ts
+  ta <- declareAllTypes emptyCategoryMap ts
   let r = CategoryResolver ta
   t <- readSingle "(string)" s
   Positional vs <- trTypeFilters r t
@@ -1091,7 +1098,7 @@
 
 getTypeDefinesFilters :: Show c => [AnyCategory c] -> String -> TrackedErrors [[String]]
 getTypeDefinesFilters ts s = do
-  ta <- declareAllTypes defaultCategories ts
+  ta <- declareAllTypes emptyCategoryMap ts
   let r = CategoryResolver ta
   t <- readSingle "(string)" s
   Positional vs <- trDefinesFilters r t
@@ -1099,13 +1106,13 @@
 
 scrapeAllRefines :: [AnyCategory c] -> [(String, String)]
 scrapeAllRefines = map (show *** show) . concat . map scrapeSingle where
-  scrapeSingle (ValueInterface _ _ n _ _ rs _)    = map ((,) n . vrType) rs
-  scrapeSingle (ValueConcrete _ _ n _ _ rs _ _ _) = map ((,) n . vrType) rs
+  scrapeSingle (ValueInterface _ _ n _ _ rs _)      = map ((,) n . vrType) rs
+  scrapeSingle (ValueConcrete _ _ n _ _ _ rs _ _ _) = map ((,) n . vrType) rs
   scrapeSingle _ = []
 
 scrapeAllDefines :: [AnyCategory c] -> [(String, String)]
 scrapeAllDefines = map (show *** show) . concat . map scrapeSingle where
-  scrapeSingle (ValueConcrete _ _ n _ _ _ ds _ _) = map ((,) n . vdType) ds
+  scrapeSingle (ValueConcrete _ _ n _ _ _ _ ds _ _) = map ((,) n . vdType) ds
   scrapeSingle _ = []
 
 checkPaired :: Show a => (a -> a -> TrackedErrors ()) -> [a] -> [a] -> TrackedErrors ()
@@ -1129,6 +1136,20 @@
   return $ check (parsed >>= o >> return ())
   where
     check x = x <!! "Check " ++ f ++ ":"
+
+checkOperationFailWith :: String -> String -> ([AnyCategory SourceContext] -> TrackedErrors a) -> IO (TrackedErrors ())
+checkOperationFailWith m f o = do
+  contents <- loadFile f
+  let parsed = readMulti f contents :: TrackedErrors [AnyCategory SourceContext]
+  return $ check (parsed >>= o >> return ())
+  where
+    check c
+      | isCompilerError c = do
+          let text = show (getCompilerError c)
+          when (not $ text =~ m) $
+            compilerErrorM $ "Expected pattern " ++ show m ++ " in error output but got\n" ++ text
+      | otherwise = compilerErrorM $ "Check " ++ f ++ ": Expected failure but got\n" ++
+                                     show (getCompilerSuccess c) ++ "\n"
 
 checkOperationFail :: String -> ([AnyCategory SourceContext] -> TrackedErrors a) -> IO (TrackedErrors ())
 checkOperationFail f o = do
diff --git a/src/Test/testfiles/allows_variance_left.0rx b/src/Test/testfiles/allows_variance_left.0rx
--- a/src/Test/testfiles/allows_variance_left.0rx
+++ b/src/Test/testfiles/allows_variance_left.0rx
@@ -1,4 +1,4 @@
-@value interface Interface {}
+@value interface Interface { }
 
 concrete Type<|#x> {
   #x allows Interface
diff --git a/src/Test/testfiles/allows_variance_right.0rx b/src/Test/testfiles/allows_variance_right.0rx
--- a/src/Test/testfiles/allows_variance_right.0rx
+++ b/src/Test/testfiles/allows_variance_right.0rx
@@ -1,4 +1,4 @@
-@value interface Interface<|#x> {}
+@value interface Interface<|#x> { }
 
 concrete Type<#x|> {
   #x allows Interface<#x>
diff --git a/src/Test/testfiles/ambiguous_merge_inherit.0rx b/src/Test/testfiles/ambiguous_merge_inherit.0rx
--- a/src/Test/testfiles/ambiguous_merge_inherit.0rx
+++ b/src/Test/testfiles/ambiguous_merge_inherit.0rx
@@ -1,4 +1,4 @@
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
diff --git a/src/Test/testfiles/bad_type_arg_variance.0rx b/src/Test/testfiles/bad_type_arg_variance.0rx
--- a/src/Test/testfiles/bad_type_arg_variance.0rx
+++ b/src/Test/testfiles/bad_type_arg_variance.0rx
@@ -1,4 +1,4 @@
-@value interface Type1<#x|> {}
+@value interface Type1<#x|> { }
 
 @type interface Type2<#x|> {
   // contravariant * contravariant = covariant
diff --git a/src/Test/testfiles/bad_type_return_variance.0rx b/src/Test/testfiles/bad_type_return_variance.0rx
--- a/src/Test/testfiles/bad_type_return_variance.0rx
+++ b/src/Test/testfiles/bad_type_return_variance.0rx
@@ -1,4 +1,4 @@
-@value interface Type1<#x|> {}
+@value interface Type1<#x|> { }
 
 @type interface Type2<|#x> {
   something () -> (Type1<#x>)
diff --git a/src/Test/testfiles/bad_value_arg_variance.0rx b/src/Test/testfiles/bad_value_arg_variance.0rx
--- a/src/Test/testfiles/bad_value_arg_variance.0rx
+++ b/src/Test/testfiles/bad_value_arg_variance.0rx
@@ -1,4 +1,4 @@
-@value interface Type1<#x|> {}
+@value interface Type1<#x|> { }
 
 @value interface Type2<#x|> {
   // contravariant * contravariant = covariant
diff --git a/src/Test/testfiles/bad_value_return_variance.0rx b/src/Test/testfiles/bad_value_return_variance.0rx
--- a/src/Test/testfiles/bad_value_return_variance.0rx
+++ b/src/Test/testfiles/bad_value_return_variance.0rx
@@ -1,4 +1,4 @@
-@value interface Type1<#x|> {}
+@value interface Type1<#x|> { }
 
 @value interface Type2<|#x> {
   something () -> (Type1<#x>)
diff --git a/src/Test/testfiles/basic_crash_test.0rt b/src/Test/testfiles/basic_crash_test.0rt
--- a/src/Test/testfiles/basic_crash_test.0rt
+++ b/src/Test/testfiles/basic_crash_test.0rt
@@ -1,5 +1,5 @@
 testcase "basic crash test" {
-  crash
+  failure
   require "pattern in output 1"
   exclude "pattern not in output 1"
   require "pattern in output 2"
diff --git a/src/Test/testfiles/concrete.0rx b/src/Test/testfiles/concrete.0rx
--- a/src/Test/testfiles/concrete.0rx
+++ b/src/Test/testfiles/concrete.0rx
@@ -1,23 +1,29 @@
-concrete/*!!!*/Type<#a,#b // <- contravariant params
-             |#c,#d // <- invariant params
-             |#e,#f /* <- covariant params*/> {
+concrete/*!!!*/Type<#a, #b // <- contravariant params
+             |#c, #d // <- invariant params
+             |#e, #f /* <- covariant params*/> {
   refines /* <- used with value interfaces */ Parent
-  refines Other<Type2<#a>,#f>
-  defines /* <- used with type interfaces */ Equals<Type<#a,#b,#c,#d,#e,#f>>
+  refines Other<Type2<#a>, #f>
+  defines /* <- used with type interfaces */ Equals<Type<#a, #b, #c, #d, #e, #f>>
   #a allows /* <- used with value-interface filters */ Parent
   #b requires Type2<#a>
   #c defines // <- used with type-interface filters
     Equals<#c /* <- a type arg */>
 
+  visibility _
+
   @category create<#x>
     #x requires Type1<Type3>
   () -> (optional Type1<Type3>)
 
+  visibility #self
+
   @type create2<#y>
     #y requires #x
     #y allows T2
     #y defines T3
   () -> (optional #x)
+
+  visibility Append<String>
 
   @value get () -> (Type1<Type3>)
   @value set (Type1<Type3>) -> ()
diff --git a/src/Test/testfiles/concrete_defines_concrete.0rx b/src/Test/testfiles/concrete_defines_concrete.0rx
--- a/src/Test/testfiles/concrete_defines_concrete.0rx
+++ b/src/Test/testfiles/concrete_defines_concrete.0rx
@@ -1,4 +1,4 @@
-concrete Parent {}
+concrete Parent { }
 
 concrete Child {
   defines Parent
diff --git a/src/Test/testfiles/concrete_defines_instance.0rx b/src/Test/testfiles/concrete_defines_instance.0rx
--- a/src/Test/testfiles/concrete_defines_instance.0rx
+++ b/src/Test/testfiles/concrete_defines_instance.0rx
@@ -1,4 +1,4 @@
-@type interface Parent {}
+@type interface Parent { }
 
 concrete Child {
   defines Parent
diff --git a/src/Test/testfiles/concrete_defines_value.0rx b/src/Test/testfiles/concrete_defines_value.0rx
--- a/src/Test/testfiles/concrete_defines_value.0rx
+++ b/src/Test/testfiles/concrete_defines_value.0rx
@@ -1,4 +1,4 @@
-@value interface Parent {}
+@value interface Parent { }
 
 concrete Child {
   defines Parent
diff --git a/src/Test/testfiles/concrete_duplicate_param.0rx b/src/Test/testfiles/concrete_duplicate_param.0rx
--- a/src/Test/testfiles/concrete_duplicate_param.0rx
+++ b/src/Test/testfiles/concrete_duplicate_param.0rx
@@ -1,1 +1,1 @@
-concrete Type<#x|#y|#x> {}
+concrete Type<#x|#y|#x> { }
diff --git a/src/Test/testfiles/concrete_refines_concrete.0rx b/src/Test/testfiles/concrete_refines_concrete.0rx
--- a/src/Test/testfiles/concrete_refines_concrete.0rx
+++ b/src/Test/testfiles/concrete_refines_concrete.0rx
@@ -1,4 +1,4 @@
-concrete Parent {}
+concrete Parent { }
 
 concrete Child {
   refines Parent
diff --git a/src/Test/testfiles/concrete_refines_instance.0rx b/src/Test/testfiles/concrete_refines_instance.0rx
--- a/src/Test/testfiles/concrete_refines_instance.0rx
+++ b/src/Test/testfiles/concrete_refines_instance.0rx
@@ -1,4 +1,4 @@
-@type interface Parent {}
+@type interface Parent { }
 
 concrete Child {
   refines Parent
diff --git a/src/Test/testfiles/concrete_refines_value.0rx b/src/Test/testfiles/concrete_refines_value.0rx
--- a/src/Test/testfiles/concrete_refines_value.0rx
+++ b/src/Test/testfiles/concrete_refines_value.0rx
@@ -1,4 +1,4 @@
-@value interface Parent {}
+@value interface Parent { }
 
 concrete Child {
   refines Parent
diff --git a/src/Test/testfiles/conflict_in_preserved.0rx b/src/Test/testfiles/conflict_in_preserved.0rx
--- a/src/Test/testfiles/conflict_in_preserved.0rx
+++ b/src/Test/testfiles/conflict_in_preserved.0rx
@@ -1,4 +1,4 @@
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
diff --git a/src/Test/testfiles/contravariant_defines_covariant.0rx b/src/Test/testfiles/contravariant_defines_covariant.0rx
--- a/src/Test/testfiles/contravariant_defines_covariant.0rx
+++ b/src/Test/testfiles/contravariant_defines_covariant.0rx
@@ -1,4 +1,4 @@
-@type interface Object1<|#x> {}
+@type interface Object1<|#x> { }
 
 concrete Object2<#y|> {
   defines Object1<#y>
diff --git a/src/Test/testfiles/contravariant_defines_invariant.0rx b/src/Test/testfiles/contravariant_defines_invariant.0rx
--- a/src/Test/testfiles/contravariant_defines_invariant.0rx
+++ b/src/Test/testfiles/contravariant_defines_invariant.0rx
@@ -1,6 +1,6 @@
-@value interface Object1<#x|> {}
+@value interface Object1<#x|> { }
 
-@type interface Object2<#z> {}
+@type interface Object2<#z> { }
 
 concrete Object3<#y|> {
   // Despite matching variance with Object1, Object2 turns it into invariant.
diff --git a/src/Test/testfiles/contravariant_refines_covariant.0rx b/src/Test/testfiles/contravariant_refines_covariant.0rx
--- a/src/Test/testfiles/contravariant_refines_covariant.0rx
+++ b/src/Test/testfiles/contravariant_refines_covariant.0rx
@@ -1,4 +1,4 @@
-@value interface Object1<|#x> {}
+@value interface Object1<|#x> { }
 
 @value interface Object2<#y|> {
   refines Object1<#y>
diff --git a/src/Test/testfiles/contravariant_refines_invariant.0rx b/src/Test/testfiles/contravariant_refines_invariant.0rx
--- a/src/Test/testfiles/contravariant_refines_invariant.0rx
+++ b/src/Test/testfiles/contravariant_refines_invariant.0rx
@@ -1,6 +1,6 @@
-@value interface Object1<#x|> {}
+@value interface Object1<#x|> { }
 
-@value interface Object2<#z> {}
+@value interface Object2<#z> { }
 
 @value interface Object3<#y|> {
   // Despite matching variance with Object1, Object2 turns it into invariant.
diff --git a/src/Test/testfiles/covariant_defines_contravariant.0rx b/src/Test/testfiles/covariant_defines_contravariant.0rx
--- a/src/Test/testfiles/covariant_defines_contravariant.0rx
+++ b/src/Test/testfiles/covariant_defines_contravariant.0rx
@@ -1,4 +1,4 @@
-@type interface Object1<#x|> {}
+@type interface Object1<#x|> { }
 
 concrete Object2<|#y> {
   defines Object1<#y>
diff --git a/src/Test/testfiles/covariant_defines_invariant.0rx b/src/Test/testfiles/covariant_defines_invariant.0rx
--- a/src/Test/testfiles/covariant_defines_invariant.0rx
+++ b/src/Test/testfiles/covariant_defines_invariant.0rx
@@ -1,6 +1,6 @@
-@value interface Object1<|#x> {}
+@value interface Object1<|#x> { }
 
-@type interface Object2<#z> {}
+@type interface Object2<#z> { }
 
 concrete Object3<|#y> {
   // Despite matching variance with Object1, Object2 turns it into invariant.
diff --git a/src/Test/testfiles/covariant_refines_contravariant.0rx b/src/Test/testfiles/covariant_refines_contravariant.0rx
--- a/src/Test/testfiles/covariant_refines_contravariant.0rx
+++ b/src/Test/testfiles/covariant_refines_contravariant.0rx
@@ -1,4 +1,4 @@
-@value interface Object1<#x|> {}
+@value interface Object1<#x|> { }
 
 @value interface Object2<|#y> {
   refines Object1<#y>
diff --git a/src/Test/testfiles/covariant_refines_invariant.0rx b/src/Test/testfiles/covariant_refines_invariant.0rx
--- a/src/Test/testfiles/covariant_refines_invariant.0rx
+++ b/src/Test/testfiles/covariant_refines_invariant.0rx
@@ -1,6 +1,6 @@
-@value interface Object1<|#x> {}
+@value interface Object1<|#x> { }
 
-@value interface Object2<#z> {}
+@value interface Object2<#z> { }
 
 @value interface Object3<|#y> {
   // Despite matching variance with Object1, Object2 turns it into invariant.
diff --git a/src/Test/testfiles/defines_variance_left.0rx b/src/Test/testfiles/defines_variance_left.0rx
--- a/src/Test/testfiles/defines_variance_left.0rx
+++ b/src/Test/testfiles/defines_variance_left.0rx
@@ -1,4 +1,4 @@
-@type interface Interface {}
+@type interface Interface { }
 
 concrete Type<#x|> {
   #x defines Interface
diff --git a/src/Test/testfiles/defines_variance_right.0rx b/src/Test/testfiles/defines_variance_right.0rx
--- a/src/Test/testfiles/defines_variance_right.0rx
+++ b/src/Test/testfiles/defines_variance_right.0rx
@@ -1,4 +1,4 @@
-@type interface Interface<|#x> {}
+@type interface Interface<|#x> { }
 
 concrete Type<|#x> {
   #x defines Interface<#x>
diff --git a/src/Test/testfiles/delayed_merging.0rx b/src/Test/testfiles/delayed_merging.0rx
--- a/src/Test/testfiles/delayed_merging.0rx
+++ b/src/Test/testfiles/delayed_merging.0rx
@@ -1,4 +1,4 @@
-@value interface Base {}
+@value interface Base { }
 
 concrete Type1 {
   refines Base
@@ -8,9 +8,9 @@
   refines Base
 }
 
-@value interface Interface1<|#x> {}
+@value interface Interface1<|#x> { }
 
-@value interface Interface2<|#x> {}
+@value interface Interface2<|#x> { }
 
 concrete Type {
   refines Interface1<Type1>
diff --git a/src/Test/testfiles/duplicate_define.0rx b/src/Test/testfiles/duplicate_define.0rx
--- a/src/Test/testfiles/duplicate_define.0rx
+++ b/src/Test/testfiles/duplicate_define.0rx
@@ -1,10 +1,10 @@
-@value interface Value0 {}
+@value interface Value0 { }
 
 @value interface Value1 {
   refines Value0
 }
 
-@type interface Type0<|#x> {}
+@type interface Type0<|#x> { }
 
 concrete Object {
   defines Type0<Value0>
diff --git a/src/Test/testfiles/duplicate_refine.0rx b/src/Test/testfiles/duplicate_refine.0rx
--- a/src/Test/testfiles/duplicate_refine.0rx
+++ b/src/Test/testfiles/duplicate_refine.0rx
@@ -1,10 +1,10 @@
-@value interface Value0 {}
+@value interface Value0 { }
 
 @value interface Value1 {
   refines Value0
 }
 
-@value interface Value2<|#x> {}
+@value interface Value2<|#x> { }
 
 concrete Object {
   refines Value2<Value0>
diff --git a/src/Test/testfiles/failed_merge.0rx b/src/Test/testfiles/failed_merge.0rx
--- a/src/Test/testfiles/failed_merge.0rx
+++ b/src/Test/testfiles/failed_merge.0rx
@@ -1,4 +1,4 @@
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
diff --git a/src/Test/testfiles/failed_merge_params.0rx b/src/Test/testfiles/failed_merge_params.0rx
--- a/src/Test/testfiles/failed_merge_params.0rx
+++ b/src/Test/testfiles/failed_merge_params.0rx
@@ -1,4 +1,4 @@
-@value interface Type1<#a> {}
+@value interface Type1<#a> { }
 
 @value interface Type2<#b> {
   refines Type1<#b>
@@ -9,14 +9,14 @@
 }
 
 @value interface Interface1<#j> {
-  something<#x,#y>
+  something<#x, #y>
     #x requires Type2<#j>
     #y allows Type1<#j>
   (#x) -> (#y)
 }
 
 @value interface Interface2<#k> {
-  something<#w,#z>
+  something<#w, #z>
     #w requires Type3<#k>
     #z allows Type3<#k>
   (#w) -> (#z)
@@ -26,7 +26,7 @@
   refines Interface1<#q>
   refines Interface2<#q>
 
-  @value something<#r,#s>
+  @value something<#r, #s>
     #r requires Type2<#q>
     #s allows Type2<#q>
   (#r) -> (#s)
diff --git a/src/Test/testfiles/filters.0rx b/src/Test/testfiles/filters.0rx
--- a/src/Test/testfiles/filters.0rx
+++ b/src/Test/testfiles/filters.0rx
@@ -1,16 +1,16 @@
-@type interface Equals<#x|> {}
+@type interface Equals<#x|> { }
 
-@value interface Function<#y|#z> {}
+@value interface Function<#y|#z> { }
 
-concrete Value0<#t,#w> {
+concrete Value0<#t, #w> {
   #t allows #w
-  #t requires Function<#t,#w>
+  #t requires Function<#t, #w>
   #w requires #t
   #w defines Equals<#w>
 }
 
 @value interface Value1 {
-  refines Function<Value1,Value2>
+  refines Function<Value1, Value2>
 }
 
 concrete Value2 {
diff --git a/src/Test/testfiles/flatten.0rx b/src/Test/testfiles/flatten.0rx
--- a/src/Test/testfiles/flatten.0rx
+++ b/src/Test/testfiles/flatten.0rx
@@ -1,15 +1,15 @@
-@value interface Object1<#x,#y> {
+@value interface Object1<#x, #y> {
   refines Object3<#y>
   // -> refines Object2
 }
 
-@value interface Object2 {}
+@value interface Object2 { }
 
-@type interface Type<#x> {}
+@type interface Type<#x> { }
 
 concrete Child {
   refines Parent<Child>
-  // -> refines Object1<Child,Object3<Object2>>
+  // -> refines Object1<Child, Object3<Object2>>
   // -> refines Object3<Object3<Object2>>
   // -> refines Object2
 
@@ -17,7 +17,7 @@
 }
 
 @value interface Parent<#x> {
-  refines Object1<#x,Object3<Object2>>
+  refines Object1<#x, Object3<Object2>>
   // -> refines Object3<Object3<Object2>>
   // -> refines Object2
 }
diff --git a/src/Test/testfiles/function_allows_missed.0rx b/src/Test/testfiles/function_allows_missed.0rx
--- a/src/Test/testfiles/function_allows_missed.0rx
+++ b/src/Test/testfiles/function_allows_missed.0rx
@@ -1,8 +1,8 @@
-@type interface Type1 {}
+@type interface Type1 { }
 
-@value interface Type2 {}
+@value interface Type2 { }
 
-@value interface Type3 {}
+@value interface Type3 { }
 
 concrete Type4<#x> {
   #x requires Type2
diff --git a/src/Test/testfiles/function_bad_allows_variance.0rx b/src/Test/testfiles/function_bad_allows_variance.0rx
--- a/src/Test/testfiles/function_bad_allows_variance.0rx
+++ b/src/Test/testfiles/function_bad_allows_variance.0rx
@@ -1,4 +1,4 @@
-@value interface Interface<#x|> {}
+@value interface Interface<#x|> { }
 
 concrete Type<|#x> {
   @value something<#y>
diff --git a/src/Test/testfiles/function_bad_defines_variance.0rx b/src/Test/testfiles/function_bad_defines_variance.0rx
--- a/src/Test/testfiles/function_bad_defines_variance.0rx
+++ b/src/Test/testfiles/function_bad_defines_variance.0rx
@@ -1,4 +1,4 @@
-@type interface Interface<|#x> {}
+@type interface Interface<|#x> { }
 
 concrete Type<|#x> {
   @value something<#y>
diff --git a/src/Test/testfiles/function_bad_requires_variance.0rx b/src/Test/testfiles/function_bad_requires_variance.0rx
--- a/src/Test/testfiles/function_bad_requires_variance.0rx
+++ b/src/Test/testfiles/function_bad_requires_variance.0rx
@@ -1,4 +1,4 @@
-@value interface Interface<|#x> {}
+@value interface Interface<|#x> { }
 
 concrete Type<|#x> {
   @value something<#y>
diff --git a/src/Test/testfiles/function_defines_missed.0rx b/src/Test/testfiles/function_defines_missed.0rx
--- a/src/Test/testfiles/function_defines_missed.0rx
+++ b/src/Test/testfiles/function_defines_missed.0rx
@@ -1,8 +1,8 @@
-@type interface Type1 {}
+@type interface Type1 { }
 
-@value interface Type2 {}
+@value interface Type2 { }
 
-@value interface Type3 {}
+@value interface Type3 { }
 
 concrete Type4<#x> {
   #x requires Type2
diff --git a/src/Test/testfiles/function_duplicate_param.0rx b/src/Test/testfiles/function_duplicate_param.0rx
--- a/src/Test/testfiles/function_duplicate_param.0rx
+++ b/src/Test/testfiles/function_duplicate_param.0rx
@@ -1,4 +1,4 @@
 concrete Type {
-  @value something<#x,#x>
+  @value something<#x, #x>
   () -> ()
 }
diff --git a/src/Test/testfiles/function_filter_type_param.0rx b/src/Test/testfiles/function_filter_type_param.0rx
--- a/src/Test/testfiles/function_filter_type_param.0rx
+++ b/src/Test/testfiles/function_filter_type_param.0rx
@@ -1,11 +1,11 @@
-@value interface Base1 {}
+@value interface Base1 { }
 
-@type interface Base2 {}
+@type interface Base2 { }
 
 concrete Type<#x|#y|#z> {
   @type call
     #x allows Base1
     #y requires Base1
     #z defines Base2
-  (#x,#y) -> (#y,#z)
+  (#x, #y) -> (#y, #z)
 }
diff --git a/src/Test/testfiles/function_filters_satisfied.0rx b/src/Test/testfiles/function_filters_satisfied.0rx
--- a/src/Test/testfiles/function_filters_satisfied.0rx
+++ b/src/Test/testfiles/function_filters_satisfied.0rx
@@ -1,23 +1,23 @@
-@type interface Type1<#x> {}
+@type interface Type1<#x> { }
 
-@value interface Type2<#x|> {}
+@value interface Type2<#x|> { }
 
-@value interface Type3<#x|> {}
+@value interface Type3<#x|> { }
 
-concrete Type4<#w,#x> {
+concrete Type4<#w, #x> {
   #w requires Type2<#w>
   #x allows Type3<#x>
   #w defines Type1<#w>
 
-  @type something<#y,#z>
+  @type something<#y, #z>
     #y requires Type2<#y>
     #z allows Type3<#z>
     #y defines Type1<#y>
-  () -> (Type4<#y,#z>)
+  () -> (Type4<#y, #z>)
 
-  @type something2<#y,#z>
+  @type something2<#y, #z>
     #y requires #w
     #z allows #x
     #y defines Type1<#y>
-  () -> (Type4<#y,#z>)
+  () -> (Type4<#y, #z>)
 }
diff --git a/src/Test/testfiles/function_requires_missed.0rx b/src/Test/testfiles/function_requires_missed.0rx
--- a/src/Test/testfiles/function_requires_missed.0rx
+++ b/src/Test/testfiles/function_requires_missed.0rx
@@ -1,8 +1,8 @@
-@type interface Type1 {}
+@type interface Type1 { }
 
-@value interface Type2 {}
+@value interface Type2 { }
 
-@value interface Type3 {}
+@value interface Type3 { }
 
 concrete Type4<#x> {
   #x requires Type2
diff --git a/src/Test/testfiles/infer_meta.0rx b/src/Test/testfiles/infer_meta.0rx
--- a/src/Test/testfiles/infer_meta.0rx
+++ b/src/Test/testfiles/infer_meta.0rx
@@ -1,4 +1,4 @@
-@value interface Type0 {}
+@value interface Type0 { }
 
 @value interface Type1 {
   refines Type0
@@ -12,11 +12,11 @@
   defines Defined<Type4>
 }
 
-@type interface Defined<#x> {}
+@type interface Defined<#x> { }
 
-@value interface Interface0<|#x> {}
+@value interface Interface0<|#x> { }
 
-@value interface Interface1<#y|> {}
+@value interface Interface1<#y|> { }
 
 @value interface Interface2<#y> {
   refines Interface0<#y>
diff --git a/src/Test/testfiles/inference.0rx b/src/Test/testfiles/inference.0rx
--- a/src/Test/testfiles/inference.0rx
+++ b/src/Test/testfiles/inference.0rx
@@ -1,14 +1,14 @@
-@value interface Interface1<|#x> {}
+@value interface Interface1<|#x> { }
 
-@value interface Interface2<#y|> {}
+@value interface Interface2<#y|> { }
 
 @value interface Interface3<#z> {
   refines Interface1<Type1>
 }
 
-@value interface Interface4<#x|#y|#z> {}
+@value interface Interface4<#x|#y|#z> { }
 
-@value interface Type0 {}
+@value interface Type0 { }
 
 @value interface Type1 {
   refines Type0
diff --git a/src/Test/testfiles/inherit_incompatible.0rx b/src/Test/testfiles/inherit_incompatible.0rx
--- a/src/Test/testfiles/inherit_incompatible.0rx
+++ b/src/Test/testfiles/inherit_incompatible.0rx
@@ -1,4 +1,4 @@
-@value interface Base {}
+@value interface Base { }
 
 @value interface Child1 {
   refines Base
@@ -8,7 +8,7 @@
   refines Base
 }
 
-@value interface Inherit<#x|> {}
+@value interface Inherit<#x|> { }
 
 @value interface Parent1 {
   refines Inherit<Child1>
diff --git a/src/Test/testfiles/internal.0rx b/src/Test/testfiles/internal.0rx
--- a/src/Test/testfiles/internal.0rx
+++ b/src/Test/testfiles/internal.0rx
@@ -1,5 +1,5 @@
 $TestsOnly$
 
-concrete Type {}
+concrete Type { }
 
-define Type {}
+define Type { }
diff --git a/src/Test/testfiles/invariant_self.0rx b/src/Test/testfiles/invariant_self.0rx
--- a/src/Test/testfiles/invariant_self.0rx
+++ b/src/Test/testfiles/invariant_self.0rx
@@ -1,4 +1,4 @@
-@value interface Base<#x> {}
+@value interface Base<#x> { }
 
 @value interface Value<#x|#y|#z> {
   refines Base<#self>
diff --git a/src/Test/testfiles/merge_incompatible.0rx b/src/Test/testfiles/merge_incompatible.0rx
--- a/src/Test/testfiles/merge_incompatible.0rx
+++ b/src/Test/testfiles/merge_incompatible.0rx
@@ -1,4 +1,4 @@
-@value interface Base {}
+@value interface Base { }
 
 @value interface Child1 {
   refines Base
@@ -8,7 +8,7 @@
   refines Base
 }
 
-@value interface Inherit<#x|> {}
+@value interface Inherit<#x|> { }
 
 @value interface Parent1 {
   refines Inherit<Child1>
diff --git a/src/Test/testfiles/merged.0rx b/src/Test/testfiles/merged.0rx
--- a/src/Test/testfiles/merged.0rx
+++ b/src/Test/testfiles/merged.0rx
@@ -1,4 +1,4 @@
-@value interface Value0 {}
+@value interface Value0 { }
 
 @value interface Value1 {
   refines Value0
@@ -12,16 +12,16 @@
   refines Value0
 }
 
-@value interface Value4<#x|#y> {}
+@value interface Value4<#x|#y> { }
 
-@type interface Type0<#x|#y> {}
+@type interface Type0<#x|#y> { }
 
 @value interface Inherit1 {
-  refines Value4<Value1,Value1>
+  refines Value4<Value1, Value1>
 }
 
 @value interface Inherit2 {
-  refines Value4<Value2,Value0>
+  refines Value4<Value2, Value0>
 }
 
 concrete Test {
diff --git a/src/Test/testfiles/partial_params.0rx b/src/Test/testfiles/partial_params.0rx
--- a/src/Test/testfiles/partial_params.0rx
+++ b/src/Test/testfiles/partial_params.0rx
@@ -1,3 +1,3 @@
 concrete Child<#x|#y> {
-  refines Parent<#x,#y>
+  refines Parent<#x, #y>
 }
diff --git a/src/Test/testfiles/preserve_merged.0rx b/src/Test/testfiles/preserve_merged.0rx
--- a/src/Test/testfiles/preserve_merged.0rx
+++ b/src/Test/testfiles/preserve_merged.0rx
@@ -1,4 +1,4 @@
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
diff --git a/src/Test/testfiles/procedures.0rx b/src/Test/testfiles/procedures.0rx
--- a/src/Test/testfiles/procedures.0rx
+++ b/src/Test/testfiles/procedures.0rx
@@ -1,4 +1,4 @@
-testFunction1 (a,b,c) (d,e,f) {
+testFunction1 (a, b, c) (d, e, f) {
   if (T.f()) {
     // do nothing
   } elif (#q.r().f()) {
@@ -7,12 +7,12 @@
     // sleep
   }
 
-  x <- Type<#z,T<#m>>{ r, Type.new() }
+  x <- Type<#z, T<#m>>{ r, Type.new() }
 
   x <- empty
 
   scoped {
-    optional T myvar <- reduce<#x,#y>(q)
+    optional T myvar <- reduce<#x, #y>(q)
   } in if (present(myvar)) {
     \ unqualified()
   } else {
@@ -20,7 +20,7 @@
   }
 
   scoped {
-    optional T myvar <- reduce<#x,#y>(q)
+    optional T myvar <- reduce<#x, #y>(q)
   } in v, _ <- x.process(myvar)
 
   scoped {
@@ -30,7 +30,7 @@
   } in \ x.process()
 
   \ z.call().call()
-  x, weak [#k|Type] y, _ <- z.call()
+  x, weak [#k | Type] y, _ <- z.call()
   x <- z?T<#z>.call().call()
   return _
   return a
@@ -44,4 +44,4 @@
   }
 }
 
-testFunction2 (a,b,c) {}
+testFunction2 (a, b, c) { }
diff --git a/src/Test/testfiles/public.0rp b/src/Test/testfiles/public.0rp
--- a/src/Test/testfiles/public.0rp
+++ b/src/Test/testfiles/public.0rp
@@ -1,4 +1,4 @@
 $ModuleOnly$
 $TestsOnly$
 
-concrete Type {}
+concrete Type { }
diff --git a/src/Test/testfiles/refine_wrong_direction.0rx b/src/Test/testfiles/refine_wrong_direction.0rx
--- a/src/Test/testfiles/refine_wrong_direction.0rx
+++ b/src/Test/testfiles/refine_wrong_direction.0rx
@@ -1,10 +1,10 @@
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
 }
 
-@value interface Inherited<|#x> {}
+@value interface Inherited<|#x> { }
 
 @value interface Parent {
   refines Inherited<Type2>
diff --git a/src/Test/testfiles/requires_variance_left.0rx b/src/Test/testfiles/requires_variance_left.0rx
--- a/src/Test/testfiles/requires_variance_left.0rx
+++ b/src/Test/testfiles/requires_variance_left.0rx
@@ -1,4 +1,4 @@
-@value interface Interface {}
+@value interface Interface { }
 
 concrete Type<#x|> {
   #x requires Interface
diff --git a/src/Test/testfiles/requires_variance_right.0rx b/src/Test/testfiles/requires_variance_right.0rx
--- a/src/Test/testfiles/requires_variance_right.0rx
+++ b/src/Test/testfiles/requires_variance_right.0rx
@@ -1,4 +1,4 @@
-@value interface Interface<|#x> {}
+@value interface Interface<|#x> { }
 
 concrete Type<|#x> {
   #x requires Interface<#x>
diff --git a/src/Test/testfiles/resolved_in_preserved.0rx b/src/Test/testfiles/resolved_in_preserved.0rx
--- a/src/Test/testfiles/resolved_in_preserved.0rx
+++ b/src/Test/testfiles/resolved_in_preserved.0rx
@@ -1,4 +1,4 @@
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
diff --git a/src/Test/testfiles/successful_merge.0rx b/src/Test/testfiles/successful_merge.0rx
--- a/src/Test/testfiles/successful_merge.0rx
+++ b/src/Test/testfiles/successful_merge.0rx
@@ -1,4 +1,4 @@
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
diff --git a/src/Test/testfiles/successful_merge_params.0rx b/src/Test/testfiles/successful_merge_params.0rx
--- a/src/Test/testfiles/successful_merge_params.0rx
+++ b/src/Test/testfiles/successful_merge_params.0rx
@@ -1,4 +1,4 @@
-@value interface Type1<#a> {}
+@value interface Type1<#a> { }
 
 @value interface Type2<#b> {
   refines Type1<#b>
@@ -9,14 +9,14 @@
 }
 
 @value interface Interface1<#j> {
-  something<#x,#y>
+  something<#x, #y>
     #x requires Type2<#j>
     #y allows Type1<#j>
   (#x) -> (#y)
 }
 
 @value interface Interface2<#k> {
-  something<#w,#z>
+  something<#w, #z>
     #w requires Type3<#k>
     #z allows Type1<#k>
   (#w) -> (#z)
@@ -26,7 +26,7 @@
   refines Interface1<#q>
   refines Interface2<#q>
 
-  @value something<#r,#s>
+  @value something<#r, #s>
     #r requires Type2<#q>
     #s allows Type2<#q>
   (#r) -> (#s)
diff --git a/src/Test/testfiles/test.0rt b/src/Test/testfiles/test.0rt
--- a/src/Test/testfiles/test.0rt
+++ b/src/Test/testfiles/test.0rt
@@ -11,5 +11,5 @@
 }
 
 define Test {
-  run () {}
+  run () { }
 }
diff --git a/src/Test/testfiles/type_duplicate_param.0rx b/src/Test/testfiles/type_duplicate_param.0rx
--- a/src/Test/testfiles/type_duplicate_param.0rx
+++ b/src/Test/testfiles/type_duplicate_param.0rx
@@ -1,1 +1,1 @@
-@type interface Type<#x|#y|#x> {}
+@type interface Type<#x|#y|#x> { }
diff --git a/src/Test/testfiles/type_interface.0rx b/src/Test/testfiles/type_interface.0rx
--- a/src/Test/testfiles/type_interface.0rx
+++ b/src/Test/testfiles/type_interface.0rx
@@ -1,4 +1,4 @@
-@type interface Type<#a,#b|#c,#d|#e,#f> {
+@type interface Type<#a, #b|#c, #d|#e, #f> {
   create () -> (optional #x)
 
   create2<#y>
diff --git a/src/Test/testfiles/valid_filter_variance.0rx b/src/Test/testfiles/valid_filter_variance.0rx
--- a/src/Test/testfiles/valid_filter_variance.0rx
+++ b/src/Test/testfiles/valid_filter_variance.0rx
@@ -1,16 +1,16 @@
-@value interface Interface1<#x|> {}
+@value interface Interface1<#x|> { }
 
 concrete Type1<#x|> {
   #x allows Interface1<#x>
 }
 
-@type interface Interface2<#x|> {}
+@type interface Interface2<#x|> { }
 
 concrete Type2<|#x> {
   #x defines Interface2<#x>
 }
 
-@value interface Interface3<#x|> {}
+@value interface Interface3<#x|> { }
 
 concrete Type3<|#x> {
   #x requires Interface3<#x>
diff --git a/src/Test/testfiles/valid_function_variance.0rx b/src/Test/testfiles/valid_function_variance.0rx
--- a/src/Test/testfiles/valid_function_variance.0rx
+++ b/src/Test/testfiles/valid_function_variance.0rx
@@ -1,4 +1,4 @@
-@value interface Type1<#x|> {}
+@value interface Type1<#x|> { }
 
 @value interface Type2<#x|> {
   // arg:    contravariant * contravariant * contravariant = contravariant
diff --git a/src/Test/testfiles/valid_self.0rx b/src/Test/testfiles/valid_self.0rx
--- a/src/Test/testfiles/valid_self.0rx
+++ b/src/Test/testfiles/valid_self.0rx
@@ -1,4 +1,4 @@
-@value interface Base<|#x> {}
+@value interface Base<|#x> { }
 
 @value interface Value<#x|#y|#z> {
   refines Base<#self>
@@ -6,11 +6,11 @@
 }
 
 concrete Child1 {
-  refines Value<all,all,all>
+  refines Value<all, all, all>
   @value call () -> (#self)
 }
 
 concrete Child2 {
-  refines Value<all,all,all>
+  refines Value<all, all, all>
   @value call () -> (all)
 }
diff --git a/src/Test/testfiles/valid_variances.0rx b/src/Test/testfiles/valid_variances.0rx
--- a/src/Test/testfiles/valid_variances.0rx
+++ b/src/Test/testfiles/valid_variances.0rx
@@ -1,6 +1,6 @@
-@value interface Object1<#x|> {}
+@value interface Object1<#x|> { }
 
-@value interface Object2<|#y> {}
+@value interface Object2<|#y> { }
 
 @value interface Object3<#w|#z> {
   refines Object1<#w>
@@ -8,7 +8,7 @@
 }
 
 @value interface Object4<#t> {
-  refines Object3<#t,#t>
+  refines Object3<#t, #t>
 }
 
 @value interface Object5<|#q> {
@@ -16,7 +16,7 @@
   refines Object1<Object1<#q>>
 }
 
-@type interface Object6<|#x> {}
+@type interface Object6<|#x> { }
 
 concrete Object7<|#s> {
   // Variance reversed twice by Object1.
diff --git a/src/Test/testfiles/value_duplicate_param.0rx b/src/Test/testfiles/value_duplicate_param.0rx
--- a/src/Test/testfiles/value_duplicate_param.0rx
+++ b/src/Test/testfiles/value_duplicate_param.0rx
@@ -1,1 +1,1 @@
-@value interface Type<#x|#y|#x> {}
+@value interface Type<#x|#y|#x> { }
diff --git a/src/Test/testfiles/value_interface.0rx b/src/Test/testfiles/value_interface.0rx
--- a/src/Test/testfiles/value_interface.0rx
+++ b/src/Test/testfiles/value_interface.0rx
@@ -1,6 +1,6 @@
-@value interface Type<#a,#b|#c,#d|#e,#f> {
+@value interface Type<#a, #b|#c, #d|#e, #f> {
   refines Parent
-  refines Other<Type2<#a>,#f>
+  refines Other<Type2<#a>, #f>
 
   get () -> (#x)
   set (#x) -> ()
diff --git a/src/Test/testfiles/value_refines_concrete.0rx b/src/Test/testfiles/value_refines_concrete.0rx
--- a/src/Test/testfiles/value_refines_concrete.0rx
+++ b/src/Test/testfiles/value_refines_concrete.0rx
@@ -1,4 +1,4 @@
-concrete Parent {}
+concrete Parent { }
 
 @value interface Child {
   refines Parent
diff --git a/src/Test/testfiles/value_refines_instance.0rx b/src/Test/testfiles/value_refines_instance.0rx
--- a/src/Test/testfiles/value_refines_instance.0rx
+++ b/src/Test/testfiles/value_refines_instance.0rx
@@ -1,4 +1,4 @@
-@type interface Parent {}
+@type interface Parent { }
 
 @value interface Child {
   refines Parent
diff --git a/src/Test/testfiles/value_refines_value.0rx b/src/Test/testfiles/value_refines_value.0rx
--- a/src/Test/testfiles/value_refines_value.0rx
+++ b/src/Test/testfiles/value_refines_value.0rx
@@ -1,4 +1,4 @@
-@value interface Parent {}
+@value interface Parent { }
 
 @value interface Child {
   refines Parent
diff --git a/src/Types/Builtin.hs b/src/Types/Builtin.hs
--- a/src/Types/Builtin.hs
+++ b/src/Types/Builtin.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -16,6 +16,7 @@
 
 -- Author: Kevin P. Barry [ta0kira@gmail.com]
 
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 module Types.Builtin (
@@ -23,29 +24,26 @@
   PrimitiveType(..),
   boolRequiredValue,
   charRequiredValue,
-  defaultCategories,
   emptyType,
   floatRequiredValue,
   formattedRequiredValue,
   intRequiredValue,
+  isIdentifierRequiredValue,
   isPointerRequiredValue,
   isOpaqueMulti,
   orderOptionalValue,
+  requiredStaticTypes,
   stringRequiredValue,
 ) where
 
-import qualified Data.Map as Map
+import qualified Data.Set as Set
 
 import Base.GeneralType
 import Base.MergeTree (reduceMergeTree)
 import Base.Positional
-import Types.TypeCategory
 import Types.TypeInstance
 
 
-defaultCategories :: CategoryMap c
-defaultCategories = Map.empty
-
 boolRequiredValue :: ValueType
 boolRequiredValue = requiredSingleton BuiltinBool
 stringRequiredValue :: ValueType
@@ -68,6 +66,13 @@
   extractSingle = reduceMergeTree (const Nothing) (const Nothing) Just
 isPointerRequiredValue _ = False
 
+isIdentifierRequiredValue :: ValueType -> Bool
+isIdentifierRequiredValue (ValueType RequiredValue t) = check $ extractSingle t where
+  check (Just (JustTypeInstance (TypeInstance BuiltinIdentifier (Positional [_])))) = True
+  check _ = False
+  extractSingle = reduceMergeTree (const Nothing) (const Nothing) Just
+isIdentifierRequiredValue _ = False
+
 emptyType :: ValueType
 emptyType = ValueType OptionalValue minBound
 
@@ -77,7 +82,8 @@
   PrimInt |
   PrimFloat |
   PrimString |
-  PrimPointer
+  PrimPointer |
+  PrimIdentifier
   deriving (Eq,Show)
 
 data ExpressionValue =
@@ -98,3 +104,19 @@
 isOpaqueMulti :: ExpressionValue -> Bool
 isOpaqueMulti (OpaqueMulti _) = True
 isOpaqueMulti _               = False
+
+requiredStaticTypes :: Set.Set CategoryName
+#ifdef darwin_HOST_OS
+-- Weak linking doesn't work on MacOS, so we need these in order to link against
+-- boxed.cpp without linker errors.
+requiredStaticTypes = Set.fromList [
+    BuiltinBool,
+    BuiltinChar,
+    BuiltinInt,
+    BuiltinFloat,
+    BuiltinPointer,
+    BuiltinIdentifier
+  ]
+#else
+requiredStaticTypes = Set.empty
+#endif
diff --git a/src/Types/DefinedCategory.hs b/src/Types/DefinedCategory.hs
--- a/src/Types/DefinedCategory.hs
+++ b/src/Types/DefinedCategory.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -143,12 +143,12 @@
   foldr (update fm) (return start) fs where
   start = Map.fromList $ map (\f -> (sfName f,f)) $ getCategoryFunctions t
   pm = getCategoryParamMap t
-  update fm f@(ScopedFunction c n t2 s as rs ps fs2 ms) fa = do
+  update fm f@(ScopedFunction c n t2 s v as rs ps fs2 ms) fa = do
     validateCategoryFunction r t f
     fa' <- fa
     case n `Map.lookup` fa' of
          Nothing -> return $ Map.insert n f fa'
-         (Just f0@(ScopedFunction c2 _ _ _ _ _ _ _ ms2)) -> do
+         (Just f0@(ScopedFunction c2 _ _ _ _ _ _ _ _ ms2)) -> do
            ("In function merge:\n---\n" ++ show f0 ++
              "\n  ->\n" ++ show f ++ "\n---\n") ??> do
               f0' <- parsedToFunctionType f0
@@ -156,7 +156,7 @@
               case s of
                    CategoryScope -> checkFunctionConvert r Map.empty Map.empty f0' f'
                    _             -> checkFunctionConvert r fm pm f0' f'
-           return $ Map.insert n (ScopedFunction (c++c2) n t2 s as rs ps fs2 ([f0]++ms++ms2)) fa'
+           return $ Map.insert n (ScopedFunction (c++c2) n t2 s v as rs ps fs2 ([f0]++ms++ms2)) fa'
 
 pairProceduresToFunctions :: (Show c, CollectErrorsM m) =>
   Map.Map FunctionName (ScopedFunction c) -> [ExecutableProcedure c] ->
@@ -189,7 +189,7 @@
                      formatFullContextBrace (epContext p) ++
                      " does not correspond to a function"
     getPair (Just f) (Just p) = do
-      processPairs_ alwaysPair (fmap pvType $ sfArgs f) (fmap inputValueName $ avNames $ epArgs p) <!!
+      processPairs_ alwaysPair (fmap (pvType . fst) $ sfArgs f) (fmap inputValueName $ avNames $ epArgs p) <!!
         ("Procedure for " ++ show (sfName f) ++
          formatFullContextBrace (avContext $ epArgs p) ++
          " has the wrong number of arguments" ++
@@ -229,13 +229,13 @@
 -- TODO: Most of this duplicates parts of flattenAllConnections.
 mergeInternalInheritance :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> DefinedCategory c -> m (CategoryMap c)
-mergeInternalInheritance tm d = "In definition of " ++ show (dcName d) ++ formatFullContextBrace (dcContext d) ??> do
+mergeInternalInheritance cm@(CategoryMap km tm) d = "In definition of " ++ show (dcName d) ++ formatFullContextBrace (dcContext d) ??> do
   let rs2 = dcRefines d
   let ds2 = dcDefines d
-  (_,t@(ValueConcrete c ns n pg ps rs ds vs fs)) <- getConcreteCategory tm (dcContext d,dcName d)
-  let c2 = ValueConcrete c ns n pg ps (rs++rs2) (ds++ds2) vs fs
+  (_,t@(ValueConcrete c ns n pg fv ps rs ds vs fs)) <- getConcreteCategory cm (dcContext d,dcName d)
+  let c2 = ValueConcrete c ns n pg fv ps (rs++rs2) (ds++ds2) vs fs
   let tm' = Map.insert (dcName d) c2 tm
-  let r = CategoryResolver tm'
+  let r = CategoryResolver (CategoryMap km tm')
   fm <- getCategoryFilterMap t
   let pm = getCategoryParamMap t
   rs2' <- fmap concat $ mapCompilerM (flattenRefine r) rs2
@@ -249,17 +249,17 @@
   pg2 <- fmap concat $ mapCompilerM getRefinesPragmas rs2
   pg3 <- fmap concat $ mapCompilerM getDefinesPragmas ds2
   let fs2 = mergeInternalFunctions fs (dcFunctions d)
-  fs' <- mergeFunctions r tm' pm fm rs' ds' fs2
-  let c2' = ValueConcrete c ns n (pg++pg2++pg3) ps rs' ds' vs fs'
+  fs' <- mergeFunctions r (CategoryMap km tm') pm fm rs' ds' fs2
+  let c2' = ValueConcrete c ns n (pg++pg2++pg3) fv ps rs' ds' vs fs'
   let tm0 = (dcName d) `Map.delete` tm
-  checkCategoryInstances tm0 [c2']
-  return $ Map.insert (dcName d) c2' tm
+  checkCategoryInstances (CategoryMap km tm0) [c2']
+  return $ CategoryMap km $ Map.insert (dcName d) c2' tm
   where
     getRefinesPragmas rf = do
-      (_,t) <- getCategory tm (vrContext rf,tiName $ vrType rf)
+      (_,t) <- getCategory cm (vrContext rf,tiName $ vrType rf)
       return $ map (prependCategoryPragmaContext $ vrContext rf) $ getCategoryPragmas t
     getDefinesPragmas df = do
-      (_,t) <- getCategory tm (vdContext df,diName $ vdType df)
+      (_,t) <- getCategory cm (vdContext df,diName $ vdType df)
       return $ map (prependCategoryPragmaContext $ vdContext df) $ getCategoryPragmas t
     mergeInternalFunctions fs1 = Map.elems . foldr single (funcMap fs1)
     funcMap = Map.fromList . map (\f -> (sfName f,f))
@@ -271,6 +271,7 @@
                sfName = sfName f,
                sfType = sfType f,
                sfScope = sfScope f,
+               sfVisibility = sfVisibility f,
                sfArgs = sfArgs f,
                sfReturns = sfReturns f,
                sfParams = sfParams f,
@@ -284,7 +285,7 @@
       validateDefinesVariance r vm Covariant t <??
         "In " ++ show t ++ formatFullContextBrace c
     flattenRefine r ra@(ValueRefine c t) = do
-      (_,t2) <- getValueCategory tm (c,tiName t)
+      (_,t2) <- getValueCategory cm (c,tiName t)
       rs <- mapCompilerM (singleRefine r ra) (getCategoryRefines t2)
       return (ra:rs)
     singleRefine r (ValueRefine c t) (ValueRefine c2 t2) = do
diff --git a/src/Types/IntegrationTest.hs b/src/Types/IntegrationTest.hs
--- a/src/Types/IntegrationTest.hs
+++ b/src/Types/IntegrationTest.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
+Copyright 2020,2023 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.
@@ -33,6 +33,7 @@
 ) where
 
 import Types.TypeCategory
+import Types.TypeInstance
 import Types.DefinedCategory
 import Types.Procedure
 
@@ -67,11 +68,13 @@
   } |
   ExpectRuntimeError {
     ereContext :: [c],
+    ereCategory :: Maybe ([c],TypeInstance),
     ereRequirePattern :: [OutputPattern],
     ereExcludePattern :: [OutputPattern]
   } |
   ExpectRuntimeSuccess {
     ersContext :: [c],
+    ersCategory :: Maybe ([c],TypeInstance),
     ersRequirePattern :: [OutputPattern],
     ersExcludePattern :: [OutputPattern]
   }
@@ -94,21 +97,21 @@
 isExpectCompilerError _                          = False
 
 isExpectRuntimeError :: ExpectedResult c -> Bool
-isExpectRuntimeError (ExpectRuntimeError _ _ _) = True
-isExpectRuntimeError _                          = False
+isExpectRuntimeError (ExpectRuntimeError _ _ _ _) = True
+isExpectRuntimeError _                            = False
 
 isExpectRuntimeSuccess :: ExpectedResult c -> Bool
-isExpectRuntimeSuccess (ExpectRuntimeSuccess _ _ _) = True
-isExpectRuntimeSuccess _                            = False
+isExpectRuntimeSuccess (ExpectRuntimeSuccess _ _ _ _) = True
+isExpectRuntimeSuccess _                              = False
 
 getRequirePattern :: ExpectedResult c -> [OutputPattern]
-getRequirePattern (ExpectCompiles _ rs _)       = rs
-getRequirePattern (ExpectCompilerError _ rs _)   = rs
-getRequirePattern (ExpectRuntimeError _ rs _)   = rs
-getRequirePattern (ExpectRuntimeSuccess _ rs _) = rs
+getRequirePattern (ExpectCompiles _ rs _)         = rs
+getRequirePattern (ExpectCompilerError _ rs _)     = rs
+getRequirePattern (ExpectRuntimeError _ _ rs _)   = rs
+getRequirePattern (ExpectRuntimeSuccess _ _ rs _) = rs
 
 getExcludePattern :: ExpectedResult c -> [OutputPattern]
-getExcludePattern (ExpectCompiles _ _ es)       = es
-getExcludePattern (ExpectCompilerError _ _ es)   = es
-getExcludePattern (ExpectRuntimeError _ _ es)   = es
-getExcludePattern (ExpectRuntimeSuccess _ _ es) = es
+getExcludePattern (ExpectCompiles _ _ es)         = es
+getExcludePattern (ExpectCompilerError _ _ es)    = es
+getExcludePattern (ExpectRuntimeError _ _ _ es)   = es
+getExcludePattern (ExpectRuntimeSuccess _ _ _ es) = es
diff --git a/src/Types/Procedure.hs b/src/Types/Procedure.hs
--- a/src/Types/Procedure.hs
+++ b/src/Types/Procedure.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2023 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.
@@ -21,6 +21,7 @@
 module Types.Procedure (
   ArgValues(..),
   Assignable(..),
+  AssignmentType(..),
   ExecutableProcedure(..),
   Expression(..),
   ExpressionStart(..),
@@ -32,6 +33,7 @@
   InputValue(..),
   InstanceOrInferred(..),
   IteratedLoop(..),
+  MacroExpression(..),
   MacroName(..),
   Operator(..),
   OutputValue(..),
@@ -42,6 +44,7 @@
   Statement(..),
   TestProcedure(..),
   TraceType(..),
+  ValueCallType(..),
   ValueLiteral(..),
   ValueOperation(..),
   VariableName(..),
@@ -180,6 +183,8 @@
   RawFailCall String |
   IgnoreValues [c] (Expression c) |
   Assignment [c] (Positional (Assignable c)) (Expression c) |
+  AssignmentEmpty [c] VariableName (Expression c) |
+  VariableSwap [c] (OutputValue c) (OutputValue c) |
   DeferredVariables [c] [Assignable c] |
   NoValueExpression [c] (VoidExpression c) |
   MarkReadOnly [c] [VariableName] |
@@ -203,6 +208,8 @@
 getStatementContext (RawFailCall _)         = []
 getStatementContext (IgnoreValues c _)      = c
 getStatementContext (Assignment c _ _)      = c
+getStatementContext (AssignmentEmpty c _ _) = c
+getStatementContext (VariableSwap c _ _)    = c
 getStatementContext (DeferredVariables c _) = c
 getStatementContext (NoValueExpression c _) = c
 getStatementContext (MarkReadOnly c _)      = c
@@ -253,7 +260,9 @@
   Literal (ValueLiteral c) |
   UnaryExpression [c] (Operator c) (Expression c) |
   InfixExpression [c] (Expression c) (Operator c) (Expression c) |
-  RawExpression ExpressionType ExpressionValue
+  RawExpression ExpressionType ExpressionValue |
+  DelegatedFunctionCall [c] (FunctionSpec c) |
+  DelegatedInitializeValue [c] (Maybe TypeInstance)
   deriving (Show)
 
 type ExpressionType = Positional ValueType
@@ -296,25 +305,33 @@
 getOperatorName (FunctionOperator _ (FunctionSpec _ _ n _)) = n
 
 getExpressionContext :: Expression c -> [c]
-getExpressionContext (Expression c _ _)        = c
-getExpressionContext (Literal l)               = getValueLiteralContext l
-getExpressionContext (UnaryExpression c _ _)   = c
-getExpressionContext (InfixExpression c _ _ _) = c
-getExpressionContext (RawExpression _ _)       = []
+getExpressionContext (Expression c _ _)             = c
+getExpressionContext (Literal l)                    = getValueLiteralContext l
+getExpressionContext (UnaryExpression c _ _)        = c
+getExpressionContext (InfixExpression c _ _ _)      = c
+getExpressionContext (RawExpression _ _)            = []
+getExpressionContext (DelegatedFunctionCall c _)    = c
+getExpressionContext (DelegatedInitializeValue c _) = c
 
 data FunctionCall c =
-  FunctionCall [c] FunctionName (Positional (InstanceOrInferred c)) (Positional (Expression c))
+  FunctionCall [c] FunctionName (Positional (InstanceOrInferred c)) (Positional (Maybe (CallArgLabel c), Expression c))
   deriving (Show)
 
+data AssignmentType =
+  AlwaysAssign |
+  AssignIfEmpty
+  deriving (Eq,Show)
+
 data ExpressionStart c =
   NamedVariable (OutputValue c) |
   NamedMacro [c] MacroName |
+  ExpressionMacro [c] MacroExpression |
   CategoryCall [c] CategoryName (FunctionCall c) |
   TypeCall [c] TypeInstanceOrParam (FunctionCall c) |
   UnqualifiedCall [c] (FunctionCall c) |
   BuiltinCall [c] (FunctionCall c) |
   ParensExpression [c] (Expression c) |
-  InlineAssignment [c] VariableName (Expression c) |
+  InlineAssignment [c] VariableName AssignmentType (Expression c) |
   InitializeValue [c] (Maybe TypeInstance) (Positional (Expression c)) |
   UnambiguousLiteral (ValueLiteral c)
   deriving (Show)
@@ -323,22 +340,27 @@
   StringLiteral [c] String |
   CharLiteral [c] Char |
   IntegerLiteral [c] Bool Integer |
-  DecimalLiteral [c] Integer Integer |
+  DecimalLiteral [c] Integer Integer Integer |
   BoolLiteral [c] Bool |
   EmptyLiteral [c]
   deriving (Show)
 
 getValueLiteralContext :: ValueLiteral c -> [c]
-getValueLiteralContext (StringLiteral c _)    = c
-getValueLiteralContext (CharLiteral c _)      = c
-getValueLiteralContext (IntegerLiteral c _ _) = c
-getValueLiteralContext (DecimalLiteral c _ _) = c
-getValueLiteralContext (BoolLiteral c _)      = c
-getValueLiteralContext (EmptyLiteral c)       = c
+getValueLiteralContext (StringLiteral c _)      = c
+getValueLiteralContext (CharLiteral c _)        = c
+getValueLiteralContext (IntegerLiteral c _ _)   = c
+getValueLiteralContext (DecimalLiteral c _ _ _) = c
+getValueLiteralContext (BoolLiteral c _)        = c
+getValueLiteralContext (EmptyLiteral c)         = c
 
+data ValueCallType =
+  AlwaysCall |
+  CallUnlessEmpty
+  deriving (Eq,Show)
+
 data ValueOperation c =
   TypeConversion [c] GeneralInstance |
-  ValueCall [c] (FunctionCall c) |
+  ValueCall [c] ValueCallType (FunctionCall c) |
   SelectReturn [c] Int
   deriving (Show)
 
@@ -352,6 +374,10 @@
   show = mnName
 
 data TraceType = NoTrace | TraceCreation deriving (Show)
+
+data MacroExpression =
+  MacroCallTrace
+  deriving (Show)
 
 data PragmaProcedure c =
   PragmaTracing {
diff --git a/src/Types/TypeCategory.hs b/src/Types/TypeCategory.hs
--- a/src/Types/TypeCategory.hs
+++ b/src/Types/TypeCategory.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
+Copyright 2019-2021,2023 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.
@@ -21,9 +21,11 @@
 
 module Types.TypeCategory (
   AnyCategory(..),
-  CategoryMap,
+  CallArgLabel(..),
+  CategoryMap(..),
   CategoryResolver(..),
   FunctionName(..),
+  FunctionVisibility(..),
   Namespace(..),
   ParamFilter(..),
   PassedValue(..),
@@ -37,8 +39,10 @@
   checkCategoryInstances,
   checkConnectedTypes,
   checkConnectionCycles,
+  checkFunctionCallVisibility,
   checkParamVariances,
   declareAllTypes, -- TODO: Remove?
+  emptyCategoryMap,
   flattenAllConnections,
   formatFullContext,
   formatFullContextBrace,
@@ -73,6 +77,7 @@
   isStaticNamespace,
   isValueConcrete,
   isValueInterface,
+  matchesCallArgLabel,
   mergeDefines,
   mergeFunctions,
   mergeInferredTypes,
@@ -85,6 +90,7 @@
   replaceSelfFunction,
   setCategoryNamespace,
   singleFromCategory,
+  toCategoryMap,
   topoSortCategories,
   uncheckedSubFunction,
   validateCategoryFunction,
@@ -129,6 +135,7 @@
     vcNamespace :: Namespace,
     vcName :: CategoryName,
     vcPragmas :: [PragmaCategory c],
+    vcVisibility :: [FunctionVisibility c],
     vcParams :: [ValueParam c],
     vcRefines :: [ValueRefine c],
     vcDefines :: [ValueDefine c],
@@ -172,7 +179,7 @@
          map (\p -> "  " ++ show p) pg ++
          map (\f -> formatInterfaceFunc f) fs) ++
       "\n}\n"
-    format (ValueConcrete cs ns n pg ps rs ds vs fs) =
+    format (ValueConcrete cs ns n pg _ ps rs ds vs fs) =
       "concrete " ++ show n ++ formatParams ps ++ namespace ns ++ " { " ++ formatContext cs ++ "\n" ++
       (intercalate "\n\n" $
          map (\p -> "  " ++ show p) pg ++
@@ -200,54 +207,59 @@
     formatConcreteFunc f = showFunctionInContext (show (sfScope f) ++ " ") "  " f
 
 getCategoryName :: AnyCategory c -> CategoryName
-getCategoryName (ValueInterface _ _ n _ _ _ _)    = n
-getCategoryName (InstanceInterface _ _ n _ _ _)   = n
-getCategoryName (ValueConcrete _ _ n _ _ _ _ _ _) = n
+getCategoryName (ValueInterface _ _ n _ _ _ _)      = n
+getCategoryName (InstanceInterface _ _ n _ _ _)     = n
+getCategoryName (ValueConcrete _ _ n _ _ _ _ _ _ _) = n
 
 getCategoryContext :: AnyCategory c -> [c]
-getCategoryContext (ValueInterface c _ _ _ _ _ _)    = c
-getCategoryContext (InstanceInterface c _ _ _ _ _)   = c
-getCategoryContext (ValueConcrete c _ _ _ _ _ _ _ _) = c
+getCategoryContext (ValueInterface c _ _ _ _ _ _)      = c
+getCategoryContext (InstanceInterface c _ _ _ _ _)     = c
+getCategoryContext (ValueConcrete c _ _ _ _ _ _ _ _ _) = c
 
 getCategoryNamespace :: AnyCategory c -> Namespace
-getCategoryNamespace (ValueInterface _ ns _ _ _ _ _)    = ns
-getCategoryNamespace (InstanceInterface _ ns _ _ _ _)   = ns
-getCategoryNamespace (ValueConcrete _ ns _ _ _ _ _ _ _) = ns
+getCategoryNamespace (ValueInterface _ ns _ _ _ _ _)      = ns
+getCategoryNamespace (InstanceInterface _ ns _ _ _ _)     = ns
+getCategoryNamespace (ValueConcrete _ ns _ _ _ _ _ _ _ _) = ns
 
 getCategoryPragmas :: AnyCategory c -> [PragmaCategory c]
-getCategoryPragmas (ValueInterface _ _ _ pg _ _ _)    = pg
-getCategoryPragmas (InstanceInterface _ _ _ pg _ _)   = pg
-getCategoryPragmas (ValueConcrete _ _ _ pg _ _ _ _ _) = pg
+getCategoryPragmas (ValueInterface _ _ _ pg _ _ _)      = pg
+getCategoryPragmas (InstanceInterface _ _ _ pg _ _)     = pg
+getCategoryPragmas (ValueConcrete _ _ _ pg _ _ _ _ _ _) = pg
 
 setCategoryNamespace :: Namespace -> AnyCategory c -> AnyCategory c
-setCategoryNamespace ns (ValueInterface c _ n pg ps rs fs)      = (ValueInterface c ns n pg ps rs fs)
-setCategoryNamespace ns (InstanceInterface c _ n pg ps fs)      = (InstanceInterface c ns n pg ps fs)
-setCategoryNamespace ns (ValueConcrete c _ n pg ps rs ds vs fs) = (ValueConcrete c ns n pg ps rs ds vs fs)
+setCategoryNamespace ns (ValueInterface c _ n pg ps rs fs)        = (ValueInterface c ns n pg ps rs fs)
+setCategoryNamespace ns (InstanceInterface c _ n pg ps fs)        = (InstanceInterface c ns n pg ps fs)
+setCategoryNamespace ns (ValueConcrete c _ n pg fv ps rs ds vs fs) = (ValueConcrete c ns n pg fv ps rs ds vs fs)
 
+getCategoryVisibilities :: AnyCategory c -> [FunctionVisibility c]
+getCategoryVisibilities (ValueInterface _ _ _ _ _ _ _)       = []
+getCategoryVisibilities (InstanceInterface _ _ _ _ _ _)      = []
+getCategoryVisibilities (ValueConcrete _ _ _ _ fv _ _ _ _ _) = fv
+
 getCategoryParams :: AnyCategory c -> [ValueParam c]
-getCategoryParams (ValueInterface _ _ _ _ ps _ _)    = ps
-getCategoryParams (InstanceInterface _ _ _ _ ps _)   = ps
-getCategoryParams (ValueConcrete _ _ _ _ ps _ _ _ _) = ps
+getCategoryParams (ValueInterface _ _ _ _ ps _ _)      = ps
+getCategoryParams (InstanceInterface _ _ _ _ ps _)     = ps
+getCategoryParams (ValueConcrete _ _ _ _ _ ps _ _ _ _) = ps
 
 getCategoryRefines :: AnyCategory c -> [ValueRefine c]
-getCategoryRefines (ValueInterface _ _ _ _ _ rs _)    = rs
-getCategoryRefines (InstanceInterface _ _ _ _ _ _)    = []
-getCategoryRefines (ValueConcrete _ _ _ _ _ rs _ _ _) = rs
+getCategoryRefines (ValueInterface _ _ _ _ _ rs _)      = rs
+getCategoryRefines (InstanceInterface _ _ _ _ _ _)      = []
+getCategoryRefines (ValueConcrete _ _ _ _ _ _ rs _ _ _) = rs
 
 getCategoryDefines :: AnyCategory c -> [ValueDefine c]
-getCategoryDefines (ValueInterface _ _ _ _ _ _ _)     = []
-getCategoryDefines (InstanceInterface _ _ _ _ _ _)    = []
-getCategoryDefines (ValueConcrete _ _ _ _ _ _ ds _ _) = ds
+getCategoryDefines (ValueInterface _ _ _ _ _ _ _)       = []
+getCategoryDefines (InstanceInterface _ _ _ _ _ _)      = []
+getCategoryDefines (ValueConcrete _ _ _ _ _ _ _ ds _ _) = ds
 
 getCategoryFilters :: AnyCategory c -> [ParamFilter c]
-getCategoryFilters (ValueInterface _ _ _ _ _ _ _)     = []
-getCategoryFilters (InstanceInterface _ _ _ _ _ _)    = []
-getCategoryFilters (ValueConcrete _ _ _ _ _ _ _ vs _) = vs
+getCategoryFilters (ValueInterface _ _ _ _ _ _ _)       = []
+getCategoryFilters (InstanceInterface _ _ _ _ _ _)      = []
+getCategoryFilters (ValueConcrete _ _ _ _ _ _ _ _ vs _) = vs
 
 getCategoryFunctions :: AnyCategory c -> [ScopedFunction c]
-getCategoryFunctions (ValueInterface _ _ _ _ _ _ fs)    = fs
-getCategoryFunctions (InstanceInterface _ _ _ _ _ fs)   = fs
-getCategoryFunctions (ValueConcrete _ _ _ _ _ _ _ _ fs) = fs
+getCategoryFunctions (ValueInterface _ _ _ _ _ _ fs)      = fs
+getCategoryFunctions (InstanceInterface _ _ _ _ _ fs)     = fs
+getCategoryFunctions (ValueConcrete _ _ _ _ _ _ _ _ _ fs) = fs
 
 singleFromCategory :: AnyCategory c -> TypeInstance
 singleFromCategory t = TypeInstance n (Positional ps) where
@@ -272,7 +284,7 @@
   fromFilter ImmutableFilter = []
   fromType (ValueType _ t2) = fromInstance t2
   fromFunction f = args ++ returns ++ filters2 where
-    args = concat $ map (fromType . pvType) $ pValues $ sfArgs f
+    args = concat $ map (fromType . pvType . fst) $ pValues $ sfArgs f
     returns = concat $ map (fromType . pvType) $ pValues $ sfReturns f
     filters2 = concat $ map (fromFilter . pfFilter) $ sfFilters f
 
@@ -285,7 +297,7 @@
 isInstanceInterface _ = False
 
 isValueConcrete :: AnyCategory c -> Bool
-isValueConcrete (ValueConcrete _ _ _ _ _ _ _ _ _) = True
+isValueConcrete (ValueConcrete _ _ _ _ _ _ _ _ _ _) = True
 isValueConcrete _ = False
 
 data Namespace =
@@ -463,18 +475,25 @@
   ParamValues -> GeneralInstance -> m GeneralInstance
 subAllParams pa = uncheckedSubInstance (getValueForParam pa)
 
-type CategoryMap c = Map.Map CategoryName (AnyCategory c)
+data CategoryMap c =
+  CategoryMap {
+    cmKnown :: Map.Map CategoryName [c],
+    cmAvailable :: Map.Map CategoryName (AnyCategory c)
+  }
 
+emptyCategoryMap :: CategoryMap c
+emptyCategoryMap = toCategoryMap []
+
+toCategoryMap :: [(CategoryName,AnyCategory c)] -> CategoryMap c
+toCategoryMap = CategoryMap Map.empty . Map.fromList
+
 getCategory :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> ([c],CategoryName) -> m ([c],AnyCategory c)
-getCategory tm (c,n) =
-  case n `Map.lookup` tm of
-       (Just t) -> return (c,t)
-       _ -> compilerErrorM $ "Type " ++ show n ++ context ++ " not found"
-  where
-    context
-      | null c = ""
-      | otherwise = formatFullContextBrace c
+getCategory (CategoryMap km tm) (c,n) = handle (n `Map.lookup` tm) (n `Map.lookup` km) where
+  handle (Just t) _ = return (c,t)
+  handle _ (Just c2) = compilerErrorM $ "Type " ++ show n ++ formatFullContextBrace c2 ++
+                                        " not visible here" ++ formatFullContextBrace c
+  handle _ _ = compilerErrorM $ "Type " ++ show n ++ " not found" ++ formatFullContextBrace c
 
 getValueCategory :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> ([c],CategoryName) -> m ([c],AnyCategory c)
@@ -520,13 +539,13 @@
 declareAllTypes :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> [AnyCategory c] -> m (CategoryMap c)
 declareAllTypes tm0 = foldr (\t tm -> tm >>= update t) (return tm0) where
-  update t tm =
+  update t (CategoryMap km tm) =
     case getCategoryName t `Map.lookup` tm of
         (Just t2) -> compilerErrorM $ "Type " ++ show (getCategoryName t) ++
                                       formatFullContextBrace (getCategoryContext t) ++
                                       " has already been declared" ++
                                       formatFullContextBrace (getCategoryContext t2)
-        _ -> return $ Map.insert (getCategoryName t) t tm
+        _ -> return $ CategoryMap km $ Map.insert (getCategoryName t) t tm
 
 getFilterMap :: CollectErrorsM m => [ValueParam c] -> [ParamFilter c] -> m ParamFilters
 getFilterMap ps fs = do
@@ -577,20 +596,20 @@
 
 checkConnectedTypes :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> [AnyCategory c] -> m ()
-checkConnectedTypes tm0 ts = do
-  tm <- declareAllTypes tm0 ts
-  collectAllM_ (map (checkSingle tm) ts)
+checkConnectedTypes cm0 ts = do
+  cm <- declareAllTypes cm0 ts
+  collectAllM_ (map (checkSingle cm) ts)
   where
-    checkSingle tm (ValueInterface c _ n _ _ rs _) = do
+    checkSingle cm (ValueInterface c _ n _ _ rs _) = do
       let ts2 = map (\r -> (vrContext r,tiName $ vrType r)) rs
-      is <- mapCompilerM (getCategory tm) ts2
+      is <- mapCompilerM (getCategory cm) ts2
       collectAllM_ (map (valueRefinesInstanceError c n) is)
       collectAllM_ (map (valueRefinesConcreteError c n) is)
-    checkSingle tm (ValueConcrete c _ n _ _ rs ds _ _) = do
+    checkSingle cm (ValueConcrete c _ n _ _ _ rs ds _ _) = do
       let ts2 = map (\r -> (vrContext r,tiName $ vrType r)) rs
       let ts3 = map (\d -> (vdContext d,diName $ vdType d)) ds
-      is1 <- mapCompilerM (getCategory tm) ts2
-      is2 <- mapCompilerM (getCategory tm) ts3
+      is1 <- mapCompilerM (getCategory cm) ts2
+      is2 <- mapCompilerM (getCategory cm) ts3
       collectAllM_ (map (concreteRefinesInstanceError c n) is1)
       collectAllM_ (map (concreteDefinesValueError c n) is2)
       collectAllM_ (map (concreteRefinesConcreteError c n) is1)
@@ -637,17 +656,18 @@
 
 checkConnectionCycles :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> [AnyCategory c] -> m ()
-checkConnectionCycles tm0 ts = collectAllM_ (map (checker []) ts) where
+checkConnectionCycles (CategoryMap km tm0) ts = collectAllM_ (map (checker []) ts) where
   tm = Map.union tm0 $ Map.fromList $ zip (map getCategoryName ts) ts
+  cm = CategoryMap km tm
   checker us (ValueInterface c _ n _ _ rs _) = do
     failIfCycle n c us
     let ts2 = map (\r -> (vrContext r,tiName $ vrType r)) rs
-    is <- mapCompilerM (getValueCategory tm) ts2
+    is <- mapCompilerM (getValueCategory cm) ts2
     collectAllM_ (map (checker (us ++ [n]) . snd) is)
-  checker us (ValueConcrete c _ n _ _ rs _ _ _) = do
+  checker us (ValueConcrete c _ n _ _ _ rs _ _ _) = do
     failIfCycle n c us
     let ts2 = map (\r -> (vrContext r,tiName $ vrType r)) rs
-    is <- mapCompilerM (getValueCategory tm) ts2
+    is <- mapCompilerM (getValueCategory cm) ts2
     collectAllM_ (map (checker (us ++ [n]) . snd) is)
   checker _ _ = return ()
   failIfCycle n c us =
@@ -658,9 +678,9 @@
 
 checkParamVariances :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> [AnyCategory c] -> m ()
-checkParamVariances tm0 ts = do
-  tm <- declareAllTypes tm0 ts
-  let r = CategoryResolver tm
+checkParamVariances cm0 ts = do
+  cm <- declareAllTypes cm0 ts
+  let r = CategoryResolver cm
   mapCompilerM_ (checkCategory r) ts
   mapCompilerM_ checkBounds ts
   where
@@ -671,11 +691,12 @@
       noDuplicates c n ps
       let vm = Map.fromList $ map (\p -> (vpParam p,vpVariance p)) ps
       collectAllM_ (map (checkRefine r vm) rs)
-    checkCategory r t@(ValueConcrete c _ n _ ps rs ds _ _) = categoryContext t ??> do
+    checkCategory r t@(ValueConcrete c _ n _ fv ps rs ds _ _) = categoryContext t ??> do
       noDuplicates c n ps
       let vm = Map.fromList $ map (\p -> (vpParam p,vpVariance p)) ps
       collectAllM_ (map (checkRefine r vm) rs)
       collectAllM_ (map (checkDefine r vm) ds)
+      collectAllM_ (map (checkVisibility r vm) fv)
     checkCategory _ t@(InstanceInterface c _ n _ ps _) = categoryContext t ??> do
       noDuplicates c n ps
     noDuplicates c n ps = collectAllM_ (map checkCount $ group $ sort $ map vpParam ps) where
@@ -689,21 +710,35 @@
     checkDefine r vm (ValueDefine c t) =
       validateDefinesVariance r vm Covariant t <??
         "In " ++ show t ++ formatFullContextBrace c
+    checkVisibility _ _ FunctionVisibilityDefault = return ()
+    checkVisibility r vm (FunctionVisibility _ ts2) =
+      collectAllM_ (map (checkVisibilitySingle r vm) ts2)
+    checkVisibilitySingle r vm (c,t) =
+      validateInstanceVariance r vm Covariant t <??
+        "In visibility " ++ show t ++ formatFullContextBrace c
 
 checkCategoryInstances :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> [AnyCategory c] -> m ()
-checkCategoryInstances tm0 ts = do
-  tm <- declareAllTypes tm0 ts
-  let r = CategoryResolver tm
+checkCategoryInstances cm0 ts = do
+  cm <- declareAllTypes cm0 ts
+  let r = CategoryResolver cm
   mapCompilerM_ (checkSingle r) ts
   where
     checkSingle r t = do
       pa <- getCategoryParamSet t
-      mapCompilerM_ (checkFilterParam pa) (getCategoryFilters t)
-      mapCompilerM_ (checkRefine r pa)    (getCategoryRefines t)
-      mapCompilerM_ (checkDefine r pa)    (getCategoryDefines t)
-      mapCompilerM_ (checkFilter r pa)    (getCategoryFilters t)
+      mapCompilerM_ (checkFilterParam pa)  (getCategoryFilters t)
+      mapCompilerM_ (checkRefine r pa)     (getCategoryRefines t)
+      mapCompilerM_ (checkDefine r pa)     (getCategoryDefines t)
+      mapCompilerM_ (checkFilter r pa)     (getCategoryFilters t)
+      mapCompilerM_ (checkVisibility r pa) (getCategoryVisibilities t)
       mapCompilerM_ (validateCategoryFunction r t) (getCategoryFunctions t)
+    checkVisibility r fm (FunctionVisibility c ts2) =
+      mapCompilerM_ (checkVisibilitySingle r fm) ts2 <??
+        "In visibility at " ++ formatFullContext c
+    checkVisibility _ _ _ = return ()
+    checkVisibilitySingle r fm (c,t) =
+      validateGeneralInstance r fm t <??
+        "In " ++ show t ++ formatFullContextBrace c
     checkFilterParam pa (ParamFilter c n _) =
       when (not $ n `Set.member` pa) $
         compilerErrorM $ "Param " ++ show n ++ formatFullContextBrace c ++ " not found"
@@ -729,7 +764,11 @@
          TypeScope     -> validatateFunctionType r pa vm funcType
          ValueScope    -> validatateFunctionType r pa vm funcType
          _             -> return ()
-    getFunctionFilterMap f >>= disallowBoundedParams where
+    getFunctionFilterMap f >>= disallowBoundedParams
+    checkVis (sfScope f) (sfVisibility f) where
+      checkVis CategoryScope va@(FunctionVisibility _ _) =
+        compilerErrorM $ "Category functions must not have restricted visibility: " ++ show va
+      checkVis _ _ = return ()
       message
         | getCategoryName t == sfType f = "In function:\n---\n" ++ show f ++ "\n---\n"
         | otherwise = "In function inherited from " ++ show (sfType f) ++
@@ -737,18 +776,18 @@
 
 topoSortCategories :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> [AnyCategory c] -> m [AnyCategory c]
-topoSortCategories tm0 ts = do
-  tm <- declareAllTypes tm0 ts
-  fmap fst $ update tm (Map.keysSet tm0) ts
+topoSortCategories cm0 ts = do
+  cm <- declareAllTypes cm0 ts
+  fmap fst $ update cm (Map.keysSet $ cmAvailable cm0) ts
   where
-    update tm ta (t:ts2) = do
+    update cm ta (t:ts2) = do
       if getCategoryName t `Set.member` ta
-         then update tm ta ts2
+         then update cm ta ts2
          else do
-           refines <- mapCompilerM (\r -> getCategory tm (vrContext r,tiName $ vrType r)) $ getCategoryRefines t
-           defines <- mapCompilerM (\d -> getCategory tm (vdContext d,diName $ vdType d)) $ getCategoryDefines t
-           (ts3,ta2) <- update tm (getCategoryName t `Set.insert` ta) (map snd $ refines ++ defines)
-           (ts4,ta3) <- update tm ta2 ts2
+           refines <- mapCompilerM (\r -> getCategory cm (vrContext r,tiName $ vrType r)) $ getCategoryRefines t
+           defines <- mapCompilerM (\d -> getCategory cm (vdContext d,diName $ vdType d)) $ getCategoryDefines t
+           (ts3,ta2) <- update cm (getCategoryName t `Set.insert` ta) (map snd $ refines ++ defines)
+           (ts4,ta3) <- update cm ta2 ts2
            return (ts3 ++ [t] ++ ts4,ta3)
     update _ ta _ = return ([],ta)
 
@@ -794,10 +833,10 @@
 
 flattenAllConnections :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> [AnyCategory c] -> m [AnyCategory c]
-flattenAllConnections tm0 ts = do
+flattenAllConnections (CategoryMap km tm0) ts = do
   -- We need to process all refines before type-checking can be done.
   tm1 <- foldr preMerge (return tm0) (reverse ts)
-  let r = CategoryResolver tm1
+  let r = CategoryResolver (CategoryMap km tm1)
   (ts',_) <- foldr (update r) (return ([],tm0)) (reverse ts)
   return ts'
   where
@@ -808,9 +847,9 @@
     preMergeSingle tm (ValueInterface c ns n pg ps rs fs) = do
       rs' <- fmap concat $ mapCompilerM (getRefines tm) rs
       return $ ValueInterface c ns n pg ps rs' fs
-    preMergeSingle tm (ValueConcrete c ns n pg ps rs ds vs fs) = do
+    preMergeSingle tm (ValueConcrete c ns n pg fv ps rs ds vs fs) = do
       rs' <- fmap concat $ mapCompilerM (getRefines tm) rs
-      return $ ValueConcrete c ns n pg ps rs' ds vs fs
+      return $ ValueConcrete c ns n pg fv ps rs' ds vs fs
     preMergeSingle _ t = return t
     update r t u = do
       (ts2,tm) <- u
@@ -827,10 +866,10 @@
       checkMerged r fm rs rs''
       pg2 <- fmap concat $ mapCompilerM (getRefinesPragmas tm) rs
       -- Only merge from direct parents.
-      fs' <- mergeFunctions r tm pm fm rs [] fs
+      fs' <- mergeFunctions r (CategoryMap km tm) pm fm rs [] fs
       return $ ValueInterface c ns n (pg++pg2) ps rs'' fs'
     -- TODO: Remove duplication below and/or have separate tests.
-    updateSingle r tm t@(ValueConcrete c ns n pg ps rs ds vs fs) = do
+    updateSingle r tm t@(ValueConcrete c ns n pg fv ps rs ds vs fs) = do
       fm <- getCategoryFilterMap t
       let pm = getCategoryParamMap t
       rs' <- fmap concat $ mapCompilerM (getRefines tm) rs
@@ -842,11 +881,11 @@
       pg2 <- fmap concat $ mapCompilerM (getRefinesPragmas tm) rs
       pg3 <- fmap concat $ mapCompilerM (getDefinesPragmas tm) ds
       -- Only merge from direct parents.
-      fs' <- mergeFunctions r tm pm fm rs ds fs
-      return $ ValueConcrete c ns n (pg++pg2++pg3) ps rs'' ds' vs fs'
+      fs' <- mergeFunctions r (CategoryMap km tm) pm fm rs ds fs
+      return $ ValueConcrete c ns n (pg++pg2++pg3) fv ps rs'' ds' vs fs'
     updateSingle _ _ t = return t
     getRefines tm ra@(ValueRefine c t@(TypeInstance n _)) = do
-      (_,v) <- getValueCategory tm (c,n)
+      (_,v) <- getValueCategory (CategoryMap km tm) (c,n)
       let refines = getCategoryRefines v
       pa <- assignParams tm c t
       fmap (ra:) $ mapCompilerM (subAll c pa) refines
@@ -854,7 +893,7 @@
       t2 <- uncheckedSubSingle (getValueForParam pa) t1
       return $ ValueRefine (c ++ c1) t2
     assignParams tm c (TypeInstance n ps) = do
-      (_,v) <- getValueCategory tm (c,n)
+      (_,v) <- getValueCategory (CategoryMap km tm) (c,n)
       let ns = map vpParam $ getCategoryParams v
       paired <- processPairs alwaysPair (Positional ns) ps
       return $ Map.insert ParamSelf selfType $ Map.fromList paired
@@ -869,31 +908,31 @@
       return ()
     checkConvert _ _ _ _ = return ()
     getRefinesPragmas tm rf = do
-      (_,t) <- getCategory tm (vrContext rf,tiName $ vrType rf)
+      (_,t) <- getCategory (CategoryMap km tm) (vrContext rf,tiName $ vrType rf)
       return $ map (prependCategoryPragmaContext $ vrContext rf) $ getCategoryPragmas t
     getDefinesPragmas tm df = do
-      (_,t) <- getCategory tm (vdContext df,diName $ vdType df)
+      (_,t) <- getCategory (CategoryMap km tm) (vdContext df,diName $ vdType df)
       return $ map (prependCategoryPragmaContext $ vdContext df) $ getCategoryPragmas t
 
 mergeFunctions :: (Show c, CollectErrorsM m, TypeResolver r) =>
   r -> CategoryMap c -> ParamValues -> ParamFilters -> [ValueRefine c] ->
   [ValueDefine c] -> [ScopedFunction c] -> m [ScopedFunction c]
-mergeFunctions r tm pm fm rs ds fs = do
-  inheritValue <- fmap concat $ mapCompilerM (getRefinesFuncs tm) rs
-  inheritType  <- fmap concat $ mapCompilerM (getDefinesFuncs tm) ds
+mergeFunctions r cm pm fm rs ds fs = do
+  inheritValue <- fmap concat $ mapCompilerM getRefinesFuncs rs
+  inheritType  <- fmap concat $ mapCompilerM getDefinesFuncs ds
   let inheritByName  = fmap (nubBy sameFunction) $ Map.fromListWith (++) $ map (\f -> (sfName f,[f])) $ inheritValue ++ inheritType
   let explicitByName = Map.fromListWith (++) $ map (\f -> (sfName f,[f])) fs
   let allNames = Set.toList $ Set.union (Map.keysSet inheritByName) (Map.keysSet explicitByName)
   mapCompilerM (mergeByName inheritByName explicitByName) allNames where
-    getRefinesFuncs tm2 (ValueRefine c (TypeInstance n ts2)) = do
-      (_,t) <- getValueCategory tm2 (c,n)
+    getRefinesFuncs (ValueRefine c (TypeInstance n ts2)) = do
+      (_,t) <- getValueCategory cm (c,n)
       let ps = map vpParam $ getCategoryParams t
       let fs2 = getCategoryFunctions t
       paired <- processPairs alwaysPair (Positional ps) ts2
       let assigned = Map.fromList $ (ParamSelf,selfType):paired
       mapCompilerM (unfixedSubFunction assigned) fs2
-    getDefinesFuncs tm2 (ValueDefine c (DefinesInstance n ts2)) = do
-      (_,t) <- getInstanceCategory tm2 (c,n)
+    getDefinesFuncs (ValueDefine c (DefinesInstance n ts2)) = do
+      (_,t) <- getInstanceCategory cm (c,n)
       let ps = map vpParam $ getCategoryParams t
       let fs2 = getCategoryFunctions t
       paired <- processPairs alwaysPair (Positional ps) ts2
@@ -915,9 +954,9 @@
                                           show (length es) ++ " times:\n---\n" ++
                                           intercalate "\n---\n" (map show es)
       | otherwise = do
-        let ff@(ScopedFunction c n2 t s as rs2 ps fa ms) = head es
+        let ff@(ScopedFunction c n2 t s v as rs2 ps fa ms) = head es
         mapCompilerM_ (checkMerge ff) is
-        return $ ScopedFunction c n2 t s as rs2 ps fa (ms ++ is)
+        return $ ScopedFunction c n2 t s v as rs2 ps fa (ms ++ is)
         where
           checkMerge f1 f2
             | sfScope f1 /= sfScope f2 =
@@ -926,11 +965,23 @@
                                show f2 ++ "\n  ->\n" ++ show f1
             | otherwise =
               "In function merge:\n---\n" ++ show f2 ++ "\n  ->\n" ++ show f1 ++ "\n---\n" ??> do
+                checkMergeVis (sfVisibility f1) (sfVisibility f2)
                 f1' <- parsedToFunctionType f1
                 f2' <- parsedToFunctionType f2
                 case sfScope f1 of
                      CategoryScope -> checkFunctionConvert r Map.empty Map.empty f2' f1'
                      _             -> checkFunctionConvert r fm pm f2' f1'
+                processPairs_ checkArgNames (sfArgs f1) (sfArgs f2)
+          checkMergeVis FunctionVisibilityDefault _ = return ()
+          checkMergeVis v1 v2 =
+            compilerErrorM $ "Cannot supersede " ++ show v2 ++ " with " ++ show v1
+          checkArgNames (_,n1) (_,n2)
+            | fmap calName n1 == fmap calName n2 = return ()
+          checkArgNames t1 t2 =
+            compilerErrorM $ "Expected arg label from " ++ showArgName t2 ++
+                            " to match " ++ showArgName t1
+          showArgName (t',Nothing) = show t'
+          showArgName (t',Just n') = show (pvType t') ++ " " ++ show n'
 
 data FunctionName =
   FunctionName {
@@ -940,24 +991,52 @@
   BuiltinReduce |
   BuiltinRequire |
   BuiltinStrong |
+  BuiltinIdentify |
   BuiltinTypename
   deriving (Eq,Ord)
 
 instance Show FunctionName where
   show (FunctionName n) = n
-  show BuiltinPresent = "present"
-  show BuiltinReduce = "reduce"
-  show BuiltinRequire = "require"
-  show BuiltinStrong = "strong"
+  show BuiltinPresent  = "present"
+  show BuiltinReduce   = "reduce"
+  show BuiltinRequire  = "require"
+  show BuiltinStrong   = "strong"
+  show BuiltinIdentify = "identify"
   show BuiltinTypename = "typename"
 
+data CallArgLabel c =
+  CallArgLabel {
+    calContext :: [c],
+    calName ::String
+  }
+  deriving (Eq,Ord)
+
+instance Show c => Show (CallArgLabel c) where
+  show (CallArgLabel c n) = n ++ formatFullContextBrace c
+
+matchesCallArgLabel :: CallArgLabel c -> String -> Bool
+matchesCallArgLabel (CallArgLabel _ n1) n2 = init n1 == n2
+
+data FunctionVisibility c =
+  FunctionVisibility {
+    fvContext :: [c],
+    fvTypes :: [([c],GeneralInstance)]
+  } |
+  FunctionVisibilityDefault
+  deriving (Eq)
+
+instance Show c => Show (FunctionVisibility c) where
+  show FunctionVisibilityDefault = "visibility _"
+  show (FunctionVisibility c ts) = "visibility " ++ intercalate ", " (map (show . snd) ts) ++ formatFullContextBrace c
+
 data ScopedFunction c =
   ScopedFunction {
     sfContext :: [c],
     sfName :: FunctionName,
     sfType :: CategoryName,
     sfScope :: SymbolScope,
-    sfArgs :: Positional (PassedValue c),
+    sfVisibility :: FunctionVisibility c,
+    sfArgs :: Positional (PassedValue c, Maybe (CallArgLabel c)),
     sfReturns :: Positional (PassedValue c),
     sfParams :: Positional (ValueParam c),
     sfFilters :: [ParamFilter c],
@@ -968,17 +1047,19 @@
   show f = showFunctionInContext (show (sfScope f) ++ " ") "" f
 
 sameFunction :: ScopedFunction c -> ScopedFunction c -> Bool
-sameFunction (ScopedFunction _ n1 t1 s1 _ _ _ _ _) (ScopedFunction _ n2 t2 s2 _ _ _ _ _) =
+sameFunction (ScopedFunction _ n1 t1 s1 _ _ _ _ _ _) (ScopedFunction _ n2 t2 s2 _ _ _ _ _ _) =
   all id [n1 == n2, t1 == t2, s1 == s2]
 
 showFunctionInContext :: Show c => String -> String -> ScopedFunction c -> String
-showFunctionInContext s indent (ScopedFunction cs n t _ as rs ps fa ms) =
+showFunctionInContext s indent (ScopedFunction cs n t _ _ as rs ps fa ms) =
   indent ++ s ++ "/*" ++ show t ++ "*/ " ++ show n ++
   showParams (pValues ps) ++ " " ++ formatContext cs ++ "\n" ++
   concat (map (\v -> indent ++ formatValue v ++ "\n") fa) ++
-  indent ++ "(" ++ intercalate "," (map (show . pvType) $ pValues as) ++ ") -> " ++
+  indent ++ "(" ++ intercalate "," (map showArg $ pValues as) ++ ") -> " ++
   "(" ++ intercalate "," (map (show . pvType) $ pValues rs) ++ ")" ++ showMerges (flatten ms)
   where
+    showArg (a,Nothing) = show (pvType a)
+    showArg (a,Just n2) = show (pvType a) ++ " " ++ show (calName n2)
     showParams [] = ""
     showParams ps2 = "<" ++ intercalate "," (map (show . vpParam) ps2) ++ ">"
     formatContext cs2 = "/*" ++ formatFullContext cs2 ++ "*/"
@@ -1001,8 +1082,8 @@
 
 parsedToFunctionType :: (Show c, CollectErrorsM m) =>
   ScopedFunction c -> m FunctionType
-parsedToFunctionType (ScopedFunction c n _ _ as rs ps fa _) = do
-  let as' = Positional $ map pvType $ pValues as
+parsedToFunctionType (ScopedFunction c n _ _ _ as rs ps fa _) = do
+  let as' = Positional $ map (pvType . fst) $ pValues as
   let rs' = Positional $ map pvType $ pValues rs
   let ps' = Positional $ map vpParam $ pValues ps
   mapCompilerM_ checkFilter fa
@@ -1027,40 +1108,71 @@
 
 unfixedSubFunction :: (Show c, CollectErrorsM m) =>
   ParamValues -> ScopedFunction c -> m (ScopedFunction c)
-unfixedSubFunction pa ff@(ScopedFunction c n t s as rs ps fa ms) =
+unfixedSubFunction pa ff@(ScopedFunction c n t s v as rs ps fa ms) =
   "In function:\n---\n" ++ show ff ++ "\n---\n" ??> do
     let unresolved = Map.fromList $ map (\n2 -> (n2,singleType $ JustParamName False n2)) $ map vpParam $ pValues ps
     let pa' = pa `Map.union` unresolved
-    as' <- fmap Positional $ mapCompilerM (subPassed pa') $ pValues as
+    as' <- fmap Positional $ mapCompilerM (subPassedNamed pa') $ pValues as
     rs' <- fmap Positional $ mapCompilerM (subPassed pa') $ pValues rs
     fa' <- mapCompilerM (subFilter pa') fa
     ms' <- mapCompilerM (uncheckedSubFunction pa) ms
-    return $ (ScopedFunction c n t s as' rs' ps fa' ms')
+    v' <- subVisibility pa' v
+    return $ (ScopedFunction c n t s v' as' rs' ps fa' ms')
     where
+      subPassedNamed pa2 (a,n2) = do
+        a2 <- subPassed pa2 a
+        return (a2,n2)
       subPassed pa2 (PassedValue c2 t2) = do
         t' <- uncheckedSubValueType (getValueForParam pa2) t2
         return $ PassedValue c2 t'
       subFilter pa2 (ParamFilter c2 n2 f) = do
         f' <- uncheckedSubFilter (getValueForParam pa2) f
         return $ ParamFilter c2 n2 f'
+      subVisibility _ FunctionVisibilityDefault = return FunctionVisibilityDefault
+      subVisibility pa2 (FunctionVisibility c2 ts) = do
+        ts' <- mapCompilerM (subVisibilitySingle pa2) ts
+        return $ FunctionVisibility c2 ts'
+      subVisibilitySingle pa2 (c2,t2) = do
+        t2' <- uncheckedSubInstance (getValueForParam pa2) t2
+        return (c2,t2')
 
 replaceSelfFunction :: (Show c, CollectErrorsM m) =>
   GeneralInstance -> ScopedFunction c -> m (ScopedFunction c)
-replaceSelfFunction self ff@(ScopedFunction c n t s as rs ps fa ms) =
+replaceSelfFunction self ff@(ScopedFunction c n t s v as rs ps fa ms) =
   "In function:\n---\n" ++ show ff ++ "\n---\n" ??> do
-    as' <- fmap Positional $ mapCompilerM subPassed $ pValues as
+    as' <- fmap Positional $ mapCompilerM subPassedNamed $ pValues as
     rs' <- fmap Positional $ mapCompilerM subPassed $ pValues rs
     fa' <- mapCompilerM subFilter fa
     ms' <- mapCompilerM (replaceSelfFunction self) ms
-    return $ (ScopedFunction c n t s as' rs' ps fa' ms')
+    v' <- subVisibility v
+    return $ (ScopedFunction c n t s v' as' rs' ps fa' ms')
     where
+      subPassedNamed (a,n2) = do
+        a2 <- subPassed a
+        return (a2,n2)
       subPassed (PassedValue c2 t2) = do
         t' <- replaceSelfValueType self t2
         return $ PassedValue c2 t'
       subFilter (ParamFilter c2 n2 f) = do
         f' <- replaceSelfFilter self f
         return $ ParamFilter c2 n2 f'
+      subVisibility FunctionVisibilityDefault = return FunctionVisibilityDefault
+      subVisibility (FunctionVisibility c2 ts) = do
+        ts' <- mapCompilerM subVisibilitySingle ts
+        return $ FunctionVisibility c2 ts'
+      subVisibilitySingle (c2,t2) = do
+        t2' <- replaceSelfInstance self t2
+        return (c2,t2')
 
+checkFunctionCallVisibility :: (Show c, CollectErrorsM m, TypeResolver r) =>
+  r -> ParamFilters -> ScopedFunction c -> GeneralInstance -> m ()
+checkFunctionCallVisibility r fs f = check (sfVisibility f) where
+  check FunctionVisibilityDefault _ = return ()
+  check (FunctionVisibility _ ts) t0 = "Cannot call " ++ show (sfName f) ++ " in context of " ++ show t0 !!>
+    collectFirstM_ (map (checkSingle t0) ts)
+  checkSingle t0 (c,t) = "In visibility " ++ show t ++ formatFullContextBrace c ??>
+    checkGeneralMatch r fs Covariant t0 t
+
 data PatternMatch =
   TypePattern {
     tpVariance :: Variance,
@@ -1098,10 +1210,10 @@
   deriving (Eq,Ord)
 
 instance Show a => Show (GuessRange a) where
-  show (GuessRange Nothing   Nothing)   = "Literally anything is possible"
-  show (GuessRange Nothing   (Just hi)) = "Something at or below " ++ show hi
-  show (GuessRange (Just lo) Nothing)   = "Something at or above " ++ show lo
-  show (GuessRange (Just lo) (Just hi)) = "Something between " ++ show lo ++ " and " ++ show hi
+  show (GuessRange Nothing   Nothing)   = "literally anything is possible"
+  show (GuessRange Nothing   (Just hi)) = "something at or below " ++ show hi
+  show (GuessRange (Just lo) Nothing)   = "something at or above " ++ show lo
+  show (GuessRange (Just lo) (Just hi)) = "something between " ++ show lo ++ " and " ++ show hi
 
 data GuessUnion =
   GuessUnion {
@@ -1130,6 +1242,7 @@
 
 mergeInferredTypes :: (CollectErrorsM m, TypeResolver r) =>
   r -> ParamFilters -> ParamFilters -> ParamValues -> MergeTree InferredTypeGuess -> m ParamValues
+mergeInferredTypes _ _ ff _ _ | null (Map.toList ff) = return Map.empty
 mergeInferredTypes r f ff ps gs0 = do
   let gs0' = mapTypeGuesses gs0
   gs1 <- mapCompilerM (\(i,is) -> fmap ((,) i) $ (reduce >=> simplifyUnion) is) $ Map.toList gs0'
@@ -1199,7 +1312,7 @@
            _                 -> tryRangeUnion (ms ++ [g2]) g1 gs
     tryRangeUnion _ _ _ = return Nothing
     takeBest [gs] = return $ Map.fromList gs
-    takeBest [] = compilerErrorM "No feasible param guesses found"
+    takeBest [] = compilerErrorM genericError
     takeBest gs = "Unable to merge alternative param guesses" !!> do
       mapCompilerM_ showAmbiguous (zip ([1..] :: [Int]) gs)
       emptyErrorM
@@ -1209,12 +1322,14 @@
       gs' <- mapCompilerM extractGuesses gs
       let mult = foldM (\xs ys -> [xs++[y] | y <- ys]) [] gs'
       let gs2 = map filterGuess mult
-      collectFirstM_ gs2 <!! "No feasible param guesses found"
+      collectFirstM_ gs2 <!! genericError
       collectAnyM gs2
+    genericError = "No guesses available for params " ++ intercalate ", " (map show $ Map.keys ff)
     filterGuess gs = checkSubFilters gs >> return gs
     extractGuesses (i,is) = do
       let is2 = map (extractSingle i) is
-      collectFirstM_ is2 <!! "No feasible guesses for param " ++ show i
+      -- resetBackgroundM prevents duplicate messages.
+      resetBackgroundM $ collectFirstM_ is2 <!! "No feasible guesses for param " ++ show i
       fmap nub $ collectAnyM is2
     extractSingle i (GuessRange (Just lo) Nothing) = return (i,lo)
     extractSingle i (GuessRange Nothing (Just hi)) = return (i,hi)
@@ -1222,7 +1337,10 @@
       p <- (Just hi) `convertsTo` (Just lo)
       if p
          then return (i,lo)
-         else compilerErrorM $ "Ambiguous guess for param " ++ show i ++ ": " ++ show g
+         else do
+           compilerBackgroundM $ "Arbitrarily using lower bound " ++ show lo ++
+                                 " for " ++ show i ++ " (" ++ show g ++ ")"
+           return (i,lo)
     extractSingle i g@(GuessRange Nothing Nothing) =
       compilerErrorM $ "Ambiguous guess for param " ++ show i ++ ": " ++ show g
     checkSubFilters gs = "In validation of inference guess: " ++ describeGuess gs ??> do
diff --git a/src/Types/TypeInstance.hs b/src/Types/TypeInstance.hs
--- a/src/Types/TypeInstance.hs
+++ b/src/Types/TypeInstance.hs
@@ -1,5 +1,5 @@
 {- -----------------------------------------------------------------------------
-Copyright 2019-2022 Kevin P. Barry
+Copyright 2019-2022,2023 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.
@@ -52,6 +52,7 @@
   getValueForParam,
   hasInferredParams,
   isDefinesFilter,
+  isOptionalValue,
   isRequiresFilter,
   isWeakValue,
   mapTypeGuesses,
@@ -124,6 +125,9 @@
 isWeakValue :: ValueType -> Bool
 isWeakValue = (== WeakValue) . vtRequired
 
+isOptionalValue :: ValueType -> Bool
+isOptionalValue = (== OptionalValue) . vtRequired
+
 requiredSingleton :: CategoryName -> ValueType
 requiredSingleton n = ValueType RequiredValue $ singleType $ JustTypeInstance $ TypeInstance n (Positional [])
 
@@ -141,22 +145,26 @@
   BuiltinFloat |
   BuiltinString |
   BuiltinPointer |
+  BuiltinIdentifier |
   BuiltinFormatted |
   BuiltinOrder |
+  BuiltinTestcase |
   CategoryNone
 
 instance Show CategoryName where
-  show (CategoryName n)    = n
-  show BuiltinBool         = "Bool"
-  show BuiltinChar         = "Char"
-  show BuiltinCharBuffer   = "CharBuffer"
-  show BuiltinInt          = "Int"
-  show BuiltinFloat        = "Float"
-  show BuiltinString       = "String"
-  show BuiltinPointer      = "Pointer"
-  show BuiltinFormatted    = "Formatted"
-  show BuiltinOrder        = "Order"
-  show CategoryNone        = "(none)"
+  show (CategoryName n)  = n
+  show BuiltinBool       = "Bool"
+  show BuiltinChar       = "Char"
+  show BuiltinCharBuffer = "CharBuffer"
+  show BuiltinInt        = "Int"
+  show BuiltinFloat      = "Float"
+  show BuiltinString     = "String"
+  show BuiltinPointer    = "Pointer"
+  show BuiltinIdentifier = "Identifier"
+  show BuiltinFormatted  = "Formatted"
+  show BuiltinOrder      = "Order"
+  show BuiltinTestcase   = "Testcase"
+  show CategoryNone      = "(none)"
 
 instance Eq CategoryName where
   c1 == c2 = show c1 == show c2
diff --git a/tests/.zeolite-module b/tests/.zeolite-module
--- a/tests/.zeolite-module
+++ b/tests/.zeolite-module
@@ -32,7 +32,6 @@
   }
 ]
 private_deps: [
-  "lib/testing"
   "visibility"
   "visibility2"
 ]
@@ -51,4 +50,4 @@
     defines: [Default]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/arg-labels.0rt b/tests/arg-labels.0rt
new file mode 100644
--- /dev/null
+++ b/tests/arg-labels.0rt
@@ -0,0 +1,308 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "call with arg labels" {
+  success
+  exclude compiler "value:"
+}
+
+unittest categoryCall {
+  \ Testing.checkEquals(Value:call(value: 1), 1)
+}
+
+unittest typeValueCall {
+  \ Testing.checkEquals(Value.new(value: 1).add(value: 10), 11)
+}
+
+concrete Value {
+  @category call (Int value:) -> (Int)
+  @type new (Int value:) -> (#self)
+  @value add (Int value:) -> (Int)
+}
+
+define Value {
+  @value Int storedValue
+
+  call (value) { return value }
+  new (value) { return Value{ value } }
+  add (value) { return storedValue+value }
+}
+
+
+testcase "call with missing required label" {
+  error
+  require compiler "value:"
+}
+
+unittest test {
+  \ Value:call(1)
+}
+
+concrete Value {
+  @category call (Int value:) -> (Int)
+}
+
+define Value {
+  call (value) { return value }
+}
+
+
+testcase "call with unexpected label" {
+  error
+  require compiler "value:"
+}
+
+unittest test {
+  \ Value:call(value: 1)
+}
+
+concrete Value {
+  @category call (Int) -> (Int)
+}
+
+define Value {
+  call (value) { return value }
+}
+
+
+testcase "call with mismatched label" {
+  error
+  require compiler "value:"
+  require compiler "number:"
+}
+
+unittest test {
+  \ Value:call(number: 1)
+}
+
+concrete Value {
+  @category call (Int value:) -> (Int)
+}
+
+define Value {
+  call (value) { return value }
+}
+
+
+testcase "merge with matched label" {
+  success
+}
+
+unittest test {
+  \ Testing.checkEquals(Value.call(value: 1), 1)
+}
+
+@type interface Base {
+  call (Int value:) -> (Int)
+}
+
+concrete Value {
+  defines Base
+
+  @type call (Int value:) -> (Int)
+}
+
+define Value {
+  call (value) { return value }
+}
+
+
+testcase "failed merge with mismatched label" {
+  error
+  require compiler "value:"
+  require compiler "number:"
+}
+
+@type interface Base {
+  call (Int value:) -> (Int)
+}
+
+concrete Value {
+  defines Base
+
+  @type call (Int number:) -> (Int)
+}
+
+define Value {
+  call (number) { return number }
+}
+
+
+testcase "failed merge with added label" {
+  error
+  require compiler "number:"
+}
+
+@type interface Base {
+  call (Int) -> (Int)
+}
+
+concrete Value {
+  defines Base
+
+  @type call (Int number:) -> (Int)
+}
+
+define Value {
+  call (number) { return number }
+}
+
+
+testcase "failed merge with removed label" {
+  error
+  require compiler "number:"
+}
+
+@type interface Base {
+  call (Int number:) -> (Int)
+}
+
+concrete Value {
+  defines Base
+
+  @type call (Int) -> (Int)
+}
+
+define Value {
+  call (number) { return number }
+}
+
+
+testcase "warning when arg name differs from arg label" {
+  compiles
+  require compiler "value:"
+  require compiler "number"
+}
+
+concrete Value {
+  @type call (Int value:) -> (Int)
+}
+
+define Value {
+  call (number) { return number }
+}
+
+
+testcase "no warning when arg with label is ignored" {
+  compiles
+  exclude compiler "value:"
+}
+
+concrete Value {
+  @type call (Int value:) -> (Int)
+}
+
+define Value {
+  call (_) { return 1 }
+}
+
+
+testcase "prefix not allowed with required labels" {
+  error
+  require compiler "value:"
+}
+
+unittest test {
+  \ `Value.call` 123
+}
+
+concrete Value {
+  @type call (Int value:) -> (Int)
+}
+
+define Value {
+  call (_) { return 1 }
+}
+
+
+testcase "infix not allowed with required labels" {
+  error
+  require compiler "value1:"
+  require compiler "value2:"
+}
+
+unittest test {
+  \ 987 `Value.call` 123
+}
+
+concrete Value {
+  @type call (Int value1:, Int value2:) -> (Int)
+}
+
+define Value {
+  call (_, _) { return 1 }
+}
+
+
+testcase "arg labels disallowed when forwarding multiple args" {
+  error
+  require compiler "value1:"
+  exclude compiler "value2:"
+}
+
+unittest test {
+  \ Value.call2(value1: Value.call1())
+}
+
+concrete Value {
+  @type call1 () -> (Int, Int)
+  @type call2 (Int value1:, Int value2:) -> ()
+}
+
+define Value {
+  call1 () { return 1, 2 }
+  call2 (_, _) { }
+}
+
+
+testcase "arg labels still checked when passing multiple returns" {
+  error
+  require compiler "value1:"
+  require compiler "value2:"
+}
+
+unittest test {
+  \ Value.call2(Value.call1())
+}
+
+concrete Value {
+  @type call1 () -> (Int, Int)
+  @type call2 (Int value1:, Int value2:) -> ()
+}
+
+define Value {
+  call1 () { return 1, 2 }
+  call2 (_, _) { }
+}
+
+
+testcase "reusing label is allowed" {
+  success
+}
+
+unittest test {
+  \ Testing.checkEquals(Value.call(positive: 1, positive: 2), 3)
+}
+
+concrete Value {
+  @type call (Int positive:, Int positive:) -> (Int)
+}
+
+define Value {
+  call (value1, value2) { return value1+value2 }
+}
diff --git a/tests/bad-path/.zeolite-module b/tests/bad-path/.zeolite-module
--- a/tests/bad-path/.zeolite-module
+++ b/tests/bad-path/.zeolite-module
@@ -1,2 +1,2 @@
 path: "/dev/null"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/builtin-types.0rt b/tests/builtin-types.0rt
--- a/tests/builtin-types.0rt
+++ b/tests/builtin-types.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020-2021 Kevin P. Barry
+Copyright 2020-2021,2023 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.
@@ -32,37 +32,37 @@
 }
 
 unittest bool {
-  if (!present(reduce<Bool,Bool>(true))) {
+  if (!present(reduce<Bool, Bool>(true))) {
     fail("Failed")
   }
 }
 
 unittest asBool {
-  if (!present(reduce<Bool,AsBool>(true))) {
+  if (!present(reduce<Bool, AsBool>(true))) {
     fail("Failed")
   }
 }
 
 unittest asChar {
-  if (present(reduce<Bool,AsChar>(true))) {
+  if (present(reduce<Bool, AsChar>(true))) {
     fail("Failed")
   }
 }
 
 unittest asInt {
-  if (!present(reduce<Bool,AsInt>(true))) {
+  if (!present(reduce<Bool, AsInt>(true))) {
     fail("Failed")
   }
 }
 
 unittest asFloat {
-  if (!present(reduce<Bool,AsFloat>(true))) {
+  if (!present(reduce<Bool, AsFloat>(true))) {
     fail("Failed")
   }
 }
 
 unittest formatted {
-  if (!present(reduce<Bool,Formatted>(true))) {
+  if (!present(reduce<Bool, Formatted>(true))) {
     fail("Failed")
   }
 }
@@ -73,37 +73,37 @@
 }
 
 unittest char {
-  if (!present(reduce<Char,Char>('a'))) {
+  if (!present(reduce<Char, Char>('a'))) {
     fail("Failed")
   }
 }
 
 unittest asBool {
-  if (!present(reduce<Char,AsBool>('a'))) {
+  if (!present(reduce<Char, AsBool>('a'))) {
     fail("Failed")
   }
 }
 
 unittest asChar {
-  if (!present(reduce<Char,AsChar>('a'))) {
+  if (!present(reduce<Char, AsChar>('a'))) {
     fail("Failed")
   }
 }
 
 unittest asInt {
-  if (!present(reduce<Char,AsInt>('a'))) {
+  if (!present(reduce<Char, AsInt>('a'))) {
     fail("Failed")
   }
 }
 
 unittest asFloat {
-  if (!present(reduce<Char,AsFloat>('a'))) {
+  if (!present(reduce<Char, AsFloat>('a'))) {
     fail("Failed")
   }
 }
 
 unittest formatted {
-  if (!present(reduce<Char,Formatted>('a'))) {
+  if (!present(reduce<Char, Formatted>('a'))) {
     fail("Failed")
   }
 }
@@ -114,37 +114,37 @@
 }
 
 unittest int {
-  if (!present(reduce<Int,Int>(1))) {
+  if (!present(reduce<Int, Int>(1))) {
     fail("Failed")
   }
 }
 
 unittest asBool {
-  if (!present(reduce<Int,AsBool>(1))) {
+  if (!present(reduce<Int, AsBool>(1))) {
     fail("Failed")
   }
 }
 
 unittest asChar {
-  if (!present(reduce<Int,AsChar>(1))) {
+  if (!present(reduce<Int, AsChar>(1))) {
     fail("Failed")
   }
 }
 
 unittest asInt {
-  if (!present(reduce<Int,AsInt>(1))) {
+  if (!present(reduce<Int, AsInt>(1))) {
     fail("Failed")
   }
 }
 
 unittest asFloat {
-  if (!present(reduce<Int,AsFloat>(1))) {
+  if (!present(reduce<Int, AsFloat>(1))) {
     fail("Failed")
   }
 }
 
 unittest formatted {
-  if (!present(reduce<Int,Formatted>(1))) {
+  if (!present(reduce<Int, Formatted>(1))) {
     fail("Failed")
   }
 }
@@ -155,37 +155,37 @@
 }
 
 unittest float {
-  if (!present(reduce<Float,Float>(1.0))) {
+  if (!present(reduce<Float, Float>(1.0))) {
     fail("Failed")
   }
 }
 
 unittest asBool {
-  if (!present(reduce<Float,AsBool>(1.0))) {
+  if (!present(reduce<Float, AsBool>(1.0))) {
     fail("Failed")
   }
 }
 
 unittest asChar {
-  if (present(reduce<Float,AsChar>(1.0))) {
+  if (present(reduce<Float, AsChar>(1.0))) {
     fail("Failed")
   }
 }
 
 unittest asInt {
-  if (!present(reduce<Float,AsInt>(1.0))) {
+  if (!present(reduce<Float, AsInt>(1.0))) {
     fail("Failed")
   }
 }
 
 unittest asFloat {
-  if (!present(reduce<Float,AsFloat>(1.0))) {
+  if (!present(reduce<Float, AsFloat>(1.0))) {
     fail("Failed")
   }
 }
 
 unittest formatted {
-  if (!present(reduce<Float,Formatted>(1.0))) {
+  if (!present(reduce<Float, Formatted>(1.0))) {
     fail("Failed")
   }
 }
@@ -196,31 +196,31 @@
 }
 
 unittest charBuffer {
-  if (!present(reduce<CharBuffer,CharBuffer>(CharBuffer.new(0)))) {
+  if (!present(reduce<CharBuffer, CharBuffer>(CharBuffer.new(0)))) {
     fail("Failed")
   }
 }
 
 unittest readAt {
-  if (!present(reduce<CharBuffer,ReadAt<Char>>(CharBuffer.new(0)))) {
+  if (!present(reduce<CharBuffer, ReadAt<Char>>(CharBuffer.new(0)))) {
     fail("Failed")
   }
-  if (present(reduce<CharBuffer,ReadAt<Int>>(CharBuffer.new(0)))) {
+  if (present(reduce<CharBuffer, ReadAt<Int>>(CharBuffer.new(0)))) {
     fail("Failed")
   }
 }
 
 unittest writeAt {
-  if (!present(reduce<CharBuffer,WriteAt<Char>>(CharBuffer.new(0)))) {
+  if (!present(reduce<CharBuffer, WriteAt<Char>>(CharBuffer.new(0)))) {
     fail("Failed")
   }
-  if (present(reduce<CharBuffer,WriteAt<Int>>(CharBuffer.new(0)))) {
+  if (present(reduce<CharBuffer, WriteAt<Int>>(CharBuffer.new(0)))) {
     fail("Failed")
   }
 }
 
 unittest container {
-  if (!present(reduce<CharBuffer,Container>(CharBuffer.new(0)))) {
+  if (!present(reduce<CharBuffer, Container>(CharBuffer.new(0)))) {
     fail("Failed")
   }
 }
@@ -231,67 +231,67 @@
 }
 
 unittest string {
-  if (!present(reduce<String,String>("a"))) {
+  if (!present(reduce<String, String>("a"))) {
     fail("Failed")
   }
 }
 
 unittest asBool {
-  if (!present(reduce<String,AsBool>("a"))) {
+  if (!present(reduce<String, AsBool>("a"))) {
     fail("Failed")
   }
 }
 
 unittest asChar {
-  if (present(reduce<String,AsChar>("a"))) {
+  if (present(reduce<String, AsChar>("a"))) {
     fail("Failed")
   }
 }
 
 unittest asInt {
-  if (present(reduce<String,AsInt>("a"))) {
+  if (present(reduce<String, AsInt>("a"))) {
     fail("Failed")
   }
 }
 
 unittest asFloat {
-  if (present(reduce<String,AsFloat>("a"))) {
+  if (present(reduce<String, AsFloat>("a"))) {
     fail("Failed")
   }
 }
 
 unittest formatted {
-  if (!present(reduce<String,Formatted>("a"))) {
+  if (!present(reduce<String, Formatted>("a"))) {
     fail("Failed")
   }
 }
 
 unittest defaultOrder {
-  if (!present(reduce<String,DefaultOrder<Char>>("a"))) {
+  if (!present(reduce<String, DefaultOrder<Char>>("a"))) {
     fail("Failed")
   }
-  if (present(reduce<String,DefaultOrder<Int>>("a"))) {
+  if (present(reduce<String, DefaultOrder<Int>>("a"))) {
     fail("Failed")
   }
 }
 
 unittest subSequence {
-  if (!present(reduce<String,SubSequence>("a"))) {
+  if (!present(reduce<String, SubSequence>("a"))) {
     fail("Failed")
   }
 }
 
 unittest readAt {
-  if (!present(reduce<String,ReadAt<Char>>("a"))) {
+  if (!present(reduce<String, ReadAt<Char>>("a"))) {
     fail("Failed")
   }
-  if (present(reduce<String,ReadAt<Int>>("a"))) {
+  if (present(reduce<String, ReadAt<Int>>("a"))) {
     fail("Failed")
   }
 }
 
 unittest container {
-  if (!present(reduce<String,Container>("a"))) {
+  if (!present(reduce<String, Container>("a"))) {
     fail("Failed")
   }
 }
@@ -306,19 +306,19 @@
 }
 
 unittest charDefault {
-  \ Testing.checkEquals(Char.default(),'\000')
+  \ Testing.checkEquals(Char.default(), '\000')
 }
 
 unittest floatDefault {
-  \ Testing.checkEquals(Float.default(),0.0)
+  \ Testing.checkEquals(Float.default(), 0.0)
 }
 
 unittest intDefault {
-  \ Testing.checkEquals(Int.default(),0)
+  \ Testing.checkEquals(Int.default(), 0)
 }
 
 unittest stringDefault {
-  \ Testing.checkEquals(String.default(),"")
+  \ Testing.checkEquals(String.default(), "")
 }
 
 unittest boolDuplicate {
@@ -326,39 +326,39 @@
 }
 
 unittest charDuplicate {
-  \ Testing.checkEquals('a'.duplicate(),'a')
+  \ Testing.checkEquals('a'.duplicate(), 'a')
 }
 
 unittest floatDuplicate {
-  \ Testing.checkEquals((1.1).duplicate(),1.1)
+  \ Testing.checkEquals((1.1).duplicate(), 1.1)
 }
 
 unittest intDuplicate {
-  \ Testing.checkEquals((123).duplicate(),123)
+  \ Testing.checkEquals((123).duplicate(), 123)
 }
 
 unittest stringDuplicate {
-  \ Testing.checkEquals("string".duplicate(),"string")
+  \ Testing.checkEquals("string".duplicate(), "string")
 }
 
 unittest boolHashed {
-  \ Testing.checkNotEquals(true.hashed(),false.hashed())
+  \ Testing.checkNotEquals(true.hashed(), false.hashed())
 }
 
 unittest charHashed {
-  \ Testing.checkNotEquals('a'.hashed(),'b'.hashed())
+  \ Testing.checkNotEquals('a'.hashed(), 'b'.hashed())
 }
 
 unittest floatHashed {
-  \ Testing.checkNotEquals((1.1).hashed(),(1.2).hashed())
+  \ Testing.checkNotEquals((1.1).hashed(), (1.2).hashed())
 }
 
 unittest intHashed {
-  \ Testing.checkNotEquals((123).hashed(),(124).hashed())
+  \ Testing.checkNotEquals((123).hashed(), (124).hashed())
 }
 
 unittest stringHashed {
-  \ Testing.checkNotEquals("string12".hashed(),"string21".hashed())
+  \ Testing.checkNotEquals("string12".hashed(), "string21".hashed())
 }
 
 unittest stringLessThan {
@@ -374,10 +374,10 @@
 }
 
 unittest stringBuilder {
-  [Append<Formatted>&Build<String>] builder <- String.builder()
-  \ Testing.checkEquals(builder.build(),"")
-  \ Testing.checkEquals(builder.append("xyz").build(),"xyz")
-  \ Testing.checkEquals(builder.append(123).build(),"xyz123")
+  [Append<Formatted> & Build<String>] builder <- String.builder()
+  \ Testing.checkEquals(builder.build(), "")
+  \ Testing.checkEquals(builder.append("xyz").build(), "xyz")
+  \ Testing.checkEquals(builder.append(123).build(), "xyz123")
 }
 
 unittest charLessThan {
@@ -534,38 +534,38 @@
 
 unittest writeAndRead {
   CharBuffer buffer <- CharBuffer.new(5)
-      .writeAt(0,'a')
-      .writeAt(1,'b')
-      .writeAt(2,'c')
-      .writeAt(3,'d')
-      .writeAt(4,'e')
+      .writeAt(0, 'a')
+      .writeAt(1, 'b')
+      .writeAt(2, 'c')
+      .writeAt(3, 'd')
+      .writeAt(4, 'e')
       // overwriting here
-      .writeAt(1,'B')
-      .writeAt(2,'C')
+      .writeAt(1, 'B')
+      .writeAt(2, 'C')
 
-  \ Testing.checkEquals(buffer.size(),5)
-  \ Testing.checkEquals(buffer.readAt(0),'a')
-  \ Testing.checkEquals(buffer.readAt(1),'B')
-  \ Testing.checkEquals(buffer.readAt(2),'C')
-  \ Testing.checkEquals(buffer.readAt(3),'d')
-  \ Testing.checkEquals(buffer.readAt(4),'e')
+  \ Testing.checkEquals(buffer.size(), 5)
+  \ Testing.checkEquals(buffer.readAt(0), 'a')
+  \ Testing.checkEquals(buffer.readAt(1), 'B')
+  \ Testing.checkEquals(buffer.readAt(2), 'C')
+  \ Testing.checkEquals(buffer.readAt(3), 'd')
+  \ Testing.checkEquals(buffer.readAt(4), 'e')
 }
 
 unittest resizeDown {
-  CharBuffer buffer <- CharBuffer.new(5).resize(3).writeAt(2,'a')
-  \ Testing.checkEquals(buffer.size(),3)
-  \ Testing.checkEquals(buffer.readAt(2),'a')
+  CharBuffer buffer <- CharBuffer.new(5).resize(3).writeAt(2, 'a')
+  \ Testing.checkEquals(buffer.size(), 3)
+  \ Testing.checkEquals(buffer.readAt(2), 'a')
 }
 
 unittest resizeUp {
-  CharBuffer buffer <- CharBuffer.new(5).resize(7).writeAt(6,'a')
-  \ Testing.checkEquals(buffer.size(),7)
-  \ Testing.checkEquals(buffer.readAt(6),'a')
+  CharBuffer buffer <- CharBuffer.new(5).resize(7).writeAt(6, 'a')
+  \ Testing.checkEquals(buffer.size(), 7)
+  \ Testing.checkEquals(buffer.readAt(6), 'a')
 }
 
 
 testcase "CharBuffer read negative" {
-  crash
+  failure
   require "-10"
   require "bounds"
 }
@@ -576,7 +576,7 @@
 
 
 testcase "CharBuffer read past end" {
-  crash
+  failure
   require "3"
   require "bounds"
 }
@@ -587,29 +587,29 @@
 
 
 testcase "CharBuffer write negative" {
-  crash
+  failure
   require "-10"
   require "bounds"
 }
 
 unittest test {
-  \ CharBuffer.new(3).writeAt(-10,'a')
+  \ CharBuffer.new(3).writeAt(-10, 'a')
 }
 
 
 testcase "CharBuffer write past end" {
-  crash
+  failure
   require "3"
   require "bounds"
 }
 
 unittest test {
-  \ CharBuffer.new(3).writeAt(3,'a')
+  \ CharBuffer.new(3).writeAt(3, 'a')
 }
 
 
 testcase "CharBuffer new negative" {
-  crash
+  failure
   require "-10"
   require "size"
 }
@@ -620,7 +620,7 @@
 
 
 testcase "CharBuffer resize negative" {
-  crash
+  failure
   require "-10"
   require "size"
 }
@@ -640,7 +640,7 @@
   if (!present(strong(value2))) {
     fail("Failed")
   }
-  [Append<Formatted>&Build<String>] builder <- String.builder().append(require(value1))
+  [Append<Formatted> & Build<String>] builder <- String.builder().append(require(value1))
   value1 <- empty
   if (present(strong(value2))) {
     fail("Failed")
@@ -661,7 +661,7 @@
 
 unittest subsequence {
   String s <- "abcde"
-  String s2 <- s.subSequence(1,3)
+  String s2 <- s.subSequence(1, 3)
   if (s2 != "bcd") {
     fail(s2)
   }
@@ -669,7 +669,7 @@
 
 unittest emptySubsequence {
   String s <- ""
-  String s2 <- s.subSequence(0,0)
+  String s2 <- s.subSequence(0, 0)
   if (s2 != "") {
     fail(s2)
   }
@@ -689,26 +689,26 @@
   String value <- "abcdefg"
 
   traverse (value.defaultOrder() -> Char c) {
-    \ Testing.checkEquals(c,value.readAt(index))
+    \ Testing.checkEquals(c, value.readAt(index))
     index <- index+1
   }
 
-  \ Testing.checkEquals(index,7)
+  \ Testing.checkEquals(index, 7)
 }
 
 unittest fromCharBuffer {
   CharBuffer buffer <- CharBuffer.new(5)
-      .writeAt(0,'a')
-      .writeAt(1,'b')
-      .writeAt(2,'c')
-      .writeAt(3,'d')
-      .writeAt(4,'e')
-  \ Testing.checkEquals(String.fromCharBuffer(buffer),"abcde")
+      .writeAt(0, 'a')
+      .writeAt(1, 'b')
+      .writeAt(2, 'c')
+      .writeAt(3, 'd')
+      .writeAt(4, 'e')
+  \ Testing.checkEquals(String.fromCharBuffer(buffer), "abcde")
 }
 
 
 testcase "String access negative" {
-  crash
+  failure
   require "-10"
   require "bounds"
 }
@@ -729,7 +729,7 @@
 
 
 testcase "String access past end" {
-  crash
+  failure
   require "3"
   require "bounds"
 }
@@ -750,7 +750,7 @@
 
 
 testcase "String subsequence past end" {
-  crash
+  failure
   require "3"
   require "size"
 }
@@ -761,7 +761,7 @@
 
 define Test {
   run () {
-    \ "abc".subSequence(1,3)
+    \ "abc".subSequence(1, 3)
   }
 }
 
@@ -775,19 +775,19 @@
 }
 
 unittest accurateMax {
-  \ Testing.checkEquals(Char.maxBound().asInt(),255)
+  \ Testing.checkEquals(Char.maxBound().asInt(), 255)
 }
 
 unittest accurateMin {
-  \ Testing.checkEquals(Char.minBound().asInt(),0)
+  \ Testing.checkEquals(Char.minBound().asInt(), 0)
 }
 
 unittest asIntIsUnsigned {
-  \ Testing.checkEquals('\xff'.asInt(),255)
+  \ Testing.checkEquals('\xff'.asInt(), 255)
 }
 
 unittest asFloatIsUnsigned {
-  \ Testing.checkEquals('\xff'.asFloat(),255.0)
+  \ Testing.checkEquals('\xff'.asFloat(), 255.0)
 }
 
 
@@ -796,21 +796,21 @@
 }
 
 unittest accurateMax {
-  \ Testing.checkEquals(Int.maxBound(),9223372036854775807)
+  \ Testing.checkEquals(Int.maxBound(), 9223372036854775807)
 }
 
 unittest accurateMin {
-  \ Testing.checkEquals(Int.minBound(),-9223372036854775808)
+  \ Testing.checkEquals(Int.minBound(), -9223372036854775808)
 }
 
 unittest asCharIsPeriodic {
-  \ Testing.checkEquals((65).asChar(),(65+256).asChar())
-  \ Testing.checkEquals((65).asChar(),(65+1024).asChar())
-  \ Testing.checkEquals((65).asChar(),(65-1024).asChar())
+  \ Testing.checkEquals((65).asChar(), (65+256).asChar())
+  \ Testing.checkEquals((65).asChar(), (65+1024).asChar())
+  \ Testing.checkEquals((65).asChar(), (65-1024).asChar())
 }
 
 unittest hexUnsigned {
-  \ Testing.checkEquals(\xffffffffffffffff,-1)
+  \ Testing.checkEquals(\xffffffffffffffff, -1)
 }
 
 
@@ -1057,4 +1057,34 @@
   if (b2 != true) {
     fail(b2)
   }
+}
+
+
+testcase "Float parsing" {
+  success
+}
+
+unittest decUnescaped {
+  \ Testing.checkEquals(123.456e-3, 123.0/1000.0+456.0/1000.0/1000.0)
+  \ Testing.checkEquals(123.456e3, 123.0*1000.0+456.0)
+}
+
+unittest decEscaped {
+  \ Testing.checkEquals(\d123.456, 123.456)
+  \ Testing.checkEquals(\D123.456, 123.456)
+}
+
+unittest hexEscaped {
+  \ Testing.checkEquals(\xff.ee, 255.0+238.0/256.0)
+  \ Testing.checkEquals(\Xff.ee, 255.0+238.0/256.0)
+}
+
+unittest octEscaped {
+  \ Testing.checkEquals(\o10.10, 8.125)
+  \ Testing.checkEquals(\O10.10, 8.125)
+}
+
+unittest binEscaped {
+  \ Testing.checkEquals(\b101.101, 5.0+5.0/8.0)
+  \ Testing.checkEquals(\B101.101, 5.0+5.0/8.0)
 }
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,3 +1,3 @@
 root: "../.."
 path: "tests/check-defs"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/check-defs/private1.0rx b/tests/check-defs/private1.0rx
--- a/tests/check-defs/private1.0rx
+++ b/tests/check-defs/private1.0rx
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-define Type {}
+define Type { }
diff --git a/tests/check-defs/private2.0rx b/tests/check-defs/private2.0rx
--- a/tests/check-defs/private2.0rx
+++ b/tests/check-defs/private2.0rx
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-define Type {}
+define Type { }
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
@@ -16,6 +16,6 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-concrete Type {}
+concrete Type { }
 
-concrete Undefined {}
+concrete Undefined { }
diff --git a/tests/cli-tests.sh b/tests/cli-tests.sh
--- a/tests/cli-tests.sh
+++ b/tests/cli-tests.sh
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 # ------------------------------------------------------------------------------
-# Copyright 2020-2022 Kevin P. Barry
+# Copyright 2020-2023 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.
@@ -84,6 +84,22 @@
 }
 
 
+test_custom_testcase() {
+  do_zeolite -p "$ZEOLITE_PATH" $PARALLEL -r tests/custom-testcase -f
+  local output=$(do_zeolite -p "$ZEOLITE_PATH" -t tests/custom-testcase 2>&1 || true)
+  if ! echo "$output" | egrep -q 'Passed.+ 0 .*Failed.+ 4 .*'; then
+    show_message 'Expected 4 failed tests and 0 passing:'
+    echo "$output" 1>&2
+    return 1
+  fi
+  if [[ $(echo "$output" | egrep -c 'custom testcase') -ne 4 ]]; then
+    show_message 'Expected 4 test failures to be related to "custom testcase":'
+    echo "$output" 1>&2
+    return 1
+  fi
+}
+
+
 require_patterns() {
   local output=$1
   local error=0
@@ -214,7 +230,7 @@
 
 
 test_leak_check() {
-  local binary="$ZEOLITE_PATH/tests/leak-check/LeakTest"
+  local binary="$ZEOLITE_PATH/tests/leak-check/LeakCheck"
   rm -f "$binary"
   do_zeolite -p "$ZEOLITE_PATH" $PARALLEL -r tests/leak-check -f
   # race-condition check
@@ -304,12 +320,12 @@
 
 test_module_only() {
   local output=$(do_zeolite -p "$ZEOLITE_PATH" -R tests/module-only -f || true)
-  if ! echo "$output" | egrep -q 'Type1 not found'; then
+  if ! echo "$output" | egrep -q 'Type1.+not found'; then
     show_message 'Expected Type1 definition error from tests/module-only:'
     echo "$output" 1>&2
     return 1
   fi
-  if echo "$output" | egrep -q 'Type2 not found'; then
+  if echo "$output" | egrep -q 'Type2.+not (found|visible)'; then
     show_message 'Unexpected Type2 definition error from tests/module-only:'
     echo "$output" 1>&2
     return 1
@@ -319,12 +335,12 @@
 
 test_module_only2() {
   local output=$(do_zeolite -p "$ZEOLITE_PATH" -R tests/module-only2 -f || true)
-  if ! echo "$output" | egrep -q 'Type1'; then
+  if ! echo "$output" | egrep -q 'Type1.+not visible'; then
     show_message 'Expected Type1 definition error from tests/module-only2:'
     echo "$output" 1>&2
     return 1
   fi
-  if echo "$output" | egrep -q 'Type2'; then
+  if echo "$output" | egrep -q 'Type2.+not (found|visible)'; then
     show_message 'Unexpected Type2 definition error from tests/module-only2:'
     echo "$output" 1>&2
     return 1
@@ -567,6 +583,7 @@
   test_bad_path
   test_bad_system_include
   test_check_defs
+  test_custom_testcase
   test_example_hello
   test_example_parser
   test_example_primes
diff --git a/tests/conditionals.0rt b/tests/conditionals.0rt
--- a/tests/conditionals.0rt
+++ b/tests/conditionals.0rt
@@ -33,7 +33,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "return if/elif/else" {
@@ -53,7 +53,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "assign if/elif condition" {
@@ -71,7 +71,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "different in if and else" {
@@ -81,8 +81,8 @@
 }
 
 define Value {
-  @value process () -> (optional Value,optional Value)
-  process () (value1,value2) {
+  @value process () -> (optional Value, optional Value)
+  process () (value1, value2) {
     if (false) {
       value1 <- empty
     } else {
@@ -91,7 +91,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "missing in if" {
@@ -111,7 +111,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "missing in elif" {
@@ -131,7 +131,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "missing in else" {
@@ -152,7 +152,7 @@
 }
 
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "missing in implicit else" {
@@ -171,7 +171,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "return set in predicate for subsequent blocks" {
@@ -190,7 +190,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 
@@ -212,14 +212,14 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "branch jump skips validation of named return" {
   compiles
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (Bool)
@@ -239,7 +239,7 @@
 
 
 testcase "crash in if" {
-  crash
+  failure
   require "empty"
 }
 
@@ -252,7 +252,7 @@
 
 
 testcase "crash in elif" {
-  crash
+  failure
   require "empty"
 }
 
@@ -270,8 +270,8 @@
 }
 
 define Value {
-  @value process () -> (optional Value,optional Value)
-  process () (value1,value2) {
+  @value process () -> (optional Value, optional Value)
+  process () (value1, value2) {
     if (false) {
       value1 <- empty
       value2 <- empty
@@ -285,7 +285,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "multi missing in if" {
@@ -294,8 +294,8 @@
 }
 
 define Value {
-  @value process () -> (optional Value,optional Value)
-  process () (value1,value2) {
+  @value process () -> (optional Value, optional Value)
+  process () (value1, value2) {
     if (false) {
       value1 <- empty
     } elif (false) {
@@ -308,7 +308,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "multi missing in elif" {
@@ -317,8 +317,8 @@
 }
 
 define Value {
-  @value process () -> (optional Value,optional Value)
-  process () (value1,value2) {
+  @value process () -> (optional Value, optional Value)
+  process () (value1, value2) {
     if (false) {
       value1 <- empty
       value2 <- empty
@@ -331,7 +331,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "multi missing in else" {
@@ -340,8 +340,8 @@
 }
 
 define Value {
-  @value process () -> (optional Value,optional Value)
-  process () (value1,value2) {
+  @value process () -> (optional Value, optional Value)
+  process () (value1, value2) {
     if (false) {
       value1 <- empty
       value2 <- empty
@@ -354,7 +354,7 @@
   }
 }
 
-concrete Value {}
+concrete Value { }
 
 
 testcase "cleanup before return in if" {
diff --git a/tests/conversions.0rt b/tests/conversions.0rt
--- a/tests/conversions.0rt
+++ b/tests/conversions.0rt
@@ -21,33 +21,33 @@
 }
 
 unittest toType {
-  \ Testing.checkEquals(Helper.getType(Value.create()?Base1),"Base1")
+  \ Testing.checkEquals(Helper.getType(Value.create()?Base1), "Base1")
 }
 
 unittest toAny {
-  \ Testing.checkEquals(Helper.getType(Value.create()?any),"any")
+  \ Testing.checkEquals(Helper.getType(Value.create()?any), "any")
 }
 
 unittest toUnion {
-  \ Testing.checkEquals(Helper.getType(Value.create()?[Base1|Base2]),"[Base1|Base2]")
+  \ Testing.checkEquals(Helper.getType(Value.create()?[Base1 | Base2]), "[Base1|Base2]")
 }
 
 unittest toIntersect {
-  \ Testing.checkEquals(Helper.getType(Value.create()?[Base1&Base2]),"[Base1&Base2]")
+  \ Testing.checkEquals(Helper.getType(Value.create()?[Base1 & Base2]), "[Base1&Base2]")
 }
 
 unittest toParam {
-  \ Testing.checkEquals(Helper.toParam<Value,Base1>(Value.create()),"Base1")
+  \ Testing.checkEquals(Helper.toParam<Value, Base1>(Value.create()), "Base1")
 }
 
 unittest toTypeOptional {
   optional Value value <- Value.create()
-  \ Testing.checkEquals(Helper.getType2(value?Base1),"Base1")
+  \ Testing.checkEquals(Helper.getType2(value?Base1), "Base1")
 }
 
 unittest toAnyOptional {
   optional Value value <- Value.create()
-  \ Testing.checkEquals(Helper.getType2(value?any),"any")
+  \ Testing.checkEquals(Helper.getType2(value?any), "any")
 }
 
 unittest convertedCall {
@@ -56,7 +56,7 @@
 }
 
 concrete Helper {
-  @type toParam<#x,#y>
+  @type toParam<#x, #y>
     #x requires #y
   (#x) -> (String)
 
@@ -95,7 +95,7 @@
 }
 
 define Value {
-  call () {}
+  call () { }
 
   create () {
     return Value{}
@@ -124,7 +124,7 @@
 }
 
 define Value {
-  call () {}
+  call () { }
 
   create () {
     return Value{}
diff --git a/tests/custom-testcase/.zeolite-module b/tests/custom-testcase/.zeolite-module
new file mode 100644
--- /dev/null
+++ b/tests/custom-testcase/.zeolite-module
@@ -0,0 +1,3 @@
+root: "../.."
+path: "tests/custom-testcase"
+mode: incremental { }
diff --git a/tests/custom-testcase/README.md b/tests/custom-testcase/README.md
new file mode 100644
--- /dev/null
+++ b/tests/custom-testcase/README.md
@@ -0,0 +1,14 @@
+# Custom `Testcase` Compiler Failures.
+
+_All_ tests this module should **always fail**. It tests that compiling a
+`testcase` with a custom `Testcase` catches type issues.
+
+To compile and run the tests:
+
+```shell
+ZEOLITE_PATH=$(zeolite --get-path)
+zeolite -p $ZEOLITE_PATH -r tests/custom-testcase
+zeolite -p $ZEOLITE_PATH -t tests/custom-testcase
+```
+
+_Every_ test should fail with an error related to `custom testcase`.
diff --git a/tests/custom-testcase/tests.0rt b/tests/custom-testcase/tests.0rt
new file mode 100644
--- /dev/null
+++ b/tests/custom-testcase/tests.0rt
@@ -0,0 +1,63 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "success with unknown type" {
+  success Foo
+}
+
+unittest test { }
+
+
+testcase "success with non-Testcase type" {
+  success String
+}
+
+unittest test { }
+
+
+testcase "success with param" {
+  success Helper<#x>
+}
+
+concrete Helper<#x> {
+  defines Testcase
+}
+
+define Helper {
+  start () { }
+  finish () { }
+}
+
+unittest test { }
+
+
+testcase "success with invalid substitution" {
+  success Helper<CharBuffer>
+}
+
+concrete Helper<#x> {
+  defines Testcase
+  #x immutable
+}
+
+define Helper {
+  start () { }
+  finish () { }
+}
+
+unittest test { }
diff --git a/tests/defer.0rt b/tests/defer.0rt
--- a/tests/defer.0rt
+++ b/tests/defer.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021-2022 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.
@@ -61,6 +61,34 @@
 }
 
 
+testcase "defer does not unset members" {
+  success
+}
+
+concrete Test {
+  @type new () -> (#self)
+  @value deferMembers () -> ()
+  @value getValueMember () -> (String)
+  @value getCategoryMember () -> (String)
+}
+
+define Test {
+  @category String categoryMember <- "abc"
+  @value    String valueMember
+
+  new () { return #self{ "def" } }
+  deferMembers () { categoryMember, valueMember <- defer }
+  getValueMember () { return valueMember }
+  getCategoryMember () { return categoryMember }
+}
+
+unittest test {
+  Test test <- Test.new()
+  \ Testing.checkEquals(test.getCategoryMember(), "abc")
+  \ Testing.checkEquals(test.getValueMember(), "def")
+}
+
+
 testcase "multiple deferred" {
   error
   require "value1"
@@ -92,7 +120,7 @@
   require "initialized"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   @value Int value
@@ -111,7 +139,7 @@
   require "initialized"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   @category Int value <- 1
@@ -131,7 +159,7 @@
   require "read-only"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   @value call (Int) -> ()
@@ -327,7 +355,7 @@
 
 
 testcase "deferred not checked in condition that jumps" {
-  crash
+  failure
   require "success"
 }
 
diff --git a/tests/delegation.0rt b/tests/delegation.0rt
new file mode 100644
--- /dev/null
+++ b/tests/delegation.0rt
@@ -0,0 +1,294 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "delegate to functions" {
+  success
+}
+
+unittest fromCategory {
+  \ Testing.checkEquals(Value:call1(13, "message"), 123-13+7)
+}
+
+unittest fromType {
+  \ Testing.checkEquals(Value.call2(13, "message"), 456-13+7)
+}
+
+unittest fromValue {
+  \ Testing.checkEquals(Value.new().call3(13, "message"), 789-13+7)
+}
+
+concrete Value {
+  @type new () -> (#self)
+  @category call1 (Int, String) -> (Int)
+  @type call2 (Int, String) -> (Int)
+  @value call3 (Int, String) -> (Int)
+}
+
+define Value {
+  new () { return #self{ } }
+
+  call1 (number1, message1) {
+    return delegate -> `delegatedTo1`
+  }
+
+  call2 (number2, message2) {
+    \ delegate -> `delegatedTo1`
+    return delegate -> `delegatedTo2`
+  }
+
+  call3 (number3, message3) {
+    \ delegate -> `delegatedTo1`
+    \ delegate -> `delegatedTo2`
+    return delegate -> `delegatedTo3`
+  }
+
+  @category delegatedTo1 (Int, String) -> (Int)
+  delegatedTo1 (number, message) {
+    return 123-number+message.size()
+  }
+
+  @type delegatedTo2 (Int, String) -> (Int)
+  delegatedTo2 (number, message) {
+    return 456-number+message.size()
+  }
+
+  @value delegatedTo3 (Int, String) -> (Int)
+  delegatedTo3 (number, message) {
+    return 789-number+message.size()
+  }
+}
+
+
+testcase "delegate to correct initializer" {
+  success
+}
+
+unittest fromCategory {
+  Value value <- Value:new1(13, "message")
+  \ Testing.checkEquals(value.getInt(), 13)
+  \ Testing.checkEquals(value.getString(), "message")
+}
+
+unittest fromType {
+  Value value <- Value.new2(13, "message")
+  \ Testing.checkEquals(value.getInt(), 13)
+  \ Testing.checkEquals(value.getString(), "message")
+}
+
+unittest fromValue {
+  Value value <- Value.new2(9, "error")
+  value <- value.new3(13, "message")
+  \ Testing.checkEquals(value.getInt(), 13)
+  \ Testing.checkEquals(value.getString(), "message")
+}
+
+concrete Value {
+  @category new1 (Int, String) -> (Value)
+  @type new2 (Int, String) -> (Value)
+  @value new3 (Int, String) -> (Value)
+  @value getInt () -> (Int)
+  @value getString () -> (String)
+}
+
+define Value {
+  @value Int number
+  @value String message
+
+  new1 (number1, message1) {
+    return delegate -> Value
+  }
+
+  new2 (number2, message2) {
+    return delegate -> Value
+  }
+
+  new3 (number3, message3) {
+    return delegate -> Value
+  }
+
+  getInt () { return number }
+  getString () { return message }
+}
+
+
+testcase "delegate to #self" {
+  success
+}
+
+unittest fromType {
+  Value value <- Value.new1(13, "message")
+  \ Testing.checkEquals(value.getInt(), 13)
+  \ Testing.checkEquals(value.getString(), "message")
+}
+
+unittest fromValue {
+  Value value <- Value.new1(9, "error")
+  value <- value.new2(13, "message")
+  \ Testing.checkEquals(value.getInt(), 13)
+  \ Testing.checkEquals(value.getString(), "message")
+}
+
+concrete Value {
+  @type new1 (Int, String) -> (Value)
+  @value new2 (Int, String) -> (Value)
+  @value getInt () -> (Int)
+  @value getString () -> (String)
+}
+
+define Value {
+  @value Int number
+  @value String message
+
+  new1 (number1, message1) {
+    return delegate -> #self
+  }
+
+  new2 (number2, message2) {
+    return delegate -> #self
+  }
+
+  getInt () { return number }
+  getString () { return message }
+}
+
+
+testcase "delegate fails from unittest" {
+  error
+  require compiler "delegation"
+}
+
+unittest test {
+  \ delegate -> `Value:new`
+}
+
+concrete Value {
+  @category new () -> (Value)
+}
+
+define Value {
+  new () {
+    return Value{ }
+  }
+}
+
+
+testcase "delegate fails from @category variable initializer" {
+  error
+  require compiler "delegation"
+}
+
+concrete Value { }
+
+define Value {
+  @category Value value <- delegate -> Value
+}
+
+
+testcase "delegate fails with ignored args" {
+  error
+  require compiler "ignore"
+}
+
+concrete Value {
+  @category new (Int, String) -> (Value)
+}
+
+define Value {
+  new (value, _) {
+    return delegate -> `delegatedTo`
+  }
+
+  @category delegatedTo (Int, String) -> (Value)
+  delegatedTo (number, message) {
+    return Value{ }
+  }
+}
+
+
+testcase "delegate fails with hidden args" {
+  error
+  require compiler "message"
+  require compiler "hidden"
+}
+
+concrete Value {
+  @category new (Int, String) -> (Value)
+}
+
+define Value {
+  new (value, message) {
+    $Hidden[message]$
+    return delegate -> `delegatedTo`
+  }
+
+  @category delegatedTo (Int, String) -> (Value)
+  delegatedTo (number, message) {
+    return Value{ }
+  }
+}
+
+
+testcase "delegate fails with missing label" {
+  error
+  require compiler "message:"
+}
+
+concrete Value {
+  @category new (Int, String) -> (Value)
+}
+
+define Value {
+  new (number, message) {
+    return delegate -> `delegatedTo`
+  }
+
+  @category delegatedTo (Int, String message:) -> (Value)
+  delegatedTo (number, message) {
+    return Value{ }
+  }
+}
+
+
+testcase "delegate passes along labels" {
+  success
+}
+
+unittest test {
+  \ Testing.checkEquals(Value:new(13, message: "message").getString(), "message")
+}
+
+concrete Value {
+  @category new (Int, String message:) -> (Value)
+  @value getString () -> (String)
+}
+
+define Value {
+  @value Int number
+  @value String message
+
+  new (number, message) {
+    return delegate -> `delegatedTo`
+  }
+
+  getString () { return message }
+
+  @category delegatedTo (Int, String message:) -> (Value)
+  delegatedTo (number, message) {
+    return delegate -> Value
+  }
+}
diff --git a/tests/expr-lookup.0rt b/tests/expr-lookup.0rt
--- a/tests/expr-lookup.0rt
+++ b/tests/expr-lookup.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
+Copyright 2020,2023 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.
@@ -27,7 +27,7 @@
 
 
 testcase "MODULE_PATH is absolute" {
-  crash
+  failure
   require "Failed condition: /.+/tests"
 }
 
@@ -37,7 +37,7 @@
 
 
 testcase "MODULE_PATH from regular source" {
-  crash
+  failure
   require "Failed condition: /.+/tests"
 }
 
@@ -57,7 +57,7 @@
 
 
 testcase "macro used inline in expressions" {
-  crash
+  failure
   require "Failed condition: /.+/tests is the path"
 }
 
@@ -71,7 +71,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals($ExprLookup[INT_EXPR]$,4)
+  \ Testing.checkEquals($ExprLookup[INT_EXPR]$, 4)
   // Use it a second time to ensure that reserving the macro name doesn't
   // carry over to the next statement.
   \ $ExprLookup[INT_EXPR]$
@@ -83,7 +83,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(ExprLookup.intExpr(),4)
+  \ Testing.checkEquals(ExprLookup.intExpr(), 4)
 }
 
 
@@ -99,7 +99,7 @@
   @category String macroLocalVar <- "hello"
 
   run () {
-    \ Testing.checkEquals($ExprLookup[LOCAL_VAR]$,"hello")
+    \ Testing.checkEquals($ExprLookup[LOCAL_VAR]$, "hello")
   }
 }
 
@@ -124,7 +124,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(ExprLookup.localVar(),99)
+  \ Testing.checkEquals(ExprLookup.localVar(), 99)
 }
 
 
@@ -133,7 +133,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals($ExprLookup[META_VAR]$,20)
+  \ Testing.checkEquals($ExprLookup[META_VAR]$, 20)
 }
 
 
@@ -142,17 +142,18 @@
 }
 
 unittest test {
-  \ Testing.checkEquals($ExprLookup[INT_EXPR]$.formatted(),"4")
+  \ Testing.checkEquals($ExprLookup[INT_EXPR]$.formatted(), "4")
 }
 
 
 testcase "source context" {
-  crash
-  require "tests/expr-lookup\.0rt"
+  failure
+  require "Failed condition: .*tests/expr-lookup\.0rt"
 }
 
 unittest test {
-  fail($SourceContext$)
+  Formatted context <- $SourceContext$
+  fail(context)
 }
 
 
@@ -167,4 +168,22 @@
 
 unittest test {
   String value <- $ExprLookup[USES_RECURSIVE]$
+}
+
+
+testcase "call trace" {
+  failure
+  require "Failed condition: .*tests/expr-lookup\.0rt .*testcase"
+}
+
+unittest test {
+  optional Order<Formatted> trace <- $CallTrace$
+  \ Testing.checkPresent(trace)
+  \ Testing.checkPresent(trace&.next())
+  String combined <- String.builder()
+      .append(require(trace).get())
+      .append(" ")
+      .append(require(trace&.next()).get())
+      .build()
+  fail(combined)
 }
diff --git a/tests/extension.0rt b/tests/extension.0rt
--- a/tests/extension.0rt
+++ b/tests/extension.0rt
@@ -21,5 +21,5 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Extension.execute(),"message")
+  \ Testing.checkEquals(Extension.execute(), "message")
 }
diff --git a/tests/filters.0rt b/tests/filters.0rt
--- a/tests/filters.0rt
+++ b/tests/filters.0rt
@@ -31,7 +31,7 @@
 }
 
 define Value {
-  something () {}
+  something () { }
 }
 
 
@@ -42,13 +42,13 @@
   require compiler "[Ll]ower.+ \[Type3\|Type4\]"
 }
 
-@value interface Type1 {}
+@value interface Type1 { }
 
-@value interface Type2 {}
+@value interface Type2 { }
 
-@value interface Type3 {}
+@value interface Type3 { }
 
-@value interface Type4 {}
+@value interface Type4 { }
 
 concrete Value<#x> {
   #x requires Type1
@@ -57,7 +57,7 @@
   #x allows   Type4
 }
 
-define Value {}
+define Value { }
 
 
 testcase "implicit bound from reversing another filter" {
@@ -67,46 +67,46 @@
   require compiler "[Ll]ower.+ #y"
 }
 
-@value interface Type {}
+@value interface Type { }
 
-concrete Value<#x,#y> {
+concrete Value<#x, #y> {
   #x requires Type
   #y requires #x
 }
 
-define Value {}
+define Value { }
 
 
 testcase "defines filter is not an upper bound" {
   compiles
 }
 
-@type interface Type1 {}
+@type interface Type1 { }
 
-@value interface Type2 {}
+@value interface Type2 { }
 
 concrete Value<#x> {
   #x defines Type1
   #x allows  Type2
 }
 
-define Value {}
+define Value { }
 
 
 testcase "defines filter is not a lower bound" {
   compiles
 }
 
-@type interface Type1 {}
+@type interface Type1 { }
 
-@value interface Type2 {}
+@value interface Type2 { }
 
 concrete Value<#x> {
   #x defines  Type1
   #x requires Type2
 }
 
-define Value {}
+define Value { }
 
 
 testcase "function param bounded on both sides" {
@@ -114,9 +114,9 @@
   require compiler "#x.+bound"
 }
 
-@value interface Type1 {}
+@value interface Type1 { }
 
-@value interface Type2 {}
+@value interface Type2 { }
 
 @value interface Value {
   call<#x>
@@ -138,7 +138,7 @@
 }
 
 define Type {
-  call () {}
+  call () { }
 
   @category call2 () -> ()
   call2 () {
@@ -159,7 +159,7 @@
 }
 
 define Type {
-  call () {}
+  call () { }
 
   @category call2 () -> ()
   call2 () {
@@ -180,7 +180,7 @@
 }
 
 define Type {
-  call () {}
+  call () { }
 
   @category call2 () -> ()
   call2 () {
@@ -255,7 +255,7 @@
 }
 
 define Type {
-  call () {}
+  call () { }
 
   @category call2 () -> ()
   call2 () {
@@ -276,7 +276,7 @@
 }
 
 define Type {
-  call () {}
+  call () { }
 
   @category call2 () -> ()
   call2 () {
@@ -297,7 +297,7 @@
 }
 
 define Type {
-  call () {}
+  call () { }
 
   @category call2 () -> ()
   call2 () {
@@ -311,7 +311,7 @@
 }
 
 concrete Type1 {
-  @type call<#i,#j,#k>
+  @type call<#i, #j, #k>
     #i requires Formatted
     #j defines Equals<#j>
     #k allows Int
@@ -319,10 +319,10 @@
 }
 
 define Type1 {
-  call () {}
+  call () { }
 }
 
-concrete Type2<#x,#y,#z> {
+concrete Type2<#x, #y, #z> {
   #x requires Formatted
   #y defines Equals<#y>
   #z allows Int
@@ -332,7 +332,7 @@
 
 define Type2 {
   call () {
-    \ Type1.call<#x,#y,#z>()
+    \ Type1.call<#x, #y, #z>()
   }
 }
 
@@ -347,7 +347,7 @@
   #x requires String
 }
 
-define Type {}
+define Type { }
 
 concrete Test {
   @type call<#x>
@@ -356,7 +356,7 @@
 }
 
 define Test {
-  call (_) {}
+  call (_) { }
 
   @type call2 () -> ()
   call2 () {
@@ -370,40 +370,40 @@
   compiles
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
-  @category call1<#x,#y>
+  @category call1<#x, #y>
     #y immutable
     #x defines LessThan<#x>
     #x requires Formatted
     #y allows Int
-  (#x,#y) -> ()
-  call1 (x,y) {
-    \ call1<#x,#y>(x,y)
+  (#x, #y) -> ()
+  call1 (x, y) {
+    \ call1<#x, #y>(x, y)
   }
 
-  @type call2<#x,#y>
+  @type call2<#x, #y>
     #y immutable
     #x defines LessThan<#x>
     #x requires Formatted
     #y allows Int
-  (#x,#y) -> ()
-  call2 (x,y) {
-    \ call2<#x,#y>(x,y)
-    \ call1<#x,#y>(x,y)
+  (#x, #y) -> ()
+  call2 (x, y) {
+    \ call2<#x, #y>(x, y)
+    \ call1<#x, #y>(x, y)
   }
 
-  @value call3<#x,#y>
+  @value call3<#x, #y>
     #y immutable
     #x defines LessThan<#x>
     #x requires Formatted
     #y allows Int
-  (#x,#y) -> ()
-  call3 (x,y) {
-    \ call3<#x,#y>(x,y)
-    \ call2<#x,#y>(x,y)
-    \ call1<#x,#y>(x,y)
+  (#x, #y) -> ()
+  call3 (x, y) {
+    \ call3<#x, #y>(x, y)
+    \ call2<#x, #y>(x, y)
+    \ call1<#x, #y>(x, y)
   }
 }
 
@@ -412,17 +412,17 @@
   compiles
 }
 
-concrete Test<#x,#y> {}
+concrete Test<#x, #y> { }
 
 define Test {
-  @category call<#x,#y>
+  @category call<#x, #y>
     #y immutable
     #x defines LessThan<#x>
     #x requires Formatted
     #y allows Int
-  (#x,#y) -> ()
-  call (x,y) {
-    \ call<#x,#y>(x,y)
+  (#x, #y) -> ()
+  call (x, y) {
+    \ call<#x, #y>(x, y)
   }
 }
 
@@ -437,7 +437,7 @@
 
 concrete Test {
   @type call<#x>
-    #x allows [Formatted&AsBool]
+    #x allows [Formatted & AsBool]
   () -> (#x)
 }
 
@@ -462,7 +462,7 @@
 
 concrete Test {
   @type call<#x>
-    #x allows [Formatted&AsBool]
+    #x allows [Formatted & AsBool]
   () -> (#x)
 }
 
@@ -483,7 +483,7 @@
 
 concrete Test {
   @type call<#x>
-    #x requires [Int|String]
+    #x requires [Int | String]
   (#x) -> ()
 }
 
@@ -507,7 +507,7 @@
 
 concrete Test {
   @type call<#x>
-    #x requires [Int|String]
+    #x requires [Int | String]
   (#x) -> ()
 }
 
diff --git a/tests/freshness/.zeolite-module b/tests/freshness/.zeolite-module
--- a/tests/freshness/.zeolite-module
+++ b/tests/freshness/.zeolite-module
@@ -4,4 +4,4 @@
   "tests/freshness/sub1"
   "tests/../tests/freshness/sub2"
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/freshness/reverse/.zeolite-module b/tests/freshness/reverse/.zeolite-module
--- a/tests/freshness/reverse/.zeolite-module
+++ b/tests/freshness/reverse/.zeolite-module
@@ -3,4 +3,4 @@
 private_deps: [
   "../../../tests/freshness"
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/function-calls.0rt b/tests/function-calls.0rt
--- a/tests/function-calls.0rt
+++ b/tests/function-calls.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020-2021 Kevin P. Barry
+Copyright 2020-2021,2023 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.
@@ -24,7 +24,7 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value1.create()
+  [Value1 | Value2] value <- Value1.create()
   \ value.get()
 }
 
@@ -72,7 +72,7 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value1.create()
+  [Value1 | Value2] value <- Value1.create()
   \ value.get()
 }
 
@@ -119,7 +119,7 @@
 }
 
 unittest test {
-  [Int|String] value <- 1
+  [Int | String] value <- 1
   \ value.asFloat()
 }
 
@@ -129,8 +129,8 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value1.create()
-  \ Testing.checkEquals(value.get().formatted(),"1")
+  [Value1 | Value2] value <- Value1.create()
+  \ Testing.checkEquals(value.get().formatted(), "1")
 }
 
 @value interface Base<|#x> {
@@ -177,7 +177,7 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value1.create()
+  [Value1 | Value2] value <- Value1.create()
   \ value.get()+1
 }
 
@@ -221,8 +221,8 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value1.create()
-  \ Testing.checkEquals(value?Base<Formatted>.get().formatted(),"1")
+  [Value1 | Value2] value <- Value1.create()
+  \ Testing.checkEquals(value?Base<Formatted>.get().formatted(), "1")
 }
 
 @value interface Base<|#x> {
@@ -280,7 +280,7 @@
 }
 
 unittest test {
-  [Base<AsBool>&Base<Formatted>] value <- Value1.create()
+  [Base<AsBool> & Base<Formatted>] value <- Value1.create()
   \ value.get()
 }
 
@@ -326,7 +326,7 @@
 }
 
 unittest test {
-  [AsBool&Formatted] value <- 1
+  [AsBool & Formatted] value <- 1
   \ value.asFloat()
 }
 
@@ -336,8 +336,8 @@
 }
 
 unittest test {
-  [Base<Int>&Base<Formatted>] value <- Value1.create()
-  \ Testing.checkEquals(value.get(),1)
+  [Base<Int> & Base<Formatted>] value <- Value1.create()
+  \ Testing.checkEquals(value.get(), 1)
 }
 
 @value interface Base<|#x> {
@@ -380,8 +380,8 @@
 }
 
 unittest test {
-  [Base<AsBool>&Base<Formatted>] value <- Value1.create()
-  \ Testing.checkEquals(value?Base<Formatted>.get().formatted(),"1")
+  [Base<AsBool> & Base<Formatted>] value <- Value1.create()
+  \ Testing.checkEquals(value?Base<Formatted>.get().formatted(), "1")
 }
 
 @value interface Base<|#x> {
@@ -436,7 +436,7 @@
 }
 
 define Value {
-  call () {}
+  call () { }
 }
 
 concrete Test {
@@ -469,7 +469,7 @@
   }
 }
 
-concrete Test {}
+concrete Test { }
 
 
 
@@ -493,7 +493,7 @@
 }
 
 define Value {
-  call () {}
+  call () { }
 
   create () {
     return Value{}
@@ -530,7 +530,7 @@
   }
 }
 
-concrete Test {}
+concrete Test { }
 
 
 testcase "convert arg" {
@@ -552,7 +552,7 @@
 }
 
 define Value {
-  call () {}
+  call () { }
 
   create () {
     return Value{}
@@ -575,11 +575,11 @@
   require "does not refine Value"
 }
 
-@value interface Base {}
+@value interface Base { }
 
-concrete Value {}
+concrete Value { }
 
-define Value {}
+define Value { }
 
 define Test {
   @type convert (Base) -> (Value)
@@ -588,7 +588,7 @@
   }
 }
 
-concrete Test {}
+concrete Test { }
 
 
 testcase "bad instance in param" {
@@ -606,14 +606,14 @@
   #x defines Equals<#x>
 }
 
-define Value {}
+define Value { }
 
 concrete Call {
   @type call<#x> () -> ()
 }
 
 define Call {
-  call () {}
+  call () { }
 }
 
 
@@ -629,7 +629,7 @@
   }
 }
 
-concrete Test {}
+concrete Test { }
 
 
 testcase "self in @category function" {
@@ -644,7 +644,7 @@
   }
 }
 
-concrete Test {}
+concrete Test { }
 
 
 testcase "large dispatch tables" {
@@ -652,47 +652,47 @@
 }
 
 unittest callDispatch {
-  \ Testing.checkEquals(Type.new().get(),00)
-  \ Testing.checkEquals(Type.new().get01(),01)
-  \ Testing.checkEquals(Type.new().get02(),02)
-  \ Testing.checkEquals(Type.new().get03(),03)
-  \ Testing.checkEquals(Type.new().get04(),04)
-  \ Testing.checkEquals(Type.new().get05(),05)
-  \ Testing.checkEquals(Type.new().get06(),06)
-  \ Testing.checkEquals(Type.new().get07(),07)
-  \ Testing.checkEquals(Type.new().get08(),08)
-  \ Testing.checkEquals(Type.new().get09(),09)
-  \ Testing.checkEquals(Type.new().get10(),10)
-  \ Testing.checkEquals(Type.new().get11(),11)
-  \ Testing.checkEquals(Type.new().get12(),12)
-  \ Testing.checkEquals(Type.new().get13(),13)
-  \ Testing.checkEquals(Type.new().get14(),14)
-  \ Testing.checkEquals(Type.new().get15(),15)
-  \ Testing.checkEquals(Type.new().get16(),16)
-  \ Testing.checkEquals(Type.new().get17(),17)
-  \ Testing.checkEquals(Type.new().get18(),18)
+  \ Testing.checkEquals(Type.new().get(), 00)
+  \ Testing.checkEquals(Type.new().get01(), 01)
+  \ Testing.checkEquals(Type.new().get02(), 02)
+  \ Testing.checkEquals(Type.new().get03(), 03)
+  \ Testing.checkEquals(Type.new().get04(), 04)
+  \ Testing.checkEquals(Type.new().get05(), 05)
+  \ Testing.checkEquals(Type.new().get06(), 06)
+  \ Testing.checkEquals(Type.new().get07(), 07)
+  \ Testing.checkEquals(Type.new().get08(), 08)
+  \ Testing.checkEquals(Type.new().get09(), 09)
+  \ Testing.checkEquals(Type.new().get10(), 10)
+  \ Testing.checkEquals(Type.new().get11(), 11)
+  \ Testing.checkEquals(Type.new().get12(), 12)
+  \ Testing.checkEquals(Type.new().get13(), 13)
+  \ Testing.checkEquals(Type.new().get14(), 14)
+  \ Testing.checkEquals(Type.new().get15(), 15)
+  \ Testing.checkEquals(Type.new().get16(), 16)
+  \ Testing.checkEquals(Type.new().get17(), 17)
+  \ Testing.checkEquals(Type.new().get18(), 18)
 }
 
 unittest reduceBuiltin {
-  \ Testing.checkPresent(reduce<Type,Type>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base01>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base02>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base03>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base04>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base05>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base06>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base07>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base08>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base09>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base10>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base11>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base12>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base13>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base14>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base15>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base16>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base17>(Type.new()))
-  \ Testing.checkPresent(reduce<Type,Base18>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Type>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base01>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base02>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base03>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base04>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base05>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base06>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base07>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base08>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base09>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base10>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base11>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base12>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base13>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base14>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base15>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base16>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base17>(Type.new()))
+  \ Testing.checkPresent(reduce<Type, Base18>(Type.new()))
 }
 
 concrete Type {
@@ -774,7 +774,7 @@
   execute (Formatted) -> ()
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type test<#x>
@@ -799,7 +799,7 @@
   execute (Formatted) -> ()
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type test<#x>
@@ -826,7 +826,7 @@
   execute () -> (String)
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type test<#x>
@@ -853,7 +853,7 @@
   execute () -> (String)
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type test<#x>
@@ -871,12 +871,12 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Value.create().call(1,"mess",2,"age"),"message")
+  \ Testing.checkEquals(Value.create().call(1, "mess", 2, "age"), "message")
 }
 
 concrete Value {
   @type create () -> (Value)
-  @value call (Int,String,any,Formatted) -> (String)
+  @value call (Int, String, any, Formatted) -> (String)
 }
 
 define Value {
@@ -884,7 +884,7 @@
     return Value{}
   }
 
-  call (_,x,_,y) {
+  call (_, x, _, y) {
     return x+y.formatted()
   }
 }
@@ -895,19 +895,19 @@
 }
 
 unittest sameType {
-  \ Testing.checkEquals(Value<Int>.sameType(),"Value<Int>")
+  \ Testing.checkEquals(Value<Int>.sameType(), "Value<Int>")
 }
 
 unittest sameValue {
-  \ Testing.checkEquals(Value<Int>.create().sameValue(),"Value<Int>")
+  \ Testing.checkEquals(Value<Int>.create().sameValue(), "Value<Int>")
 }
 
 unittest diffType {
-  \ Testing.checkEquals(Value<Int>.diffType<String>(),"Value<String>")
+  \ Testing.checkEquals(Value<Int>.diffType<String>(), "Value<String>")
 }
 
 unittest diffValue {
-  \ Testing.checkEquals(Value<Int>.create().diffValue<String>(),"Value<String>")
+  \ Testing.checkEquals(Value<Int>.create().diffValue<String>(), "Value<String>")
 }
 
 concrete Value<#x> {
@@ -942,5 +942,99 @@
 
   diffValue () {
     return Value<#y>.call()
+  }
+}
+
+
+testcase "cannot use @category function as @type function" {
+  error
+  require "something"
+  require "category"
+}
+
+concrete Test { }
+
+define Test {
+  @category something () -> ()
+  something () { }
+
+  @category test () -> ()
+  test () {
+    \ Test.something()
+  }
+}
+
+
+testcase "cannot use @category function as @value function" {
+  error
+  require "something"
+  require "category"
+}
+
+concrete Test { }
+
+define Test {
+  @category something () -> ()
+  something () { }
+
+  @category test () -> ()
+  test () {
+    \ Test{ }.something()
+  }
+}
+
+
+testcase "cannot use @type function as @value function" {
+  error
+  require "something"
+  require "value"
+}
+
+concrete Test { }
+
+define Test {
+  @type something () -> ()
+  something () { }
+
+  @category test () -> ()
+  test () {
+    \ Test{ }.something()
+  }
+}
+
+
+testcase "cannot use @value function as @type function" {
+  error
+  require "something"
+  require "type"
+}
+
+concrete Test { }
+
+define Test {
+  @value something () -> ()
+  something () { }
+
+  @category test () -> ()
+  test () {
+    \ Test.something()
+  }
+}
+
+
+testcase "cannot implicitly use @value function as @type function" {
+  error
+  require "something"
+}
+
+concrete Test { }
+
+define Test {
+  @value something () -> ()
+  something () { }
+
+  @type test () -> ()
+  test () {
+    \ something()
   }
 }
diff --git a/tests/function-merging.0rt b/tests/function-merging.0rt
--- a/tests/function-merging.0rt
+++ b/tests/function-merging.0rt
@@ -50,7 +50,7 @@
   require "Interface2"
 }
 
-@value interface Interface {}
+@value interface Interface { }
 
 @value interface Interface2 {
   refines Interface
@@ -108,7 +108,7 @@
   require "Interface2"
 }
 
-@value interface Interface {}
+@value interface Interface { }
 
 @value interface Interface2 {
   refines Interface
@@ -144,14 +144,14 @@
   call (String) -> ()
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   refines Base0
   refines Base1
 
   @value call (Formatted) -> ()
-  call (_) {}
+  call (_) { }
 }
 
 
@@ -175,7 +175,7 @@
   refines Base1
 
   @value call (Formatted) -> ()
-  call (_) {}
+  call (_) { }
 }
 
 
@@ -200,7 +200,7 @@
 
 define Type {
   @value call (any) -> ()
-  call (_) {}
+  call (_) { }
 }
 
 
@@ -224,7 +224,7 @@
   refines Base0
   refines Base1
 
-  call (_) {}
+  call (_) { }
 }
 
 
@@ -252,7 +252,7 @@
 
 define Type {
   @value call (Formatted) -> ()
-  call (_) {}
+  call (_) { }
 }
 
 
@@ -280,7 +280,7 @@
   refines Base0
   refines Base1
 
-  call (_) {}
+  call (_) { }
 }
 
 
@@ -316,7 +316,7 @@
   refines Base3
 
   @value call (any) -> ()
-  call (_) {}
+  call (_) { }
 }
 
 
@@ -356,5 +356,5 @@
   refines Base2
   refines Base3
 
-  call (_) {}
+  call (_) { }
 }
diff --git a/tests/function-visibility.0rt b/tests/function-visibility.0rt
new file mode 100644
--- /dev/null
+++ b/tests/function-visibility.0rt
@@ -0,0 +1,419 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "visibility bad types" {
+  error
+  require "visibility"
+  require "#x"
+  require "Foo"
+}
+
+concrete Test {
+  visibility Append<#x>, Foo
+}
+
+define Test { }
+
+
+testcase "visibility filters not checked" {
+  compiles
+}
+
+concrete Test<#x> {
+  #x requires Order<#x>
+
+  visibility Test<Int>
+}
+
+define Test { }
+
+
+testcase "visibility disallows contravariant" {
+  error
+  require "visibility"
+  require "#x"
+  require "covariant"
+}
+
+concrete Test<#x|> {
+  visibility #x
+}
+
+define Test { }
+
+
+testcase "visibility allows covariant" {
+  compiles
+}
+
+concrete Test<|#x> {
+  visibility #x
+}
+
+define Test { }
+
+
+testcase "visibility allowed types" {
+  compiles
+}
+
+concrete Test<#x> {
+  visibility [Int | Char], #self
+  visibility _
+  visibility #x, Test<#x>
+}
+
+define Test { }
+
+
+testcase "restricted visibility not allowed for @category" {
+  error
+  require "visibility"
+  require "foo"
+}
+
+concrete Test {
+  visibility all
+
+  @category foo () -> ()
+}
+
+define Test {
+  foo () { }
+}
+
+
+testcase "restricted visibility not allowed for merge" {
+  error
+  require "visibility"
+  require "default"
+}
+
+concrete Test {
+  defines Default
+
+  visibility all
+
+  @type default () -> (Test)
+}
+
+define Test {
+  default () { return #self{ } }
+}
+
+
+testcase "restricted visibility not broken by internal merge" {
+  compiles
+}
+
+concrete Test {
+  visibility all
+
+  @type default () -> (Test)
+}
+
+define Test {
+  @type default () -> (Test)
+  default () {
+    return #self{ }
+  }
+}
+
+
+testcase "restricted visibility ignored in own category" {
+  compiles
+}
+
+concrete Test {
+  visibility all
+
+  @type default () -> (Test)
+}
+
+define Test {
+  default () {
+    return #self{ }
+  }
+
+  @type foo () -> ()
+  foo () {
+    \ default()
+  }
+
+  @category bar () -> ()
+  bar () {
+    \ Test.default()
+  }
+}
+
+
+testcase "restricted visibility ignored in unittest" {
+  success
+}
+
+unittest test {
+  \ Test.default()
+}
+
+concrete Test {
+  visibility all
+
+  @type default () -> (Test)
+}
+
+define Test {
+  default () {
+    return #self{ }
+  }
+}
+
+
+testcase "visibilty checks correct conversion direction" {
+  compiles
+}
+
+@value interface Base<|#x> { }
+
+concrete Test {
+  visibility Base<Formatted>
+
+  @type call () -> ()
+}
+
+define Test {
+  call () { }
+}
+
+concrete Value {
+  refines Base<String>
+
+  @type test () -> ()
+}
+
+define Value {
+  test () {
+    \ Test.call()
+  }
+}
+
+
+testcase "visibilty checked in @category function" {
+  error
+  require "visibility"
+  require "call"
+}
+
+concrete Test {
+  visibility Value
+
+  @type call () -> ()
+}
+
+define Test {
+  call () { }
+}
+
+concrete Value {
+  @category test () -> ()
+}
+
+define Value {
+  test () {
+    \ Test.call()
+  }
+}
+
+
+testcase "visibilty checked in @category member" {
+  error
+  require "visibility"
+  require "call"
+}
+
+concrete Test {
+  visibility Value
+
+  @type call () -> (Int)
+}
+
+define Test {
+  call () {
+    return 1
+  }
+}
+
+concrete Value { }
+
+define Value {
+  @category Int value <- Test.call()
+}
+
+
+testcase "visibilty checked in @category function with reset" {
+  compiles
+}
+
+concrete Test {
+  visibility Value
+  visibility _
+
+  @type call () -> ()
+}
+
+define Test {
+  call () { }
+}
+
+concrete Value {
+  @category test () -> ()
+}
+
+define Value {
+  test () {
+    \ Test.call()
+  }
+}
+
+
+testcase "visibility handles #self" {
+  error
+  require "visibility"
+  require "Order<Test>"
+}
+
+concrete Test {
+  visibility Order<#self>
+
+  @type call () -> ()
+}
+
+define Test {
+  call () { }
+}
+
+concrete Value {
+  refines Order<Int>
+}
+
+define Value {
+  next () {
+    \ Test.call()
+    return empty
+  }
+
+  get () {
+    return 1
+  }
+}
+
+
+testcase "visibility handles params" {
+  error
+  require "visibility"
+  require "Append<Int>"
+}
+
+concrete Test<#x> {
+  visibility Append<#x>
+
+  @type call () -> ()
+}
+
+define Test {
+  call () { }
+}
+
+concrete Value {
+  refines Append<String>
+}
+
+define Value {
+  append (_) {
+    \ Test<Int>.call()
+    return self
+  }
+}
+
+
+testcase "visibilty allows any" {
+  compiles
+}
+
+@value interface Base { }
+
+concrete Test {
+  visibility String, Int, Base, Bool
+
+  @type call () -> ()
+}
+
+define Test {
+  call () { }
+}
+
+concrete Value {
+  refines Base
+
+  @type test () -> ()
+}
+
+define Value {
+  test () {
+    \ Test.call()
+  }
+}
+
+
+testcase "conversion can change visibility" {
+  success
+}
+
+unittest test {
+  \ Test.test()
+}
+
+@value interface Base<|#x> { }
+
+concrete Value<|#x> {
+  @type new () -> (#self)
+
+  visibility Base<#x>
+
+  @value call () -> ()
+}
+
+define Value {
+  new () {
+    return #self{ }
+  }
+
+  call () { }
+}
+
+concrete Test {
+  refines Base<Formatted>
+
+  @type test () -> ()
+}
+
+define Test {
+  test () {
+    Value<Int> value <- Value<Int>.new()
+    \ value?Value<Formatted>.call()
+  }
+}
diff --git a/tests/identify.0rt b/tests/identify.0rt
new file mode 100644
--- /dev/null
+++ b/tests/identify.0rt
@@ -0,0 +1,275 @@
+/* -----------------------------------------------------------------------------
+Copyright 2023 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 "identifier comparisons" {
+  success
+}
+
+unittest boxed {
+  String value1 <- "value"
+  String value2 <- "value"
+  Identifier<String> id1 <- `identify` value1
+  Identifier<String> id2 <- `identify` value2
+  $Hidden[value1, value2]$
+
+  \ Testing.checkTrue((id1 < id2) ^ (id2 < id1))
+  \ Testing.checkTrue((id1 > id2) ^ (id2 > id1))
+  \ Testing.checkTrue((id1 <= id2) ^ (id2 <= id1))
+  \ Testing.checkTrue((id1 >= id2) ^ (id2 >= id1))
+
+  \ Testing.checkTrue(id1 <= id1)
+  \ Testing.checkTrue(id1 >= id1)
+  \ Testing.checkTrue(id1 == id1)
+
+  \ Testing.checkFalse(id1 == id2)
+  \ Testing.checkTrue(id1 != id2)
+}
+
+unittest unboxed {
+  Int value1 <- 1
+  Int value2 <- 2
+  Int value3 <- 1
+  Identifier<Int> id1 <- identify(value1)
+  Identifier<Int> id2 <- identify(value2)
+  Identifier<Int> id3 <- identify(value3)
+  $Hidden[value1, value2, value3]$
+
+  \ Testing.checkTrue((id1 < id2) ^ (id2 < id1))
+  \ Testing.checkTrue((id1 > id2) ^ (id2 > id1))
+  \ Testing.checkTrue((id1 <= id2) ^ (id2 <= id1))
+  \ Testing.checkTrue((id1 >= id2) ^ (id2 >= id1))
+
+  \ Testing.checkTrue(id1 <= id1)
+  \ Testing.checkTrue(id1 >= id1)
+  \ Testing.checkTrue(id1 == id1)
+
+  \ Testing.checkFalse(id1 == id2)
+  \ Testing.checkTrue(id1 != id2)
+
+  \ Testing.checkTrue(id1 == id3)
+}
+
+unittest differentTypes {
+  String value1 <- "value"
+  Int value2 <- 2
+  Identifier<String> id1 <- identify(value1)
+  Identifier<Int> id2 <- identify(value2)
+  $Hidden[value1, value2]$
+
+  \ Testing.checkTrue((id1 < id2) ^ (id2 < id1))
+  \ Testing.checkTrue((id1 > id2) ^ (id2 > id1))
+  \ Testing.checkTrue((id1 <= id2) ^ (id2 <= id1))
+  \ Testing.checkTrue((id1 >= id2) ^ (id2 >= id1))
+
+  \ Testing.checkFalse(id1 == id2)
+  \ Testing.checkTrue(id1 != id2)
+}
+
+unittest hashing {
+  String value1 <- "value"
+  String value2 <- "value"
+  Identifier<String> id1 <- `identify` value1
+  Identifier<String> id2 <- `identify` value2
+  $Hidden[value1, value2]$
+
+  \ Testing.checkEquals(id1.hashed(), id1.hashed())
+  \ Testing.checkNotEquals(id1.hashed(), id2.hashed())
+}
+
+unittest meta {
+  String value1 <- "value"
+  Int value2 <- 2
+  Identifier<String> id1 <- identify(value1)
+  Identifier<Int> id2 <- identify(value2)
+  $Hidden[value1, value2]$
+
+  \ Testing.checkTrue(id1 == `identify` id1)
+  \ Testing.checkTrue(id2 == `identify` id2)
+  \ Testing.checkFalse(`identify` id1 == `identify` id2)
+}
+
+unittest formatted {
+  String value1 <- "value"
+  String value2 <- "value"
+  Identifier<String> id1 <- `identify` value1
+  Identifier<String> id2 <- `identify` value2
+  $Hidden[value1, value2]$
+
+  \ Testing.checkEquals(id1.formatted(), id1.formatted())
+  \ Testing.checkNotEquals(id1.formatted(), id2.formatted())
+
+  \ Testing.checkEquals((`identify` empty).formatted(), "0000000000000000")
+}
+
+unittest equals {
+  String value1 <- "value"
+  String value2 <- "value"
+  Identifier<String> id1 <- `identify` value1
+  Identifier<String> id2 <- `identify` value2
+  $Hidden[value1, value2]$
+
+  \ Testing.checkTrue(id1 `Identifier<any>.equals` id1)
+  \ Testing.checkFalse(id1 `Identifier<any>.equals` id2)
+}
+
+unittest lessThan {
+  String value1 <- "value"
+  String value2 <- "value"
+  Identifier<String> id1 <- `identify` value1
+  Identifier<String> id2 <- `identify` value2
+  $Hidden[value1, value2]$
+
+  \ Testing.checkFalse(id1 `Identifier<any>.lessThan` id1)
+  \ Testing.checkTrue((id1 `Identifier<any>.lessThan` id2) ^ (id2 `Identifier<any>.lessThan` id1))
+}
+
+unittest swap {
+  String value1 <- "value"
+  String value2 <- "value"
+  Identifier<String> id1 <- `identify` value1
+  Identifier<String> id2 <- `identify` value2
+  $Hidden[value1, value2]$
+  Identifier<String> id3 <- id1
+
+  \ Testing.checkTrue(id1 == id3)
+  \ Testing.checkFalse(id2 == id3)
+  id1 <-> id2
+  \ Testing.checkFalse(id1 == id3)
+  \ Testing.checkTrue(id2 == id3)
+}
+
+unittest reduceTest {
+  Identifier<String> id <- `identify` "value"
+  \ Testing.checkEquals<Identifier<any>>(reduce<Identifier<String>, Identifier<Formatted>>(id), id)
+  \ Testing.checkEquals<Identifier<any>>(reduce<Identifier<String>, Identifier<Int>>(id), empty)
+  \ Testing.checkEquals<Identifier<any>>(reduce<Identifier<Formatted>, Identifier<String>>(id), empty)
+}
+
+
+testcase "bad identifier conversion" {
+  error
+  require "Identifier<String>"
+}
+
+unittest test {
+  Identifier<Int> value <- identify("value")
+}
+
+
+testcase "identifier variable storage" {
+  success
+}
+
+unittest test {
+  \ Test.new().run()
+}
+
+concrete Test {
+  @type new () -> (Test)
+  @value run () -> ()
+}
+
+define Test {
+  @value Identifier<String> valueId
+  @value optional Identifier<String> optionalValueId
+  @value weak Identifier<String> weakValueId
+
+  @category Identifier<String> categoryId <- `identify` "value"
+  @category optional Identifier<String> optionalCategoryId <- categoryId
+  @category weak Identifier<String> weakCategoryId <- categoryId
+
+  new () {
+    Identifier<String> valueId <- `identify` "value"
+    return Test{ valueId, valueId, valueId }
+  }
+
+  run () {
+    // @value vs. local
+    scoped {
+      Identifier<String> localId <- valueId
+      optional Identifier<String> optionalLocalId <- optionalValueId
+      weak Identifier<String> weakLocalId <- weakValueId
+    } in {
+      \ Testing.checkEquals<Identifier<any>>(localId, valueId)
+      \ Testing.checkEquals<Identifier<any>>(optionalLocalId, optionalValueId)
+      \ Testing.checkEquals<Identifier<any>>(`strong` weakLocalId, `strong` weakLocalId)
+    }
+    // @category vs. local
+    scoped {
+      Identifier<String> localId <- categoryId
+      optional Identifier<String> optionalLocalId <- optionalCategoryId
+      weak Identifier<String> weakLocalId <- weakCategoryId
+    } in {
+      \ Testing.checkEquals<Identifier<any>>(localId, categoryId)
+      \ Testing.checkEquals<Identifier<any>>(optionalLocalId, optionalCategoryId)
+      \ Testing.checkEquals<Identifier<any>>(`strong` weakLocalId, `strong` weakLocalId)
+    }
+  }
+}
+
+
+testcase "disallow identify on weak" {
+  error
+  require "[Ww]eak"
+}
+
+unittest test {
+  weak String value <- empty
+  \ identify(value)
+}
+
+
+testcase "disallow identify with params" {
+  error
+  require "param"
+}
+
+unittest test {
+  \ identify<Formatted>("value")
+}
+
+
+testcase "too many args to identify" {
+  error
+  require "arg"
+}
+
+unittest test {
+  \ identify("value", 123)
+}
+
+
+testcase "too many returns passed to to identify" {
+  error
+  require "return"
+  require "arg"
+}
+
+unittest test {
+  \ identify(Helper.foo())
+}
+
+concrete Helper {
+  @type foo () -> (Int, Int)
+}
+
+define Helper {
+  foo () {
+    return 1, 2
+  }
+}
diff --git a/tests/immutable.0rt b/tests/immutable.0rt
--- a/tests/immutable.0rt
+++ b/tests/immutable.0rt
@@ -168,7 +168,7 @@
   require "value2"
 }
 
-@value interface Foo {}
+@value interface Foo { }
 
 concrete Test {
   immutable
@@ -204,16 +204,16 @@
   require "value"
 }
 
-@value interface Foo {}
+@value interface Foo { }
 
-@value interface Bar {}
+@value interface Bar { }
 
 concrete Test {
   immutable
 }
 
 define Test {
-  @value [Foo&Bar] value
+  @value [Foo & Bar] value
 }
 
 
@@ -221,7 +221,7 @@
   compiles
 }
 
-@value interface Foo {}
+@value interface Foo { }
 
 @value interface Bar {
   immutable
@@ -232,7 +232,7 @@
 }
 
 define Test {
-  @value [Foo&Bar] value
+  @value [Foo & Bar] value
 }
 
 
@@ -242,7 +242,7 @@
   require "value"
 }
 
-@value interface Foo {}
+@value interface Foo { }
 
 @value interface Bar {
   immutable
@@ -253,7 +253,7 @@
 }
 
 define Test {
-  @value [Foo|Bar] value
+  @value [Foo | Bar] value
 }
 
 
@@ -274,7 +274,7 @@
 }
 
 define Test {
-  @value [Foo|Bar] value
+  @value [Foo | Bar] value
 }
 
 
@@ -373,7 +373,7 @@
   require "Foo"
 }
 
-@value interface Foo {}
+@value interface Foo { }
 
 concrete Test<#x> {
   #x immutable
@@ -421,7 +421,7 @@
   require "Foo"
 }
 
-@value interface Foo {}
+@value interface Foo { }
 
 concrete Test {
   @type call<#x>
@@ -474,9 +474,9 @@
   immutable
 }
 
-@value interface Foo {}
+@value interface Foo { }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   refines Base
@@ -497,9 +497,9 @@
   immutable
 }
 
-@value interface Foo {}
+@value interface Foo { }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   defines Base
diff --git a/tests/inference.0rt b/tests/inference.0rt
--- a/tests/inference.0rt
+++ b/tests/inference.0rt
@@ -21,23 +21,23 @@
 }
 
 unittest test {
-  Int value1, String value2 <- Test.get<?,?>(10,"message")
-  \ Testing.checkEquals<?>(value1,10)
-  \ Testing.checkEquals<?>(value2,"message")
+  Int value1, String value2 <- Test.get<?, ?>(10, "message")
+  \ Testing.checkEquals<?>(value1, 10)
+  \ Testing.checkEquals<?>(value2, "message")
 }
 
 unittest testImplicit {
-  Int value1, String value2 <- Test.get(10,"message")
-  \ Testing.checkEquals<?>(value1,10)
-  \ Testing.checkEquals<?>(value2,"message")
+  Int value1, String value2 <- Test.get(10, "message")
+  \ Testing.checkEquals<?>(value1, 10)
+  \ Testing.checkEquals<?>(value2, "message")
 }
 
 concrete Test {
-  @type get<#x,#y> (#x,#y) -> (#x,#y)
+  @type get<#x, #y> (#x, #y) -> (#x, #y)
 }
 
 define Test {
-  get (x,y) {
+  get (x, y) {
     return x, y
   }
 }
@@ -50,15 +50,15 @@
 }
 
 unittest test {
-  Int value1, String value2 <- Test.get<?>(10,"message")
+  Int value1, String value2 <- Test.get<?>(10, "message")
 }
 
 concrete Test {
-  @type get<#x,#y> (#x,#y) -> (#x,#y)
+  @type get<#x, #y> (#x, #y) -> (#x, #y)
 }
 
 define Test {
-  get (x,y) {
+  get (x, y) {
     return x, y
   }
 }
@@ -192,7 +192,7 @@
 }
 
 unittest test {
-  Type<Int> value <- Test.get<?>(Type<Int>.create(),"bad")
+  Type<Int> value <- Test.get<?>(Type<Int>.create(), "bad")
 }
 
 concrete Type<#x> {
@@ -206,11 +206,11 @@
 }
 
 concrete Test {
-  @type get<#x> (Type<#x>,#x) -> (Type<#x>)
+  @type get<#x> (Type<#x>, #x) -> (Type<#x>)
 }
 
 define Test {
-  get (x,_) {
+  get (x, _) {
     return x
   }
 }
@@ -256,7 +256,7 @@
 }
 
 unittest test {
-  \ Test.get<Formatted,?>(Type<String>.new())
+  \ Test.get<Formatted, ?>(Type<String>.new())
 }
 
 concrete Type<#x> {
@@ -268,7 +268,7 @@
 }
 
 concrete Test {
-  @type get<#y,#x>
+  @type get<#y, #x>
     #x allows #y
   (Type<#x>) -> (Type<#x>)
 }
@@ -348,7 +348,7 @@
 unittest test {
   Type<String> x <- Type<String>.create()
   Type<String> y <- Type<String>.create()
-  \ Test.get<?,?>(x,y)
+  \ Test.get<?, ?>(x, y)
 }
 
 concrete Type<#x> {
@@ -362,14 +362,14 @@
 }
 
 concrete Test {
-  @type get<#x,#y>
+  @type get<#x, #y>
     #x defines LessThan<#y>
     #y defines LessThan<#x>
-  (Type<#x>,Type<#y>) -> ()
+  (Type<#x>, Type<#y>) -> ()
 }
 
 define Test {
-  get (_,_) {}
+  get (_, _) { }
 }
 
 
@@ -379,13 +379,13 @@
 
 unittest test {
   Type z <- Type.create()
-  \ Test.get<?,?,?>(z,1,"message")
+  \ Test.get<?, ?, ?>(z, 1, "message")
 }
 
-@type interface Base<#x,#y> {}
+@type interface Base<#x, #y> { }
 
 concrete Type {
-  defines Base<Int,String>
+  defines Base<Int, String>
   @type create () -> (Type)
 }
 
@@ -396,13 +396,13 @@
 }
 
 concrete Test {
-  @type get<#z,#x,#y>
-    #z defines Base<#x,#y>
-  (#z,#x,#y) -> ()
+  @type get<#z, #x, #y>
+    #z defines Base<#x, #y>
+  (#z, #x, #y) -> ()
 }
 
 define Test {
-  get (_,_,_) {}
+  get (_, _, _) { }
 }
 
 
@@ -411,7 +411,7 @@
 }
 
 concrete Test {
-  @type call<#x,#y> (Convert<#x,#y>) -> (#x,#y)
+  @type call<#x, #y> (Convert<#x, #y>) -> (#x, #y)
 }
 
 define Test {
@@ -421,8 +421,8 @@
 
   @value run () -> ()
   run () {
-    any x1, all y1 <- call<?,?>(Convert<any,all>.create())
-    all x2, any y2 <- call<?,?>(Convert<all,any>.create())
+    any x1, all y1 <- call<?, ?>(Convert<any, all>.create())
+    all x2, any y2 <- call<?, ?>(Convert<all, any>.create())
   }
 }
 
@@ -438,7 +438,7 @@
 
 
 testcase "dependent implicit param inferred from requires" {
-  crash
+  failure
   require "m e s s a g e"
 }
 
@@ -452,16 +452,16 @@
 
 define Type {
   run () {
-    \ call<?,?>("message")
-    \ call<String,?>("message")
+    \ call<?, ?>("message")
+    \ call<String, ?>("message")
   }
 
-  @type call<#x,#c>
+  @type call<#x, #c>
     #c requires Formatted
     #x requires DefaultOrder<#c>
   (#x) -> ()
   call (value) {
-    [Append<Formatted>&Build<String>] builder <- String.builder()
+    [Append<Formatted> & Build<String>] builder <- String.builder()
     traverse (value.defaultOrder() -> #c char) {
       \ builder.append(char).append(" ")
     }
@@ -471,7 +471,7 @@
 
 
 testcase "dependent implicit param inferred from defines" {
-  crash
+  failure
   require "String"
 }
 
@@ -485,11 +485,11 @@
 
 define Type {
   run () {
-    \ call<?,?>("message")
-    \ call<String,?>("message")
+    \ call<?, ?>("message")
+    \ call<String, ?>("message")
   }
 
-  @type call<#x,#c>
+  @type call<#x, #c>
     #x defines LessThan<#c>
   (#x) -> ()
   call (value) {
@@ -499,7 +499,7 @@
 
 
 testcase "dependent implicit param inferred from allows" {
-  crash
+  failure
   require "String"
 }
 
@@ -513,11 +513,11 @@
 
 define Type {
   run () {
-    \ call<?,?>("message")
-    \ call<String,?>("message")
+    \ call<?, ?>("message")
+    \ call<String, ?>("message")
   }
 
-  @type call<#x,#c>
+  @type call<#x, #c>
     #x allows #c
   (#x) -> ()
   call (value) {
@@ -527,7 +527,7 @@
 
 
 testcase "dependent implicit param inferred from reverse allows" {
-  crash
+  failure
   require "String"
 }
 
@@ -541,11 +541,11 @@
 
 define Type {
   run () {
-    \ call<?,?>("message")
-    \ call<String,?>("message")
+    \ call<?, ?>("message")
+    \ call<String, ?>("message")
   }
 
-  @type call<#x,#c>
+  @type call<#x, #c>
     #c allows #x
   (#x) -> ()
   call (value) {
@@ -555,7 +555,7 @@
 
 
 testcase "dependent implicit param inferred from reverse requires" {
-  crash
+  failure
   require "String"
 }
 
@@ -569,11 +569,11 @@
 
 define Type {
   run () {
-    \ call<?,?>("message")
-    \ call<String,?>("message")
+    \ call<?, ?>("message")
+    \ call<String, ?>("message")
   }
 
-  @type call<#x,#c>
+  @type call<#x, #c>
     #c requires #x
   (#x) -> ()
   call (value) {
@@ -608,7 +608,7 @@
 
 
 testcase "filter guess merged with arg guess" {
-  crash
+  failure
   require "String"
   exclude "Formatted"
 }
@@ -642,9 +642,9 @@
   \ Test.call<?>(Type.create())
 }
 
-@value interface Base1<|#x> {}
+@value interface Base1<|#x> { }
 
-@value interface Base2<|#x> {}
+@value interface Base2<|#x> { }
 
 concrete Type {
   refines Base1<Int>
@@ -660,7 +660,7 @@
 }
 
 concrete Test {
-  @type call<#x> ([Base1<#x>|Base2<#x>]) -> ()
+  @type call<#x> ([Base1<#x> | Base2<#x>]) -> ()
 }
 
 define Test {
@@ -678,9 +678,9 @@
   \ Test.call<?>(Type.create())
 }
 
-@value interface Base1<#x> {}
+@value interface Base1<#x> { }
 
-@value interface Base2<#x> {}
+@value interface Base2<#x> { }
 
 concrete Type {
   refines Base1<Int>
@@ -696,7 +696,7 @@
 }
 
 concrete Test {
-  @type call<#x> ([Base1<#x>&Base2<#x>]) -> ()
+  @type call<#x> ([Base1<#x> & Base2<#x>]) -> ()
 }
 
 define Test {
@@ -705,7 +705,7 @@
 
 
 testcase "variance allows guesses to be merged using meta type" {
-  crash
+  failure
   require "\[Int\|String\]"
 }
 
@@ -713,9 +713,9 @@
   \ Test.call<?>(Type.create())
 }
 
-@value interface Base1<|#x> {}
+@value interface Base1<|#x> { }
 
-@value interface Base2<|#x> {}
+@value interface Base2<|#x> { }
 
 concrete Type {
   refines Base1<Int>
@@ -731,7 +731,7 @@
 }
 
 concrete Test {
-  @type call<#x> ([Base1<#x>&Base2<#x>]) -> ()
+  @type call<#x> ([Base1<#x> & Base2<#x>]) -> ()
 }
 
 define Test {
@@ -742,7 +742,7 @@
 
 
 testcase "alternative guess eliminated by filter" {
-  crash
+  failure
   require "Int"
 }
 
@@ -750,9 +750,9 @@
   \ Test.call<?>(Type.create())
 }
 
-@value interface Base1<|#x> {}
+@value interface Base1<|#x> { }
 
-@value interface Base2<|#x> {}
+@value interface Base2<|#x> { }
 
 concrete Type {
   refines Base1<Int>
@@ -770,7 +770,7 @@
 concrete Test {
   @type call<#x>
     #x requires AsChar
-  ([Base1<#x>|Base2<#x>]) -> ()
+  ([Base1<#x> | Base2<#x>]) -> ()
 }
 
 define Test {
diff --git a/tests/infix-functions.0rt b/tests/infix-functions.0rt
--- a/tests/infix-functions.0rt
+++ b/tests/infix-functions.0rt
@@ -28,11 +28,11 @@
 }
 
 concrete Test {
-  @category add (Int,Int) -> (Int)
+  @category add (Int, Int) -> (Int)
 }
 
 define Test {
-  add (x,y) {
+  add (x, y) {
     return x + y
   }
 }
@@ -50,11 +50,11 @@
 }
 
 concrete Test {
-  @type add (Int,Int) -> (Int)
+  @type add (Int, Int) -> (Int)
 }
 
 define Test {
-  add (x,y) {
+  add (x, y) {
     return x + y
   }
 }
@@ -73,7 +73,7 @@
 
 concrete Arithmetic {
   @type create () -> (Arithmetic)
-  @value add (Int,Int) -> (Int)
+  @value add (Int, Int) -> (Int)
 }
 
 define Arithmetic {
@@ -81,7 +81,7 @@
     return Arithmetic{ }
   }
 
-  add (x,y) {
+  add (x, y) {
     return x + y
   }
 }
@@ -96,8 +96,8 @@
 }
 
 define Test {
-  @type add (Int,Int) -> (Int)
-  add (x,y) {
+  @type add (Int, Int) -> (Int)
+  add (x, y) {
     return x + y
   }
 
@@ -126,11 +126,11 @@
 }
 
 concrete Test {
-  @category add (Int,Int) -> (Int)
+  @category add (Int, Int) -> (Int)
 }
 
 define Test {
-  add (x,y) {
+  add (x, y) {
     return x + y
   }
 }
@@ -148,11 +148,11 @@
 }
 
 concrete Test {
-  @category add (Int,Int) -> (Int)
+  @category add (Int, Int) -> (Int)
 }
 
 define Test {
-  add (x,y) {
+  add (x, y) {
     return x + y
   }
 }
@@ -172,20 +172,20 @@
 
 define Test {
   check () {
-    \ Testing.checkEquals(1 `one` 2 `two` 4,5)
+    \ Testing.checkEquals(1 `one` 2 `two` 4, 5)
   }
 
-  @type one (Int,Int) -> (Int)
-  one (x,y) {
-    \ Testing.checkEquals(x,1)
-    \ Testing.checkEquals(y,2)
+  @type one (Int, Int) -> (Int)
+  one (x, y) {
+    \ Testing.checkEquals(x, 1)
+    \ Testing.checkEquals(y, 2)
     return 3
   }
 
-  @type two (Int,Int) -> (Int)
-  two (x,y) {
-    \ Testing.checkEquals(x,3)
-    \ Testing.checkEquals(y,4)
+  @type two (Int, Int) -> (Int)
+  two (x, y) {
+    \ Testing.checkEquals(x, 3)
+    \ Testing.checkEquals(y, 4)
     return 5
   }
 }
diff --git a/tests/infix-operations.0rt b/tests/infix-operations.0rt
--- a/tests/infix-operations.0rt
+++ b/tests/infix-operations.0rt
@@ -21,55 +21,55 @@
 }
 
 unittest plus {
-  \ Testing.checkEquals(8 + 2,10)
+  \ Testing.checkEquals(8 + 2, 10)
 }
 
 unittest minus {
-  \ Testing.checkEquals(8 - 2,6)
+  \ Testing.checkEquals(8 - 2, 6)
 }
 
 unittest times {
-  \ Testing.checkEquals(8 * 2,16)
+  \ Testing.checkEquals(8 * 2, 16)
 }
 
 unittest divide {
-  \ Testing.checkEquals(8 / 2,4)
+  \ Testing.checkEquals(8 / 2, 4)
 }
 
 unittest modulo {
-  \ Testing.checkEquals(8 % 2,0)
+  \ Testing.checkEquals(8 % 2, 0)
 }
 
 unittest left {
-  \ Testing.checkEquals(1 << 2,4)
+  \ Testing.checkEquals(1 << 2, 4)
 }
 
 unittest right {
-  \ Testing.checkEquals(7 >> 1,3)
+  \ Testing.checkEquals(7 >> 1, 3)
 }
 
 unittest xor {
-  \ Testing.checkEquals(7 ^ 2 ,5)
+  \ Testing.checkEquals(7 ^ 2 , 5)
 }
 
 unittest and {
-  \ Testing.checkEquals(7 & ~2,5)
+  \ Testing.checkEquals(7 & ~2, 5)
 }
 
 unittest or {
-  \ Testing.checkEquals(5 | 2 ,7)
+  \ Testing.checkEquals(5 | 2 , 7)
 }
 
 unittest arithmetic {
-  \ Testing.checkEquals(\x10 + 1 * 2 - 8 / 2 - 3 % 2,13)
+  \ Testing.checkEquals(\x10 + 1 * 2 - 8 / 2 - 3 % 2, 13)
 }
 
 unittest bitwise {
-  \ Testing.checkEquals(1 << 4 | 7 >> 1 & ~2,17)
+  \ Testing.checkEquals(1 << 4 | 7 >> 1 & ~2, 17)
 }
 
 unittest sameOperator {
-  \ Testing.checkEquals(2 - 1 - 1,0)
+  \ Testing.checkEquals(2 - 1 - 1, 0)
 }
 
 unittest lessThan {
@@ -136,14 +136,14 @@
 unittest rightAssociative {
   Int x <- 2
   Bool y <- true && (x <- 3) == 2 || x == 3
-  \ Testing.checkEquals(x,3)
+  \ Testing.checkEquals(x, 3)
   \ Testing.checkTrue(y)
 }
 
 unittest shortCircuit {
   Int x <- 2
   Bool y <- true || (x <- 3) == 2 || x == 3
-  \ Testing.checkEquals(x,2)
+  \ Testing.checkEquals(x, 2)
   \ Testing.checkTrue(y)
 }
 
@@ -153,7 +153,7 @@
 }
 
 unittest precedence {
-  \ Testing.checkEquals(16.0 + 1.0 * 2.0 - 8.0 / 2.0 - 3.0 / 3.0,13.0)
+  \ Testing.checkEquals(16.0 + 1.0 * 2.0 - 8.0 / 2.0 - 3.0 / 3.0, 13.0)
 }
 
 unittest lessThan {
@@ -190,7 +190,7 @@
 }
 
 unittest add {
-  \ Testing.checkEquals("x" + "y" + "z","xyz")
+  \ Testing.checkEquals("x" + "y" + "z", "xyz")
 }
 
 unittest lessThan {
@@ -227,7 +227,7 @@
 }
 
 unittest minus {
-  \ Testing.checkEquals('z' - 'a',25)
+  \ Testing.checkEquals('z' - 'a', 25)
 }
 
 unittest lessThan {
diff --git a/tests/internal-inheritance.0rt b/tests/internal-inheritance.0rt
--- a/tests/internal-inheritance.0rt
+++ b/tests/internal-inheritance.0rt
@@ -96,9 +96,9 @@
   success
 }
 
-unittest test {}
+unittest test { }
 
-concrete Value {}
+concrete Value { }
 
 define Value {
   refines Formatted
@@ -108,7 +108,7 @@
     return "Value"
   }
 
-  equals (_,_) {
+  equals (_, _) {
     return true
   }
 }
@@ -121,7 +121,7 @@
 unittest test {
   Value value1 <- Value.create(1)
   Value value2 <- Value.create(2)
-  if (Value.compare(value1,value2)) {
+  if (Value.compare(value1, value2)) {
     fail("Failed")
   }
 }
@@ -129,18 +129,18 @@
 concrete Compare<#x> {
   #x defines Equals<#x>
 
-  @type compare (#x,#x) -> (Bool)
+  @type compare (#x, #x) -> (Bool)
 }
 
 define Compare {
-  compare (x,y) {
-    return #x.equals(x,y)
+  compare (x, y) {
+    return #x.equals(x, y)
   }
 }
 
 concrete Value {
   @type create (Int) -> (Value)
-  @type compare (Value,Value) -> (Bool)
+  @type compare (Value, Value) -> (Bool)
 }
 
 define Value {
@@ -148,7 +148,7 @@
 
   @value Int value
 
-  equals (x,y) {
+  equals (x, y) {
     return x.get() == y.get()
   }
 
@@ -156,8 +156,8 @@
     return Value{ x }
   }
 
-  compare (x,y) {
-    return Compare<Value>.compare(x,y)
+  compare (x, y) {
+    return Compare<Value>.compare(x, y)
   }
 
   @value get () -> (Int)
@@ -177,18 +177,18 @@
 unittest test {
   Value value1 <- Value.create(1)
   Value value2 <- Value.create(2)
-  \ Compare<Value>.compare(value1,value2)
+  \ Compare<Value>.compare(value1, value2)
 }
 
 concrete Compare<#x> {
   #x defines Equals<#x>
 
-  @type compare (#x,#x) -> (Bool)
+  @type compare (#x, #x) -> (Bool)
 }
 
 define Compare {
-  compare (x,y) {
-    return #x.equals(x,y)
+  compare (x, y) {
+    return #x.equals(x, y)
   }
 }
 
@@ -201,7 +201,7 @@
 
   @value Int value
 
-  equals (x,y) {
+  equals (x, y) {
     return x.get() == y.get()
   }
 
@@ -222,9 +222,9 @@
   require "type interface"
 }
 
-@value interface Base {}
+@value interface Base { }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   defines Base
@@ -241,7 +241,7 @@
   call (#x) -> ()
 }
 
-concrete Type<|#x> {}
+concrete Type<|#x> { }
 
 define Type {
   refines Base<#x>
@@ -258,7 +258,7 @@
   call (#x) -> ()
 }
 
-concrete Type<|#x> {}
+concrete Type<|#x> { }
 
 define Type {
   defines Base<#x>
@@ -269,7 +269,7 @@
   compiles
 }
 
-@value interface Base1<#x> {}
+@value interface Base1<#x> { }
 
 @value interface Base2<#x> {
   refines Base1<#x>
diff --git a/tests/leak-check/.zeolite-module b/tests/leak-check/.zeolite-module
--- a/tests/leak-check/.zeolite-module
+++ b/tests/leak-check/.zeolite-module
@@ -12,6 +12,6 @@
   "lib/util"
 ]
 mode: binary {
-  category: LeakTest
+  category: LeakCheck
   function: run
 }
diff --git a/tests/leak-check/README.md b/tests/leak-check/README.md
--- a/tests/leak-check/README.md
+++ b/tests/leak-check/README.md
@@ -17,7 +17,7 @@
 
 ```shell
 # The "leak" argument is important.
-valgrind --leak-check=yes $ZEOLITE_PATH/tests/leak-check/LeakTest leak
+valgrind --leak-check=yes $ZEOLITE_PATH/tests/leak-check/LeakCheck leak
 ```
 
 You should get output that looks something like this:
@@ -26,7 +26,7 @@
 ==8487== Memcheck, a memory error detector
 ==8487== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
 ==8487== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
-==8487== Command: tests/leak-check/LeakTest leak
+==8487== Command: tests/leak-check/LeakCheck leak
 ==8487==
 ==8487==
 ==8487== HEAP SUMMARY:
@@ -58,7 +58,7 @@
 
 ```shell
 # The "race" argument is important.
-$ZEOLITE_PATH/tests/leak-check/LeakTest race
+$ZEOLITE_PATH/tests/leak-check/LeakCheck race
 ```
 
 *Do not use `valgrind` to run in `race` mode!* The latency introduced by
@@ -70,10 +70,10 @@
 
 ---
 
-A more comprehensive test involves leaving `LeakTest` running for an hour or so.
+A more comprehensive test involves leaving `LeakCheck` running indefinitely.
 
 ```shell
-$ZEOLITE_PATH/tests/leak-check/LeakTest forever
+$ZEOLITE_PATH/tests/leak-check/LeakCheck forever
 ```
 
 This will cause `LeakCheck` to run for a very long time, while attempting to
diff --git a/tests/leak-check/leak-check.0rx b/tests/leak-check/leak-check.0rx
--- a/tests/leak-check/leak-check.0rx
+++ b/tests/leak-check/leak-check.0rx
@@ -1,20 +1,38 @@
-concrete LeakTest {
+/* -----------------------------------------------------------------------------
+Copyright 2021-2022 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 LeakCheck {
   @type run () -> ()
 }
 
-define LeakTest {
+define LeakCheck {
   @value Vector<Int> memory
 
   run () {
     if (Argv.global().size() != 2) {
       \ message()
     } elif (Argv.global().readAt(1) == "race") {
-      \ runWith(1000,0)
+      \ runWith(1000, 0)
       \ BasicOutput.stderr().writeNow("no race conditions this time\n")
     } elif (Argv.global().readAt(1) == "leak") {
-      \ runWith(100,0)
+      \ runWith(100, 0)
     } elif (Argv.global().readAt(1) == "forever") {
-      \ runWith(10000000,1024*1024)
+      \ runWith(10000000, 1024*1024)
     } else {
       \ message()
     }
@@ -22,23 +40,23 @@
 
   @type message () -> ()
   message () {
-    fail(Argv.global().readAt(0) + " [race|leak|forever]")
+    fail(Argv.global().readAt(0) + " [race | leak | forever]")
   }
 
-  @type runWith (Int,Int) -> ()
-  runWith (iterations,size) {
-    Int threads <- Ranges:max(3,$ExprLookup[CPU_CORE_COUNT]$-1)
+  @type runWith (Int, Int) -> ()
+  runWith (iterations, size) {
+    Int threads <- Ranges:max(3, $ExprLookup[CPU_CORE_COUNT]$-1)
     ReadAt<BarrierWait> barriers <- EnumeratedBarrier.new(threads+1)
     traverse (Counter.zeroIndexed(iterations) -> Int i) {
-      \ doIteration<LastReference>(i,size,barriers)
-      \ doIteration<Chaos>(i,size,barriers)
+      \ doIteration<LastReference>(i, size, barriers)
+      \ doIteration<Chaos>(i, size, barriers)
     }
   }
 
   @type doIteration<#f>
     #f defines ThreadFactory
-  (Int,Int,ReadAt<BarrierWait>) -> ()
-  doIteration (i,size,barriers) {
+  (Int, Int, ReadAt<BarrierWait>) -> ()
+  doIteration (i, size, barriers) {
     \ BasicOutput.stderr()
         .write(typename<#f>())
         .write(": iteration ")
@@ -46,15 +64,15 @@
         .write("\n")
         .flush()
 
-    optional LeakTest original <- LeakTest{ Vector:createSize<Int>(size) }
-    weak LeakTest checkedValue <- original
+    optional LeakCheck original <- LeakCheck{ Vector:createSize<Int>(size) }
+    weak LeakCheck checkedValue <- original
     $ReadOnly[checkedValue]$
 
     scoped {
       Vector<Thread> threads <- Vector<Thread>.new()
     } in {
       traverse (Counter.zeroIndexed(barriers.size()-1) -> Int j) {
-        \ threads.push(ProcessThread.from(#f.create(original,barriers.readAt(j+1))).start())
+        \ threads.push(ProcessThread.from(#f.create(original, barriers.readAt(j+1))).start())
       }
 
       \ barriers.readAt(0).wait()
@@ -71,7 +89,7 @@
 }
 
 @type interface ThreadFactory {
-  create (optional LeakTest,BarrierWait) -> (Routine)
+  create (optional LeakCheck, BarrierWait) -> (Routine)
 }
 
 concrete LastReference {
@@ -83,10 +101,10 @@
 
   refines Routine
 
-  @value weak LeakTest copy
+  @value weak LeakCheck copy
   @value BarrierWait barrier
 
-  create (copy,barrier) {
+  create (copy, barrier) {
     return LastReference{ copy, barrier }
   }
 
@@ -105,23 +123,26 @@
 
   refines Routine
 
-  @value weak LeakTest copy
+  @value weak LeakCheck copy
   @value BarrierWait barrier
 
-  create (copy,barrier) {
+  create (copy, barrier) {
     return Chaos{ copy, barrier }
   }
 
   run ()  {
-    weak LeakTest copy1 <- empty
-    weak LeakTest copy2 <- empty
-    weak LeakTest copy3 <- empty
-    weak LeakTest copy4 <- empty
-    weak LeakTest copy5 <- empty
+    weak LeakCheck copy1 <- empty
+    weak LeakCheck copy2 <- empty
+    weak LeakCheck copy3 <- empty
+    weak LeakCheck copy4 <- empty
+    weak LeakCheck copy5 <- empty
     \ barrier.wait()
     copy1 <- copy
     copy2 <- copy1
     copy3 <- copy1
+    copy3 <-> copy1
+    copy1 <-> copy1
+    copy2 <-> copy1
     copy <- empty
     copy4 <- strong(copy2)
     copy1 <- strong(copy3)
diff --git a/tests/local-rules.0rt b/tests/local-rules.0rt
--- a/tests/local-rules.0rt
+++ b/tests/local-rules.0rt
@@ -52,7 +52,7 @@
     $ReadOnly[foo]$
   }
   foo <- 2
-  \ Testing.checkEquals(foo,2)
+  \ Testing.checkEquals(foo, 2)
 }
 
 
@@ -82,7 +82,7 @@
     $ReadOnly[foo]$
   }
   foo <- 2
-  \ Testing.checkEquals(foo,2)
+  \ Testing.checkEquals(foo, 2)
 }
 
 
@@ -97,9 +97,9 @@
     foo <- 2
     $ReadOnly[foo]$
   }
-  \ Testing.checkEquals(foo,2)
+  \ Testing.checkEquals(foo, 2)
   foo <- 3
-  \ Testing.checkEquals(foo,3)
+  \ Testing.checkEquals(foo, 3)
 }
 
 
@@ -151,7 +151,7 @@
   cleanup {
     foo <- 2
   } in $ReadOnly[foo]$
-  \ Testing.checkEquals(foo,2)
+  \ Testing.checkEquals(foo, 2)
 }
 
 
@@ -181,7 +181,7 @@
     $Hidden[foo]$
   }
   foo <- 2
-  \ Testing.checkEquals(foo,2)
+  \ Testing.checkEquals(foo, 2)
 }
 
 
@@ -240,9 +240,9 @@
     foo <- 2
     $Hidden[foo]$
   }
-  \ Testing.checkEquals(foo,2)
+  \ Testing.checkEquals(foo, 2)
   foo <- 3
-  \ Testing.checkEquals(foo,3)
+  \ Testing.checkEquals(foo, 3)
 }
 
 
@@ -264,7 +264,7 @@
   exclude "bar"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $ReadOnly[foo]$
@@ -286,7 +286,7 @@
   exclude "bar"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $Hidden[foo]$
@@ -307,7 +307,7 @@
   require "foo.+does not exist"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $Hidden[foo]$
@@ -321,7 +321,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Type:get(),2)
+  \ Testing.checkEquals(Type:get(), 2)
 }
 
 concrete Type {
@@ -347,7 +347,7 @@
   exclude "bar"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $ReadOnly[foo]$
@@ -369,7 +369,7 @@
   exclude "bar"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $Hidden[foo]$
@@ -390,7 +390,7 @@
   require "foo.+does not exist"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $Hidden[foo]$
@@ -404,7 +404,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Type.create().get(),1)
+  \ Testing.checkEquals(Type.create().get(), 1)
 }
 
 concrete Type {
@@ -434,7 +434,7 @@
   exclude "bar"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $ReadOnlyExcept[bar]$
@@ -456,7 +456,7 @@
   exclude "bar"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $ReadOnlyExcept[bar]$
@@ -477,7 +477,7 @@
   require "foo.+read-only"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $ReadOnlyExcept[foo]$
@@ -497,7 +497,7 @@
   require compiler "ReadOnlyExcept"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $ReadOnlyExcept[foo]$
@@ -519,7 +519,7 @@
   require "foo.+does not exist"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   $ReadOnlyExcept[foo]$
diff --git a/tests/local.0rp b/tests/local.0rp
--- a/tests/local.0rp
+++ b/tests/local.0rp
@@ -18,4 +18,4 @@
 
 $ModuleOnly$
 
-@value interface ModuleOnly {}
+@value interface ModuleOnly { }
diff --git a/tests/member-init.0rt b/tests/member-init.0rt
--- a/tests/member-init.0rt
+++ b/tests/member-init.0rt
@@ -25,7 +25,7 @@
   @type Bool value <- false
 }
 
-concrete Test {}
+concrete Test { }
 
 
 testcase "@category member from @type" {
@@ -36,7 +36,7 @@
   // NOTE: Executing the test ensures that C++ compilation works.
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category Bool value <- true
@@ -56,7 +56,7 @@
   // NOTE: Executing the test ensures that C++ compilation works.
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category Bool value <- true
@@ -73,7 +73,7 @@
   require "get"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category Bool value <- get()
@@ -90,7 +90,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Test:get(),2)
+  \ Testing.checkEquals(Test:get(), 2)
 }
 
 concrete Test {
@@ -126,7 +126,7 @@
   }
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category Bool value <- Util.doNotUse()
@@ -134,7 +134,7 @@
 
 
 testcase "@category member init when read" {
-  crash
+  failure
   require "do not use"
 }
 
@@ -166,7 +166,7 @@
 
 
 testcase "@category member init when assigned" {
-  crash
+  failure
   require "do not use"
 }
 
@@ -198,7 +198,7 @@
 
 
 testcase "@category member init when ignored" {
-  crash
+  failure
   require "do not use"
 }
 
@@ -258,8 +258,8 @@
 
 
 testcase "@category init cycle" {
-  crash
-  require "Value1|Value2"
+  failure
+  require "Value1 | Value2"
 }
 
 unittest test {
@@ -296,7 +296,7 @@
   require "self"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category Test value <- self
@@ -308,7 +308,7 @@
   require "disallowed"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category Bool value <- get()
@@ -325,7 +325,7 @@
   require "value1.+read-only"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category Int value1 <- 1
@@ -338,7 +338,7 @@
   require "Foo not found"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category optional Foo value <- empty
@@ -350,7 +350,7 @@
   require "Foo not found"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value Foo value
@@ -362,7 +362,7 @@
   require "#x not found"
 }
 
-concrete Test<#x> {}
+concrete Test<#x> { }
 
 define Test {
   @category optional #x value <- empty
diff --git a/tests/modified-storage.0rt b/tests/modified-storage.0rt
--- a/tests/modified-storage.0rt
+++ b/tests/modified-storage.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
+Copyright 2020,2023 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.
@@ -185,7 +185,7 @@
 }
 
 concrete Test {
-  @type get () -> (Value,Value)
+  @type get () -> (Value, Value)
 }
 
 define Test {
@@ -260,7 +260,7 @@
     return Test{ }
   }
 
-  call () {}
+  call () { }
 }
 
 
@@ -271,35 +271,405 @@
 unittest boxed {
   String value <- "message"
   weak String value2 <- value
-  $ValidateRefs[value,value2]$
+  $ValidateRefs[value, value2]$
 }
 
 unittest int {
   Int value <- 1
   weak Int value2 <- value
-  $ValidateRefs[value,value2]$
+  $ValidateRefs[value, value2]$
 }
 
 unittest char {
   Char value <- 'a'
   weak Char value2 <- value
-  $ValidateRefs[value,value2]$
+  $ValidateRefs[value, value2]$
 }
 
 unittest bool {
   Bool value <- false
   weak Bool value2 <- value
-  $ValidateRefs[value,value2]$
+  $ValidateRefs[value, value2]$
 }
 
 unittest float {
   Float value <- 1.0
   weak Float value2 <- value
-  $ValidateRefs[value,value2]$
+  $ValidateRefs[value, value2]$
 }
 
 unittest emptyVal {
   optional all value <- empty
   weak all value2 <- value
-  $ValidateRefs[value,value2]$
+  $ValidateRefs[value, value2]$
+}
+
+
+testcase "call from empty" {
+  error
+  require "optional"
+}
+
+unittest test {
+  \ empty.foo()
+}
+
+
+testcase "inline assignment to weak" {
+  success
+}
+
+unittest unboxed {
+  weak Int value <- empty
+  Int value2 <- (value <- 123)
+  \ Testing.checkEquals(value2, 123)
+  \ Testing.checkEquals(strong(value), 123)
+}
+
+unittest boxedNoRefs {
+  weak String value <- empty
+  \ Testing.checkEquals((value <- "message"), "message")
+  \ Testing.checkEquals(strong(value), empty)
+}
+
+unittest boxedRefs {
+  weak String value <- empty
+  String original <- "message"
+  String value2 <- (value <- original)
+  \ Testing.checkEquals(value2, original)
+  \ Testing.checkEquals(strong(value), original)
+}
+
+unittest weakToWeak {
+  weak String value <- empty
+  String original <- "message"
+  weak String value2 <- original
+  \ Testing.checkEquals(`strong` (value <- value2), original)
+  \ Testing.checkEquals(strong(value), original)
+}
+
+
+testcase "conditional inline assignment" {
+  success
+}
+
+concrete Helper {
+  @type notCalled () -> (all)
+}
+
+define Helper {
+  notCalled () {
+    fail("should not be called")
+  }
+}
+
+unittest shortCircuit {
+  optional Int value <- 123
+  Int value2 <- (value <-| Helper.notCalled())
+  \ Testing.checkEquals(value2, 123)
+  \ Testing.checkEquals(value, 123)
+}
+
+unittest unboxedToUnboxed {
+  optional Int value <- empty
+
+  Int value2 <- (value <-| 123)
+  \ Testing.checkEquals(value2, 123)
+  \ Testing.checkEquals(value, 123)
+
+  Int value3 <- (value <-| 456)
+  \ Testing.checkEquals(value3, 123)
+  \ Testing.checkEquals(value, 123)
+}
+
+unittest boxedToBoxed {
+  optional String value <- empty
+
+  String value2 <- (value <-| "message")
+  \ Testing.checkEquals(value2, "message")
+  \ Testing.checkEquals(value, "message")
+
+  String value3 <- (value <-| "other")
+  \ Testing.checkEquals(value3, "message")
+  \ Testing.checkEquals(value, "message")
+}
+
+
+testcase "conditional assignment" {
+  success
+}
+
+concrete Helper {
+  @type notCalled () -> (all)
+}
+
+define Helper {
+  notCalled () {
+    fail("should not be called")
+  }
+}
+
+unittest shortCircuit {
+  optional Int value <- 123
+  value <-| Helper.notCalled()
+  \ Testing.checkEquals(value, 123)
+}
+
+unittest unboxedToUnboxed {
+  optional Int value <- empty
+
+  value <-| 123
+  \ Testing.checkEquals(value, 123)
+
+  value <-| 456
+  \ Testing.checkEquals(value, 123)
+}
+
+unittest boxedToBoxed {
+  optional String value <- empty
+
+  value <-| "message"
+  \ Testing.checkEquals(value, "message")
+
+  value <-| "other"
+  \ Testing.checkEquals(value, "message")
+}
+
+
+testcase "conditional inline assignment wrong return type" {
+  error
+  require "Formatted"
+  require "Int"
+}
+
+unittest test {
+  optional Formatted value <- empty
+  Int value2 <- (value <-| 123)
+}
+
+
+testcase "conditional inline assignment wrong return storage" {
+  error
+  require "storage modifier"
+}
+
+unittest test {
+  optional Int value <- 123
+  Int value2 <- (value <-| empty)
+}
+
+
+testcase "conditional inline assignment deferred" {
+  error
+  require "initialized"
+  require "value"
+}
+
+unittest test {
+  optional Int value <- defer
+  Int value2 <- (value <-| 123)
+}
+
+
+testcase "conditional inline assignment required" {
+  error
+  require "optional"
+}
+
+unittest test {
+  Int value <- 456
+  Int value2 <- (value <-| 123)
+}
+
+
+testcase "conditional inline assignment weak" {
+  error
+  require "optional"
+}
+
+unittest test {
+  weak Int value <- 456
+  Int value2 <- (value <-| 123)
+}
+
+
+testcase "conditional assignment wrong return count" {
+  error
+  require "return"
+}
+
+concrete Helper {
+  @type get () -> (Int, Int)
+}
+
+define Helper {
+  get () {
+    return 1, 2
+  }
+}
+
+unittest test {
+  optional Int value <- 456
+  value <-| Helper.get()
+}
+
+
+testcase "optional value calls" {
+  success
+}
+
+concrete Helper {
+  @type notCalled () -> (all)
+}
+
+define Helper {
+  notCalled () {
+    fail("should not be called")
+  }
+}
+
+unittest nonEmpty {
+  optional Int value <- 123
+  \ Testing.checkEquals(value&.formatted(), "123")
+}
+
+unittest chainedCalls {
+  optional Int value <- 123
+  \ Testing.checkEquals(value&.formatted()&.readAt(0), '1')
+}
+
+unittest emptyValue {
+  optional Int value <- empty
+  \ Testing.checkEquals(value&.formatted(), empty)
+}
+
+unittest shortCircuit {
+  optional String value <- empty
+  \ value&.readAt(Helper.notCalled())
+}
+
+
+testcase "conditional call return types become optional" {
+  error
+  require "storage modifier"
+}
+
+unittest test {
+  optional Int value <- empty
+  String value2 <- value&.formatted()
+}
+
+
+testcase "conditional call not allowed for required" {
+  error
+  require "[Oo]ptional"
+}
+
+unittest test {
+  \ "123"&.readAt(0)
+}
+
+
+testcase "conditional call not allowed for weak" {
+  error
+  require "[Oo]ptional"
+}
+
+unittest test {
+  weak String value <- empty
+  \ value&.readAt(0)
+}
+
+
+testcase "conditional call with multi returns" {
+  success
+}
+
+concrete Value {
+  @type new () -> (Value)
+  @value get () -> (optional Int, Int)
+}
+
+define Value {
+  new () {
+    return Value{ }
+  }
+
+  get () {
+    return 1, 2
+  }
+}
+
+unittest chained {
+  optional Value value <- Value.new()
+  \ Testing.checkEquals(value&.get(){1}&.formatted(), "2")
+}
+
+unittest notCalled {
+  optional Value value <- empty
+  optional Int result1, optional Int result2 <- value&.get()
+  \ Testing.checkEmpty(result1)
+  \ Testing.checkEmpty(result2)
+
+}
+
+
+testcase "conditional call only happens once" {
+  success
+}
+
+concrete Value {
+  @type new () -> (optional Value)
+  @value get () -> (Int)
+}
+
+define Value {
+  @category Bool used <- false
+
+  new () {
+    if (used) {
+      fail("already called")
+    }
+    used <- true
+    return Value{ }
+  }
+
+  get () {
+    return 1
+  }
+}
+
+unittest test {
+  \ Testing.checkEquals(Value.new()&.get()&.formatted()&.readAt(0), '1')
+}
+
+
+testcase "conditional call bad function name" {
+  error
+  require "foo"
+}
+
+unittest test {
+  optional String value <- empty
+  \ value&.foo()
+}
+
+
+testcase "conditional call from empty" {
+  error
+  require "formatted"
+  require "all"
+}
+
+unittest test {
+  \ empty&.formatted()
+}
+
+
+testcase "conditional call from converted empty" {
+  success
+}
+
+unittest test {
+  \ Testing.checkEmpty(empty?Formatted&.formatted())
 }
diff --git a/tests/module-only/.zeolite-module b/tests/module-only/.zeolite-module
--- a/tests/module-only/.zeolite-module
+++ b/tests/module-only/.zeolite-module
@@ -3,4 +3,4 @@
 private_deps: [
   "internal"
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/module-only/internal/.zeolite-module b/tests/module-only/internal/.zeolite-module
--- a/tests/module-only/internal/.zeolite-module
+++ b/tests/module-only/internal/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../../.."
 path: "tests/module-only/internal"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/module-only/internal/private.0rp b/tests/module-only/internal/private.0rp
--- a/tests/module-only/internal/private.0rp
+++ b/tests/module-only/internal/private.0rp
@@ -18,4 +18,4 @@
 
 $ModuleOnly$
 
-@value interface Type1 {}
+@value interface Type1 { }
diff --git a/tests/module-only/internal/public.0rp b/tests/module-only/internal/public.0rp
--- a/tests/module-only/internal/public.0rp
+++ b/tests/module-only/internal/public.0rp
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-@value interface Type2 {}
+@value interface Type2 { }
diff --git a/tests/module-only/private.0rx b/tests/module-only/private.0rx
--- a/tests/module-only/private.0rx
+++ b/tests/module-only/private.0rx
@@ -16,7 +16,7 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value Type1 val1
diff --git a/tests/module-only2/.zeolite-module b/tests/module-only2/.zeolite-module
--- a/tests/module-only2/.zeolite-module
+++ b/tests/module-only2/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../.."
 path: "tests/module-only2"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/module-only2/private1.0rp b/tests/module-only2/private1.0rp
--- a/tests/module-only2/private1.0rp
+++ b/tests/module-only2/private1.0rp
@@ -18,4 +18,4 @@
 
 $ModuleOnly$
 
-@value interface Type1 {}
+@value interface Type1 { }
diff --git a/tests/module-only3/.zeolite-module b/tests/module-only3/.zeolite-module
--- a/tests/module-only3/.zeolite-module
+++ b/tests/module-only3/.zeolite-module
@@ -7,4 +7,4 @@
   "tests/module-only3/source1.cpp"
   "tests/module-only3/source2.cpp"
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/module-only3/internal/.zeolite-module b/tests/module-only3/internal/.zeolite-module
--- a/tests/module-only3/internal/.zeolite-module
+++ b/tests/module-only3/internal/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../../.."
 path: "tests/module-only3/internal"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/module-only3/internal/private.0rp b/tests/module-only3/internal/private.0rp
--- a/tests/module-only3/internal/private.0rp
+++ b/tests/module-only3/internal/private.0rp
@@ -18,4 +18,4 @@
 
 $ModuleOnly$
 
-@value interface Type1 {}
+@value interface Type1 { }
diff --git a/tests/module-only3/internal/public.0rp b/tests/module-only3/internal/public.0rp
--- a/tests/module-only3/internal/public.0rp
+++ b/tests/module-only3/internal/public.0rp
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-@value interface Type2 {}
+@value interface Type2 { }
diff --git a/tests/module-only4/.zeolite-module b/tests/module-only4/.zeolite-module
--- a/tests/module-only4/.zeolite-module
+++ b/tests/module-only4/.zeolite-module
@@ -7,4 +7,4 @@
     requires: [Type2]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/multiple-returns.0rt b/tests/multiple-returns.0rt
--- a/tests/multiple-returns.0rt
+++ b/tests/multiple-returns.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020-2021 Kevin P. Barry
+Copyright 2020-2021,2023 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.
@@ -18,11 +18,11 @@
 
 testcase "multi return to call" {
   error
-  require "call.+\{Value,Value\}"
+  require "call.+Value.+Value"
 }
 
 @value interface Value {
-  get () -> (Value,Value)
+  get () -> (Value, Value)
   call () -> ()
 }
 
@@ -33,12 +33,12 @@
   }
 }
 
-concrete Test {}
+concrete Test { }
 
 
 testcase "zero return to call" {
   error
-  require "call.+\{\}"
+  require "call.+none"
 }
 
 @value interface Value {
@@ -53,7 +53,7 @@
   }
 }
 
-concrete Test {}
+concrete Test { }
 
 
 testcase "multi return assign" {
@@ -68,7 +68,7 @@
 
 concrete Test {
   @type create () -> (Test)
-  @value double () -> (Test,Test)
+  @value double () -> (Test, Test)
 }
 
 define Test {
@@ -95,7 +95,7 @@
     return 1, 2
   }
 
-  call (x,y) {
+  call (x, y) {
     if (x != 1) {
       fail("Failed")
     }
@@ -106,26 +106,58 @@
 }
 
 concrete Test {
-  @type get () -> (Int,Int)
-  @type call (Int,Int) -> ()
+  @type get () -> (Int, Int)
+  @type call (Int, Int) -> ()
 }
 
 
+testcase "multi return as args with params" {
+  success
+}
+
+unittest testExplicit {
+    \ Test.call<Int>(Test.get())
+}
+
+unittest testInferred {
+    \ Test.call(Test.get())
+}
+
+define Test {
+  get () {
+    return 1, 2
+  }
+
+  call (x, y) {
+    if (!(x `#x.lessThan` y)) {
+      fail("Failed")
+    }
+  }
+}
+
+concrete Test {
+  @type get () -> (Int, Int)
+  @type call<#x>
+    #x defines LessThan<#x>
+  (#x, #x) -> ()
+}
+
+
 testcase "return selection" {
   success
 }
 
 unittest correctPosition {
-  \ Testing.checkEquals(Test.get(){0},123)
-  \ Testing.checkEquals(Test.get(){1},"message")
+  \ Testing.checkEquals(Test.get(){0}, 123)
+  \ Testing.checkEquals(Test.get(){1}, "message")
 }
 
 unittest chainedCall {
-  \ Testing.checkEquals(Test.get(){0}.formatted(),"123")
+  \ Testing.checkEquals(Test.get(){0}.formatted(), "123")
 }
 
 concrete Test {
-  @type get () -> (Int,String)
+  @type get () -> (Int, String)
 }
 
 define Test {
@@ -145,7 +177,7 @@
 }
 
 concrete Test {
-  @type get () -> (Int,String)
+  @type get () -> (Int, String)
 }
 
 define Test {
diff --git a/tests/named-returns.0rt b/tests/named-returns.0rt
--- a/tests/named-returns.0rt
+++ b/tests/named-returns.0rt
@@ -21,13 +21,13 @@
   require "value"
 }
 
-@value interface Value {}
+@value interface Value { }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (Value)
-  process () (value) {}
+  process () (value) { }
 }
 
 
@@ -35,7 +35,7 @@
   compiles
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (Bool)
@@ -50,7 +50,7 @@
   require "value.+initialized"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (Bool)
@@ -64,7 +64,7 @@
   compiles
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (Int)
@@ -78,7 +78,7 @@
   compiles
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (Int)
@@ -93,7 +93,7 @@
   require "value.+initialized"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category process () -> (Int)
@@ -131,7 +131,7 @@
   require "value.+initialized"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category process () -> (Int)
@@ -145,7 +145,7 @@
   compiles
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category process () -> (Int)
@@ -160,7 +160,7 @@
   require "value.+initialized"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category process () -> (Int)
@@ -185,7 +185,7 @@
 }
 
 concrete Test {
-  @type get () -> (Int,Int)
+  @type get () -> (Int, Int)
 }
 
 define Test {
@@ -220,11 +220,11 @@
 }
 
 concrete Test {
-  @type get () -> (Value,Int,Int)
+  @type get () -> (Value, Int, Int)
 }
 
 define Test {
-  get () (v,x,y) {
+  get () (v, x, y) {
     // This makes sure that x and y (primitive) are offset.
     v <- Value.create()
     x <- 1
@@ -248,11 +248,11 @@
 }
 
 concrete Test {
-  @type get () -> (Int,Int)
+  @type get () -> (Int, Int)
 }
 
 define Test {
-  get () (x,y) {
+  get () (x, y) {
     x <- 1
     y <- 2
     return _
@@ -261,7 +261,7 @@
 
 
 testcase "positional return sets named return" {
-  crash
+  failure
   require "message"
 }
 
@@ -287,11 +287,11 @@
 }
 
 unittest withScoped {
-  \ Testing.checkEquals(Test.withScoped(),"message")
+  \ Testing.checkEquals(Test.withScoped(), "message")
 }
 
 unittest withCleanup {
-  \ Testing.checkEquals(Test.withCleanup(),"message")
+  \ Testing.checkEquals(Test.withCleanup(), "message")
 }
 
 concrete Test {
@@ -313,7 +313,7 @@
 
 
 testcase "named return not checked in condition that jumps" {
-  crash
+  failure
   require "success"
 }
 
diff --git a/tests/pointer/.zeolite-module b/tests/pointer/.zeolite-module
--- a/tests/pointer/.zeolite-module
+++ b/tests/pointer/.zeolite-module
@@ -1,8 +1,5 @@
 root: "../.."
 path: "tests/pointer"
-private_deps: [
-  "lib/testing"
-]
 extra_files: [
   category_source {
     source: "tests/pointer/Extension_Request.cpp"
@@ -18,4 +15,4 @@
   }
   "tests/pointer/call.hpp"
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/pointer/call.0rp b/tests/pointer/call.0rp
--- a/tests/pointer/call.0rp
+++ b/tests/pointer/call.0rp
@@ -45,6 +45,6 @@
 }
 
 concrete Service {
-  @type send (Pointer<Request>,Pointer<Response>) -> ()
-  @type send2 (Pointer<Message>,Pointer<Response>) -> ()
+  @type send (Pointer<Request>, Pointer<Response>) -> ()
+  @type send2 (Pointer<Message>, Pointer<Response>) -> ()
 }
diff --git a/tests/pointer/call.0rt b/tests/pointer/call.0rt
--- a/tests/pointer/call.0rt
+++ b/tests/pointer/call.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2022 Kevin P. Barry
+Copyright 2022-2023 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.
@@ -31,26 +31,34 @@
     Response response <- Response.create("")
     // This is just meant to test code generated for passing Pointer as a
     // function arg or return.
-    return Response.getData(callWithPointers(request.asRequest(),response.asResponse()))
+    return Response.getData(callWithPointers(request.asRequest(), response.asResponse()))
   }
 
   call2 (data) {
     Request request <- Request.create(data)
     Response response <- Response.create("")
-    \ Service.send2(request.asMessage(),response.asResponse())
+    \ Service.send2(request.asMessage(), response.asResponse())
     return response.formatted()
   }
 
-  @type callWithPointers (Pointer<Request>,Pointer<Response>) -> (Pointer<Response>)
-  callWithPointers (request,response) {
-    \ Service.send(request,response)
+  @type callWithPointers (Pointer<Request>, Pointer<Response>) -> (Pointer<Response>)
+  callWithPointers (request, response) {
+    \ Service.send(request, response)
     return response
   }
 }
 
 unittest test {
-  \ CallHelper.call("DATA") `Testing.checkEquals` "DATA has been processed as Request"
-  \ CallHelper.call2("DATA") `Testing.checkEquals` "REQUEST has been processed as Message"
+  scoped {
+    String value <- CallHelper.call("DATA")
+  } in if (value != "DATA has been processed as Request") {
+    fail(value)
+  }
+  scoped {
+    String value <- CallHelper.call2("DATA")
+  } in if (value != "REQUEST has been processed as Message") {
+    fail(value)
+  }
 }
 
 
@@ -75,23 +83,70 @@
   }
 
   call () {
-    \ Request.getData(requestPointer) `Testing.checkEquals` "request"
-    \ Response.getData(responsePointer) `Testing.checkEquals` "response"
+    scoped {
+      String value <- Request.getData(requestPointer)
+    } in if (value != "request") {
+      fail(value)
+    }
+    scoped {
+      String value <- Response.getData(responsePointer)
+    } in if (value != "response") {
+      fail(value)
+    }
 
     Pointer<Request> localRequest <- requestPointer
     Pointer<Response> localResponse <- responsePointer
-    \ Request.getData(localRequest) `Testing.checkEquals` "request"
-    \ Response.getData(localResponse) `Testing.checkEquals` "response"
+    scoped {
+      String value <- Request.getData(localRequest)
+    } in if (value != "request") {
+      fail(value)
+    }
+    scoped {
+      String value <- Response.getData(localResponse)
+    } in if (value != "response") {
+      fail(value)
+    }
 
     localRequest <- request.asRequest()
     localResponse <- response.asResponse()
-    \ Request.getData(localRequest) `Testing.checkEquals` "request"
-    \ Response.getData(localResponse) `Testing.checkEquals` "response"
+    scoped {
+      String value <- Request.getData(localRequest)
+    } in if (value != "request") {
+      fail(value)
+    }
+    scoped {
+      String value <- Response.getData(localResponse)
+    } in if (value != "response") {
+      fail(value)
+    }
 
     requestPointer <- localRequest
     responsePointer <- localResponse
-    \ Request.getData(requestPointer) `Testing.checkEquals` "request"
-    \ Response.getData(responsePointer) `Testing.checkEquals` "response"
+    scoped {
+      String value <- Request.getData(requestPointer)
+    } in if (value != "request") {
+      fail(value)
+    }
+    scoped {
+      String value <- Response.getData(responsePointer)
+    } in if (value != "response") {
+      fail(value)
+    }
+
+    // Test swapping.
+    Request request2 <- Request.create("request2")
+    Pointer<Request> requestPointer2 <- request2.asRequest()
+    localRequest <-> requestPointer2
+    scoped {
+      String value <- Request.getData(localRequest)
+    } in if (value != "request2") {
+      fail(value)
+    }
+    scoped {
+      String value <- Request.getData(requestPointer2)
+    } in if (value != "request") {
+      fail(value)
+    }
   }
 }
 
diff --git a/tests/positional-returns.0rt b/tests/positional-returns.0rt
--- a/tests/positional-returns.0rt
+++ b/tests/positional-returns.0rt
@@ -21,13 +21,13 @@
   require "Value"
 }
 
-@value interface Value {}
+@value interface Value { }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (Value)
-  process () {}
+  process () { }
 }
 
 
@@ -46,11 +46,11 @@
 }
 
 concrete Test {
-  @type get () -> (Int,Int)
+  @type get () -> (Int, Int)
 }
 
 define Test {
-  get () (x,y) {
+  get () (x, y) {
     if (false) {
       x <- 1
     } else {
@@ -76,11 +76,11 @@
 }
 
 concrete Test {
-  @type get () -> (Int,Int)
+  @type get () -> (Int, Int)
 }
 
 define Test {
-  get () (x,y) {
+  get () (x, y) {
     return 1, 2
   }
 }
@@ -101,11 +101,11 @@
 }
 
 concrete Test {
-  @type get () -> (Int,Int)
+  @type get () -> (Int, Int)
 }
 
 define Test {
-  get () (x,y) {
+  get () (x, y) {
     y <- 2
     if (false) {
       x <- 1
diff --git a/tests/program-exit.0rt b/tests/program-exit.0rt
--- a/tests/program-exit.0rt
+++ b/tests/program-exit.0rt
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "fail builtin" {
-  crash
+  failure
   require stderr "Failed"
   require stderr "failedReturn"
 }
@@ -80,7 +80,7 @@
 
 
 testcase "exit builtin non-zero" {
-  crash
+  failure
   // Should not output a stack trace.
   exclude "exitReturn"
 }
@@ -112,7 +112,7 @@
 
 
 testcase "require empty" {
-  crash
+  failure
   require stderr "require.+empty"
 }
 
diff --git a/tests/reduce.0rt b/tests/reduce.0rt
--- a/tests/reduce.0rt
+++ b/tests/reduce.0rt
@@ -23,7 +23,7 @@
 unittest test {
   Value value <- Value.create()
   scoped {
-    optional Value value2 <- reduce<Value,Value>(value)
+    optional Value value2 <- reduce<Value, Value>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
@@ -47,7 +47,7 @@
 unittest test {
   Value value <- Value.create()
   scoped {
-    optional String value2 <- reduce<Value,String>(value)
+    optional String value2 <- reduce<Value, String>(value)
   } in if (present(value2)) {
     fail("Failed")
   }
@@ -73,7 +73,7 @@
 
 unittest test {
   Value value <- Value.create()
-  optional Value value2 <- reduce<String,Value>(value)
+  optional Value value2 <- reduce<String, Value>(value)
 }
 
 concrete Value {
@@ -96,7 +96,7 @@
 
 unittest test {
   Value value <- Value.create()
-  optional Value value2 <- reduce<Value,String>(value)
+  optional Value value2 <- reduce<Value, String>(value)
 }
 
 concrete Value {
@@ -136,11 +136,11 @@
   }
 
   attempt () {
-    return reduce<Value<#x>,Value<#y>>(self)
+    return reduce<Value<#x>, Value<#y>>(self)
   }
 }
 
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
@@ -173,11 +173,11 @@
   }
 
   attempt () {
-    return reduce<Value<#x>,Value<#y>>(self)
+    return reduce<Value<#x>, Value<#y>>(self)
   }
 }
 
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
@@ -210,11 +210,11 @@
   }
 
   attempt () {
-    return reduce<Value<#x>,Value<#y>>(self)
+    return reduce<Value<#x>, Value<#y>>(self)
   }
 }
 
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
@@ -247,11 +247,11 @@
   }
 
   attempt () {
-    return reduce<Value<#x>,Value<#y>>(self)
+    return reduce<Value<#x>, Value<#y>>(self)
   }
 }
 
-@value interface Type1 {}
+@value interface Type1 { }
 
 @value interface Type2 {
   refines Type1
@@ -263,15 +263,15 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value1.create()
+  [Value1 | Value2] value <- Value1.create()
   scoped {
-    optional Base value2 <- reduce<[Value1|Value2],Base>(value)
+    optional Base value2 <- reduce<[Value1 | Value2], Base>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
 }
 
-@value interface Base {}
+@value interface Base { }
 
 concrete Value1 {
   refines Base
@@ -295,15 +295,15 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value1.create()
+  [Value1 | Value2] value <- Value1.create()
   scoped {
-    optional Value2 value2 <- reduce<[Value1|Value2],Value2>(value)
+    optional Value2 value2 <- reduce<[Value1 | Value2], Value2>(value)
   } in if (present(value2)) {
     fail("Failed")
   }
 }
 
-@value interface Base {}
+@value interface Base { }
 
 concrete Value1 {
   refines Base
@@ -329,15 +329,15 @@
 unittest test {
   Value value <- Value.create()
   scoped {
-    optional [Base1&Base2] value2 <- reduce<Value,[Base1&Base2]>(value)
+    optional [Base1 & Base2] value2 <- reduce<Value, [Base1 & Base2]>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
 }
 
-@value interface Base1 {}
+@value interface Base1 { }
 
-@value interface Base2 {}
+@value interface Base2 { }
 
 concrete Value {
   refines Base1
@@ -360,15 +360,15 @@
 unittest test {
   Value value <- Value.create()
   scoped {
-    optional [Base1&Base2] value2 <- reduce<Value,[Base1&Base2]>(value)
+    optional [Base1 & Base2] value2 <- reduce<Value, [Base1 & Base2]>(value)
   } in if (present(value2)) {
     fail("Failed")
   }
 }
 
-@value interface Base1 {}
+@value interface Base1 { }
 
-@value interface Base2 {}
+@value interface Base2 { }
 
 concrete Value {
   refines Base1
@@ -388,17 +388,17 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value2.create()
+  [Value1 | Value2] value <- Value2.create()
   scoped {
-    optional [Base1&Base2] value2 <- reduce<[Value1|Value2],[Base1&Base2]>(value)
+    optional [Base1 & Base2] value2 <- reduce<[Value1 | Value2], [Base1 & Base2]>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
 }
 
-@value interface Base1 {}
+@value interface Base1 { }
 
-@value interface Base2 {}
+@value interface Base2 { }
 
 @value interface Value1 {
   refines Base1
@@ -424,17 +424,17 @@
 }
 
 unittest test {
-  [Value1|Value2] value <- Value2.create()
+  [Value1 | Value2] value <- Value2.create()
   scoped {
-    optional [Base1&Base2] value2 <- reduce<[Value1|Value2],[Base1&Base2]>(value)
+    optional [Base1 & Base2] value2 <- reduce<[Value1 | Value2], [Base1 & Base2]>(value)
   } in if (present(value2)) {
     fail("Failed")
   }
 }
 
-@value interface Base1 {}
+@value interface Base1 { }
 
-@value interface Base2 {}
+@value interface Base2 { }
 
 @value interface Value1 {
   refines Base1
@@ -459,23 +459,23 @@
 }
 
 unittest test {
-  [Value1&Value2] value <- Data.create()
+  [Value1 & Value2] value <- Data.create()
   scoped {
-    optional [Base1|Base2] value2 <- reduce<[Value1&Value2],[Base1|Base2]>(value)
+    optional [Base1 | Base2] value2 <- reduce<[Value1 & Value2], [Base1 | Base2]>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
 }
 
-@value interface Base1 {}
+@value interface Base1 { }
 
-@value interface Base2 {}
+@value interface Base2 { }
 
 @value interface Value1 {
   refines Base1
 }
 
-@value interface Value2 {}
+@value interface Value2 { }
 
 concrete Data {
   refines Value1
@@ -496,21 +496,21 @@
 }
 
 unittest test {
-  [Value1&Value2] value <- Data.create()
+  [Value1 & Value2] value <- Data.create()
   scoped {
-    optional [Base1|Base2] value2 <- reduce<[Value1&Value2],[Base1|Base2]>(value)
+    optional [Base1 | Base2] value2 <- reduce<[Value1 & Value2], [Base1 | Base2]>(value)
   } in if (present(value2)) {
     fail("Failed")
   }
 }
 
-@value interface Base1 {}
+@value interface Base1 { }
 
-@value interface Base2 {}
+@value interface Base2 { }
 
-@value interface Value1 {}
+@value interface Value1 { }
 
-@value interface Value2 {}
+@value interface Value2 { }
 
 concrete Data {
   refines Value1
@@ -533,7 +533,7 @@
 unittest test {
   Value<String> value <- Value<String>.create()
   scoped {
-    optional Value<any> value2 <- reduce<Value<String>,Value<any>>(value)
+    optional Value<any> value2 <- reduce<Value<String>, Value<any>>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
@@ -557,7 +557,7 @@
 unittest test {
   Value<String> value <- Value<String>.create()
   scoped {
-    optional Value<all> value2 <- reduce<Value<String>,Value<all>>(value)
+    optional Value<all> value2 <- reduce<Value<String>, Value<all>>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
@@ -581,7 +581,7 @@
 unittest test {
   Value<String> value <- Value<String>.create()
   scoped {
-    optional Value<any> value2 <- reduce<Value<String>,Value<any>>(value)
+    optional Value<any> value2 <- reduce<Value<String>, Value<any>>(value)
   } in if (present(value2)) {
     fail("Failed")
   }
@@ -605,7 +605,7 @@
 unittest test {
   Value<all> value <- Value<all>.create()
   scoped {
-    optional Value<String> value2 <- reduce<Value<all>,Value<String>>(value)
+    optional Value<String> value2 <- reduce<Value<all>, Value<String>>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
@@ -629,7 +629,7 @@
 unittest test {
   Value<any> value <- Value<any>.create()
   scoped {
-    optional Value<String> value2 <- reduce<Value<any>,Value<String>>(value)
+    optional Value<String> value2 <- reduce<Value<any>, Value<String>>(value)
   } in if (!present(value2)) {
     fail("Failed")
   }
@@ -653,7 +653,7 @@
 unittest test {
   Value<all> value <- Value<all>.create()
   scoped {
-    optional Value<String> value2 <- reduce<Value<all>,Value<String>>(value)
+    optional Value<String> value2 <- reduce<Value<all>, Value<String>>(value)
   } in if (present(value2)) {
     fail("Failed")
   }
@@ -675,7 +675,7 @@
 }
 
 unittest test {
-  if (!present(reduce<Base1,Base0>(Value.create()))) {
+  if (!present(reduce<Base1, Base0>(Value.create()))) {
     fail("Failed")
   }
 }
@@ -704,21 +704,21 @@
 }
 
 unittest test {
-  if (!present(reduce<Value,Base2<Base0>>(Value.create()))) {
+  if (!present(reduce<Value, Base2<Base0>>(Value.create()))) {
     fail("Failed")
   }
-  if (present(reduce<Value,Base2<Base1>>(Value.create()))) {
+  if (present(reduce<Value, Base2<Base1>>(Value.create()))) {
     fail("Failed")
   }
 }
 
-@value interface Base0 {}
+@value interface Base0 { }
 
 @value interface Base1 {
   refines Base0
 }
 
-@value interface Base2<|#x> {}
+@value interface Base2<|#x> { }
 
 concrete Value {
   refines Base2<Base0>
@@ -738,9 +738,9 @@
   success
 }
 
-@value interface A {}
-@value interface B {}
-@value interface C {}
+@value interface A { }
+@value interface B { }
+@value interface C { }
 
 concrete AB {
   refines A
@@ -763,23 +763,23 @@
 }
 
 unittest leftNestedInRight {
-  \ Testing.checkTrue(present(reduce<[A&B],[[A&B]|C]>(AB.create())))
+  \ Testing.checkTrue(present(reduce<[A & B], [[A & B] | C]>(AB.create())))
 }
 
 unittest rightNestedInLeft {
-  \ Testing.checkTrue(present(reduce<[[A|B]&C],[A|B]>(AC.create())))
+  \ Testing.checkTrue(present(reduce<[[A | B] & C], [A | B]>(AC.create())))
 }
 
 unittest unionToUnion {
-  \ Testing.checkTrue(present(reduce<[[A&B]|C],[[A&B]|C]>(AB.create())))
+  \ Testing.checkTrue(present(reduce<[[A & B] | C], [[A & B] | C]>(AB.create())))
 }
 
 unittest intersectToIntersect {
-  \ Testing.checkTrue(present(reduce<[[A|B]&C],[[A|B]&C]>(AC.create())))
+  \ Testing.checkTrue(present(reduce<[[A | B] & C], [[A | B] & C]>(AC.create())))
 }
 
 unittest unionToIntersect {
-  \ Testing.checkFalse(present(reduce<[A|B],[A&B]>(AB.create())))
+  \ Testing.checkFalse(present(reduce<[A | B], [A & B]>(AB.create())))
 }
 
 
@@ -788,13 +788,13 @@
 }
 
 unittest test {
-  \ Testing.checkTrue(present(reduce<Type,Build<AsBool>>(Type.new())))
-  \ Testing.checkTrue(present(reduce<Type,Build<ReadAt<Char>>>(Type.new())))
-  \ Testing.checkFalse(present(reduce<Type,Build<String>>(Type.new())))
+  \ Testing.checkTrue(present(reduce<Type, Build<AsBool>>(Type.new())))
+  \ Testing.checkTrue(present(reduce<Type, Build<ReadAt<Char>>>(Type.new())))
+  \ Testing.checkFalse(present(reduce<Type, Build<String>>(Type.new())))
 }
 
 concrete Type {
-  refines Build<[AsBool&ReadAt<Char>]>
+  refines Build<[AsBool & ReadAt<Char>]>
 
   @type new () -> (Type)
 }
@@ -810,11 +810,11 @@
 }
 
 unittest test {
-  \ Testing.checkTrue(present(reduce<Type,Order<Type>>(Type.new())))
-  \ Testing.checkTrue(present(reduce<Type,Order<Order<Type>>>(Type.new())))
-  \ Testing.checkFalse(present(reduce<Type,Build<Type>>(Type.new())))
-  \ Testing.checkTrue(present(reduce<Type,Build<Order<Type>>>(Type.new())))
-  \ Testing.checkTrue(present(reduce<Type,Order<Formatted>>(Type.new())))
+  \ Testing.checkTrue(present(reduce<Type, Order<Type>>(Type.new())))
+  \ Testing.checkTrue(present(reduce<Type, Order<Order<Type>>>(Type.new())))
+  \ Testing.checkFalse(present(reduce<Type, Build<Type>>(Type.new())))
+  \ Testing.checkTrue(present(reduce<Type, Build<Order<Type>>>(Type.new())))
+  \ Testing.checkTrue(present(reduce<Type, Order<Formatted>>(Type.new())))
 }
 
 concrete Type {
diff --git a/tests/regressions.0rt b/tests/regressions.0rt
--- a/tests/regressions.0rt
+++ b/tests/regressions.0rt
@@ -30,7 +30,7 @@
 }
 
 define Type1 {
-  call (_) {}
+  call (_) { }
 }
 
 concrete Type2<#y> {
@@ -82,8 +82,8 @@
 }
 
 define Child {
-  call1 (_) {}
-  call2 (_) {}
+  call1 (_) { }
+  call2 (_) { }
 }
 
 
@@ -139,7 +139,7 @@
   }
 
   // The count here would be 4 if scoped was prepended to cleanup.
-  \ Testing.checkEquals<?>(count,3)
+  \ Testing.checkEquals<?>(count, 3)
 }
 
 
@@ -213,8 +213,8 @@
 
 testcase "Issue #126 is fixed" {
   // https://github.com/ta0kira/zeolite/issues/126
-  crash
-  require "message,12345,false"
+  failure
+  require "message, 12345, false"
 }
 
 unittest test {
@@ -222,15 +222,15 @@
 }
 
 concrete Type {
-  @type call () -> (String,Int,Bool)
+  @type call () -> (String, Int, Bool)
 }
 
 define Type {
-  call () (x,y,z) {
+  call () (x, y, z) {
     cleanup {
       // Primitive variables y and z need to be explicitly set separately from
       // the return tuple, since the former are stored separately.
-      fail(x + "," + y.formatted() + "," + z.formatted())
+      fail(x + ", " + y.formatted() + ", " + z.formatted())
     } in return "message", 12345, false
   }
 }
@@ -259,7 +259,7 @@
       if (true && message != "message") {
         fail("failed")
       }
-    } in {}
+    } in { }
     return "return"
   }
 }
@@ -288,7 +288,7 @@
       while (true && message != "message") {
         fail("failed")
       }
-    } in {}
+    } in { }
     return "return"
   }
 }
@@ -296,7 +296,7 @@
 
 testcase "Issue #185 is fixed" {
   // https://github.com/ta0kira/zeolite/issues/185
-  crash
+  failure
   require "message"
 }
 
diff --git a/tests/scoped.0rt b/tests/scoped.0rt
--- a/tests/scoped.0rt
+++ b/tests/scoped.0rt
@@ -47,7 +47,7 @@
   compiles
 }
 
-concrete Value {}
+concrete Value { }
 
 define Value {
   @value process () -> (optional Value)
@@ -63,7 +63,7 @@
   compiles
 }
 
-concrete Value {}
+concrete Value { }
 
 define Value {
   @value process () -> (optional Value)
@@ -93,7 +93,7 @@
   compiles
 }
 
-concrete Value {}
+concrete Value { }
 
 define Value {
   @value process () -> (optional Value)
@@ -109,7 +109,7 @@
   compiles
 }
 
-concrete Value {}
+concrete Value { }
 
 define Value {
   @value process () -> (optional Value)
@@ -196,7 +196,7 @@
 
 
 testcase "positional return sets named returns for cleanup" {
-  crash
+  failure
   require "message"
   exclude "failed"
 }
@@ -208,11 +208,11 @@
 concrete Test {
   // Using a second return ensures that assignment works properly when the
   // ReturnTuple is constructed during assignment.
-  @type get () -> (String,Int)
+  @type get () -> (String, Int)
 }
 
 define Test {
-  get () (value,value2) {
+  get () (value, value2) {
     value <- "failed"
     cleanup {
       fail(value)
@@ -224,7 +224,7 @@
 
 
 testcase "top-level assignment sets named return for cleanup" {
-  crash
+  failure
   require "message"
   exclude "failed"
 }
@@ -252,7 +252,7 @@
   require "value.+not defined"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type get () -> (Int)
@@ -352,8 +352,8 @@
 
 concrete Value {
   @type create () -> (Value)
-  @value call () -> (Int,Int,Int)
-  @value get () -> (Int,Int,Int)
+  @value call () -> (Int, Int, Int)
+  @value get () -> (Int, Int, Int)
 }
 
 define Value {
@@ -396,7 +396,7 @@
     value <- 1
   } in Int value <- 2
 
-  \ Testing.checkEquals(value,1)
+  \ Testing.checkEquals(value, 1)
 }
 
 
@@ -409,7 +409,7 @@
   scoped {
   } cleanup {
     value <- 1
-  } in {}
+  } in { }
 
   Int value <- 2
 }
@@ -462,7 +462,7 @@
 
 
 testcase "cleanup skipped for fail" {
-  crash
+  failure
   require "scoped"
 }
 
@@ -563,7 +563,7 @@
 
 
 testcase "separate trace context for cleanup" {
-  crash
+  failure
   require "Failed"
   require "cleanup block"
   require "Test\.run"
@@ -610,13 +610,13 @@
         }
       }
     }
-    \ Testing.checkEquals(x,0)
-    \ Testing.checkEquals(y,2)
-    \ Testing.checkEquals(z,3)
+    \ Testing.checkEquals(x, 0)
+    \ Testing.checkEquals(y, 2)
+    \ Testing.checkEquals(z, 3)
   }
-  \ Testing.checkEquals(x,1)
-  \ Testing.checkEquals(y,2)
-  \ Testing.checkEquals(z,3)
+  \ Testing.checkEquals(x, 1)
+  \ Testing.checkEquals(y, 2)
+  \ Testing.checkEquals(z, 3)
 }
 
 
@@ -634,8 +634,8 @@
   } in {
     while ((called <- called+1) < 3) {
       if (called > 1) {
-        \ Testing.checkEquals(y,2)
-        \ Testing.checkEquals(z,3)
+        \ Testing.checkEquals(y, 2)
+        \ Testing.checkEquals(z, 3)
       }
       cleanup {
         y <- 2
@@ -647,14 +647,14 @@
         }
       }
     }
-    \ Testing.checkEquals(called,3)
-    \ Testing.checkEquals(x,0)
-    \ Testing.checkEquals(y,2)
-    \ Testing.checkEquals(z,3)
+    \ Testing.checkEquals(called, 3)
+    \ Testing.checkEquals(x, 0)
+    \ Testing.checkEquals(y, 2)
+    \ Testing.checkEquals(z, 3)
   }
-  \ Testing.checkEquals(x,1)
-  \ Testing.checkEquals(y,2)
-  \ Testing.checkEquals(z,3)
+  \ Testing.checkEquals(x, 1)
+  \ Testing.checkEquals(y, 2)
+  \ Testing.checkEquals(z, 3)
 }
 
 
@@ -667,23 +667,23 @@
   scoped {
     Int x, Int y, Int z <- value.call()
   } in {
-    \ Testing.checkEquals(x,0)
-    \ Testing.checkEquals(y,0)
-    \ Testing.checkEquals(z,0)
+    \ Testing.checkEquals(x, 0)
+    \ Testing.checkEquals(y, 0)
+    \ Testing.checkEquals(z, 0)
   }
   scoped {
     Int x, Int y, Int z <- value.get()
   } in {
-    \ Testing.checkEquals(x,1)
-    \ Testing.checkEquals(y,2)
-    \ Testing.checkEquals(z,3)
+    \ Testing.checkEquals(x, 1)
+    \ Testing.checkEquals(y, 2)
+    \ Testing.checkEquals(z, 3)
   }
 }
 
 concrete Value {
   @type create () -> (Value)
-  @value call () -> (Int,Int,Int)
-  @value get () -> (Int,Int,Int)
+  @value call () -> (Int, Int, Int)
+  @value get () -> (Int, Int, Int)
 }
 
 define Value {
@@ -723,7 +723,7 @@
   require compiler "assign.+value"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type get () -> (String)
@@ -739,7 +739,7 @@
   compiles
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type get () -> (String)
@@ -756,7 +756,7 @@
   require compiler "return.+cleanup"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type get () -> (Int)
diff --git a/tests/self-offset/.zeolite-module b/tests/self-offset/.zeolite-module
--- a/tests/self-offset/.zeolite-module
+++ b/tests/self-offset/.zeolite-module
@@ -1,8 +1,5 @@
 root: "../.."
 path: "tests/self-offset"
-private_deps: [
-  "lib/testing"
-]
 extra_files: [
   category_source {
     source: "tests/self-offset/Extension_Destructor.cpp"
@@ -13,4 +10,4 @@
     categories: [Offset]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/self-offset/offset.0rt b/tests/self-offset/offset.0rt
--- a/tests/self-offset/offset.0rt
+++ b/tests/self-offset/offset.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -21,12 +21,16 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Offset.new().call().get(),"different")
+  scoped {
+    String value <- Offset.new().call().get()
+  } in if (value != "different") {
+    fail(value)
+  }
 }
 
 
 testcase "VAR_SELF not allowed in destructor" {
-  crash
+  failure
   require "VAR_SELF"
   require "Destructor"
 }
diff --git a/tests/self-type.0rt b/tests/self-type.0rt
--- a/tests/self-type.0rt
+++ b/tests/self-type.0rt
@@ -37,7 +37,7 @@
   require "#self not found"
 }
 
-concrete Type {}
+concrete Type { }
 
 define Type {
   @category optional #self value <- empty
@@ -239,11 +239,11 @@
   }
 
   checkFrom () {
-    return present(reduce<#self,#y>(self))
+    return present(reduce<#self, #y>(self))
   }
 
   checkTo (from) {
-    return present(reduce<#y,#self>(from))
+    return present(reduce<#y, #self>(from))
   }
 }
 
@@ -253,7 +253,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Type<Bool>.get(),"Type<Type<Bool>>")
+  \ Testing.checkEquals(Type<Bool>.get(), "Type<Type<Bool>>")
 }
 
 concrete Type<#x> {
@@ -374,12 +374,12 @@
 
 unittest withConcrete {
   Type value <- Type.create()
-  \ Testing.checkEquals(value.next().prev().prev().next().next().get(),1)
+  \ Testing.checkEquals(value.next().prev().prev().next().next().get(), 1)
 }
 
 unittest withIntersect {
-  [Cell<Int>&Forward&Reverse] value <- Type.create()
-  \ Testing.checkEquals(value.next().prev().prev().next().next().get(),1)
+  [Cell<Int> & Forward & Reverse] value <- Type.create()
+  \ Testing.checkEquals(value.next().prev().prev().next().next().get(), 1)
 }
 
 @value interface Cell<|#x> {
@@ -431,7 +431,7 @@
 
 unittest test {
   Type value <- Helper.new<Type>()
-  \ Testing.checkEquals((value `Advance.by` 3).get(),3)
+  \ Testing.checkEquals((value `Advance.by` 3).get(), 3)
 }
 
 @value interface Forward {
@@ -453,11 +453,11 @@
 concrete Advance {
   @type by<#x>
     #x requires Forward
-  (#x,Int) -> (#x)
+  (#x, Int) -> (#x)
 }
 
 define Advance {
-  by (x,i) (x2) {
+  by (x, i) (x2) {
     x2 <- x
     scoped {
       Int count <- 0
@@ -501,7 +501,7 @@
   #x allows #self
 }
 
-define Type {}
+define Type { }
 
 
 testcase "#self nested in param filter" {
@@ -513,9 +513,9 @@
   #x defines Equals<#self>
 }
 
-define Type {}
+define Type { }
 
-@value interface Writer<#x|> {}
+@value interface Writer<#x|> { }
 
 
 testcase "#self nested in inheritance" {
@@ -554,13 +554,13 @@
   #x allows #self
 }
 
-define Type1 {}
+define Type1 { }
 
 concrete Type2<#x> {
   #x requires #self
 }
 
-define Type2 {}
+define Type2 { }
 
 
 testcase "#self allowed in covavariant position of function param filter" {
@@ -654,7 +654,7 @@
   exclude "#self"
 }
 
-@value interface Reader<|#x> {}
+@value interface Reader<|#x> { }
 
 concrete Type {
   refines Reader<#self>
@@ -675,13 +675,13 @@
   require "contravariant"
 }
 
-@value interface Base<#x|> {}
+@value interface Base<#x|> { }
 
 concrete Type {
   refines Base<#self>
 }
 
-define Type {}
+define Type { }
 
 
 testcase "#self cannot be contravariant in define" {
@@ -691,13 +691,13 @@
   require "contravariant"
 }
 
-@type interface Base<#x|> {}
+@type interface Base<#x|> { }
 
 concrete Type {
   defines Base<#self>
 }
 
-define Type {}
+define Type { }
 
 
 testcase "#self cannot be invariant in refine" {
@@ -707,13 +707,13 @@
   require "invariant"
 }
 
-@value interface Base<#x> {}
+@value interface Base<#x> { }
 
 concrete Type {
   refines Base<#self>
 }
 
-define Type {}
+define Type { }
 
 
 testcase "#self cannot be invariant in define" {
@@ -723,13 +723,13 @@
   require "invariant"
 }
 
-@type interface Base<#x> {}
+@type interface Base<#x> { }
 
 concrete Type {
   defines Base<#self>
 }
 
-define Type {}
+define Type { }
 
 
 testcase "#self nested in value init with filters" {
@@ -737,7 +737,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Type<Int>.create().formatted(),"Type<Type<Int>>")
+  \ Testing.checkEquals(Type<Int>.create().formatted(), "Type<Type<Int>>")
 }
 
 concrete Type<|#x> {
diff --git a/tests/simple.0rt b/tests/simple.0rt
--- a/tests/simple.0rt
+++ b/tests/simple.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020,2022 Kevin P. Barry
+Copyright 2020,2022-2023 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.
@@ -47,7 +47,7 @@
 
 
 testcase "basic crash test" {
-  crash
+  failure
   require stderr "testcase:"
   require stderr "failure message!!!"
   exclude stdout "failure message!!!"
@@ -86,7 +86,7 @@
 }
 
 // The declaration for String is in scope, but it lives outside of this module.
-define String {}
+define String { }
 
 
 testcase "attempt to define interface" {
@@ -95,9 +95,9 @@
   require "concrete"
 }
 
-@value interface Type {}
+@value interface Type { }
 
-define Type {}
+define Type { }
 
 
 testcase "no deadlock with recursive type" {
@@ -113,7 +113,7 @@
 }
 
 define Type {
-  call () {}
+  call () { }
 }
 
 
@@ -127,13 +127,13 @@
 
 
 testcase "custom timeout works as expected" {
-  crash
+  failure
   require "signal 14"
   timeout 1
 }
 
 unittest test {
-  while (true) {}
+  while (true) { }
 }
 
 
@@ -144,4 +144,146 @@
   args "\" }; exit(1); const char fake[] = { \""
 }
 
-unittest test {}
+unittest test { }
+
+
+testcase "inline assignment return type" {
+  success
+}
+
+unittest unboxedToUnboxed {
+  Int value <- 456
+  Int value2 <- (value <- 123)
+  \ Testing.checkEquals(value2, 123)
+  \ Testing.checkEquals(value, 123)
+}
+
+unittest unboxedToBoxed {
+  optional Formatted value <- empty
+  Int value2 <- (value <- 123)
+  \ Testing.checkEquals(value2, 123)
+  \ Testing.checkPresent(value)
+  \ Testing.checkEquals(require(value).formatted(), "123")
+}
+
+unittest boxedToBoxed {
+  optional Formatted value <- empty
+  String value2 <- (value <- "message")
+  \ Testing.checkEquals(value2, "message")
+  \ Testing.checkPresent(value)
+  \ Testing.checkEquals(require(value).formatted(), "message")
+}
+
+
+testcase "inline assignment wrong return type" {
+  error
+  require "String"
+  require "Int"
+}
+
+unittest test {
+  optional Formatted value <- empty
+  String value2 <- (value <- 123)
+}
+
+
+testcase "custom testcase is started and finished" {
+  failure Helper
+  require "started"
+  exclude "not started"
+}
+
+concrete Helper {
+  defines Testcase
+  @type started () -> (Bool)
+}
+
+define Helper {
+  @category Bool started <- false
+
+  started () {
+    return started
+  }
+
+  start () {
+    started <- true
+  }
+
+  finish () {
+    if (started) {
+      fail("started")
+    } else {
+      fail("not started")
+    }
+  }
+}
+
+unittest test {
+  \ Testing.checkTrue(Helper.started())
+}
+
+
+testcase "custom testcase is finished on exit" {
+  failure Helper
+  require "started"
+  exclude "not started"
+}
+
+concrete Helper {
+  defines Testcase
+}
+
+define Helper {
+  @category Bool started <- false
+
+  start () {
+    started <- true
+  }
+
+  finish () {
+    if (started) {
+      fail("started")
+    } else {
+      fail("not started")
+    }
+  }
+}
+
+unittest test {
+  exit(0)
+}
+
+
+testcase "custom testcase is finished at most once with explicit exit" {
+  success Helper
+}
+
+concrete Helper {
+  defines Testcase
+  @type started () -> (Bool)
+}
+
+define Helper {
+  @category Bool started <- false
+  @category Bool called <- false
+
+  started () {
+    return started
+  }
+
+  start () {
+    started <- true
+  }
+
+  finish () {
+    if (called) {
+      fail("called twice")
+    }
+    called <- true
+  }
+}
+
+unittest test {
+  \ Testing.checkTrue(Helper.started())
+  exit(0)
+}
diff --git a/tests/simulate-refs/main.0rx b/tests/simulate-refs/main.0rx
--- a/tests/simulate-refs/main.0rx
+++ b/tests/simulate-refs/main.0rx
@@ -47,17 +47,17 @@
   @type getRoutines (ReferenceState) -> (DefaultOrder<StateMachine>)
   getRoutines (state) {
     return Vector<StateMachine>.new()
-        .append(Routines.newShared("shared1",state))
-        .append(Routines.newWeak("weak1",state))
-        .append(Routines.newWeak("weak2",state))
+        .append(Routines.newShared("shared1", state))
+        .append(Routines.newWeak("weak1", state))
+        .append(Routines.newWeak("weak2", state))
   }
 
   @type getRoutinesBroken (ReferenceState) -> (DefaultOrder<StateMachine>)
   getRoutinesBroken (state) {
     return Vector<StateMachine>.new()
-        .append(Routines.newSharedBroken("shared1",state))
-        .append(Routines.newWeakBroken("weak1",state))
-        .append(Routines.newWeakBroken("weak2",state))
+        .append(Routines.newSharedBroken("shared1", state))
+        .append(Routines.newWeakBroken("weak1", state))
+        .append(Routines.newWeakBroken("weak2", state))
   }
 
   @type runOnce (Bool) -> (Bool)
@@ -69,7 +69,7 @@
     } else {
       routines <- getRoutines(state)
     }
-    \ StateExecutor:multiplexStates(routines,random)
+    \ StateExecutor:multiplexStates(routines, random)
 
     scoped {
       optional String error <- state.getError()
diff --git a/tests/simulate-refs/ref-state.0rp b/tests/simulate-refs/ref-state.0rp
--- a/tests/simulate-refs/ref-state.0rp
+++ b/tests/simulate-refs/ref-state.0rp
@@ -18,16 +18,6 @@
 
 $ModuleOnly$
 
-@value interface ReferenceMachine {
-  immutable
-
-  runWith (String,ReferenceState) -> (optional ReferenceMachine)
-}
-
-concrete Run {
-  @type then (ReferenceMachine,ReferenceMachine) -> (ReferenceMachine)
-}
-
 concrete ReferenceState {
   refines Formatted
 
diff --git a/tests/simulate-refs/ref-state.0rx b/tests/simulate-refs/ref-state.0rx
--- a/tests/simulate-refs/ref-state.0rx
+++ b/tests/simulate-refs/ref-state.0rx
@@ -22,7 +22,7 @@
   @value Bool locked
   @value Int aliveCleanup
   @value Int dataCleanup
-  @value [Append<Formatted>&DefaultOrder<Formatted>] operations
+  @value [Append<Formatted> & DefaultOrder<Formatted>] operations
 
   new () {
     return ReferenceState{ 0, 0, false, 0, 0, Vector<Formatted>.new() }
@@ -136,36 +136,5 @@
         .append(" DC: ")
         .append(dataCleanup)
         .build()
-  }
-}
-
-define Run {
-  then (first,second) {
-    return RunThen.new(first,second)
-  }
-}
-
-concrete RunThen {
-  @type new (ReferenceMachine,ReferenceMachine) -> (ReferenceMachine)
-}
-
-define RunThen {
-  refines ReferenceMachine
-
-  @value ReferenceMachine first
-  @value ReferenceMachine second
-
-  new (first,second) {
-    return RunThen{ first, second }
-  }
-
-  runWith (name,state) {
-    scoped {
-      optional ReferenceMachine next <- first.runWith(name,state)
-    } in if (present(next)) {
-      return RunThen{ require(next), second }
-    } else {
-      return second
-    }
   }
 }
diff --git a/tests/simulate-refs/routines.0rp b/tests/simulate-refs/routines.0rp
--- a/tests/simulate-refs/routines.0rp
+++ b/tests/simulate-refs/routines.0rp
@@ -19,9 +19,9 @@
 $ModuleOnly$
 
 concrete Routines {
-  @type newShared (String,ReferenceState) -> (StateMachine)
-  @type newWeak   (String,ReferenceState) -> (StateMachine)
+  @type newShared (String, ReferenceState) -> (StateMachine)
+  @type newWeak   (String, ReferenceState) -> (StateMachine)
 
-  @type newSharedBroken (String,ReferenceState) -> (StateMachine)
-  @type newWeakBroken   (String,ReferenceState) -> (StateMachine)
+  @type newSharedBroken (String, ReferenceState) -> (StateMachine)
+  @type newWeakBroken   (String, ReferenceState) -> (StateMachine)
 }
diff --git a/tests/simulate-refs/routines.0rx b/tests/simulate-refs/routines.0rx
--- a/tests/simulate-refs/routines.0rx
+++ b/tests/simulate-refs/routines.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021-2023 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.
@@ -17,86 +17,69 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define Routines {
-  $ReadOnlyExcept[machine]$
-
-  refines StateMachine
-
-  @category ReferenceMachine dropWeak <-
-      IfElseDecrWeakIsZero.new(
-      /*if*/   FreeRef.new(),
-      /*else*/ empty)
-
-  @category ReferenceMachine sharedThenDrop <-
-      LockRef.new() `Run.then`
-      IfElseIncrRefIsOne.new(
-      /*if*/   DecrRef.new() `Run.then`
-               UnlockRef.new() `Run.then`
-               dropWeak,
-      /*else*/ UnlockRef.new() `Run.then`
-               dropWeak `Run.then`
-               dropShared)
+  @category optional StateTransition<String, ReferenceState> dropWeak <-
+      Run:ifElse(
+      test:   DecrWeakIsZero.new(),
+      doIf:   FreeRef.new(),
+      doElse: empty)
 
-  @category ReferenceMachine dropShared <-
-      LockRef.new() `Run.then`
-      IfElseDecrRefIsZero.new(
-      /*if*/   FreeObject.new() `Run.then`
-               UnlockRef.new() `Run.then`
-               IfElseDecrWeakIsZero.new(
-               /*if*/   FreeRef.new(),
-               /*else*/ empty),
-      /*else*/ UnlockRef.new())
+  @category optional StateTransition<String, ReferenceState> sharedThenDrop <-
+      LockRef.new() `Run:then`
+      Run:ifElse(
+      test:   IncrRefIsOne.new(),
+      doIf:   DecrRef.new() `Run:then`
+              UnlockRef.new() `Run:then`
+              dropWeak,
+      doElse: UnlockRef.new() `Run:then`
+              dropWeak `Run:then`
+              dropShared)
 
-  @category ReferenceMachine sharedThenDropBroken <-
-      IfElsePlusLockModLockZero.new(
-      /*if*/   MinusLock.new() `Run.then`
-               dropWeak,
-      /*else*/ MinusLockPlusOne.new() `Run.then`
-               dropWeak `Run.then`
-               dropSharedBroken)
+  @category optional StateTransition<String, ReferenceState> dropShared <-
+      LockRef.new() `Run:then`
+      Run:ifElse(
+      test:   DecrRefIsZero.new(),
+      doIf:   FreeObject.new() `Run:then`
+              UnlockRef.new() `Run:then`
+              dropWeak,
+      doElse: UnlockRef.new())
 
-  @category ReferenceMachine dropSharedBroken <-
-      IfElseDecrRefIsZero.new(
-      /*if*/   FreeObject.new() `Run.then`
-               IfElseDecrWeakIsZero.new(
-               /*if*/   FreeRef.new(),
-               /*else*/ empty),
-      /*else*/ empty)
+  @category optional StateTransition<String, ReferenceState> sharedThenDropBroken <-
+      Run:ifElse(
+      test:   PlusLockModLockZero.new(),
+      doIf:   MinusLock.new() `Run:then`
+              dropWeak,
+      doElse: MinusLockPlusOne.new() `Run:then`
+              dropWeak `Run:then`
+              dropSharedBroken)
 
-  @value String name
-  @value ReferenceState state
-  @value optional ReferenceMachine machine
+  @category optional StateTransition<String, ReferenceState> dropSharedBroken <-
+      Run:ifElse(
+      test:   DecrRefIsZero.new(),
+      doIf:   FreeObject.new() `Run:then`
+              dropWeak,
+      doElse: empty)
 
-  newShared (name,state) {
+  newShared (name, state) {
     if (state.addStrong() == 1) {
       \ state.addWeak()
     }
-    return #self{ name, state, dropShared }
+    return LabeledStateMachine:new(name, state, dropShared)
   }
 
-  newWeak (name,state) {
+  newWeak (name, state) {
     \ state.addWeak()
-    return #self{ name, state, sharedThenDrop }
+    return LabeledStateMachine:new(name, state, sharedThenDrop)
   }
 
-  newSharedBroken (name,state) {
+  newSharedBroken (name, state) {
     if (state.addStrong() == 1) {
       \ state.addWeak()
     }
-    return #self{ name, state, dropSharedBroken }
+    return LabeledStateMachine:new(name, state, dropSharedBroken)
   }
 
-  newWeakBroken (name,state) {
+  newWeakBroken (name, state) {
     \ state.addWeak()
-    return #self{ name, state, sharedThenDropBroken }
-  }
-
-  transition () {
-    if (!present(machine)) {
-      return empty
-    } elif (present((machine <- require(machine).runWith(name,state)))) {
-      return self
-    } else {
-      return empty
-    }
+    return LabeledStateMachine:new(name, state, sharedThenDropBroken)
   }
 }
diff --git a/tests/simulate-refs/state.0rp b/tests/simulate-refs/state.0rp
--- a/tests/simulate-refs/state.0rp
+++ b/tests/simulate-refs/state.0rp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -22,6 +22,38 @@
   transition () -> (optional StateMachine)
 }
 
+@value interface StateTransition<#l, #s|> {
+  immutable
+
+  runWith (#l, #s) -> (optional StateTransition<#l, #s>)
+}
+
+@value interface StatePredicate<#l, #s|> {
+  immutable
+
+  test (#l, #s) -> (Bool)
+}
+
+concrete Run {
+  @category then<#l, #s>
+  (optional StateTransition<#l, #s>, optional StateTransition<#l, #s>) ->
+  (optional StateTransition<#l, #s>)
+
+  @category ifElse<#l, #s>
+  (StatePredicate<#l, #s> test:,
+   optional StateTransition<#l, #s> doIf:,
+   optional StateTransition<#l, #s> doElse:) ->
+  (StateTransition<#l, #s>)
+}
+
+concrete LabeledStateMachine<#l, #s> {
+  #l immutable
+
+  @category new<#l, #s>
+    #l immutable
+  (#l, #s, optional StateTransition<#l, #s>) -> (StateMachine)
+}
+
 concrete StateExecutor {
-  @category multiplexStates (DefaultOrder<StateMachine>,Generator<Float>) -> ()
+  @category multiplexStates (DefaultOrder<StateMachine>, Generator<Float>) -> ()
 }
diff --git a/tests/simulate-refs/state.0rx b/tests/simulate-refs/state.0rx
--- a/tests/simulate-refs/state.0rx
+++ b/tests/simulate-refs/state.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021-2023 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.
@@ -16,16 +16,107 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
+define Run {
+  then (first, second) {
+    return delegate -> `RunThen:new`
+  }
+
+  ifElse (test, doIf, doElse) {
+    return delegate -> `RunIfElse:new`
+  }
+}
+
+concrete RunThen<#l, #s|> {
+  @category new<#l, #s> (optional StateTransition<#l, #s>, optional StateTransition<#l, #s>) -> (optional StateTransition<#l, #s>)
+}
+
+define RunThen {
+  refines StateTransition<#l, #s>
+
+  @value StateTransition<#l, #s> first
+  @value StateTransition<#l, #s> second
+
+  new (first, second) {
+    if (! `present` first) {
+      return second
+    } elif (! `present` second) {
+      return first
+    } else {
+      return RunThen<#l, #s>{ `require` first, `require` second }
+    }
+  }
+
+  runWith (label, state) {
+    scoped {
+      optional StateTransition<#l, #s> next <- label `first.runWith` state
+    } in if (`present` next) {
+      return #self{ `require` next, second }
+    } else {
+      return second
+    }
+  }
+}
+
+concrete RunIfElse<#l, #s|> {
+  @category new<#l, #s>
+  (StatePredicate<#l, #s> test:, optional StateTransition<#l, #s> doIf:, optional StateTransition<#l, #s> doElse:) ->
+  (StateTransition<#l, #s>)
+}
+
+define RunIfElse {
+  refines StateTransition<#l, #s>
+
+  @value StatePredicate<#l, #s> test
+  @value optional StateTransition<#l, #s> doIf
+  @value optional StateTransition<#l, #s> doElse
+
+  new (test, doIf, doElse) {
+    return delegate -> RunIfElse<#l, #s>
+  }
+
+  runWith (label, state) {
+    if (label `test.test` state) {
+      return doIf
+    } else {
+      return doElse
+    }
+  }
+}
+
+define LabeledStateMachine {
+  $ReadOnlyExcept[machine]$
+
+  refines StateMachine
+
+  @value #l label
+  @value #s state
+  @value optional StateTransition<#l, #s> machine
+
+  new (label, state, transition) {
+    return delegate -> LabeledStateMachine<#l, #s>
+  }
+
+  transition () {
+    if (!present(machine)) {
+      return empty
+    } elif (`present` (machine <- label `require(machine).runWith` state)) {
+      return self
+    } else {
+      return empty
+    }
+  }
+}
+
 define StateExecutor {
-  multiplexStates (original,random) {
-    HashedMap<Int,StateMachine> states <- HashedMap<Int,StateMachine>.new()
+  multiplexStates (original, random) {
+    HashedMap<Int, StateMachine> states <- HashedMap<Int, StateMachine>.new()
     CategoricalTree<Int> weights <- CategoricalTree<Int>.new()
 
     scoped {
       Int index <- 0
     } in traverse (original.defaultOrder() -> StateMachine state) {
-      \ states.set(index,state)
-      \ weights.setWeight(index,1)
+      \ index `states.set` state
+      \ index `weights.setWeight` 1
     } update {
       index <- index+1
     }
@@ -35,11 +126,11 @@
       Int index <- weights.locate((random.generate()*weights.getTotal().asFloat()).asInt())
       scoped {
         optional StateMachine newState <- require(states.get(index)).transition()
-      } in if (present(newState)) {
-        \ states.set(index,require(newState))
+      } in if (`present` newState) {
+        \ index `states.set` `require` newState
       } else {
-        \ states.remove(index)
-        \ weights.setWeight(index,0)
+        \ `states.remove` index
+        \ index `weights.setWeight` 0
       }
     }
   }
diff --git a/tests/simulate-refs/transitions.0rp b/tests/simulate-refs/transitions.0rp
--- a/tests/simulate-refs/transitions.0rp
+++ b/tests/simulate-refs/transitions.0rp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -19,49 +19,49 @@
 $ModuleOnly$
 
 concrete LockRef {
-  @type new () -> (ReferenceMachine)
+  @type new () -> (StateTransition<String, ReferenceState>)
 }
 
-concrete IfElseDecrRefIsZero {
-  @type new (optional ReferenceMachine,optional ReferenceMachine) -> (ReferenceMachine)
+concrete DecrRefIsZero {
+  @type new () -> (StatePredicate<String, ReferenceState>)
 }
 
 concrete UnlockRef {
-  @type new () -> (ReferenceMachine)
+  @type new () -> (StateTransition<String, ReferenceState>)
 }
 
 concrete FreeObject {
-  @type new () -> (ReferenceMachine)
+  @type new () -> (StateTransition<String, ReferenceState>)
 }
 
-concrete IfElseDecrWeakIsZero {
-  @type new (optional ReferenceMachine,optional ReferenceMachine) -> (ReferenceMachine)
+concrete DecrWeakIsZero {
+  @type new () -> (StatePredicate<String, ReferenceState>)
 }
 
 concrete FreeRef {
-  @type new () -> (ReferenceMachine)
+  @type new () -> (StateTransition<String, ReferenceState>)
 }
 
-concrete IfElseIncrRefIsOne {
-  @type new (optional ReferenceMachine,optional ReferenceMachine) -> (ReferenceMachine)
+concrete IncrRefIsOne {
+  @type new () -> (StatePredicate<String, ReferenceState>)
 }
 
 concrete DecrRef {
-  @type new () -> (ReferenceMachine)
+  @type new () -> (StateTransition<String, ReferenceState>)
 }
 
 concrete DecrWeak {
-  @type new () -> (ReferenceMachine)
+  @type new () -> (StateTransition<String, ReferenceState>)
 }
 
-concrete IfElsePlusLockModLockZero {
-  @type new (optional ReferenceMachine,optional ReferenceMachine) -> (ReferenceMachine)
+concrete PlusLockModLockZero {
+  @type new () -> (StatePredicate<String, ReferenceState>)
 }
 
 concrete MinusLockPlusOne {
-  @type new () -> (ReferenceMachine)
+  @type new () -> (StateTransition<String, ReferenceState>)
 }
 
 concrete MinusLock {
-  @type new () -> (ReferenceMachine)
+  @type new () -> (StateTransition<String, ReferenceState>)
 }
diff --git a/tests/simulate-refs/transitions.0rx b/tests/simulate-refs/transitions.0rx
--- a/tests/simulate-refs/transitions.0rx
+++ b/tests/simulate-refs/transitions.0rx
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2021 Kevin P. Barry
+Copyright 2021,2023 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.
@@ -17,11 +17,11 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define LockRef {
-  refines ReferenceMachine
+  refines StateTransition<String, ReferenceState>
 
   new () { return #self{ } }
 
-  runWith (name,state) {
+  runWith (name, state) {
     \ state.addOperation<#self>(name)
     if (state.tryLock()) {
       return empty
@@ -31,30 +31,23 @@
   }
 }
 
-define IfElseDecrRefIsZero {
-  refines ReferenceMachine
-
-  @value optional ReferenceMachine doIf
-  @value optional ReferenceMachine doElse
+define DecrRefIsZero {
+  refines StatePredicate<String, ReferenceState>
 
-  new (doIf,doElse) { return #self{ doIf, doElse } }
+  new () { return #self{ } }
 
-  runWith (name,state) {
+  test (name, state) {
     \ state.addOperation<#self>(name)
-    if (state.remStrong() == 0) {
-      return doIf
-    } else {
-      return doElse
-    }
+    return state.remStrong() == 0
   }
 }
 
 define UnlockRef {
-  refines ReferenceMachine
+  refines StateTransition<String, ReferenceState>
 
   new () { return #self{ } }
 
-  runWith (name,state) {
+  runWith (name, state) {
     \ state.addOperation<#self>(name)
     \ state.remLock()
     return empty
@@ -62,71 +55,57 @@
 }
 
 define FreeObject {
-  refines ReferenceMachine
+  refines StateTransition<String, ReferenceState>
 
   new () { return #self{ } }
 
-  runWith (name,state) {
+  runWith (name, state) {
     \ state.addOperation<#self>(name)
     \ state.kill()
     return empty
   }
 }
 
-define IfElseDecrWeakIsZero {
-  refines ReferenceMachine
-
-  @value optional ReferenceMachine doIf
-  @value optional ReferenceMachine doElse
+define DecrWeakIsZero {
+  refines StatePredicate<String, ReferenceState>
 
-  new (doIf,doElse) { return #self{ doIf, doElse } }
+  new () { return #self{ } }
 
-  runWith (name,state) {
+  test (name, state) {
     \ state.addOperation<#self>(name)
-    if (state.remWeak() == 0) {
-      return doIf
-    } else {
-      return doElse
-    }
+    return state.remWeak() == 0
   }
 }
 
 define FreeRef {
-  refines ReferenceMachine
+  refines StateTransition<String, ReferenceState>
 
   new () { return #self{ } }
 
-  runWith (name,state) {
+  runWith (name, state) {
     \ state.addOperation<#self>(name)
     \ state.freeData()
     return empty
   }
 }
 
-define IfElseIncrRefIsOne {
-  refines ReferenceMachine
-
-  @value optional ReferenceMachine doIf
-  @value optional ReferenceMachine doElse
+define IncrRefIsOne {
+  refines StatePredicate<String, ReferenceState>
 
-  new (doIf,doElse) { return #self{ doIf, doElse } }
+  new () { return #self{ } }
 
-  runWith (name,state) {
+  test (name, state) {
     \ state.addOperation<#self>(name)
-    if (state.addStrong() == 1) {
-      return doIf
-    } else {
-      return doElse
-    }
+    return state.addStrong() == 1
   }
 }
 
 define DecrRef {
-  refines ReferenceMachine
+  refines StateTransition<String, ReferenceState>
 
   new () { return #self{ } }
 
-  runWith (name,state) {
+  runWith (name, state) {
     \ state.addOperation<#self>(name)
     \ state.remStrong()
     return empty
@@ -134,41 +113,34 @@
 }
 
 define DecrWeak {
-  refines ReferenceMachine
+  refines StateTransition<String, ReferenceState>
 
   new () { return #self{ } }
 
-  runWith (name,state) {
+  runWith (name, state) {
     \ state.addOperation<#self>(name)
     \ state.remWeak()
     return empty
   }
 }
 
-define IfElsePlusLockModLockZero {
-  refines ReferenceMachine
-
-  @value optional ReferenceMachine doIf
-  @value optional ReferenceMachine doElse
+define PlusLockModLockZero {
+  refines StatePredicate<String, ReferenceState>
 
-  new (doIf,doElse) { return #self{ doIf, doElse } }
+  new () { return #self{ } }
 
-  runWith (name,state) {
+  test (name, state) {
     \ state.addOperation<#self>(name)
-    if (state.plusStrong($ExprLookup[LOCK_VAL]$) % $ExprLookup[LOCK_VAL]$ == 0) {
-      return doIf
-    } else {
-      return doElse
-    }
+    return state.plusStrong($ExprLookup[LOCK_VAL]$) % $ExprLookup[LOCK_VAL]$ == 0
   }
 }
 
 define MinusLockPlusOne {
-  refines ReferenceMachine
+  refines StateTransition<String, ReferenceState>
 
   new () { return #self{ } }
 
-  runWith (name,state) {
+  runWith (name, state) {
     \ state.addOperation<#self>(name)
     \ state.minusStrong($ExprLookup[LOCK_VAL]$-1)
     return empty
@@ -176,11 +148,11 @@
 }
 
 define MinusLock {
-  refines ReferenceMachine
+  refines StateTransition<String, ReferenceState>
 
   new () { return #self{ } }
 
-  runWith (name,state) {
+  runWith (name, state) {
     \ state.addOperation<#self>(name)
     \ state.minusStrong($ExprLookup[LOCK_VAL]$)
     return empty
diff --git a/tests/swap.0rt b/tests/swap.0rt
new file mode 100644
--- /dev/null
+++ b/tests/swap.0rt
@@ -0,0 +1,367 @@
+/* -----------------------------------------------------------------------------
+Copyright 2022-2023 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 "successful swaps" {
+  success
+}
+
+concrete Test {
+  @type new () -> (#self)
+  @value testUnboxedCategoryValue () -> ()
+  @value testBoxedCategoryValue () -> ()
+  @value testWeakCategoryValue () -> ()
+  @value testCategorySame () -> ()
+  @value testUnboxedValueSame () -> ()
+  @value testBoxedValueSame () -> ()
+  @value testUnboxedNamedReturn () -> ()
+  @value testBoxedNamedReturn () -> ()
+}
+
+define Test {
+  @category Int         unboxedCategory <- 123
+  @category String      boxedCategory   <- "abc"
+  @category weak String weakCategory    <- boxedCategory
+
+  @value Int         unboxedValue
+  @value String      boxedValue
+  @value weak String weakValue
+
+  new () {
+    String value <- "def"
+    return #self{ 456, value, value }
+  }
+
+  testUnboxedCategoryValue () {
+    unboxedCategory <-> unboxedValue
+    \ Testing.checkEquals(unboxedCategory, 456)
+    \ Testing.checkEquals(unboxedValue, 123)
+    unboxedValue <-> unboxedCategory
+    \ Testing.checkEquals(unboxedCategory, 123)
+    \ Testing.checkEquals(unboxedValue, 456)
+  }
+
+  testBoxedCategoryValue () {
+    boxedCategory <-> boxedValue
+    \ Testing.checkEquals(boxedCategory, "def")
+    \ Testing.checkEquals(boxedValue, "abc")
+    boxedValue <-> boxedCategory
+    \ Testing.checkEquals(boxedCategory, "abc")
+    \ Testing.checkEquals(boxedValue, "def")
+  }
+
+  testWeakCategoryValue () {
+    weakCategory <-> weakValue
+    \ Testing.checkEquals(`strong` weakCategory, "def")
+    \ Testing.checkEquals(`strong` weakValue, "abc")
+    weakValue <-> weakCategory
+    \ Testing.checkEquals(`strong` weakCategory, "abc")
+    \ Testing.checkEquals(`strong` weakValue, "def")
+  }
+
+  testCategorySame () {
+    boxedCategory <-> boxedCategory
+    \ Testing.checkEquals(boxedCategory, "abc")
+  }
+
+  testUnboxedValueSame () {
+    unboxedValue <-> unboxedValue
+    \ Testing.checkEquals(unboxedValue, 456)
+  }
+
+  testBoxedValueSame () {
+    boxedValue <-> boxedValue
+    \ Testing.checkEquals(boxedValue, "def")
+  }
+
+  testUnboxedNamedReturn () {
+    \ Testing.checkEquals(callWithNamedUnboxed(), 456)
+  }
+
+  testBoxedNamedReturn () {
+    \ Testing.checkEquals(callWithNamedBoxed(), "def")
+  }
+
+  @value callWithNamedUnboxed () -> (Int)
+  callWithNamedUnboxed () (value1) {
+    value1     <- 123
+    Int value2 <- 456
+    value1 <-> value2
+    \ Testing.checkEquals(value1, 456)
+    \ Testing.checkEquals(value2, 123)
+  }
+
+  @value callWithNamedBoxed () -> (String)
+  callWithNamedBoxed () (value1) {
+    value1        <- "abc"
+    String value2 <- "def"
+    value1 <-> value2
+    \ Testing.checkEquals(value1, "def")
+    \ Testing.checkEquals(value2, "abc")
+  }
+}
+
+unittest testUnboxedCategoryValue {
+  \ Test.new().testUnboxedCategoryValue()
+}
+
+unittest testBoxedCategoryValue {
+  \ Test.new().testBoxedCategoryValue()
+}
+
+unittest testWeakCategoryValue {
+  \ Test.new().testWeakCategoryValue()
+}
+
+unittest testCategorySame {
+  \ Test.new().testCategorySame()
+}
+
+unittest testUnboxedValueSame {
+  \ Test.new().testUnboxedValueSame()
+}
+
+unittest testBoxedValueSame {
+  \ Test.new().testBoxedValueSame()
+}
+
+unittest testUnboxedNamedReturn {
+  \ Test.new().testUnboxedNamedReturn()
+}
+
+unittest testBoxedNamedReturn {
+  \ Test.new().testBoxedNamedReturn()
+}
+
+unittest testUnboxedLocal {
+  Int value1 <- 123
+  Int value2 <- 456
+  value1 <-> value2
+  \ Testing.checkEquals(value1, 456)
+  \ Testing.checkEquals(value2, 123)
+}
+
+unittest testBoxedLocal {
+  String value1 <- "abc"
+  String value2 <- "def"
+  value1 <-> value2
+  \ Testing.checkEquals(value1, "def")
+  \ Testing.checkEquals(value2, "abc")
+}
+
+unittest testOptionalLocal {
+  optional String value1 <- "abc"
+  optional String value2 <- "def"
+  value1 <-> value2
+  \ Testing.checkEquals(value1, "def")
+  \ Testing.checkEquals(value2, "abc")
+}
+
+unittest testWeakLocal {
+  optional String source1 <- "abc"
+  optional String source2 <- "def"
+  weak String value1 <- source1
+  weak String value2 <- source2
+  $Hidden[source1, source2]$
+  value1 <-> value2
+  \ Testing.checkEquals(`strong` value1, "def")
+  \ Testing.checkEquals(`strong` value2, "abc")
+}
+
+unittest testUnboxedTopLevelScoped {
+  Int value1 <- 123
+  scoped {
+    Int value2 <- 456
+  } cleanup {
+    \ Testing.checkEquals(value1, 456)
+  } in value1 <-> value2
+  \ Testing.checkEquals(value1, 456)
+}
+
+unittest testBoxedTopLevelScoped {
+  String value1 <- "abc"
+  weak String reference <- value1
+  scoped {
+    String value2 <- "def"
+  } cleanup {
+    \ Testing.checkEquals(value1, "def")
+    \ Testing.checkEquals(value2, "abc")
+    \ Testing.checkEquals(`strong` reference, "abc")
+  } in value1 <-> value2
+  \ Testing.checkEquals(value1, "def")
+  // value2 is out of scope now.
+  \ Testing.checkEmpty(`strong` reference)
+}
+
+
+testcase "swap works with parameters" {
+  success
+}
+
+concrete Test<#x> {
+  #x requires Formatted
+  #x defines Equals<#x>
+
+  @type new (#x) -> (#self)
+  @value testSwap (#x) -> ()
+}
+
+define Test {
+  @value #x value
+
+  new (value) { return #self{ value } }
+
+  testSwap (value2) {
+    // value2 is read-only because it's an argument.
+    #x temp <- value2
+    value <-> temp
+  \ Testing.checkEquals(value, value2)
+  }
+}
+
+unittest testUnboxed {
+  \ Test<Int>.new(123).testSwap(456)
+}
+
+unittest testBxed {
+  \ Test<String>.new("abc").testSwap("def")
+}
+
+
+testcase "swap not allowed with incompatible types" {
+  error
+  require "Int"
+  require "String"
+}
+
+unittest test {
+  Int    value1 <- 123
+  String value2 <- "abc"
+  value1 <-> value2
+}
+
+
+testcase "swap not allowed with invalid left assignment" {
+  error
+  require "modifier"
+}
+
+unittest test {
+  Int          value1 <- 123
+  optional Int value2 <- empty
+  value1 <-> value2
+}
+
+
+testcase "swap not allowed with invalid right assignment" {
+  error
+  require "modifier"
+}
+
+unittest test {
+  optional Int value1 <- empty
+  Int          value2 <- 456
+  value1 <-> value2
+}
+
+
+testcase "swap not allowed with read-only left" {
+  error
+  require "value1"
+  require "read-only"
+  exclude "value2"
+}
+
+unittest test {
+  Int value1 <- 1
+  Int value2 <- 2
+  $ReadOnly[value1]$
+  value1 <-> value2
+}
+
+
+testcase "swap not allowed with read-only right" {
+  error
+  require "value2"
+  require "read-only"
+  exclude "value1"
+}
+
+unittest test {
+  Int value1 <- 1
+  Int value2 <- 2
+  $ReadOnly[value2]$
+  value1 <-> value2
+}
+
+
+testcase "swap not allowed with hidden left" {
+  error
+  require "value1"
+  require "hidden"
+  exclude "value2"
+}
+
+unittest test {
+  Int value1 <- 1
+  Int value2 <- 2
+  $Hidden[value1]$
+  value1 <-> value2
+}
+
+
+testcase "swap not allowed with hidden right" {
+  error
+  require "value2"
+  require "hidden"
+  exclude "value1"
+}
+
+unittest test {
+  Int value1 <- 1
+  Int value2 <- 2
+  $Hidden[value2]$
+  value1 <-> value2
+}
+
+
+testcase "swap not allowed with deferred left" {
+  error
+  require "value1"
+  require "initialized"
+  exclude "value2"
+}
+
+unittest test {
+  Int value1 <- defer
+  Int value2 <- 2
+  value1 <-> value2
+}
+
+
+testcase "swap not allowed with deferred right" {
+  error
+  require "value2"
+  require "initialized"
+  exclude "value1"
+}
+
+unittest test {
+  Int value1 <- 1
+  Int value2 <- defer
+  value1 <-> value2
+}
diff --git a/tests/templates/.zeolite-module b/tests/templates/.zeolite-module
--- a/tests/templates/.zeolite-module
+++ b/tests/templates/.zeolite-module
@@ -10,4 +10,4 @@
     categories: [Templated2]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/templates/tests.0rt b/tests/templates/tests.0rt
--- a/tests/templates/tests.0rt
+++ b/tests/templates/tests.0rt
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "template compiles to binary" {
-  crash
+  failure
   require "Templated:create is not implemented"
 }
 
@@ -27,7 +27,7 @@
 
 
 testcase "private template generated" {
-  crash
+  failure
   require "Templated2\.create is not implemented"
 }
 
diff --git a/tests/test-helpers.0rp b/tests/test-helpers.0rp
new file mode 100644
--- /dev/null
+++ b/tests/test-helpers.0rp
@@ -0,0 +1,113 @@
+/* -----------------------------------------------------------------------------
+Copyright 2020-2021,2023 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]
+
+$ModuleOnly$
+$TestsOnly$
+
+// General helpers for use in unit tests.
+concrete Testing {
+  // Check the values for equality. Crashes if they are not equal.
+  //
+  // Args:
+  // - #x: Actual value.
+  // - #x: Expected value.
+  @type checkEquals<#x>
+    #x defines Equals<#x>
+  (optional #x, optional #x) -> ()
+
+  // Check the values for inequality. Crashes if they are equal.
+  //
+  // Args:
+  // - #x: Actual value.
+  // - #x: Expected value.
+  @type checkNotEquals<#x>
+    #x defines Equals<#x>
+  (optional #x, optional #x) -> ()
+
+  // Check the value for empty. Crashes if present.
+  //
+  // Args:
+  // - #x: Actual value.
+  @type checkEmpty<#x> (optional #x) -> ()
+
+  // Check the value for present. Crashes if empty.
+  //
+  // Args:
+  // - optional any: Actual value.
+  @type checkPresent (optional any) -> ()
+
+  // Check the value for true. Crashes if false.
+  //
+  // Args:
+  // - AsBool: Actual value.
+  @type checkTrue (optional AsBool) -> ()
+
+  // Check the value for false. Crashes if true.
+  //
+  // Args:
+  // - AsBool: Actual value.
+  @type checkFalse (optional AsBool) -> ()
+
+  // Check that the value is within a range. Crashes if it is not in the range.
+  //
+  // Args:
+  // - #x: Actual value.
+  // - #x: Lower bound.
+  // - #x: Upper bound.
+  //
+  // Notes:
+  // - This could be done without Equals, except that some types have
+  //   "undefined" values (e.g., NaN, for Float) that prevent inferring equality
+  //   from less-than comparisons.
+  @type checkBetween<#x>
+    #x defines Equals<#x>
+    #x defines LessThan<#x>
+  (optional #x, #x, #x) -> ()
+
+  // Check that the value is above the limit. Crashes if it is not in the range.
+  //
+  // Args:
+  // - #x: Actual value.
+  // - #x: Lower bound.
+  //
+  // Notes:
+  // - This could be done without Equals, except that some types have
+  //   "undefined" values (e.g., NaN, for Float) that prevent inferring equality
+  //   from less-than comparisons.
+  // - This comparison doesn't allow the value to equal the bound.
+  @type checkGreaterThan<#x>
+    #x defines Equals<#x>
+    #x defines LessThan<#x>
+  (optional #x, #x) -> ()
+
+  // Check that the value is below the limit. Crashes if it is not in the range.
+  //
+  // Args:
+  // - #x: Actual value.
+  // - #x: Upper bound.
+  //
+  // Notes:
+  // - This could be done without Equals, except that some types have
+  //   "undefined" values (e.g., NaN, for Float) that prevent inferring equality
+  //   from less-than comparisons.
+  // - This comparison doesn't allow the value to equal the bound.
+  @type checkLessThan<#x>
+    #x defines Equals<#x>
+    #x defines LessThan<#x>
+  (optional #x, #x) -> ()
+}
diff --git a/tests/test-helpers.0rt b/tests/test-helpers.0rt
new file mode 100644
--- /dev/null
+++ b/tests/test-helpers.0rt
@@ -0,0 +1,300 @@
+/* -----------------------------------------------------------------------------
+Copyright 2020-2021,2023 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 "checkEquals success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkEquals(13, 13)
+  \ Testing.checkEquals<Int>(empty, empty)
+}
+
+
+testcase "checkEquals fail non-empty" {
+  failure
+  require stderr "13"
+  require stderr "15"
+}
+
+unittest test {
+  \ Testing.checkEquals(13, 15)
+}
+
+
+testcase "checkEquals fail empty" {
+  failure
+  require stderr "13"
+  require stderr "empty"
+}
+
+unittest test {
+  \ Testing.checkEquals(13, empty)
+}
+
+
+testcase "checkNotEquals success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkNotEquals(12, 13)
+  \ Testing.checkNotEquals(empty, 13)
+  \ Testing.checkNotEquals(12, empty)
+}
+
+
+testcase "checkNotEquals fail non-empty" {
+  failure
+  require stderr "13.*13"
+}
+
+unittest test {
+  \ Testing.checkNotEquals(13, 13)
+}
+
+
+testcase "checkNotEquals fail empty" {
+  failure
+  require stderr "empty.*empty"
+}
+
+unittest test {
+  \ Testing.checkNotEquals<Int>(empty, empty)
+}
+
+
+testcase "checkEmpty success" {
+  success
+}
+
+unittest formatted {
+  \ Testing.checkEmpty(empty)
+}
+
+unittest notFormatted {
+  optional CharBuffer value <- empty
+  \ Testing.checkEmpty(value)
+}
+
+
+testcase "checkEmpty fail formatted" {
+  failure
+  require stderr "13"
+  require stderr "empty"
+}
+
+unittest test {
+  \ Testing.checkEmpty(13)
+}
+
+
+testcase "checkEmpty fail unformatted" {
+  failure
+  require stderr "unknown"
+  require stderr "empty"
+}
+
+unittest test {
+  CharBuffer value <- CharBuffer.new(10)
+  \ Testing.checkEmpty(value)
+}
+
+
+testcase "checkPresent success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkPresent(13)
+}
+
+
+testcase "checkPresent fail" {
+  failure
+  require stderr "empty"
+}
+
+unittest test {
+  \ Testing.checkPresent(empty)
+}
+
+
+testcase "checkBetween success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkBetween(13, 13, 15)
+}
+
+
+testcase "checkBetween fail non-empty" {
+  failure
+  require stderr "13"
+  require stderr "14"
+  require stderr "15"
+}
+
+unittest test {
+  \ Testing.checkBetween(13, 14, 15)
+}
+
+
+testcase "checkBetween fail empty" {
+  failure
+  require stderr "empty"
+  require stderr "14"
+  require stderr "15"
+}
+
+unittest test {
+  \ Testing.checkBetween(empty, 14, 15)
+}
+
+
+testcase "checkBetween bad range" {
+  failure
+  exclude stderr "14"
+  require stderr "13"
+  require stderr "15"
+}
+
+unittest test {
+  \ Testing.checkBetween(14, 15, 13)
+}
+
+
+testcase "checkGreaterThan success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkGreaterThan(13, 11)
+}
+
+
+testcase "checkGreaterThan fail non-empty" {
+  failure
+  require stderr "13"
+  require stderr "14"
+}
+
+unittest test {
+  \ Testing.checkGreaterThan(13, 14)
+}
+
+
+testcase "checkGreaterThan fail empty" {
+  failure
+  require stderr "empty"
+  require stderr "14"
+}
+
+unittest test {
+  \ Testing.checkGreaterThan(empty, 14)
+}
+
+
+testcase "checkLessThan success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkLessThan(13, 14)
+}
+
+
+testcase "checkLessThan fail non-empty" {
+  failure
+  require stderr "13"
+  require stderr "11"
+}
+
+unittest test {
+  \ Testing.checkLessThan(13, 11)
+}
+
+
+testcase "checkLessThan fail empty" {
+  failure
+  require stderr "empty"
+  require stderr "11"
+}
+
+unittest test {
+  \ Testing.checkLessThan(empty, 11)
+}
+
+
+testcase "checkTrue success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkTrue(1)
+}
+
+
+testcase "checkTrue fail non-empty" {
+  failure
+  require stderr "true"
+}
+
+unittest test {
+  \ Testing.checkTrue(0)
+}
+
+
+testcase "checkTrue fail empty" {
+  failure
+  require stderr "true"
+}
+
+unittest test {
+  \ Testing.checkTrue(empty)
+}
+
+
+testcase "checkFalse success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkFalse(0)
+}
+
+
+testcase "checkFalse fail non-empty" {
+  failure
+  require stderr "false"
+}
+
+unittest test {
+  \ Testing.checkFalse(1)
+}
+
+
+testcase "checkFalse fail empty" {
+  failure
+  require stderr "false"
+}
+
+unittest test {
+  \ Testing.checkFalse(empty)
+}
diff --git a/tests/test-helpers.0rx b/tests/test-helpers.0rx
new file mode 100644
--- /dev/null
+++ b/tests/test-helpers.0rx
@@ -0,0 +1,169 @@
+/* -----------------------------------------------------------------------------
+Copyright 2020-2021,2023 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]
+
+$TestsOnly$
+
+define Testing {
+  checkEquals (x, y) { $NoTrace$
+    Bool failed <- false
+    if (`present` x && `present` y && !#x.equals(`require` x, `require` y)) {
+      failed <- true
+    } elif (`present` x != `present` y) {
+      failed <- true
+    }
+    if (failed) {
+      fail(String.builder()
+          .append(autoFormat(x))
+          .append(" is not equal to ")
+          .append(autoFormat(y))
+          .build())
+    }
+  }
+
+  checkNotEquals (x, y) { $NoTrace$
+    Bool failed <- false
+    if (`present` x && `present` y && #x.equals(`require` x, `require` y)) {
+      failed <- true
+    } elif (! `present` x &&  ! `present` y) {
+      failed <- true
+    }
+    if (failed) {
+      fail(String.builder()
+          .append(autoFormat(x))
+          .append(" is equal to ")
+          .append(autoFormat(y))
+          .build())
+    }
+  }
+
+  checkEmpty (x) { $NoTrace$
+    if (present(x)) {
+      fail(String.builder()
+          .append(autoFormat(x))
+          .append(" is not empty")
+          .build())
+    }
+  }
+
+  checkPresent (x) { $NoTrace$
+    if (!present(x)) {
+      fail(String.builder()
+          .append("expected non-empty value")
+          .build())
+    }
+  }
+
+  checkTrue (x) { $NoTrace$
+    if (! `present` x) {
+      fail("expected true but got empty")
+    }
+    if (!require(x).asBool()) {
+      fail("expected true but got false")
+    }
+  }
+
+  checkFalse (x) { $NoTrace$
+    if (! `present` x) {
+      fail("expected false but got empty")
+    }
+    if (require(x).asBool()) {
+      fail("expected false but got true")
+    }
+  }
+
+  checkBetween (x, l, h) { $NoTrace$
+    if (!lessEquals(l, h)) {
+      fail(String.builder()
+          .append("Upper limit ")
+          .append(autoFormat(l))
+          .append(" is not at or above lower limit ")
+          .append(autoFormat(h))
+          .build())
+    }
+    Bool failed <- false
+    if (! `present` x) {
+      failed <- true
+    } elif (!lessEquals(l, `require` x) || !lessEquals(`require` x, h)) {
+      failed <- true
+    }
+    if (failed) {
+      fail(String.builder()
+          .append(autoFormat(x))
+          .append(" is not between ")
+          .append(autoFormat(l))
+          .append(" and ")
+          .append(autoFormat(h))
+          .build())
+    }
+  }
+
+  checkGreaterThan (x, l) { $NoTrace$
+    Bool failed <- false
+    if (! `present` x) {
+      failed <- true
+    } elif (!lessEquals(l, `require` x)) {
+      failed <- true
+    }
+    if (failed) {
+      fail(String.builder()
+          .append(autoFormat(x))
+          .append(" is not greater than ")
+          .append(autoFormat(l))
+          .build())
+    }
+  }
+
+  checkLessThan (x, h) { $NoTrace$
+    Bool failed <- false
+    if (! `present` x) {
+      failed <- true
+    } elif (!lessEquals(`require` x, h)) {
+      failed <- true
+    }
+    if (failed) {
+      fail(String.builder()
+          .append(autoFormat(x))
+          .append(" is not less than ")
+          .append(autoFormat(h))
+          .build())
+    }
+  }
+
+  @type autoFormat<#x> (optional #x) -> (Formatted)
+  autoFormat (x) {
+    if (! `present` x) {
+      return "empty"
+    }
+    scoped {
+      optional Formatted formatted <- reduce<#x, Formatted>(x)
+    } in if (`present` formatted) {
+      return `require` formatted
+    } else {
+      return "(unknown)"
+    }
+  }
+
+  @type lessEquals<#x>
+    #x defines Equals<#x>
+    #x defines LessThan<#x>
+  (#x, #x) -> (Bool)
+  lessEquals (x, y) { $NoTrace$
+    // Using !#x.lessThan(y, x) wouldn't account for NaNs.
+    return #x.lessThan(x, y) || #x.equals(x, y)
+  }
+}
diff --git a/tests/tests-only/.zeolite-module b/tests/tests-only/.zeolite-module
--- a/tests/tests-only/.zeolite-module
+++ b/tests/tests-only/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../.."
 path: "tests/tests-only"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/tests-only/private.0rx b/tests/tests-only/private.0rx
--- a/tests/tests-only/private.0rx
+++ b/tests/tests-only/private.0rx
@@ -16,6 +16,6 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-define Type1 {}
+define Type1 { }
 
-define Type2 {}
+define Type2 { }
diff --git a/tests/tests-only/public1.0rp b/tests/tests-only/public1.0rp
--- a/tests/tests-only/public1.0rp
+++ b/tests/tests-only/public1.0rp
@@ -18,4 +18,4 @@
 
 $TestsOnly$
 
-concrete Type1 {}
+concrete Type1 { }
diff --git a/tests/tests-only/public2.0rp b/tests/tests-only/public2.0rp
--- a/tests/tests-only/public2.0rp
+++ b/tests/tests-only/public2.0rp
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-concrete Type2 {}
+concrete Type2 { }
diff --git a/tests/tests-only2/private.0rx b/tests/tests-only2/private.0rx
--- a/tests/tests-only2/private.0rx
+++ b/tests/tests-only2/private.0rx
@@ -19,5 +19,5 @@
 $TestsOnly$
 
 define Testing {
-  run () {}
+  run () { }
 }
diff --git a/tests/tests-only3/.zeolite-module b/tests/tests-only3/.zeolite-module
--- a/tests/tests-only3/.zeolite-module
+++ b/tests/tests-only3/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../.."
 path: "tests/tests-only3"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/tests-only3/public.0rp b/tests/tests-only3/public.0rp
--- a/tests/tests-only3/public.0rp
+++ b/tests/tests-only3/public.0rp
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-concrete NotTestsOnly {}
+concrete NotTestsOnly { }
diff --git a/tests/tests-only3/public.0rx b/tests/tests-only3/public.0rx
--- a/tests/tests-only3/public.0rx
+++ b/tests/tests-only3/public.0rx
@@ -18,4 +18,4 @@
 
 $TestsOnly$
 
-define NotTestsOnly {}
+define NotTestsOnly { }
diff --git a/tests/tests-only4/.zeolite-module b/tests/tests-only4/.zeolite-module
--- a/tests/tests-only4/.zeolite-module
+++ b/tests/tests-only4/.zeolite-module
@@ -10,4 +10,4 @@
     categories: [Type3]
   }
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/tests-only4/normal.0rp b/tests/tests-only4/normal.0rp
--- a/tests/tests-only4/normal.0rp
+++ b/tests/tests-only4/normal.0rp
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-concrete Type3 {}
+concrete Type3 { }
diff --git a/tests/tests-only4/testing.0rp b/tests/tests-only4/testing.0rp
--- a/tests/tests-only4/testing.0rp
+++ b/tests/tests-only4/testing.0rp
@@ -18,6 +18,6 @@
 
 $TestsOnly$
 
-concrete Type1 {}
+concrete Type1 { }
 
-@value interface Type2 {}
+@value interface Type2 { }
diff --git a/tests/traces/.zeolite-module b/tests/traces/.zeolite-module
--- a/tests/traces/.zeolite-module
+++ b/tests/traces/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../.."
 path: "tests/traces"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/traces/traces.0rx b/tests/traces/traces.0rx
--- a/tests/traces/traces.0rx
+++ b/tests/traces/traces.0rx
@@ -16,7 +16,7 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type doNotExecute () -> ()
diff --git a/tests/tracing.0rt b/tests/tracing.0rt
--- a/tests/tracing.0rt
+++ b/tests/tracing.0rt
@@ -17,7 +17,7 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 testcase "NoTrace skips tracing" {
-  crash
+  failure
   require "message"
   require "Test\.error"
   exclude "Test\.noTrace"
@@ -43,7 +43,7 @@
 }
 
 testcase "NoTrace works in cleanup" {
-  crash
+  failure
   require "message"
   require "Test\.error"
   exclude "Test\.noTrace"
@@ -66,13 +66,13 @@
   error () {
     cleanup {
       fail("message")
-    } in {}
+    } in { }
   }
 }
 
 
 testcase "TraceCreation captures trace" {
-  crash
+  failure
   require "message"
   require "Type.+creation"
 }
@@ -103,7 +103,7 @@
   require compiler "tracing ignored"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @type run () -> ()
@@ -112,7 +112,7 @@
 
 
 testcase "TraceCreation still works with NoTrace" {
-  crash
+  failure
   require "message"
   require "Type.+creation"
   exclude "Type\.call"
@@ -140,7 +140,7 @@
 
 
 testcase "TraceCreation only uses the latest" {
-  crash
+  failure
   require "message"
   require "Type1.+creation"
   exclude "Type2.+creation"
@@ -185,7 +185,7 @@
 
 
 testcase "correct function name used for non-merged function" {
-  crash
+  failure
   require "message"
   require "Type\.call"
   exclude "Base"
diff --git a/tests/traverse.0rt b/tests/traverse.0rt
--- a/tests/traverse.0rt
+++ b/tests/traverse.0rt
@@ -25,7 +25,7 @@
   traverse (Counter.zeroIndexed(5) -> Int i) {
     total <- total+i
   }
-  \ Testing.checkEquals(total,10)
+  \ Testing.checkEquals(total, 10)
 }
 
 unittest ignoreValue {
@@ -33,7 +33,7 @@
   traverse (Counter.zeroIndexed(5) -> _) {
     total <- total+1
   }
-  \ Testing.checkEquals(total,5)
+  \ Testing.checkEquals(total, 5)
 }
 
 unittest existingVariable {
@@ -42,8 +42,8 @@
   traverse (Counter.zeroIndexed(5) -> i) {
     total <- total+i
   }
-  \ Testing.checkEquals(total,10)
-  \ Testing.checkEquals(i,4)
+  \ Testing.checkEquals(total, 10)
+  \ Testing.checkEquals(i, 4)
 }
 
 unittest breakAtLimit {
@@ -54,7 +54,7 @@
     }
     total <- total+i
   }
-  \ Testing.checkEquals(total,10)
+  \ Testing.checkEquals(total, 10)
 }
 
 unittest continueOnOdd {
@@ -65,7 +65,7 @@
     }
     total <- total+i
   }
-  \ Testing.checkEquals(total,20)
+  \ Testing.checkEquals(total, 20)
 }
 
 unittest withUpdate {
@@ -77,7 +77,7 @@
   } update {
     total <- total+i
   }
-  \ Testing.checkEquals(total,45)
+  \ Testing.checkEquals(total, 45)
 }
 
 unittest overwriteVisibleValue {
@@ -86,7 +86,7 @@
     // i shouldn't be overwritten after the last iteration.
     i <- 0
   }
-  \ Testing.checkEquals(i,0)
+  \ Testing.checkEquals(i, 0)
 }
 
 unittest overwriteLocalValue {
@@ -95,7 +95,7 @@
     i <- 0
   } update {
     // i should also be visible here.
-    \ Testing.checkEquals(i,0)
+    \ Testing.checkEquals(i, 0)
   }
 }
 
@@ -138,7 +138,7 @@
 }
 
 unittest test {
-  traverse (0 -> _) {}
+  traverse (0 -> _) { }
 }
 
 
@@ -168,7 +168,7 @@
 }
 
 unittest test {
-  traverse (Empty:new<String>() -> Int i) {}
+  traverse (Empty:new<String>() -> Int i) { }
 }
 
 
@@ -191,7 +191,7 @@
     traverse (Counter.new() -> Int i) {
       total <- total+i
     }
-    \ Testing.checkEquals(total,10)
+    \ Testing.checkEquals(total, 10)
   }
 }
 
@@ -242,7 +242,7 @@
     traverse (Counter.new() -> Int i) {
       total <- total+i
     }
-    \ Testing.checkEquals(total,10)
+    \ Testing.checkEquals(total, 10)
   }
 }
 
@@ -280,7 +280,7 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(Test.run("message"),"message")
+  \ Testing.checkEquals(Test.run("message"), "message")
 }
 
 concrete Test {
@@ -291,24 +291,24 @@
   run (x) (i) {
     i <- x
     Int total <- 0
-    traverse (Repeat:times(x,10) -> i) {
+    traverse (Repeat:times(x, 10) -> i) {
       total <- total+1
     }
-    \ Testing.checkEquals(total,10)
+    \ Testing.checkEquals(total, 10)
   }
 }
 
 concrete Repeat<|#x> {
   refines Order<#x>
 
-  @category times<#y> (#y,Int) -> (optional Repeat<#y>)
+  @category times<#y> (#y, Int) -> (optional Repeat<#y>)
 }
 
 define Repeat {
   @value #x value
   @value Int count
 
-  times (value,count) {
+  times (value, count) {
     if (count < 1) {
       return empty
     } else {
diff --git a/tests/typename.0rt b/tests/typename.0rt
--- a/tests/typename.0rt
+++ b/tests/typename.0rt
@@ -21,94 +21,94 @@
 }
 
 unittest string {
-  \ Testing.checkEquals(typename<String>().formatted(),"String")
+  \ Testing.checkEquals(typename<String>().formatted(), "String")
 }
 
 unittest int {
-  \ Testing.checkEquals(typename<Int>().formatted(),"Int")
+  \ Testing.checkEquals(typename<Int>().formatted(), "Int")
 }
 
 unittest char {
-  \ Testing.checkEquals(typename<Char>().formatted(),"Char")
+  \ Testing.checkEquals(typename<Char>().formatted(), "Char")
 }
 
 unittest charBuffer {
-  \ Testing.checkEquals(typename<CharBuffer>().formatted(),"CharBuffer")
+  \ Testing.checkEquals(typename<CharBuffer>().formatted(), "CharBuffer")
 }
 
 unittest float {
-  \ Testing.checkEquals(typename<Float>().formatted(),"Float")
+  \ Testing.checkEquals(typename<Float>().formatted(), "Float")
 }
 
 unittest bool {
-  \ Testing.checkEquals(typename<Bool>().formatted(),"Bool")
+  \ Testing.checkEquals(typename<Bool>().formatted(), "Bool")
 }
 
 unittest anyType {
-  \ Testing.checkEquals(typename<any>().formatted(),"any")
+  \ Testing.checkEquals(typename<any>().formatted(), "any")
 }
 
 unittest allType {
-  \ Testing.checkEquals(typename<all>().formatted(),"all")
+  \ Testing.checkEquals(typename<all>().formatted(), "all")
 }
 
 unittest intersectDedup {
-  \ Testing.checkEquals(typename<[AsBool&Char&Formatted&Int]>().formatted(),
-                           typename<[Char&Int]>().formatted())
+  \ Testing.checkEquals(typename<[AsBool & Char & Formatted & Int]>().formatted(),
+                           typename<[Char & Int]>().formatted())
 }
 
 unittest unionDedup {
-  \ Testing.checkEquals(typename<[AsBool|Char|Formatted|Int]>().formatted(),
-                           typename<[AsBool|Formatted]>().formatted())
+  \ Testing.checkEquals(typename<[AsBool | Char | Formatted | Int]>().formatted(),
+                           typename<[AsBool | Formatted]>().formatted())
 }
 
 unittest param {
-  \ Testing.checkEquals(Wrapped:getTypename<String,Value<Int>>().formatted(),
+  \ Testing.checkEquals(Wrapped:getTypename<String, Value<Int>>().formatted(),
                            "Type<String,Value<Int>>")
 }
 
 unittest intersectParam {
-  String text <- Wrapped:getTypenameIntersect<Formatted,Int>().formatted()
+  String text <- Wrapped:getTypenameIntersect<Formatted, Int>().formatted()
   if (text != "[Formatted&Int]") {
     fail(text)
   }
 }
 
 unittest unionParam {
-  String text <- Wrapped:getTypenameUnion<Formatted,Int>().formatted()
+  String text <- Wrapped:getTypenameUnion<Formatted, Int>().formatted()
   if (text != "[Formatted|Int]") {
     fail(text)
   }
 }
 
 unittest intersectDedupParam {
-  \ Testing.checkEquals(Wrapped:getTypenameIntersect<Int,Int>().formatted(),"Int")
+  \ Testing.checkEquals(Wrapped:getTypenameIntersect<Int, Int>().formatted(), "Int")
 }
 
 unittest unionDedupParam {
-  \ Testing.checkEquals(Wrapped:getTypenameUnion<Int,Int>().formatted(),"Int")
+  \ Testing.checkEquals(Wrapped:getTypenameUnion<Int, Int>().formatted(), "Int")
 }
 
-@value interface Value<#x> {}
+@value interface Value<#x> { }
 
-@value interface Type<#x,#y> {}
+@value interface Type<#x, #y> { }
 
 concrete Wrapped {
-  @category getTypename<#x,#y> () -> (Formatted)
-  @category getTypenameIntersect<#x,#y> () -> (Formatted)
-  @category getTypenameUnion<#x,#y> () -> (Formatted)
+  @category getTypename<#x, #y> () -> (Formatted)
+  @category getTypenameIntersect<#x, #y> () -> (Formatted)
+  @category getTypenameUnion<#x, #y> () -> (Formatted)
 }
 
 define Wrapped {
   getTypename () {
-    return typename<Type<#x,#y>>()
+    return typename<Type<#x, #y>>()
   }
 
   getTypenameIntersect () {
-    return typename<[#x&#y]>()
+    return typename<[#x & #y]>()
   }
 
   getTypenameUnion () {
-    return typename<[#x|#y]>()
+    return typename<[#x | #y]>()
   }
 }
diff --git a/tests/unary-functions.0rt b/tests/unary-functions.0rt
--- a/tests/unary-functions.0rt
+++ b/tests/unary-functions.0rt
@@ -126,12 +126,12 @@
 }
 
 concrete Test {
-  @category add (Int,Int) -> (Int)
+  @category add (Int, Int) -> (Int)
   @category neg (Int) -> (Int)
 }
 
 define Test {
-  add (x,y) {
+  add (x, y) {
     return x + y
   }
 
@@ -147,7 +147,7 @@
 
 unittest requireUnary {
   optional Int value <- 1
-  \ Testing.checkEquals(`require` value,1)
+  \ Testing.checkEquals(`require` value, 1)
 }
 
 unittest presentUnary {
@@ -165,6 +165,6 @@
 }
 
 unittest reduceUnary {
-  optional Formatted value <- `reduce<Int,Formatted>` 123
-  \ Testing.checkEquals(require(value).formatted(),"123")
+  optional Formatted value <- `reduce<Int, Formatted>` 123
+  \ Testing.checkEquals(require(value).formatted(), "123")
 }
diff --git a/tests/unreachable.0rt b/tests/unreachable.0rt
--- a/tests/unreachable.0rt
+++ b/tests/unreachable.0rt
@@ -43,7 +43,7 @@
   require compiler "unreachable"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category failedReturn () -> (Int)
@@ -59,7 +59,7 @@
   require compiler "unreachable"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category failedReturn () -> (Int)
@@ -79,7 +79,7 @@
   require compiler "unreachable"
 }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @category failedReturn () -> (Int)
@@ -128,7 +128,7 @@
     fail("message")
   } cleanup {
     \ empty
-  } in {}
+  } in { }
 }
 
 
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/tests/warn-public/.zeolite-module b/tests/warn-public/.zeolite-module
--- a/tests/warn-public/.zeolite-module
+++ b/tests/warn-public/.zeolite-module
@@ -4,4 +4,4 @@
   "internal"
   "internal2"
 ]
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/warn-public/internal/.zeolite-module b/tests/warn-public/internal/.zeolite-module
--- a/tests/warn-public/internal/.zeolite-module
+++ b/tests/warn-public/internal/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../../.."
 path: "tests/warn-public/internal"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/warn-public/internal/public.0rp b/tests/warn-public/internal/public.0rp
--- a/tests/warn-public/internal/public.0rp
+++ b/tests/warn-public/internal/public.0rp
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-@value interface Type1 {}
+@value interface Type1 { }
diff --git a/tests/warn-public/internal2/.zeolite-module b/tests/warn-public/internal2/.zeolite-module
--- a/tests/warn-public/internal2/.zeolite-module
+++ b/tests/warn-public/internal2/.zeolite-module
@@ -1,3 +1,3 @@
 root: "../../.."
 path: "tests/warn-public/internal2"
-mode: incremental {}
+mode: incremental { }
diff --git a/tests/warn-public/internal2/public.0rp b/tests/warn-public/internal2/public.0rp
--- a/tests/warn-public/internal2/public.0rp
+++ b/tests/warn-public/internal2/public.0rp
@@ -16,4 +16,4 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-@value interface Type2 {}
+@value interface Type2 { }
diff --git a/tests/while.0rt b/tests/while.0rt
--- a/tests/while.0rt
+++ b/tests/while.0rt
@@ -21,9 +21,9 @@
   require "value.+initialized"
 }
 
-@value interface Value {}
+@value interface Value { }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (optional Value)
@@ -40,9 +40,9 @@
   require "return"
 }
 
-@value interface Value {}
+@value interface Value { }
 
-concrete Test {}
+concrete Test { }
 
 define Test {
   @value process () -> (optional Value)
@@ -295,7 +295,7 @@
 
 
 testcase "crash in while" {
-  crash
+  failure
   require "empty"
 }
 
@@ -336,7 +336,7 @@
     while (true) {
       cleanup {
         fail(message)
-      } in {}
+      } in { }
     }
     return "return"
   }
@@ -353,7 +353,7 @@
   while(true) {
     cleanup {
       break
-    } in {}
+    } in { }
   }
 }
 
@@ -368,7 +368,7 @@
   while(true) {
     cleanup {
       continue
-    } in {}
+    } in { }
   }
 }
 
@@ -432,7 +432,7 @@
 
 
 testcase "return set in predicate" {
-  crash
+  failure
   require "message"
 }
 
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.23.0.0
+version:             0.24.0.0
 synopsis:            Zeolite is a statically-typed, general-purpose programming language.
 
 description:
@@ -59,7 +59,7 @@
 license-file:        LICENSE
 author:              Kevin P. Barry
 maintainer:          Kevin P. Barry <ta0kira@gmail.com>
-copyright:           (c) Kevin P. Barry 2019-2022
+copyright:           (c) Kevin P. Barry 2019-2023
 category:            Compiler
 build-type:          Simple
 
@@ -102,8 +102,8 @@
                      lib/container/*.0rp,
                      lib/container/src/*.0rp,
                      lib/container/src/*.0rx,
-                     lib/container/test/*.0rt,
                      lib/container/src/*.cpp,
+                     lib/container/test/*.0rt,
                      lib/file/README.md,
                      lib/file/.zeolite-module,
                      lib/file/*.0rp,
@@ -120,7 +120,11 @@
                      lib/testing/README.md,
                      lib/testing/.zeolite-module,
                      lib/testing/*.0rp,
+                     lib/testing/src/*.0rp,
                      lib/testing/src/*.0rx,
+                     lib/testing/src/*.cpp,
+                     lib/testing/test/*.0rp,
+                     lib/testing/test/*.0rx,
                      lib/testing/test/*.0rt,
                      lib/thread/README.md,
                      lib/thread/.zeolite-module,
@@ -147,6 +151,9 @@
                      tests/check-defs/.zeolite-module,
                      tests/check-defs/*.0rp,
                      tests/check-defs/*.0rx,
+                     tests/custom-testcase/README.md,
+                     tests/custom-testcase/.zeolite-module,
+                     tests/custom-testcase/*.0rt,
                      tests/fast-static/README.md,
                      tests/fast-static/*.0rx,
                      tests/freshness/.zeolite-module,
@@ -320,20 +327,20 @@
                        TypeSynonymInstances,
                        Unsafe
 
-  build-depends:       base >= 4.9 && < 4.15,
+  build-depends:       base >= 4.9 && < 4.20,
                        containers >= 0.3 && < 0.7,
                        directory >= 1.2.3 && < 1.4,
                        filepath >= 1.0 && < 1.5,
-                       hashable >= 1.0 && < 1.4,
-                       megaparsec >= 7.0 && < 9.1,
+                       hashable >= 1.0 && < 1.5,
+                       megaparsec >= 7.0 && < 9.7,
                        microlens >= 0.4.9 && < 0.5,
                        microlens-th >= 0.1.0.0 && < 0.5,
-                       mtl >= 1.0 && < 2.3,
+                       mtl >= 1.0 && < 2.4,
                        parser-combinators >= 0.2 && < 2.0,
                        regex-tdfa >= 1.0 && < 1.4,
-                       time >= 1.0 && < 1.11,
-                       transformers >= 0.1 && < 0.6,
-                       unix >= 2.6 && <= 2.8
+                       time >= 1.0 && < 1.13,
+                       transformers >= 0.1 && < 0.7,
+                       unix >= 2.6 && <= 2.9
 
   hs-source-dirs:      src
 
