Index: libcfa/src/Makefile.am
===================================================================
--- libcfa/src/Makefile.am	(revision 180f249d8c08c772b9b9314d1b36dd26f03cd3cb)
+++ libcfa/src/Makefile.am	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
@@ -87,4 +87,6 @@
 	containers/pair.hfa \
 	containers/result.hfa \
+	containers/string.hfa \
+	containers/string_res.hfa \
 	containers/vector.hfa \
 	device/cpu.hfa
Index: libcfa/src/containers/string.cfa
===================================================================
--- libcfa/src/containers/string.cfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
+++ libcfa/src/containers/string.cfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
@@ -0,0 +1,317 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// string -- variable-length, mutable run of text, with value semantics
+//
+// Author           : Michael L. Brooks
+// Created On       : Fri Sep 03 11:00:00 2021
+// Last Modified By : Michael L. Brooks
+// Last Modified On : Fri Sep 03 11:00:00 2021
+// Update Count     : 1
+//
+
+#include "string.hfa"
+#include "string_res.hfa"
+#include <stdlib.hfa>
+
+
+/*
+Implementation Principle: typical operation translates to the equivalent
+operation on `inner`.  Exceptions are implementing new RAII pattern for value
+semantics and some const-hell handling.
+*/
+
+////////////////////////////////////////////////////////
+// string RAII
+
+
+void ?{}( string & this ) {
+    (this.inner) { malloc() };
+    ?{}( *this.inner );
+}
+
+// private (not in header)
+static void ?{}( string & this, string_res & src, size_t start, size_t end ) {
+    (this.inner) { malloc() };
+    ?{}( *this.inner, src, SHARE_EDITS, start, end );
+}
+
+void ?{}( string & this, const string & other ) {
+    (this.inner) { malloc() };
+    ?{}( *this.inner, *other.inner, COPY_VALUE );
+}
+
+void ?{}( string & this, string & other ) {
+    ?{}( this, (const string &) other );
+}
+
+void ?{}( string & this, const char * val ) {
+    (this.inner) { malloc() };
+    ?{}( *this.inner, val );
+}
+
+void ?{}( string & this, const char* buffer, size_t bsize) {
+    (this.inner) { malloc() };
+    ?{}( *this.inner, buffer, bsize );
+}
+
+void ^?{}( string & this ) {
+    ^(*this.inner){};
+    free( this.inner );
+    this.inner = 0p;
+}
+
+////////////////////////////////////////////////////////
+// Alternate construction: request shared edits
+
+string_WithSharedEdits ?`shareEdits( string & this ) {
+    string_WithSharedEdits ret = { &this };
+    return ret;
+}
+
+void ?{}( string & this, string_WithSharedEdits src ) {
+    ?{}( this, *src.s->inner, 0, src.s->inner->Handle.lnth);
+}
+
+////////////////////////////////////////////////////////
+// Assignment
+
+void ?=?( string & this, const char * val ) {
+    (*this.inner) = val;
+}
+
+void ?=?(string & this, const string & other) {
+    (*this.inner) = (*other.inner);
+}
+
+void ?=?( string & this, char val ) {
+    (*this.inner) = val;
+}
+
+string ?=?(string & this, string other) {
+    (*this.inner) = (*other.inner);
+    return this;
+}
+
+
+////////////////////////////////////////////////////////
+// Output
+
+ofstream & ?|?( ofstream & fs, const string & this ) {
+    return fs | (*this.inner);
+}
+
+void ?|?( ofstream & fs, const string & this ) {
+    fs | (*this.inner);
+}
+
+////////////////////////////////////////////////////////
+// Slicing
+
+string ?()( string & this, size_t start, size_t end ) {
+    string ret = { *this.inner, start, end };
+    return ret`shareEdits;
+}
+
+////////////////////////////////////////////////////////
+// Comparison
+
+bool ?==?(const string &s, const string &other) {
+    return *s.inner == *other.inner;
+}
+
+bool ?!=?(const string &s, const string &other) {
+    return *s.inner != *other.inner;
+}
+
+bool ?==?(const string &s, const char* other) {
+    return *s.inner == other;
+}
+
+bool ?!=?(const string &s, const char* other) {
+    return *s.inner != other;
+}
+
+////////////////////////////////////////////////////////
+// Getter
+
+size_t size(const string &s) {
+    return size( * s.inner );
+}
+
+////////////////////////////////////////////////////////
+// Concatenation
+
+void ?+=?(string &s, char other) {
+    (*s.inner) += other;
+}
+
+void ?+=?(string &s, const string &s2) {
+    (*s.inner) += (*s2.inner);
+}
+
+void ?+=?(string &s, const char* other) {
+    (*s.inner) += other;
+}
+
+string ?+?(const string &s, char other) {
+    string ret = s;
+    ret += other;
+    return ret;
+}
+
+string ?+?(const string &s, const string &s2) {
+    string ret = s;
+    ret += s2;
+    return ret;
+}
+
+string ?+?(const char* s1, const char* s2) {
+    string ret = s1;
+    ret += s2;
+    return ret;
+}
+
+string ?+?(const string &s, const char* other) {
+    string ret = s;
+    ret += other;
+    return ret;
+}
+
+////////////////////////////////////////////////////////
+// Repetition
+
+string ?*?(const string &s, size_t factor) {
+    string ret = "";
+    for (factor) ret += s;
+    return ret;
+}
+
+string ?*?(char c, size_t size) {
+    string ret = "";
+    for ((size_t)size) ret += c;
+    return ret;
+}
+
+string ?*?(const char *s, size_t factor) {
+    string ss = s;
+    return ss * factor;
+}
+
+////////////////////////////////////////////////////////
+// Character access
+
+char ?[?](const string &s, size_t index) {
+    return (*s.inner)[index];
+}
+
+string ?[?](string &s, size_t index) {
+    string ret = { *s.inner, index, index + 1 };
+    return ret`shareEdits;
+}
+
+////////////////////////////////////////////////////////
+// Search
+
+bool contains(const string &s, char ch) {
+    return contains( *s.inner, ch );
+}
+
+int find(const string &s, char search) {
+    return find( *s.inner, search );
+}
+
+int find(const string &s, const string &search) {
+    return find( *s.inner, *search.inner );
+}
+
+int find(const string &s, const char* search) {
+    return find( *s.inner, search);
+}
+
+int find(const string &s, const char* search, size_t searchsize) {
+    return find( *s.inner, search, searchsize);
+}
+
+bool includes(const string &s, const string &search) {
+    return includes( *s.inner, *search.inner );
+}
+
+bool includes(const string &s, const char* search) {
+    return includes( *s.inner, search );
+}
+
+bool includes(const string &s, const char* search, size_t searchsize) {
+    return includes( *s.inner, search, searchsize );
+}
+
+bool startsWith(const string &s, const string &prefix) {
+    return startsWith( *s.inner, *prefix.inner );
+}
+
+bool startsWith(const string &s, const char* prefix) {
+    return startsWith( *s.inner, prefix );
+}
+
+bool startsWith(const string &s, const char* prefix, size_t prefixsize) {
+    return startsWith( *s.inner, prefix, prefixsize );
+}
+
+bool endsWith(const string &s, const string &suffix) {
+    return endsWith( *s.inner, *suffix.inner );
+}
+
+bool endsWith(const string &s, const char* suffix) {
+    return endsWith( *s.inner, suffix );
+}
+
+bool endsWith(const string &s, const char* suffix, size_t suffixsize) {
+    return endsWith( *s.inner, suffix, suffixsize );
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+// charclass, include, exclude
+
+void ?{}( charclass & this, const string & chars) {
+    (this.inner) { malloc() };
+    ?{}( *this.inner, *(const string_res *)chars.inner );
+}
+
+void ?{}( charclass & this, const char * chars ) {
+    (this.inner) { malloc() };
+    ?{}( *this.inner, chars );
+}
+
+void ?{}( charclass & this, const char * chars, size_t charssize ) {
+    (this.inner) { malloc() };
+    ?{}( *this.inner, chars, charssize );
+}
+
+void ^?{}( charclass & this ) {
+    ^(*this.inner){};
+    free( this.inner );
+    this.inner = 0p;
+}
+
+
+int exclude(const string &s, const charclass &mask) {
+    return exclude( *s.inner, *mask.inner );
+}
+/*
+StrSlice exclude(string &s, const charclass &mask) {
+}
+*/
+
+int include(const string &s, const charclass &mask) {
+    return include( *s.inner, *mask.inner );
+}
+
+/*
+StrSlice include(string &s, const charclass &mask) {
+}
+*/
+
Index: libcfa/src/containers/string.hfa
===================================================================
--- libcfa/src/containers/string.hfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
+++ libcfa/src/containers/string.hfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
@@ -0,0 +1,138 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// string -- variable-length, mutable run of text, with value semantics
+//
+// Author           : Michael L. Brooks
+// Created On       : Fri Sep 03 11:00:00 2021
+// Last Modified By : Michael L. Brooks
+// Last Modified On : Fri Sep 03 11:00:00 2021
+// Update Count     : 1
+//
+
+#pragma once
+
+#include <fstream.hfa>
+
+
+// in string_res.hfa
+struct string_res;
+struct charclass_res;
+
+struct string {
+    string_res * inner;
+};
+
+// Getters
+size_t size(const string &s);
+
+// RAII, assignment
+void ?{}( string & this ); // empty string
+void ?{}(string &s, const char* initial); // copy from string literal (NULL-terminated)
+void ?{}(string &s, const char* buffer, size_t bsize); // copy specific length from buffer
+
+void ?{}(string &s, const string & s2);
+void ?{}(string &s, string & s2);
+
+void ?=?(string &s, const char* other); // copy assignment from literal
+void ?=?(string &s, const string &other);
+void ?=?(string &s, char other);
+string ?=?(string &s, string other);  // string tolerates memcpys; still saw calls to autogen 
+
+void ^?{}(string &s);
+
+// Alternate construction: request shared edits
+struct string_WithSharedEdits {
+    string * s;
+};
+string_WithSharedEdits ?`shareEdits( string & this );
+void ?{}( string & this, string_WithSharedEdits src );
+
+// IO Operator
+ofstream & ?|?(ofstream &out, const string &s);
+void ?|?(ofstream &out, const string &s);
+
+// Concatenation
+void ?+=?(string &s, char other); // append a character
+void ?+=?(string &s, const string &s2); // append-concatenate to first string
+void ?+=?(string &s, const char* other); // append-concatenate to first string
+string ?+?(const string &s, char other); // add a character to a copy of the string
+string ?+?(const string &s, const string &s2); // copy and concatenate both strings
+string ?+?(const char* s1, const char* s2); // concatenate both strings
+string ?+?(const string &s, const char* other); // copy and concatenate with NULL-terminated string
+
+// Repetition
+string ?*?(const string &s, size_t factor);
+string ?*?(char c, size_t size);
+string ?*?(const char *s, size_t size);
+
+// Character access
+char ?[?](const string &s, size_t index);
+string ?[?](string &s, size_t index);  // mutable length-1 slice of original
+//char codePointAt(const string &s, size_t index);  // to revisit under Unicode
+
+// Comparisons
+bool ?==?(const string &s, const string &other);
+bool ?!=?(const string &s, const string &other);
+bool ?==?(const string &s, const char* other);
+bool ?!=?(const string &s, const char* other);
+
+// Slicing
+string ?()( string & this, size_t start, size_t end );  // TODO const?
+string ?()( string & this, size_t start);
+
+// String search
+bool contains(const string &s, char ch); // single character
+
+int find(const string &s, char search);
+int find(const string &s, const string &search);
+int find(const string &s, const char* search);
+int find(const string &s, const char* search, size_t searchsize);
+
+bool includes(const string &s, const string &search);
+bool includes(const string &s, const char* search);
+bool includes(const string &s, const char* search, size_t searchsize);
+
+bool startsWith(const string &s, const string &prefix);
+bool startsWith(const string &s, const char* prefix);
+bool startsWith(const string &s, const char* prefix, size_t prefixsize);
+
+bool endsWith(const string &s, const string &suffix);
+bool endsWith(const string &s, const char* suffix);
+bool endsWith(const string &s, const char* suffix, size_t suffixsize);
+
+// Modifiers
+void padStart(string &s, size_t n);
+void padStart(string &s, size_t n, char padding);
+void padEnd(string &s, size_t n);
+void padEnd(string &s, size_t n, char padding);
+
+
+
+struct charclass {
+    charclass_res * inner;
+};
+
+void ?{}( charclass & ) = void;
+void ?{}( charclass &, charclass) = void;
+charclass ?=?( charclass &, charclass) = void;
+
+void ?{}( charclass &, const string & chars);
+void ?{}( charclass &, const char * chars );
+void ?{}( charclass &, const char * chars, size_t charssize );
+void ^?{}( charclass & );
+
+int include(const string &s, const charclass &mask);
+
+int exclude(const string &s, const charclass &mask);
+
+/*
+What to do with?
+StrRet include(string &s, const charclass &mask);
+StrRet exclude(string &s, const charclass &mask);
+*/
+
+
Index: libcfa/src/containers/string_res.cfa
===================================================================
--- libcfa/src/containers/string_res.cfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
+++ libcfa/src/containers/string_res.cfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
@@ -0,0 +1,881 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// string_res -- variable-length, mutable run of text, with resource semantics
+//
+// Author           : Michael L. Brooks
+// Created On       : Fri Sep 03 11:00:00 2021
+// Last Modified By : Michael L. Brooks
+// Last Modified On : Fri Sep 03 11:00:00 2021
+// Update Count     : 1
+//
+
+#include "string_res.hfa"
+#include <stdlib.hfa>  // e.g. malloc
+#include <string.h>    // e.g. strlen
+
+//######################### VbyteHeap "header" #########################
+
+
+#ifdef VbyteDebug
+extern HandleNode *HeaderPtr;
+#endif // VbyteDebug
+
+struct VbyteHeap {
+
+    int NoOfCompactions;				// number of compactions of the byte area
+    int NoOfExtensions;					// number of extensions in the size of the byte area
+    int NoOfReductions;					// number of reductions in the size of the byte area
+    
+    int InitSize;					// initial number of bytes in the byte-string area
+    int CurrSize;					// current number of bytes in the byte-string area
+    char *StartVbyte;					// pointer to the `st byte of the start of the byte-string area
+    char *EndVbyte;					// pointer to the next byte after the end of the currently used portion of byte-string area
+    void *ExtVbyte;					// pointer to the next byte after the end of the byte-string area
+
+    HandleNode Header;					// header node for handle list
+}; // VbyteHeap
+
+    
+static inline void compaction( VbyteHeap & );				// compaction of the byte area
+static inline void garbage( VbyteHeap & );				// garbage collect the byte area
+static inline void extend( VbyteHeap &, int );			// extend the size of the byte area
+static inline void reduce( VbyteHeap &, int );			// reduce the size of the byte area
+
+static inline void ?{}( VbyteHeap &, int = 1000 );
+static inline void ^?{}( VbyteHeap & );
+static inline void ByteCopy( VbyteHeap &, char *, int, int, char *, int, int ); // copy a block of bytes from one location in the heap to another
+static inline int ByteCmp( VbyteHeap &, char *, int, int, char *, int, int );	// compare 2 blocks of bytes
+static inline char *VbyteAlloc( VbyteHeap &, int );			// allocate a block bytes in the heap
+
+
+static inline void AddThisAfter( HandleNode &, HandleNode & );
+static inline void DeleteNode( HandleNode & );
+static inline void MoveThisAfter( HandleNode &, const HandleNode & );		// move current handle after parameter handle
+
+
+// Allocate the storage for the variable sized area and intialize the heap variables.
+
+static inline void ?{}( VbyteHeap & this, int Size ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:VbyteHeap::VbyteHeap, this:" | &this | " Size:" | Size;
+#endif // VbyteDebug
+    NoOfCompactions = NoOfExtensions = NoOfReductions = 0;
+    InitSize = CurrSize = Size;
+    StartVbyte = EndVbyte = alloc(CurrSize);
+    ExtVbyte = (void *)( StartVbyte + CurrSize );
+    Header.flink = Header.blink = &Header;
+#ifdef VbyteDebug
+    HeaderPtr = &Header;
+    serr | "exit:VbyteHeap::VbyteHeap, this:" | &this;
+#endif // VbyteDebug
+} // VbyteHeap
+
+
+// Release the dynamically allocated storage for the byte area.
+
+static inline void ^?{}( VbyteHeap & this ) with(this) {
+    free( StartVbyte );
+} // ~VbyteHeap
+
+
+//######################### HandleNode #########################
+
+
+// Create a handle node. The handle is not linked into the handle list.  This is the responsibilitiy of the handle
+// creator.
+
+void ?{}( HandleNode & this ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:HandleNode::HandleNode, this:" | &this;
+#endif // VbyteDebug
+    s = 0;
+    lnth = 0;
+#ifdef VbyteDebug
+    serr | "exit:HandleNode::HandleNode, this:" | &this;
+#endif // VbyteDebug
+} // HandleNode
+
+// Create a handle node. The handle is linked into the handle list at the end. This means that this handle will NOT be
+// in order by string address, but this is not a problem because a string with length zero does nothing during garbage
+// collection.
+
+void ?{}( HandleNode & this, VbyteHeap & vh ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:HandleNode::HandleNode, this:" | &this;
+#endif // VbyteDebug
+    s = 0;
+    lnth = 0;
+    AddThisAfter( this, *vh.Header.blink );
+#ifdef VbyteDebug
+    serr | "exit:HandleNode::HandleNode, this:" | &this;
+#endif // VbyteDebug
+} // HandleNode
+
+
+// Delete a node from the handle list by unchaining it from the list. If the handle node was allocated dynamically, it
+// is the responsibility of the creator to destroy it.
+
+void ^?{}( HandleNode & this ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:HandleNode::~HandleNode, this:" | & this;
+    {
+	serr | nlOff;
+	serr | " lnth:" | lnth | " s:" | (void *)s | ",\"";
+	for ( int i = 0; i < lnth; i += 1 ) {
+	    serr | s[i];
+	} // for
+	serr | "\" flink:" | flink | " blink:" | blink | nl;
+	serr | nlOn;
+    }
+#endif // VbyteDebug
+    DeleteNode( this );
+} // ~HandleNode
+
+//######################### String Resource #########################
+
+
+VbyteHeap HeapArea;
+
+// Returns the size of the string in bytes
+size_t size(const string_res &s) with(s) {
+    return Handle.lnth;
+}
+
+// Output operator
+ofstream & ?|?(ofstream &out, const string_res &s) {
+    // Store auto-newline state so it can be restored
+    bool anl = getANL$(out);
+    nlOff(out);
+    for (size_t i = 0; i < s.Handle.lnth; i++) {
+        out | s[i];
+    }
+    out | sep;
+    // Re-apply newlines after done, for chaining version
+    if (anl) nlOn(out);
+    return out;
+}
+
+void ?|?(ofstream &out, const string_res &s) {
+    // Store auto-newline state so it can be restored
+    bool anl = getANL$(out);
+    nlOff(out);
+    for (size_t i = 0; i < s.Handle.lnth; i++) {
+        // Need to re-apply on the last output operator, for whole-statement version
+        if (anl && i == s.Handle.lnth-1) nlOn(out);
+        out | s[i];
+    }
+    return out;
+}
+
+// Empty constructor
+void ?{}(string_res &s) with(s) {
+    (Handle){ HeapArea };
+    s.shareEditSet_prev = &s;
+    s.shareEditSet_next = &s;
+}
+
+// Constructor from a raw buffer and size
+void ?{}(string_res &s, const char* rhs, size_t rhslnth) with(s) {
+    (Handle){ HeapArea };
+    Handle.s = VbyteAlloc(HeapArea, rhslnth);
+    Handle.lnth = rhslnth;
+    for ( int i = 0; i < rhslnth; i += 1 ) {		// copy characters
+        Handle.s[i] = rhs[i];
+    } // for
+    s.shareEditSet_prev = &s;
+    s.shareEditSet_next = &s;
+}
+
+// String literal constructor
+void ?{}(string_res &s, const char* rhs) {
+    (s){ rhs, strlen(rhs) };
+}
+
+// General copy constructor
+void ?{}(string_res &s, const string_res & s2, StrResInitMode mode, size_t start, size_t end ) {
+
+    (s.Handle){ HeapArea };
+    s.Handle.s = s2.Handle.s + start;
+    s.Handle.lnth = end - start;
+    MoveThisAfter(s.Handle, s2.Handle );			// insert this handle after rhs handle
+    // ^ bug?  skip others at early point in string
+    
+    if (mode == COPY_VALUE) {
+        // make s alone in its shareEditSet
+        s.shareEditSet_prev = &s;
+        s.shareEditSet_next = &s;
+    } else {
+        assert( mode == SHARE_EDITS );
+
+        // s2 is logically const but not implementation const
+        string_res & s2mod = (string_res &) s2;
+
+        // insert s after s2 on shareEditSet
+        s.shareEditSet_next = s2mod.shareEditSet_next;
+        s.shareEditSet_prev = &s2mod;
+        s.shareEditSet_next->shareEditSet_prev = &s;
+        s.shareEditSet_prev->shareEditSet_next = &s;
+    }
+}
+
+void ?=?(string_res &s, const char* other) {
+    string_res sother = other;
+    const string_res & sother_ref = sother; 
+    s = sother_ref;  // `s = sother` calls autogen ?=?
+}
+
+void ?=?(string_res &s, char other) {
+    char otherCstr[2] = {other, 0};
+    s = otherCstr;
+}
+
+// Copy assignment operator
+void ?=?(string_res & this, const string_res & rhs) with( this ) {
+
+    char * afterBegin = this.Handle.s + this.Handle.lnth;
+
+    char * shareEditSetStart = this.Handle.s;
+    char * shareEditSetEnd = afterBegin;
+    for (string_res * editPeer = this.shareEditSet_next; editPeer != &this; editPeer = editPeer->shareEditSet_next) {
+        shareEditSetStart = min( shareEditSetStart, editPeer->Handle.s );
+        shareEditSetEnd = max( shareEditSetStart, editPeer->Handle.s + editPeer->Handle.lnth);
+    }
+
+    char * beforeBegin = shareEditSetStart;
+    size_t beforeLen = this.Handle.s - shareEditSetStart;
+    size_t afterLen = shareEditSetEnd - afterBegin;
+
+    string_res pasting = { beforeBegin, beforeLen };
+    pasting += rhs;
+    string_res after = { afterBegin, afterLen }; // juxtaposed with in-progress pasting
+    pasting += after;                        // optimized case
+
+    size_t oldLnth = this.Handle.lnth;
+
+    this.Handle.s = pasting.Handle.s + beforeLen;
+    this.Handle.lnth = rhs.Handle.lnth;
+    MoveThisAfter( this.Handle, pasting.Handle );
+
+    // adjust all substring string and handle locations, and check if any substring strings are outside the new base string
+    char *limit = pasting.Handle.s + pasting.Handle.lnth;
+    for (string_res * p = this.shareEditSet_next; p != &this; p = p->shareEditSet_next) {
+        assert (p->Handle.s >= beforeBegin);
+        if ( p->Handle.s < beforeBegin + beforeLen ) {
+            // p starts before the edit
+            if ( p->Handle.s + p->Handle.lnth < beforeBegin + beforeLen ) {
+                // p ends before the edit
+                // take end as start-anchored too
+                // p->Handle.lnth unaffected
+            } else if ( p->Handle.s + p->Handle.lnth < afterBegin ) {
+                // p ends during the edit
+                // clip end of p to end at start of edit
+                p->Handle.lnth = beforeLen - ( p->Handle.s - beforeBegin );
+            } else {
+                // p ends after the edit
+                assert ( p->Handle.s + p->Handle.lnth <= afterBegin + afterLen );
+                // take end as end-anchored
+                // stretch-shrink p according to the edit
+                p->Handle.lnth += this.Handle.lnth;
+                p->Handle.lnth -= oldLnth;
+            }
+            // take start as start-anchored
+            size_t startOffsetFromStart = p->Handle.s - beforeBegin;
+            p->Handle.s = pasting.Handle.s + startOffsetFromStart;
+        } else if ( p->Handle.s < afterBegin ) {
+            // p starts during the edit
+            assert( p->Handle.s + p->Handle.lnth >= beforeBegin + beforeLen );
+            if ( p->Handle.s + p->Handle.lnth < afterBegin ) {
+                // p ends during the edit
+                // set p to empty string at start of edit
+                p->Handle.s = this.Handle.s;
+                p->Handle.lnth = 0;
+            } else {
+                // p ends after the edit
+                // clip start of p to start at end of edit
+                p->Handle.s = this.Handle.s + this.Handle.lnth;
+                p->Handle.lnth += this.Handle.lnth;
+                p->Handle.lnth -= oldLnth;
+            }
+        } else {
+            assert ( p->Handle.s <= afterBegin + afterLen );
+            assert ( p->Handle.s + p->Handle.lnth <= afterBegin + afterLen );
+            // p starts after the edit
+            // take start and end as end-anchored
+            size_t startOffsetFromEnd = afterBegin + afterLen - p->Handle.s;
+            p->Handle.s = limit - startOffsetFromEnd;
+            // p->Handle.lnth unaffected
+        }
+        MoveThisAfter( p->Handle, pasting.Handle );	// move substring handle to maintain sorted order by string position
+    }
+}
+
+void ?=?(string_res & this, string_res & rhs) with( this ) {
+    const string_res & rhs2 = rhs;
+    this = rhs2;
+}
+
+
+// Destructor
+void ^?{}(string_res &s) with(s) {
+    // much delegated to implied ^VbyteSM
+
+    // sever s from its share-edit peers, if any (four no-ops when already solo)
+    s.shareEditSet_prev->shareEditSet_next = s.shareEditSet_next;
+    s.shareEditSet_next->shareEditSet_prev = s.shareEditSet_prev;
+    s.shareEditSet_next = &s;
+    s.shareEditSet_prev = &s;
+}
+
+
+// Returns the character at the given index
+// With unicode support, this may be different from just the byte at the given
+// offset from the start of the string.
+char ?[?](const string_res &s, size_t index) with(s) {
+    //TODO: Check if index is valid (no exceptions yet)
+    return Handle.s[index];
+}
+
+///////////////////////////////////////////////////////////////////
+// Slice-Concatenate helper
+
+void append(string_res &str1, const string_res & str_src, size_t start, size_t end) {
+    size_t clnth = size(str1) + end - start;
+    if ( str1.Handle.s + size(str1) == str_src.Handle.s && start == 0) { // already juxtapose ?
+    } else {						// must copy some text
+        if ( str1.Handle.s + size(str1) == VbyteAlloc(HeapArea, 0) ) { // str1 at end of string area ?
+            VbyteAlloc(HeapArea, end - start); // create room for 2nd part at the end of string area
+        } else {					// copy the two parts
+            char * str1oldBuf = str1.Handle.s;
+            str1.Handle.s = VbyteAlloc( HeapArea, clnth );
+            ByteCopy( HeapArea, str1.Handle.s, 0, str1.Handle.lnth, str1oldBuf, 0, str1.Handle.lnth);
+        } // if
+        ByteCopy( HeapArea, str1.Handle.s, str1.Handle.lnth, str_src.Handle.lnth, str_src.Handle.s, start, end);
+        //       VbyteHeap & this, char *Dst, int DstStart, int DstLnth, char *Src, int SrcStart, int SrcLnth 
+    } // if
+    str1.Handle.lnth = clnth;
+}
+
+
+
+///////////////////////////////////////////////////////////////////
+// Concatenation
+
+void ?+=?(string_res &str1, const string_res &str2) {
+    append( str1, str2, 0, size(str2) );
+}
+
+void ?+=?(string_res &s, char other) {
+    string_res other_s = { &other, 1 };
+    s += other_s;
+}
+
+void ?+=?(string_res &s, const char* other) {
+    string_res other_s = other;
+    s += other_s;
+}
+
+
+
+
+//////////////////////////////////////////////////////////
+// Comparisons
+
+
+bool ?==?(const string_res &s1, const string_res &s2) {
+    return ByteCmp( HeapArea, s1.Handle.s, 0, s1.Handle.lnth, s2.Handle.s, 0, s2.Handle.lnth) == 0;
+}
+
+bool ?!=?(const string_res &s1, const string_res &s2) {
+    return !(s1 == s2);
+}
+bool ?==?(const string_res &s, const char* other) {
+    string_res sother = other;
+    return s == sother;
+}
+bool ?!=?(const string_res &s, const char* other) {
+    return !(s == other);
+}
+
+
+//////////////////////////////////////////////////////////
+// Search
+
+bool contains(const string_res &s, char ch) {
+    for (i; size(s)) {
+        if (s[i] == ch) return true;
+    }
+    return false;
+}
+
+int find(const string_res &s, char search) {
+    for (i; size(s)) {
+        if (s[i] == search) return i;
+    }
+    return size(s);
+}
+
+    /* Remaining implementations essentially ported from Sunjay's work */
+
+int find(const string_res &s, const string_res &search) {
+    return find(s, search.Handle.s, search.Handle.lnth);
+}
+
+int find(const string_res &s, const char* search) {
+    return find(s, search, strlen(search));
+}
+
+int find(const string_res &s, const char* search, size_t searchsize) {
+    // FIXME: This is a naive algorithm. We probably want to switch to someting
+    // like Boyer-Moore in the future.
+    // https://en.wikipedia.org/wiki/String_searching_algorithm
+
+    // Always find the empty string
+    if (searchsize == 0) {
+        return 0;
+    }
+
+    for (size_t i = 0; i < s.Handle.lnth; i++) {
+        size_t remaining = s.Handle.lnth - i;
+        // Never going to find the search string if the remaining string is
+        // smaller than search
+        if (remaining < searchsize) {
+            break;
+        }
+
+        bool matched = true;
+        for (size_t j = 0; j < searchsize; j++) {
+            if (search[j] != s.Handle.s[i + j]) {
+                matched = false;
+                break;
+            }
+        }
+        if (matched) {
+            return i;
+        }
+    }
+
+    return s.Handle.lnth;
+}
+
+bool includes(const string_res &s, const string_res &search) {
+    return includes(s, search.Handle.s, search.Handle.lnth);
+}
+
+bool includes(const string_res &s, const char* search) {
+    return includes(s, search, strlen(search));
+}
+
+bool includes(const string_res &s, const char* search, size_t searchsize) {
+    return find(s, search, searchsize) < s.Handle.lnth;
+}
+
+bool startsWith(const string_res &s, const string_res &prefix) {
+    return startsWith(s, prefix.Handle.s, prefix.Handle.lnth);
+}
+
+bool startsWith(const string_res &s, const char* prefix) {
+    return startsWith(s, prefix, strlen(prefix));
+}
+
+bool startsWith(const string_res &s, const char* prefix, size_t prefixsize) {
+    if (s.Handle.lnth < prefixsize) {
+        return false;
+    }
+    return memcmp(s.Handle.s, prefix, prefixsize) == 0;
+}
+
+bool endsWith(const string_res &s, const string_res &suffix) {
+    return endsWith(s, suffix.Handle.s, suffix.Handle.lnth);
+}
+
+bool endsWith(const string_res &s, const char* suffix) {
+    return endsWith(s, suffix, strlen(suffix));
+}
+
+bool endsWith(const string_res &s, const char* suffix, size_t suffixsize) {
+    if (s.Handle.lnth < suffixsize) {
+        return false;
+    }
+    // Amount to offset the bytes pointer so that we are comparing the end of s
+    // to suffix. s.bytes + offset should be the first byte to compare against suffix
+    size_t offset = s.Handle.lnth - suffixsize;
+    return memcmp(s.Handle.s + offset, suffix, suffixsize) == 0;
+}
+
+    /* Back to Mike's work */
+
+
+///////////////////////////////////////////////////////////////////////////
+// charclass, include, exclude
+
+void ?{}( charclass_res & this, const string_res & chars) {
+    (this){ chars.Handle.s, chars.Handle.lnth };
+}
+
+void ?{}( charclass_res & this, const char * chars ) {
+    (this){ chars, strlen(chars) };
+}
+
+void ?{}( charclass_res & this, const char * chars, size_t charssize ) {
+    (this.chars){ chars, charssize };
+    // now sort it ?
+}
+
+void ^?{}( charclass_res & this ) {
+    ^(this.chars){};
+}
+
+static bool test( const charclass_res & mask, char c ) {
+    // instead, use sorted char list?
+    return contains( mask.chars, c );
+}
+
+int exclude(const string_res &s, const charclass_res &mask) {
+    for (int i = 0; i < size(s); i++) {
+        if ( test(mask, s[i]) ) return i;
+    }
+    return size(s);
+}
+
+int include(const string_res &s, const charclass_res &mask) {
+    for (int i = 0; i < size(s); i++) {
+        if ( ! test(mask, s[i]) ) return i;
+    }
+    return size(s);
+}
+
+//######################### VbyteHeap "implementation" #########################
+
+
+// Add a new HandleNode node n after the current HandleNode node.
+
+static inline void AddThisAfter( HandleNode & this, HandleNode & n ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:AddThisAfter, this:" | &this | " n:" | &n;
+#endif // VbyteDebug
+    flink = n.flink;
+    blink = &n;
+    n.flink->blink = &this;
+    n.flink = &this;
+#ifdef VbyteDebug
+    {
+		serr | "HandleList:";
+		serr | nlOff;
+		for ( HandleNode *ni = HeaderPtr->flink; ni != HeaderPtr; ni = ni->flink ) {
+			serr | "\tnode:" | ni | " lnth:" | ni->lnth | " s:" | (void *)ni->s | ",\"";
+			for ( int i = 0; i < ni->lnth; i += 1 ) {
+				serr | ni->s[i];
+			} // for
+			serr | "\" flink:" | ni->flink | " blink:" | ni->blink | nl;
+		} // for
+		serr | nlOn;
+    }
+    serr | "exit:AddThisAfter";
+#endif // VbyteDebug
+} // AddThisAfter
+
+
+// Delete the current HandleNode node.
+
+static inline void DeleteNode( HandleNode & this ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:DeleteNode, this:" | &this;
+#endif // VbyteDebug
+    flink->blink = blink;
+    blink->flink = flink;
+#ifdef VbyteDebug
+    serr | "exit:DeleteNode";
+#endif // VbyteDebug
+} //  DeleteNode
+
+
+
+// Allocates specified storage for a string from byte-string area. If not enough space remains to perform the
+// allocation, the garbage collection routine is called and a second attempt is made to allocate the space. If the
+// second attempt fails, a further attempt is made to create a new, larger byte-string area.
+
+static inline char * VbyteAlloc( VbyteHeap & this, int size ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:VbyteAlloc, size:" | size;
+#endif // VbyteDebug
+    uintptr_t NoBytes;
+    char *r;
+
+    NoBytes = ( uintptr_t )EndVbyte + size;
+    if ( NoBytes > ( uintptr_t )ExtVbyte ) {		// enough room for new byte-string ?
+		garbage( this );					// firer up the garbage collector
+		NoBytes = ( uintptr_t )EndVbyte + size;		// try again
+		if ( NoBytes > ( uintptr_t )ExtVbyte ) {	// enough room for new byte-string ?
+assert( 0 && "need to implement actual growth" );
+			// extend( size );				// extend the byte-string area
+		} // if
+    } // if
+    r = EndVbyte;
+    EndVbyte += size;
+#ifdef VbyteDebug
+    serr | "exit:VbyteAlloc, r:" | (void *)r | " EndVbyte:" | (void *)EndVbyte | " ExtVbyte:" | ExtVbyte;
+#endif // VbyteDebug
+    return r;
+} // VbyteAlloc
+
+
+// Move an existing HandleNode node h somewhere after the current HandleNode node so that it is in ascending order by
+// the address in the byte string area.
+
+static inline void MoveThisAfter( HandleNode & this, const HandleNode  & h ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:MoveThisAfter, this:" | & this | " h:" | & h;
+#endif // VbyteDebug
+    if ( s < h.s ) {					// check argument values
+		// serr | "VbyteSM: Error - Cannot move byte string starting at:" | s | " after byte string starting at:"
+		//      | ( h->s ) | " and keep handles in ascending order";
+		// exit(-1 );
+		assert( 0 && "VbyteSM: Error - Cannot move byte strings as requested and keep handles in ascending order");
+    } // if
+
+    HandleNode *i;
+    for ( i = h.flink; i->s != 0 && s > ( i->s ); i = i->flink ); // find the position for this node after h
+    if ( & this != i->blink ) {
+		DeleteNode( this );
+		AddThisAfter( this, *i->blink );
+    } // if
+#ifdef VbyteDebug
+    serr | "exit:MoveThisAfter";
+    {
+	serr | "HandleList:";
+	serr | nlOff;
+	for ( HandleNode *n = HeaderPtr->flink; n != HeaderPtr; n = n->flink ) {
+	    serr | "\tnode:" | n | " lnth:" | n->lnth | " s:" | (void *)n->s | ",\"";
+	    for ( int i = 0; i < n->lnth; i += 1 ) {
+		serr | n->s[i];
+	    } // for
+	    serr | "\" flink:" | n->flink | " blink:" | n->blink;
+	} // for
+	serr | nlOn;
+    }
+#endif // VbyteDebug
+} // MoveThisAfter
+
+
+
+
+
+//######################### VbyteHeap #########################
+
+#ifdef VbyteDebug
+HandleNode *HeaderPtr = 0p;
+#endif // VbyteDebug
+
+// Move characters from one location in the byte-string area to another. The routine handles the following situations:
+//
+// if the |Src| > |Dst| => truncate
+// if the |Dst| > |Src| => pad Dst with blanks
+
+void ByteCopy( VbyteHeap & this, char *Dst, int DstStart, int DstLnth, char *Src, int SrcStart, int SrcLnth ) {
+    for ( int i = 0; i < DstLnth; i += 1 ) {
+      if ( i == SrcLnth ) {				// |Dst| > |Src|
+	    for ( ; i < DstLnth; i += 1 ) {		// pad Dst with blanks
+		Dst[DstStart + i] = ' ';
+	    } // for
+	    break;
+	} // exit
+	Dst[DstStart + i] = Src[SrcStart + i];
+    } // for
+} // ByteCopy
+
+// Compare two byte strings in the byte-string area. The routine returns the following values:
+//
+// 1 => Src1-byte-string > Src2-byte-string
+// 0 => Src1-byte-string = Src2-byte-string
+// -1 => Src1-byte-string < Src2-byte-string
+
+int ByteCmp( VbyteHeap & this, char *Src1, int Src1Start, int Src1Lnth, char *Src2, int Src2Start, int Src2Lnth )  with(this) {
+#ifdef VbyteDebug
+    serr | "enter:ByteCmp, Src1Start:" | Src1Start | " Src1Lnth:" | Src1Lnth | " Src2Start:" | Src2Start | " Src2Lnth:" | Src2Lnth;
+#endif // VbyteDebug
+    int cmp;
+
+    CharZip: for ( int i = 0; ; i += 1 ) {
+	if ( i == Src2Lnth - 1 ) {
+	    for ( ; ; i += 1 ) {
+		if ( i == Src1Lnth - 1 ) {
+		    cmp = 0;
+		    break CharZip;
+		} // exit
+		if ( Src1[Src1Start + i] != ' ') {
+			// SUSPECTED BUG:  this could be be why Peter got the bug report about == " "  (why is this case here at all?)
+		    cmp = 1;
+		    break CharZip;
+		} // exit
+	    } // for
+	} // exit
+	if ( i == Src1Lnth - 1 ) {
+	    for ( ; ; i += 1 ) {
+	    	if ( i == Src2Lnth - 1 ) {
+		    cmp = 0;
+		    break CharZip;
+		} // exit
+	    	if ( Src2[Src2Start + i] != ' ') {
+		    cmp = -1;
+		    break CharZip;
+		} // exit
+	    } // for
+	} // exit
+      if ( Src2[Src2Start + i] != Src1[Src1Start+ i]) {
+	    cmp = Src1[Src1Start + i] > Src2[Src2Start + i] ? 1 : -1;
+	    break CharZip;
+	} // exit
+    } // for
+#ifdef VbyteDebug
+    serr | "exit:ByteCmp, cmp:" | cmp;
+#endif // VbyteDebug
+    return cmp;
+} // ByteCmp
+
+
+// The compaction moves all of the byte strings currently in use to the beginning of the byte-string area and modifies
+// the handles to reflect the new positions of the byte strings. Compaction assumes that the handle list is in ascending
+// order by pointers into the byte-string area.  The strings associated with substrings do not have to be moved because
+// the containing string has been moved. Hence, they only require that their string pointers be adjusted.
+
+void compaction(VbyteHeap & this) with(this) {
+    HandleNode *h;
+    char *obase, *nbase, *limit;
+    
+    NoOfCompactions += 1;
+    EndVbyte = StartVbyte;
+    h = Header.flink;					// ignore header node
+    for (;;) {
+		ByteCopy( this, EndVbyte, 0, h->lnth, h->s, 0, h->lnth );
+		obase = h->s;
+		h->s = EndVbyte;
+		nbase = h->s;
+		EndVbyte += h->lnth;
+		limit = obase + h->lnth;
+		h = h->flink;
+		
+		// check if any substrings are allocated within a string
+		
+		for (;;) {
+			if ( h == &Header ) break;			// end of header list ?
+			if ( h->s >= limit ) break;			// outside of current string ?
+			h->s = nbase + (( uintptr_t )h->s - ( uintptr_t )obase );
+			h = h->flink;
+		} // for
+		if ( h == &Header ) break;			// end of header list ?
+    } // for
+} // compaction
+
+
+// Garbage determines the amount of free space left in the heap and then reduces, leave the same, or extends the size of
+// the heap.  The heap is then compacted in the existing heap or into the newly allocated heap.
+
+void garbage(VbyteHeap & this ) with(this) {
+#ifdef VbyteDebug
+    serr | "enter:garbage";
+    {
+		serr | "HandleList:";
+		for ( HandleNode *n = Header.flink; n != &Header; n = n->flink ) {
+			serr | nlOff;
+			serr | "\tnode:" | n | " lnth:" | n->lnth | " s:" | (void *)n->s | ",\"";
+			for ( int i = 0; i < n->lnth; i += 1 ) {
+				serr | n->s[i];
+			} // for
+			serr | nlOn;
+			serr | "\" flink:" | n->flink | " blink:" | n->blink;
+		} // for
+    }
+#endif // VbyteDebug
+    int AmountUsed, AmountFree;
+
+    AmountUsed = 0;
+    for ( HandleNode *i = Header.flink; i != &Header; i = i->flink ) { // calculate amount of byte area used
+		AmountUsed += i->lnth;
+    } // for
+    AmountFree = ( uintptr_t )ExtVbyte - ( uintptr_t )StartVbyte - AmountUsed;
+    
+    if ( AmountFree < ( int )( CurrSize * 0.1 )) {	// free space less than 10% ?
+
+assert( 0 && "need to implement actual growth" );
+//		extend( CurrSize );				// extend the heap
+
+			//  Peter says, "This needs work before it should be used."
+			//  } else if ( AmountFree > CurrSize / 2 ) {		// free space greater than 3 times the initial allocation ? 
+			//		reduce(( AmountFree / CurrSize - 3 ) * CurrSize ); // reduce the memory
+
+    } // if
+    compaction(this);					// compact the byte area, in the same or new heap area
+#ifdef VbyteDebug
+    {
+		serr | "HandleList:";
+		for ( HandleNode *n = Header.flink; n != &Header; n = n->flink ) {
+			serr | nlOff;
+			serr | "\tnode:" | n | " lnth:" | n->lnth | " s:" | (void *)n->s | ",\"";
+			for ( int i = 0; i < n->lnth; i += 1 ) {
+				serr | n->s[i];
+			} // for
+			serr | nlOn;
+			serr | "\" flink:" | n->flink | " blink:" | n->blink;
+		} // for
+    }
+    serr | "exit:garbage";
+#endif // VbyteDebug
+} // garbage
+
+#undef VbyteDebug
+
+//WIP
+#if 0
+
+
+// Extend the size of the byte-string area by creating a new area and copying the old area into it. The old byte-string
+// area is deleted.
+
+void VbyteHeap::extend( int size ) {
+#ifdef VbyteDebug
+    serr | "enter:extend, size:" | size;
+#endif // VbyteDebug
+    char *OldStartVbyte;
+
+    NoOfExtensions += 1;
+    OldStartVbyte = StartVbyte;				// save previous byte area
+    
+    CurrSize += size > InitSize ? size : InitSize;	// minimum extension, initial size
+    StartVbyte = EndVbyte = new char[CurrSize];
+    ExtVbyte = (void *)( StartVbyte + CurrSize );
+    compaction();					// copy from old heap to new & adjust pointers to new heap
+    delete OldStartVbyte;				// release old heap
+#ifdef VbyteDebug
+    serr | "exit:extend, CurrSize:" | CurrSize;
+#endif // VbyteDebug
+} // extend
+
+
+// Extend the size of the byte-string area by creating a new area and copying the old area into it. The old byte-string
+// area is deleted.
+
+void VbyteHeap::reduce( int size ) {
+#ifdef VbyteDebug
+    serr | "enter:reduce, size:" | size;
+#endif // VbyteDebug
+    char *OldStartVbyte;
+
+    NoOfReductions += 1;
+    OldStartVbyte = StartVbyte;				// save previous byte area
+    
+    CurrSize -= size;
+    StartVbyte = EndVbyte = new char[CurrSize];
+    ExtVbyte = (void *)( StartVbyte + CurrSize );
+    compaction();					// copy from old heap to new & adjust pointers to new heap
+    delete  OldStartVbyte;				// release old heap
+#ifdef VbyteDebug
+    serr | "exit:reduce, CurrSize:" | CurrSize;
+#endif // VbyteDebug
+} // reduce
+
+
+#endif
Index: libcfa/src/containers/string_res.hfa
===================================================================
--- libcfa/src/containers/string_res.hfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
+++ libcfa/src/containers/string_res.hfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
@@ -0,0 +1,138 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// string_res -- variable-length, mutable run of text, with resource semantics
+//
+// Author           : Michael L. Brooks
+// Created On       : Fri Sep 03 11:00:00 2021
+// Last Modified By : Michael L. Brooks
+// Last Modified On : Fri Sep 03 11:00:00 2021
+// Update Count     : 1
+//
+
+#pragma once
+
+#include <fstream.hfa>
+
+    
+//######################### HandleNode #########################
+//private
+
+struct VbyteHeap;
+
+struct HandleNode {
+    HandleNode *flink;					// forward link
+    HandleNode *blink;					// backward link
+
+    char *s;						// pointer to byte string
+    unsigned int lnth;					// length of byte string
+}; // HandleNode
+
+void ?{}( HandleNode & );			// constructor for header node
+
+void ?{}( HandleNode &, VbyteHeap & );		// constructor for nodes in the handle list
+void ^?{}( HandleNode & );			// destructor for handle nodes
+
+
+//######################### String #########################
+
+// A dynamically-sized string
+struct string_res {
+    HandleNode Handle; // chars, start, end, global neighbours
+    string_res * shareEditSet_prev;
+    string_res * shareEditSet_next;
+};
+
+
+//######################### charclass_res #########################
+
+struct charclass_res {
+    string_res chars;
+};
+
+void ?{}( charclass_res & ) = void;
+void ?{}( charclass_res &, charclass_res) = void;
+charclass_res ?=?( charclass_res &, charclass_res) = void;
+void ?{}( charclass_res &, const string_res & chars);
+void ?{}( charclass_res &, const char * chars );
+void ?{}( charclass_res &, const char * chars, size_t charssize );
+void ^?{}( charclass_res & );
+
+
+//######################### String #########################
+
+// Getters
+size_t size(const string_res &s);
+
+// Constructors, Assignment Operators, Destructor
+void ?{}(string_res &s); // empty string
+void ?{}(string_res &s, const char* initial); // copy from string literal (NULL-terminated)
+void ?{}(string_res &s, const char* buffer, size_t bsize); // copy specific length from buffer
+
+void ?{}(string_res &s, const string_res & s2) = void;
+void ?{}(string_res &s, string_res & s2) = void;
+
+enum StrResInitMode { COPY_VALUE, SHARE_EDITS };
+void ?{}(string_res &s, const string_res & src, StrResInitMode, size_t start, size_t end );
+static inline void ?{}(string_res &s, const string_res & src, StrResInitMode mode ) {
+    ?{}( s, src, mode, 0, size(src));
+}
+
+void ?=?(string_res &s, const char* other); // copy assignment from literal
+void ?=?(string_res &s, const string_res &other);
+void ?=?(string_res &s, string_res &other);
+void ?=?(string_res &s, char other);  // Str tolerates memcpys; still saw calls to autogen 
+
+void ^?{}(string_res &s);
+
+// IO Operator
+ofstream & ?|?(ofstream &out, const string_res &s);
+void ?|?(ofstream &out, const string_res &s);
+
+// Concatenation
+void ?+=?(string_res &s, char other); // append a character
+void ?+=?(string_res &s, const string_res &s2); // append-concatenate to first string
+void ?+=?(string_res &s, const char* other); // append-concatenate to first string
+
+// Character access
+char ?[?](const string_res &s, size_t index); // Mike changed to ret by val from Sunjay's ref, to match Peter's
+//char codePointAt(const string_res &s, size_t index); // revisit under Unicode
+
+// Comparisons
+bool ?==?(const string_res &s, const string_res &other);
+bool ?!=?(const string_res &s, const string_res &other);
+bool ?==?(const string_res &s, const char* other);
+bool ?!=?(const string_res &s, const char* other);
+
+// String search
+bool contains(const string_res &s, char ch); // single character
+
+int find(const string_res &s, char search);
+int find(const string_res &s, const string_res &search);
+int find(const string_res &s, const char* search);
+int find(const string_res &s, const char* search, size_t searchsize);
+
+bool includes(const string_res &s, const string_res &search);
+bool includes(const string_res &s, const char* search);
+bool includes(const string_res &s, const char* search, size_t searchsize);
+
+bool startsWith(const string_res &s, const string_res &prefix);
+bool startsWith(const string_res &s, const char* prefix);
+bool startsWith(const string_res &s, const char* prefix, size_t prefixsize);
+
+bool endsWith(const string_res &s, const string_res &suffix);
+bool endsWith(const string_res &s, const char* suffix);
+bool endsWith(const string_res &s, const char* suffix, size_t suffixsize);
+
+int include(const string_res &s, const charclass_res &mask);
+int exclude(const string_res &s, const charclass_res &mask);
+
+// Modifiers
+void padStart(string_res &s, size_t n);
+void padStart(string_res &s, size_t n, char padding);
+void padEnd(string_res &s, size_t n);
+void padEnd(string_res &s, size_t n, char padding);
+
Index: tests/collections/.expect/string-api-coverage.txt
===================================================================
--- tests/collections/.expect/string-api-coverage.txt	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
+++ tests/collections/.expect/string-api-coverage.txt	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
@@ -0,0 +1,51 @@
+hello hello hello
+true false
+true false
+true false
+true false
+true false
+true false
+true false
+true false
+true false
+true false
+true false
+true false
+123
+hello
+hello
+world
+hello
+world
+5
+helloworld
+helloworld
+helloworld!
+hello!
+hellohello
+hellohello, friend
+hello, friend
+bye, friend
+hellohellohello
+QQQ
+asdfasdfasdf
+e
+help!!!o
+help!!!!
+help!!!
+p
+true true false
+0 4 7 8
+0 0 25 0 26 3
+true true true true false true
+0 0 26 3
+true true false true
+true false false true
+0 0 26 3
+true true false true
+true false false true
+3 3 1 0 22 0 3 5 3
+true true true true false true true false true
+true true false true true true true true false
+true false true false true true true false true false
+3 0 0 11 26 0
Index: tests/collections/string-api-coverage.cfa
===================================================================
--- tests/collections/string-api-coverage.cfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
+++ tests/collections/string-api-coverage.cfa	(revision f450f2f3d268f91ca50a97d15db72c48d26ad4b6)
@@ -0,0 +1,291 @@
+#include <containers/string.hfa>
+
+void assertWellFormedHandleList( int maxLen ) { // with(HeapArea)
+    // HandleNode *n;
+    // int limit1 = maxLen;
+    // for ( n = Header.flink; (limit1-- > 0) && n != &Header; n = n->flink ) {}
+    // assert (n == &Header);
+    // int limit2 = maxLen;
+    // for ( n = Header.blink; (limit2-- > 0) && n != &Header; n = n->blink ) {}
+    // assert (n == &Header);
+    // assert (limit1 == limit2);
+}
+
+// The given string is reachable.
+void assertOnHandleList( string & q ) { // with(HeapArea)
+    // HandleNode *n;
+    // for ( n = Header.flink; n != &Header; n = n->flink ) {
+    //     if ( n == & q.inner->Handle ) return;
+    // }
+    // assert( false );
+}
+
+
+// Purpose: call each function in string.hfa, top to bottom
+
+int main () {
+    string s = "hello";
+    string s2 = "hello";
+    string s3 = "world";
+    string frag = "ell";
+
+    // IO operator, x2
+    sout | s | s | s;
+
+    // Comparisons
+    // all print "true false"
+    sout | (s == s2) | (s == s3);
+    sout | (s != s3) | (s != s2);
+    sout | (s == "hello") | (s == "world");
+    sout | (s != "world") | (s != "hello");
+    sout | ( frag == s(1,4) ) | ( s3   == s(1,4) );
+    sout | ( s3   != s(1,4) ) | ( frag != s(1,4) );
+    sout | ( s2(1,4) == s(1,4) ) | ( s3(1,4)   == s(1,4) );
+    sout | ( s3(1,4) != s(1,4) ) | ( s2(1,4)   != s(1,4) );
+    sout | ( s(1,4) == frag ) | ( s(1,4) == s3   );
+    sout | ( s(1,4) != s3   ) | ( s(1,4) != frag );
+    sout | ( s(1,4) == "ell"   ) | ( s(1,4) == "world" );
+    sout | ( s(1,4) != "world" ) | ( s(1,4) != "ell"   );
+
+
+                                            assertWellFormedHandleList( 10 );
+    //
+    // breadth Constructors
+    //
+    {
+        string b1 = { "1234567", 3 };
+        sout | b1; // 123
+
+        string b2 = s;
+        sout | b2; // hello
+
+        // todo: a plain string &
+        const string & s_ref = s;
+        string b3 = s_ref;
+        sout | b3;  // hello
+
+        & s_ref = & s3;
+        b3 = s_ref;
+        sout | b3; // world
+
+        const string & s_constref = s;
+        string b4 = s_constref;
+        sout | b4; // hello
+
+        & s_constref = & s3;
+        b4 = s_constref;
+        sout | b4;  // world
+    }
+                                            assertWellFormedHandleList( 10 );
+
+    sout | size(s); // 5
+
+    //
+    // concatenation/append
+    //
+
+    string sx = s + s3;
+                                            assertWellFormedHandleList( 10 );
+    sout | sx; // helloworld
+                                            assertWellFormedHandleList( 10 );
+    sx = "xx";
+                                            assertWellFormedHandleList( 10 );
+    sx = s + s3;
+                                            assertWellFormedHandleList( 10 );
+    sout | sx; // helloworld
+                                            assertWellFormedHandleList( 10 );
+
+    sx += '!';
+    sout | sx; // helloworld!
+    sx = s + '!';
+    sout | sx; // hello!
+
+    sx = s;
+    sx += s;
+    sout | sx; // hellohello
+                                            assertWellFormedHandleList( 10 );
+    sx += ", friend";    
+    sout | sx; // hellohello, friend
+
+    sx = s + ", friend";
+    sout | sx; // hello, friend
+
+    sx = "bye, " + "friend";
+    sout | sx; // bye, friend
+
+    //
+    // repetition
+    //
+    sx = s * 3;
+    sout | sx; // hellohellohello
+
+    sx = 'Q' * (size_t)3;
+    sout | sx; // QQQ
+
+    sx = "asdf" * 3;
+    sout | sx; // asdfasdfasdf
+
+    //
+    // slicing
+    //
+
+    //...
+
+    //
+    // character access
+    //
+
+    char c = s[1];
+    sout | c;   // e
+
+    s[3] = "p!!!";
+    sout | s;   // help!!!o
+
+    s[7] = '!';
+    sout | s;   // help!!!!
+
+    s[7] = "";
+    sout | s;   // help!!!
+
+    sout | s[3]; // p
+
+    //
+    // search
+    //
+
+    s += '?'; // already tested
+    sout | contains( s, 'h' ) | contains( s, '?' ) | contains( s, 'o' ); // true true false
+
+    sout
+        | find( s, 'h' )  // 0
+        | find( s, '!' )  // 4
+        | find( s, '?' )  // 7
+        | find( s, 'o' ); // 8, not found
+
+    string alphabet = "abcdefghijklmnopqrstuvwxyz";
+
+    sout
+        | find( alphabet, "" )    // 0
+        | find( alphabet, "a" )   // 0
+        | find( alphabet, "z" )   // 25
+        | find( alphabet, "abc" ) // 0
+        | find( alphabet, "abq" ) // 26, not found
+        | find( alphabet, "def"); // 3
+    
+    sout
+        | includes( alphabet, "" )    // true
+        | includes( alphabet, "a" )   // true
+        | includes( alphabet, "z" )   // true
+        | includes( alphabet, "abc" ) // true
+        | includes( alphabet, "abq" ) // false
+        | includes( alphabet, "def"); // true
+    
+    {
+        char *empty_c = "";
+        char *a_c = "a";
+        char *z_c = "z";
+        char *dex_c = "dex";
+
+        sout
+            | find( alphabet, empty_c )   // 0
+            | find( alphabet, a_c )       // 0
+            | find( alphabet, dex_c )     // 26, not found
+            | find( alphabet, dex_c, 2 ); // 3
+
+        sout
+            | includes( alphabet, empty_c )   // true
+            | includes( alphabet, a_c )       // true
+            | includes( alphabet, dex_c )     // false
+            | includes( alphabet, dex_c, 2 ); // true
+
+        sout
+            | startsWith( alphabet, a_c)            // true
+            | endsWith  ( alphabet, a_c)            // false
+            | startsWith( alphabet, z_c)            // false
+            | endsWith  ( alphabet, z_c);           // true
+
+        string empty = empty_c;
+        string a = a_c;
+        string z = z_c;
+        string dex = dex_c;
+
+        sout
+            | find( alphabet, empty )     // 0
+            | find( alphabet, a )         // 0
+            | find( alphabet, dex )       // 26, not found
+            | find( alphabet, dex(0,2) ); // 3
+
+        sout
+            | includes( alphabet, empty )     // true
+            | includes( alphabet, a )         // true
+            | includes( alphabet, dex )       // false
+            | includes( alphabet, dex(0,2) ); // true
+
+        sout
+            | startsWith( alphabet, a)            // true
+            | endsWith  ( alphabet, a)            // false
+            | startsWith( alphabet, z)            // false
+            | endsWith  ( alphabet, z);           // true
+    }
+
+    sout
+        | find( alphabet        , "def")  // 3
+        | find( alphabet( 0, 26), "def")  // 3
+        | find( alphabet( 2, 26), "def")  // 1
+        | find( alphabet( 3, 26), "def")  // 0
+        | find( alphabet( 4, 26), "def")  // 22, not found
+        | find( alphabet( 4, 26),  "ef")  // 0
+        | find( alphabet( 0,  6), "def")  // 3
+        | find( alphabet( 0,  5), "def")  // 5, not found
+        | find( alphabet( 0,  5), "de" ); // 3
+
+    sout
+        | includes( alphabet        , "def")  // true
+        | includes( alphabet( 0, 26), "def")  // true
+        | includes( alphabet( 2, 26), "def")  // true
+        | includes( alphabet( 3, 26), "def")  // true
+        | includes( alphabet( 4, 26), "def")  // false
+        | includes( alphabet( 4, 26),  "ef")  // true
+        | includes( alphabet( 0,  6), "def")  // true
+        | includes( alphabet( 0,  5), "def")  // false
+        | includes( alphabet( 0,  5), "de" ); // true
+
+    sout
+        | startsWith( alphabet        , "abc")  // true
+        | startsWith( alphabet( 0, 26), "abc")  // true
+        | startsWith( alphabet( 1, 26), "abc")  // false
+        | startsWith( alphabet( 1, 26),  "bc")  // true
+        | startsWith( alphabet( 0, 26), "abc")  // true
+        | startsWith( alphabet( 0,  4), "abc")  // true
+        | startsWith( alphabet( 0,  3), "abc")  // true
+        | startsWith( alphabet( 0,  3), "ab" )  // true
+        | startsWith( alphabet        , "xyz"); // false
+
+    sout
+        | endsWith( alphabet        , "xyz")  // true
+        | endsWith( alphabet        , "xyzz") // false
+        | endsWith( alphabet( 0, 26), "xyz")  // true
+        | endsWith( alphabet( 0, 25), "xyz")  // false
+        | endsWith( alphabet( 0, 25), "xy" )  // true
+        | endsWith( alphabet( 0, 26), "xyz")  // true
+        | endsWith( alphabet(23, 26), "xyz")  // true
+        | endsWith( alphabet(24, 26), "xyz")  // false
+        | endsWith( alphabet(24, 26),  "yz")  // true
+        | endsWith( alphabet        , "abc"); // false
+
+    charclass cc_cba = {"cba"};
+    charclass cc_onml = {"onml"};
+    charclass cc_alphabet = {alphabet};
+
+    // include (rest of the) numbers:  tell me where the numbers stop
+    // exclude (until)       numbers:  tell me where the numbers start (include rest of the non-numbers)
+
+    sout
+        | include( alphabet, cc_cba )  // 3
+        | exclude( alphabet, cc_cba )  // 0
+        | include( alphabet, cc_onml )  // 0
+        | exclude( alphabet, cc_onml )  // 11
+        | include( alphabet, cc_alphabet )  // 26
+        | exclude( alphabet, cc_alphabet ); // 0
+}
+
