yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Sam EstepNote that `Slang::String` is owned/null-terminated (#7648)bcd53322f

master
28.4 KiB957 linesraw
1#ifndef SLANG_CORE_STRING_H
2#define SLANG_CORE_STRING_H
3
4#include "slang-common.h"
5#include "slang-hash.h"
6#include "slang-secure-crt.h"
7#include "slang-smart-pointer.h"
8#include "slang-stable-hash.h"
9
10#include <cstdlib>
11#include <iostream>
12#include <new>
13#include <stdio.h>
14#include <string.h>
15#include <type_traits>
16
17namespace Slang
18{
19class _EndLine
20{
21};
22extern _EndLine EndLine;
23
24// in-place reversion, works only for ascii string
25inline void reverseInplaceAscii(char* buffer, int length)
26{
27    int i, j;
28    char c;
29    for (i = 0, j = length - 1; i < j; i++, j--)
30    {
31        c = buffer[i];
32        buffer[i] = buffer[j];
33        buffer[j] = c;
34    }
35}
36template<typename IntType>
37inline int intToAscii(char* buffer, IntType val, int radix, int padTo = 0)
38{
39    static_assert(std::is_integral_v<IntType>);
40
41    int i = 0;
42    IntType sign;
43
44    sign = val;
45    if (sign < 0)
46    {
47        val = (IntType)(0 - val);
48    }
49
50    do
51    {
52        int digit = (val % radix);
53        if (digit <= 9)
54            buffer[i++] = (char)(digit + '0');
55        else
56            buffer[i++] = (char)(digit - 10 + 'A');
57    } while ((val /= radix) > 0);
58
59    SLANG_ASSERT(i >= 0);
60    while (i < padTo)
61        buffer[i++] = '0';
62
63    if (sign < 0)
64        buffer[i++] = '-';
65
66    // Put in normal character order
67    reverseInplaceAscii(buffer, i);
68
69    buffer[i] = '\0';
70    return i;
71}
72
73SLANG_FORCE_INLINE bool isUtf8LeadingByte(char ch)
74{
75    return (((unsigned char)ch) & 0xC0) == 0xC0;
76}
77
78SLANG_FORCE_INLINE bool isUtf8ContinuationByte(char ch)
79{
80    return (((unsigned char)ch) & 0xC0) == 0x80;
81}
82
83/* A string slice that doesn't own the contained characters.
84It is the responsibility of code using the type to keep the memory backing
85the slice in scope.
86A slice is generally *not* zero terminated. */
87struct SLANG_RT_API UnownedStringSlice
88{
89public:
90    typedef UnownedStringSlice ThisType;
91
92    // Type to indicate that a ctor is with a length to disabmiguate 0/nullptr
93    // causing ambiguity.
94    struct WithLength
95    {
96    };
97
98    UnownedStringSlice()
99        : m_begin(nullptr), m_end(nullptr)
100    {
101    }
102
103    explicit UnownedStringSlice(char const* a)
104        : m_begin(a), m_end(a ? a + strlen(a) : nullptr)
105    {
106    }
107    UnownedStringSlice(char const* b, char const* e)
108        : m_begin(b), m_end(e)
109    {
110    }
111    UnownedStringSlice(char const* b, size_t len)
112        : m_begin(b), m_end(b + len)
113    {
114    }
115    UnownedStringSlice(WithLength, char const* b, size_t len)
116        : m_begin(b), m_end(b + len)
117    {
118    }
119
120    SLANG_FORCE_INLINE char const* begin() const { return m_begin; }
121
122    SLANG_FORCE_INLINE char const* end() const { return m_end; }
123
124    /// True if slice is strictly contained in memory.
125    bool isMemoryContained(const UnownedStringSlice& slice) const
126    {
127        return slice.m_begin >= m_begin && slice.m_end <= m_end;
128    }
129    bool isMemoryContained(const char* pos) const { return pos >= m_begin && pos <= m_end; }
130
131    /// Get the length in *bytes*
132    Count getLength() const { return Index(m_end - m_begin); }
133
134    /// Finds first index of char 'c'. If not found returns -1.
135    Index indexOf(char c) const;
136    /// Find first index of slice. If not found returns -1
137    Index indexOf(const UnownedStringSlice& slice) const;
138
139    /// Returns a substring. idx is the start index, and len
140    /// is the amount of characters.
141    /// The returned length might be truncated, if len extends beyond slice.
142    UnownedStringSlice subString(Index idx, Index len) const;
143
144    /// Return a head of the slice - everything up to the index
145    SLANG_FORCE_INLINE UnownedStringSlice head(Index idx) const
146    {
147        SLANG_ASSERT(idx >= 0 && idx <= getLength());
148        return UnownedStringSlice(m_begin, idx);
149    }
150    /// Return a tail of the slice - everything from the index to the end of the slice
151    SLANG_FORCE_INLINE UnownedStringSlice tail(Index idx) const
152    {
153        SLANG_ASSERT(idx >= 0 && idx <= getLength());
154        return UnownedStringSlice(m_begin + idx, m_end);
155    }
156
157    /// True if rhs and this are equal without having to take into account case
158    /// Note 'case' here is *not* locale specific - it is only A-Z and a-z
159    bool caseInsensitiveEquals(const ThisType& rhs) const;
160
161    Index lastIndexOf(char c) const
162    {
163        const Index size = Index(m_end - m_begin);
164        for (Index i = size - 1; i >= 0; --i)
165        {
166            if (m_begin[i] == c)
167            {
168                return i;
169            }
170        }
171        return -1;
172    }
173
174    const char& operator[](Index i) const
175    {
176        assert(i >= 0 && i < Index(m_end - m_begin));
177        return m_begin[i];
178    }
179
180    bool operator==(ThisType const& other) const;
181    bool operator!=(UnownedStringSlice const& other) const { return !(*this == other); }
182
183    bool operator==(char const* str) const { return (*this) == UnownedStringSlice(str); }
184    bool operator!=(char const* str) const { return !(*this == str); }
185
186    /// True if contents is a single char of c
187    SLANG_FORCE_INLINE bool isChar(char c) const { return getLength() == 1 && m_begin[0] == c; }
188
189    bool startsWithCaseInsensitive(UnownedStringSlice const& other) const;
190    bool startsWith(UnownedStringSlice const& other) const;
191    bool startsWith(char const* str) const;
192
193    bool endsWithCaseInsensitive(UnownedStringSlice const& other) const;
194    bool endsWithCaseInsensitive(char const* str) const;
195
196    bool endsWith(UnownedStringSlice const& other) const;
197    bool endsWith(char const* str) const;
198
199    /// Trims any horizontal whitespace from the start and end and returns as a substring
200    UnownedStringSlice trim() const;
201    /// Trims any 'c' from the start or the end, and returns as a substring
202    UnownedStringSlice trim(char c) const;
203
204    /// Trims any horizontal whitespace from start and returns as a substring
205    UnownedStringSlice trimStart() const;
206
207    static constexpr bool kHasUniformHash = true;
208    HashCode64 getHashCode() const { return Slang::getHashCode(m_begin, size_t(m_end - m_begin)); }
209
210    template<size_t SIZE>
211    SLANG_FORCE_INLINE static UnownedStringSlice fromLiteral(const char (&in)[SIZE])
212    {
213        return UnownedStringSlice(in, SIZE - 1);
214    }
215
216protected:
217    char const* m_begin;
218    char const* m_end;
219};
220
221/// Three-way comparison of string slices.
222///
223/// * Returns 0 if `lhs == rhs`
224/// * Returns a value < 0 if `lhs < rhs`
225/// * Returns a value > 0 if `lhs > rhs`
226///
227int compare(UnownedStringSlice const& lhs, UnownedStringSlice const& rhs);
228
229// A more convenient way to make slices from *string literals*
230template<size_t SIZE>
231SLANG_FORCE_INLINE UnownedStringSlice toSlice(const char (&in)[SIZE])
232{
233    return UnownedStringSlice(in, SIZE - 1);
234}
235
236/// Same as UnownedStringSlice, but must be zero terminated.
237/// Zero termination is *not* included in the length.
238struct SLANG_RT_API UnownedTerminatedStringSlice : public UnownedStringSlice
239{
240public:
241    typedef UnownedStringSlice Super;
242    typedef UnownedTerminatedStringSlice ThisType;
243
244    /// We can turn into a regular zero terminated string
245    SLANG_FORCE_INLINE operator const char*() const { return m_begin; }
246
247    /// Exists to match the equivalent function in String.
248    SLANG_FORCE_INLINE char const* getBuffer() const { return m_begin; }
249
250    /// Construct from a literal directly.
251    template<size_t SIZE>
252    SLANG_FORCE_INLINE static ThisType fromLiteral(const char (&in)[SIZE])
253    {
254        return ThisType(in, SIZE - 1);
255    }
256
257    /// Default constructor
258    UnownedTerminatedStringSlice()
259        : Super(Super::WithLength(), "", 0)
260    {
261    }
262
263    /// Note, b cannot be null because if it were then the string would not be null terminated
264    UnownedTerminatedStringSlice(char const* b)
265        : Super(b, b + strlen(b))
266    {
267    }
268    UnownedTerminatedStringSlice(char const* b, size_t len)
269        : Super(b, len)
270    {
271        // b must be valid and it must be null terminated
272        SLANG_ASSERT(b && b[len] == 0);
273    }
274};
275
276// A more convenient way to make terminated slices from *string literals*
277template<size_t SIZE>
278SLANG_FORCE_INLINE UnownedTerminatedStringSlice toTerminatedSlice(const char (&in)[SIZE])
279{
280    return UnownedTerminatedStringSlice(in, SIZE - 1);
281}
282
283// A `StringRepresentation` provides the backing storage for
284// all reference-counted string-related types.
285class SLANG_RT_API StringRepresentation : public RefObject
286{
287public:
288    Index length;
289    Index capacity;
290
291    SLANG_FORCE_INLINE Index getLength() const { return length; }
292
293    SLANG_FORCE_INLINE char* getData() { return (char*)(this + 1); }
294    SLANG_FORCE_INLINE const char* getData() const { return (const char*)(this + 1); }
295
296    /// Set the contents to be the slice. Must be enough capacity to hold the slice.
297    void setContents(const UnownedStringSlice& slice);
298
299    static const char* getData(const StringRepresentation* stringRep)
300    {
301        return stringRep ? stringRep->getData() : "";
302    }
303
304    static UnownedStringSlice asSlice(const StringRepresentation* rep)
305    {
306        return rep ? UnownedStringSlice(rep->getData(), rep->getLength()) : UnownedStringSlice();
307    }
308
309    static bool equal(const StringRepresentation* a, const StringRepresentation* b)
310    {
311        return (a == b) || asSlice(a) == asSlice(b);
312    }
313
314    static StringRepresentation* createWithCapacityAndLength(Index capacity, Index length)
315    {
316        SLANG_ASSERT(capacity >= length);
317        void* allocation = operator new(sizeof(StringRepresentation) + capacity + 1);
318        StringRepresentation* obj = new (allocation) StringRepresentation();
319        obj->capacity = capacity;
320        obj->length = length;
321        obj->getData()[length] = 0;
322        return obj;
323    }
324
325    static StringRepresentation* createWithCapacity(Index capacity)
326    {
327        return createWithCapacityAndLength(capacity, 0);
328    }
329
330    static StringRepresentation* createWithLength(Index length)
331    {
332        return createWithCapacityAndLength(length, length);
333    }
334
335    /// Create a representation from the slice. If slice is empty will return nullptr.
336    static StringRepresentation* create(const UnownedStringSlice& slice);
337    /// Same as create, but representation will have refcount of 1 (if not nullptr)
338    static StringRepresentation* createWithReference(const UnownedStringSlice& slice);
339
340    StringRepresentation* cloneWithCapacity(Index newCapacity)
341    {
342        StringRepresentation* newObj = createWithCapacityAndLength(newCapacity, length);
343        memcpy(getData(), newObj->getData(), length + 1);
344        return newObj;
345    }
346
347    StringRepresentation* clone() { return cloneWithCapacity(length); }
348
349    StringRepresentation* ensureCapacity(Index required)
350    {
351        if (capacity >= required)
352            return this;
353
354        Index newCapacity = capacity;
355        if (!newCapacity)
356            newCapacity = 16; // TODO: figure out good value for minimum capacity
357
358        while (newCapacity < required)
359        {
360            newCapacity = 2 * newCapacity;
361        }
362
363        return cloneWithCapacity(newCapacity);
364    }
365
366    /// Overload delete to silence ASAN new-delete-type-mismatch errors.
367    /// These occur because the allocation size of StringRepresentation
368    /// does not match deallocation size (due variable sized string payload).
369    void operator delete(void* p)
370    {
371        StringRepresentation* str = (StringRepresentation*)p;
372        ::operator delete(str);
373    }
374};
375
376class String;
377
378struct SLANG_RT_API StringSlice
379{
380public:
381    StringSlice();
382
383    StringSlice(String const& str);
384
385    StringSlice(String const& str, UInt beginIndex, UInt endIndex);
386
387    UInt getLength() const { return endIndex - beginIndex; }
388
389    char const* begin() const
390    {
391        return representation ? representation->getData() + beginIndex : "";
392    }
393
394    char const* end() const { return begin() + getLength(); }
395
396private:
397    RefPtr<StringRepresentation> representation;
398    UInt beginIndex;
399    UInt endIndex;
400
401    friend class String;
402
403    StringSlice(RefPtr<StringRepresentation> const& representation, UInt beginIndex, UInt endIndex)
404        : representation(representation), beginIndex(beginIndex), endIndex(endIndex)
405    {
406    }
407};
408
409/// String as expected by underlying platform APIs
410class SLANG_RT_API OSString
411{
412public:
413    /// Default
414    OSString();
415    /// NOTE! This assumes that begin is a new wchar_t[] buffer, and it will
416    /// now be owned by the OSString
417    OSString(wchar_t* begin, wchar_t* end);
418    /// Move Ctor
419    OSString(OSString&& rhs)
420        : m_begin(rhs.m_begin), m_end(rhs.m_end)
421    {
422        rhs.m_begin = nullptr;
423        rhs.m_end = nullptr;
424    }
425    // Copy Ctor
426    OSString(const OSString& rhs)
427        : m_begin(nullptr), m_end(nullptr)
428    {
429        set(rhs.m_begin, rhs.m_end);
430    }
431
432    /// =
433    void operator=(const OSString& rhs) { set(rhs.m_begin, rhs.m_end); }
434    void operator=(OSString&& rhs)
435    {
436        auto begin = m_begin;
437        auto end = m_end;
438        m_begin = rhs.m_begin;
439        m_end = rhs.m_end;
440        rhs.m_begin = begin;
441        rhs.m_end = end;
442    }
443
444    ~OSString() { _releaseBuffer(); }
445
446    size_t getLength() const { return (m_end - m_begin); }
447    void set(const wchar_t* begin, const wchar_t* end);
448
449    operator wchar_t const*() const { return begin(); }
450
451    wchar_t const* begin() const;
452    wchar_t const* end() const;
453
454private:
455    void _releaseBuffer();
456
457    wchar_t* m_begin; ///< First character. This is a new wchar_t[] buffer
458    wchar_t* m_end;   ///< Points to terminating 0
459};
460
461/*!
462@brief Represents an owned, zero-terminated UTF-8 encoded string.
463*/
464class SLANG_RT_API String
465{
466    friend struct StringSlice;
467    friend class StringBuilder;
468
469private:
470    char* getData() const { return m_buffer ? m_buffer->getData() : (char*)""; }
471
472
473    void ensureUniqueStorageWithCapacity(Index capacity);
474
475    RefPtr<StringRepresentation> m_buffer;
476
477public:
478    explicit String(StringRepresentation* buffer)
479        : m_buffer(buffer)
480    {
481    }
482
483    static String fromWString(const wchar_t* wstr);
484    static String fromWString(const wchar_t* wstr, const wchar_t* wend);
485    static String fromWChar(const wchar_t ch);
486    static String fromUnicodePoint(Char32 codePoint);
487
488    String() {}
489
490    /// Returns a buffer which can hold at least count chars
491    char* prepareForAppend(Index count);
492    /// Append data written to buffer output via 'prepareForAppend' directly written 'inplace'
493    void appendInPlace(const char* chars, Index count);
494
495    /// Get the internal string represenation
496    SLANG_FORCE_INLINE StringRepresentation* getStringRepresentation() const { return m_buffer; }
497
498    /// Detach the representation (will leave string as empty). Rep ref count will remain unchanged.
499    SLANG_FORCE_INLINE StringRepresentation* detachStringRepresentation()
500    {
501        return m_buffer.detach();
502    }
503
504    const char* begin() const { return getData(); }
505    const char* end() const { return getData() + getLength(); }
506
507    void append(int32_t value, int radix = 10);
508    void append(uint32_t value, int radix = 10);
509    void append(int64_t value, int radix = 10);
510    void append(uint64_t value, int radix = 10);
511    void append(float val, const char* format = "%g");
512    void append(double val, const char* format = "%g");
513
514    // Padded hex representations
515    void append(StableHashCode32 val);
516    void append(StableHashCode64 val);
517
518    void append(char const* str);
519    void append(char const* str, size_t len);
520    void append(const char* textBegin, char const* textEnd);
521    void append(char chr);
522    void append(String const& str);
523    void append(StringSlice const& slice);
524    void append(UnownedStringSlice const& slice);
525
526    /// Append a character (to remove ambiguity with other integral types)
527    void appendChar(char chr);
528
529    /// Append the specified char count times
530    void appendRepeatedChar(char chr, Index count);
531
532    String(const char* str) { append(str); }
533    String(const char* textBegin, char const* textEnd) { append(textBegin, textEnd); }
534
535    // Make all String ctors from a numeric explicit, to avoid unexpected/unnecessary conversions
536    explicit String(int32_t val, int radix = 10) { append(val, radix); }
537    explicit String(uint32_t val, int radix = 10) { append(val, radix); }
538    explicit String(int64_t val, int radix = 10) { append(val, radix); }
539    explicit String(uint64_t val, int radix = 10) { append(val, radix); }
540    explicit String(StableHashCode32 val) { append(val); }
541    explicit String(StableHashCode64 val) { append(val); }
542    explicit String(float val, const char* format = "%g") { append(val, format); }
543    explicit String(double val, const char* format = "%g") { append(val, format); }
544
545    explicit String(char chr) { appendChar(chr); }
546    String(String const& str) { m_buffer = str.m_buffer; }
547    String(String&& other) { m_buffer = _Move(other.m_buffer); }
548
549    String(StringSlice const& slice) { append(slice); }
550
551    String(UnownedStringSlice const& slice) { append(slice); }
552
553    ~String() { m_buffer.setNull(); }
554
555    String& operator=(const String& str)
556    {
557        m_buffer = str.m_buffer;
558        return *this;
559    }
560    String& operator=(String&& other)
561    {
562        m_buffer = _Move(other.m_buffer);
563        return *this;
564    }
565    char operator[](Index id) const
566    {
567        SLANG_ASSERT(id >= 0 && id < getLength());
568        // Silence a pedantic warning on GCC
569#if __GNUC__
570        if (id < 0)
571            __builtin_unreachable();
572#endif
573        return begin()[id];
574    }
575
576    Index getLength() const { return m_buffer ? m_buffer->getLength() : 0; }
577    /// Make the length of the string the amount specified. Must be less than current size
578    void reduceLength(Index length);
579
580    friend String operator+(const char* op1, const String& op2);
581    friend String operator+(const String& op1, const char* op2);
582    friend String operator+(const String& op1, const String& op2);
583
584    StringSlice trimStart() const
585    {
586        if (!m_buffer)
587            return StringSlice();
588        Index startIndex = 0;
589        const char* const data = getData();
590        while (startIndex < getLength() && (data[startIndex] == ' ' || data[startIndex] == '\t' ||
591                                            data[startIndex] == '\r' || data[startIndex] == '\n'))
592            startIndex++;
593        return StringSlice(m_buffer, startIndex, getLength());
594    }
595
596    StringSlice trimEnd() const
597    {
598        if (!m_buffer)
599            return StringSlice();
600
601        Index endIndex = getLength();
602        const char* const data = getData();
603        while (endIndex > 0 && (data[endIndex - 1] == ' ' || data[endIndex - 1] == '\t' ||
604                                data[endIndex - 1] == '\r' || data[endIndex - 1] == '\n'))
605            endIndex--;
606
607        return StringSlice(m_buffer, 0, endIndex);
608    }
609
610    StringSlice trim() const
611    {
612        if (!m_buffer)
613            return StringSlice();
614
615        Index startIndex = 0;
616        const char* const data = getData();
617        while (startIndex < getLength() && (data[startIndex] == ' ' || data[startIndex] == '\t' ||
618                                            data[startIndex] == '\r' || data[startIndex] == '\n'))
619            startIndex++;
620        Index endIndex = getLength();
621        while (endIndex > startIndex && (data[endIndex - 1] == ' ' || data[endIndex - 1] == '\t' ||
622                                         data[endIndex - 1] == '\r' || data[endIndex - 1] == '\n'))
623            endIndex--;
624
625        return StringSlice(m_buffer, startIndex, endIndex);
626    }
627
628    StringSlice subString(Index id, Index len) const
629    {
630        if (len == 0)
631            return StringSlice();
632
633        if (id + len > getLength())
634            len = getLength() - id;
635#if _DEBUG
636        if (id < 0 || id >= getLength() || (id + len) > getLength())
637            SLANG_ASSERT_FAILURE("SubString: index out of range.");
638        if (len < 0)
639            SLANG_ASSERT_FAILURE("SubString: length less than zero.");
640#endif
641        return StringSlice(m_buffer, id, id + len);
642    }
643
644    char const* getBuffer() const { return getData(); }
645
646    OSString toWString(Index* len = 0) const;
647
648    bool equals(const String& str, bool caseSensitive = true)
649    {
650        if (caseSensitive)
651            return (strcmp(begin(), str.begin()) == 0);
652        else
653        {
654#ifdef _MSC_VER
655            return (_stricmp(begin(), str.begin()) == 0);
656#else
657            return (strcasecmp(begin(), str.begin()) == 0);
658#endif
659        }
660    }
661    bool operator==(const char* strbuffer) const { return (strcmp(begin(), strbuffer) == 0); }
662
663    bool operator==(const String& str) const { return (strcmp(begin(), str.begin()) == 0); }
664    bool operator!=(const char* strbuffer) const { return (strcmp(begin(), strbuffer) != 0); }
665    bool operator!=(const String& str) const { return (strcmp(begin(), str.begin()) != 0); }
666    bool operator>(const String& str) const { return (strcmp(begin(), str.begin()) > 0); }
667    bool operator<(const String& str) const { return (strcmp(begin(), str.begin()) < 0); }
668    bool operator>=(const String& str) const { return (strcmp(begin(), str.begin()) >= 0); }
669    bool operator<=(const String& str) const { return (strcmp(begin(), str.begin()) <= 0); }
670
671    SLANG_FORCE_INLINE bool operator==(const UnownedStringSlice& slice) const
672    {
673        return getUnownedSlice() == slice;
674    }
675    SLANG_FORCE_INLINE bool operator!=(const UnownedStringSlice& slice) const
676    {
677        return getUnownedSlice() != slice;
678    }
679
680    String toUpper() const
681    {
682        String result;
683        for (auto c : *this)
684        {
685            char d = (c >= 'a' && c <= 'z') ? (c - ('a' - 'A')) : c;
686            result.append(d);
687        }
688        return result;
689    }
690
691    String toLower() const
692    {
693        String result;
694        for (auto c : *this)
695        {
696            char d = (c >= 'A' && c <= 'Z') ? (c - ('A' - 'a')) : c;
697            result.append(d);
698        }
699        return result;
700    }
701
702    Index indexOf(const char* str, Index id) const // String str
703    {
704        if (id >= getLength())
705            return Index(-1);
706        auto findRs = strstr(begin() + id, str);
707        Index res = findRs ? findRs - begin() : Index(-1);
708        return res;
709    }
710
711    Index indexOf(const String& str, Index id) const { return indexOf(str.begin(), id); }
712
713    Index indexOf(const char* str) const { return indexOf(str, 0); }
714
715    Index indexOf(const String& str) const { return indexOf(str.begin(), 0); }
716
717    void swapWith(String& other) { m_buffer.swapWith(other.m_buffer); }
718
719    Index indexOf(char ch, Index id) const
720    {
721        const Index length = getLength();
722        SLANG_ASSERT(id >= 0 && id <= length);
723
724        if (!m_buffer)
725            return Index(-1);
726
727        const char* data = getData();
728        for (Index i = id; i < length; i++)
729            if (data[i] == ch)
730                return i;
731        return Index(-1);
732    }
733
734    Index indexOf(char ch) const { return indexOf(ch, 0); }
735
736    Index lastIndexOf(char ch) const
737    {
738        const Index length = getLength();
739        const char* data = getData();
740
741        for (Index i = length - 1; i >= 0; --i)
742            if (data[i] == ch)
743                return i;
744        return Index(-1);
745    }
746
747    bool startsWith(const char* str) const
748    {
749        if (!m_buffer)
750            return false;
751        Index strLen = Index(::strlen(str));
752        if (strLen > getLength())
753            return false;
754
755        const char* const data = getData();
756
757        for (Index i = 0; i < strLen; i++)
758            if (str[i] != data[i])
759                return false;
760        return true;
761    }
762
763    bool startsWith(const String& str) const { return startsWith(str.begin()); }
764
765    bool endsWith(char const* str) const // String str
766    {
767        if (!m_buffer)
768            return false;
769
770        const Index strLen = Index(::strlen(str));
771        const Index len = getLength();
772
773        if (strLen > len)
774            return false;
775        const char* data = getData();
776        for (Index i = strLen; i > 0; i--)
777            if (str[i - 1] != data[len - strLen + i - 1])
778                return false;
779        return true;
780    }
781
782    bool endsWith(const String& str) const { return endsWith(str.begin()); }
783
784    bool contains(const char* str) const // String str
785    {
786        return m_buffer && indexOf(str) != Index(-1);
787    }
788
789    bool contains(const String& str) const { return contains(str.begin()); }
790
791    static constexpr bool kHasUniformHash = true;
792    HashCode64 getHashCode() const
793    {
794        return Slang::getHashCode(StringRepresentation::asSlice(m_buffer));
795    }
796
797    UnownedStringSlice getUnownedSlice() const { return StringRepresentation::asSlice(m_buffer); }
798};
799
800class ImmutableHashedString
801{
802public:
803    String slice;
804    HashCode64 hashCode;
805    ImmutableHashedString()
806        : hashCode(0)
807    {
808    }
809    ImmutableHashedString(const UnownedStringSlice& slice)
810        : slice(slice), hashCode(slice.getHashCode())
811    {
812    }
813    ImmutableHashedString(const char* begin, const char* end)
814        : slice(begin, end), hashCode(slice.getHashCode())
815    {
816    }
817    ImmutableHashedString(const char* begin, size_t len)
818        : slice(UnownedStringSlice(begin, len)), hashCode(slice.getHashCode())
819    {
820    }
821    ImmutableHashedString(const char* begin)
822        : slice(begin), hashCode(slice.getHashCode())
823    {
824    }
825    ImmutableHashedString(const String& str)
826        : slice(str), hashCode(str.getHashCode())
827    {
828    }
829    ImmutableHashedString(String&& str)
830        : slice(_Move(str)), hashCode(str.getHashCode())
831    {
832    }
833    ImmutableHashedString(const ImmutableHashedString& other) = default;
834    ImmutableHashedString& operator=(const ImmutableHashedString& other) = default;
835    bool operator==(const ImmutableHashedString& other) const
836    {
837        return hashCode == other.hashCode && slice == other.slice;
838    }
839    bool operator!=(const ImmutableHashedString& other) const
840    {
841        return hashCode != other.hashCode || slice != other.slice;
842    }
843    bool operator==(const UnownedStringSlice& other) const { return slice == other; }
844    bool operator!=(const UnownedStringSlice& other) const { return slice != other; }
845    bool operator==(const String& other) const { return slice == other.getUnownedSlice(); }
846    bool operator!=(const String& other) const { return slice != other.getUnownedSlice(); }
847    bool operator==(const char* other) const { return slice == UnownedStringSlice(other); }
848    HashCode64 getHashCode() const { return hashCode; }
849};
850
851class SLANG_RT_API StringBuilder : public String
852{
853private:
854    enum
855    {
856        InitialSize = 1024
857    };
858
859public:
860    typedef String Super;
861    using Super::append;
862
863    explicit StringBuilder(UInt bufferSize = InitialSize)
864    {
865        ensureUniqueStorageWithCapacity(bufferSize);
866    }
867
868    void ensureCapacity(UInt size) { ensureUniqueStorageWithCapacity(size); }
869    StringBuilder& operator<<(char ch)
870    {
871        appendChar(ch);
872        return *this;
873    }
874    StringBuilder& operator<<(Int32 val)
875    {
876        append(val);
877        return *this;
878    }
879    StringBuilder& operator<<(UInt32 val)
880    {
881        append(val);
882        return *this;
883    }
884    StringBuilder& operator<<(Int64 val)
885    {
886        append(val);
887        return *this;
888    }
889    StringBuilder& operator<<(UInt64 val)
890    {
891        append(val);
892        return *this;
893    }
894    StringBuilder& operator<<(float val)
895    {
896        append(val);
897        return *this;
898    }
899    StringBuilder& operator<<(double val)
900    {
901        append(val);
902        return *this;
903    }
904    StringBuilder& operator<<(const char* str)
905    {
906        append(str, strlen(str));
907        return *this;
908    }
909    StringBuilder& operator<<(const String& str)
910    {
911        append(str);
912        return *this;
913    }
914    StringBuilder& operator<<(UnownedStringSlice const& str)
915    {
916        append(str);
917        return *this;
918    }
919    StringBuilder& operator<<(const _EndLine)
920    {
921        appendChar('\n');
922        return *this;
923    }
924
925    String toString() { return *this; }
926
927    String produceString() { return *this; }
928
929#if 0
930        void Remove(int id, int len)
931        {
932#if _DEBUG
933            if (id >= length || id < 0)
934                SLANG_ASSERT_FAILURE("Remove: Index out of range.");
935            if (len < 0)
936                SLANG_ASSERT_FAILURE("Remove: remove length smaller than zero.");
937#endif
938            int actualDelLength = ((id + len) >= length) ? (length - id) : len;
939            for (int i = id + actualDelLength; i <= length; i++)
940                buffer[i - actualDelLength] = buffer[i];
941            length -= actualDelLength;
942        }
943#endif
944    friend std::ostream& operator<<(std::ostream& stream, const String& s);
945
946    void clear() { m_buffer.setNull(); }
947};
948
949int stringToInt(const String& str, int radix = 10);
950unsigned int stringToUInt(const String& str, int radix = 10);
951double stringToDouble(const String& str);
952float stringToFloat(const String& str);
953} // namespace Slang
954
955std::ostream& operator<<(std::ostream& stream, const Slang::String& s);
956
957#endif