diff options
Diffstat (limited to 'include/fud_format.hpp')
-rw-r--r-- | include/fud_format.hpp | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/include/fud_format.hpp b/include/fud_format.hpp new file mode 100644 index 0000000..8985faf --- /dev/null +++ b/include/fud_format.hpp @@ -0,0 +1,293 @@ +/* + * libfud + * Copyright 2024 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FUD_FORMAT_HPP +#define FUD_FORMAT_HPP + +#include "fud_assert.hpp" +#include "fud_result.hpp" +#include "fud_span.hpp" +#include "fud_status.hpp" +#include "fud_string_view.hpp" + +#include <format> // for std::format_string +#include <cstdint> +#include <optional> + +namespace fud { + +template <size_t Size> +using CharSpan = Span<char, Size>; + +template <typename... Args> +using FormatLiteral = std::format_string<Args...>; + +template <typename... Args, size_t Size> +Result<size_t, FudStatus> format(CharSpan<Size> buffer, FormatLiteral<Args...> formatLiteral, Args&&... args); + +enum class FormatAlign : uint8_t { + Left, + Right, + Center +}; + +struct FormatFill { + FormatAlign align; + char fill; +}; + +enum class FormatSign : uint8_t { + Plus, + Minus, + Space +}; + +struct FormatSpec { + std::optional<FormatFill> fill; + std::optional<FormatSign> formatSign; + uint32_t minWidth; +}; + +namespace detail { + +template <typename Arg, typename... Args, size_t Size> +Result<size_t, FudStatus> formatHelper( + CharSpan<Size> buffer, + size_t formattedSize, + StringView formatView, + Arg&& arg, + Args&&... args); + +template <typename Arg, typename... Args, size_t Size> +Result<size_t, FudStatus> formatHelper( + CharSpan<Size> buffer, + size_t formattedSize, + StringView formatView, + Arg&& arg); + +template <size_t Size> +Result<size_t, FudStatus> formatHelper( + CharSpan<Size> buffer, + size_t formattedSize, + StringView formatView); + +} // namespace detail + +template <typename... Args, size_t Size> +Result<size_t, FudStatus> format(CharSpan<Size> buffer, FormatLiteral<Args...> formatLiteral, Args&&... args) +{ + static_assert(Size > 0); + + if (buffer.data() == nullptr) { + return FudStatus::NullPointer; + } + + StringView formatView{formatLiteral.get()}; + + if (formatView.length() == 0 || formatView.data()[0] == '\0') { + return 0U; + } + + size_t argCount = sizeof...(args); + static_cast<void>(argCount); + + size_t formattedSize = 0; + + return detail::formatHelper(buffer, formattedSize, formatView, std::forward<Args>(args)...); +} + +namespace detail { + +#define FUDETAIL_ADVANCE_FORMAT(FORMAT_VIEW, ADVANCE_BY) \ + fudAssert(ADVANCE_BY <= FORMAT_VIEW.m_length); \ + FORMAT_VIEW.m_length -= ADVANCE_BY; \ + FORMAT_VIEW.m_data += ADVANCE_BY; \ + +constexpr bool findBracket(size_t& copyLength, StringView formatView) +{ + while (copyLength < formatView.m_length) { + if (formatView.m_data[copyLength] == '{') { + return true; + } + copyLength++; + } + + return false; +} + +template <size_t Size> +size_t copyRemaining( + CharSpan<Size> buffer, + size_t formattedSize, + StringView& formatView, + size_t copyLength) +{ + fudAssert(copyLength <= formatView.length()); + if (copyLength + formattedSize > Size) { + copyLength = Size - formattedSize; + } + auto copyResult = copyMem( + buffer.data() + formattedSize, + Size - formattedSize, + formatView.m_data, + copyLength); + fudAssert(copyResult == FudStatus::Success); + FUDETAIL_ADVANCE_FORMAT(formatView, copyLength); + return formattedSize + copyLength; +} + +template <typename Arg, size_t Size> +Result<size_t, FudStatus> handleSpec( + CharSpan<Size> buffer, + size_t formattedSize, + StringView& formatView, + Arg&& arg, + bool& consumed) +{ + fudAssert(formattedSize < Size); + fudAssert(formatView.length() > 1); + + if (formatView.m_data[1] == '{') { + consumed = false; + buffer[formattedSize] = '{'; + FUDETAIL_ADVANCE_FORMAT(formatView, 2); + return formattedSize + 1; + } + + static_cast<void>(arg); + buffer[formattedSize] = 'X'; + formattedSize += 1; + size_t index = 0; + for (; index < formatView.m_length; ++index) { + if (formatView.m_data[index] == '}') { + break; + } + } + FUDETAIL_ADVANCE_FORMAT(formatView, index + 1); + return formattedSize; +} + +template <typename Arg, typename... Args, size_t Size> +Result<size_t, FudStatus> formatHelper( + CharSpan<Size> buffer, + size_t formattedSize, + StringView formatView, + Arg&& arg, + Args&&... args) +{ + while (formattedSize < Size) { + size_t copyLength = 0; + auto found = findBracket(copyLength, formatView); + formattedSize = copyRemaining(buffer, formattedSize, formatView, copyLength); + fudAssert(formattedSize <= Size); + if (!found || formattedSize == Size) { + return formattedSize; + } + + bool consumed = false; + auto specResult = handleSpec(buffer, formattedSize, formatView, std::forward<Arg>(arg), consumed); + formattedSize = M_TakeOrReturn(specResult); + fudAssert(formattedSize <= Size); + if (formattedSize == Size) { + return formattedSize; + } + + if (consumed) { + return formatHelper(buffer, formattedSize, formatView, std::forward<Args>(args)...); + } + } + + return formattedSize; +} + +template <typename Arg, typename... Args, size_t Size> +Result<size_t, FudStatus> formatHelper( + CharSpan<Size> buffer, + size_t formattedSize, + StringView formatView, + Arg&& arg) +{ + while (formattedSize < Size) { + size_t copyLength = 0; + auto found = findBracket(copyLength, formatView); + formattedSize = copyRemaining(buffer, formattedSize, formatView, copyLength); + fudAssert(formattedSize <= Size); + if (!found || formattedSize == Size) { + return formattedSize; + } + + bool consumed = false; + auto specResult = handleSpec(buffer, formattedSize, formatView, std::forward<Arg>(arg), consumed); + formattedSize = M_TakeOrReturn(specResult); + if (consumed) { + return formatHelper(buffer, formattedSize, formatView); + } + } + return formattedSize; +} + +template <size_t Size> +Result<size_t, FudStatus> formatHelper( + CharSpan<Size> buffer, + size_t formattedSize, + StringView formatView) +{ + size_t index = 0; + while (formattedSize < Size && formatView.m_length > 0) { + while (index < formatView.m_length && formattedSize + index < Size) { + if (formatView.m_data[index] == '{') { + break; + } + index++; + } + bool isBracket{false}; + if (index + 1 < formatView.m_length && formattedSize + index + 1 < Size) { + if (formatView.m_data[index] == '{') { + isBracket = true; + index++; + } + } + auto copyResult = copyMem( + buffer.data() + formattedSize, + Size - formattedSize, + formatView.m_data, + index); + formattedSize += index; + formatView.m_length -= index; + formatView.m_data += index; + if (isBracket) { + index = 0; + if (formatView.m_length > 0) { + formatView.m_length--; + formatView.m_data++; + } + if (formattedSize < Size) { + buffer.data()[formattedSize] = 'X'; + formattedSize++; + } + } + } + return formattedSize; +} + +#undef FUDETAIL_ADVANCE_FORMAT + +} // namespace detail + +} // namespace fud + +#endif |