// 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 sInterns = new .() ~ delete _; static List 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(offset <= 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 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 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 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_strsize)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_strsize)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 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); } internal 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; } internal 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; } internal 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() != 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 FormatError() { return .Err; } /** Appends formatted text. * @param provider The format provider * @returns This method can fail if the format string is invalid. */ public Result AppendF(IFormatProvider provider, StringView format, params Object[] args) { if (format.Ptr == null) { return .Err; } //Contract.Ensures(Contract.Result() != 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 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 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 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; } internal 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; } internal 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 EndsWith(char8 c) { if (mLength == 0) return false; return Ptr[mLength - 1] == c; } public void ReplaceLargerHelper(String find, String replace) { List replaceEntries = scope List(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 enumerable) { bool isFirst = true; for (var str in enumerable) { if (!isFirst) Append(sep); Append(str); isFirst = false; } } public void Join(StringView sep, IEnumerator 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, 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 stringViews = scope List(); target.Split(stringViews, splitChar); var strings = stack List(); for (int i = 0; i < stringViews.Count; i++) strings.Add(stack String(stringViews[i])); strings }*/ /*public static mixin StackSplit(var target, var splitChar) { var stringViews = scope List(); target.Split(stringViews, splitChar); var strings = stack String[stringViews.Count]; for (int i = 0; i < stringViews.Count; i++) strings[i] = stack String(stringViews[i]); strings }*/ public static mixin StackSplit(var target, var splitChar) { var strings = scope:mixin List(); 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 this.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, int len) 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, int len) 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* 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 GetNext() mut { if (!MoveNext()) return .Err; return Current; } public Result GetNextRef() mut { if (!MoveNext()) return .Err; return &CurrentRef; } } public struct UTF8Enumerator : IEnumerator { 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 GetNext() mut { if (!MoveNext()) return .Err; return Current; } } } public enum StringSplitOptions { None = 0, RemoveEmptyEntries = 1 } struct StringSplitEnumerator : IEnumerator { 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 GetNext() mut { if (!MoveNext()) return .Err; return Current; } } public struct StringView : Span, IFormattable, IPrintable, IOpEquals, 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.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.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.EqualsHelper(str.Ptr, mPtr, mLength); } public bool Equals(StringView str, bool ignoreCase) { if (mLength != str.[Friend]mLength) return false; if (ignoreCase) return String.EqualsIgnoreCaseHelper(str.Ptr, mPtr, mLength); return String.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.EqualsIgnoreCaseHelper(this.Ptr, b.Ptr, b.Length); return String.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.EqualsIgnoreCaseHelper(Ptr + mLength - b.mLength, b.Ptr, b.mLength); return String.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 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 }