|
- /*
- ==============================================================================
-
- This file is part of the Water library.
- Copyright (c) 2016 ROLI Ltd.
- Copyright (C) 2017-2019 Filipe Coelho <falktx@falktx.com>
-
- Permission is granted to use this software under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license/
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
- TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
- OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
- USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
- OF THIS SOFTWARE.
-
- ==============================================================================
- */
-
- #include "String.h"
- #include "NewLine.h"
- #include "../maths/MathsFunctions.h"
- #include "../memory/HeapBlock.h"
- #include "../streams/OutputStream.h"
-
- #include "CarlaJuceUtils.hpp"
-
- #include <locale>
- #include <iostream>
-
- #ifdef CARLA_OS_MAC
- // FIXME
- // # import <CoreData/CoreData.h>
- #endif
-
- // #import <Carbon/UnicodeConverter.h>
-
- namespace water {
-
- //==============================================================================
- // (Mirrors the structure of StringHolder, but without the atomic member, so can be statically constructed)
- struct EmptyString
- {
- int refCount;
- size_t allocatedBytes;
- String::CharPointerType::CharType text;
- };
-
- static const EmptyString emptyString = { 0x3fffffff, sizeof (String::CharPointerType::CharType), '\0' };
-
- //==============================================================================
- class StringHolder
- {
- public:
- StringHolder() WATER_DELETED_FUNCTION;
-
- typedef String::CharPointerType CharPointerType;
- typedef String::CharPointerType::CharType CharType;
-
- //==============================================================================
- static CharPointerType createUninitialisedBytes (size_t numBytes)
- {
- numBytes = (numBytes + 3) & ~(size_t) 3;
- StringHolder* const s = reinterpret_cast<StringHolder*> (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]);
- s->refCount.value = 0;
- s->allocatedNumBytes = numBytes;
- return CharPointerType (s->text);
- }
-
- template <class CharPointer>
- static CharPointerType createFromCharPointer (const CharPointer text)
- {
- if (text.getAddress() == nullptr || text.isEmpty())
- return CharPointerType (&(emptyString.text));
-
- const size_t bytesNeeded = sizeof (CharType) + CharPointerType::getBytesRequiredFor (text);
- const CharPointerType dest (createUninitialisedBytes (bytesNeeded));
- CharPointerType (dest).writeAll (text);
- return dest;
- }
-
- template <class CharPointer>
- static CharPointerType createFromCharPointer (const CharPointer text, size_t maxChars)
- {
- if (text.getAddress() == nullptr || text.isEmpty() || maxChars == 0)
- return CharPointerType (&(emptyString.text));
-
- CharPointer end (text);
- size_t numChars = 0;
- size_t bytesNeeded = sizeof (CharType);
-
- while (numChars < maxChars && ! end.isEmpty())
- {
- bytesNeeded += CharPointerType::getBytesRequiredFor (end.getAndAdvance());
- ++numChars;
- }
-
- const CharPointerType dest (createUninitialisedBytes (bytesNeeded));
- CharPointerType (dest).writeWithCharLimit (text, (int) numChars + 1);
- return dest;
- }
-
- template <class CharPointer>
- static CharPointerType createFromCharPointer (const CharPointer start, const CharPointer end)
- {
- if (start.getAddress() == nullptr || start.isEmpty())
- return CharPointerType (&(emptyString.text));
-
- CharPointer e (start);
- int numChars = 0;
- size_t bytesNeeded = sizeof (CharType);
-
- while (e < end && ! e.isEmpty())
- {
- bytesNeeded += CharPointerType::getBytesRequiredFor (e.getAndAdvance());
- ++numChars;
- }
-
- const CharPointerType dest (createUninitialisedBytes (bytesNeeded));
- CharPointerType (dest).writeWithCharLimit (start, numChars + 1);
- return dest;
- }
-
- static CharPointerType createFromCharPointer (const CharPointerType start, const CharPointerType end)
- {
- if (start.getAddress() == nullptr || start.isEmpty())
- return CharPointerType (&(emptyString.text));
-
- const size_t numBytes = (size_t) (reinterpret_cast<const char*> (end.getAddress())
- - reinterpret_cast<const char*> (start.getAddress()));
- const CharPointerType dest (createUninitialisedBytes (numBytes + sizeof (CharType)));
- memcpy (dest.getAddress(), start, numBytes);
- dest.getAddress()[numBytes / sizeof (CharType)] = 0;
- return dest;
- }
-
- static CharPointerType createFromFixedLength (const char* const src, const size_t numChars)
- {
- const CharPointerType dest (createUninitialisedBytes (numChars * sizeof (CharType) + sizeof (CharType)));
- CharPointerType (dest).writeWithCharLimit (CharPointer_UTF8 (src), (int) (numChars + 1));
- return dest;
- }
-
- //==============================================================================
- static void retain (const CharPointerType text) noexcept
- {
- StringHolder* const b = bufferFromText (text);
-
- if (b != (StringHolder*) &emptyString)
- ++(b->refCount);
- }
-
- static inline void release (StringHolder* const b) noexcept
- {
- if (b != (StringHolder*) &emptyString)
- if (--(b->refCount) == -1)
- delete[] reinterpret_cast<char*> (b);
- }
-
- static void release (const CharPointerType text) noexcept
- {
- release (bufferFromText (text));
- }
-
- static inline int getReferenceCount (const CharPointerType text) noexcept
- {
- return bufferFromText (text)->refCount.get() + 1;
- }
-
- //==============================================================================
- static CharPointerType makeUniqueWithByteSize (const CharPointerType text, size_t numBytes)
- {
- StringHolder* const b = bufferFromText (text);
-
- if (b == (StringHolder*) &emptyString)
- {
- CharPointerType newText (createUninitialisedBytes (numBytes));
- newText.writeNull();
- return newText;
- }
-
- if (b->allocatedNumBytes >= numBytes && b->refCount.get() <= 0)
- return text;
-
- CharPointerType newText (createUninitialisedBytes (jmax (b->allocatedNumBytes, numBytes)));
- memcpy (newText.getAddress(), text.getAddress(), b->allocatedNumBytes);
- release (b);
-
- return newText;
- }
-
- static size_t getAllocatedNumBytes (const CharPointerType text) noexcept
- {
- return bufferFromText (text)->allocatedNumBytes;
- }
-
- //==============================================================================
- Atomic<int> refCount;
- size_t allocatedNumBytes;
- CharType text[1];
-
- private:
- static inline StringHolder* bufferFromText (const CharPointerType text) noexcept
- {
- // (Can't use offsetof() here because of warnings about this not being a POD)
- return reinterpret_cast<StringHolder*> (reinterpret_cast<char*> (text.getAddress())
- - (reinterpret_cast<size_t> (reinterpret_cast<StringHolder*> (1)->text) - 1));
- }
- };
-
- //==============================================================================
- String::String() noexcept : text (&(emptyString.text))
- {
- }
-
- String::~String() noexcept
- {
- StringHolder::release (text);
- }
-
- String::String (const String& other) noexcept : text (other.text)
- {
- StringHolder::retain (text);
- }
-
- void String::swapWith (String& other) noexcept
- {
- std::swap (text, other.text);
- }
-
- void String::clear() noexcept
- {
- StringHolder::release (text);
- text = &(emptyString.text);
- }
-
- String& String::operator= (const String& other) noexcept
- {
- StringHolder::retain (other.text);
- StringHolder::release (text.atomicSwap (other.text));
- return *this;
- }
-
- #if WATER_COMPILER_SUPPORTS_MOVE_SEMANTICS
- String::String (String&& other) noexcept : text (other.text)
- {
- other.text = &(emptyString.text);
- }
-
- String& String::operator= (String&& other) noexcept
- {
- std::swap (text, other.text);
- return *this;
- }
- #endif
-
- inline String::PreallocationBytes::PreallocationBytes (const size_t num) noexcept : numBytes (num) {}
-
- String::String (const PreallocationBytes& preallocationSize)
- : text (StringHolder::createUninitialisedBytes (preallocationSize.numBytes + sizeof (CharPointerType::CharType)))
- {
- }
-
- void String::preallocateBytes (const size_t numBytesNeeded)
- {
- text = StringHolder::makeUniqueWithByteSize (text, numBytesNeeded + sizeof (CharPointerType::CharType));
- }
-
- int String::getReferenceCount() const noexcept
- {
- return StringHolder::getReferenceCount (text);
- }
-
- //==============================================================================
- String::String (const char* const t)
- : text (StringHolder::createFromCharPointer (CharPointer_UTF8 (t)))
- {
- /* If you get an assertion here, then you're trying to create a string from 8-bit data
- that contains values greater than 127. These can NOT be correctly converted to unicode
- because there's no way for the String class to know what encoding was used to
- create them. The source data could be UTF-8, ASCII or one of many local code-pages.
-
- To get around this problem, you must be more explicit when you pass an ambiguous 8-bit
- string to the String class - so for example if your source data is actually UTF-8,
- you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to
- correctly convert the multi-byte characters to unicode. It's *highly* recommended that
- you use UTF-8 with escape characters in your source code to represent extended characters,
- because there's no other way to represent these strings in a way that isn't dependent on
- the compiler, source code editor and platform.
- */
- CARLA_SAFE_ASSERT (t == nullptr || CharPointer_UTF8::isValidString (t, std::numeric_limits<int>::max()));
- }
-
- String::String (const char* const t, const size_t maxChars)
- : text (StringHolder::createFromCharPointer (CharPointer_UTF8 (t), maxChars))
- {
- /* If you get an assertion here, then you're trying to create a string from 8-bit data
- that contains values greater than 127. These can NOT be correctly converted to unicode
- because there's no way for the String class to know what encoding was used to
- create them. The source data could be UTF-8, ASCII or one of many local code-pages.
-
- To get around this problem, you must be more explicit when you pass an ambiguous 8-bit
- string to the String class - so for example if your source data is actually UTF-8,
- you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to
- correctly convert the multi-byte characters to unicode. It's *highly* recommended that
- you use UTF-8 with escape characters in your source code to represent extended characters,
- because there's no other way to represent these strings in a way that isn't dependent on
- the compiler, source code editor and platform.
- */
- CARLA_SAFE_ASSERT (t == nullptr || CharPointer_UTF8::isValidString (t, (int) maxChars));
- }
-
- String::String (const CharPointer_UTF8 t) : text (StringHolder::createFromCharPointer (t)) {}
- String::String (const CharPointer_UTF8 t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {}
- String::String (const CharPointer_UTF8 start, const CharPointer_UTF8 end) : text (StringHolder::createFromCharPointer (start, end)) {}
-
- String::String (const std::string& s) : text (StringHolder::createFromFixedLength (s.data(), s.size())) {}
- String::String (StringRef s) : text (StringHolder::createFromCharPointer (s.text)) {}
-
- String String::charToString (const water_uchar character)
- {
- String result (PreallocationBytes (CharPointerType::getBytesRequiredFor (character)));
- CharPointerType t (result.text);
- t.write (character);
- t.writeNull();
- return result;
- }
-
- //==============================================================================
- namespace NumberToStringConverters
- {
- static char* numberToString (char* t, uint64 v) noexcept
- {
- return printDigits (t, v);
- }
-
- static char* numberToString (char* t, const int n) noexcept
- {
- if (n >= 0)
- return printDigits (t, static_cast<unsigned int> (n));
-
- // NB: this needs to be careful not to call -std::numeric_limits<int>::min(),
- // which has undefined behaviour
- t = printDigits (t, static_cast<unsigned int> (-(n + 1)) + 1);
- *--t = '-';
- return t;
- }
-
- static char* numberToString (char* t, const unsigned int v) noexcept
- {
- return printDigits (t, v);
- }
-
- static char* numberToString (char* t, const long n) noexcept
- {
- if (n >= 0)
- return printDigits (t, static_cast<unsigned long> (n));
-
- t = printDigits (t, static_cast<unsigned long> (-(n + 1)) + 1);
- *--t = '-';
- return t;
- }
-
- static char* numberToString (char* t, const unsigned long v) noexcept
- {
- return printDigits (t, v);
- }
-
- struct StackArrayStream : public std::basic_streambuf<char, std::char_traits<char> >
- {
- explicit StackArrayStream (char* d)
- {
- static const std::locale classicLocale (std::locale::classic());
- imbue (classicLocale);
- setp (d, d + charsNeededForDouble);
- }
-
- size_t writeDouble (double n, int numDecPlaces)
- {
- {
- std::ostream o (this);
-
- if (numDecPlaces > 0)
- o.precision ((std::streamsize) numDecPlaces);
-
- o << n;
- }
-
- return (size_t) (pptr() - pbase());
- }
- };
-
- static char* doubleToString (char* buffer, const int numChars, double n, int numDecPlaces, size_t& len) noexcept
- {
- if (numDecPlaces > 0 && numDecPlaces < 7 && n > -1.0e20 && n < 1.0e20)
- {
- char* const end = buffer + numChars;
- char* t = end;
- int64 v = (int64) (pow (10.0, numDecPlaces) * std::abs (n) + 0.5);
- *--t = (char) 0;
-
- while (numDecPlaces >= 0 || v > 0)
- {
- if (numDecPlaces == 0)
- *--t = '.';
-
- *--t = (char) ('0' + (v % 10));
-
- v /= 10;
- --numDecPlaces;
- }
-
- if (n < 0)
- *--t = '-';
-
- len = (size_t) (end - t - 1);
- return t;
- }
-
- StackArrayStream strm (buffer);
- len = strm.writeDouble (n, numDecPlaces);
- wassert (len <= charsNeededForDouble);
- return buffer;
- }
-
- template <typename IntegerType>
- static String::CharPointerType createFromInteger (const IntegerType number)
- {
- char buffer [charsNeededForInt];
- char* const end = buffer + numElementsInArray (buffer);
- char* const start = numberToString (end, number);
- return StringHolder::createFromFixedLength (start, (size_t) (end - start - 1));
- }
-
- static String::CharPointerType createFromDouble (const double number, const int numberOfDecimalPlaces)
- {
- char buffer [charsNeededForDouble];
- size_t len;
- char* const start = doubleToString (buffer, numElementsInArray (buffer), (double) number, numberOfDecimalPlaces, len);
- return StringHolder::createFromFixedLength (start, len);
- }
- }
-
- //==============================================================================
- String::String (const int number) : text (NumberToStringConverters::createFromInteger (number)) {}
- String::String (const unsigned int number) : text (NumberToStringConverters::createFromInteger (number)) {}
- String::String (const short number) : text (NumberToStringConverters::createFromInteger ((int) number)) {}
- String::String (const unsigned short number) : text (NumberToStringConverters::createFromInteger ((unsigned int) number)) {}
- String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {}
- String::String (const uint64 number) : text (NumberToStringConverters::createFromInteger (number)) {}
- String::String (const long number) : text (NumberToStringConverters::createFromInteger (number)) {}
- String::String (const unsigned long number) : text (NumberToStringConverters::createFromInteger (number)) {}
-
- String::String (const float number) : text (NumberToStringConverters::createFromDouble ((double) number, 0)) {}
- String::String (const double number) : text (NumberToStringConverters::createFromDouble (number, 0)) {}
- String::String (const float number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble ((double) number, numberOfDecimalPlaces)) {}
- String::String (const double number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble (number, numberOfDecimalPlaces)) {}
-
- //==============================================================================
- int String::length() const noexcept
- {
- return (int) text.length();
- }
-
- static size_t findByteOffsetOfEnd (String::CharPointerType text) noexcept
- {
- return (size_t) (((char*) text.findTerminatingNull().getAddress()) - (char*) text.getAddress());
- }
-
- size_t String::getByteOffsetOfEnd() const noexcept
- {
- return findByteOffsetOfEnd (text);
- }
-
- water_uchar String::operator[] (int index) const noexcept
- {
- wassert (index == 0 || (index > 0 && index <= (int) text.lengthUpTo ((size_t) index + 1)));
- return text [index];
- }
-
- template <typename Type>
- struct HashGenerator
- {
- template <typename CharPointer>
- static Type calculate (CharPointer t) noexcept
- {
- Type result = Type();
-
- while (! t.isEmpty())
- result = ((Type) multiplier) * result + (Type) t.getAndAdvance();
-
- return result;
- }
-
- enum { multiplier = sizeof (Type) > 4 ? 101 : 31 };
- };
-
- int String::hashCode() const noexcept { return HashGenerator<int> ::calculate (text); }
- int64 String::hashCode64() const noexcept { return HashGenerator<int64> ::calculate (text); }
- size_t String::hash() const noexcept { return HashGenerator<size_t> ::calculate (text); }
-
- //==============================================================================
- bool operator== (const String& s1, const String& s2) noexcept { return s1.compare (s2) == 0; }
- bool operator!= (const String& s1, const String& s2) noexcept { return s1.compare (s2) != 0; }
- bool operator== (const String& s1, const char* s2) noexcept { return s1.compare (s2) == 0; }
- bool operator!= (const String& s1, const char* s2) noexcept { return s1.compare (s2) != 0; }
- bool operator== (const String& s1, StringRef s2) noexcept { return s1.getCharPointer().compare (s2.text) == 0; }
- bool operator!= (const String& s1, StringRef s2) noexcept { return s1.getCharPointer().compare (s2.text) != 0; }
- bool operator== (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; }
- bool operator!= (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; }
- bool operator> (const String& s1, const String& s2) noexcept { return s1.compare (s2) > 0; }
- bool operator< (const String& s1, const String& s2) noexcept { return s1.compare (s2) < 0; }
- bool operator>= (const String& s1, const String& s2) noexcept { return s1.compare (s2) >= 0; }
- bool operator<= (const String& s1, const String& s2) noexcept { return s1.compare (s2) <= 0; }
-
- bool String::equalsIgnoreCase (const char* const t) const noexcept
- {
- return t != nullptr ? text.compareIgnoreCase (CharPointer_UTF8 (t)) == 0
- : isEmpty();
- }
-
- bool String::equalsIgnoreCase (StringRef t) const noexcept
- {
- return text.compareIgnoreCase (t.text) == 0;
- }
-
- bool String::equalsIgnoreCase (const String& other) const noexcept
- {
- return text == other.text
- || text.compareIgnoreCase (other.text) == 0;
- }
-
- int String::compare (const String& other) const noexcept { return (text == other.text) ? 0 : text.compare (other.text); }
- int String::compare (const char* const other) const noexcept { return text.compare (CharPointer_UTF8 (other)); }
- int String::compareIgnoreCase (const String& other) const noexcept { return (text == other.text) ? 0 : text.compareIgnoreCase (other.text); }
-
- static int stringCompareRight (String::CharPointerType s1, String::CharPointerType s2) noexcept
- {
- for (int bias = 0;;)
- {
- const water_uchar c1 = s1.getAndAdvance();
- const bool isDigit1 = CharacterFunctions::isDigit (c1);
-
- const water_uchar c2 = s2.getAndAdvance();
- const bool isDigit2 = CharacterFunctions::isDigit (c2);
-
- if (! (isDigit1 || isDigit2)) return bias;
- if (! isDigit1) return -1;
- if (! isDigit2) return 1;
-
- if (c1 != c2 && bias == 0)
- bias = c1 < c2 ? -1 : 1;
-
- wassert (c1 != 0 && c2 != 0);
- }
- }
-
- static int stringCompareLeft (String::CharPointerType s1, String::CharPointerType s2) noexcept
- {
- for (;;)
- {
- const water_uchar c1 = s1.getAndAdvance();
- const bool isDigit1 = CharacterFunctions::isDigit (c1);
-
- const water_uchar c2 = s2.getAndAdvance();
- const bool isDigit2 = CharacterFunctions::isDigit (c2);
-
- if (! (isDigit1 || isDigit2)) return 0;
- if (! isDigit1) return -1;
- if (! isDigit2) return 1;
- if (c1 < c2) return -1;
- if (c1 > c2) return 1;
- }
- }
-
- static int naturalStringCompare (String::CharPointerType s1, String::CharPointerType s2, bool isCaseSensitive) noexcept
- {
- bool firstLoop = true;
-
- for (;;)
- {
- const bool hasSpace1 = s1.isWhitespace();
- const bool hasSpace2 = s2.isWhitespace();
-
- if ((! firstLoop) && (hasSpace1 ^ hasSpace2))
- return hasSpace2 ? 1 : -1;
-
- firstLoop = false;
-
- if (hasSpace1) s1 = s1.findEndOfWhitespace();
- if (hasSpace2) s2 = s2.findEndOfWhitespace();
-
- if (s1.isDigit() && s2.isDigit())
- {
- const int result = (*s1 == '0' || *s2 == '0') ? stringCompareLeft (s1, s2)
- : stringCompareRight (s1, s2);
-
- if (result != 0)
- return result;
- }
-
- water_uchar c1 = s1.getAndAdvance();
- water_uchar c2 = s2.getAndAdvance();
-
- if (c1 != c2 && ! isCaseSensitive)
- {
- c1 = CharacterFunctions::toUpperCase (c1);
- c2 = CharacterFunctions::toUpperCase (c2);
- }
-
- if (c1 == c2)
- {
- if (c1 == 0)
- return 0;
- }
- else
- {
- const bool isAlphaNum1 = CharacterFunctions::isLetterOrDigit (c1);
- const bool isAlphaNum2 = CharacterFunctions::isLetterOrDigit (c2);
-
- if (isAlphaNum2 && ! isAlphaNum1) return -1;
- if (isAlphaNum1 && ! isAlphaNum2) return 1;
-
- return c1 < c2 ? -1 : 1;
- }
-
- wassert (c1 != 0 && c2 != 0);
- }
- }
-
- int String::compareNatural (StringRef other, bool isCaseSensitive) const noexcept
- {
- return naturalStringCompare (getCharPointer(), other.text, isCaseSensitive);
- }
-
- //==============================================================================
- void String::append (const String& textToAppend, size_t maxCharsToTake)
- {
- appendCharPointer (this == &textToAppend ? String (textToAppend).text
- : textToAppend.text, maxCharsToTake);
- }
-
- void String::appendCharPointer (const CharPointerType textToAppend)
- {
- appendCharPointer (textToAppend, textToAppend.findTerminatingNull());
- }
-
- void String::appendCharPointer (const CharPointerType startOfTextToAppend,
- const CharPointerType endOfTextToAppend)
- {
- wassert (startOfTextToAppend.getAddress() != nullptr && endOfTextToAppend.getAddress() != nullptr);
-
- const int extraBytesNeeded = getAddressDifference (endOfTextToAppend.getAddress(),
- startOfTextToAppend.getAddress());
- wassert (extraBytesNeeded >= 0);
-
- if (extraBytesNeeded > 0)
- {
- const size_t byteOffsetOfNull = getByteOffsetOfEnd();
- preallocateBytes (byteOffsetOfNull + (size_t) extraBytesNeeded);
-
- CharPointerType::CharType* const newStringStart = addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull);
- memcpy (newStringStart, startOfTextToAppend.getAddress(), (size_t) extraBytesNeeded);
- CharPointerType (addBytesToPointer (newStringStart, extraBytesNeeded)).writeNull();
- }
- }
-
- String& String::operator+= (const char* const t)
- {
- appendCharPointer (CharPointer_UTF8 (t)); // (using UTF8 here triggers a faster code-path than ascii)
- return *this;
- }
-
- String& String::operator+= (const String& other)
- {
- if (isEmpty())
- return operator= (other);
-
- if (this == &other)
- return operator+= (String (*this));
-
- appendCharPointer (other.text);
- return *this;
- }
-
- String& String::operator+= (StringRef other)
- {
- return operator+= (String (other));
- }
-
- String& String::operator+= (const char ch)
- {
- const char asString[] = { ch, 0 };
- return operator+= (asString);
- }
-
- String& String::operator+= (const water_uchar ch)
- {
- return operator+= (charToString(ch));
- }
-
- namespace StringHelpers
- {
- template <typename T>
- inline String& operationAddAssign (String& str, const T number)
- {
- char buffer [(sizeof(T) * 8) / 2];
- char* end = buffer + numElementsInArray (buffer);
- char* start = NumberToStringConverters::numberToString (end, number);
-
- str.appendCharPointer (String::CharPointerType (start), String::CharPointerType (end));
- return str;
- }
- }
-
- String& String::operator+= (const int number) { return StringHelpers::operationAddAssign<int> (*this, number); }
- String& String::operator+= (const int64 number) { return StringHelpers::operationAddAssign<int64> (*this, number); }
- String& String::operator+= (const uint64 number) { return StringHelpers::operationAddAssign<uint64> (*this, number); }
-
- //==============================================================================
- String operator+ (const char* const s1, const String& s2) { String s (s1); return s += s2; }
- String operator+ (const char s1, const String& s2) { return String::charToString ((water_uchar) (uint8) s1) + s2; }
- String operator+ (String s1, const String& s2) { return s1 += s2; }
- String operator+ (String s1, const char* const s2) { return s1 += s2; }
- String operator+ (String s1, const char s2) { return s1 += s2; }
-
- String operator+ (const water_uchar s1, const String& s2) { return String::charToString (s1) + s2; }
- String operator+ (String s1, const water_uchar s2) { return s1 += s2; }
- String& operator<< (String& s1, const water_uchar s2) { return s1 += s2; }
-
- String& operator<< (String& s1, const char s2) { return s1 += s2; }
- String& operator<< (String& s1, const char* const s2) { return s1 += s2; }
- String& operator<< (String& s1, const String& s2) { return s1 += s2; }
- String& operator<< (String& s1, StringRef s2) { return s1 += s2; }
-
- String& operator<< (String& s1, const int number) { return s1 += number; }
- String& operator<< (String& s1, const short number) { return s1 += (int) number; }
- String& operator<< (String& s1, const unsigned short number) { return s1 += (uint64) number; }
- String& operator<< (String& s1, const long number) { return s1 += String (number); }
- String& operator<< (String& s1, const unsigned long number) { return s1 += String (number); }
- String& operator<< (String& s1, const int64 number) { return s1 += String (number); }
- String& operator<< (String& s1, const uint64 number) { return s1 += String (number); }
- String& operator<< (String& s1, const float number) { return s1 += String (number); }
- String& operator<< (String& s1, const double number) { return s1 += String (number); }
-
- OutputStream& operator<< (OutputStream& stream, const String& text)
- {
- return operator<< (stream, StringRef (text));
- }
-
- OutputStream& operator<< (OutputStream& stream, StringRef text)
- {
- const size_t numBytes = CharPointer_UTF8::getBytesRequiredFor (text.text);
-
- stream.write (text.text.getAddress(), numBytes);
- return stream;
- }
-
- //==============================================================================
- int String::indexOfChar (const water_uchar character) const noexcept
- {
- return text.indexOf (character);
- }
-
- int String::indexOfChar (const int startIndex, const water_uchar character) const noexcept
- {
- CharPointerType t (text);
-
- for (int i = 0; ! t.isEmpty(); ++i)
- {
- if (i >= startIndex)
- {
- if (t.getAndAdvance() == character)
- return i;
- }
- else
- {
- ++t;
- }
- }
-
- return -1;
- }
-
- int String::lastIndexOfChar (const water_uchar character) const noexcept
- {
- CharPointerType t (text);
- int last = -1;
-
- for (int i = 0; ! t.isEmpty(); ++i)
- if (t.getAndAdvance() == character)
- last = i;
-
- return last;
- }
-
- int String::indexOfAnyOf (StringRef charactersToLookFor, const int startIndex, const bool ignoreCase) const noexcept
- {
- CharPointerType t (text);
-
- for (int i = 0; ! t.isEmpty(); ++i)
- {
- if (i >= startIndex)
- {
- if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0)
- return i;
- }
- else
- {
- ++t;
- }
- }
-
- return -1;
- }
-
- int String::indexOf (StringRef other) const noexcept
- {
- return other.isEmpty() ? 0 : text.indexOf (other.text);
- }
-
- int String::indexOfIgnoreCase (StringRef other) const noexcept
- {
- return other.isEmpty() ? 0 : CharacterFunctions::indexOfIgnoreCase (text, other.text);
- }
-
- int String::indexOf (const int startIndex, StringRef other) const noexcept
- {
- if (other.isEmpty())
- return -1;
-
- CharPointerType t (text);
-
- for (int i = startIndex; --i >= 0;)
- {
- if (t.isEmpty())
- return -1;
-
- ++t;
- }
-
- int found = t.indexOf (other.text);
- if (found >= 0)
- found += startIndex;
- return found;
- }
-
- int String::indexOfIgnoreCase (const int startIndex, StringRef other) const noexcept
- {
- if (other.isEmpty())
- return -1;
-
- CharPointerType t (text);
-
- for (int i = startIndex; --i >= 0;)
- {
- if (t.isEmpty())
- return -1;
-
- ++t;
- }
-
- int found = CharacterFunctions::indexOfIgnoreCase (t, other.text);
- if (found >= 0)
- found += startIndex;
- return found;
- }
-
- int String::lastIndexOf (StringRef other) const noexcept
- {
- if (other.isNotEmpty())
- {
- const int len = other.length();
- int i = length() - len;
-
- if (i >= 0)
- {
- for (CharPointerType n (text + i); i >= 0; --i)
- {
- if (n.compareUpTo (other.text, len) == 0)
- return i;
-
- --n;
- }
- }
- }
-
- return -1;
- }
-
- int String::lastIndexOfIgnoreCase (StringRef other) const noexcept
- {
- if (other.isNotEmpty())
- {
- const int len = other.length();
- int i = length() - len;
-
- if (i >= 0)
- {
- for (CharPointerType n (text + i); i >= 0; --i)
- {
- if (n.compareIgnoreCaseUpTo (other.text, len) == 0)
- return i;
-
- --n;
- }
- }
- }
-
- return -1;
- }
-
- int String::lastIndexOfAnyOf (StringRef charactersToLookFor, const bool ignoreCase) const noexcept
- {
- CharPointerType t (text);
- int last = -1;
-
- for (int i = 0; ! t.isEmpty(); ++i)
- if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0)
- last = i;
-
- return last;
- }
-
- bool String::contains (StringRef other) const noexcept
- {
- return indexOf (other) >= 0;
- }
-
- bool String::containsChar (const water_uchar character) const noexcept
- {
- return text.indexOf (character) >= 0;
- }
-
- bool String::containsIgnoreCase (StringRef t) const noexcept
- {
- return indexOfIgnoreCase (t) >= 0;
- }
-
- int String::indexOfWholeWord (StringRef word) const noexcept
- {
- if (word.isNotEmpty())
- {
- CharPointerType t (text);
- const int wordLen = word.length();
- const int end = (int) t.length() - wordLen;
-
- for (int i = 0; i <= end; ++i)
- {
- if (t.compareUpTo (word.text, wordLen) == 0
- && (i == 0 || ! (t - 1).isLetterOrDigit())
- && ! (t + wordLen).isLetterOrDigit())
- return i;
-
- ++t;
- }
- }
-
- return -1;
- }
-
- int String::indexOfWholeWordIgnoreCase (StringRef word) const noexcept
- {
- if (word.isNotEmpty())
- {
- CharPointerType t (text);
- const int wordLen = word.length();
- const int end = (int) t.length() - wordLen;
-
- for (int i = 0; i <= end; ++i)
- {
- if (t.compareIgnoreCaseUpTo (word.text, wordLen) == 0
- && (i == 0 || ! (t - 1).isLetterOrDigit())
- && ! (t + wordLen).isLetterOrDigit())
- return i;
-
- ++t;
- }
- }
-
- return -1;
- }
-
- bool String::containsWholeWord (StringRef wordToLookFor) const noexcept
- {
- return indexOfWholeWord (wordToLookFor) >= 0;
- }
-
- bool String::containsWholeWordIgnoreCase (StringRef wordToLookFor) const noexcept
- {
- return indexOfWholeWordIgnoreCase (wordToLookFor) >= 0;
- }
-
- //==============================================================================
- template <typename CharPointer>
- struct WildCardMatcher
- {
- static bool matches (CharPointer wildcard, CharPointer test, const bool ignoreCase) noexcept
- {
- for (;;)
- {
- const water_uchar wc = wildcard.getAndAdvance();
-
- if (wc == '*')
- return wildcard.isEmpty() || matchesAnywhere (wildcard, test, ignoreCase);
-
- if (! characterMatches (wc, test.getAndAdvance(), ignoreCase))
- return false;
-
- if (wc == 0)
- return true;
- }
- }
-
- static bool characterMatches (const water_uchar wc, const water_uchar tc, const bool ignoreCase) noexcept
- {
- return (wc == tc) || (wc == '?' && tc != 0)
- || (ignoreCase && CharacterFunctions::toLowerCase (wc) == CharacterFunctions::toLowerCase (tc));
- }
-
- static bool matchesAnywhere (const CharPointer wildcard, CharPointer test, const bool ignoreCase) noexcept
- {
- for (; ! test.isEmpty(); ++test)
- if (matches (wildcard, test, ignoreCase))
- return true;
-
- return false;
- }
- };
-
- bool String::matchesWildcard (StringRef wildcard, const bool ignoreCase) const noexcept
- {
- return WildCardMatcher<CharPointerType>::matches (wildcard.text, text, ignoreCase);
- }
-
- //==============================================================================
- String String::repeatedString (StringRef stringToRepeat, int numberOfTimesToRepeat)
- {
- if (numberOfTimesToRepeat <= 0)
- return String();
-
- String result (PreallocationBytes (findByteOffsetOfEnd (stringToRepeat) * (size_t) numberOfTimesToRepeat));
- CharPointerType n (result.text);
-
- while (--numberOfTimesToRepeat >= 0)
- n.writeAll (stringToRepeat.text);
-
- return result;
- }
-
- String String::paddedLeft (const water_uchar padCharacter, int minimumLength) const
- {
- wassert (padCharacter != 0);
-
- int extraChars = minimumLength;
- CharPointerType end (text);
-
- while (! end.isEmpty())
- {
- --extraChars;
- ++end;
- }
-
- if (extraChars <= 0 || padCharacter == 0)
- return *this;
-
- const size_t currentByteSize = (size_t) (((char*) end.getAddress()) - (char*) text.getAddress());
- String result (PreallocationBytes (currentByteSize + (size_t) extraChars * CharPointerType::getBytesRequiredFor (padCharacter)));
- CharPointerType n (result.text);
-
- while (--extraChars >= 0)
- n.write (padCharacter);
-
- n.writeAll (text);
- return result;
- }
-
- String String::paddedRight (const water_uchar padCharacter, int minimumLength) const
- {
- CARLA_SAFE_ASSERT_RETURN (padCharacter != 0, *this);
-
- int extraChars = minimumLength;
- CharPointerType end (text);
-
- while (! end.isEmpty())
- {
- --extraChars;
- ++end;
- }
-
- if (extraChars <= 0 || padCharacter == 0)
- return *this;
-
- const size_t currentByteSize = (size_t) (((char*) end.getAddress()) - (char*) text.getAddress());
- String result (PreallocationBytes (currentByteSize + (size_t) extraChars * CharPointerType::getBytesRequiredFor (padCharacter)));
- CharPointerType n (result.text);
-
- n.writeAll (text);
-
- while (--extraChars >= 0)
- n.write (padCharacter);
-
- n.writeNull();
- return result;
- }
-
- //==============================================================================
- String String::replaceSection (int index, int numCharsToReplace, StringRef stringToInsert) const
- {
- if (index < 0)
- {
- // a negative index to replace from?
- wassertfalse;
- index = 0;
- }
-
- if (numCharsToReplace < 0)
- {
- // replacing a negative number of characters?
- numCharsToReplace = 0;
- wassertfalse;
- }
-
- CharPointerType insertPoint (text);
-
- for (int i = 0; i < index; ++i)
- {
- if (insertPoint.isEmpty())
- {
- // replacing beyond the end of the string?
- wassertfalse;
- return *this + stringToInsert;
- }
-
- ++insertPoint;
- }
-
- CharPointerType startOfRemainder (insertPoint);
-
- for (int i = 0; i < numCharsToReplace && ! startOfRemainder.isEmpty(); ++i)
- ++startOfRemainder;
-
- if (insertPoint == text && startOfRemainder.isEmpty())
- return stringToInsert.text;
-
- const size_t initialBytes = (size_t) (((char*) insertPoint.getAddress()) - (char*) text.getAddress());
- const size_t newStringBytes = findByteOffsetOfEnd (stringToInsert);
- const size_t remainderBytes = (size_t) (((char*) startOfRemainder.findTerminatingNull().getAddress()) - (char*) startOfRemainder.getAddress());
-
- const size_t newTotalBytes = initialBytes + newStringBytes + remainderBytes;
- if (newTotalBytes <= 0)
- return String();
-
- String result (PreallocationBytes ((size_t) newTotalBytes));
-
- char* dest = (char*) result.text.getAddress();
- memcpy (dest, text.getAddress(), initialBytes);
- dest += initialBytes;
- memcpy (dest, stringToInsert.text.getAddress(), newStringBytes);
- dest += newStringBytes;
- memcpy (dest, startOfRemainder.getAddress(), remainderBytes);
- dest += remainderBytes;
- CharPointerType ((CharPointerType::CharType*) dest).writeNull();
-
- return result;
- }
-
- String String::replace (StringRef stringToReplace, StringRef stringToInsert, const bool ignoreCase) const
- {
- const int stringToReplaceLen = stringToReplace.length();
- const int stringToInsertLen = stringToInsert.length();
-
- int i = 0;
- String result (*this);
-
- while ((i = (ignoreCase ? result.indexOfIgnoreCase (i, stringToReplace)
- : result.indexOf (i, stringToReplace))) >= 0)
- {
- result = result.replaceSection (i, stringToReplaceLen, stringToInsert);
- i += stringToInsertLen;
- }
-
- return result;
- }
-
- class StringCreationHelper
- {
- public:
- StringCreationHelper (const size_t initialBytes)
- : source (nullptr), dest (nullptr), allocatedBytes (initialBytes), bytesWritten (0)
- {
- result.preallocateBytes (allocatedBytes);
- dest = result.getCharPointer();
- }
-
- StringCreationHelper (const String::CharPointerType s)
- : source (s), dest (nullptr), allocatedBytes (StringHolder::getAllocatedNumBytes (s)), bytesWritten (0)
- {
- result.preallocateBytes (allocatedBytes);
- dest = result.getCharPointer();
- }
-
- void write (water_uchar c)
- {
- bytesWritten += String::CharPointerType::getBytesRequiredFor (c);
-
- if (bytesWritten > allocatedBytes)
- {
- allocatedBytes += jmax ((size_t) 8, allocatedBytes / 16);
- const size_t destOffset = (size_t) (((char*) dest.getAddress()) - (char*) result.getCharPointer().getAddress());
- result.preallocateBytes (allocatedBytes);
- dest = addBytesToPointer (result.getCharPointer().getAddress(), (int) destOffset);
- }
-
- dest.write (c);
- }
-
- String result;
- String::CharPointerType source;
-
- private:
- String::CharPointerType dest;
- size_t allocatedBytes, bytesWritten;
- };
-
- String String::replaceCharacter (const water_uchar charToReplace, const water_uchar charToInsert) const
- {
- if (! containsChar (charToReplace))
- return *this;
-
- StringCreationHelper builder (text);
-
- for (;;)
- {
- water_uchar c = builder.source.getAndAdvance();
-
- if (c == charToReplace)
- c = charToInsert;
-
- builder.write (c);
-
- if (c == 0)
- break;
- }
-
- return builder.result;
- }
-
- String String::replaceCharacters (StringRef charactersToReplace, StringRef charactersToInsertInstead) const
- {
- // Each character in the first string must have a matching one in the
- // second, so the two strings must be the same length.
- wassert (charactersToReplace.length() == charactersToInsertInstead.length());
-
- StringCreationHelper builder (text);
-
- for (;;)
- {
- water_uchar c = builder.source.getAndAdvance();
-
- const int index = charactersToReplace.text.indexOf (c);
- if (index >= 0)
- c = charactersToInsertInstead [index];
-
- builder.write (c);
-
- if (c == 0)
- break;
- }
-
- return builder.result;
- }
-
- //==============================================================================
- bool String::startsWith (StringRef other) const noexcept
- {
- return text.compareUpTo (other.text, other.length()) == 0;
- }
-
- bool String::startsWithIgnoreCase (StringRef other) const noexcept
- {
- return text.compareIgnoreCaseUpTo (other.text, other.length()) == 0;
- }
-
- bool String::startsWithChar (const water_uchar character) const noexcept
- {
- // strings can't contain a null character!
- CARLA_SAFE_ASSERT_RETURN (character != 0, false);
-
- return *text == character;
- }
-
- bool String::endsWithChar (const water_uchar character) const noexcept
- {
- // strings can't contain a null character!
- CARLA_SAFE_ASSERT_RETURN (character != 0, false);
-
- if (text.isEmpty())
- return false;
-
- CharPointerType t (text.findTerminatingNull());
- return *--t == character;
- }
-
- bool String::endsWith (StringRef other) const noexcept
- {
- CharPointerType end (text.findTerminatingNull());
- CharPointerType otherEnd (other.text.findTerminatingNull());
-
- while (end > text && otherEnd > other.text)
- {
- --end;
- --otherEnd;
-
- if (*end != *otherEnd)
- return false;
- }
-
- return otherEnd == other.text;
- }
-
- bool String::endsWithIgnoreCase (StringRef other) const noexcept
- {
- CharPointerType end (text.findTerminatingNull());
- CharPointerType otherEnd (other.text.findTerminatingNull());
-
- while (end > text && otherEnd > other.text)
- {
- --end;
- --otherEnd;
-
- if (end.toLowerCase() != otherEnd.toLowerCase())
- return false;
- }
-
- return otherEnd == other.text;
- }
-
- //==============================================================================
- String String::toUpperCase() const
- {
- StringCreationHelper builder (text);
-
- for (;;)
- {
- const water_uchar c = builder.source.toUpperCase();
- builder.write (c);
-
- if (c == 0)
- break;
-
- ++(builder.source);
- }
-
- return builder.result;
- }
-
- String String::toLowerCase() const
- {
- StringCreationHelper builder (text);
-
- for (;;)
- {
- const water_uchar c = builder.source.toLowerCase();
- builder.write (c);
-
- if (c == 0)
- break;
-
- ++(builder.source);
- }
-
- return builder.result;
- }
-
- //==============================================================================
- water_uchar String::getLastCharacter() const noexcept
- {
- return isEmpty() ? water_uchar() : text [length() - 1];
- }
-
- String String::substring (int start, const int end) const
- {
- if (start < 0)
- start = 0;
-
- if (end <= start)
- return String();
-
- int i = 0;
- CharPointerType t1 (text);
-
- while (i < start)
- {
- if (t1.isEmpty())
- return String();
-
- ++i;
- ++t1;
- }
-
- CharPointerType t2 (t1);
- while (i < end)
- {
- if (t2.isEmpty())
- {
- if (start == 0)
- return *this;
-
- break;
- }
-
- ++i;
- ++t2;
- }
-
- return String (t1, t2);
- }
-
- String String::substring (int start) const
- {
- if (start <= 0)
- return *this;
-
- CharPointerType t (text);
-
- while (--start >= 0)
- {
- if (t.isEmpty())
- return String();
-
- ++t;
- }
-
- return String (t);
- }
-
- String String::dropLastCharacters (const int numberToDrop) const
- {
- return String (text, (size_t) jmax (0, length() - numberToDrop));
- }
-
- String String::getLastCharacters (const int numCharacters) const
- {
- return String (text + jmax (0, length() - jmax (0, numCharacters)));
- }
-
- String String::fromFirstOccurrenceOf (StringRef sub,
- const bool includeSubString,
- const bool ignoreCase) const
- {
- const int i = ignoreCase ? indexOfIgnoreCase (sub)
- : indexOf (sub);
- if (i < 0)
- return String();
-
- return substring (includeSubString ? i : i + sub.length());
- }
-
- String String::fromLastOccurrenceOf (StringRef sub,
- const bool includeSubString,
- const bool ignoreCase) const
- {
- const int i = ignoreCase ? lastIndexOfIgnoreCase (sub)
- : lastIndexOf (sub);
- if (i < 0)
- return *this;
-
- return substring (includeSubString ? i : i + sub.length());
- }
-
- String String::upToFirstOccurrenceOf (StringRef sub,
- const bool includeSubString,
- const bool ignoreCase) const
- {
- const int i = ignoreCase ? indexOfIgnoreCase (sub)
- : indexOf (sub);
- if (i < 0)
- return *this;
-
- return substring (0, includeSubString ? i + sub.length() : i);
- }
-
- String String::upToLastOccurrenceOf (StringRef sub,
- const bool includeSubString,
- const bool ignoreCase) const
- {
- const int i = ignoreCase ? lastIndexOfIgnoreCase (sub)
- : lastIndexOf (sub);
- if (i < 0)
- return *this;
-
- return substring (0, includeSubString ? i + sub.length() : i);
- }
-
- bool String::isQuotedString() const
- {
- const water_uchar trimmedStart = trimStart()[0];
-
- return trimmedStart == '"'
- || trimmedStart == '\'';
- }
-
- String String::unquoted() const
- {
- const int len = length();
-
- if (len == 0)
- return String();
-
- const water_uchar lastChar = text [len - 1];
- const int dropAtStart = (*text == '"' || *text == '\'') ? 1 : 0;
- const int dropAtEnd = (lastChar == '"' || lastChar == '\'') ? 1 : 0;
-
- return substring (dropAtStart, len - dropAtEnd);
- }
-
- String String::quoted (water_uchar quoteCharacter) const
- {
- if (isEmpty())
- return charToString (quoteCharacter) + quoteCharacter;
-
- String t (*this);
-
- if (! t.startsWithChar (quoteCharacter))
- t = charToString (quoteCharacter) + t;
-
- if (! t.endsWithChar (quoteCharacter))
- t += quoteCharacter;
-
- return t;
- }
-
- //==============================================================================
- static String::CharPointerType findTrimmedEnd (const String::CharPointerType start,
- String::CharPointerType end)
- {
- while (end > start)
- {
- if (! (--end).isWhitespace())
- {
- ++end;
- break;
- }
- }
-
- return end;
- }
-
- String String::trim() const
- {
- if (isNotEmpty())
- {
- CharPointerType start (text.findEndOfWhitespace());
-
- const CharPointerType end (start.findTerminatingNull());
- CharPointerType trimmedEnd (findTrimmedEnd (start, end));
-
- if (trimmedEnd <= start)
- return String();
-
- if (text < start || trimmedEnd < end)
- return String (start, trimmedEnd);
- }
-
- return *this;
- }
-
- String String::trimStart() const
- {
- if (isNotEmpty())
- {
- const CharPointerType t (text.findEndOfWhitespace());
-
- if (t != text)
- return String (t);
- }
-
- return *this;
- }
-
- String String::trimEnd() const
- {
- if (isNotEmpty())
- {
- const CharPointerType end (text.findTerminatingNull());
- CharPointerType trimmedEnd (findTrimmedEnd (text, end));
-
- if (trimmedEnd < end)
- return String (text, trimmedEnd);
- }
-
- return *this;
- }
-
- String String::trimCharactersAtStart (StringRef charactersToTrim) const
- {
- CharPointerType t (text);
-
- while (charactersToTrim.text.indexOf (*t) >= 0)
- ++t;
-
- return t == text ? *this : String (t);
- }
-
- String String::trimCharactersAtEnd (StringRef charactersToTrim) const
- {
- if (isNotEmpty())
- {
- const CharPointerType end (text.findTerminatingNull());
- CharPointerType trimmedEnd (end);
-
- while (trimmedEnd > text)
- {
- if (charactersToTrim.text.indexOf (*--trimmedEnd) < 0)
- {
- ++trimmedEnd;
- break;
- }
- }
-
- if (trimmedEnd < end)
- return String (text, trimmedEnd);
- }
-
- return *this;
- }
-
- //==============================================================================
- String String::retainCharacters (StringRef charactersToRetain) const
- {
- if (isEmpty())
- return String();
-
- StringCreationHelper builder (text);
-
- for (;;)
- {
- water_uchar c = builder.source.getAndAdvance();
-
- if (charactersToRetain.text.indexOf (c) >= 0)
- builder.write (c);
-
- if (c == 0)
- break;
- }
-
- builder.write (0);
- return builder.result;
- }
-
- String String::removeCharacters (StringRef charactersToRemove) const
- {
- if (isEmpty())
- return String();
-
- StringCreationHelper builder (text);
-
- for (;;)
- {
- water_uchar c = builder.source.getAndAdvance();
-
- if (charactersToRemove.text.indexOf (c) < 0)
- builder.write (c);
-
- if (c == 0)
- break;
- }
-
- return builder.result;
- }
-
- String String::initialSectionContainingOnly (StringRef permittedCharacters) const
- {
- for (CharPointerType t (text); ! t.isEmpty(); ++t)
- if (permittedCharacters.text.indexOf (*t) < 0)
- return String (text, t);
-
- return *this;
- }
-
- String String::initialSectionNotContaining (StringRef charactersToStopAt) const
- {
- for (CharPointerType t (text); ! t.isEmpty(); ++t)
- if (charactersToStopAt.text.indexOf (*t) >= 0)
- return String (text, t);
-
- return *this;
- }
-
- bool String::containsOnly (StringRef chars) const noexcept
- {
- for (CharPointerType t (text); ! t.isEmpty();)
- if (chars.text.indexOf (t.getAndAdvance()) < 0)
- return false;
-
- return true;
- }
-
- bool String::containsAnyOf (StringRef chars) const noexcept
- {
- for (CharPointerType t (text); ! t.isEmpty();)
- if (chars.text.indexOf (t.getAndAdvance()) >= 0)
- return true;
-
- return false;
- }
-
- bool String::containsNonWhitespaceChars() const noexcept
- {
- for (CharPointerType t (text); ! t.isEmpty(); ++t)
- if (! t.isWhitespace())
- return true;
-
- return false;
- }
-
- //=====================================================================================================================
- static String getStringFromWindows1252Codepage (const char* data, size_t num)
- {
- HeapBlock<char> unicode;
- CARLA_SAFE_ASSERT_RETURN(unicode.malloc(num + 1), String());
-
- for (size_t i = 0; i < num; ++i)
- unicode[i] = CharacterFunctions::getUnicodeCharFromWindows1252Codepage ((uint8) data[i]);
-
- unicode[num] = 0;
- return CharPointer_UTF8 (unicode);
- }
-
- String String::createStringFromData (const void* const unknownData, int size)
- {
- const uint8* const data = static_cast<const uint8*> (unknownData);
-
- if (size <= 0 || data == nullptr)
- return String();
-
- if (size == 1)
- return charToString ((water_uchar) data[0]);
-
- const char* start = (const char*) data;
-
- if (size >= 3 && CharPointer_UTF8::isByteOrderMark (data))
- {
- start += 3;
- size -= 3;
- }
-
- if (CharPointer_UTF8::isValidString (start, size))
- return String (CharPointer_UTF8 (start),
- CharPointer_UTF8 (start + size));
-
- return getStringFromWindows1252Codepage (start, (size_t) size);
- }
-
- // Note! The format parameter here MUST NOT be a reference, otherwise MS's va_start macro fails to work (but still compiles).
- String String::formatted (const String pf, ... )
- {
- size_t bufferSize = 256;
-
- HeapBlock<char> temp;
- CARLA_SAFE_ASSERT_RETURN(temp.malloc(bufferSize), String());
-
- for (;;)
- {
- va_list args;
- va_start (args, pf);
-
- // FIXME - needed?
- temp.clear (bufferSize);
-
- const int num = vsnprintf (temp.getData(), bufferSize - 1, pf.toRawUTF8(), args);
-
- va_end (args);
-
- if (num > 0)
- return String (temp);
-
- bufferSize += 256;
-
- if (num == 0 || bufferSize > 65536) // the upper limit is a sanity check to avoid situations where vsnprintf repeatedly
- break; // returns -1 because of an error rather than because it needs more space.
-
- temp.realloc (bufferSize);
- }
-
- return String();
- }
-
- //==============================================================================
- int String::getIntValue() const noexcept { return text.getIntValue32(); }
- int64 String::getLargeIntValue() const noexcept { return text.getIntValue64(); }
- float String::getFloatValue() const noexcept { return (float) getDoubleValue(); }
- double String::getDoubleValue() const noexcept { return text.getDoubleValue(); }
-
- int String::getTrailingIntValue() const noexcept
- {
- int n = 0;
- int mult = 1;
- CharPointerType t (text.findTerminatingNull());
-
- while (--t >= text)
- {
- if (! t.isDigit())
- {
- if (*t == '-')
- n = -n;
-
- break;
- }
-
- n += mult * (*t - '0');
- mult *= 10;
- }
-
- return n;
- }
-
- static const char hexDigits[] = "0123456789abcdef";
-
- template <typename Type>
- static String hexToString (Type v)
- {
- String::CharPointerType::CharType buffer[32];
- String::CharPointerType::CharType* const end = buffer + numElementsInArray (buffer) - 1;
- String::CharPointerType::CharType* t = end;
- *t = 0;
-
- do
- {
- *--t = hexDigits [(int) (v & 15)];
- v >>= 4;
-
- } while (v != 0);
-
- return String (String::CharPointerType (t),
- String::CharPointerType (end));
- }
-
- String String::toHexString (int number) { return hexToString ((unsigned int) number); }
- String String::toHexString (int64 number) { return hexToString ((uint64) number); }
- String String::toHexString (short number) { return toHexString ((int) (unsigned short) number); }
-
- String String::toHexString (const void* const d, const int size, const int groupSize)
- {
- if (size <= 0)
- return String();
-
- int numChars = (size * 2) + 2;
- if (groupSize > 0)
- numChars += size / groupSize;
-
- String s (PreallocationBytes (sizeof (CharPointerType::CharType) * (size_t) numChars));
-
- const unsigned char* data = static_cast<const unsigned char*> (d);
- CharPointerType dest (s.text);
-
- for (int i = 0; i < size; ++i)
- {
- const unsigned char nextByte = *data++;
- dest.write ((water_uchar) hexDigits [nextByte >> 4]);
- dest.write ((water_uchar) hexDigits [nextByte & 0xf]);
-
- if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1))
- dest.write ((water_uchar) ' ');
- }
-
- dest.writeNull();
- return s;
- }
-
- int String::getHexValue32() const noexcept { return CharacterFunctions::HexParser<int> ::parse (text); }
- int64 String::getHexValue64() const noexcept { return CharacterFunctions::HexParser<int64>::parse (text); }
-
- //==============================================================================
- static const water_uchar emptyChar = 0;
-
- template <class CharPointerType_Src, class CharPointerType_Dest>
- struct StringEncodingConverter
- {
- static CharPointerType_Dest convert (const String& s)
- {
- String& source = const_cast<String&> (s);
-
- typedef typename CharPointerType_Dest::CharType DestChar;
-
- if (source.isEmpty())
- return CharPointerType_Dest (reinterpret_cast<const DestChar*> (&emptyChar));
-
- CharPointerType_Src text (source.getCharPointer());
- const size_t extraBytesNeeded = CharPointerType_Dest::getBytesRequiredFor (text) + sizeof (typename CharPointerType_Dest::CharType);
- const size_t endOffset = (text.sizeInBytes() + 3) & ~3u; // the new string must be word-aligned or many Windows
- // functions will fail to read it correctly!
- source.preallocateBytes (endOffset + extraBytesNeeded);
- text = source.getCharPointer();
-
- void* const newSpace = addBytesToPointer (text.getAddress(), (int) endOffset);
- const CharPointerType_Dest extraSpace (static_cast<DestChar*> (newSpace));
-
- #ifdef DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..)
- const size_t bytesToClear = (size_t) jmin ((int) extraBytesNeeded, 4);
- zeromem (addBytesToPointer (newSpace, extraBytesNeeded - bytesToClear), bytesToClear);
- #endif
-
- CharPointerType_Dest (extraSpace).writeAll (text);
- return extraSpace;
- }
- };
-
- template <>
- struct StringEncodingConverter<CharPointer_UTF8, CharPointer_UTF8>
- {
- static CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 ((CharPointer_UTF8::CharType*) source.getCharPointer().getAddress()); }
- };
-
- CharPointer_UTF8 String::toUTF8() const { return StringEncodingConverter<CharPointerType, CharPointer_UTF8 >::convert (*this); }
-
- const char* String::toRawUTF8() const
- {
- return toUTF8().getAddress();
- }
-
- std::string String::toStdString() const
- {
- return std::string (toRawUTF8());
- }
-
- //==============================================================================
- template <class CharPointerType_Src, class CharPointerType_Dest>
- struct StringCopier
- {
- static size_t copyToBuffer (const CharPointerType_Src source, typename CharPointerType_Dest::CharType* const buffer, const size_t maxBufferSizeBytes)
- {
- wassert (((ssize_t) maxBufferSizeBytes) >= 0); // keep this value positive!
-
- if (buffer == nullptr)
- return CharPointerType_Dest::getBytesRequiredFor (source) + sizeof (typename CharPointerType_Dest::CharType);
-
- return CharPointerType_Dest (buffer).writeWithDestByteLimit (source, maxBufferSizeBytes);
- }
- };
-
- size_t String::copyToUTF8 (CharPointer_UTF8::CharType* const buffer, size_t maxBufferSizeBytes) const noexcept
- {
- return StringCopier<CharPointerType, CharPointer_UTF8>::copyToBuffer (text, buffer, maxBufferSizeBytes);
- }
-
- //==============================================================================
- size_t String::getNumBytesAsUTF8() const noexcept
- {
- return CharPointer_UTF8::getBytesRequiredFor (text);
- }
-
- String String::fromUTF8 (const char* const buffer, int bufferSizeBytes)
- {
- if (buffer != nullptr)
- {
- if (bufferSizeBytes < 0)
- return String (CharPointer_UTF8 (buffer));
-
- if (bufferSizeBytes > 0)
- {
- wassert (CharPointer_UTF8::isValidString (buffer, bufferSizeBytes));
- return String (CharPointer_UTF8 (buffer), CharPointer_UTF8 (buffer + bufferSizeBytes));
- }
- }
-
- return String();
- }
-
- #ifdef CARLA_OS_MAC
-
- String String::convertToPrecomposedUnicode() const
- {
- #if 0
- UnicodeMapping map;
-
- map.unicodeEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault,
- kUnicodeNoSubset,
- kTextEncodingDefaultFormat);
-
- map.otherEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault,
- kUnicodeCanonicalCompVariant,
- kTextEncodingDefaultFormat);
-
- map.mappingVersion = kUnicodeUseLatestMapping;
-
- UnicodeToTextInfo conversionInfo = 0;
- String result;
-
- if (CreateUnicodeToTextInfo (&map, &conversionInfo) == noErr)
- {
- const size_t bytesNeeded = CharPointer_UTF16::getBytesRequiredFor (getCharPointer());
-
- HeapBlock<char> tempOut;
- tempOut.calloc (bytesNeeded + 4);
-
- ByteCount bytesRead = 0;
- ByteCount outputBufferSize = 0;
-
- if (ConvertFromUnicodeToText (conversionInfo,
- bytesNeeded, (ConstUniCharArrayPtr) toUTF16().getAddress(),
- kUnicodeDefaultDirectionMask,
- 0, 0, 0, 0,
- bytesNeeded, &bytesRead,
- &outputBufferSize, tempOut) == noErr)
- {
- result = String (CharPointer_UTF16 ((CharPointer_UTF16::CharType*) tempOut.getData()));
- }
-
- DisposeUnicodeToTextInfo (&conversionInfo);
- }
-
- return result;
- #else
- return *this;
- #endif
- }
- #endif
-
- //==============================================================================
- StringRef::StringRef() noexcept : text ((const String::CharPointerType::CharType*) "\0\0\0")
- {
- }
-
- StringRef::StringRef (const char* stringLiteral) noexcept
- : text (stringLiteral)
- {
- wassert (stringLiteral != nullptr); // This must be a valid string literal, not a null pointer!!
-
- /* If you get an assertion here, then you're trying to create a string from 8-bit data
- that contains values greater than 127. These can NOT be correctly converted to unicode
- because there's no way for the String class to know what encoding was used to
- create them. The source data could be UTF-8, ASCII or one of many local code-pages.
-
- To get around this problem, you must be more explicit when you pass an ambiguous 8-bit
- string to the StringRef class - so for example if your source data is actually UTF-8,
- you'd call StringRef (CharPointer_UTF8 ("my utf8 string..")), and it would be able to
- correctly convert the multi-byte characters to unicode. It's *highly* recommended that
- you use UTF-8 with escape characters in your source code to represent extended characters,
- because there's no other way to represent these strings in a way that isn't dependent on
- the compiler, source code editor and platform.
- */
- wassert (CharPointer_UTF8::isValidString (stringLiteral, std::numeric_limits<int>::max()));
- }
-
- StringRef::StringRef (String::CharPointerType stringLiteral) noexcept : text (stringLiteral)
- {
- wassert (stringLiteral.getAddress() != nullptr); // This must be a valid string literal, not a null pointer!!
- }
-
- StringRef::StringRef (const String& string) noexcept : text (string.getCharPointer()) {}
-
- }
-
- //==============================================================================
|