https://github.com/shader-slang/slang
Raw File
Tip revision: 45c7d33fe87e1628de7991f46ca68f8ddd2f7e4c authored by Yong He on 20 March 2024, 22:47:36 UTC
Fix spirv generation for using output stream in a function. (#3806)
Tip revision: 45c7d33
slang-string.cpp
#include "slang-string.h"
#include "slang-text-io.h"

#include "slang-char-util.h"

namespace Slang
{
    // HACK!
    // JS: Many of the inlined functions of CharUtil just access a global map. That referencing this global is *NOT* enough to
    // link correctly with CharUtil on linux for a shared library. The following call exists to try and force linkage of CharUtil
    // for anything that uses core
    static const auto s_charUtilLink = CharUtil::_ensureLink();


    // StringRepresentation

    void StringRepresentation::setContents(const UnownedStringSlice& slice)
    {
        const auto sliceLength = slice.getLength();
        SLANG_ASSERT(sliceLength <= capacity);

        char* chars = getData();

        // Use move (rather than memcpy), because the slice *could* be contained in the StringRepresentation
        ::memmove(chars, slice.begin(), sliceLength * sizeof(char));
        // Zero terminate. 
        chars[sliceLength] = 0;
        // Set the length
        length = sliceLength;
    }


    /* static */StringRepresentation* StringRepresentation::create(const UnownedStringSlice& slice)
    {
        const auto sliceLength = slice.getLength();

        if (sliceLength)
        {
            StringRepresentation* rep = StringRepresentation::createWithLength(sliceLength);
         
            char* chars = rep->getData();
            ::memcpy(chars, slice.begin(), sizeof(char) * sliceLength);
            chars[sliceLength] = 0;

            return rep;
        }
        else
        {
            return nullptr;
        }
    }

    /* static */StringRepresentation* StringRepresentation::createWithReference(const UnownedStringSlice& slice)
    {
        const auto sliceLength = slice.getLength();

        if (sliceLength)
        {
            StringRepresentation* rep = StringRepresentation::createWithLength(sliceLength);
            rep->addReference();

            char* chars = rep->getData();
            ::memcpy(chars, slice.begin(), sizeof(char) * sliceLength);
            chars[sliceLength] = 0;

            return rep;
        }
        else
        {
            return nullptr;
        }
    }

    // OSString

    OSString::OSString()
        : m_begin(nullptr)
        , m_end(nullptr)
    {}

    OSString::OSString(wchar_t* begin, wchar_t* end)
        : m_begin(begin)
        , m_end(end)
    {}

    void OSString::_releaseBuffer()
    {
        if (m_begin)
        {
            delete[] m_begin;
        }
    }

    void OSString::set(const wchar_t* begin, const wchar_t* end)
    {
        if (m_begin)
        {
            delete[] m_begin;
            m_begin = nullptr;
            m_end = nullptr;
        }
        const size_t len = end - begin;
        if (len > 0)
        {
            // TODO(JS): The allocation is only done this way to be compatible with the buffer being detached from an array
            // This is unfortunate, because it means that the allocation stores the size (and alignment fix), which is a shame because we know the size
            m_begin = new wchar_t[len + 1];
            memcpy(m_begin, begin, len * sizeof(wchar_t));
            // Zero terminate
            m_begin[len] = 0;
            m_end = m_begin + len;
        }
    }

    static const wchar_t kEmptyOSString[] = { 0 };

    wchar_t const* OSString::begin() const
    {
        return m_begin ? m_begin : kEmptyOSString;
    }

    wchar_t const* OSString::end() const
    {
        return m_end ? m_end : kEmptyOSString;
    }

    // UnownedStringSlice

    bool UnownedStringSlice::startsWith(UnownedStringSlice const& other) const
    {
        UInt thisSize = getLength();
        UInt otherSize = other.getLength();

        if (otherSize > thisSize)
            return false;

        return head(otherSize) == other;
    }

    bool UnownedStringSlice::startsWith(char const* str) const
    {
        return startsWith(UnownedTerminatedStringSlice(str));
    }

    bool UnownedStringSlice::startsWithCaseInsensitive(UnownedStringSlice const& other) const
    {
        UInt thisSize = getLength();
        UInt otherSize = other.getLength();

        if (otherSize > thisSize)
            return false;

        return head(otherSize).caseInsensitiveEquals(other);
    }


    bool UnownedStringSlice::endsWith(UnownedStringSlice const& other) const
    {
        UInt thisSize = getLength();
        UInt otherSize = other.getLength();

        if (otherSize > thisSize)
            return false;

        return UnownedStringSlice(
            end() - otherSize, end()) == other;
    }

    bool UnownedStringSlice::endsWithCaseInsensitive(UnownedStringSlice const& other) const
    {
        UInt thisSize = getLength();
        UInt otherSize = other.getLength();

        if (otherSize > thisSize)
            return false;

        return UnownedStringSlice(end() - otherSize, end()).caseInsensitiveEquals(other);
    }

    bool UnownedStringSlice::endsWith(char const* str) const
    {
        return endsWith(UnownedTerminatedStringSlice(str));
    }

    bool UnownedStringSlice::endsWithCaseInsensitive(char const* str) const
    {
        return endsWithCaseInsensitive(UnownedTerminatedStringSlice(str));
    }

    UnownedStringSlice UnownedStringSlice::trim() const
    {
        const char* start = m_begin;
        const char* end = m_end;

        while (start < end && CharUtil::isHorizontalWhitespace(*start)) start++;
        while (end > start && CharUtil::isHorizontalWhitespace(end[-1])) end--;
        return UnownedStringSlice(start, end);
    }

    UnownedStringSlice UnownedStringSlice::trimStart() const
    {
        const char* start = m_begin;

        while (start < m_end && CharUtil::isHorizontalWhitespace(*start)) start++;
        return UnownedStringSlice(start, m_end);
    }

    UnownedStringSlice UnownedStringSlice::trim(char c) const
    {
        const char* start = m_begin;
        const char* end = m_end;

        while (start < end && *start == c) start++;
        while (end > start && end[-1] == c) end--;
        return UnownedStringSlice(start, end);
    }

    // StringSlice

    StringSlice::StringSlice()
        : representation(0)
        , beginIndex(0)
        , endIndex(0)
    {}

    StringSlice::StringSlice(String const& str)
        : representation(str.m_buffer)
        , beginIndex(0)
        , endIndex(str.getLength())
    {}

    StringSlice::StringSlice(String const& str, UInt beginIndex, UInt endIndex)
        : representation(str.m_buffer)
        , beginIndex(beginIndex)
        , endIndex(endIndex)
    {}


    //

    _EndLine EndLine;

    String operator+(const char * op1, const String & op2)
    {
        String result(op1);
        result.append(op2);
        return result;
    }

    String operator+(const String & op1, const char * op2)
    {
        String result(op1);
        result.append(op2);
        return result;
    }

    String operator+(const String & op1, const String & op2)
    {
        String result(op1);
        result.append(op2);
        return result;
    }

    int stringToInt(const String& str, int radix)
    {
        if (str.startsWith("0x"))
            return (int)strtoll(str.getBuffer(), NULL, 16);
        else
            return (int)strtoll(str.getBuffer(), NULL, radix);
    }
    unsigned int stringToUInt(const String& str, int radix)
    {
        if (str.startsWith("0x"))
            return (unsigned int)strtoull(str.getBuffer(), NULL, 16);
        else
            return (unsigned int)strtoull(str.getBuffer(), NULL, radix);
    }
    double stringToDouble(const String& str)
    {
        return (double)strtod(str.getBuffer(), NULL);
    }
    float stringToFloat(const String& str)
    {
        return strtof(str.getBuffer(), NULL);
    }

#if 0
    String String::ReplaceAll(String src, String dst) const
    {
        String rs = *this;
        int index = 0;
        int srcLen = src.length;
        int len = rs.length;
        while ((index = rs.IndexOf(src, index)) != -1)
        {
            rs = rs.SubString(0, index) + dst + rs.SubString(index + srcLen, len - index - srcLen);
            len = rs.length;
        }
        return rs;
    }
#endif

    String String::fromWString(const wchar_t* wstr)
    {
        List<char> buf;
#ifdef _WIN32
        Slang::CharEncoding::UTF16->decode((const Byte*)wstr, (int)(wcslen(wstr) * sizeof(wchar_t)), buf);
#else
        Slang::CharEncoding::UTF32->decode((const Byte*)wstr, (int)(wcslen(wstr) * sizeof(wchar_t)), buf);
#endif
        return String(buf.begin(), buf.end());
    }

    String String::fromWString(const wchar_t* wstr, const wchar_t* wend)
    {
        List<char> buf;
#ifdef _WIN32
        Slang::CharEncoding::UTF16->decode((const Byte*)wstr, (int)((wend - wstr) * sizeof(wchar_t)), buf);
#else
        Slang::CharEncoding::UTF32->decode((const Byte*)wstr, (int)((wend - wstr) * sizeof(wchar_t)), buf);
#endif
        return String(buf.begin(), buf.end());
    }

    String String::fromWChar(const wchar_t ch)
    {
        List<char> buf;
#ifdef _WIN32
        Slang::CharEncoding::UTF16->decode((const Byte*)&ch, (int)(sizeof(wchar_t)), buf);
#else
        Slang::CharEncoding::UTF32->decode((const Byte*)&ch, (int)(sizeof(wchar_t)), buf);
#endif
        return String(buf.begin(), buf.end());
    }

    /* static */String String::fromUnicodePoint(Char32 codePoint)
    {
        char buf[6];
        int len = Slang::encodeUnicodePointToUTF8(codePoint, buf);
        return String(buf, buf + len);
    }

    OSString String::toWString(Index* outLength) const
    {
        if (!m_buffer)
        {
            return OSString();
        }
        else
        {
            List<Byte> buf;
            switch(sizeof(wchar_t))
            {
            case 2:
                Slang::CharEncoding::UTF16->encode(getUnownedSlice(), buf); 
                break;

            case 4:
                Slang::CharEncoding::UTF32->encode(getUnownedSlice(), buf);
                break;

            default:
                break;
            }

            auto length = Index(buf.getCount() / sizeof(wchar_t));
            if (outLength)
                *outLength = length;

            for(size_t ii = 0; ii < sizeof(wchar_t); ++ii)
                buf.add(0);

            wchar_t* beginData = (wchar_t*)buf.getBuffer();
            wchar_t* endData = beginData + length;

            OSString ret;
            ret.set(beginData, endData);
            return ret;
        }
    }

    //

    void String::ensureUniqueStorageWithCapacity(Index requiredCapacity)
    {
        if (m_buffer && m_buffer->isUniquelyReferenced() && m_buffer->capacity >= requiredCapacity)
            return;

        Index newCapacity = m_buffer ? 2 * m_buffer->capacity : 16;
        if (newCapacity < requiredCapacity)
        {
            newCapacity = requiredCapacity;
        }

        Index length = getLength();
        StringRepresentation* newRepresentation = StringRepresentation::createWithCapacityAndLength(newCapacity, length);

        if (m_buffer)
        {
            memcpy(newRepresentation->getData(), m_buffer->getData(), length + 1);
        }

        m_buffer = newRepresentation;
    }

    char* String::prepareForAppend(Index count)
    {
        auto oldLength = getLength();
        auto newLength = oldLength + count;
        ensureUniqueStorageWithCapacity(newLength);
        return getData() + oldLength;
    }
    void String::appendInPlace(const char* chars, Index count)
    {
        SLANG_UNUSED(chars);

        if (count > 0)
        {
            SLANG_ASSERT(m_buffer && m_buffer->isUniquelyReferenced());

            auto oldLength = getLength();
            auto newLength = oldLength + count;

            char* dst = m_buffer->getData();

            // Make sure the input buffer is the same one returned from prepareForAppend
            SLANG_ASSERT(chars == dst + oldLength);
            // It has to fit within the capacity
            SLANG_ASSERT(newLength <= m_buffer->capacity);

            // We just need to modify the length
            m_buffer->length = newLength;

            // And mark with a terminating 0
            dst[newLength] = 0;
        }
    }

    void String::reduceLength(Index newLength)
    {
        Index oldLength = getLength();
        SLANG_ASSERT(newLength <= oldLength);
        if (oldLength == newLength)
        {
            return;
        }

        // It must have a buffer, because only 0 length allows for nullptr
        // and being 0 sized is already covered
        SLANG_ASSERT(m_buffer);

        if (m_buffer->isUniquelyReferenced())
        {
            m_buffer->length = newLength;
            m_buffer->getData()[newLength] = 0;
        }
        else
        {
            // If 0 length is wanted we can just free
            if (newLength == 0)
            {
                m_buffer.setNull();
            }
            else
            {
                // We need to make a new copy, that we will shrink

                // We'll just go with capacity enough for the new length
                const Index newCapacity = newLength;
                StringRepresentation* newRepresentation = StringRepresentation::createWithCapacityAndLength(newCapacity, newLength);

                // Copy 
                char* dst = newRepresentation->getData();
                memcpy(dst, m_buffer->getData(), sizeof(char) * newLength);
                // Zero terminate
                dst[newLength] = 0;

                // Set the new rep
                m_buffer = newRepresentation;
            }
        }
    }

    void String::append(char const* str, size_t len)
    {
        append(str, str + len);
    }

    void String::append(const char* textBegin, char const* textEnd)
    {
        auto oldLength = getLength();
        auto textLength = textEnd - textBegin;
        if (textLength <= 0)
            return;

        auto newLength = oldLength + textLength;

        ensureUniqueStorageWithCapacity(newLength);

        memcpy(getData() + oldLength, textBegin, textLength);
        getData()[newLength] = 0;
        m_buffer->length = newLength;
    }

    void String::append(char const* str)
    {
        if (str)
        {
            append(str, str + strlen(str));
        }
    }

    void String::appendRepeatedChar(char chr, Index count)
    {
        SLANG_ASSERT(count >= 0);
        if (count > 0)
        { 
            char* chars = prepareForAppend(count);
            // Set all space to repeated chr. 
            ::memset(chars, chr, sizeof(char) * count);
            appendInPlace(chars, count);
        }
    }

    void String::appendChar(char c)
    {
        const auto oldLength = getLength();
        const auto newLength = oldLength + 1;

        ensureUniqueStorageWithCapacity(newLength);

        // Since there must be space for at least one character, m_buffer cannot be nullptr
        SLANG_ASSERT(m_buffer);
        char* data = m_buffer->getData();
        data[oldLength] = c;
        data[newLength] = 0;

        m_buffer->length = newLength;
    }

    void String::append(char chr)
    {
        appendChar(chr);
    }

    void String::append(String const& str)
    {
        if (!m_buffer)
        {
            m_buffer = str.m_buffer;
            return;
        }

        append(str.begin(), str.end());
    }

    void String::append(StringSlice const& slice)
    {
        append(slice.begin(), slice.end());
    }

    void String::append(UnownedStringSlice const& slice)
    {
        append(slice.begin(), slice.end());
    }

    void String::append(int32_t value, int radix)
    {
        enum { kCount = 33 };
        char* data = prepareForAppend(kCount);
        const auto count = intToAscii(data, value, radix);
        m_buffer->length += count;
    }

    void String::append(uint32_t value, int radix)
    {
        enum { kCount = 33 };
        char* data = prepareForAppend(kCount);
        const auto count = intToAscii(data, value, radix);
        m_buffer->length += count;
    }

    void String::append(int64_t value, int radix)
    {
        enum { kCount = 65 };
        char* data = prepareForAppend(kCount);
        auto count = intToAscii(data, value, radix);
        m_buffer->length += count;
    }

    void String::append(uint64_t value, int radix)
    {
        enum { kCount = 65 };
        char* data = prepareForAppend(kCount);
        auto count = intToAscii(data, value, radix);
        m_buffer->length += count;
    }

    void String::append(float val, const char* format)
    {
        enum { kCount = 128 };
        char* data = prepareForAppend(kCount);
        sprintf_s(data, kCount, format, val);
        m_buffer->length += strnlen_s(data, kCount);
    }

    void String::append(double val, const char* format)
    {
        enum { kCount = 128 };
        char* data = prepareForAppend(kCount);
        sprintf_s(data, kCount, format, val);
        m_buffer->length += strnlen_s(data, kCount);
    }

    void String::append(StableHashCode32 value)
    {
        const Index digits = 8;
        // + null terminator
        char* data = prepareForAppend(digits + 1);
        auto count = intToAscii(data, value.hash, 16, digits);
        m_buffer->length += count;
    }

    void String::append(StableHashCode64 value)
    {
        const Index digits = 16;
        // + null terminator
        char* data = prepareForAppend(digits + 1);
        auto count = intToAscii(data, value.hash, 16, digits);
        m_buffer->length += count;
    }

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! UnownedStringSlice !!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    Index UnownedStringSlice::indexOf(char c) const
    {
        const Index size = Index(m_end - m_begin);
        for (Index i = 0; i < size; ++i)
        {
            if (m_begin[i] == c)
            {
                return i;
            }
        }
        return -1;
    }

    Index UnownedStringSlice::indexOf(const UnownedStringSlice& in) const
    {
        const Index len = getLength();
        const Index inLen = in.getLength();
        if (inLen > len)
        {
            return -1;
        }

        const char* inChars = in.m_begin;
        switch (inLen)
        {
            case 0:         return 0;
            case 1:         return indexOf(inChars[0]);
            default: break;
        }

        const char* chars = m_begin;
        const char firstChar = inChars[0];

        for (Int i = 0; i <= len - inLen; ++i)
        {
            if (chars[i] == firstChar && in == UnownedStringSlice(chars + i, inLen))
            {
                return i;
            }
        }

        return -1;
    }

    UnownedStringSlice UnownedStringSlice::subString(Index idx, Index len) const
    {
        const Index totalLen = getLength();
        SLANG_ASSERT(idx >= 0 && len >= 0 && idx <= totalLen);

        // If too large, we truncate
        len = (idx + len > totalLen) ? (totalLen - idx) : len;

        // Return the substring
        return UnownedStringSlice(m_begin + idx, m_begin + idx + len);
    }

    bool UnownedStringSlice::operator==(ThisType const& other) const
    {
        // Note that memcmp is undefined when passed in null ptrs, so if we want to handle
        // we need to cover that case.
        // Can only be nullptr if size is 0.
        auto thisSize = getLength();
        auto otherSize = other.getLength();

        if (thisSize != otherSize)
        {
            return false;
        }

        const char*const thisChars = begin();
        const char*const otherChars = other.begin();
        if (thisChars == otherChars || thisSize == 0)
        {
            return true;
        }
        SLANG_ASSERT(thisChars && otherChars);
        return memcmp(thisChars, otherChars, thisSize) == 0;
    }

    bool UnownedStringSlice::caseInsensitiveEquals(const ThisType& rhs) const
    {
        const auto length = getLength();
        if (length != rhs.getLength())
        {
            return false;
        }

        const char* a = m_begin;
        const char* b = rhs.m_begin;

        // Assuming this is a faster test
        if (memcmp(a, b, length) != 0)
        {
            // They aren't identical so compare character by character
            for (Index i = 0; i < length; ++i)
            {
                if (CharUtil::toLower(a[i]) != CharUtil::toLower(b[i]))
                {
                    return false;
                }
            }
        }

        return true;
    }
}

std::ostream& operator<< (std::ostream& stream, const Slang::String& s)
{
    stream << s.getBuffer();
    return stream;
}
back to top