mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-10 20:42:21 +02:00
4555 lines
100 KiB
Beef
4555 lines
100 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;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Interop;
|
|
using System;
|
|
|
|
//using internal System.String;
|
|
|
|
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,
|
|
}
|
|
|
|
[Ordered]
|
|
class String : IHashable, IFormattable, IPrintable
|
|
{
|
|
public enum CreateFlags
|
|
{
|
|
None = 0,
|
|
NullTerminate = 1
|
|
}
|
|
|
|
public struct Interns
|
|
{
|
|
public static Monitor sMonitor = new Monitor() ~ delete _;
|
|
public static HashSet<String> sInterns = new .() ~ delete _;
|
|
public static List<String> sOwnedInterns = new .() ~ DeleteContainerAndItems!(_);
|
|
}
|
|
|
|
int_strsize mLength;
|
|
uint_strsize mAllocSizeAndFlags;
|
|
char8* mPtrOrBuffer = null;
|
|
|
|
extern const String* sStringLiterals;
|
|
extern const String* sIdStringLiterals;
|
|
static String* sPrevInternLinkPtr; // For detecting changes to sStringLiterals for hot loads
|
|
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(ZeroGap=true)]
|
|
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(int count) // 0
|
|
{
|
|
int bufferSize = count;
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
mPtrOrBuffer = addlPtr;
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize | cStrPtrFlag;
|
|
mLength = 0;
|
|
}
|
|
|
|
[AllowAppend(ZeroGap=true)]
|
|
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()
|
|
{
|
|
let bufferSize = 8;
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
mPtrOrBuffer = addlPtr;
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize | cStrPtrFlag;
|
|
mLength = 0;
|
|
}
|
|
|
|
[AllowAppend(ZeroGap=true)]
|
|
public this(String str)
|
|
{
|
|
let count = str.mLength;
|
|
/*int a = __appendIdx;
|
|
int b = offsetof(String, mPtrOrBuffer);
|
|
if (__appendIdx == offsetof(String, mPtrOrBuffer))
|
|
{
|
|
|
|
}*/
|
|
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)
|
|
{
|
|
let count = str.mLength;
|
|
/*int a = __appendIdx;
|
|
int b = offsetof(String, mPtrOrBuffer);
|
|
if (__appendIdx == offsetof(String, mPtrOrBuffer))
|
|
{
|
|
|
|
}*/
|
|
int bufferSize = count;
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
mPtrOrBuffer = addlPtr;
|
|
Internal.MemCpy(Ptr, str.Ptr, count);
|
|
mLength = count;
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize | cStrPtrFlag;
|
|
}
|
|
|
|
[AllowAppend(ZeroGap=true)]
|
|
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]*(?);
|
|
Init(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(ZeroGap=true)]
|
|
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]*(?);
|
|
Init(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(ZeroGap=true)]
|
|
public this(char8 c, int count)
|
|
{
|
|
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
Init(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(ZeroGap=true)]
|
|
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]*(?);
|
|
Init(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(ZeroGap=true)]
|
|
public this(char8* char8Ptr, int count)
|
|
{
|
|
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
Init(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(ZeroGap=true)]
|
|
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]*(?);
|
|
Init(bufferSize);
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
|
|
mLength = 0;
|
|
UTF16.Decode(char16Ptr, this);
|
|
}
|
|
|
|
[AllowAppend(ZeroGap=true)]
|
|
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]*(?);
|
|
Init(bufferSize);
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
|
|
mLength = 0;
|
|
UTF16.Decode(chars, this);
|
|
}
|
|
|
|
[AllowAppend(ZeroGap=true)]
|
|
public this(StringView strView)
|
|
{
|
|
let count = strView.Length;
|
|
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);
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
|
|
mLength = (int_strsize)strView.Length;
|
|
}
|
|
|
|
[AllowAppend]
|
|
public this(StringView strView)
|
|
{
|
|
let count = strView.Length;
|
|
int bufferSize = count;
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
mPtrOrBuffer = addlPtr;
|
|
let ptr = Ptr;
|
|
Internal.MemCpy(ptr, strView.Ptr, strView.Length);
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize | cStrPtrFlag;
|
|
mLength = (int_strsize)strView.Length;
|
|
}
|
|
|
|
[AllowAppend(ZeroGap=true)]
|
|
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]*(?);
|
|
Init(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(ZeroGap=true)]
|
|
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]*(?);
|
|
Init(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(ZeroGap=true)]
|
|
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]*(?);
|
|
Init(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(ZeroGap=true)]
|
|
public this(char8[] chars, int offset, int count)
|
|
{
|
|
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
Init(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(Span<StringView> strs)
|
|
{
|
|
int count = 0;
|
|
for (var str in strs)
|
|
count += str.Length;
|
|
return count;
|
|
}
|
|
|
|
static int StrLengths(Span<String> strs)
|
|
{
|
|
int count = 0;
|
|
for (var str in strs)
|
|
count += str.Length;
|
|
return count;
|
|
}
|
|
|
|
[AllowAppend]
|
|
public this(params Span<StringView> strs)
|
|
{
|
|
int count = StrLengths(strs);
|
|
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
Init(bufferSize);
|
|
let ptr = Ptr;
|
|
int curIdx = 0;
|
|
for (var str in strs)
|
|
{
|
|
Internal.MemCpy(ptr + curIdx, str.Ptr, str.Length);
|
|
curIdx += str.Length;
|
|
}
|
|
|
|
mLength = (int_strsize)count;
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
|
|
}
|
|
|
|
[AllowAppend]
|
|
public this(Span<String> strs)
|
|
{
|
|
int count = StrLengths(strs);
|
|
int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
|
|
#unwarn
|
|
char8* addlPtr = append char8[bufferSize]*(?);
|
|
Init(bufferSize);
|
|
let ptr = Ptr;
|
|
int curIdx = 0;
|
|
for (var str in strs)
|
|
{
|
|
Internal.MemCpy(ptr + curIdx, str.Ptr, str.Length);
|
|
curIdx += str.Length;
|
|
}
|
|
|
|
mLength = (int_strsize)count;
|
|
mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
|
|
}
|
|
|
|
#if !VALGRIND
|
|
[SkipCall]
|
|
#endif
|
|
void Init(int appendSize)
|
|
{
|
|
Internal.MemSet(Ptr, 0, appendSize + (int_strsize)sizeof(char8*));
|
|
}
|
|
|
|
public ~this()
|
|
{
|
|
if (IsDynAlloc)
|
|
delete:this mPtrOrBuffer;
|
|
}
|
|
|
|
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)value <= (uint)mLength);
|
|
mLength = (int_strsize)value;
|
|
}
|
|
}
|
|
|
|
public int NumCodePoints
|
|
{
|
|
get
|
|
{
|
|
char8* ptr = Ptr;
|
|
|
|
int count = 0;
|
|
for (int i < mLength)
|
|
{
|
|
char8 c = ptr[i];
|
|
if (((uint8)c & 0xC0) != 0x80)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
|
|
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) ? mPtrOrBuffer : (char8*)&mPtrOrBuffer;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
[Obsolete("Replaced with Quote", false)]
|
|
public static void QuoteString(char8* ptr, int length, String outString) => Quote(ptr, length, outString);
|
|
|
|
public static void Quote(char8* ptr, int length, String outString)
|
|
{
|
|
outString.Append('"');
|
|
Escape(ptr, length, outString);
|
|
outString.Append('"');
|
|
}
|
|
|
|
public void Quote(String outString)
|
|
{
|
|
Quote(Ptr, Length, outString);
|
|
}
|
|
|
|
public static void Escape(char8* ptr, int length, String outString)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Escape(String outString)
|
|
{
|
|
Escape(Ptr, Length, outString);
|
|
}
|
|
|
|
[Obsolete("Replaced with Unquote", false)]
|
|
public static Result<void> UnQuoteString(char8* ptr, int length, String outString) => Unquote(ptr, length, outString);
|
|
|
|
public static Result<void> Unquote(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;
|
|
|
|
return Unescape(ptr + 1, length - 2, outString);
|
|
}
|
|
|
|
public Result<void> Unquote(String outString)
|
|
{
|
|
return Unquote(Ptr, Length, outString);
|
|
}
|
|
|
|
public static Result<void> Unescape(char8* ptr, int length, String outString)
|
|
{
|
|
var ptr;
|
|
char8* endPtr = ptr + length;
|
|
|
|
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");
|
|
case 'x':
|
|
uint8 num = 0;
|
|
for (let i < 2)
|
|
{
|
|
if (ptr == endPtr)
|
|
return .Err;
|
|
let hexC = *(ptr++);
|
|
|
|
if ((hexC >= '0') && (hexC <= '9'))
|
|
num = num*0x10 + (uint8)(hexC - '0');
|
|
else if ((hexC >= 'A') && (hexC <= 'F'))
|
|
num = num*0x10 + (uint8)(hexC - 'A') + 10;
|
|
else if ((hexC >= 'a') && (hexC <= 'f'))
|
|
num = num*0x10 + (uint8)(hexC - 'a') + 10;
|
|
else return .Err;
|
|
}
|
|
|
|
outString.Append((char8)num);
|
|
default:
|
|
return .Err;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
outString.Append(c);
|
|
}
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
public Result<void> Unescape(String outString)
|
|
{
|
|
return Unescape(Ptr, Length, outString);
|
|
}
|
|
|
|
static String sHexUpperChars = "0123456789ABCDEF";
|
|
public void ToString(String outString, String format, IFormatProvider formatProvider)
|
|
{
|
|
if (format == "Q")
|
|
{
|
|
Quote(Ptr, mLength, outString);
|
|
return;
|
|
}
|
|
outString.Append(this);
|
|
}
|
|
|
|
void IPrintable.Print(String outString)
|
|
{
|
|
String.Quote(Ptr, mLength, outString);
|
|
}
|
|
|
|
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 implicit operator Span<char8>(String str)
|
|
{
|
|
if (str == null)
|
|
return .((char8*)null, 0);
|
|
return .(str.Ptr, str.Length);
|
|
}
|
|
|
|
[Commutable]
|
|
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.mPtrOrBuffer;
|
|
}
|
|
|
|
str.mPtrOrBuffer = mPtrOrBuffer;
|
|
str.mAllocSizeAndFlags = mAllocSizeAndFlags;
|
|
str.mLength = mLength;
|
|
|
|
if (keepRef)
|
|
{
|
|
mAllocSizeAndFlags &= ~cDynAllocFlag;
|
|
}
|
|
else
|
|
{
|
|
mPtrOrBuffer = null;
|
|
mAllocSizeAndFlags = (int_strsize)sizeof(char8*);
|
|
mLength = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
str.Set(this);
|
|
if (!keepRef)
|
|
Clear();
|
|
}
|
|
}
|
|
|
|
public void Reference(String str)
|
|
{
|
|
if (IsDynAlloc)
|
|
delete:this mPtrOrBuffer;
|
|
mPtrOrBuffer = str.Ptr;
|
|
mLength = str.mLength;
|
|
mAllocSizeAndFlags = cStrPtrFlag;
|
|
}
|
|
|
|
public void Reference(char8* ptr, int length, int allocSize)
|
|
{
|
|
if (IsDynAlloc)
|
|
delete:this mPtrOrBuffer;
|
|
mPtrOrBuffer = ptr;
|
|
mLength = (int_strsize)length;
|
|
mAllocSizeAndFlags = cStrPtrFlag;
|
|
}
|
|
|
|
public void Reference(char8* ptr, int length)
|
|
{
|
|
if (IsDynAlloc)
|
|
delete:this mPtrOrBuffer;
|
|
mPtrOrBuffer = 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 mPtrOrBuffer;
|
|
mPtrOrBuffer = 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);
|
|
mPtrOrBuffer += 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;
|
|
}
|
|
|
|
[Inline]
|
|
void CalculatedReserve(int newSize)
|
|
{
|
|
if (newSize > AllocSize)
|
|
Realloc(CalcNewSize(newSize));
|
|
}
|
|
|
|
void Realloc(int newSize)
|
|
{
|
|
Debug.Assert(AllocSize > 0, "String has been frozen");
|
|
Runtime.Assert((uint)newSize <= cSizeFlags);
|
|
char8* newPtr = new:this char8[newSize]* (?);
|
|
Internal.MemCpy(newPtr, Ptr, mLength);
|
|
#if VALGRIND
|
|
Internal.MemSet(newPtr + mLength, 0, newSize - mLength);
|
|
#endif
|
|
if (IsDynAlloc)
|
|
delete:this mPtrOrBuffer;
|
|
mPtrOrBuffer = newPtr;
|
|
mAllocSizeAndFlags = (uint_strsize)newSize | cDynAllocFlag | cStrPtrFlag;
|
|
}
|
|
|
|
public void Reserve(int size)
|
|
{
|
|
if (size > AllocSize)
|
|
Realloc(size);
|
|
}
|
|
|
|
void Realloc(char8* newPtr, int newSize)
|
|
{
|
|
Runtime.Assert(newSize <= int_cosize.MaxValue);
|
|
Debug.Assert(AllocSize > 0, "String has been frozen");
|
|
Runtime.Assert((uint)newSize <= cSizeFlags);
|
|
Internal.MemCpy(newPtr, Ptr, mLength);
|
|
if (IsDynAlloc)
|
|
delete:this mPtrOrBuffer;
|
|
mPtrOrBuffer = 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;
|
|
}
|
|
|
|
[NoDiscard]
|
|
public StringView Substring(int pos)
|
|
{
|
|
return .(this, pos);
|
|
}
|
|
|
|
[NoDiscard]
|
|
public StringView Substring(int pos, int length)
|
|
{
|
|
return .(this, pos, length);
|
|
}
|
|
|
|
[NoDiscard]
|
|
public StringView Substring(IndexRange range)
|
|
{
|
|
return .(this)[range];
|
|
}
|
|
|
|
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]* (?);
|
|
#if VALGRIND
|
|
Internal.MemSet(newPtr, 0, newSize);
|
|
#endif
|
|
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]* (?);
|
|
#if VALGRIND
|
|
Internal.MemSet(newPtr, 0, newSize);
|
|
#endif
|
|
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]* (?);
|
|
#if VALGRIND
|
|
Internal.MemSet(newPtr, 0, newSize);
|
|
#endif
|
|
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;
|
|
CalculatedReserve(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 Span<StringView> strings)
|
|
{
|
|
for (var str in strings)
|
|
Append(str);
|
|
}
|
|
|
|
public void Append(char8 c)
|
|
{
|
|
CalculatedReserve(mLength + 1);
|
|
let ptr = Ptr;
|
|
ptr[mLength++] = c;
|
|
}
|
|
|
|
public void Append(char8 c, int count)
|
|
{
|
|
if (count <= 0)
|
|
return;
|
|
|
|
CalculatedReserve(mLength + count);
|
|
let ptr = Ptr;
|
|
for (int_strsize i = 0; i < count; i++)
|
|
ptr[mLength++] = c;
|
|
}
|
|
|
|
public void Append(char32 c)
|
|
{
|
|
if (c < (char32)0x80)
|
|
{
|
|
CalculatedReserve(mLength + 1);
|
|
let ptr = Ptr;
|
|
ptr[mLength++] = (char8)c;
|
|
}
|
|
else if (c < (char32)0x800)
|
|
{
|
|
CalculatedReserve(mLength + 2);
|
|
let ptr = Ptr;
|
|
ptr[mLength++] = (char8)(c>>6) | (char8)0xC0;
|
|
ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
|
|
}
|
|
else if (c < (char32)0x10000)
|
|
{
|
|
CalculatedReserve(mLength + 3);
|
|
let ptr = Ptr;
|
|
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)
|
|
{
|
|
CalculatedReserve(mLength + 4);
|
|
let ptr = Ptr;
|
|
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 Append(char32 c, int count)
|
|
{
|
|
if (count <= 0)
|
|
return;
|
|
|
|
if (count == 1)
|
|
{
|
|
Append(c);
|
|
return;
|
|
}
|
|
|
|
int encodedLen = UTF8.GetEncodedLength(c);
|
|
|
|
CalculatedReserve(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 Append(Object object)
|
|
{
|
|
if (object == null)
|
|
return;
|
|
Append(object.ToString(.. scope .(128)));
|
|
}
|
|
|
|
public void operator+=(String str)
|
|
{
|
|
Append(str);
|
|
}
|
|
|
|
public void operator+=(StringView sv)
|
|
{
|
|
Append(sv);
|
|
}
|
|
|
|
public void operator+=(char8 c)
|
|
{
|
|
Append(c);
|
|
}
|
|
|
|
public void operator+=(char32 c)
|
|
{
|
|
Append(c);
|
|
}
|
|
|
|
public void operator+=(Object obj)
|
|
{
|
|
Append(obj);
|
|
}
|
|
|
|
[Error("String addition is not supported. Consider allocating a new string and using Append, Concat, or +=")]
|
|
public static String operator+(String lhs, String rhs)
|
|
{
|
|
return lhs;
|
|
}
|
|
|
|
[Error("String addition is not supported. Consider allocating a new string and using Append, Concat, or +=")]
|
|
public static String operator+(String lhs, StringView rhs)
|
|
{
|
|
return lhs;
|
|
}
|
|
|
|
[Error("String addition is not supported. Consider allocating a new string and using Append, Concat, or +=")]
|
|
public static String operator+(String lhs, char32 rhs)
|
|
{
|
|
return lhs;
|
|
}
|
|
|
|
public void EnsureNullTerminator()
|
|
{
|
|
int allocSize = AllocSize;
|
|
if ((allocSize == mLength) || (Ptr[mLength] != 0))
|
|
{
|
|
CalculatedReserve(mLength + 1);
|
|
Ptr[mLength] = 0;
|
|
}
|
|
}
|
|
|
|
public ref char8 this[int index]
|
|
{
|
|
[Checked]
|
|
get
|
|
{
|
|
Debug.Assert((uint)index < (uint)mLength);
|
|
return ref Ptr[index];
|
|
}
|
|
|
|
[Unchecked, Inline]
|
|
get
|
|
{
|
|
return ref Ptr[index];
|
|
}
|
|
|
|
[Checked]
|
|
set
|
|
{
|
|
Debug.Assert((uint)index < (uint)mLength);
|
|
Ptr[index] = value;
|
|
}
|
|
|
|
[Unchecked, Inline]
|
|
set
|
|
{
|
|
Ptr[index] = value;
|
|
}
|
|
}
|
|
|
|
public ref char8 this[Index index]
|
|
{
|
|
[Checked]
|
|
get
|
|
{
|
|
int idx;
|
|
switch (index)
|
|
{
|
|
case .FromFront(let offset): idx = offset;
|
|
case .FromEnd(let offset): idx = mLength - offset;
|
|
}
|
|
Debug.Assert((uint)idx < (uint)mLength);
|
|
return ref Ptr[idx];
|
|
}
|
|
|
|
[Unchecked, Inline]
|
|
get
|
|
{
|
|
int idx;
|
|
switch (index)
|
|
{
|
|
case .FromFront(let offset): idx = offset;
|
|
case .FromEnd(let offset): idx = mLength - offset;
|
|
}
|
|
return ref Ptr[idx];
|
|
}
|
|
|
|
[Checked]
|
|
set
|
|
{
|
|
int idx;
|
|
switch (index)
|
|
{
|
|
case .FromFront(let offset): idx = offset;
|
|
case .FromEnd(let offset): idx = mLength - offset;
|
|
}
|
|
Debug.Assert((uint)idx < (uint)mLength);
|
|
Ptr[idx] = value;
|
|
}
|
|
|
|
[Unchecked, Inline]
|
|
set
|
|
{
|
|
int idx;
|
|
switch (index)
|
|
{
|
|
case .FromFront(let offset): idx = offset;
|
|
case .FromEnd(let offset): idx = mLength - offset;
|
|
}
|
|
Ptr[idx] = value;
|
|
}
|
|
}
|
|
|
|
public StringView this[IndexRange range]
|
|
{
|
|
#if !DEBUG
|
|
[Inline]
|
|
#endif
|
|
get
|
|
{
|
|
return StringView(Ptr, Length)[range];
|
|
}
|
|
}
|
|
|
|
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 Span<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 charsLeft = len - pos;
|
|
Reserve(mLength + charsLeft);
|
|
char8* ptr = Ptr;
|
|
|
|
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);
|
|
ptr[mLength++] = ch;
|
|
}
|
|
|
|
if (pos == len) break;
|
|
pos++;
|
|
int index = 0;
|
|
if (pos == len || (ch = format[pos]) < '0' || ch > '9')
|
|
{
|
|
if ((pos < len) &&
|
|
((ch == '}') || (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.Length) 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 ((provider == null) && (fmt.IsEmpty) && (width == 0))
|
|
{
|
|
if (arg == null)
|
|
Append("null");
|
|
else
|
|
arg.ToString(this);
|
|
}
|
|
else
|
|
{
|
|
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 Span<Object> args)
|
|
{
|
|
return AppendF((IFormatProvider)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 int IndexOfAny(char8[] targets)
|
|
{
|
|
return IndexOfAny(targets, 0, mLength);
|
|
}
|
|
|
|
public int IndexOfAny(char8[] targets, int startIdx)
|
|
{
|
|
return IndexOfAny(targets, startIdx, mLength - startIdx);
|
|
}
|
|
|
|
public int IndexOfAny(char8[] targets, int startIdx, int count)
|
|
{
|
|
let ptr = Ptr;
|
|
for (var i = startIdx; i < count; i++)
|
|
{
|
|
let ch = ptr[i];
|
|
for (let tag in targets)
|
|
{
|
|
if (ch == tag)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public bool Contains(StringView str, bool ignoreCase = false)
|
|
{
|
|
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 mPtrOrBuffer;
|
|
mPtrOrBuffer = newPtr;
|
|
mAllocSizeAndFlags = (uint_strsize)newSize | cDynAllocFlag | cStrPtrFlag;
|
|
}
|
|
}
|
|
|
|
public void ToUpper()
|
|
{
|
|
CaseConv(true);
|
|
}
|
|
|
|
public void ToLower()
|
|
{
|
|
CaseConv(false);
|
|
}
|
|
|
|
[CallingConvention(.Cdecl)]
|
|
static extern int UTF8GetAllocSize(char8* str, int strlen, int32 options);
|
|
[CallingConvention(.Cdecl)]
|
|
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 mPtrOrBuffer;
|
|
mPtrOrBuffer = 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.mPtrOrBuffer;
|
|
destStr.mPtrOrBuffer = 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;
|
|
CalculatedReserve(newLength);
|
|
|
|
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 idx, char8 c)
|
|
{
|
|
Contract.Requires(idx >= 0);
|
|
|
|
let newLength = mLength + 1;
|
|
CalculatedReserve(newLength);
|
|
|
|
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 idx, char8 c, int count)
|
|
{
|
|
Contract.Requires(idx >= 0);
|
|
|
|
if (count <= 0)
|
|
return;
|
|
|
|
let newLength = mLength + (int_strsize)count;
|
|
CalculatedReserve(newLength);
|
|
|
|
let moveChars = mLength - idx;
|
|
let ptr = Ptr;
|
|
if (moveChars > 0)
|
|
Internal.MemMove(ptr + idx + count, ptr + idx, moveChars);
|
|
for (let i < count)
|
|
ptr[idx + i] = c;
|
|
mLength = newLength;
|
|
}
|
|
|
|
public void Insert(int idx, char32 c)
|
|
{
|
|
Contract.Requires(idx >= 0);
|
|
|
|
let moveChars = mLength - idx;
|
|
if (c < (char32)0x80)
|
|
{
|
|
CalculatedReserve(mLength + 1);
|
|
let ptr = Ptr;
|
|
if (moveChars > 0)
|
|
Internal.MemMove(ptr + idx + 1, ptr + idx, moveChars);
|
|
ptr[idx] = (char8)c;
|
|
mLength++;
|
|
}
|
|
else if (c < (char32)0x800)
|
|
{
|
|
CalculatedReserve(mLength + 2);
|
|
let ptr = Ptr;
|
|
if (moveChars > 0)
|
|
Internal.MemMove(ptr + idx + 2, ptr + idx, moveChars);
|
|
ptr[idx] = (char8)(c>>6) | (char8)0xC0;
|
|
ptr[idx + 1] = (char8)(c & (char8)0x3F) | (char8)0x80;
|
|
mLength += 2;
|
|
}
|
|
else if (c < (char32)0x10000)
|
|
{
|
|
CalculatedReserve(mLength + 3);
|
|
let ptr = Ptr;
|
|
if (moveChars > 0)
|
|
Internal.MemMove(ptr + idx + 3, ptr + idx, moveChars);
|
|
ptr[idx] = (char8)(c>>12) | (char8)0xE0;
|
|
ptr[idx + 1] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
|
|
ptr[idx + 2] = (char8)(c & (char8)0x3F) | (char8)0x80;
|
|
mLength += 3;
|
|
}
|
|
else if (c < (char32)0x110000)
|
|
{
|
|
CalculatedReserve(mLength + 4);
|
|
let ptr = Ptr;
|
|
if (moveChars > 0)
|
|
Internal.MemMove(ptr + idx + 4, ptr + idx, moveChars);
|
|
ptr[idx] = (char8)((c>>18) | (char8)0xF0);
|
|
ptr[idx + 1] = (char8)((c>>12) & (char8)0x3F) | (char8)0x80;
|
|
ptr[idx + 2] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
|
|
ptr[idx + 3] = (char8)(c & (char8)0x3F) | (char8)0x80;
|
|
mLength += 4;
|
|
}
|
|
}
|
|
|
|
public void Insert(int idx, char32 c, int count)
|
|
{
|
|
Contract.Requires(idx >= 0);
|
|
|
|
if (count <= 0)
|
|
return;
|
|
|
|
if (count == 1)
|
|
{
|
|
Insert(idx, c);
|
|
return;
|
|
}
|
|
|
|
let encodedLen = UTF8.GetEncodedLength(c);
|
|
let newLength = mLength + (int_strsize)(count * encodedLen);
|
|
CalculatedReserve(newLength);
|
|
|
|
let moveChars = mLength - idx;
|
|
let ptr = Ptr;
|
|
if (moveChars > 0)
|
|
Internal.MemMove(ptr + idx + count * encodedLen, ptr + idx, moveChars);
|
|
for (let i < count)
|
|
{
|
|
let ofs = idx + i * encodedLen;
|
|
if (c < (char32)0x80)
|
|
{
|
|
ptr[ofs] = (char8)c;
|
|
}
|
|
else if (c < (char32)0x800)
|
|
{
|
|
ptr[ofs] = (char8)(c>>6) | (char8)0xC0;
|
|
ptr[ofs + 1] = (char8)(c & (char8)0x3F) | (char8)0x80;
|
|
}
|
|
else if (c < (char32)0x10000)
|
|
{
|
|
ptr[ofs] = (char8)(c>>12) | (char8)0xE0;
|
|
ptr[ofs + 1] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
|
|
ptr[ofs + 2] = (char8)(c & (char8)0x3F) | (char8)0x80;
|
|
}
|
|
else if (c < (char32)0x110000)
|
|
{
|
|
ptr[ofs] = (char8)((c>>18) | (char8)0xF0);
|
|
ptr[ofs + 1] = (char8)((c>>12) & (char8)0x3F) | (char8)0x80;
|
|
ptr[ofs + 2] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
|
|
ptr[ofs + 3] = (char8)(c & (char8)0x3F) | (char8)0x80;
|
|
}
|
|
}
|
|
|
|
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 chars - notice that we need just one compare per char
|
|
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 char
|
|
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 chars - notice that we need just one compare per char
|
|
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 char
|
|
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 chars - notice that we need just one compare per char
|
|
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 char
|
|
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 Equals(StringView str)
|
|
{
|
|
if (mLength != str.[Friend]mLength)
|
|
return false;
|
|
return EqualsHelper(str.Ptr, Ptr, mLength);
|
|
}
|
|
|
|
public bool Equals(StringView str, StringComparison comparisonType = StringComparison.Ordinal)
|
|
{
|
|
if (mLength != str.[Friend]mLength)
|
|
return false;
|
|
if (comparisonType == StringComparison.OrdinalIgnoreCase)
|
|
return EqualsIgnoreCaseHelper(str.Ptr, Ptr, mLength);
|
|
return EqualsHelper(str.Ptr, Ptr, 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;
|
|
}
|
|
|
|
private void ReplaceLargerHelper(StringView find, StringView replace)
|
|
{
|
|
List<int> replaceEntries = scope List<int>(8192);
|
|
|
|
int_strsize moveOffset = (.) (replace.Length - find.Length);
|
|
|
|
for (int startIdx = 0; startIdx <= mLength - find.Length; startIdx++)
|
|
{
|
|
if (EqualsHelper(Ptr + startIdx, find.Ptr, find.Length))
|
|
{
|
|
replaceEntries.Add(startIdx);
|
|
startIdx += find.Length - 1;
|
|
}
|
|
}
|
|
|
|
if (replaceEntries.Count == 0)
|
|
return;
|
|
|
|
int destLength = mLength + moveOffset * replaceEntries.Count;
|
|
int needSize = destLength;
|
|
CalculatedReserve(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.Length;
|
|
int destStartIdx = srcStartIdx + moveIdx * moveOffset;
|
|
int destEndIdx = destStartIdx + replace.Length;
|
|
|
|
for (int i = lastDestStartIdx - destEndIdx - 1; i >= 0; i--)
|
|
ptr[destEndIdx + i] = ptr[srcEndIdx + i];
|
|
|
|
for (int i < replace.Length)
|
|
ptr[destStartIdx + i] = replacePtr[i];
|
|
|
|
lastDestStartIdx = destStartIdx;
|
|
}
|
|
|
|
mLength = (int_strsize)destLength;
|
|
}
|
|
|
|
public void Replace(StringView find, StringView replace)
|
|
{
|
|
if (replace.Length > find.Length)
|
|
{
|
|
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)
|
|
{
|
|
if (ptr[inIdx] == findC)
|
|
{
|
|
for (int i = 0; i < replace.Length; i++)
|
|
ptr[outIdx++] = replacePtr[i];
|
|
|
|
inIdx += (.) find.Length;
|
|
}
|
|
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.Length)
|
|
{
|
|
if (EqualsHelper(ptr + inIdx, findPtr, find.Length))
|
|
{
|
|
for (int i = 0; i < replace.Length; i++)
|
|
ptr[outIdx++] = replacePtr[i];
|
|
|
|
inIdx += (.) find.Length;
|
|
}
|
|
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 Replace(int start, int length, StringView replaceWith)
|
|
{
|
|
Debug.Assert(start >= 0 && start <= Length);
|
|
Debug.Assert(length >= 0 && length <= Length - start);
|
|
|
|
if (replaceWith.IsEmpty)
|
|
return;
|
|
|
|
if (replaceWith.Length == length)
|
|
{
|
|
Internal.MemCpy(&Ptr[start], replaceWith.Ptr, length);
|
|
}
|
|
else if (replaceWith.Length > length)
|
|
{
|
|
int additional = replaceWith.Length - length;
|
|
CalculatedReserve(Length + additional);
|
|
|
|
if (start + length < Length)
|
|
Internal.MemMove(&Ptr[start + replaceWith.Length], &Ptr[start + length], Length - (start + length));
|
|
|
|
mLength += (.) additional;
|
|
|
|
Internal.MemCpy(&Ptr[start], replaceWith.Ptr, replaceWith.Length);
|
|
}
|
|
else
|
|
{
|
|
int difference = length - replaceWith.Length;
|
|
|
|
Internal.MemCpy(&Ptr[start], replaceWith.Ptr, replaceWith.Length);
|
|
|
|
if (start + length < Length)
|
|
Internal.MemMove(&Ptr[start + replaceWith.Length], &Ptr[start + length], Length - (start + length));
|
|
|
|
mLength -= (.) difference;
|
|
}
|
|
}
|
|
|
|
public void Replace(IndexRange range, StringView replaceWith)
|
|
{
|
|
StringView view = this;
|
|
|
|
int start = view.[Friend]GetRangeStart(range);
|
|
int end = view.[Friend]GetRangeEnd(range);
|
|
|
|
Replace(start, end - start, replaceWith);
|
|
}
|
|
|
|
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 TrimEnd(char32 trimChar)
|
|
{
|
|
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 != trimChar)
|
|
{
|
|
if (i < mLength - 1)
|
|
RemoveToEnd(i + 1);
|
|
return;
|
|
}
|
|
i = idx;
|
|
}
|
|
else if ((char32)c != trimChar)
|
|
{
|
|
if (i < mLength - 1)
|
|
RemoveToEnd(i + 1);
|
|
return;
|
|
}
|
|
}
|
|
Clear();
|
|
}
|
|
|
|
public void TrimEnd(char8 trimChar)
|
|
{
|
|
TrimEnd((char32)trimChar);
|
|
}
|
|
|
|
public void TrimStart(char32 trimChar)
|
|
{
|
|
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 != trimChar)
|
|
{
|
|
if (i > 0)
|
|
Remove(0, i);
|
|
return;
|
|
}
|
|
i += len - 1;
|
|
}
|
|
else if ((char32)c != trimChar)
|
|
{
|
|
if (i > 0)
|
|
Remove(0, i);
|
|
return;
|
|
}
|
|
}
|
|
Clear();
|
|
}
|
|
|
|
public void TrimStart(char8 trimChar)
|
|
{
|
|
TrimStart((char32)trimChar);
|
|
}
|
|
|
|
public void Trim(char32 trimChar)
|
|
{
|
|
TrimStart(trimChar);
|
|
TrimEnd(trimChar);
|
|
}
|
|
|
|
public void Trim(char8 trimChar)
|
|
{
|
|
TrimStart((.)trimChar);
|
|
TrimEnd((.)trimChar);
|
|
}
|
|
|
|
public void PadLeft(int totalWidth, char8 paddingChar)
|
|
{
|
|
Insert(0, paddingChar, totalWidth - Length);
|
|
}
|
|
|
|
public void PadLeft(int totalWidth, char32 paddingChar)
|
|
{
|
|
Insert(0, paddingChar, totalWidth - Length);
|
|
}
|
|
|
|
public void PadLeft(int totalWidth)
|
|
{
|
|
Insert(0, ' ', totalWidth - Length);
|
|
}
|
|
|
|
public void PadRight(int totalWidth, char8 paddingChar)
|
|
{
|
|
Append(paddingChar, totalWidth - Length);
|
|
}
|
|
|
|
public void PadRight(int totalWidth, char32 paddingChar)
|
|
{
|
|
Append(paddingChar, totalWidth - Length);
|
|
}
|
|
|
|
public void PadRight(int totalWidth)
|
|
{
|
|
Append(' ', totalWidth - Length);
|
|
}
|
|
|
|
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(StringView separator, Span<String> values)
|
|
{
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
if (i > 0)
|
|
Append(separator);
|
|
values[i].ToString(this);
|
|
}
|
|
}
|
|
|
|
public void Join(StringView separator, params Span<StringView> values)
|
|
{
|
|
for (int i = 0; i < values.Length; 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 Span<char8> separators)
|
|
{
|
|
return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringSplitEnumerator Split(Span<char8> separators)
|
|
{
|
|
return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringSplitEnumerator Split(Span<char8> separators, int count)
|
|
{
|
|
return StringSplitEnumerator(Ptr, Length, separators, count, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringSplitEnumerator Split(Span<char8> separators, int count, StringSplitOptions options)
|
|
{
|
|
return StringSplitEnumerator(Ptr, Length, separators, count, options);
|
|
}
|
|
|
|
public StringSplitEnumerator Split(Span<char8> separators, StringSplitOptions options)
|
|
{
|
|
return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, options);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView sv)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, sv, Int32.MaxValue, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView separator, int count)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separator, count, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView separator, StringSplitOptions options)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separator, Int32.MaxValue, options);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView separator, int count, StringSplitOptions options)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separator, count, options);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(params StringView[] separators)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView[] separators)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView[] separators, int count)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, count, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView[] separators, int count, StringSplitOptions options)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, count, options);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView[] separators, StringSplitOptions options)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, 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);
|
|
c_wchar* buf;
|
|
if (encodedLen < 128)
|
|
{
|
|
buf = scope:mixin c_wchar[encodedLen+1]* ( ? );
|
|
}
|
|
else
|
|
{
|
|
buf = new c_wchar[encodedLen+1]* ( ? );
|
|
defer:mixin delete buf;
|
|
}
|
|
|
|
if (sizeof(c_wchar) == 2)
|
|
UTF16.Encode(this, (.)buf, encodedLen);
|
|
else
|
|
UTF32.Encode(this, (.)buf, encodedLen);
|
|
buf[encodedLen] = 0;
|
|
buf
|
|
}
|
|
|
|
[Comptime(ConstEval=true)]
|
|
public Span<c_wchar> ToConstNativeW()
|
|
{
|
|
if (sizeof(c_wchar) == 2)
|
|
{
|
|
int encodedLen = UTF16.GetEncodedLen(this);
|
|
var buf = new char16[encodedLen + 1]* ( ? );
|
|
UTF16.Encode(this, buf, encodedLen);
|
|
buf[encodedLen] = 0;
|
|
return .((.)buf, encodedLen + 1);
|
|
}
|
|
else
|
|
{
|
|
int encodedLen = UTF32.GetEncodedLen(this);
|
|
var buf = new char32[encodedLen + 1]* ( ? );
|
|
UTF32.Encode(this, buf, encodedLen);
|
|
buf[encodedLen] = 0;
|
|
return .((.)buf, encodedLen + 1);
|
|
}
|
|
}
|
|
|
|
[Comptime(ConstEval=true)]
|
|
public static String ConstF(String format, params Span<Object> args)
|
|
{
|
|
return new String()..AppendF(format, params args);
|
|
}
|
|
|
|
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 static bool Equals(char8* str1, char8* str2, int length)
|
|
{
|
|
for (int i < length)
|
|
{
|
|
char8 c = str1[i];
|
|
char8 c2 = str2[i];
|
|
if (c != c2)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public RawEnumerator RawChars
|
|
{
|
|
[Inline]
|
|
get
|
|
{
|
|
return RawEnumerator(Ptr, 0, mLength);
|
|
}
|
|
}
|
|
|
|
public UTF8Enumerator DecodedChars
|
|
{
|
|
[Inline]
|
|
get
|
|
{
|
|
return UTF8Enumerator(Ptr, 0, mLength);
|
|
}
|
|
}
|
|
|
|
public UTF8Enumerator DecodedChars(int startIdx)
|
|
{
|
|
return UTF8Enumerator(Ptr, startIdx, mLength);
|
|
}
|
|
|
|
public bool HasMultibyteChars
|
|
{
|
|
get
|
|
{
|
|
char8* ptr = Ptr;
|
|
int len = Length;
|
|
for (int i < len)
|
|
if (ptr[i] >= '\x80')
|
|
return true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[Obsolete("HasMultibyteChars() method has been replaced with the HasMultibyteChars property")]
|
|
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, int8 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;
|
|
Interns.sInterns.Add(str);
|
|
}
|
|
}
|
|
|
|
public String Intern()
|
|
{
|
|
using (Interns.sMonitor.Enter())
|
|
{
|
|
bool needsLiteralPass = Interns.sInterns.Count == 0;
|
|
String* internalLinkPtr = *((String**)(sStringLiterals));
|
|
if (internalLinkPtr != sPrevInternLinkPtr)
|
|
{
|
|
sPrevInternLinkPtr = internalLinkPtr;
|
|
needsLiteralPass = true;
|
|
}
|
|
if (needsLiteralPass)
|
|
CheckLiterals(sStringLiterals);
|
|
|
|
String* entryPtr;
|
|
if (Interns.sInterns.TryAdd(this, out entryPtr))
|
|
{
|
|
String result = new String(mLength + 1);
|
|
result.Append(this);
|
|
result.EnsureNullTerminator();
|
|
*entryPtr = result;
|
|
Interns.sOwnedInterns.Add(result);
|
|
return result;
|
|
}
|
|
return *entryPtr;
|
|
}
|
|
}
|
|
|
|
public static String GetById(int id)
|
|
{
|
|
if (Compiler.IsComptime)
|
|
{
|
|
return Compiler.[Friend]Comptime_GetStringById((.)id);
|
|
}
|
|
else
|
|
return sIdStringLiterals[id];
|
|
}
|
|
|
|
public struct RawEnumerator : IRefEnumerator<char8*>, IEnumerator<char8>
|
|
{
|
|
char8* mPtr;
|
|
int_strsize mIdx;
|
|
int_strsize mLength;
|
|
|
|
[Inline]
|
|
public this(char8* ptr, int idx, int length)
|
|
{
|
|
mPtr = ptr;
|
|
mIdx = (int_strsize)idx - 1;
|
|
mLength = (int_strsize)length;
|
|
}
|
|
|
|
public char8 Current
|
|
{
|
|
[Inline]
|
|
get
|
|
{
|
|
return mPtr[mIdx];
|
|
}
|
|
|
|
[Inline]
|
|
set
|
|
{
|
|
mPtr[mIdx] = value;
|
|
}
|
|
}
|
|
|
|
public ref char8 CurrentRef
|
|
{
|
|
[Inline]
|
|
get
|
|
{
|
|
return ref mPtr[mIdx];
|
|
}
|
|
}
|
|
|
|
public int Index
|
|
{
|
|
[Inline]
|
|
get
|
|
{
|
|
return mIdx;
|
|
}
|
|
}
|
|
|
|
public int Length
|
|
{
|
|
[Inline]
|
|
get
|
|
{
|
|
return mLength;
|
|
}
|
|
}
|
|
|
|
[Inline]
|
|
public Result<char8> GetNext() mut
|
|
{
|
|
++mIdx;
|
|
if (mIdx >= mLength)
|
|
return .Err;
|
|
return mPtr[mIdx];
|
|
}
|
|
|
|
[Inline]
|
|
public Result<char8*> GetNextRef() mut
|
|
{
|
|
++mIdx;
|
|
if (mIdx >= mLength)
|
|
return .Err;
|
|
return &mPtr[mIdx];
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
public struct StringSplitEnumerator : IEnumerator<StringView>
|
|
{
|
|
StringSplitOptions mSplitOptions;
|
|
char8 mFirstSeparator;
|
|
char8* mSeparatorPtr;
|
|
int32 mSeparatorCount;
|
|
char8* mPtr;
|
|
int_strsize mStrLen;
|
|
int32 mCurCount;
|
|
int32 mMaxCount;
|
|
int_strsize mPos;
|
|
int_strsize mMatchPos;
|
|
|
|
public this(char8* ptr, int strLength, Span<char8> separators, int count, StringSplitOptions splitOptions)
|
|
{
|
|
mPtr = ptr;
|
|
mStrLen = (int_strsize)strLength;
|
|
if (separators.Length > 0)
|
|
{
|
|
mFirstSeparator = separators[0];
|
|
mSeparatorPtr = &separators[0];
|
|
mSeparatorCount = (.)separators.Length;
|
|
}
|
|
else
|
|
{
|
|
mFirstSeparator = 0;
|
|
mSeparatorPtr = null;
|
|
mSeparatorCount = 0;
|
|
}
|
|
mCurCount = 0;
|
|
mMaxCount = (int32)count;
|
|
mPos = 0;
|
|
mMatchPos = -1;
|
|
mSplitOptions = splitOptions;
|
|
}
|
|
|
|
public this(char8* ptr, int strLength, char8 separator, int count, StringSplitOptions splitOptions)
|
|
{
|
|
mPtr = ptr;
|
|
mStrLen = (int_strsize)strLength;
|
|
mFirstSeparator = separator;
|
|
mSeparatorPtr = null;
|
|
mSeparatorCount = 1;
|
|
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 int32 MatchIndex
|
|
{
|
|
get
|
|
{
|
|
return mCurCount - 1;
|
|
}
|
|
}
|
|
|
|
public bool HasMore
|
|
{
|
|
get
|
|
{
|
|
return mMatchPos < mStrLen && (!mSplitOptions.HasFlag(StringSplitOptions.RemoveEmptyEntries) || mStrLen != 0);
|
|
}
|
|
}
|
|
|
|
public StringView Remnant
|
|
{
|
|
get
|
|
{
|
|
int offset = 0;
|
|
if(mMatchPos < mStrLen)
|
|
offset = 1;
|
|
return .(mPtr + mMatchPos + offset, mStrLen - (mMatchPos+offset));
|
|
}
|
|
}
|
|
|
|
public char8 Separator
|
|
{
|
|
get
|
|
{
|
|
if (mMatchPos < 0)
|
|
return 0;
|
|
return mPtr[mMatchPos];
|
|
}
|
|
}
|
|
|
|
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 (mSeparatorCount == 0)
|
|
{
|
|
if (c.IsWhiteSpace)
|
|
foundMatch = true;
|
|
}
|
|
else if (c == mFirstSeparator)
|
|
{
|
|
foundMatch = true;
|
|
}
|
|
else if (mSeparatorCount < 2)
|
|
{
|
|
continue;
|
|
}
|
|
else if (c == mSeparatorPtr[1])
|
|
{
|
|
foundMatch = true;
|
|
}
|
|
else if (mSeparatorCount < 3)
|
|
{
|
|
continue;
|
|
}
|
|
else if (c == mSeparatorPtr[2])
|
|
{
|
|
foundMatch = true;
|
|
}
|
|
else if (mSeparatorCount < 3)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 3; i < mSeparatorCount; i++)
|
|
if (c == mSeparatorPtr[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 StringStringSplitEnumerator : IEnumerator<StringView>
|
|
{
|
|
StringSplitOptions mSplitOptions;
|
|
StringView mFirstSeparator;
|
|
Span<StringView> mSeparators;
|
|
char8* mPtr;
|
|
int_strsize mStrLen;
|
|
int32 mCurCount;
|
|
int32 mMaxCount;
|
|
int_strsize mPos;
|
|
int_strsize mMatchPos;
|
|
int_strsize mMatchLen;
|
|
|
|
public this(char8* ptr, int strLength, Span<StringView> separators, int count, StringSplitOptions splitOptions)
|
|
{
|
|
mPtr = ptr;
|
|
mStrLen = (int_strsize)strLength;
|
|
if (separators.Length > 0)
|
|
mFirstSeparator = separators[0];
|
|
else
|
|
mFirstSeparator = .();
|
|
mSeparators = separators;
|
|
mCurCount = 0;
|
|
mMaxCount = (int32)count;
|
|
mPos = 0;
|
|
mMatchPos = -1;
|
|
mMatchLen = 1;
|
|
mSplitOptions = splitOptions;
|
|
}
|
|
|
|
public this(char8* ptr, int strLength, StringView separator, int count, StringSplitOptions splitOptions)
|
|
{
|
|
mPtr = ptr;
|
|
mStrLen = (int_strsize)strLength;
|
|
mFirstSeparator = separator;
|
|
mSeparators = null;
|
|
mCurCount = 0;
|
|
mMaxCount = (int32)count;
|
|
mPos = 0;
|
|
mMatchPos = -1;
|
|
mMatchLen = 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 int32 MatchIndex
|
|
{
|
|
get
|
|
{
|
|
return mCurCount - 1;
|
|
}
|
|
}
|
|
|
|
public bool HasMore
|
|
{
|
|
get
|
|
{
|
|
return mMatchPos < mStrLen && (!mSplitOptions.HasFlag(StringSplitOptions.RemoveEmptyEntries) || mStrLen != 0);
|
|
}
|
|
}
|
|
|
|
public StringView Remnant
|
|
{
|
|
get
|
|
{
|
|
int offset = 0;
|
|
if(mMatchPos < mStrLen)
|
|
offset = mMatchLen;
|
|
return .(mPtr + mMatchPos + offset, mStrLen - (mMatchPos+offset));
|
|
}
|
|
}
|
|
|
|
public char8 Separator
|
|
{
|
|
get
|
|
{
|
|
if (mMatchPos < 0)
|
|
return 0;
|
|
return mPtr[mMatchPos];
|
|
}
|
|
}
|
|
|
|
public bool MoveNext() mut
|
|
{
|
|
if (mCurCount >= mMaxCount)
|
|
return false;
|
|
|
|
mPos = mMatchPos + mMatchLen;
|
|
|
|
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
|
|
{
|
|
if (mFirstSeparator.IsNull && (mSeparators.IsEmpty) && mPtr[mMatchPos].IsWhiteSpace)
|
|
{
|
|
foundMatch = true;
|
|
mMatchLen = 1;
|
|
}
|
|
else if (mFirstSeparator.Length <= mStrLen - mMatchPos && StringView(&mPtr[mMatchPos], mFirstSeparator.Length) == mFirstSeparator)
|
|
{
|
|
foundMatch = true;
|
|
mMatchLen = (int_strsize)mFirstSeparator.Length;
|
|
}
|
|
else if (!mSeparators.IsEmpty)
|
|
{
|
|
for (int i = 1; i < mSeparators.Length; i++)
|
|
{
|
|
if (mSeparators[i].Length <= mStrLen - mMatchPos && StringView(&mPtr[mMatchPos], mSeparators[i].Length) == mSeparators[i])
|
|
{
|
|
foundMatch = true;
|
|
mMatchLen = (int_strsize)mSeparators[i].Length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundMatch)
|
|
{
|
|
if ((mMatchPos >= mPos + 1) || (!mSplitOptions.HasFlag(StringSplitOptions.RemoveEmptyEntries)))
|
|
return true;
|
|
mPos = mMatchPos + mMatchLen;
|
|
if (mPos >= mStrLen)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
mMatchLen = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
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, IHashable
|
|
{
|
|
public this()
|
|
{
|
|
mPtr = null;
|
|
mLength = 0;
|
|
}
|
|
|
|
public this(String string)
|
|
{
|
|
if (string == null)
|
|
{
|
|
this = default;
|
|
return;
|
|
}
|
|
mPtr = string.Ptr;
|
|
mLength = string.Length;
|
|
}
|
|
|
|
public this(String string, int offset)
|
|
{
|
|
if (string == null)
|
|
{
|
|
Debug.Assert(offset == 0);
|
|
this = default;
|
|
return;
|
|
}
|
|
Debug.Assert((uint)offset <= (uint)string.Length);
|
|
mPtr = string.Ptr + offset;
|
|
mLength = string.Length - offset;
|
|
}
|
|
|
|
public this(String string, int offset, int length)
|
|
{
|
|
if (string == null)
|
|
{
|
|
Debug.Assert(offset == 0 && length == 0);
|
|
this = default;
|
|
return;
|
|
}
|
|
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)
|
|
{
|
|
if (arr == null)
|
|
{
|
|
Debug.Assert(offset == 0 && length == 0);
|
|
this = default;
|
|
return;
|
|
}
|
|
Debug.Assert((uint)offset + (uint)length <= (uint)arr.Count);
|
|
mPtr = arr.CArray() + offset;
|
|
mLength = length;
|
|
}
|
|
|
|
public this(char8* ptr)
|
|
{
|
|
if (ptr == null)
|
|
{
|
|
this = default;
|
|
return;
|
|
}
|
|
mPtr = ptr;
|
|
mLength = String.StrLen(ptr);
|
|
}
|
|
|
|
public this(char8* ptr, int length)
|
|
{
|
|
if (ptr == null)
|
|
{
|
|
Debug.Assert(length == 0);
|
|
this = default;
|
|
return;
|
|
}
|
|
mPtr = ptr;
|
|
mLength = length;
|
|
}
|
|
|
|
public this(Span<uint8> data)
|
|
{
|
|
mPtr = (.)data.Ptr;
|
|
mLength = data.Length;
|
|
}
|
|
|
|
public ref char8 this[int index]
|
|
{
|
|
[Checked]
|
|
get
|
|
{
|
|
Runtime.Assert((uint)index < (uint)mLength);
|
|
return ref mPtr[index];
|
|
}
|
|
|
|
[Unchecked, Inline]
|
|
get
|
|
{
|
|
return ref mPtr[index];
|
|
}
|
|
}
|
|
|
|
public ref char8 this[Index index]
|
|
{
|
|
[Checked]
|
|
get
|
|
{
|
|
int idx;
|
|
switch (index)
|
|
{
|
|
case .FromFront(let offset): idx = offset;
|
|
case .FromEnd(let offset): idx = mLength - offset;
|
|
}
|
|
Runtime.Assert((uint)idx < (uint)mLength);
|
|
return ref mPtr[idx];
|
|
}
|
|
|
|
[Unchecked, Inline]
|
|
get
|
|
{
|
|
int idx;
|
|
switch (index)
|
|
{
|
|
case .FromFront(let offset): idx = offset;
|
|
case .FromEnd(let offset): idx = mLength - offset;
|
|
}
|
|
return ref mPtr[idx];
|
|
}
|
|
}
|
|
|
|
public StringView this[IndexRange range]
|
|
{
|
|
#if !DEBUG
|
|
[Inline]
|
|
#endif
|
|
get
|
|
{
|
|
int start = GetRangeStart(range);
|
|
int end = GetRangeEnd(range);
|
|
|
|
return .(mPtr + start, end - start);
|
|
}
|
|
}
|
|
|
|
public int NumCodePoints
|
|
{
|
|
get
|
|
{
|
|
char8* ptr = Ptr;
|
|
|
|
int count = 0;
|
|
for (int i < mLength)
|
|
{
|
|
char8 c = ptr[i];
|
|
if (((uint8)c & 0xC0) != 0x80)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
|
|
private int GetRangeStart(IndexRange range)
|
|
{
|
|
switch (range.Start)
|
|
{
|
|
case .FromFront(let offset):
|
|
Debug.Assert((uint)offset <= (uint)mLength);
|
|
return offset;
|
|
case .FromEnd(let offset):
|
|
Debug.Assert((uint)offset <= (uint)mLength);
|
|
return mLength - offset;
|
|
}
|
|
}
|
|
|
|
private int GetRangeEnd(IndexRange range)
|
|
{
|
|
if (range.IsClosed)
|
|
{
|
|
switch (range.End)
|
|
{
|
|
case .FromFront(let offset):
|
|
Debug.Assert((uint)offset < (uint)mLength);
|
|
return offset + 1;
|
|
case .FromEnd(let offset):
|
|
Debug.Assert((uint)(offset - 1) <= (uint)mLength);
|
|
return mLength - offset + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (range.End)
|
|
{
|
|
case .FromFront(let offset):
|
|
Debug.Assert((uint)offset <= (uint)mLength);
|
|
return offset;
|
|
case .FromEnd(let offset):
|
|
Debug.Assert((uint)offset <= (uint)mLength);
|
|
return mLength - offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 bool HasMultibyteChars
|
|
{
|
|
get
|
|
{
|
|
char8* ptr = Ptr;
|
|
int len = Length;
|
|
for (int i < len)
|
|
if (ptr[i] >= '\x80')
|
|
return true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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.Quote(mPtr, mLength, outString);
|
|
return;
|
|
}
|
|
outString.Append(mPtr, mLength);
|
|
}
|
|
|
|
void IPrintable.Print(String outString)
|
|
{
|
|
String.Quote(mPtr, mLength, outString);
|
|
}
|
|
|
|
[Commutable]
|
|
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);
|
|
}
|
|
|
|
[Commutable]
|
|
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 operator<=>(StringView val1, StringView val2)
|
|
{
|
|
return String.[Friend]CompareOrdinalHelper(val1.mPtr, val1.mLength, val2.mPtr, val2.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 int CompareTo(StringView strB, bool ignoreCase = false)
|
|
{
|
|
if (ignoreCase)
|
|
return String.[Friend]CompareOrdinalIgnoreCaseHelper(Ptr, Length, strB.Ptr, strB.Length);
|
|
return String.[Friend]CompareOrdinalHelper(Ptr, Length, strB.Ptr, strB.Length);
|
|
}
|
|
|
|
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.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.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 int IndexOfAny(char8[] targets)
|
|
{
|
|
return IndexOfAny(targets, 0, mLength);
|
|
}
|
|
|
|
public int IndexOfAny(char8[] targets, int startIdx)
|
|
{
|
|
return IndexOfAny(targets, startIdx, mLength - startIdx);
|
|
}
|
|
|
|
public int IndexOfAny(char8[] targets, int startIdx, int count)
|
|
{
|
|
let ptr = mPtr;
|
|
for (var i = startIdx; i < count; i++)
|
|
{
|
|
let ch = ptr[i];
|
|
for (let tag in targets)
|
|
{
|
|
if (ch == tag)
|
|
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, bool ignoreCase = false)
|
|
{
|
|
return IndexOf(stringView, ignoreCase) != -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 void TrimEnd(char32 trimChar) 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 != trimChar)
|
|
{
|
|
if (i < mLength - 1)
|
|
{
|
|
mLength = i + 1;
|
|
}
|
|
return;
|
|
}
|
|
i = idx;
|
|
}
|
|
else if (c != (char32)trimChar)
|
|
{
|
|
if (i < mLength - 1)
|
|
{
|
|
mLength = i + 1;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
Clear();
|
|
}
|
|
|
|
public void TrimEnd(char8 trimChar) mut
|
|
{
|
|
TrimEnd((char32)trimChar);
|
|
}
|
|
|
|
public void TrimStart(char32 trimChar) 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 != trimChar)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
mPtr += i;
|
|
mLength -= i;
|
|
}
|
|
return;
|
|
}
|
|
i += len - 1;
|
|
}
|
|
else if (c != (char32)trimChar)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
mPtr += i;
|
|
mLength -= i;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
Clear();
|
|
}
|
|
|
|
public void TrimStart(char8 trimChar) mut
|
|
{
|
|
TrimStart((char32)trimChar);
|
|
}
|
|
|
|
public void Trim(char32 trimChar) mut
|
|
{
|
|
TrimStart(trimChar);
|
|
TrimEnd(trimChar);
|
|
}
|
|
|
|
public void Trim(char8 trimChar) mut
|
|
{
|
|
TrimStart((.)trimChar);
|
|
TrimEnd((.)trimChar);
|
|
}
|
|
|
|
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 QuoteString(String outString)
|
|
{
|
|
String.Quote(Ptr, Length, outString);
|
|
}
|
|
|
|
public Result<void> UnQuoteString(String outString)
|
|
{
|
|
return String.Unquote(Ptr, Length, outString);
|
|
}
|
|
|
|
public Result<void> Unescape(String outString)
|
|
{
|
|
return String.Unescape(Ptr, Length, outString);
|
|
}
|
|
|
|
[NoDiscard]
|
|
public StringView Substring(int pos)
|
|
{
|
|
return .(this, pos);
|
|
}
|
|
|
|
[NoDiscard]
|
|
public StringView Substring(int pos, int length)
|
|
{
|
|
return .(this, pos, length);
|
|
}
|
|
|
|
[NoDiscard]
|
|
public StringView Substring(IndexRange range)
|
|
{
|
|
return .(this)[range];
|
|
}
|
|
|
|
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 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 StringSplitEnumerator Split(char8[] separators, StringSplitOptions options)
|
|
{
|
|
return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, options);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView c)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, c, Int32.MaxValue, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView separator, int count)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separator, count, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView separator, StringSplitOptions options)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separator, Int32.MaxValue, options);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(StringView separator, int count, StringSplitOptions options)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separator, count, options);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(params Span<StringView> separators)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(Span<StringView> separators)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(Span<StringView> separators, int count)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, count, StringSplitOptions.None);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(Span<StringView> separators, int count, StringSplitOptions options)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, count, options);
|
|
}
|
|
|
|
public StringStringSplitEnumerator Split(Span<StringView> separators, StringSplitOptions options)
|
|
{
|
|
return StringStringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, options);
|
|
}
|
|
|
|
public String Intern()
|
|
{
|
|
using (String.Interns.sMonitor.Enter())
|
|
{
|
|
bool needsLiteralPass = String.Interns.sInterns.Count == 0;
|
|
String* internalLinkPtr = *((String**)(String.[Friend]sStringLiterals));
|
|
if (internalLinkPtr != String.[Friend]sPrevInternLinkPtr)
|
|
{
|
|
String.[Friend]sPrevInternLinkPtr = internalLinkPtr;
|
|
needsLiteralPass = true;
|
|
}
|
|
if (needsLiteralPass)
|
|
String.[Friend]CheckLiterals(String.[Friend]sStringLiterals);
|
|
|
|
String* entryPtr;
|
|
if (String.Interns.sInterns.TryAddAlt(this, out entryPtr))
|
|
{
|
|
String result = new String(mLength + 1);
|
|
result.Append(this);
|
|
result.EnsureNullTerminator();
|
|
*entryPtr = result;
|
|
String.Interns.sOwnedInterns.Add(result);
|
|
return result;
|
|
}
|
|
return *entryPtr;
|
|
}
|
|
}
|
|
|
|
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);
|
|
c_wchar* buf;
|
|
if (encodedLen < 128)
|
|
{
|
|
buf = scope:mixin c_wchar[encodedLen+1]* ( ? );
|
|
}
|
|
else
|
|
{
|
|
buf = new c_wchar[encodedLen+1]* ( ? );
|
|
defer:mixin delete buf;
|
|
}
|
|
|
|
if (sizeof(c_wchar) == 2)
|
|
UTF16.Encode(this, (.)buf, encodedLen);
|
|
else
|
|
UTF32.Encode(this, (.)buf, encodedLen);
|
|
buf[encodedLen] = 0;
|
|
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
|
|
class StringTest
|
|
{
|
|
[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
|
|
}
|