1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-10 12:32:20 +02:00
Beef/BeefLibs/corlib/src/String.bf

3088 lines
69 KiB
Beef

// This file contains portions of code released by Microsoft under the MIT license as part
// of an open-sourcing initiative in 2014 of the C# core libraries.
// The original source was submitted to https://github.com/Microsoft/referencesource
using System.Diagnostics.Contracts;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System;
namespace System
{
// String size type
#if BF_LARGE_STRINGS
typealias int_strsize = int64;
typealias uint_strsize = uint64;
#else
typealias int_strsize = int32;
typealias uint_strsize = uint32;
#endif
enum UnicodeNormalizationOptions
{
Stable = (1<<1),
Compat = (1<<2),
Compose = (1<<3),
Decompose = (1<<4),
CaseFold = (1<<10),
CharBound = (1<<11),
Lump = (1<<12),
NFD = Stable | Decompose,
NFC = Stable | Compose,
NFKD = Stable | Decompose | Compat,
NFKC = Stable | Compose | Compat,
}
[CRepr]
class String : IHashable, IFormattable, IPrintable, IOpComparable
{
enum CreateFlags
{
None = 0,
NullTerminate = 1
}
int_strsize mLength;
uint_strsize mAllocSizeAndFlags;
char8* mPtr = null;
extern const String* sStringLiterals;
extern const String* sIdStringLiterals;
static String* sPrevInternLinkPtr; // For detecting changes to sStringLiterals for hot loads
static Monitor sMonitor = new Monitor() ~ delete _;
static HashSet<String> sInterns = new .() ~ delete _;
static List<String> sOwnedInterns = new .() ~ DeleteContainerAndItems!(_);
public const String Empty = "";
#if BF_LARGE_STRINGS
const uint64 cSizeFlags = 0x3FFFFFFF'FFFFFFFF;
const uint64 cDynAllocFlag = 0x80000000'00000000;
const uint64 cStrPtrFlag = 0x40000000'00000000;
#else
const uint32 cSizeFlags = 0x3FFFFFFF;
const uint32 cDynAllocFlag = 0x80000000;
const uint32 cStrPtrFlag = 0x40000000;
#endif
[AllowAppend]
public this(int count) // 0
{
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = 0;
}
[AllowAppend]
public this()
{
let bufferSize = 16 - sizeof(char8*);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = 0;
}
[AllowAppend]
public this(String str)
{
let count = str.mLength;
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
Internal.MemCpy(Ptr, str.Ptr, count);
mLength = count;
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
}
[AllowAppend]
public this(String str, int offset)
{
Debug.Assert((uint)offset <= (uint)str.Length);
let count = str.mLength - offset;
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
let srcPtr = str.Ptr;
for (int_strsize i = 0; i < count; i++)
ptr[i] = srcPtr[i + offset];
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = (int_strsize)count;
}
[AllowAppend]
public this(String str, int offset, int count)
{
Debug.Assert((uint)offset <= (uint)str.Length);
Debug.Assert(offset + count <= str.Length);
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
let srcPtr = str.Ptr;
for (int i = 0; i < count; i++)
ptr[i] = srcPtr[i + offset];
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = (int_strsize)count;
}
[AllowAppend]
public this(char8 c, int count)
{
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
for (int_strsize i = 0; i < count; i++)
ptr[i] = c;
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = (int_strsize)count;
}
[AllowAppend]
public this(char8* char8Ptr)
{
let count = Internal.CStrLen(char8Ptr);
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
for (int_strsize i = 0; i < count; i++)
ptr[i] = char8Ptr[i];
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = count;
}
[AllowAppend]
public this(char8* char8Ptr, int count)
{
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
for (int i = 0; i < count; i++)
ptr[i] = char8Ptr[i];
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = (int_strsize)count;
}
[AllowAppend]
public this(char16* char16Ptr)
{
let count = UTF16.GetLengthAsUTF8(char16Ptr);
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = 0;
UTF16.Decode(char16Ptr, this);
}
[AllowAppend]
public this(Span<char16> chars)
{
let count = UTF16.GetLengthAsUTF8(chars);
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = 0;
UTF16.Decode(chars, this);
}
[AllowAppend]
public this(StringView strView)
{
let tryBufferSize = strView.Length - sizeof(char8*);
let bufferSize = (tryBufferSize >= 0) ? tryBufferSize : 0;
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
Internal.MemCpy(ptr, strView.Ptr, strView.Length);
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = (int_strsize)strView.Length;
}
[AllowAppend]
public this(StringView strView, CreateFlags flags)
{
let count = strView.Length + (flags.HasFlag(.NullTerminate) ? 1 : 0);
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
Internal.MemCpy(ptr, strView.Ptr, strView.Length);
if (flags.HasFlag(.NullTerminate))
ptr[strView.Length] = 0;
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int32)sizeof(char8*);
mLength = (int32)strView.Length;
}
[AllowAppend]
public this(StringView strView, int offset)
{
Debug.Assert((uint)offset <= (uint)strView.Length);
let count = strView.Length - offset;
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
let srcPtr = strView.Ptr;
for (int i = 0; i < count; i++)
ptr[i] = srcPtr[i + offset];
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = (int_strsize)count;
}
[AllowAppend]
public this(StringView strView, int offset, int count)
{
Debug.Assert((uint)offset <= (uint)strView.Length);
Debug.Assert(offset + count <= strView.Length);
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
let srcPtr = strView.Ptr;
for (int i = 0; i < count; i++)
ptr[i] = srcPtr[i + offset];
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = (int_strsize)count;
}
[AllowAppend]
public this(char8[] chars, int offset, int count)
{
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
for (int i = 0; i < count; i++)
ptr[i] = chars[i + offset];
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
mLength = (int_strsize)count;
}
static int StrLengths(String[] strs)
{
int count = 0;
for (var str in strs)
count += str.Length;
return count;
}
[AllowAppend]
public this(params String[] strs)
{
int count = StrLengths(strs);
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
char8* addlPtr = append char8[bufferSize]*;
let ptr = Ptr;
int curIdx = 0;
for (var str in strs)
{
Internal.MemCpy(ptr + curIdx, str.Ptr, str.mLength);
curIdx += str.Length;
}
mLength = (int_strsize)count;
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
}
public ~this()
{
//TODO: Remove this!
//char8* checkPtr = (char8*)(void*)this + ((System.Reflection.TypeInstance)typeof(String)).mInstSize;
if (IsDynAlloc)
delete:this mPtr;
/*else if ((mPtr == checkPtr) && (*(int*)(void*)this & 8 == 0))
{
if (mPtr[mAllocSizeAndFlags] != 0xBF)
{
throw new Exception();
}
}*/
}
void FakeMethod ()
{
}
protected virtual void* Alloc(int size, int align)
{
return new char8[size]* {?};
}
protected virtual void Free(void* ptr)
{
delete ptr;
}
public int Length
{
[Inline]
get
{
return mLength;
}
set
{
Debug.Assert((uint)mLength <= (uint)value);
mLength = (int_strsize)value;
}
}
public int AllocSize
{
[Inline]
get
{
return (int_strsize)(mAllocSizeAndFlags & cSizeFlags);
}
}
public bool IsDynAlloc
{
[Inline]
get
{
return (mAllocSizeAndFlags & cDynAllocFlag) != 0;
}
}
public bool HasExternalPtr
{
get
{
return (mAllocSizeAndFlags & cStrPtrFlag) != 0;
}
}
public char8* Ptr
{
//[Optimize]
get
{
return ((mAllocSizeAndFlags & cStrPtrFlag) != 0) ? mPtr : (char8*)&mPtr;
}
}
public bool IsWhiteSpace
{
get
{
let ptr = Ptr;
for (int i = 0; i < mLength; i++)
if (!ptr[i].IsWhiteSpace)
return false;
return true;
}
}
public bool IsEmpty
{
get
{
return mLength == 0;
}
}
static int GetHashCode(char8* ptr, int length)
{
int charsLeft = length;
int hash = 0;
char8* curPtr = ptr;
let intSize = sizeof(int);
while (charsLeft >= intSize)
{
hash = (hash ^ *((int*)curPtr)) + (hash * 16777619);
charsLeft -= intSize;
curPtr += intSize;
}
while (charsLeft > 1)
{
hash = ((hash ^ (int)*curPtr) << 5) - hash;
charsLeft--;
curPtr++;
}
return hash;
}
public int GetHashCode()
{
return GetHashCode(Ptr, mLength);
}
public override void ToString(String strBuffer)
{
strBuffer.Append(this);
}
public static void QuoteString(char8* ptr, int length, String outString)
{
outString.Append('"');
for (int i < length)
{
char8 c = ptr[i];
switch (c)
{
case '\'': outString.Append(@"\'");
case '\"': outString.Append("\\\"");
case '\\': outString.Append(@"\\");
case '\0': outString.Append(@"\0");
case '\a': outString.Append(@"\a");
case '\b': outString.Append(@"\b");
case '\f': outString.Append(@"\f");
case '\n': outString.Append(@"\n");
case '\r': outString.Append(@"\r");
case '\t': outString.Append(@"\t");
case '\v': outString.Append(@"\v");
default:
if (c < (char8)32)
{
outString.Append(@"\x");
outString.Append(sHexUpperChars[((int)c>>4) & 0xF]);
outString.Append(sHexUpperChars[(int)c & 0xF]);
break;
}
outString.Append(c);
}
}
outString.Append('"');
}
public void QuoteString(String outString)
{
QuoteString(Ptr, Length, outString);
}
public static Result<void> UnQuoteString(char8* ptr, int length, String outString)
{
if (length < 2)
return .Err;
// Literal string?
if ((ptr[0] == '@') && (ptr[1] == '"') && (ptr[length - 1] == '\"'))
{
outString.Append(ptr + 2, length - 3);
return .Ok;
}
if ((*ptr != '\"') && (ptr[length - 1] != '\"'))
{
return .Err;
}
var ptr;
ptr++;
char8* endPtr = ptr + length - 2;
while (ptr < endPtr)
{
char8 c = *(ptr++);
if (c == '\\')
{
if (ptr == endPtr)
return .Err;
char8 nextC = *(ptr++);
switch (nextC)
{
case '\'': outString.Append("'");
case '\"': outString.Append("\"");
case '\\': outString.Append("\\");
case '0': outString.Append("\0");
case 'a': outString.Append("\a");
case 'b': outString.Append("\b");
case 'f': outString.Append("\f");
case 'n': outString.Append("\n");
case 'r': outString.Append("\r");
case 't': outString.Append("\t");
case 'v': outString.Append("\v");
default:
return .Err;
}
continue;
}
outString.Append(c);
}
return .Ok;
}
public Result<void> UnQuoteString(String outString)
{
return UnQuoteString(Ptr, Length, outString);
}
static String sHexUpperChars = "0123456789ABCDEF";
public void ToString(String outString, String format, IFormatProvider formatProvider)
{
if (format == "Q")
{
QuoteString(Ptr, mLength, outString);
return;
}
outString.Append(this);
}
void IPrintable.Print(String outString)
{
String.QuoteString(Ptr, mLength, outString);
}
[AlwaysInclude]
public char8* CStr()
{
EnsureNullTerminator();
return Ptr;
}
public static implicit operator char8*(String str)
{
if (str == null)
return null;
str.EnsureNullTerminator();
return str.Ptr;
}
public static bool operator==(String s1, String s2)
{
return Equals(s1, s2);
}
public static int operator<=>(String s1, String s2)
{
return String.Compare(s1, s2, false);
}
public void Clear()
{
mLength = 0;
}
public void Set(String str)
{
if ((Object)this == (Object)str)
return;
mLength = 0;
Append(str.Ptr, str.mLength);
}
public void Set(StringView str)
{
mLength = 0;
Append(str.Ptr, str.Length);
}
public void MoveTo(String str, bool keepRef = false)
{
if (IsDynAlloc)
{
if (str.IsDynAlloc)
{
delete str.mPtr;
}
str.mPtr = mPtr;
str.mAllocSizeAndFlags = mAllocSizeAndFlags;
str.mLength = mLength;
if (keepRef)
{
mAllocSizeAndFlags &= ~cDynAllocFlag;
}
else
{
mPtr = null;
mAllocSizeAndFlags = (int_strsize)sizeof(char8*);
mLength = 0;
}
}
else
{
str.Set(this);
if (!keepRef)
Clear();
}
}
public void Reference(String str)
{
if (IsDynAlloc)
delete:this mPtr;
mPtr = str.Ptr;
mLength = str.mLength;
mAllocSizeAndFlags = cStrPtrFlag;
}
public void Reference(char8* ptr, int length, int allocSize)
{
if (IsDynAlloc)
delete:this mPtr;
mPtr = ptr;
mLength = (int_strsize)length;
mAllocSizeAndFlags = cStrPtrFlag;
}
public void Reference(char8* ptr, int length)
{
if (IsDynAlloc)
delete:this mPtr;
mPtr = ptr;
mLength = (int_strsize)length;
mAllocSizeAndFlags = cStrPtrFlag;
}
public void Reference(StringView stringView)
{
Reference(stringView.Ptr, stringView.Length, stringView.Length);
}
public void Reference(char8* ptr)
{
if (IsDynAlloc)
delete:this mPtr;
mPtr = ptr;
mLength = StrLen(ptr);
mAllocSizeAndFlags = cStrPtrFlag;
}
// This is a fast way to remove characters are the beginning of a string, but only works for strings
// that are not dynamically allocated. Mostly useful for parsing through Referenced strings quickly.
public void AdjustPtr(int adjBytes)
{
Debug.Assert(!IsDynAlloc);
Debug.Assert(AllocSize == 0); // Assert is reference
Debug.Assert((uint)mLength >= (uint)adjBytes);
mPtr += adjBytes;
mLength -= (int_strsize)adjBytes;
}
int CalcNewSize(int minSize)
{
// Grow factor is 1.5
int bumpSize = AllocSize;
bumpSize += bumpSize / 2;
return (bumpSize > minSize) ? bumpSize : minSize;
}
void Realloc(int newSize)
{
Debug.Assert(AllocSize > 0, "String has been frozen");
Debug.Assert((uint)newSize <= cSizeFlags);
char8* newPtr = new:this char8[newSize]*;
Internal.MemCpy(newPtr, Ptr, mLength);
if (IsDynAlloc)
delete:this mPtr;
mPtr = newPtr;
mAllocSizeAndFlags = (uint_strsize)newSize | cDynAllocFlag | cStrPtrFlag;
}
public void Reserve(int size)
{
if (size > AllocSize)
Realloc(size);
}
void Realloc(char8* newPtr, int newSize)
{
Debug.Assert(AllocSize > 0, "String has been frozen");
Debug.Assert((uint)newSize <= cSizeFlags);
Internal.MemCpy(newPtr, Ptr, mLength);
if (IsDynAlloc)
delete:this mPtr;
mPtr = newPtr;
mAllocSizeAndFlags = (uint_strsize)newSize | cDynAllocFlag | cStrPtrFlag;
}
public static int_strsize StrLen(char8* str)
{
for (int_strsize i = 0; true; i++)
if (str[i] == (char8)0)
return i;
}
public void Append(StringView strView)
{
Append(strView.Ptr, strView.Length);
}
public void Append(Span<char16> utf16Str)
{
UTF16.Decode(utf16Str, this);
}
public void Append(char16* utf16Str)
{
UTF16.Decode(utf16Str, this);
}
public void Append(StringView str, int offset)
{
Debug.Assert((uint)offset <= (uint)str.[Friend]mLength);
Append(str.Ptr + offset, str.[Friend]mLength - offset);
}
public void Append(StringView str, int offset, int length)
{
Debug.Assert((uint)offset + (uint)length <= (uint)str.[Friend]mLength);
Append(str.Ptr + offset, length);
}
public void Append(char8* appendPtr)
{
int_strsize length = StrLen(appendPtr);
int_strsize newCurrentIndex = mLength + length;
char8* ptr;
if (newCurrentIndex > AllocSize)
{
// This handles appending to ourselves, we invalidate 'ptr' after calling Realloc
int newSize = CalcNewSize(newCurrentIndex);
char8* newPtr = new:this char8[newSize]*;
Internal.MemCpy(newPtr + mLength, appendPtr, length);
Realloc(newPtr, newSize);
ptr = newPtr;
}
else
{
ptr = Ptr;
Internal.MemCpy(ptr + mLength, appendPtr, length);
}
mLength = newCurrentIndex;
}
public void Append(char8* appendPtr, int length)
{
int newCurrentIndex = mLength + length;
char8* ptr;
if (newCurrentIndex > AllocSize)
{
// This handles appending to ourselves, we invalidate 'ptr' after calling Realloc
int newSize = CalcNewSize(newCurrentIndex);
char8* newPtr = new:this char8[newSize]*;
Internal.MemCpy(newPtr + mLength, appendPtr, length);
Realloc(newPtr, newSize);
ptr = newPtr;
}
else
{
ptr = Ptr;
Internal.MemCpy(ptr + mLength, appendPtr, length);
}
mLength = (int_strsize)newCurrentIndex;
}
public void Append(char8[] arr, int idx, int length)
{
int newCurrentIndex = mLength + length;
char8* ptr;
if (newCurrentIndex > AllocSize)
{
// This handles appending to ourselves, we invalidate 'ptr' after calling Realloc
int newSize = CalcNewSize(newCurrentIndex);
char8* newPtr = new:this char8[newSize]*;
Internal.MemCpy(newPtr + mLength, arr.CArray() + idx, length);
Realloc(newPtr, newSize);
ptr = newPtr;
}
else
{
ptr = Ptr;
Internal.MemCpy(ptr + mLength, arr.CArray() + idx, length);
}
mLength = (int_strsize)newCurrentIndex;
}
public char8* PrepareBuffer(int bytes)
{
Debug.Assert(bytes >= 0);
if (bytes <= 0)
return null;
int count = bytes;
if (mLength + count >= AllocSize)
Realloc(CalcNewSize(mLength + count + 1));
char8* ptr = Ptr + mLength;
mLength += (int_strsize)bytes;
return ptr;
}
// Appends a copy of this string at the end of this string builder.
public void Append(String value)
{
//Contract.Ensures(Contract.Result<String>() != null);
Append(value.Ptr, value.mLength);
}
public void Append(String str, int offset)
{
Debug.Assert((uint)offset <= (uint)str.mLength);
Append(str.Ptr + offset, str.mLength - offset);
}
public void Append(String str, int offset, int length)
{
Debug.Assert((uint)offset <= (uint)str.mLength);
Debug.Assert(length >= 0);
Debug.Assert((uint)offset + (uint)length <= (uint)str.mLength);
Append(str.Ptr + offset, length);
}
public void Append(params String[] strings)
{
for (var str in strings)
Append(str);
}
public void Append(char8 c)
{
if (mLength + 1 > AllocSize)
Realloc(CalcNewSize(mLength + 1));
let ptr = Ptr;
ptr[mLength++] = c;
}
public void Append(char8 c, int count = 1)
{
if (count == 0)
return;
if (mLength + count > AllocSize)
Realloc(CalcNewSize(mLength + count));
let ptr = Ptr;
for (int_strsize i = 0; i < count; i++)
ptr[mLength++] = c;
}
public void Append(char32 c, int count = 1)
{
if (count == 0)
return;
int encodedLen = UTF8.GetEncodedLength(c);
if (mLength + count * encodedLen > AllocSize)
Realloc(CalcNewSize(mLength + count * encodedLen));
let ptr = Ptr;
for (int_strsize i = 0; i < count; i++)
{
if (c < (char32)0x80)
{
ptr[mLength++] = (char8)c;
}
else if (c < (char32)0x800)
{
ptr[mLength++] = (char8)(c>>6) | (char8)0xC0;
ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
}
else if (c < (char32)0x10000)
{
ptr[mLength++] = (char8)(c>>12) | (char8)0xE0;
ptr[mLength++] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
}
else if (c < (char32)0x110000)
{
ptr[mLength++] = (char8)((c>>18) | (char8)0xF0);
ptr[mLength++] = (char8)((c>>12) & (char8)0x3F) | (char8)0x80;
ptr[mLength++] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
}
}
}
/*public void Substring(int idx, int len, String outBuffer)
{
outBuffer.Append(this, idx, len);
}*/
public void EnsureNullTerminator()
{
int allocSize = AllocSize;
if ((allocSize == mLength) || (Ptr[mLength] != 0))
{
if (mLength >= allocSize)
Realloc(CalcNewSize(mLength + 1));
Ptr[mLength] = 0;
}
}
public ref char8 this[int index]
{
get
{
Debug.Assert((uint)index < (uint)mLength);
return ref Ptr[index];
}
set
{
Debug.Assert((uint)index < (uint)mLength);
Ptr[index] = value;
}
}
public void Concat(params Object[] objects)
{
// This reserves the correct number of characters if it can
int totalLen = 0;
for (var obj in objects)
{
if (var str = obj as String)
{
totalLen += str.Length;
}
else if (obj is char8)
{
totalLen++;
}
else
{
totalLen = 0;
break;
}
}
if (totalLen > 0)
Reserve(mLength + totalLen);
for (var obj in objects)
{
obj.ToString(this);
}
}
public static String GetStringOrEmpty(String str)
{
return str ?? Empty;
}
public static bool IsNullOrEmpty(String str)
{
return (str == null) || (str.Length == 0);
}
public static bool IsNullOrWhiteSpace(String str)
{
if ((str == null) || (str.Length == 0))
return true;
let strPtr = str.Ptr;
for (int_strsize i = 0; i < str.mLength; i++)
{
if (!strPtr[i].IsWhiteSpace)
return false;
}
return true;
}
Result<void> FormatError()
{
return .Err;
}
/** Appends formatted text.
* @param provider The format provider
* @returns This method can fail if the format string is invalid.
*/
public Result<void> AppendF(IFormatProvider provider, StringView format, params Object[] args)
{
if (format.Ptr == null)
{
return .Err;
}
//Contract.Ensures(Contract.Result<StringBuilder>() != null);
//Contract.EndContractBlock();
int pos = 0;
int len = format.Length;
char8 ch = '\x00';
/*ICustomFormatter cf = null;
if (provider != null)
{
cf = (ICustomFormatter)provider.GetFormat(typeof(ICustomFormatter));
}*/
String s = null;
String fmt = "";
int autoArgIdx = 0;
while (true)
{
int p = pos;
int i = pos;
while (pos < len)
{
ch = format[pos];
pos++;
if (ch == '}')
{
if (pos < len && format[pos] == '}') // Treat as escape character for }}
pos++;
else
return FormatError();
}
if (ch == '{')
{
if (pos < len && format[pos] == '{') // Treat as escape character for {{
pos++;
else
{
pos--;
break;
}
}
Append(ch);
}
if (pos == len) break;
pos++;
int index = 0;
if (pos == len || (ch = format[pos]) < '0' || ch > '9')
{
if ((pos < len) &&
((ch == '}') || (ch == ':')))
index = autoArgIdx++;
else
return FormatError();
}
else
{
repeat
{
index = index * 10 + ch - '0';
pos++;
if (pos == len) return FormatError();
ch = format[pos];
}
while (ch >= '0' && ch <= '9' && index < 1000000);
}
if (index >= args.Count) return FormatError();
while (pos < len && (ch = format[pos]) == ' ') pos++;
bool leftJustify = false;
int width = 0;
if (ch == ',')
{
pos++;
while (pos < len && format[pos] == ' ') pos++;
if (pos == len) return FormatError();
ch = format[pos];
if (ch == '-')
{
leftJustify = true;
pos++;
if (pos == len) return FormatError();
ch = format[pos];
}
if (ch < '0' || ch > '9') return FormatError();
repeat
{
width = width * 10 + ch - '0';
pos++;
if (pos == len) return FormatError();
ch = format[pos];
}
while (ch >= '0' && ch <= '9' && width < 1000000);
}
while (pos < len && (ch = format[pos]) == ' ') pos++;
Object arg = args[index];
if (ch == ':')
{
if (fmt == "")
fmt = scope:: String(64);
else
fmt.Clear();
bool isFormatEx = false;
pos++;
p = pos;
i = pos;
while (true)
{
if (pos == len) return FormatError();
ch = format[pos];
pos++;
if (ch == '{')
{
isFormatEx = true;
if (pos < len && format[pos] == '{') // Treat as escape character for {{
pos++;
else
return FormatError();
}
else if (ch == '}')
{
// We only treat '}}' as an escape character if the format had an opening '{'. Otherwise we just close on the first '}'
if ((isFormatEx) && (pos < len && format[pos] == '}')) // Treat as escape character for }}
pos++;
else
{
pos--;
break;
}
}
if (fmt == null)
{
fmt = scope:: String(0x100);
}
fmt.Append(ch);
}
}
if (ch != '}') return FormatError();
pos++;
if (s == null)
s = scope:: String(128);
s.Clear();
IFormattable formattableArg = arg as IFormattable;
if (formattableArg != null)
formattableArg.ToString(s, fmt, provider);
else if (arg != null)
arg.ToString(s);
else
s.Append("null");
if (fmt != (Object)"")
fmt.Clear();
if (s == null) s = String.Empty;
int pad = width - s.Length;
if (!leftJustify && pad > 0) Append(' ', pad);
Append(s);
if (leftJustify && pad > 0) Append(' ', pad);
}
return .Ok;
}
public Result<void> AppendF(StringView format, params Object[] args)
{
return AppendF(null, format, params args);
}
public int IndexOf(StringView subStr, bool ignoreCase = false)
{
for (int ofs = 0; ofs <= Length - subStr.Length; ofs++)
{
if (Compare(Ptr+ofs, subStr.Length, subStr.Ptr, subStr.Length, ignoreCase) == 0)
return ofs;
}
return -1;
}
public int IndexOf(StringView subStr, int startIdx, bool ignoreCase = false)
{
for (int ofs = startIdx; ofs <= Length - subStr.Length; ofs++)
{
if (Compare(Ptr+ofs, subStr.Length, subStr.Ptr, subStr.Length, ignoreCase) == 0)
return ofs;
}
return -1;
}
public int Count(char8 c)
{
int count = 0;
let ptr = Ptr;
for (int i = 0; i < mLength; i++)
if (ptr[i] == c)
count++;
return count;
}
public int IndexOf(char8 c, int startIdx = 0)
{
let ptr = Ptr;
for (int i = startIdx; i < mLength; i++)
if (ptr[i] == c)
return i;
return -1;
}
public int LastIndexOf(char8 c)
{
let ptr = Ptr;
for (int i = mLength - 1; i >= 0; i--)
if (ptr[i] == c)
return i;
return -1;
}
public int LastIndexOf(char8 c, int startCheck)
{
let ptr = Ptr;
for (int i = startCheck; i >= 0; i--)
if (ptr[i] == c)
return i;
return -1;
}
public bool Contains(String str)
{
return IndexOf(str) != -1;
}
public bool Contains(String str, bool ignoreCase)
{
return IndexOf(str, ignoreCase) != -1;
}
public bool Contains(char8 c)
{
let ptr = Ptr;
for (int_strsize i = 0; i < mLength; i++)
if (ptr[i] == c)
return true;
return false;
}
public void Replace(char8 c, char8 newC)
{
let ptr = Ptr;
for (int_strsize i = 0; i < mLength; i++)
if (ptr[i] == c)
ptr[i] = newC;
}
void CaseConv(bool toUpper)
{
let ptr = Ptr;
int addSize = 0;
bool sizeChanged = false;
for (int i = 0; i < mLength; i++)
{
let c = toUpper ? ptr[i].ToUpper : ptr[i].ToLower;
if (c < '\x80')
{
ptr[i] = c;
continue;
}
var (c32, decLen) = UTF8.Decode(ptr + i, mLength - i);
if (c32 == (char32)-1)
return; // Error
let uc32 = toUpper ? c32.ToUpper : c32.ToLower;
int encLen = UTF8.Encode(uc32, .(ptr + i, decLen));
if (encLen != decLen)
{
// Put old char back
sizeChanged = true;
addSize += encLen - decLen;
encLen = UTF8.Encode(c32, .(ptr + i, decLen));
}
i += encLen - 1;
}
if (!sizeChanged)
return;
// Handle 'slow' resize case
if (sizeChanged)
{
int newSize = mLength + addSize + 1;
char8* newPtr = new:this char8[newSize]*;
int outIdx = 0;
for (int i = 0; i < mLength; i++)
{
let c = toUpper ? ptr[i].ToUpper : ptr[i].ToLower;
if (c < '\x80')
{
newPtr[outIdx++] = c;
continue;
}
var (c32, decLen) = UTF8.Decode(ptr + i, mLength - i);
if (c32 == (char32)-1)
return; // Error
c32 = toUpper ? c32.ToUpper : c32.ToLower;
int encLen = UTF8.Encode(c32, .(newPtr + outIdx, newSize - outIdx));
i += decLen - 1;
outIdx += encLen;
}
newPtr[outIdx] = '\0';
if (IsDynAlloc)
delete mPtr;
mPtr = newPtr;
mAllocSizeAndFlags = (uint_strsize)newSize | cDynAllocFlag | cStrPtrFlag;
}
}
public void ToUpper()
{
CaseConv(true);
}
public void ToLower()
{
CaseConv(false);
}
//[CLink]
//static extern int utf8proc_map(char8* str, int strlen, out char8* outStr, int options);
static extern int UTF8GetAllocSize(char8* str, int strlen, int32 options);
static extern int UTF8Map(char8* str, int strlen, char8* outStr, int outSize, int32 options);
public Result<void> Normalize(UnicodeNormalizationOptions unicodeNormalizationOptions = .NFC)
{
int allocSize = UTF8GetAllocSize(Ptr, mLength, (int32)unicodeNormalizationOptions);
if (allocSize < 0)
return .Err;
char8* newStr = (char8*)Alloc(allocSize, 1);
int newLen = UTF8Map(Ptr, mLength, newStr, allocSize, (int32)unicodeNormalizationOptions);
if (IsDynAlloc)
delete:this mPtr;
mPtr = newStr;
mLength = (int_strsize)newLen;
mAllocSizeAndFlags = (uint32)(allocSize) | cDynAllocFlag | cStrPtrFlag;
return .Ok;
}
public Result<void> Normalize(String destStr, UnicodeNormalizationOptions unicodeNormalizationOptions = .NFC)
{
if (destStr == (Object)this)
return Normalize(unicodeNormalizationOptions);
int allocSize = UTF8GetAllocSize(Ptr, mLength, (int32)unicodeNormalizationOptions);
if (allocSize < 0)
{
// Just append unnormalized
destStr.Append(this);
return .Err;
}
char8* newStr = (char8*)Alloc(allocSize, 1);
int newLen = UTF8Map(Ptr, mLength, newStr, allocSize, (int32)unicodeNormalizationOptions);
if (destStr.IsDynAlloc)
delete:destStr destStr.mPtr;
destStr.mPtr = newStr;
destStr.mLength = (int_strsize)newLen;
destStr.mAllocSizeAndFlags = (uint_strsize)(newLen + 1) | cDynAllocFlag | cStrPtrFlag;
return .Ok;
}
public void Remove(int startIdx, int length)
{
Contract.Requires((startIdx >= 0) && (length >= 0) && (startIdx + length <= mLength));
int moveCount = mLength - startIdx - length;
let ptr = Ptr;
if (moveCount > 0)
Internal.MemMove(ptr + startIdx, ptr + startIdx + length, mLength - startIdx - length);
mLength -= (int_strsize)length;
}
public void Remove(int char8Idx)
{
Remove(char8Idx, 1);
}
public void RemoveToEnd(int startIdx)
{
Remove(startIdx, mLength - startIdx);
}
public void RemoveFromEnd(int length)
{
Remove(mLength - length, length);
}
public void Insert(int idx, StringView addString)
{
Contract.Requires(idx >= 0);
int_strsize length = (int_strsize)addString.Length;
int_strsize newLength = mLength + length;
if (newLength > AllocSize)
{
int newSize = Math.Max(AllocSize * 2, newLength);
Realloc(newSize);
}
let moveChars = mLength - idx;
let ptr = Ptr;
if (moveChars > 0)
Internal.MemMove(ptr + idx + length, ptr + idx, moveChars);
Internal.MemCpy(ptr + idx, addString.Ptr, length);
mLength = newLength;
}
public void Insert(int_strsize idx, char8 c)
{
Contract.Requires(idx >= 0);
int_strsize newLength = mLength + 1;
if (newLength > AllocSize)
{
int newSize = Math.Max(AllocSize * 2, newLength);
Realloc(newSize);
}
let moveChars = mLength - idx;
let ptr = Ptr;
if (moveChars > 0)
Internal.MemMove(ptr + idx + 1, ptr + idx, moveChars);
ptr[idx] = c;
mLength = newLength;
}
public void Insert(int_strsize idx, char8 c, int count)
{
Contract.Requires(idx >= 0);
int_strsize newLength = mLength + (int_strsize)count;
if (newLength > AllocSize)
{
int newSize = Math.Max(AllocSize * 2, newLength);
Realloc(newSize);
}
let moveChars = mLength - idx;
let ptr = Ptr;
if (moveChars > 0)
Internal.MemMove(ptr + idx + count, ptr + idx, moveChars);
for (int i < count)
ptr[idx + i] = c;
mLength = newLength;
}
static bool EqualsHelper(char8* a, char8* b, int length)
{
for (int i = 0; i < length; i++)
if (a[i] != b[i])
return false;
return true;
}
static bool EqualsIgnoreCaseHelper(char8* a, char8* b, int length)
{
char8* curA = a;
char8* curB = b;
int curLength = length;
/*Contract.Requires(strA != null);
Contract.Requires(strB != null);
Contract.EndContractBlock();*/
while (curLength != 0)
{
int_strsize charA = (int_strsize)*curA;
int_strsize charB = (int_strsize)*curB;
//Contract.Assert((char8A | char8B) <= 0x7F, "strings have to be ASCII");
// uppercase both char8s - notice that we need just one compare per char8
if ((uint32)(charA - 'a') <= (uint32)('z' - 'a')) charA -= 0x20;
if ((uint32)(charB - 'a') <= (uint32)('z' - 'a')) charB -= 0x20;
//Return the (case-insensitive) difference between them.
if (charA != charB)
return false;
// Next char8
curA++;curB++;
curLength--;
}
return true;
}
private static int CompareOrdinalIgnoreCaseHelper(String strA, String strB)
{
/*Contract.Requires(strA != null);
Contract.Requires(strB != null);
Contract.EndContractBlock();*/
int_strsize length = Math.Min(strA.mLength, strB.mLength);
char8* a = strA.Ptr;
char8* b = strB.Ptr;
while (length != 0)
{
int_strsize charA = (int_strsize)*a;
int_strsize charB = (int_strsize)*b;
//Contract.Assert((char8A | char8B) <= 0x7F, "strings have to be ASCII");
// uppercase both char8s - notice that we need just one compare per char8
if ((uint32)(charA - 'a') <= (uint32)('z' - 'a')) charA -= 0x20;
if ((uint32)(charB - 'a') <= (uint32)('z' - 'a')) charB -= 0x20;
//Return the (case-insensitive) difference between them.
if (charA != charB)
return charA - charB;
// Next char8
a++;b++;
length--;
}
return strA.mLength - strB.mLength;
}
private static int CompareOrdinalIgnoreCaseHelper(char8* strA, int lengthA, char8* strB, int lengthB)
{
char8* a = strA;
char8* b = strB;
int length = Math.Min(lengthA, lengthB);
while (length != 0)
{
int_strsize charA = (int_strsize)*a;
int_strsize charB = (int_strsize)*b;
//Contract.Assert((char8A | char8B) <= 0x7F, "strings have to be ASCII");
// uppercase both char8s - notice that we need just one compare per char8
if ((uint32)(charA - 'a') <= (uint32)('z' - 'a')) charA -= 0x20;
if ((uint32)(charB - 'a') <= (uint32)('z' - 'a')) charB -= 0x20;
//Return the (case-insensitive) difference between them.
if (charA != charB)
return charA - charB;
// Next char8
a++;b++;
length--;
}
return lengthA - lengthB;
}
private static int CompareOrdinalIgnoreCaseHelper(String strA, int indexA, int lengthA, String strB, int indexB, int lengthB)
{
return CompareOrdinalIgnoreCaseHelper(strA.Ptr + indexA, lengthA, strB.Ptr + indexB, lengthB);
}
private static int CompareOrdinalHelper(char8* strA, int lengthA, char8* strB, int lengthB)
{
char8* a = strA;
char8* b = strB;
int length = Math.Min(lengthA, lengthB);
while (length != 0)
{
int_strsize char8A = (int_strsize)*a;
int_strsize char8B = (int_strsize)*b;
//Return the (case-insensitive) difference between them.
if (char8A != char8B)
return char8A - char8B;
// Next char8
a++;b++;
length--;
}
return lengthA - lengthB;
}
public static int Compare(char8* strA, int lengthA, char8* strB, int lengthB, bool ignoreCase)
{
if (ignoreCase)
return CompareOrdinalIgnoreCaseHelper(strA, lengthA, strB, lengthB);
return CompareOrdinalHelper(strA, lengthA, strB, lengthB);
}
private static int CompareOrdinalHelper(String strA, int indexA, int lengthA, String strB, int indexB, int lengthB)
{
return CompareOrdinalHelper(strA.Ptr + indexA, lengthA, strB.Ptr + indexB, lengthB);
}
public int CompareTo(String strB, bool ignoreCase = false)
{
if (ignoreCase)
return CompareOrdinalIgnoreCaseHelper(Ptr, Length, strB.Ptr, strB.Length);
return CompareOrdinalHelper(Ptr, Length, strB.Ptr, strB.Length);
}
public static int Compare(String strA, String strB, bool ignoreCase)
{
//they can't both be null;
if (strA == null)
{
return -1;
}
if (strB == null)
{
return 1;
}
if (ignoreCase)
return CompareOrdinalIgnoreCaseHelper(strA.Ptr, strA.Length, strB.Ptr, strB.Length);
return CompareOrdinalHelper(strA.Ptr, strA.Length, strB.Ptr, strB.Length);
//return Compare(strA, 0, strB, 0, strB.Length, ignoreCase);
}
public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
{
int lengthA = length;
int lengthB = length;
if (strA!=null)
{
if (strA.Length - indexA < lengthA)
{
lengthA = (strA.Length - indexA);
}
}
if (strB!=null)
{
if (strB.Length - indexB < lengthB)
{
lengthB = (strB.Length - indexB);
}
}
if (ignoreCase)
return CompareOrdinalIgnoreCaseHelper(strA, indexA, lengthA, strB, indexB, lengthB);
return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
/*if (ignoreCase) {
return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
}
return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);*/
}
public bool Equals(String b, StringComparison comparisonType = StringComparison.Ordinal)
{
return String.Equals(this, b, comparisonType);
}
public static bool Equals(String a, String b, StringComparison comparisonType = StringComparison.Ordinal)
{
if ((Object)a == (Object)b)
return true;
if ((Object)a == null || (Object)b == null)
return false;
if (a.mLength != b.mLength)
return false;
if (comparisonType == StringComparison.OrdinalIgnoreCase)
return EqualsIgnoreCaseHelper(a.Ptr, b.Ptr, a.mLength);
return EqualsHelper(a.Ptr, b.Ptr, a.mLength);
}
public bool StartsWith(StringView b, StringComparison comparisonType = StringComparison.Ordinal)
{
if (mLength < b.[Friend]mLength)
return false;
if (comparisonType == StringComparison.OrdinalIgnoreCase)
return EqualsIgnoreCaseHelper(this.Ptr, b.Ptr, b.Length);
return EqualsHelper(this.Ptr, b.Ptr, b.[Friend]mLength);
}
public bool EndsWith(StringView b, StringComparison comparisonType = StringComparison.Ordinal)
{
if (mLength < b.[Friend]mLength)
return false;
if (comparisonType == StringComparison.OrdinalIgnoreCase)
return EqualsIgnoreCaseHelper(Ptr + mLength - b.[Friend]mLength, b.Ptr, b.[Friend]mLength);
return EqualsHelper(this.Ptr + mLength - b.[Friend]mLength, b.Ptr, b.[Friend]mLength);
}
public bool StartsWith(char8 c)
{
if (mLength == 0)
return false;
return Ptr[0] == c;
}
public bool StartsWith(char32 c)
{
if (c < '\x80')
return StartsWith((char8)c);
if (mLength == 0)
return false;
return UTF8.Decode(Ptr, mLength).c == c;
}
public bool EndsWith(char8 c)
{
if (mLength == 0)
return false;
return Ptr[mLength - 1] == c;
}
public bool EndsWith(char32 c)
{
if (c < '\x80')
return EndsWith((char8)c);
if (mLength == 0)
return false;
char8* ptr = Ptr;
int idx = mLength - 1;
while ((idx > 0) && ((uint8)ptr[idx] & 0xC0 == 0x80))
idx--;
return UTF8.Decode(ptr + idx, mLength - idx).c == c;
}
public void ReplaceLargerHelper(String find, String replace)
{
List<int> replaceEntries = scope List<int>(8192);
int_strsize moveOffset = replace.mLength - find.mLength;
for (int startIdx = 0; startIdx <= mLength - find.mLength; startIdx++)
{
if (EqualsHelper(Ptr + startIdx, find.Ptr, find.mLength))
{
replaceEntries.Add(startIdx);
startIdx += find.mLength - 1;
}
}
if (replaceEntries.Count == 0)
return;
int destLength = mLength + moveOffset * replaceEntries.Count;
int needSize = destLength;
if (needSize > AllocSize)
Realloc((int_strsize)needSize);
let replacePtr = replace.Ptr;
let ptr = Ptr;
int lastDestStartIdx = destLength;
for (int moveIdx = replaceEntries.Count - 1; moveIdx >= 0; moveIdx--)
{
int srcStartIdx = replaceEntries[moveIdx];
int srcEndIdx = srcStartIdx + find.mLength;
int destStartIdx = srcStartIdx + moveIdx * moveOffset;
int destEndIdx = destStartIdx + replace.mLength;
for (int i = lastDestStartIdx - destEndIdx - 1; i >= 0; i--)
ptr[destEndIdx + i] = ptr[srcEndIdx + i];
for (int i < replace.mLength)
ptr[destStartIdx + i] = replacePtr[i];
lastDestStartIdx = destStartIdx;
}
mLength = (int_strsize)destLength;
}
public void Replace(String find, String replace)
{
if (replace.mLength > find.mLength)
{
ReplaceLargerHelper(find, replace);
return;
}
let ptr = Ptr;
let findPtr = find.Ptr;
let replacePtr = replace.Ptr;
int_strsize inIdx = 0;
int_strsize outIdx = 0;
if (find.Length == 1)
{
// Optimized version for single-character replacements
char8 findC = find[0];
while (inIdx < mLength - find.mLength)
{
if (ptr[inIdx] == findC)
{
for (int i = 0; i < replace.mLength; i++)
ptr[outIdx++] = replacePtr[i];
inIdx += find.mLength;
}
else if (inIdx == outIdx)
{
++inIdx;
++outIdx;
}
else // We need to physically move characters once we've found an equal span
{
ptr[outIdx++] = ptr[inIdx++];
}
}
}
else
{
while (inIdx <= mLength - find.mLength)
{
if (EqualsHelper(ptr + inIdx, findPtr, find.mLength))
{
for (int i = 0; i < replace.mLength; i++)
ptr[outIdx++] = replacePtr[i];
inIdx += find.mLength;
}
else if (inIdx == outIdx)
{
++inIdx;
++outIdx;
}
else // We need to physically move characters once we've found an equal span
{
ptr[outIdx++] = ptr[inIdx++];
}
}
}
while (inIdx < mLength)
{
if (inIdx == outIdx)
{
++inIdx;
++outIdx;
}
else
{
ptr[outIdx++] = ptr[inIdx++];
}
}
mLength = outIdx;
}
public void TrimEnd()
{
let ptr = Ptr;
for (int i = mLength - 1; i >= 0; i--)
{
char8 c = ptr[i];
if (c >= (char8)0x80)
{
var (c32, idx, len) = GetChar32WithBacktrack(i);
if (!c32.IsWhiteSpace)
{
if (i < mLength - 1)
RemoveToEnd(i + 1);
return;
}
i = idx;
}
else if (!c.IsWhiteSpace)
{
if (i < mLength - 1)
RemoveToEnd(i + 1);
return;
}
}
Clear();
}
public void TrimStart()
{
let ptr = Ptr;
for (int i = 0; i < mLength; i++)
{
char8 c = ptr[i];
if (c >= (char8)0x80)
{
var (c32, len) = GetChar32(i);
if (!c32.IsWhiteSpace)
{
if (i > 0)
Remove(0, i);
return;
}
i += len - 1;
}
else if (!c.IsWhiteSpace)
{
if (i > 0)
Remove(0, i);
return;
}
}
Clear();
}
public void Trim()
{
TrimStart();
TrimEnd();
}
public void Join(StringView sep, IEnumerator<String> enumerable)
{
bool isFirst = true;
for (var str in enumerable)
{
if (!isFirst)
Append(sep);
Append(str);
isFirst = false;
}
}
public void Join(StringView sep, IEnumerator<StringView> enumerable)
{
bool isFirst = true;
for (var str in enumerable)
{
if (!isFirst)
Append(sep);
Append(str);
isFirst = false;
}
}
public void Join(String separator, params String[] values)
{
for (int i = 0; i < values.Count; i++)
{
if (i > 0)
Append(separator);
values[i].ToString(this);
}
}
public StringSplitEnumerator Split(char8 c)
{
return StringSplitEnumerator(Ptr, Length, c, Int32.MaxValue, StringSplitOptions.None);
}
public StringSplitEnumerator Split(char8 separator, int count)
{
return StringSplitEnumerator(Ptr, Length, separator, count, StringSplitOptions.None);
}
public StringSplitEnumerator Split(char8 separator, StringSplitOptions options)
{
return StringSplitEnumerator(Ptr, Length, separator, Int32.MaxValue, options);
}
public StringSplitEnumerator Split(char8 separator, int count, StringSplitOptions options)
{
return StringSplitEnumerator(Ptr, Length, separator, count, options);
}
public StringSplitEnumerator Split(params char8[] separators)
{
return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
}
public StringSplitEnumerator Split(char8[] separators)
{
return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
}
public StringSplitEnumerator Split(char8[] separators, int count)
{
return StringSplitEnumerator(Ptr, Length, separators, count, StringSplitOptions.None);
}
public StringSplitEnumerator Split(char8[] separators, int count, StringSplitOptions options)
{
return StringSplitEnumerator(Ptr, Length, separators, count, options);
}
public static mixin NewOrSet(var target, var source)
{
if (target == null)
target = new String(source);
else
target.Set(source);
}
public static mixin DupIfReferenceEqual(var inStr, var outStr)
{
if ((Object)inStr == (Object)outStr)
inStr = scope:: String(outStr);
}
public static mixin StackSplit(var target, var splitChar)
{
var strings = scope:mixin List<String>();
for (var strView in target.Split(splitChar))
strings.Add(scope:mixin String(strView));
strings
}
public mixin Split(var splitChar)
{
int count = 0;
for (this.Split(splitChar))
count++;
var stringArr = scope:mixin StringView[count];
int idx = 0;
for (var strView in Split(splitChar))
stringArr[idx++] = strView;
stringArr
}
public mixin ToScopedNativeWChar()
{
int encodedLen = UTF16.GetEncodedLen(this);
char16* buf;
if (encodedLen < 128)
{
buf = scope:mixin char16[encodedLen]* { ? };
}
else
{
buf = new char16[encodedLen]* { ? };
defer:mixin delete buf;
}
UTF16.Encode(this, buf, encodedLen);
buf
}
public static bool Equals(char8* str1, char8* str2)
{
for (int i = 0; true; i++)
{
char8 c = str1[i];
char8 c2 = str2[i];
if (c != c2)
return false;
if ((c == (char8)0) || (c2 == (char8)0))
return ((c == (char8)0) && (c2 == (char8)0));
}
}
public RawEnumerator RawChars
{
get
{
return RawEnumerator(Ptr, 0, mLength);
}
}
public UTF8Enumerator DecodedChars
{
get
{
return UTF8Enumerator(Ptr, 0, mLength);
}
}
public UTF8Enumerator DecodedChars(int startIdx)
{
return UTF8Enumerator(Ptr, startIdx, mLength);
}
public bool HasMultibyteChars()
{
char8* ptr = Ptr;
int len = Length;
for (int i < len)
if (ptr[i] >= '\x80')
return true;
return false;
}
public (char32 c, int8 length) GetChar32(int idx)
{
Debug.Assert((uint)idx < (uint)mLength);
char8* ptr = Ptr;
char8 c = ptr[idx];
if (c < '\x80')
return (c, 1);
if (((uint8)ptr[idx] & 0xC0) == 0x80)
return (0, 0); // Invalid UTF8 data
return UTF8.Decode(ptr + idx, mLength - idx);
}
public (char32 c, int idx, int8 length) GetChar32WithBacktrack(int idx)
{
Debug.Assert((uint)idx < (uint)mLength);
char8* ptr = Ptr;
char8 c = ptr[idx];
if (c < '\x80')
return (c, idx, 1);
var idx;
while (((uint8)ptr[idx] & 0xC0) == 0x80)
{
if (idx == 0) // Invalid UTF8 data
return (0, 0, 0);
idx--;
}
let (c32, len) = UTF8.Decode(ptr + idx, mLength - idx);
return (c32, idx, len);
}
public (int startIdx, int length) GetCodePointSpan(int idx)
{
char8* ptr = Ptr;
int startIdx = idx;
// Move to start of char
while (startIdx >= 0)
{
char8 c = ptr[startIdx];
if (((uint8)c & 0x80) == 0)
return (startIdx, 1);
if (((uint8)c & 0xC0) != 0x80)
break;
startIdx--;
}
return (startIdx, UTF8.GetDecodedLength(ptr + startIdx));
}
public (int startIdx, int length) GetGraphemeClusterSpan(int idx)
{
char8* ptr = Ptr;
int startIdx = idx;
// Move to start of char
while (startIdx >= 0)
{
char8 c = ptr[startIdx];
if (((uint8)c & 0x80) == 0)
{
// This is the simple and fast case - ASCII followed by the string end or more ASCII
if ((startIdx == mLength - 1) || ((uint8)ptr[startIdx + 1] & 0x80) == 0)
return (startIdx, 1);
break;
}
if (((uint8)c & 0xC0) != 0x80)
{
let (c32, cLen) = UTF8.Decode(ptr + startIdx, mLength - startIdx);
if (!c32.IsCombiningMark)
break;
}
startIdx--;
}
int curIdx = startIdx;
while (true)
{
let (c32, cLen) = UTF8.Decode(ptr + curIdx, mLength - curIdx);
int nextIdx = curIdx + cLen;
if ((curIdx != startIdx) && (!c32.IsCombiningMark))
return (startIdx, curIdx - startIdx);
if (nextIdx == mLength)
return (startIdx, nextIdx - startIdx);
curIdx = nextIdx;
}
}
static void CheckLiterals(String* ptr)
{
var ptr;
String* prevList = *((String**)(ptr++));
if (prevList != null)
{
CheckLiterals(prevList);
}
while (true)
{
String str = *(ptr++);
if (str == null)
break;
sInterns.Add(str);
}
}
public String Intern()
{
using (sMonitor.Enter())
{
bool needsLiteralPass = sInterns.Count == 0;
String* internalLinkPtr = *((String**)(sStringLiterals));
if (internalLinkPtr != sPrevInternLinkPtr)
{
sPrevInternLinkPtr = internalLinkPtr;
needsLiteralPass = true;
}
if (needsLiteralPass)
CheckLiterals(sStringLiterals);
String* entryPtr;
if (sInterns.TryAdd(this, out entryPtr))
{
String result = new String(mLength + 1);
result.Append(this);
result.EnsureNullTerminator();
*entryPtr = result;
sOwnedInterns.Add(result);
return result;
}
return *entryPtr;
}
}
public struct RawEnumerator : IRefEnumerator<char8>
{
char8* mPtr;
int_strsize mIdx;
int_strsize mLength;
public this(char8* ptr, int idx, int length)
{
mPtr = ptr;
mIdx = (int_strsize)idx - 1;
mLength = (int_strsize)length;
}
public char8 Current
{
get
{
return mPtr[mIdx];
}
set
{
mPtr[mIdx] = value;
}
}
public ref char8 CurrentRef
{
get
{
return ref mPtr[mIdx];
}
}
public int Index
{
get
{
return mIdx;
}
}
public int Length
{
get
{
return mLength;
}
}
public void Dispose()
{
}
public void Reset()
{
}
public bool MoveNext() mut
{
++mIdx;
if (mIdx >= mLength)
return false;
return true;
}
public Result<char8> GetNext() mut
{
if (!MoveNext())
return .Err;
return Current;
}
public Result<char8*> GetNextRef() mut
{
if (!MoveNext())
return .Err;
return &CurrentRef;
}
}
public struct UTF8Enumerator : IEnumerator<char32>
{
char8* mPtr;
int_strsize mNextIndex;
int_strsize mLength;
char32 mChar;
public this(char8* ptr, int idx, int length)
{
mPtr = ptr;
mNextIndex = (int_strsize)idx;
mLength = (int_strsize)length;
mChar = 0;
}
public int NextIndex
{
get
{
return mNextIndex;
}
set mut
{
mNextIndex = (int_strsize)value;
}
}
public char32 Current
{
get
{
return mChar;
}
}
public bool MoveNext() mut
{
let strLen = mLength;
if (mNextIndex >= strLen)
return false;
mChar = (char32)mPtr[mNextIndex++];
if (mChar < (char32)0x80)
return true;
#if BF_UTF_PEDANTIC
// If this fails then we are starting on a trailing byte
Debug.Assert((mChar & (char32)0xC0) != (char32)0x80);
#endif
int8 trailingBytes = UTF8.sTrailingBytesForUTF8[mChar];
if (mNextIndex + trailingBytes > strLen)
return true;
switch (trailingBytes)
{
case 3: mChar <<= 6; mChar += (int32)mPtr[mNextIndex++]; fallthrough;
case 2: mChar <<= 6; mChar += (int32)mPtr[mNextIndex++]; fallthrough;
case 1: mChar <<= 6; mChar += (int32)mPtr[mNextIndex++]; fallthrough;
}
mChar -= (int32)UTF8.sOffsetsFromUTF8[trailingBytes];
return true;
}
public void Reset() mut
{
mNextIndex = -1;
}
public void Dispose()
{
}
public Result<char32> GetNext() mut
{
if (!MoveNext())
return .Err;
return Current;
}
}
}
public enum StringSplitOptions
{
None = 0,
RemoveEmptyEntries = 1
}
struct StringSplitEnumerator : IEnumerator<StringView>
{
StringSplitOptions mSplitOptions;
char8 mSplitChar0;
char8[] mSplitChars;
char8* mPtr;
int_strsize mStrLen;
int32 mCurCount;
int32 mMaxCount;
int_strsize mPos;
int_strsize mMatchPos;
public this(char8* ptr, int strLength, char8[] splitChars, int count, StringSplitOptions splitOptions)
{
mPtr = ptr;
mStrLen = (int_strsize)strLength;
if (splitChars.Count > 0)
mSplitChar0 = splitChars[0];
else
mSplitChar0 = '\0';
mSplitChars = splitChars;
mCurCount = 0;
mMaxCount = (int32)count;
mPos = 0;
mMatchPos = -1;
mSplitOptions = splitOptions;
}
public this(char8* ptr, int strLength, char8 splitChar, int count, StringSplitOptions splitOptions)
{
mPtr = ptr;
mStrLen = (int_strsize)strLength;
mSplitChar0 = splitChar;
mSplitChars = null;
mCurCount = 0;
mMaxCount = (int32)count;
mPos = 0;
mMatchPos = -1;
mSplitOptions = splitOptions;
}
public StringView Current
{
get
{
return StringView(mPtr + mPos, mMatchPos - mPos);
}
}
public int_strsize Pos
{
get
{
return mPos;
}
}
public int_strsize MatchPos
{
get
{
return mMatchPos;
}
}
public bool HasMore
{
get
{
return mMatchPos < mStrLen;
}
}
public bool MoveNext() mut
{
if (mCurCount >= mMaxCount)
return false;
mPos = mMatchPos + 1;
mCurCount++;
if (mCurCount == mMaxCount)
{
mMatchPos = (int_strsize)mStrLen;
if (mPos > mMatchPos)
return false;
if ((mMatchPos == mPos) && (mSplitOptions.HasFlag(.RemoveEmptyEntries)))
return false;
return true;
}
int endDiff = mStrLen - mMatchPos;
if (endDiff == 0)
return false;
while (true)
{
mMatchPos++;
endDiff--;
bool foundMatch = false;
if (endDiff == 0)
{
foundMatch = true;
}
else
{
char8 c = mPtr[mMatchPos];
if (c == mSplitChar0)
{
foundMatch = true;
}
else if (mSplitChars != null)
{
for (int i = 1; i < mSplitChars.Count; i++)
if (c == mSplitChars[i])
foundMatch = true;
}
}
if (foundMatch)
{
if ((mMatchPos > mPos + 1) || (!mSplitOptions.HasFlag(StringSplitOptions.RemoveEmptyEntries)))
return true;
mPos = mMatchPos + 1;
if (mPos >= mStrLen)
return false;
}
}
}
public void Reset() mut
{
mPos = 0;
mMatchPos = -1;
}
public void Dispose()
{
}
public Result<StringView> GetNext() mut
{
if (!MoveNext())
return .Err;
return Current;
}
}
public struct StringView : Span<char8>, IFormattable, IPrintable, IOpEquals<String>, IHashable
{
public this()
{
mPtr = null;
mLength = 0;
}
public this(String string)
{
mPtr = string.Ptr;
mLength = string.Length;
}
public this(String string, int offset)
{
Debug.Assert((uint)offset <= (uint)string.Length);
mPtr = string.Ptr + offset;
mLength = string.Length - offset;
}
public this(String string, int offset, int length)
{
Debug.Assert((uint)offset + (uint)length <= (uint)string.Length);
mPtr = string.Ptr + offset;
mLength = length;
}
public this(StringView stringView)
{
mPtr = stringView.mPtr;
mLength = stringView.mLength;
}
public this(StringView stringView, int offset)
{
Debug.Assert((uint)offset <= (uint)stringView.Length);
mPtr = stringView.mPtr + offset;
mLength = stringView.mLength - offset;
}
public this(StringView stringView, int offset, int length)
{
Debug.Assert((uint)offset + (uint)length <= (uint)stringView.Length);
mPtr = stringView.mPtr + offset;
mLength = length;
}
public this(char8[] arr, int offset, int length)
{
Debug.Assert((uint)offset + (uint)length <= (uint)arr.Count);
mPtr = arr.CArray() + offset;
mLength = length;
}
public this(char8* ptr)
{
mPtr = ptr;
mLength = String.StrLen(ptr);
}
public this(char8* ptr, int length)
{
mPtr = ptr;
mLength = length;
}
public String.RawEnumerator RawChars
{
get
{
return String.RawEnumerator(Ptr, 0, mLength);
}
}
public String.UTF8Enumerator DecodedChars
{
get
{
return String.UTF8Enumerator(Ptr, 0, mLength);
}
}
public bool IsWhiteSpace
{
get
{
for (int i = 0; i < mLength; i++)
if (!mPtr[i].IsWhiteSpace)
return false;
return true;
}
}
public int GetHashCode()
{
return String.[Friend]GetHashCode(mPtr, mLength);
}
public override void ToString(String strBuffer)
{
strBuffer.Append(mPtr, mLength);
}
public void ToString(String outString, String format, IFormatProvider formatProvider)
{
if (format == "Q")
{
String.QuoteString(mPtr, mLength, outString);
return;
}
outString.Append(mPtr, mLength);
}
void IPrintable.Print(String outString)
{
String.QuoteString(mPtr, mLength, outString);
}
public static bool operator==(StringView val1, StringView val2)
{
if (val1.mLength != val2.mLength)
return false;
char8* ptr1 = val1.mPtr;
char8* ptr2 = val2.mPtr;
if (ptr1 == ptr2)
return true;
if ((ptr1 == null) || (ptr2 == null))
return false;
return String.[Friend]EqualsHelper(ptr1, ptr2, val1.mLength);
}
public static bool operator==(StringView val1, String val2)
{
if (val1.mLength != val2.Length)
return false;
char8* ptr1 = val1.mPtr;
char8* ptr2 = val2.Ptr;
if (ptr1 == ptr2)
return true;
if ((ptr1 == null) || (ptr2 == null))
return false;
return String.[Friend]EqualsHelper(ptr1, ptr2, val1.mLength);
}
public static int Compare(StringView val1, StringView val2, bool ignoreCase = false)
{
if (ignoreCase)
return String.[Friend]CompareOrdinalIgnoreCaseHelper(val1.mPtr, val1.mLength, val2.mPtr, val2.mLength);
else
return String.[Friend]CompareOrdinalHelper(val1.mPtr, val1.mLength, val2.mPtr, val2.mLength);
}
public bool Equals(StringView str)
{
if (mLength != str.[Friend]mLength)
return false;
return String.[Friend]EqualsHelper(str.Ptr, mPtr, mLength);
}
public bool Equals(StringView str, bool ignoreCase)
{
if (mLength != str.[Friend]mLength)
return false;
if (ignoreCase)
return String.[Friend]EqualsIgnoreCaseHelper(str.Ptr, mPtr, mLength);
return String.[Friend]EqualsHelper(str.Ptr, mPtr, mLength);
}
public int IndexOf(StringView subStr, bool ignoreCase = false)
{
/*for (int ofs = 0; ofs <= Length - subStr.Length; ofs++)
{
if (String.[Friend]Compare(mPtr + ofs, subStr.mLength, subStr.mPtr, subStr.mLength, ignoreCase) == 0)
return ofs;
}
return -1;*/
return IndexOf(subStr, 0, ignoreCase);
}
public int IndexOf(StringView subStr, int startIdx, bool ignoreCase = false)
{
if (subStr.Length == 0)
return -1;
if (!ignoreCase)
{
char8 firstC = subStr[0];
for (int ofs = startIdx; ofs <= mLength - subStr.mLength; ofs++)
{
if ((mPtr[ofs] == firstC) && (String.[Friend]Compare(mPtr + ofs + 1, subStr.mLength - 1, subStr.mPtr + 1, subStr.mLength - 1, ignoreCase) == 0))
return ofs;
}
}
else
{
char8 firstC = subStr[0];
char8 firstC2 = firstC;
if (firstC.IsLower)
firstC2 = firstC.ToUpper;
else
firstC2 = firstC.ToLower;
for (int ofs = startIdx; ofs <= mLength - subStr.mLength; ofs++)
{
if (((mPtr[ofs] == firstC) || (mPtr[ofs] == firstC2)) &&
(String.[Friend]Compare(mPtr + ofs + 1, subStr.mLength - 1, subStr.mPtr + 1, subStr.mLength - 1, ignoreCase) == 0))
return ofs;
}
}
return -1;
}
public int IndexOf(char8 c, int startIdx = 0)
{
let ptr = mPtr;
for (int i = startIdx; i < mLength; i++)
if (ptr[i] == c)
return i;
return -1;
}
public int LastIndexOf(char8 c)
{
let ptr = mPtr;
for (int i = mLength - 1; i >= 0; i--)
if (ptr[i] == c)
return i;
return -1;
}
public int LastIndexOf(char8 c, int startCheck)
{
let ptr = mPtr;
for (int i = startCheck; i >= 0; i--)
if (ptr[i] == c)
return i;
return -1;
}
public bool Contains(char8 c)
{
for (int i = 0; i < mLength; i++)
if (mPtr[i] == c)
return true;
return false;
}
public bool Contains(StringView stringView)
{
return IndexOf(stringView) != -1;
}
public bool StartsWith(StringView b, StringComparison comparisonType = StringComparison.Ordinal)
{
if (mLength < b.mLength)
return false;
if (comparisonType == StringComparison.OrdinalIgnoreCase)
return String.[Friend]EqualsIgnoreCaseHelper(this.Ptr, b.Ptr, b.Length);
return String.[Friend]EqualsHelper(this.Ptr, b.Ptr, b.mLength);
}
public bool EndsWith(StringView b, StringComparison comparisonType = StringComparison.Ordinal)
{
if (mLength < b.mLength)
return false;
if (comparisonType == StringComparison.OrdinalIgnoreCase)
return String.[Friend]EqualsIgnoreCaseHelper(Ptr + mLength - b.mLength, b.Ptr, b.mLength);
return String.[Friend]EqualsHelper(this.Ptr + mLength - b.mLength, b.Ptr, b.mLength);
}
public void TrimEnd() mut
{
let ptr = Ptr;
for (int i = mLength - 1; i >= 0; i--)
{
char8 c = ptr[i];
if (c >= (char8)0x80)
{
var (c32, idx, len) = GetChar32WithBacktrack(i);
if (!c32.IsWhiteSpace)
{
if (i < mLength - 1)
{
mLength = i + 1;
}
return;
}
i = idx;
}
else if (!c.IsWhiteSpace)
{
if (i < mLength - 1)
{
mLength = i + 1;
}
return;
}
}
Clear();
}
public void TrimStart() mut
{
let ptr = Ptr;
for (int i = 0; i < mLength; i++)
{
char8 c = ptr[i];
if (c >= (char8)0x80)
{
var (c32, len) = GetChar32(i);
if (!c32.IsWhiteSpace)
{
if (i > 0)
{
mPtr += i;
mLength -= i;
}
return;
}
i += len - 1;
}
else if (!c.IsWhiteSpace)
{
if (i > 0)
{
mPtr += i;
mLength -= i;
}
return;
}
}
Clear();
}
public void Trim() mut
{
TrimStart();
TrimEnd();
}
public bool EndsWith(char8 c)
{
if (mLength == 0)
return false;
return Ptr[mLength - 1] == c;
}
public void QuoteString(String outString)
{
String.QuoteString(Ptr, Length, outString);
}
public Result<void> UnQuoteString(String outString)
{
return String.UnQuoteString(Ptr, Length, outString);
}
[NoDiscard]
public StringView Substring(int pos)
{
return .(this, pos);
}
[NoDiscard]
public StringView Substring(int pos, int length)
{
return .(this, pos, length);
}
public (char32, int) GetChar32(int idx)
{
Debug.Assert((uint)idx < (uint)mLength);
char8* ptr = Ptr;
char8 c = ptr[idx];
if (c < '\x80')
return (c, 1);
if (((uint8)ptr[idx] & 0xC0) == 0x80)
return (0, 0); // Invalid UTF8 data
return UTF8.Decode(ptr + idx, mLength - idx);
}
public (char32, int, int) GetChar32WithBacktrack(int idx)
{
Debug.Assert((uint)idx < (uint)mLength);
char8* ptr = Ptr;
char8 c = ptr[idx];
if (c < '\x80')
return (c, idx, 1);
var idx;
while (((uint8)ptr[idx] & 0xC0) == 0x80)
{
if (idx == 0) // Invalid UTF8 data
return (0, 0, 0);
idx--;
}
let (c32, len) = UTF8.Decode(ptr + idx, mLength - idx);
return (c32, idx, len);
}
public StringSplitEnumerator Split(char8 c)
{
return StringSplitEnumerator(Ptr, Length, c, Int32.MaxValue, StringSplitOptions.None);
}
public StringSplitEnumerator Split(char8 separator, int count)
{
return StringSplitEnumerator(Ptr, Length, separator, count, StringSplitOptions.None);
}
public StringSplitEnumerator Split(char8 separator, StringSplitOptions options)
{
return StringSplitEnumerator(Ptr, Length, separator, Int32.MaxValue, options);
}
public StringSplitEnumerator Split(char8 separator, int count, StringSplitOptions options)
{
return StringSplitEnumerator(Ptr, Length, separator, count, options);
}
public StringSplitEnumerator Split(params char8[] separators)
{
return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
}
public StringSplitEnumerator Split(char8[] separators, int count = Int32.MaxValue, StringSplitOptions options = .None)
{
return StringSplitEnumerator(Ptr, Length, separators, count, options);
}
public static operator StringView (String str)
{
StringView sv;
if (str != null)
{
sv.mPtr = str.Ptr;
sv.mLength = str.[Friend]mLength;
}
else
{
sv = default;
}
return sv;
}
public mixin ToScopeCStr(int maxInlineChars = 128)
{
char8* ptr = null;
if (this.mPtr != null)
{
if (mLength < maxInlineChars)
{
ptr = scope:mixin char8[mLength + 1]*;
}
else
{
ptr = new char8[mLength + 1]*;
defer:mixin delete ptr;
}
Internal.MemCpy(ptr, mPtr, mLength);
ptr[mLength] = 0;
}
ptr
}
public mixin ToScopedNativeWChar()
{
int encodedLen = UTF16.GetEncodedLen(this);
char16* buf;
if (encodedLen < 128)
{
buf = scope:mixin char16[encodedLen]* { ? };
}
else
{
buf = new char16[encodedLen]* { ? };
defer:mixin delete buf;
}
UTF16.Encode(this, buf, encodedLen);
buf
}
}
class StringWithAlloc : String
{
IRawAllocator mAlloc;
[AllowAppend]
private this()
{
}
[AllowAppend]
public this(IRawAllocator alloc)
{
mAlloc = alloc;
}
protected override void* Alloc(int size, int align)
{
return mAlloc.Alloc(size, align);
}
protected override void Free(void* ptr)
{
mAlloc.Free(ptr);
}
}
#if TEST
extension String
{
[Test]
public static void Test_Intern()
{
String str = "TestString";
str.EnsureNullTerminator();
String strA = scope String("Test");
strA.Append("String");
Runtime.Assert(strA == str);
Runtime.Assert((Object)strA != str);
String strB = strA.Intern();
Runtime.Assert(strB == str);
Runtime.Assert((Object)strB == str);
}
}
#endif
}