/* * 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_array.hpp" #include "fud_option.hpp" #include "fud_result.hpp" #include "fud_status.hpp" #include "fud_string.hpp" #include "fud_string_convert.hpp" #include "fud_string_view.hpp" #include "fud_utf8.hpp" #include #include #include #include namespace fud { // TODO: constants file? constexpr size_t bitsPerOctal = 3; constexpr size_t maxIntCharCount = bitsPerOctal * sizeof(uint64_t) + 4; struct FormatString { template consteval FormatString(const char (&input)[N]) : m_size{N - 1}, m_data{input} { static_assert(N > 0); } StringView view() { return StringView{m_size, reinterpret_cast(m_data)}; } size_t m_size; const char* m_data; }; struct FormatAlign { enum class Value : utf8 { Left = '<', Right = '>', Center = '^', Default = std::numeric_limits::max() }; constexpr static FormatAlign makeDefault() noexcept { return {Value::Default}; } constexpr static Option from(utf8 letter) { FormatAlign formatAlign; switch (letter) { case '<': formatAlign.value = Value::Left; break; case '>': formatAlign.value = Value::Right; break; case '^': formatAlign.value = Value::Center; break; default: return NullOpt; } return formatAlign; } Value operator()() const { return value; } Value value; }; struct FormatFill { FormatAlign align; utf8 fill; constexpr static FormatFill make() noexcept { return FormatFill{FormatAlign::makeDefault(), ' '}; } constexpr static Result, FudStatus> make(StringView formatView, size_t& length) noexcept { using RetType = Result, FudStatus>; if (formatView.length() < 1) { return RetType::okay(NullOpt); } const auto* data = formatView.data(); auto align1 = FormatAlign::from(data[0]); decltype(align1) align2 = NullOpt; if (formatView.length() > 1) { align2 = FormatAlign::from(data[1]); } if (align2.hasValue()) { length = 2; auto fill = data[0]; if (not Ascii::valid(fill)) { return FudStatus::Utf8Invalid; } if (fill == '{' || fill == '}') { return FudStatus::FormatInvalid; } return RetType::okay(FormatFill{std::move(align2).value(), data[0]}); } if (align1.hasValue()) { length = 1; return RetType::okay(FormatFill{std::move(align1).value(), ' '}); } return RetType::okay(NullOpt); } }; enum class FormatSign : uint8_t { Plus = '+', Minus = '-', Space = ' ', Default = std::numeric_limits::max() }; enum class FormatType : utf8 { Unspecified = '\0', String = 's', Escaped = '?', BinaryLower = 'b', BinaryUpper = 'B', Character = 'c', Decimal = 'd', Octal = 'o', HexLower = 'x', HexUpper = 'X', FloatHexLower = 'a', FloatHexUpper = 'A', ScientificLower = 'e', ScientificUpper = 'E', FixedLower = 'f', FixedUpper = 'F', GeneralLower = 'g', GeneralUpper = 'G', }; struct FormatSpec; using FormatSpecResult = Result; struct FormatSpec { static constexpr uint32_t widthUnspecified = std::numeric_limits::max(); static constexpr uint32_t precisionUnspecified = std::numeric_limits::max(); static constexpr uint16_t positionUnspecified = std::numeric_limits::max(); static constexpr utf8 openBracket = '{'; static constexpr utf8 closeBracket = '}'; static constexpr utf8 formatTypeUnspecified = std::numeric_limits::max(); static constexpr utf8 localeChar = 'L'; uint32_t width{widthUnspecified}; uint32_t precision{precisionUnspecified}; uint16_t position{positionUnspecified}; FormatFill fill{FormatFill::make()}; FormatSign formatSign{FormatSign::Default}; FormatType formatType{}; bool takesWidth : 1 = false; bool takesPrecision : 1 = false; bool alternateForm : 1 = false; bool leadingZero : 1 = false; bool hasLocale : 1 = false; [[nodiscard]] constexpr bool takesPosition() const { return position != positionUnspecified; } static Result parse(StringView formatView, size_t& specLength); }; namespace impl { struct FormatHandle { void* object; auto operator<=>(const FormatHandle& rhs) const noexcept = default; }; } // namespace impl using FormatArgument = std::variant< bool, utf8, int32_t, uint32_t, int64_t, uint64_t, float, double, long double, const utf8*, StringView, const void*, impl::FormatHandle>; template struct FormatArguments { Array arguments; constexpr size_t size() const { return Size; } constexpr const FormatArgument& operator[](size_t index) const { return arguments[index]; } template static auto makeFormatArguments(Args&&... args) -> FormatArguments { return FormatArguments{Array{{FormatArgument{args}...}}}; } }; static_assert(sizeof(FormatArguments<1>) > 0); enum class FormatCharMode { Unchecked, AsciiOnly, ValidUtf8 }; struct FormatResult { size_t bytesWritten{0}; FudStatus status{FudStatus::Failure}; [[nodiscard]] constexpr bool isOkay() { return status == FudStatus::Success; } }; /* TODO : require concept of a sink that takes pushBack() -> FudStatus */ /* TODO : sink concept also requires drain() -> FudStatus */ template FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt); template FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Args&&... args); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int32_t arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint32_t arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int64_t arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint64_t arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, float arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, double arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, long double arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const void* arg); template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, impl::FormatHandle arg); namespace impl { template FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, const FormatArguments& args); } // namespace impl template FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Args&&... args) { static_cast(sink); if (formatMode != FormatCharMode::Unchecked) { return {0, FudStatus::NotImplemented}; } constexpr size_t numArgs = sizeof...(args); auto formatArguments{FormatArguments::makeFormatArguments(std::forward(args)...)}; return impl::vFormat(sink, formatMode, fmt, formatArguments); } template FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt) { static_cast(sink); static_cast(fmt); if (formatMode != FormatCharMode::Unchecked) { return {0, FudStatus::NotImplemented}; } return {0, FudStatus::NotImplemented}; } namespace impl { size_t findSpec(StringView scanView); template Result getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments& args); template Result vFormatPrepareSpec( StringView& scanView, const FormatArguments& args, const uint32_t& argIndex, bool& firstSpec, bool& takesPosition); template Result vFormatPrepareFirstSpec( StringView& scanView, const FormatArguments& args, const uint32_t& argIndex, bool& takesPosition); template FudStatus vFormatPreparePositionalSpec(FormatSpec& formatSpec, const FormatArguments& args); template FudStatus vFormatPrepareNonPositionalSpec( FormatSpec& formatSpec, const FormatArguments& args, const uint32_t& argIndex); template FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, const FormatArguments& args) { FormatResult result{0, FudStatus::Success}; auto scanView = fmt.view(); bool firstSpec = true; bool takesPosition = false; uint32_t argIndex = 0; while (scanView.length() > 0) { if (argIndex > Size) { result.status = FudStatus::FormatInvalid; return result; } auto specIndex = findSpec(scanView); fudAssert(specIndex <= scanView.length()); StringView run{specIndex, scanView.data()}; auto drainResult = sink.drain(run); result.bytesWritten += drainResult.bytesWritten; if (drainResult.status != FudStatus::Success) { result.status = drainResult.status; return result; } scanView.advanceUnsafe(specIndex); if (specIndex == scanView.length()) { return result; } auto specResult{vFormatPrepareSpec(scanView, args, argIndex, firstSpec, takesPosition)}; if (specResult.isError()) { result.status = specResult.takeError(); return result; } auto formatSpec{specResult.takeOkay()}; uint32_t formatArgIndex = argIndex; if (formatSpec.takesPosition()) { if (formatSpec.position >= Size) { result.status = specResult.takeError(); return result; } formatArgIndex = formatSpec.position; } auto argResult{std::visit( [&](const auto& arg) -> FormatResult { return format(sink, formatMode, formatSpec, arg); }, args[formatArgIndex])}; if (argResult.status != FudStatus::Success) { result.status = argResult.status; return result; } result.bytesWritten += argResult.bytesWritten; if (!formatSpec.takesPosition()) { argIndex++; argIndex += static_cast(formatSpec.takesWidth); argIndex += static_cast(formatSpec.takesPrecision); } } return result; } size_t findSpec(StringView scanView) { size_t index = 0; bool foundBracket = false; while (index < scanView.length()) { auto letter = scanView[index]; if (letter == FormatSpec::openBracket && not foundBracket) { foundBracket = true; } else if (letter == FormatSpec::openBracket && foundBracket) { foundBracket = false; } else if (foundBracket) { index--; break; } index++; } return index; } template Result getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments& args) { static_assert(not std::is_signed_v); if (not formatSpec.takesPosition() || not formatSpec.takesWidth) { return FudStatus::ArgumentInvalid; } if (index >= Size) { return FudStatus::FormatInvalid; } if (std::holds_alternative(args[index])) { return std::get(args[index]); } if (std::holds_alternative(args[index])) { auto value = std::get(args[index]); if (value < 0) { return FudStatus::FormatInvalid; } return static_cast(value); } if (std::holds_alternative(args[index])) { auto value = std::get(args[index]); if (value < 0 || value > std::numeric_limits::max()) { return FudStatus::FormatInvalid; } return static_cast(value); } if (std::holds_alternative(args[index])) { return std::get(args[index]); } if (std::holds_alternative(args[index])) { auto value = std::get(args[index]); if (value > std::numeric_limits::max()) { return FudStatus::FormatInvalid; } return static_cast(value); } return FudStatus::FormatInvalid; } template Result vFormatPrepareSpec( StringView& scanView, const FormatArguments& args, const uint32_t& argIndex, bool& firstSpec, bool& takesPosition) { if (firstSpec) { firstSpec = false; return vFormatPrepareFirstSpec(scanView, args, argIndex, takesPosition); } size_t specLength{0}; auto formatSpecResult{FormatSpec::parse(scanView, specLength)}; if (formatSpecResult.isError()) { return formatSpecResult; } fudAssert(specLength <= scanView.length()); auto formatSpec{formatSpecResult.getOkay()}; if (takesPosition != formatSpec.takesPosition() || (takesPosition && formatSpec.position >= Size)) { return FudStatus::FormatInvalid; } if (takesPosition) { auto status = vFormatPreparePositionalSpec(formatSpec, args); if (status != FudStatus::Success) { return status; } } else { auto status = vFormatPrepareNonPositionalSpec(formatSpec, args, argIndex); if (status != FudStatus::Success) { return status; } } scanView.advanceUnsafe(specLength); return formatSpec; } template Result vFormatPrepareFirstSpec( StringView& scanView, const FormatArguments& args, const uint32_t& argIndex, bool& takesPosition) { size_t specLength{0}; auto formatSpecResult{FormatSpec::parse(scanView, specLength)}; if (formatSpecResult.isError()) { return formatSpecResult; } fudAssert(specLength <= scanView.length()); auto formatSpec{formatSpecResult.takeOkay()}; takesPosition = formatSpec.takesPosition(); if (takesPosition) { auto status = vFormatPreparePositionalSpec(formatSpec, args); if (status != FudStatus::Success) { return status; } } else { auto status = vFormatPrepareNonPositionalSpec(formatSpec, args, argIndex); if (status != FudStatus::Success) { return status; } } scanView.advanceUnsafe(specLength); return formatSpec; } template FudStatus vFormatPreparePositionalSpec(FormatSpec& formatSpec, const FormatArguments& args) { if (not formatSpec.takesPosition()) { return FudStatus::OperationInvalid; } if (formatSpec.takesWidth) { auto widthResult = getSpecField(formatSpec, formatSpec.width, args); if (widthResult.isError()) { return widthResult.takeError(); } formatSpec.width = widthResult.takeOkay(); } if (formatSpec.takesPrecision) { auto precisionResult = getSpecField(formatSpec, formatSpec.precision, args); if (precisionResult.isError()) { return precisionResult.takeError(); } formatSpec.precision = precisionResult.takeOkay(); } return FudStatus::Success; } template FudStatus vFormatPrepareNonPositionalSpec( FormatSpec& formatSpec, const FormatArguments& args, const uint32_t& argIndex) { if (formatSpec.takesPosition()) { return FudStatus::OperationInvalid; } uint32_t fieldIndex = argIndex + 1; if (formatSpec.takesWidth) { if (fieldIndex < argIndex) { return FudStatus::FormatInvalid; } auto widthResult = getSpecField(formatSpec, fieldIndex, args); if (widthResult.isError()) { return widthResult.takeError(); } formatSpec.width = widthResult.takeOkay(); fieldIndex++; } if (formatSpec.takesPrecision) { if (fieldIndex < argIndex) { return FudStatus::FormatInvalid; } auto precisionResult = getSpecField(formatSpec, fieldIndex, args); if (precisionResult.isError()) { return precisionResult.takeError(); } formatSpec.precision = precisionResult.takeOkay(); } return FudStatus::Success; } template requires std::is_integral_v [[nodiscard]] StringView validateFormatType( FormatResult& result, FormatType formatType, Radix& radix, bool& uppercase, T arg) { StringView prefix{""}; switch (formatType) { case FormatType::Unspecified: break; case FormatType::BinaryLower: prefix = StringView{"0b"}; radix = Radix::Binary; break; case FormatType::BinaryUpper: prefix = StringView{"0B"}; radix = Radix::Binary; uppercase = true; break; case FormatType::Character: if constexpr (std::is_signed_v) { if (arg < 0) { result.status = FudStatus::FormatInvalid; } } if (arg > std::numeric_limits::max()) { result.status = FudStatus::FormatInvalid; break; } break; case FormatType::Decimal: break; case FormatType::Octal: prefix = StringView{"o"}; radix = Radix::Octal; break; case FormatType::HexLower: prefix = StringView{"0x"}; radix = Radix::Hexadecimal; break; case FormatType::HexUpper: prefix = StringView{"0X"}; radix = Radix::Hexadecimal; uppercase = true; break; case FormatType::FloatHexLower: case FormatType::FloatHexUpper: case FormatType::String: case FormatType::Escaped: case FormatType::ScientificLower: case FormatType::ScientificUpper: case FormatType::FixedLower: case FormatType::FixedUpper: case FormatType::GeneralLower: case FormatType::GeneralUpper: result.status = FudStatus::FormatInvalid; default: result.status = FudStatus::Failure; } return prefix; } inline FudStatus fillUnsignedBuffer( Array& buffer, uint64_t value, uint8_t& bufferLength, Radix radix, bool uppercase) { static_assert(maxIntCharCount < std::numeric_limits::max()); bufferLength = 0; constexpr Array(Radix::Hexadecimal)> lower{ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}}; constexpr Array(Radix::Hexadecimal)> upper{ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}}; const auto& lookup = uppercase ? upper : lower; if (value == 0) { buffer[0] = '0'; bufferLength = 1; return FudStatus::Success; } static_assert(std::is_same_v, uint8_t>); auto radixValue = static_cast(radix); while (value > 0 && bufferLength < maxIntCharCount) { auto digit = static_cast(value % radixValue); buffer[bufferLength] = lookup[digit]; value /= radixValue; bufferLength++; } if (value > 0 || bufferLength > maxIntCharCount) { return FudStatus::Failure; } // TODO: implement fud_algorithm reverse for (size_t idx = 0; idx < bufferLength / 2; ++idx) { auto rhsIndex = bufferLength - idx - 1; std::swap(buffer[idx], buffer[rhsIndex]); } return FudStatus::Success; } template [[nodiscard]] FormatResult fillPad(Sink& sink, utf8 fill, size_t count) { FormatResult result{0, FudStatus::Success}; constexpr size_t BigBlockSize = 256; constexpr size_t MediumBlockSize = 64; constexpr size_t SmallBlockSize = 16; constexpr size_t SmallestBlockSize = 4; auto backingBuffer = Array::constFill(fill); constexpr auto bufferSizes{Array{{BigBlockSize, MediumBlockSize, SmallBlockSize, SmallestBlockSize}}}; for (auto bufferSize : bufferSizes) { while (count > bufferSize) { auto drainResult = sink.drain(StringView{bufferSize, backingBuffer.data()}); result.bytesWritten += drainResult.bytesWritten; result.status = drainResult.status; if (result.status != FudStatus::Success) { return result; } count -= result.bytesWritten; } } if (count > 0) { fudAssert(count < backingBuffer.size()); auto drainResult = sink.drain(StringView{count, backingBuffer.data()}); result.bytesWritten += drainResult.bytesWritten; result.status = drainResult.status; } return result; } template requires std::is_integral_v and std::is_scalar_v /* and std::is_unsigned_v */ [[nodiscard]] FormatResult formatUnsigned(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg) { FormatResult result{0, FudStatus::Success}; if (formatSpec.takesPrecision || formatSpec.precision != FormatSpec::precisionUnspecified) { result.status = FudStatus::FormatInvalid; return result; } auto fill = formatSpec.fill.fill; auto align = formatSpec.fill.align.value; bool padZero = false; if (align == FormatAlign::Value::Default) { align = FormatAlign::Value::Right; fill = formatSpec.leadingZero ? '0' : fill; padZero = formatSpec.leadingZero; } auto width = formatSpec.width; if (width == FormatSpec::widthUnspecified) { width = 0; } auto radix = Radix::Decimal; bool uppercase = false; auto prefix = validateFormatType(result, formatSpec.formatType, radix, uppercase, arg); if (result.status != FudStatus::Success) { return result; } if (formatSpec.formatType == FormatType::Character) { return format(sink, formatMode, formatSpec, static_cast(arg)); } if (not formatSpec.alternateForm) { prefix = StringView{""}; } auto calculatedWidth = prefix.length(); Array buffer{}; uint8_t bufferLength = 0; // TODO: constexpr if for signed values result.status = fillUnsignedBuffer(buffer, static_cast(arg), bufferLength, radix, uppercase); if (result.status != FudStatus::Success) { return result; } calculatedWidth += bufferLength; StringView sign{""}; switch (formatSpec.formatSign) { case FormatSign::Plus: sign = StringView{"+"}; break; case FormatSign::Space: sign = StringView{" "}; break; case FormatSign::Minus: case FormatSign::Default: default: if constexpr (std::is_signed_v) { if (arg < 0) { sign = StringView{"-"}; } } break; } calculatedWidth += sign.length(); if (align == FormatAlign::Value::Right and width > calculatedWidth) { auto leftPadSize = width - calculatedWidth; auto padResult = fillPad(sink, fill, leftPadSize); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } else if (align == FormatAlign::Value::Center and width > calculatedWidth) { auto leftPadSize = (width - calculatedWidth) / 2; auto padResult = fillPad(sink, fill, leftPadSize); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } if (result.status != FudStatus::Success) { return result; } if (sign.length() > 0) { auto drainResult = sink.drain(sign); result.bytesWritten += drainResult.bytesWritten; result.status = drainResult.status; } if (result.status != FudStatus::Success) { return result; } if (prefix.length() > 0) { auto drainResult = sink.drain(sign); result.bytesWritten += drainResult.bytesWritten; result.status = drainResult.status; } if (result.status != FudStatus::Success) { return result; } if (padZero) { auto leftPadSize = width - calculatedWidth; auto padResult = fillPad(sink, '0', leftPadSize); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } if (result.status != FudStatus::Success) { return result; } auto drainNumberResult = sink.drain(StringView{bufferLength, buffer.data()}); result.bytesWritten += drainNumberResult.bytesWritten; result.status = drainNumberResult.status; if (result.status != FudStatus::Success) { return result; } if (align == FormatAlign::Value::Left and width > calculatedWidth) { auto rightPadSize = width - calculatedWidth; auto padResult = fillPad(sink, fill, rightPadSize); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } else if (align == FormatAlign::Value::Center and width > calculatedWidth) { auto leftPadSize = (width - calculatedWidth) / 2; auto rightPadSize = width - leftPadSize; auto padResult = fillPad(sink, fill, rightPadSize); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } return result; } } // namespace impl template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int32_t arg) { return impl::formatUnsigned(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint32_t arg) { return impl::formatUnsigned(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int64_t arg) { return impl::formatUnsigned(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint64_t arg) { return impl::formatUnsigned(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, float arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, double arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, long double arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const void* arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, impl::FormatHandle arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(sink); static_cast(formatMode); static_cast(formatSpec); static_cast(arg); return result; } namespace fudTest { inline void test() { StringView prefix{0, ""}; String sink{}; static_cast(format(sink, FormatCharMode::Unchecked, "HELL YEAH")); static_cast(format(sink, FormatCharMode::Unchecked, "HELL YEAH", 1, true, "ye")); } } // namespace fudTest } // namespace fud #endif