/* * 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 // for std::format_string #include #include namespace fud { template using CharSpan = Span; template using FormatLiteral = std::format_string; template Result format(CharSpan buffer, FormatLiteral 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 fill; std::optional formatSign; uint32_t minWidth; }; namespace detail { template Result formatHelper( CharSpan buffer, size_t formattedSize, StringView formatView, Arg&& arg, Args&&... args); template Result formatHelper( CharSpan buffer, size_t formattedSize, StringView formatView, Arg&& arg); template Result formatHelper( CharSpan buffer, size_t formattedSize, StringView formatView); } // namespace detail template Result format(CharSpan buffer, FormatLiteral 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(argCount); size_t formattedSize = 0; return detail::formatHelper(buffer, formattedSize, formatView, std::forward(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 copyRemaining( CharSpan 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 Result handleSpec( CharSpan 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(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 Result formatHelper( CharSpan 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), consumed); formattedSize = M_TakeOrReturn(specResult); fudAssert(formattedSize <= Size); if (formattedSize == Size) { return formattedSize; } if (consumed) { return formatHelper(buffer, formattedSize, formatView, std::forward(args)...); } } return formattedSize; } template Result formatHelper( CharSpan 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), consumed); formattedSize = M_TakeOrReturn(specResult); if (consumed) { return formatHelper(buffer, formattedSize, formatView); } } return formattedSize; } template Result formatHelper( CharSpan 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