From 78e799443531a84c5f337109d462af53249ed49c Mon Sep 17 00:00:00 2001
From: Phil Nash <github@philnash.me>
Date: Thu, 29 Jun 2017 11:18:14 +0100
Subject: [PATCH] Added string classes

---
 CMakeLists.txt                            |  13 ++
 include/internal/catch_impl.hpp           |   9 ++
 include/internal/catch_string.cpp         |  83 ++++++++++++
 include/internal/catch_string.h           |  48 +++++++
 include/internal/catch_stringbuilder.cpp  | 111 ++++++++++++++++
 include/internal/catch_stringbuilder.h    |  72 +++++++++++
 include/internal/catch_stringdata.cpp     |  49 +++++++
 include/internal/catch_stringdata.h       |  52 ++++++++
 include/internal/catch_stringref.cpp      | 140 ++++++++++++++++++++
 include/internal/catch_stringref.h        |  84 ++++++++++++
 projects/SelfTest/String.tests.cpp        |  20 +++
 projects/SelfTest/StringBuilder.tests.cpp |  76 +++++++++++
 projects/SelfTest/StringRef.tests.cpp     | 151 ++++++++++++++++++++++
 13 files changed, 908 insertions(+)
 create mode 100644 include/internal/catch_string.cpp
 create mode 100644 include/internal/catch_string.h
 create mode 100644 include/internal/catch_stringbuilder.cpp
 create mode 100644 include/internal/catch_stringbuilder.h
 create mode 100644 include/internal/catch_stringdata.cpp
 create mode 100644 include/internal/catch_stringdata.h
 create mode 100644 include/internal/catch_stringref.cpp
 create mode 100644 include/internal/catch_stringref.h
 create mode 100644 projects/SelfTest/String.tests.cpp
 create mode 100644 projects/SelfTest/StringBuilder.tests.cpp
 create mode 100644 projects/SelfTest/StringRef.tests.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index be37e4d7..ed863deb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,6 +75,9 @@ set(TEST_SOURCES
         ${SELF_TEST_DIR}/TrickyTests.cpp
         ${SELF_TEST_DIR}/VariadicMacrosTests.cpp
         ${SELF_TEST_DIR}/MatchersTests.cpp
+        ${SELF_TEST_DIR}/String.tests.cpp
+        ${SELF_TEST_DIR}/StringBuilder.tests.cpp
+        ${SELF_TEST_DIR}/StringRef.tests.cpp
         )
 CheckFileList(TEST_SOURCES ${SELF_TEST_DIR})
 
@@ -181,6 +184,14 @@ set(INTERNAL_HEADERS
         ${HEADER_DIR}/internal/catch_stream.h
         ${HEADER_DIR}/internal/catch_stream.hpp
         ${HEADER_DIR}/internal/catch_streambuf.h
+        ${HEADER_DIR}/internal/catch_string.cpp
+        ${HEADER_DIR}/internal/catch_string.h
+        ${HEADER_DIR}/internal/catch_stringbuilder.cpp
+        ${HEADER_DIR}/internal/catch_stringbuilder.h
+        ${HEADER_DIR}/internal/catch_stringdata.cpp
+        ${HEADER_DIR}/internal/catch_stringdata.h
+        ${HEADER_DIR}/internal/catch_stringref.cpp
+        ${HEADER_DIR}/internal/catch_stringref.h
         ${HEADER_DIR}/internal/catch_suppress_warnings.h
         ${HEADER_DIR}/internal/catch_tag_alias.h
         ${HEADER_DIR}/internal/catch_tag_alias_registry.h
@@ -243,6 +254,8 @@ SOURCE_GROUP("Benchmarks" FILES ${BENCH_SOURCES})
 # configure the executable
 include_directories(${HEADER_DIR})
 
+add_definitions( -DCATCH_CONFIG_FULL_PROJECT )
+
 # Projects consuming Catch via ExternalProject_Add might want to use install step
 # without building all of our selftests.
 if (NOT NO_SELFTEST)
diff --git a/include/internal/catch_impl.hpp b/include/internal/catch_impl.hpp
index 4843dad6..2306af7f 100644
--- a/include/internal/catch_impl.hpp
+++ b/include/internal/catch_impl.hpp
@@ -37,6 +37,15 @@
 #include "catch_matchers_string.hpp"
 #include "catch_startup_exception_registry.hpp"
 
+// These files are not included in the full (not single include) project
+// as they are compiled as proper cpp files
+#ifndef CATCH_CONFIG_FULL_PROJECT
+#   include "catch_stringref.cpp"
+#   include "catch_string.cpp"
+#   include "catch_stringbuilder.cpp"
+#   include "catch_stringdata.cpp"
+#endif
+
 #include "../reporters/catch_reporter_multi.hpp"
 #include "../reporters/catch_reporter_xml.hpp"
 #include "../reporters/catch_reporter_junit.hpp"
diff --git a/include/internal/catch_string.cpp b/include/internal/catch_string.cpp
new file mode 100644
index 00000000..f7cc53a5
--- /dev/null
+++ b/include/internal/catch_string.cpp
@@ -0,0 +1,83 @@
+/*
+ *  Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+#include "catch_string.h"
+#include "catch_stringref.h"
+#include "catch_stringbuilder.h"
+
+#include "catch_stringdata.h"
+
+#include <ostream>
+
+namespace Catch {
+    
+    String::String()
+    : m_data( StringData::getEmpty() )
+    {}
+    
+    String::String( StringRef const& stringRef )
+    : m_data( StringData::create( stringRef ) )
+    {}
+    
+    String::String( char const* rawString )
+    : String( StringRef( rawString ) )
+    {}
+
+    String::String( String const& other )
+    : m_data( other.m_data )
+    {
+        m_data->addRef();
+    }
+    String::String( String&& other )
+    : m_data( other.m_data )
+    {
+        other.m_data = StringData::getEmpty();
+    }
+    String::String( StringBuilder&& stringBuf )
+    : m_data( stringBuf.m_data )
+    {
+        // const_cast is ok here because we are taking ownership
+        const_cast<StringData*>( m_data )->size = stringBuf.size();
+        stringBuf.m_data = StringData::getEmpty();
+        stringBuf.m_size = 0;
+    }
+
+    
+    String::~String() noexcept {
+        m_data->release();
+    }
+    
+    auto String::operator = ( String const& other ) -> String& {
+        m_data = other.m_data;
+        m_data->addRef();
+        return *this;
+    }
+    
+    
+    auto String::empty() const noexcept -> bool {
+        return m_data->size == 0;
+    }
+    auto String::size() const noexcept -> size_type {
+        return m_data->size;
+    }
+    auto String::c_str() const noexcept -> char const* {
+        return m_data->chars;
+    }
+    
+    auto String::operator == ( StringRef const& other ) const noexcept -> bool {
+        return other == StringRef( *this );
+    }
+    auto String::operator == ( char const* other ) const noexcept -> bool {
+        return StringRef( other ) == StringRef( *this );
+    }
+    
+    std::ostream& operator << ( std::ostream& os, String const& str ) {
+        os << str.c_str();
+        return os;
+    }
+    
+} // namespace Catch
diff --git a/include/internal/catch_string.h b/include/internal/catch_string.h
new file mode 100644
index 00000000..b9d310c9
--- /dev/null
+++ b/include/internal/catch_string.h
@@ -0,0 +1,48 @@
+/*
+ *  Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef CATCH_STRING_H_INCLUDED
+#define CATCH_STRING_H_INCLUDED
+
+namespace Catch {
+    
+    class StringData;
+    class StringRef;
+    class StringBuilder;
+
+    /// An owning, ref-counted, immutable string type.
+    /// The ref count (not visible here as it is defined in StringData) is atomic
+    /// so instances should be safe to share across threads
+    class String {
+        friend class StringRef;
+        friend class StringBuilder;
+
+        StringData const* m_data = nullptr;
+    public:
+        using size_type = unsigned long;
+        
+        String();
+        String( StringRef const& stringRef );
+        String( char const* rawString );
+        String( String const& other );
+        String( String&& other );
+        String( StringBuilder&& stringBuf );
+        
+        ~String() noexcept;
+        
+        auto operator = ( String const& other ) -> String&;
+        
+        auto operator == ( StringRef const& other ) const noexcept -> bool;
+        auto operator == ( char const* other ) const noexcept -> bool;
+        
+        auto empty() const noexcept -> bool;
+        auto size() const noexcept -> size_type;
+        auto c_str() const noexcept -> char const*;
+    };
+    
+} // namespace Catch
+
+#endif // CATCH_STRING_H_INCLUDED
diff --git a/include/internal/catch_stringbuilder.cpp b/include/internal/catch_stringbuilder.cpp
new file mode 100644
index 00000000..80ba1bbd
--- /dev/null
+++ b/include/internal/catch_stringbuilder.cpp
@@ -0,0 +1,111 @@
+/*
+ *  Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+#include "catch_stringbuilder.h"
+#include "catch_stringref.h"
+#include "catch_stringdata.h"
+#include "catch_string.h"
+
+#include <cstring>
+#include <algorithm>
+#include <cassert>
+#include <stdexcept>
+
+namespace Catch {
+
+    static const StringBuilder::size_type s_minimumCapacity = 32;
+
+    StringBuilder::StringBuilder()
+    :   m_data( StringData::getEmpty() )
+    {}
+    
+    StringBuilder::StringBuilder( size_type initialCapacity )
+    :   m_size( 0 ),
+        m_data( StringData::create( StringRef(), initialCapacity ) )
+    {}
+    StringBuilder::StringBuilder( StringRef const& str, size_type initialCapacity )
+    :   m_size( str.size() ),
+        m_data( StringData::create( str, initialCapacity ) )
+    {}
+    StringBuilder::StringBuilder( StringBuilder const& other, size_type initialCapacity )
+    :   StringBuilder( StringRef( other.m_data->chars, other.m_size ), initialCapacity )
+    {}
+    StringBuilder::StringBuilder( StringBuilder&& other ) noexcept
+    :   StringBuilder()
+    {
+        swap( other );
+    }
+    StringBuilder::StringBuilder( String&& str )
+    :   m_size( str.size() ),
+        m_data( StringData::getEmpty() )
+    {
+        if( str.m_data->isUniquelyOwned() )
+        {
+            std::swap( m_data, const_cast<StringData*&>( str.m_data ) );
+        }
+        else
+        {
+            size_type initialCapacity = std::min( s_minimumCapacity, m_size );
+            m_data = StringData::create( str, initialCapacity );
+        }
+    }
+    StringBuilder::StringBuilder( String const& other )
+    : StringBuilder( StringRef( other ), std::min( s_minimumCapacity, other.size() ) )
+    {}
+
+    StringBuilder::~StringBuilder() noexcept {
+        m_data->release();
+    }
+
+    auto StringBuilder::size() const noexcept -> size_type {
+        return m_size;
+    }
+    
+    void StringBuilder::swap( StringBuilder& other ) noexcept {
+        std::swap( m_size, other.m_size );
+        std::swap( m_data, other.m_data );
+    }
+    void StringBuilder::reserve( size_type minimumCapacity ) {
+        if( minimumCapacity > capacity() ) {
+            StringBuilder temp( *this, minimumCapacity );
+            swap( temp );
+        }
+    }
+    void StringBuilder::reserveExponential( size_type minimumCapacity ) {
+        if( minimumCapacity > capacity() ) {
+            size_type candidateCapacity = capacity() < s_minimumCapacity ? s_minimumCapacity : capacity()*2;
+            while( candidateCapacity < minimumCapacity )
+                candidateCapacity = candidateCapacity * 3/2; // grow factor of 1.5
+            StringBuilder temp( *this, candidateCapacity );
+            swap( temp );
+        }
+    }
+    auto StringBuilder::capacity() const noexcept -> size_type {
+        return m_data->size;
+    }
+    void StringBuilder::writeTo( size_type index, StringRef const& str ) {
+        assert( index + str.size() < capacity() );
+        if( str.size() > 0 )
+            std::memcpy( m_data->chars+index, str.data(), str.size() );
+    }
+    void StringBuilder::append( StringRef const& str ) {
+        reserveExponential( m_size + str.size() + 1 );
+        writeTo( m_size, str );
+        m_size += str.size();
+        m_data->chars[m_size] = '\0';
+    }
+
+    auto operator << ( StringBuilder& sb, StringRef sr ) -> StringBuilder& {
+        sb.append( sr );
+        return sb;
+    }
+    auto operator << ( StringBuilder&& sb, StringRef sr ) -> StringBuilder&& {
+        sb.append( sr );
+        return std::move( sb );
+    }
+    
+} // namespace Catch
diff --git a/include/internal/catch_stringbuilder.h b/include/internal/catch_stringbuilder.h
new file mode 100644
index 00000000..c3d99d81
--- /dev/null
+++ b/include/internal/catch_stringbuilder.h
@@ -0,0 +1,72 @@
+/*
+ *  Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef CATCH_STRINGBUILDER_H_INCLUDED
+#define CATCH_STRINGBUILDER_H_INCLUDED
+
+#include "catch_stringref.h"
+
+namespace Catch {
+
+    class String;
+    class StringData;
+
+    /// A mutable container for string data
+    /// Use to build up strings before transferring to an immutable String.
+    /// Construct the String using the rvalue reference constructor (which
+    /// will usually involve std::move-ing the StringBuilder). This will transfer
+    /// The underlying buffer without any extra allocations or ref counts.
+    class StringBuilder {
+        friend class String;
+    public:
+        using size_type = unsigned long;
+        
+        StringBuilder();
+        StringBuilder( size_type initialCapacity );
+        StringBuilder( StringRef const& str, size_type initialCapacity );
+        StringBuilder( StringBuilder const& other, size_type initialCapacity );
+        StringBuilder( StringBuilder&& other ) noexcept;
+        StringBuilder( String&& other );
+        StringBuilder( String const& other );
+        ~StringBuilder() noexcept;
+
+        void swap( StringBuilder& other ) noexcept;
+
+        auto size() const noexcept -> size_type;
+        auto capacity() const noexcept -> size_type;
+
+        /// Grows the buffer to exactly the capacity requested, or
+        /// does nothing if it is already at least as big
+        void reserve(size_type capacity);
+
+        /// Grows the buffer exponentially (from a baseline of 32 bytes)
+        /// until it is at least as large as the requested capacity -
+        /// or does nothing if already large enough
+        void reserveExponential(size_type capacity);
+
+        /// Writes the string at the current insertion point then moves
+        /// the insertion point forward by the string length.
+        /// If the buffer needs to grow to accomodate the string it does so
+        /// using the exponential strategy
+        void append( StringRef const& str );
+        
+        friend auto operator << ( StringBuilder& sb, StringRef sr ) -> StringBuilder&;
+        friend auto operator << ( StringBuilder&& sb, StringRef sr ) -> StringBuilder&&;
+
+        /// Writes the contents of the string ref into the buffer at
+        /// the indexed location.
+        /// The bounds are not checked! Use append() to just add to the
+        /// end of the buffer, extending it if the capacity is not enough.
+        void writeTo( size_type index, StringRef const& str );
+
+    private:
+        size_type m_size = 0;
+        StringData* m_data;
+    };
+    
+} // namespace Catch
+
+#endif // CATCH_STRINGBUILDER_H_INCLUDED
diff --git a/include/internal/catch_stringdata.cpp b/include/internal/catch_stringdata.cpp
new file mode 100644
index 00000000..9f1625a8
--- /dev/null
+++ b/include/internal/catch_stringdata.cpp
@@ -0,0 +1,49 @@
+/*
+ *  Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+
+#include "catch_stringdata.h"
+#include "catch_stringref.h"
+
+#include <new>
+#include <cstring>
+#include <cassert>
+
+namespace Catch {
+    
+    auto StringData::getEmpty() -> StringData* {
+        static StringData s_empty( 0 );
+        return &s_empty;
+    }
+    auto StringData::create( StringRef const& stringRef ) -> StringData* {
+        return create( stringRef, stringRef.size() );
+    }
+    auto StringData::create( StringRef const& stringRef, unsigned long capacity ) -> StringData* {
+        if( capacity == 0 ) {
+            return getEmpty();
+        }
+        else {
+            assert( stringRef.size() <= capacity );
+            auto bufferLen = sizeof(StringData)+capacity;
+            void* buffer = new char[bufferLen];
+            
+            return new(buffer) StringData( stringRef, capacity );
+        }
+    }
+    StringData::StringData( unsigned int initialRef )
+    :   m_refs( initialRef ),
+        size( 0 )
+    {}
+    StringData::StringData( StringRef const& stringRef, unsigned long capacity )
+    :   m_refs( 1 ),
+        size( capacity)
+    {
+        std::memcpy( chars, stringRef.data(), stringRef.size() );
+        chars[stringRef.size() ] = 0;
+    }
+    
+} // namespace Catch
\ No newline at end of file
diff --git a/include/internal/catch_stringdata.h b/include/internal/catch_stringdata.h
new file mode 100644
index 00000000..5ffafad7
--- /dev/null
+++ b/include/internal/catch_stringdata.h
@@ -0,0 +1,52 @@
+/*
+ *  Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef CATCH_STRINGDATA_H_INCLUDED
+#define CATCH_STRINGDATA_H_INCLUDED
+
+#include <atomic>
+
+namespace Catch {
+    
+    class StringRef;
+
+    class StringData {
+        mutable std::atomic<unsigned int> m_refs;
+    public:
+        unsigned int size;
+        union {
+            char chars[1];
+        };
+
+        auto isUniquelyOwned() const noexcept -> bool {
+            return m_refs == 1;
+        }
+        static auto getEmpty() -> StringData*;
+        static auto create( StringRef const& stringRef ) -> StringData*;
+        static auto create( StringRef const& stringRef, unsigned long capacity ) -> StringData*;
+
+        void addRef() const noexcept {
+            if( m_refs > 0 )
+                m_refs++;
+        }
+        void release() const noexcept {
+            unsigned int refs = m_refs;
+            if( refs > 1 )
+                m_refs--;
+            else if( refs == 1 )
+                delete[] reinterpret_cast<char const*>( this );
+        }
+    private:
+        StringData( unsigned int initialRef = 1 );
+        StringData( StringRef const& stringRef, unsigned long capacity );
+        
+        StringData( StringData const& ) = delete;
+        StringData& operator=( StringData const& ) = delete;
+    };
+
+} // namespace Catch
+
+#endif // CATCH_STRINGDATA_H_INCLUDED
diff --git a/include/internal/catch_stringref.cpp b/include/internal/catch_stringref.cpp
new file mode 100644
index 00000000..d577d3af
--- /dev/null
+++ b/include/internal/catch_stringref.cpp
@@ -0,0 +1,140 @@
+/*
+ *  Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+#include "catch_stringref.h"
+#include "catch_stringbuilder.h"
+#include "catch_string.h"
+#include "catch_stringdata.h"
+
+#include <cstring>
+#include <ostream>
+
+namespace Catch {
+    
+    StringRef StringRef::s_emptyStringRef = "";
+    
+    StringRef::StringRef()
+    :   StringRef( s_emptyStringRef )
+    {}
+    
+    StringRef::StringRef( StringRef const& other )
+    :   m_start( other.m_start ),
+        m_size( other.m_size ),
+        m_data( other.m_data )
+    {
+        if( m_data )
+            m_data->addRef();
+    }
+    
+    StringRef::StringRef( StringRef&& other ) noexcept
+    :   m_start( other.m_start ),
+        m_size( other.m_size ),
+        m_data( other.m_data )
+    {
+        other.m_data = nullptr;
+    }
+    
+    StringRef::StringRef( char const* rawChars )
+    :   m_start( rawChars ),
+        m_size( std::strlen( rawChars ) )
+    {}
+    
+    StringRef::StringRef( char const* rawChars, size_type size )
+    :   m_start( rawChars ),
+        m_size( size )
+    {
+        size_type rawSize = rawChars == nullptr ? 0 : std::strlen( rawChars );
+        if( rawSize < size )
+            size = rawSize;
+    }
+    
+    StringRef::StringRef( String const& other )
+    :   m_start( other.c_str() ),
+        m_size( other.size() ),
+        m_data( nullptr )
+    {}
+    
+    StringRef::StringRef( String&& str ) noexcept
+    :   m_start( str.c_str() ),
+        m_size( str.size() ),
+        m_data( str.m_data )
+    {
+        str.m_data = StringData::getEmpty();
+    }
+    
+    StringRef::~StringRef() noexcept {
+        if( isOwned() )
+            m_data->release();
+    }
+    
+    auto StringRef::operator = ( StringRef other ) -> StringRef& {
+        swap( other );
+        return *this;
+    }
+
+    void StringRef::swap( StringRef& other ) noexcept {
+        std::swap( m_start, other.m_start );
+        std::swap( m_size, other.m_size );
+        std::swap( m_data, other.m_data );
+    }
+    
+    auto StringRef::c_str() const -> char const* {
+        if( isSubstring() )
+           const_cast<StringRef*>( this )->takeOwnership();
+        return m_start;
+    }
+    auto StringRef::data() const noexcept -> char const* {
+        return m_start;
+    }
+
+    auto StringRef::isOwned() const noexcept -> bool {
+        return m_data != nullptr;
+    }
+    auto StringRef::isSubstring() const noexcept -> bool {
+        return m_start[m_size] != '\0';
+    }
+    
+    void StringRef::takeOwnership() {
+        if( !isOwned() ) {
+            StringRef temp = String( *this );
+            swap( temp );
+        }        
+    }
+    auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef {
+        if( start < m_size )
+            return StringRef( m_start+start, size );
+        else
+            return StringRef();
+    }
+    auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool {
+        return
+            size() == other.size() &&
+            (std::strncmp( m_start, other.m_start, size() ) == 0);
+    }
+    auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool {
+        return !operator==( other );
+    }
+        
+    auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> String {
+        StringBuilder buf;
+        buf.reserve( lhs.size() + rhs.size() );
+        buf.append( lhs );
+        buf.append( rhs );
+        return String( std::move( buf ) );
+    }
+    auto operator + ( StringRef const& lhs, const char* rhs ) -> String {
+        return lhs + StringRef( rhs );
+    }
+    auto operator + ( char const* lhs, StringRef const& rhs ) -> String {
+        return StringRef( lhs ) + rhs;
+    }
+
+    std::ostream& operator << ( std::ostream& os, StringRef const& str ) {
+        return os << str.c_str();
+    }
+        
+} // namespace Catch
diff --git a/include/internal/catch_stringref.h b/include/internal/catch_stringref.h
new file mode 100644
index 00000000..a0d8200b
--- /dev/null
+++ b/include/internal/catch_stringref.h
@@ -0,0 +1,84 @@
+/*
+ *  Copyright 2016 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef CATCH_STRINGREF_H_INCLUDED
+#define CATCH_STRINGREF_H_INCLUDED
+
+namespace Catch {
+    
+    class String;
+    class StringData;
+
+    /// A non-owning string class (similar to the forthcoming std::string_view)
+    /// Note that, because a StringRef may be a substring of another string,
+    /// it may not be null terminated. c_str() must return a null terminated
+    /// string, however, and so the StringRef will internally take ownership
+    /// (taking a copy), if necessary. In theory this ownership is not externally
+    /// visible - but it does mean (substring) StringRefs should not be shared between
+    /// threads.
+    class StringRef {
+        friend struct StringRefTestAccess;
+        friend class StringData;
+        friend class StringBuilder;
+
+        using size_type = unsigned long;
+        
+        char const* m_start;
+        size_type m_size;
+        
+        StringData const* m_data = nullptr;
+        
+        static StringRef s_emptyStringRef;
+        
+        void takeOwnership();
+        
+    public: // construction/ assignment
+        StringRef();
+        StringRef( StringRef const& other );
+        StringRef( StringRef&& other ) noexcept;
+        StringRef( char const* rawChars );
+        StringRef( char const* rawChars, size_type size );
+        StringRef( String const& other );
+        StringRef( String&& other ) noexcept;
+        ~StringRef() noexcept;
+        
+        auto operator = ( StringRef other ) -> StringRef&;
+
+        void swap( StringRef& other ) noexcept;
+        
+    public: // operators
+        auto operator == ( StringRef const& other ) const noexcept -> bool;
+        auto operator != ( StringRef const& other ) const noexcept -> bool;
+        
+        auto operator[] ( size_type index ) const noexcept -> char {
+            return m_start[index];
+        }
+        
+    public: // named queries
+        auto empty() const noexcept -> bool {
+            return m_size == 0;
+        }
+        auto size() const noexcept -> size_type {
+            return m_size;
+        }
+        auto c_str() const -> char const*;
+        
+    public: // substrings and searches
+        auto substr( size_type start, size_type size ) const noexcept -> StringRef;
+
+    private: // ownership queries - may not be consistent between calls
+        auto isOwned() const noexcept -> bool;
+        auto isSubstring() const noexcept -> bool;
+        auto data() const noexcept -> char const*;
+    };
+
+    auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> String;
+    auto operator + ( StringRef const& lhs, char const* rhs ) -> String;
+    auto operator + ( char const* lhs, StringRef const& rhs ) -> String;
+    
+} // namespace Catch
+
+#endif // CATCH_STRINGREF_H_INCLUDED
diff --git a/projects/SelfTest/String.tests.cpp b/projects/SelfTest/String.tests.cpp
new file mode 100644
index 00000000..ccd90964
--- /dev/null
+++ b/projects/SelfTest/String.tests.cpp
@@ -0,0 +1,20 @@
+#include "../include/internal/catch_string.h"
+
+#include "catch.hpp"
+
+TEST_CASE( "String", "[Strings]" ) {
+    using Catch::String;
+    
+    SECTION( "empty string" ) {
+        String empty;
+        REQUIRE( empty.empty() );
+        REQUIRE( empty.size() == 0 );
+        REQUIRE( std::strcmp( empty.c_str(), "" ) == 0 );
+    }
+    SECTION( "from literal" ) {
+        String s = "hello";
+        REQUIRE( s.empty() == false );
+        REQUIRE( s.size() == 5 );
+    }
+    
+}
\ No newline at end of file
diff --git a/projects/SelfTest/StringBuilder.tests.cpp b/projects/SelfTest/StringBuilder.tests.cpp
new file mode 100644
index 00000000..e91d450d
--- /dev/null
+++ b/projects/SelfTest/StringBuilder.tests.cpp
@@ -0,0 +1,76 @@
+#include "internal/catch_stringbuilder.h"
+#include "../include/internal/catch_stringref.h"
+#include "../include/internal/catch_string.h"
+
+#include "catch.hpp"
+
+TEST_CASE( "StringBuilder", "[Strings]" ) {
+    
+    using Catch::StringBuilder;
+    using Catch::String;
+    
+    StringBuilder sb;
+    
+    SECTION( "basic" ) {
+        REQUIRE( sb.capacity() == 0 );
+        REQUIRE( sb.size() == 0 );
+
+        sb.reserve( 32 );
+        REQUIRE( sb.capacity() == 32 );
+        REQUIRE( sb.size() == 0 );
+        
+        sb.append( "hello" );
+        REQUIRE( sb.capacity() == 32 );
+        REQUIRE( sb.size() == 5 );
+        
+        String s = std::move( sb );
+        REQUIRE( s == "hello" );
+        REQUIRE( s.size() == 5 );
+    }
+    
+    SECTION( "concatenation" ) {
+        sb << "hello" << " " << "world";
+        String s = std::move( sb );
+        REQUIRE( s == "hello world" );
+    }
+
+    SECTION( "concat & move" ) {
+        String s = StringBuilder() << "hello" << " " << "world";
+        REQUIRE( s == "hello world" );
+    }
+
+    SECTION( "reserved" ) {
+        StringBuilder sb16( 16 );
+        REQUIRE( sb16.capacity() == 16 );
+        sb16 << "hello" << " " << "world";
+        REQUIRE( sb16.capacity() == 16 );
+        String s = std::move( sb16 );
+        REQUIRE( s == "hello world" );
+    }
+
+    SECTION( "from String" ) {
+        String s = "hello";
+
+        SECTION( "copy" ) {
+            StringBuilder sb2 = s;
+            String s2( std::move(sb2) );
+            REQUIRE( s2 == s );
+            REQUIRE( s2.c_str() != s.c_str() );
+        }
+        SECTION( "move from uniquely owned string" ) {
+            auto originalPointer = s.c_str();
+            StringBuilder sb2( std::move( s ) );
+            String s2( std::move(sb2) );
+            REQUIRE( s2 == "hello" );
+            REQUIRE( s2.c_str() == originalPointer );
+        }
+        SECTION( "move from shared string (copies)" ) {
+            auto originalPointer = s.c_str();
+            String keepAlive = s;
+            StringBuilder sb2( std::move( s ) );
+            String s2( std::move(sb2) );
+            REQUIRE( s2 == "hello" );
+            REQUIRE( s2.c_str() != originalPointer );
+        }
+    }
+}
diff --git a/projects/SelfTest/StringRef.tests.cpp b/projects/SelfTest/StringRef.tests.cpp
new file mode 100644
index 00000000..e5da3f2a
--- /dev/null
+++ b/projects/SelfTest/StringRef.tests.cpp
@@ -0,0 +1,151 @@
+#include "../include/internal/catch_stringref.h"
+#include "../include/internal/catch_string.h"
+
+#include "catch.hpp"
+
+namespace Catch {
+
+    // Implementation of test accessors
+    struct StringRefTestAccess {
+        static auto isOwned( StringRef const& stringRef ) -> bool {
+            return stringRef.isOwned();
+        }
+        static auto isSubstring( StringRef const& stringRef ) -> bool {
+            return stringRef.isSubstring();
+        }
+        static auto data( StringRef const& stringRef ) -> char const* {
+            return stringRef.data();
+        }
+    };
+
+    auto isOwned( StringRef const& stringRef ) -> bool {
+        return StringRefTestAccess::isOwned( stringRef );
+    }
+    auto isSubstring( StringRef const& stringRef ) -> bool {
+        return StringRefTestAccess::isSubstring( stringRef );
+    }
+    auto data( StringRef const& stringRef ) -> char const* {
+        return StringRefTestAccess::data( stringRef );
+    }
+} // namespace Catch2
+
+namespace Catch {
+    inline auto toString( Catch::StringRef const& stringRef ) -> std::string {
+        return std::string( data( stringRef ), stringRef.size() );
+    }
+} // namespace Catch
+
+TEST_CASE( "StringRef", "[Strings]" ) {
+    
+    using Catch::StringRef;
+    using Catch::String;
+    
+    SECTION( "Empty string" ) {
+        StringRef empty;
+        REQUIRE( empty.empty() );
+        REQUIRE( empty.size() == 0 );
+        REQUIRE( std::strcmp( empty.c_str(), "" ) == 0 );
+    }
+    
+    SECTION( "From string literal" ) {
+        StringRef s = "hello";
+        REQUIRE( s.empty() == false );
+        REQUIRE( s.size() == 5 );
+        REQUIRE( isSubstring( s ) == false );
+        
+        auto rawChars = data( s );
+        REQUIRE( std::strcmp( rawChars, "hello" ) == 0 );
+        
+        SECTION( "c_str() does not cause copy" ) {
+            REQUIRE( isOwned( s ) == false );
+            
+            REQUIRE( s.c_str() == rawChars );
+            
+            REQUIRE( isOwned( s ) == false );
+        }
+    }
+    SECTION( "From sub-string" ) {
+        StringRef original = StringRef( "original string" ).substr(0, 8);
+        REQUIRE( original == "original" );
+        REQUIRE( isSubstring( original ) );
+        REQUIRE( isOwned( original ) == false );
+        
+        original.c_str(); // Forces it to take ownership
+        
+        REQUIRE( isSubstring( original ) == false );
+        REQUIRE( isOwned( original ) );
+        
+    }
+    
+    
+    SECTION( "Substrings" ) {
+        StringRef s = "hello world!";
+        StringRef ss = s.substr(0, 5);
+        
+        SECTION( "zero-based substring" ) {
+            REQUIRE( ss.empty() == false );
+            REQUIRE( ss.size() == 5 );
+            REQUIRE( std::strcmp( ss.c_str(), "hello" ) == 0 );
+            REQUIRE( ss == "hello" );
+        }
+        SECTION( "c_str() causes copy" ) {
+            REQUIRE( isSubstring( ss ) );
+            REQUIRE( isOwned( ss ) == false );
+            
+            auto rawChars = data( ss );
+            REQUIRE( rawChars == data( s ) ); // same pointer value
+            REQUIRE( ss.c_str() != rawChars );
+            
+            REQUIRE( isSubstring( ss ) == false );
+            REQUIRE( isOwned( ss ) );
+            
+            REQUIRE( data( ss ) != data( s ) ); // different pointer value
+        }
+        
+        SECTION( "non-zero-based substring") {
+            ss = s.substr( 6, 6 );
+            REQUIRE( ss.size() == 6 );
+            REQUIRE( std::strcmp( ss.c_str(), "world!" ) == 0 );
+        }
+        
+        SECTION( "Pointer values of full refs should match" ) {
+            StringRef s2 = s;
+            REQUIRE( s.c_str() == s2.c_str() );
+        }
+        
+        SECTION( "Pointer values of substring refs should not match" ) {
+            REQUIRE( s.c_str() != ss.c_str() );
+        }
+    }
+    
+    SECTION( "Comparisons" ) {
+        REQUIRE( StringRef("hello") == StringRef("hello") );
+        REQUIRE( StringRef("hello") != StringRef("cello") );
+    }
+    
+    SECTION( "From string" ) {
+        String str = "hot potato";
+        auto originalPointer = str.c_str();
+        
+        SECTION( "Copied" ) {
+            // After a String is "copied" to a StringRef
+            // It has only copied the pointer and size
+            // - it does not take ownership
+            StringRef copied = str;
+            REQUIRE( copied == "hot potato" );
+            REQUIRE( str == "hot potato" );
+            REQUIRE( isOwned( copied ) == false );
+            REQUIRE( data( copied ) == originalPointer );
+        }
+        SECTION( "Moved" ) {
+            // After a String is *moved* to a StringRef
+            // The StringRef takes ownership of the underlying data
+            // and the String is left in an empty state
+            StringRef copied = std::move( str );
+            REQUIRE( copied == "hot potato" );
+            REQUIRE( isOwned( copied ) );
+            REQUIRE( str.empty() );
+            REQUIRE( data( copied ) == originalPointer );
+        }
+    }
+}