https://github.com/shader-slang/slang
Raw File
Tip revision: f44da6cc5c0f211c13bd1eb0743d79c7861ea64e authored by Yong He on 09 February 2024, 02:29:32 UTC
Support pointers in SPIRV. (#3561)
Tip revision: f44da6c
slang-string-util.cpp
#include "slang-string-util.h"

#include "slang-blob.h"

#include "slang-char-util.h"
#include "slang-text-io.h"

namespace Slang {

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! StringUtil !!!!!!!!!!!!!!!!!!!!!!!!!!!

/* static */bool StringUtil::areAllEqual(const List<UnownedStringSlice>& a, const List<UnownedStringSlice>& b, EqualFn equalFn)
{
    if (a.getCount() != b.getCount())
    {
        return false;
    }

    const Index count = a.getCount();
    for (Index i = 0; i < count; ++i)
    {
        if (!equalFn(a[i], b[i]))
        {
            return false;
        }
    }
    return true;
}

/* static */bool StringUtil::areAllEqualWithSplit(const UnownedStringSlice& a, const UnownedStringSlice& b, char splitChar, EqualFn equalFn)
{
    List<UnownedStringSlice> slicesA, slicesB;
    StringUtil::split(a, splitChar, slicesA);
    StringUtil::split(b, splitChar, slicesB);
    return areAllEqual(slicesA, slicesB, equalFn);
}

/* static */void StringUtil::appendSplitOnWhitespace(const UnownedStringSlice& in, List<UnownedStringSlice>& outSlices)
{
    const char* start = in.begin();
    const char* end = in.end();

    // Skip any at the start
    while (start < end && CharUtil::isWhitespace(*start)) start++;
    
    while (start < end)
    {
        // Find all the non white space in a run
        const char* cur = start;
        while (cur < end && !CharUtil::isWhitespace(*cur))
        {
            cur++;
        }

        // Add to output
        outSlices.add(UnownedStringSlice(start, cur));

        // Find the next start
        start = cur + 1;

        // Skip the split
        while (start < end && CharUtil::isWhitespace(*start)) start++;
    }
}

/* static */void StringUtil::appendSplit(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& outSlices)
{
    const char* start = in.begin();
    const char* end = in.end();

    while (start < end)
    {
        // Move cur so it's either at the end or at next split character
        const char* cur = start;
        while (cur < end && *cur != splitChar)
        {
            cur++;
        }

        // Add to output
        outSlices.add(UnownedStringSlice(start, cur));

        // Skip the split character, if at end we are okay anyway
        start = cur + 1;
    }
}

/* static */void StringUtil::appendSplit(const UnownedStringSlice& in, const UnownedStringSlice& splitSlice, List<UnownedStringSlice>& outSlices)
{
    const Index splitLen = splitSlice.getLength();

    if (splitLen == 1)
    {
        return appendSplit(in, splitSlice[0], outSlices);
    }

    SLANG_ASSERT(splitLen > 0);
    if (splitLen <= 0)
    {
        return;
    }

    const char* start = in.begin();
    const char* end = in.end();

    const char splitChar = splitSlice[0];

    while (start < end)
    {
        // Move cur so it's either at the end or at next splitSlice
        const char* cur = start;
        while (cur < end)
        {
            if (*cur == splitChar &&
                (cur + splitLen <= end && UnownedStringSlice(cur, splitLen) == splitSlice))
            {
                // We hit a split
                break;
            }

            cur++;
        }

        // Add to output
        outSlices.add(UnownedStringSlice(start, cur));

        // Skip the split, if at end we are okay anyway
        start = cur + splitLen;
    }
}

/* static */void StringUtil::split(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& outSlices)
{
    outSlices.clear();
    appendSplit(in, splitChar, outSlices);
}

/* static */void StringUtil::split(const UnownedStringSlice& in, const UnownedStringSlice& splitSlice, List<UnownedStringSlice>& outSlices)
{
    outSlices.clear();
    appendSplit(in, splitSlice, outSlices);
}

/* static */void StringUtil::splitOnWhitespace(const UnownedStringSlice& in, List<UnownedStringSlice>& outSlices)
{
    outSlices.clear();
    appendSplitOnWhitespace(in, outSlices);
}

/* static */Index StringUtil::split(const UnownedStringSlice& in, char splitChar, Index maxSlices, UnownedStringSlice* outSlices)
{
    Index index = 0;

    const char* start = in.begin();
    const char* end = in.end();

    while (start < end && index < maxSlices)
    {
        // Move cur so it's either at the end or at next split character
        const char* cur = start;
        while (cur < end && *cur != splitChar)
        {
            cur++;
        }

        // Add to output
        outSlices[index++] = UnownedStringSlice(start, cur);

        // Skip the split character, if at end we are okay anyway
        start = cur + 1;
    }

    return index;
}

/* static */SlangResult StringUtil::split(const UnownedStringSlice& in, char splitChar, Index maxSlices, UnownedStringSlice* outSlices, Index& outSlicesCount)
{
    const Index sliceCount = split(in, splitChar, maxSlices, outSlices);
    if (sliceCount == maxSlices && sliceCount > 0)
    {
        // To succeed must have parsed all of the input
        if (in.end() != outSlices[sliceCount - 1].end())
        {
            return SLANG_FAIL;
        }
    }
    outSlicesCount = sliceCount;
    return SLANG_OK;
}

/* static */void StringUtil::join(const List<String>& values, char separator, StringBuilder& out)
{
    join(values, UnownedStringSlice(&separator, 1), out);
}

/* static */void StringUtil::join(const List<String>& values, const UnownedStringSlice& separator, StringBuilder& out)
{
    const Index count = values.getCount();
    if (count <= 0)
    {
        return;
    }
    out.append(values[0]);
    for (Index i = 1; i < count; i++)
    {
        out.append(separator);
        out.append(values[i]);
    }
}

/* static */void StringUtil::join(const UnownedStringSlice* values, Index valueCount, char separator, StringBuilder& out)
{
    join(values, valueCount, UnownedStringSlice(&separator, 1), out);
}

/* static */void StringUtil::join(const UnownedStringSlice* values, Index valueCount, const UnownedStringSlice& separator, StringBuilder& out)
{
    if (valueCount <= 0)
    {
        return;
    }
    out.append(values[0]);
    for (Index i = 1; i < valueCount; i++)
    {
        out.append(separator);
        out.append(values[i]);
    }
}

/* static */Index StringUtil::indexOfInSplit(const UnownedStringSlice& in, char splitChar, const UnownedStringSlice& find)
{
    const char* start = in.begin();
    const char* end = in.end();

    for (Index i = 0; start < end; ++i)
    {
        // Move cur so it's either at the end or at next split character
        const char* cur = start;
        while (cur < end && *cur != splitChar)
        {
            cur++;
        }

        // See if we have a match
        if (UnownedStringSlice(start, cur) == find)
        {
            return i;
        }

        // Skip the split character, if at end we are okay anyway
        start = cur + 1;
    }
    return -1;
}

UnownedStringSlice StringUtil::getAtInSplit(const UnownedStringSlice& in, char splitChar, Index index)
{
    const char* start = in.begin();
    const char* end = in.end();

    for (Index i = 0; start < end; ++i)
    {
        // Move cur so it's either at the end or at next split character
        const char* cur = start;
        while (cur < end && *cur != splitChar)
        {
            cur++;
        }

        if (i == index)
        {
            return UnownedStringSlice(start, cur);
        }

        // Skip the split character, if at end we are okay anyway
        start = cur + 1;
    }

    return UnownedStringSlice();
}

/* static */size_t StringUtil::calcFormattedSize(const char* format, va_list args)
{
#if SLANG_WINDOWS_FAMILY
    return _vscprintf(format, args);
#else
     return vsnprintf(nullptr, 0, format, args);
#endif
}

/* static */void StringUtil::calcFormatted(const char* format, va_list args, size_t numChars, char* dst)
{
#if SLANG_WINDOWS_FAMILY
    vsnprintf_s(dst, numChars + 1, _TRUNCATE, format, args);
#else
    vsnprintf(dst, numChars + 1, format, args);
#endif
}

/* static */void StringUtil::append(const char* format, va_list args, StringBuilder& buf)
{
    // Calculate the size required (not including terminating 0)
    size_t numChars;
    {
        // Create a copy of args, as will be consumed by calcFormattedSize
        va_list argsCopy;
        va_copy(argsCopy, args);
        numChars = calcFormattedSize(format, argsCopy);
        va_end(argsCopy);
    }

    // Requires + 1 , because calcFormatted appends a terminating 0
    char* dst = buf.prepareForAppend(numChars + 1);
    calcFormatted(format, args, numChars, dst);
    buf.appendInPlace(dst, numChars);
}

/* static */void StringUtil::appendFormat(StringBuilder& buf, const char* format, ...)
{
    va_list args;
    va_start(args, format);
    append(format, args, buf);
    va_end(args);
}

/* static */String StringUtil::makeStringWithFormat(const char* format, ...)
{
    StringBuilder builder;

    va_list args;
    va_start(args, format);
    append(format, args, builder);
    va_end(args);

    return std::move(builder);
}

/* static */UnownedStringSlice StringUtil::getSlice(ISlangBlob* blob)
{
    if (blob)
    {
        size_t size = blob->getBufferSize();
        if (size > 0)
        {
            const char* contents = (const char*)blob->getBufferPointer();
            // Check it has terminating 0, if it has we skip it, because slices do not need zero termination 
            if (contents[size - 1] == 0)
            {
                size--;
            }
            return UnownedStringSlice(contents, contents + size);
        }
    }
    return UnownedStringSlice();
}

/* static */String StringUtil::getString(ISlangBlob* blob)
{
    return getSlice(blob);
}

ComPtr<ISlangBlob> StringUtil::createStringBlob(const String& string)
{
    return StringBlob::create(string);
}

/* static */String StringUtil::calcCharReplaced(const UnownedStringSlice& slice, char fromChar, char toChar)
{
    if (fromChar == toChar)
    {
        return slice;
    }

    const Index numChars = slice.getLength();
    const char* srcChars = slice.begin();

    StringBuilder builder;
    char* dstChars = builder.prepareForAppend(numChars);

    for (Index i = 0; i < numChars; ++i)
    {
        char c = srcChars[i];
        dstChars[i] = (c == fromChar) ? toChar : c;
    }

    builder.appendInPlace(dstChars, numChars);
    return std::move(builder);
}

/* static */String StringUtil::calcCharReplaced(const String& string, char fromChar, char toChar)
{
    return (fromChar == toChar || string.indexOf(fromChar) == Index(-1)) ? string : calcCharReplaced(string.getUnownedSlice(), fromChar, toChar);
}

String StringUtil::replaceAll(UnownedStringSlice text, UnownedStringSlice subStr, UnownedStringSlice replacement)
{
    StringBuilder builder;
    for (Index i = 0; i < text.getLength();)
    {
        if (i + subStr.getLength() >= text.getLength())
        {
            builder.append(text.subString(i, text.getLength() - i));
            break;
        }
        if (text.subString(i, subStr.getLength()) == subStr)
        {
            builder.append(replacement);
            i += subStr.getLength();
        }
        else
        {
            builder.append(text[i]);
            i++;
        }
    }
    return builder.produceString();
}


/* static */void StringUtil::appendStandardLines(const UnownedStringSlice& text, StringBuilder& out)
{
    const char* cur = text.begin();
    const char* start = cur;
    const char* const end = text.end();

    while (cur < end)
    {
        const char c = *cur;
        switch (c)
        {
            case '\n':
            {
                ++cur;
                if (cur < end && *cur == '\r')
                {
                    // If we have following \r, we should append with \n
                    // Append (including \n) 
                    out.append(start, cur);
                    // Skip the \r
                    start = ++cur;
                }
                else
                {
                    // If not, we don't need to append because just \n is 'standard', and everything remaining
                    // is appended at the end
                }
                break;
            }
            case '\r':
            {
                out.append(start, cur);
                out.appendChar('\n');

                ++cur;
                // If next is \n, we want to skip that
                cur += Index(cur < end && *cur == '\n');
                start = cur;
                break;
            }
            default:
            {
                cur++;
                break;
            }
        }
    }

    if (start < end)
    {
        out.append(start, end);
    }
}

/* static */bool StringUtil::extractLine(UnownedStringSlice& ioText, UnownedStringSlice& outLine)
{
    char const*const begin = ioText.begin();
    char const*const end = ioText.end();

    // If we have hit the end then return the 'special' terminator
    if (begin == nullptr)
    {
        outLine = UnownedStringSlice(nullptr, nullptr);
        return false;
    }

    char const* cursor = begin;
    while (cursor < end)
    {
        int c = *cursor++;
        switch (c)
        {
            case '\r': case '\n':
            {
                // Remember the end of the line
                const char*const lineEnd = cursor - 1;

                // When we see a line-break character we need
                // to record the line break, but we also need
                // to deal with the annoying issue of encodings,
                // where a multi-byte sequence might encode
                // the line break.
                if (cursor < end)
                {
                    int d = *cursor;
                    if ((c ^ d) == ('\r' ^ '\n'))
                        cursor++;
                }

                ioText = UnownedStringSlice(cursor, end);
                outLine = UnownedStringSlice(begin, lineEnd);
                return true;
            }
            default:
                break;
        }
    }

    // There is nothing remaining
    ioText = UnownedStringSlice(nullptr, nullptr);

    // Could be empty, or the remaining line (without line end terminators of)
    SLANG_ASSERT(begin <= cursor);

    outLine = UnownedStringSlice(begin, cursor);
    return true;
}

/* static */void StringUtil::calcLines(const UnownedStringSlice& textIn, List<UnownedStringSlice>& outLines)
{
    outLines.clear();
    UnownedStringSlice text(textIn), line;
    while (extractLine(text, line))
    {
        outLines.add(line);
    }
}

/* static */UnownedStringSlice StringUtil::trimEndOfLine(const UnownedStringSlice& line)
{
    // Strip CR/LF from end of line if present

    const char* begin = line.begin();
    const char* end = line.end();

    if (end > begin)
    {
        const char c = end[-1];
        // If last char is CR/LF move back a char
        if (c == '\n' || c == '\r')
        {
            --end;
            // If next char is a match for the CR/LF pair move back an extra char.
            end -= Index((end > begin) && (c ^ end[-1]) == ('\r' ^ '\n'));
        }
    }

    return line.head(Index(end - begin));
}

/* static */bool StringUtil::areLinesEqual(const UnownedStringSlice& inA, const UnownedStringSlice& inB)
{
    UnownedStringSlice a(inA), b(inB), lineA, lineB;
    
    while (true)
    {
        const auto hasLineA = extractLine(a, lineA);
        const auto hasLineB = extractLine(b, lineB);

        if (!(hasLineA && hasLineB))
        {
            return hasLineA == hasLineB;
        }

        // The lines must be equal
        if (lineA != lineB)
        {
            return false;
        }
    }
}

/* static */SlangResult StringUtil::parseDouble(const UnownedStringSlice& text, double& out)
{
    const Index bufSize = 32;

    const auto len = text.getLength();

    if (len > bufSize - 1)
    {
        List<char> work;
        work.setCount(len + 1);
        char* dst = work.getBuffer();

        ::memcpy(dst, text.begin(), len * sizeof(char));
        dst[len] = 0;

        out = atof(dst);
    }
    else
    {
        char buf[bufSize];
        ::memcpy(buf, text.begin(), len * sizeof(char));
        buf[len] = 0;
        out = atof(buf);
    }
    return SLANG_OK;
}

/* static */SlangResult StringUtil::parseInt(const UnownedStringSlice& in, Int& outValue)
{
    const char* cur = in.begin();
    const char* end = in.end();

    bool negate = false;
    if (cur < end && *cur == '-')
    {
        negate = true;
        cur++;
    }

    int radix = 10;
    auto getDigit = CharUtil::getDecimalDigitValue;
    if (cur+1 < end && *cur == '0' && (*(cur+1) == 'x' || *(cur+1) == 'X'))
    {
        radix = 16;
        getDigit = CharUtil::getHexDigitValue;
        cur += 2;
    }

    // We need at least one digit
    if (cur >= end || !CharUtil::isDigit(*cur))
    {
        return SLANG_FAIL;
    }
    
    Int value = 0;
    // Do the digits
    for (; cur < end; ++cur)
    {
        const auto d = getDigit(*cur);
        if (d == -1)
            return SLANG_FAIL;
        value = value * radix + d;
    }

    value = negate ? -value : value;

    outValue = value;
    return SLANG_OK;
}

/* static */SlangResult StringUtil::parseInt64(const UnownedStringSlice& text, int64_t& out)
{
    bool negate = false;

    const char* cur = text.begin();
    const char* end = text.end();

    if (cur < end)
    {
        if (*cur == '-')
        {
            negate = true;
            cur++;
        }
        else if (*cur == '+')
        {
            cur++;
        }
    }

    // Must have at least one digit
    if (cur >= end || !CharUtil::isDigit(*cur))
    {
        return SLANG_FAIL;
    }

    uint64_t value = 0;
    // We can have 20 digits, but the last digit can cause overflow.
    // Lets do the easy first digits first
    Index numSimple = 19;
    for (; cur < end && CharUtil::isDigit(*cur) && numSimple > 0; ++cur, --numSimple)
    {
        value = value * 10 + (*cur - '0');
    }

    if (cur < end && CharUtil::isDigit(*cur))
    {
        const auto prevValue = value;
        value = value * 10 + (*cur - '0');
        cur++;

        if (value < prevValue)
        {
            // We have overflow
            return SLANG_FAIL;
        }
    }

    if (negate)
    {
        if (value > ~((~uint64_t(0)) >> 1))
        {
            // Overflow
            return SLANG_FAIL;
        }
        out = -int64_t(value);
    }
    else
    {
        if (value > ((~uint64_t(0)) >> 1))
        {
            // Overflow
            return SLANG_FAIL;
        }
        out = value;
    }

    return (cur == end) ? SLANG_OK : SLANG_FAIL;
}

int StringUtil::parseIntAndAdvancePos(UnownedStringSlice text, Index& pos)
{
    int result = 0;
    while (text[pos] == ' ' && pos < text.getLength())
    {
        pos++;
        continue;
    }
    bool isNeg = false;
    if (pos < text.getLength() && text[pos] == '-')
    {
        pos++;
        isNeg = true;
    }
    while (pos < text.getLength())
    {
        if (text[pos] >= '0' && text[pos] <= '9')
        {
            result *= 10;
            result += text[pos] - '0';
            pos++;
        }
        else
        {
            break;
        }
    }
    if (isNeg)
        result = -result;
    return result;
}

} // namespace Slang
back to top