Added string classes

This commit is contained in:
Phil Nash
2017-06-29 11:18:14 +01:00
parent cb0a5194af
commit 78e7994435
13 changed files with 908 additions and 0 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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