Skip to content

File FixedCapStr.h

File List > external-docs > libDaisy > src > util > FixedCapStr.h

Go to the documentation of this file

Source Code

#pragma once

#include <string_view>
#include <algorithm>

namespace daisy
{
template <class CharType = char>
class FixedCapStrBase
{
  public:
    constexpr FixedCapStrBase(CharType* buffer, size_t capacity)
    : capacity_(capacity), buffer_(buffer)
    {
    }

    constexpr FixedCapStrBase(const FixedCapStrBase& other) = delete;

    constexpr FixedCapStrBase& operator=(const FixedCapStrBase& str)
    {
        size_ = std::min(str.Size(), capacity_);
        Copy_(str.Data(), str.Data() + size_, buffer_);
        buffer_[size_] = 0;
        return *this;
    }

    constexpr FixedCapStrBase& operator=(const CharType* str)
    {
        size_ = std::min(strlen(str), capacity_);
        Copy_(str, str + size_, buffer_);
        buffer_[size_] = 0;
        return *this;
    }

    constexpr operator const CharType*() const noexcept { return buffer_; }
    constexpr const CharType* Cstr() const noexcept { return buffer_; }
    constexpr const CharType* Data() const noexcept { return buffer_; }
    constexpr CharType*       Data() noexcept { return buffer_; }

    constexpr auto Size() const noexcept { return size_; }
    constexpr auto UpdateSize() noexcept
    {
        size_          = std::min(strlen(buffer_), capacity_);
        buffer_[size_] = 0;
    }

    constexpr auto Capacity() const noexcept { return capacity_; }

    constexpr auto Empty() const noexcept { return size_ == 0; }

    constexpr void Clear() noexcept
    {
        size_      = 0;
        buffer_[0] = '\0';
    }

    constexpr void Reset(const CharType* str)
    {
        size_ = std::min(strlen(str), capacity_);
        Reset_(str, size_);
    }

    constexpr void Reset(const CharType* str, std::size_t length)
    {
        size_ = std::min(length, capacity_);
        Reset_(str, size_);
    }

    constexpr void ResetAt(const CharType* str, std::size_t writePosition)
    {
        if(writePosition > size_)
            return;
        const auto strLen = strlen(str);
        const auto newSize
            = std::max(std::min(strLen + writePosition, capacity_), size_);
        const auto numCharsToWrite = std::min(newSize - writePosition, strLen);
        size_                      = newSize;
        ResetAt_(str, numCharsToWrite, writePosition);
    }

    constexpr void Append(const CharType singleChar)
    {
        if(size_ < capacity_)
        {
            buffer_[size_]     = singleChar;
            buffer_[size_ + 1] = '\0';
            size_++;
        }
    }

    constexpr void Append(const CharType* str)
    {
        auto to_copy = std::min(strlen(str), (capacity_ - size_));
        Append_(str, to_copy);
    }

    constexpr void Append(const CharType* str, std::size_t length)
    {
        auto to_copy = std::min(length, (capacity_ - size_));
        Append_(str, to_copy);
    }

    template <typename IntType>
    constexpr void AppendInt(IntType value, bool alwaysIncludeSign = false)
    {
        if(value == 0)
        {
            if(alwaysIncludeSign)
                Append('+');
            Append("0");
            return;
        }

        if(value < 0)
        {
            value = -value;
            Append('-');
        }
        else if(alwaysIncludeSign)
            Append('+');

        const auto firstDigit = Size();
        while(value != 0)
        {
            const auto rem = value % 10;
            value          = value / 10;
            Append(rem + '0');
        }
        const auto lastDigit = Size() - 1;
        ReverseSection(firstDigit, lastDigit);
    }

    constexpr void AppendFloat(float value,
                               int   maxNumDigits      = 2,
                               bool  omitTrailingZeros = false,
                               bool  alwaysIncludeSign = false)
    {
        if(value == 0.0f)
        {
            if(alwaysIncludeSign)
                Append('+');
            Append("0");
            if((!omitTrailingZeros) && (maxNumDigits > 0))
            {
                Append(".");
                for(int i = 0; i < maxNumDigits; i++)
                    Append("0");
            }
            return;
        }

        if(value < 0)
        {
            value = -value;
            Append('-');
        }
        else if(alwaysIncludeSign)
            Append('+');

        float         factor    = 1;
        constexpr int tableSize = 10;
        // clang-format off
        constexpr float powTable[tableSize] = {
            1.0f,
            10.0f,
            100.0f,
            1000.0f,
            10000.0f,
            100000.0f,
            1000000.0f,
            10000000.0f,
            100000000.0f,
            1000000000.0f
        };
        constexpr float roundOffsTable[tableSize] = {
            0.5f,
            0.05f,
            0.005f,
            0.0005f,
            0.00005f,
            0.000005f,
            0.0000005f,
            0.00000005f,
            0.000000005f,
        };
        // clang-format on
        if(maxNumDigits <= tableSize)
        {
            factor = powTable[maxNumDigits];
            value += roundOffsTable[maxNumDigits];
        }
        else
        {
            float roundOffs = 0.5f;
            for(int i = 0; i < maxNumDigits; i++)
            {
                factor *= 10.0f;
                roundOffs /= 10.0f;
            }
            value += roundOffs;
        }

        int beforeDecPt = int(value);
        int afterDecPt  = int(value * factor);

        const auto firstDigit = Size();
        // print digits after the decimal point
        for(int i = 0; i < maxNumDigits; i++)
        {
            const auto rem = afterDecPt % 10;
            afterDecPt     = afterDecPt / 10;
            if((rem == 0) && (omitTrailingZeros))
                continue;
            Append(rem + '0');
        }
        // print decimal point
        if(Size() != firstDigit)
            Append('.');
        // print digits before the decimal point
        if(beforeDecPt == 0)
            Append('0');
        else
        {
            while(beforeDecPt != 0)
            {
                const auto rem = beforeDecPt % 10;
                beforeDecPt    = beforeDecPt / 10;
                Append(rem + '0');
            }
        }
        const auto lastDigit = Size() - 1;
        ReverseSection(firstDigit, lastDigit);
    }

    constexpr bool StartsWith(const CharType* pattern) const noexcept
    {
        const CharType* ptr = buffer_;
        while(*pattern)
        {
            if(*ptr != *pattern)
                return false;
            pattern++;
            ptr++;
        }
        return true;
    }

    constexpr bool
    StartsWithIgnoringCase(const CharType* pattern) const noexcept
    {
        const CharType* ptr = buffer_;
        while(*pattern)
        {
            if(ToUpper_(*ptr) != ToUpper_(*pattern))
                return false;
            pattern++;
            ptr++;
        }
        return true;
    }

    constexpr bool EndsWith(const CharType* pattern) const noexcept
    {
        const CharType* ptr           = &buffer_[size_ - 1];
        const auto      patternLength = strlen(pattern);
        const CharType* patternPtr    = pattern + patternLength - 1;
        while(patternPtr > pattern)
        {
            if(*ptr != *patternPtr)
                return false;
            patternPtr--;
            ptr--;
        }
        return *ptr == *patternPtr;
    }

    constexpr bool EndsWithIgnoringCase(const CharType* pattern) const noexcept
    {
        const CharType* ptr           = &buffer_[size_ - 1];
        const auto      patternLength = strlen(pattern);
        const CharType* patternPtr    = pattern + patternLength - 1;
        while(patternPtr > pattern)
        {
            if(ToUpper_(*ptr) != ToUpper_(*patternPtr))
                return false;
            patternPtr--;
            ptr--;
        }
        return ToUpper_(*ptr) == ToUpper_(*patternPtr);
    }

    constexpr void RemovePrefix(std::size_t length)
    {
        Copy_(buffer_ + length, buffer_ + size_, buffer_);
        size_ -= length;
        buffer_[size_] = '\0';
    }

    constexpr void RemoveSuffix(std::size_t length) noexcept
    {
        size_          = size_ - length;
        buffer_[size_] = '\0';
    }

    constexpr void ReverseSection(std::size_t firstIdx, std::size_t lastIdx)
    {
        firstIdx = clamp(firstIdx, 0, size_ - 1);
        lastIdx  = clamp(lastIdx, 0, size_ - 1);
        while(firstIdx < lastIdx)
        {
            CharType tmp      = buffer_[lastIdx];
            buffer_[lastIdx]  = buffer_[firstIdx];
            buffer_[firstIdx] = tmp;
            firstIdx++;
            lastIdx--;
        }
    }

    constexpr bool operator==(const CharType* rhs) const
    {
        const CharType* ptr = buffer_;
        while(*rhs && *ptr) // abort on first string end
        {
            if(*ptr != *rhs)
                return false;
            rhs++;
            ptr++;
        }

        return *rhs == *ptr; // both strings ended at the same '0'?
    }

    constexpr bool operator!=(const CharType* rhs) const
    {
        return !(*this == rhs);
    }

    constexpr bool operator<(const CharType* other) const
    {
        auto ptr = buffer_;
        while(*ptr && *other && (*ptr == *other))
        {
            ptr++;
            other++;
        }
        return *ptr < *other;
    }

    constexpr bool operator<=(const CharType* other) const
    {
        return (*this == other) || (*this < other);
    }

    constexpr bool operator>(const CharType* other) const
    {
        auto ptr = buffer_;
        while(*ptr && *other && (*ptr == *other))
        {
            ptr++;
            other++;
        }
        return *ptr > *other;
    }

    constexpr bool operator>=(const CharType* other) const
    {
        return (*this == other) || (*this > other);
    }

    constexpr void Swap(FixedCapStrBase& rhs) noexcept
    {
        auto tmp  = size_;
        size_     = rhs.size_;
        rhs.size_ = tmp;

        Swap_(buffer_, rhs.buffer_, std::max(size_, rhs.size_));
    }

  protected:
    static constexpr std::size_t strlen(const CharType* string)
    {
        std::size_t result = 0;
        while(*string++ != '\0')
            result++;
        return result;
    }

    constexpr void Reset_(const CharType* str, std::size_t length)
    {
        Copy_(str, str + length, buffer_);
        buffer_[length] = '\0';
    }

    constexpr void
    ResetAt_(const CharType* str, std::size_t strLen, std::size_t writePosition)
    {
        Copy_(str, str + strLen, buffer_ + writePosition);
        if(writePosition + strLen > size_)
            buffer_[writePosition + strLen] = '\0';
    }

    constexpr void Append_(const CharType* str, std::size_t to_copy)
    {
        Copy_(str, str + to_copy, buffer_ + size_);
        size_ += to_copy;
        buffer_[size_] = '\0';
    }

    static constexpr void
    Copy_(const CharType* src, const CharType* srcEnd, CharType* dest)
    {
        while(src != srcEnd)
        {
            *dest = *src;
            src++;
            dest++;
        }
    }

    static constexpr void Swap_(CharType* a, CharType* b, size_t length)
    {
        for(size_t i = 0; i < length; i++)
        {
            CharType tmp = *a;
            *a           = *b;
            *b           = tmp;
            a++;
            b++;
        }
    }

    // TODO: add wstring version
    static constexpr char ToUpper_(char c) noexcept
    {
        switch(c)
        {
            default: return c;
            case 'a': return 'A';
            case 'b': return 'B';
            case 'c': return 'C';
            case 'd': return 'D';
            case 'e': return 'E';
            case 'f': return 'F';
            case 'g': return 'G';
            case 'h': return 'H';
            case 'i': return 'I';
            case 'j': return 'J';
            case 'k': return 'K';
            case 'l': return 'L';
            case 'm': return 'M';
            case 'n': return 'N';
            case 'o': return 'O';
            case 'p': return 'P';
            case 'q': return 'Q';
            case 'r': return 'R';
            case 's': return 'S';
            case 't': return 'T';
            case 'u': return 'U';
            case 'v': return 'V';
            case 'w': return 'W';
            case 'x': return 'X';
            case 'y': return 'Y';
            case 'z': return 'Z';
        }
    }

    std::size_t clamp(std::size_t val, std::size_t min, std::size_t max)
    {
        return (val < min) ? min : ((val > max) ? max : val);
    }

    std::size_t  size_{0};
    const size_t capacity_;
    CharType*    buffer_;
};

template <std::size_t capacity, class CharType = char>
class FixedCapStr : public FixedCapStrBase<CharType>
{
  public:
    constexpr FixedCapStr() noexcept
    : FixedCapStrBase<CharType>(buffer_, capacity)
    {
    }

    constexpr FixedCapStr(const FixedCapStr& str) noexcept
    : FixedCapStrBase<CharType>(buffer_, capacity)
    {
        this->size_ = std::min(str.Size(), capacity);
        this->Copy_(str.Data(), str.Data() + this->size_, buffer_);
    }

    template <size_t otherSize>
    constexpr FixedCapStr(const FixedCapStr<otherSize>& str) noexcept
    : FixedCapStrBase<CharType>(buffer_, capacity)
    {
        this->size_ = std::min(str.Size(), capacity);
        this->Copy_(str.Data(), str.Data() + this->size_, buffer_);
    }

    constexpr FixedCapStr(const CharType* str) noexcept
    : FixedCapStrBase<CharType>(buffer_, capacity)
    {
        this->size_ = std::min(this->strlen(str), capacity);
        this->Copy_(str, str + this->size_, buffer_);
    }

    constexpr FixedCapStr(const CharType* str, std::size_t length) noexcept
    : FixedCapStrBase<CharType>(buffer_, capacity)
    {
        this->size_ = std::min(length, capacity);
        this->Copy_(str, str + this->size_, buffer_);
    }

    constexpr FixedCapStr& operator=(const FixedCapStr& str) noexcept
    {
        *static_cast<FixedCapStrBase<CharType>*>(this) = str;
        return *this;
    }

  private:
    CharType buffer_[capacity + 1]{};
};

template <class CharType, std::size_t capacity>
inline constexpr void Swap(const FixedCapStr<capacity, CharType>& lhs,
                           const FixedCapStr<capacity, CharType>& rhs) noexcept
{
    rhs.Swap(lhs);
}

} // namespace daisy