/* * 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 "dragonbox/dragonbox.h" #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 RetType::error(FudStatus::Utf8Invalid); } if (fill == '{' || fill == '}') { return RetType::error(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, char, utf8, int32_t, uint32_t, int64_t, uint64_t, float, double, // long double, // Deliberately not implemented. const utf8*, const char*, 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, char 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, const char* 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); size_t findSpec(StringView scanView); } // 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) { if (formatMode != FormatCharMode::Unchecked) { return {0, FudStatus::NotImplemented}; } auto scanView = fmt.view(); auto specIndex = impl::findSpec(scanView); if (specIndex < scanView.length()) { return {0, FudStatus::FormatInvalid}; } auto drainResult = sink.drain(fmt.view()); return FormatResult{drainResult.bytesWritten, drainResult.status}; } namespace impl { 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; } template Result getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments& args) { using RetType = Result; static_assert(not std::is_signed_v); if (not formatSpec.takesPosition() || not formatSpec.takesWidth) { return RetType::error(FudStatus::ArgumentInvalid); } if (index >= Size) { return RetType::error(FudStatus::FormatInvalid); } if (std::holds_alternative(args[index])) { return RetType::okay(std::get(args[index])); } if (std::holds_alternative(args[index])) { auto value = std::get(args[index]); if (value < 0) { return RetType::error(FudStatus::FormatInvalid); } return RetType::okay(static_cast(value)); } if (std::holds_alternative(args[index])) { auto value = std::get(args[index]); if (value < 0 || value > std::numeric_limits::max()) { return RetType::error(FudStatus::FormatInvalid); } return RetType::okay(static_cast(value)); } if (std::holds_alternative(args[index])) { return RetType::okay(std::get(args[index])); } if (std::holds_alternative(args[index])) { auto value = std::get(args[index]); if (value > std::numeric_limits::max()) { return RetType::error(FudStatus::FormatInvalid); } return RetType::okay(static_cast(value)); } return RetType::error(FudStatus::FormatInvalid); } template Result vFormatPrepareSpec( StringView& scanView, const FormatArguments& args, const uint32_t& argIndex, bool& firstSpec, bool& takesPosition) { using RetType = Result; 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 RetType::error(FudStatus::FormatInvalid); } if (takesPosition) { auto status = vFormatPreparePositionalSpec(formatSpec, args); if (status != FudStatus::Success) { return RetType::error(status); } } else { auto status = vFormatPrepareNonPositionalSpec(formatSpec, args, argIndex); if (status != FudStatus::Success) { return RetType::error(status); } } scanView.advanceUnsafe(specLength); return RetType::okay(formatSpec); } template Result vFormatPrepareFirstSpec( StringView& scanView, const FormatArguments& args, const uint32_t& argIndex, bool& takesPosition) { using RetType = Result; 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 RetType::error(status); } } else { auto status = vFormatPrepareNonPositionalSpec(formatSpec, args, argIndex); if (status != FudStatus::Success) { return RetType::error(status); } } scanView.advanceUnsafe(specLength); return RetType::okay(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 validateIntegralFormatType( 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; break; default: result.status = FudStatus::Failure; } return prefix; } using IntCharArray = Array; FudStatus fillUnsignedBuffer(IntCharArray& buffer, uint64_t value, uint8_t& bufferLength, Radix radix, bool uppercase); template FudStatus fillSignedBuffer(IntCharArray& buffer, T value, uint8_t& bufferLength, Radix radix, bool uppercase) { static_assert(sizeof(T) <= sizeof(uint64_t)); static_assert(std::is_signed_v); if (value < 0) { value++; value = -value; value++; } if constexpr (std::is_same_v) { return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); } else if constexpr (std::is_same_v) { return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); } else if constexpr (std::is_same_v) { return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); } else if constexpr (std::is_same_v) { return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); } else if constexpr (std::is_same_v) { return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); } } template [[nodiscard]] FormatResult fillPad(Sink& sink, utf8 fill, size_t count); template FormatResult drainIntegral( Sink& sink, StringView buffer, FormatAlign::Value align, utf8 fill, size_t width, StringView sign, StringView prefix, bool padZero); template FudStatus fillIntegralBuffer( FormatType formatType, IntCharArray& buffer, uint8_t& bufferLength, T arg, Radix radix, bool uppercase); template requires std::is_scalar_v StringView getSign(FormatSign formatSign, T arg) { StringView sign{""}; if constexpr (std::is_signed_v) { if (arg < 0) { sign = StringView{"-"}; return sign; } } switch (formatSign) { case FormatSign::Plus: sign = StringView{"+"}; break; case FormatSign::Space: sign = StringView{" "}; break; case FormatSign::Minus: case FormatSign::Default: default: break; } return sign; } template requires std::is_integral_v and std::is_scalar_v [[nodiscard]] FormatResult formatIntegral(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg) { static_cast(formatMode); 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 = validateIntegralFormatType(result, formatSpec.formatType, radix, uppercase, arg); if (result.status != FudStatus::Success) { return result; } IntCharArray buffer{}; uint8_t bufferLength = 0; result.status = fillIntegralBuffer(formatSpec.formatType, buffer, bufferLength, arg, radix, uppercase); if (result.status != FudStatus::Success) { return result; } if (not formatSpec.alternateForm) { prefix = StringView{""}; } StringView sign{getSign(formatSpec.formatSign, arg)}; return drainIntegral(sink, StringView{bufferLength, buffer.data()}, align, fill, width, sign, prefix, padZero); } template FudStatus fillIntegralBuffer( FormatType formatType, IntCharArray& buffer, uint8_t& bufferLength, T arg, Radix radix, bool uppercase) { if constexpr (std::is_same_v) { if (formatType == FormatType::Unspecified) { formatType = FormatType::Character; } } if (formatType == FormatType::Character) { // return format(sink, formatMode, formatSpec, static_cast(arg)); if constexpr (std::is_signed_v) { if (arg < 0) { return FudStatus::FormatInvalid; } } if (arg > std::numeric_limits::max()) { return FudStatus::FormatInvalid; } bufferLength = 1; buffer[0] = static_cast(arg); return FudStatus::Success; } // TODO: constexpr if for signed values static_assert(not std::is_signed_v); static_assert(std::is_signed_v); if constexpr (std::is_signed_v) { return fillSignedBuffer(buffer, arg, bufferLength, radix, uppercase); } return fillUnsignedBuffer(buffer, static_cast(arg), bufferLength, radix, uppercase); } template FormatResult leftPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 fill, size_t calculatedWidth); template FormatResult rightPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 fill, size_t calculatedWidth); template FormatResult drainIntegral( Sink& sink, StringView buffer, FormatAlign::Value align, utf8 fill, size_t width, StringView sign, StringView prefix, bool padZero) { const auto calculatedWidth = buffer.length() + prefix.length() + sign.length(); auto result = leftPad(sink, align, width, fill, calculatedWidth); 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(prefix); result.bytesWritten += drainResult.bytesWritten; result.status = drainResult.status; } if (result.status != FudStatus::Success) { return result; } if (padZero) { auto zeroPadSize = width - calculatedWidth; auto padResult = fillPad(sink, '0', zeroPadSize); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } if (result.status != FudStatus::Success) { return result; } auto drainNumberResult = sink.drain(buffer); result.bytesWritten += drainNumberResult.bytesWritten; result.status = drainNumberResult.status; if (result.status != FudStatus::Success) { return result; } auto rightPadResult = rightPad(sink, align, width, fill, calculatedWidth); result.bytesWritten += rightPadResult.bytesWritten; result.status = rightPadResult.status; return result; } 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 -= drainResult.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 FormatResult leftPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 fill, size_t calculatedWidth) { if (align == FormatAlign::Value::Right and width > calculatedWidth) { auto leftPadSize = width - calculatedWidth; return fillPad(sink, fill, leftPadSize); } if (align == FormatAlign::Value::Center and width > calculatedWidth) { auto leftPadSize = (width - calculatedWidth) / 2; return fillPad(sink, fill, leftPadSize); } return FormatResult{0, FudStatus::Success}; } template FormatResult rightPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 fill, size_t calculatedWidth) { if (align == FormatAlign::Value::Left and width > calculatedWidth) { auto rightPadSize = width - calculatedWidth; return fillPad(sink, fill, rightPadSize); } if (align == FormatAlign::Value::Center and width > calculatedWidth) { auto leftPadSize = (width - calculatedWidth) / 2; auto rightPadSize = width - calculatedWidth - leftPadSize; return fillPad(sink, fill, rightPadSize); } return FormatResult{0, FudStatus::Success}; } [[nodiscard]] Result validateFloatFormatType(FormatType formatType); template [[nodiscard]] FormatResult formatNAN(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool negative) { auto uppercaseResult = validateFloatFormatType(formatSpec.formatType); if (uppercaseResult.isError()) { return FormatResult{0, uppercaseResult.takeError()}; } auto uppercase = uppercaseResult.takeOkay(); StringView output{"nan"}; if (negative && uppercase) { output = StringView{"-NAN"}; } else if (negative) { output = StringView{"-nan"}; } else if (formatSpec.formatSign == FormatSign::Plus && uppercase) { output = StringView{"+NAN"}; } else if (formatSpec.formatSign == FormatSign::Plus) { output = StringView{"+nan"}; } else if (formatSpec.formatSign == FormatSign::Space && uppercase) { output = StringView{" NAN"}; } else if (formatSpec.formatSign == FormatSign::Space) { output = StringView{" nan"}; } FormatSpec newSpec{}; newSpec.width = formatSpec.width; newSpec.fill = formatSpec.fill; return format(sink, formatMode, newSpec, output); } template [[nodiscard]] FormatResult formatINF(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool negative) { auto uppercaseResult = validateFloatFormatType(formatSpec.formatType); if (uppercaseResult.isError()) { return FormatResult{0, uppercaseResult.takeError()}; } auto uppercase = uppercaseResult.takeOkay(); StringView output{"inf"}; if (negative && uppercase) { output = StringView{"-INF"}; } else if (negative) { output = StringView{"-inf"}; } else if (formatSpec.formatSign == FormatSign::Plus && uppercase) { output = StringView{"+INF"}; } else if (formatSpec.formatSign == FormatSign::Plus) { output = StringView{"+inf"}; } else if (formatSpec.formatSign == FormatSign::Space && uppercase) { output = StringView{" INF"}; } else if (formatSpec.formatSign == FormatSign::Space) { output = StringView{" inf"}; } FormatSpec newSpec{}; newSpec.width = formatSpec.width; newSpec.fill = formatSpec.fill; return format(sink, formatMode, newSpec, output); } template [[nodiscard]] FormatResult formatZero( Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool negative) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(formatMode); static_cast(sink); static_cast(formatSpec); static_cast(negative); return result; } constexpr int16_t decimalExpMin = -324; constexpr int16_t decimalExpMax = 308; constexpr uint8_t maxDecimalExpLength = 3; using ExponentBuffer = Array; ExponentBuffer getScientificExponent(int exponent, uint8_t& exponentLength, bool uppercase); template void fillScientificBuffer(IntCharArray& buffer, Significand significand, uint8_t& bufferLength, auto precision) { constexpr auto radix = static_cast(Radix::Decimal); bufferLength = 0; while (significand > 0) { auto digit = static_cast(significand % radix); buffer[bufferLength++] = digit; significand /= radix; } constexpr auto roundingPlace = 5; if (precision + 1 < bufferLength) { size_t offset = bufferLength - precision - 1; bool carry = static_cast(buffer[offset - 1] >= roundingPlace); while (carry && offset < bufferLength) { if (buffer[offset] == radix - 1) { buffer[offset] = 0; } else { buffer[offset]++; carry = false; } offset++; } if (carry) { bufferLength++; buffer[offset] = 1; } } forEach(Span{buffer.data(), bufferLength}, [&](uint8_t digit) { return static_cast(digit + '0'); }); for (size_t idx = 0; idx < bufferLength / 2; ++idx) { auto rhsIndex = bufferLength - idx - 1; std::swap(buffer[idx], buffer[rhsIndex]); } } template [[nodiscard]] FormatResult formatScientific( Sink& sink, const FormatSpec& formatSpec, DecimalRepr decimalRepr, bool uppercase) { FormatResult result{0, FudStatus::Success}; if (decimalRepr.significand == 0) { result.status = FudStatus::ArgumentInvalid; return result; } constexpr auto defaultPrecision = 6; if (decimalRepr.exponent < decimalExpMin || decimalRepr.exponent > decimalExpMax) { result.status = FudStatus::ArgumentInvalid; return result; } const auto precisionSpecified = formatSpec.precision != FormatSpec::precisionUnspecified; const auto precision = precisionSpecified ? formatSpec.precision : defaultPrecision; const bool hasDecimal = formatSpec.alternateForm || precision > 0; IntCharArray buffer{}; uint8_t bufferLength = 0; fillScientificBuffer(buffer, decimalRepr.significand, bufferLength, precision); Array floatBuffer{}; floatBuffer[0] = buffer[0]; size_t precisionPlaces = 0; if (hasDecimal) { floatBuffer[1] = '.'; for (size_t idx = 1; idx < bufferLength && precisionPlaces < precision; ++idx) { floatBuffer[idx + 1] = buffer[idx]; precisionPlaces++; } } StringView numberView{1 + static_cast(hasDecimal) + precisionPlaces, floatBuffer.data()}; uint8_t exponentLength = 0; auto exponent = decimalRepr.exponent + bufferLength - 1; auto exponentBuffer{getScientificExponent(exponent, exponentLength, uppercase)}; StringView exponentView{exponentLength, exponentBuffer.data()}; StringView sign{getSign(formatSpec.formatSign, -1 * static_cast(decimalRepr.is_negative))}; const size_t calculatedWidth = 1 + precision + sign.length() + static_cast(hasDecimal) + exponentLength; 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; } if (width > calculatedWidth && not padZero) { auto padResult = leftPad(sink, align, width, fill, calculatedWidth); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } if (result.status == FudStatus::Success && sign.length() > 0) { auto drainResult = sink.drain(sign); result.bytesWritten += drainResult.bytesWritten; result.status = drainResult.status; } if (result.status == FudStatus::Success && padZero && width > calculatedWidth) { auto zeroPadSize = width - calculatedWidth; auto padResult = fillPad(sink, '0', zeroPadSize); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } if (result.status == FudStatus::Success) { auto drainNumberResult = sink.drain(numberView); result.bytesWritten += drainNumberResult.bytesWritten; result.status = drainNumberResult.status; } if (result.status == FudStatus::Success && precision > precisionPlaces) { auto remainingPlaces = precision - precisionPlaces; auto precisionResult = fillPad(sink, '0', remainingPlaces); result.bytesWritten += precisionResult.bytesWritten; result.status = precisionResult.status; } if (result.status == FudStatus::Success) { auto exponentDrainResult = sink.drain(exponentView); result.bytesWritten += exponentDrainResult.bytesWritten; result.status = exponentDrainResult.status; } if (result.status == FudStatus::Success) { auto padResult = rightPad(sink, align, width, fill, calculatedWidth); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; } return result; } template requires std::is_floating_point_v and std::is_scalar_v [[nodiscard]] FormatResult formatFloat(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg) { switch (std::fpclassify(arg)) { case FP_INFINITE: return formatINF(sink, formatMode, formatSpec, std::signbit(arg)); case FP_NAN: return formatNAN(sink, formatMode, formatSpec, std::signbit(arg)); case FP_ZERO: return formatZero(sink, formatMode, formatSpec, std::signbit(arg)); default: break; } auto decimalRepr{jkj::dragonbox::to_decimal(arg)}; static_assert(std::is_unsigned_v); if constexpr (std::is_same_v) { static_assert(sizeof(decimalRepr.significand) == sizeof(std::uint64_t)); static_assert(std::is_same_v); } else { static_assert(std::is_same_v); static_assert(std::is_same_v); } static_assert(std::is_same_v); static_assert(std::is_same_v); static_cast(decimalRepr); auto uppercaseResult = validateFloatFormatType(formatSpec.formatType); if (uppercaseResult.isError()) { return FormatResult{0, uppercaseResult.takeError()}; } auto uppercase = uppercaseResult.takeOkay(); FormatSpec floatSpec{formatSpec}; if (floatSpec.formatType == FormatType::Unspecified) { if (floatSpec.precision != FormatSpec::precisionUnspecified) { floatSpec.formatType = FormatType::GeneralLower; } } FormatResult result{0, FudStatus::NotImplemented}; using FormatType::FixedLower, FormatType::FixedUpper; using FormatType::FloatHexLower, FormatType::FloatHexUpper; using FormatType::GeneralLower, FormatType::GeneralUpper; using FormatType::ScientificLower, FormatType::ScientificUpper; if (floatSpec.formatType == FloatHexLower || floatSpec.formatType == FloatHexUpper) { } else if (floatSpec.formatType == ScientificLower || floatSpec.formatType == ScientificUpper) { return formatScientific(sink, floatSpec, decimalRepr, uppercase); } else if (floatSpec.formatType == FixedLower || floatSpec.formatType == FixedUpper) { } else if (floatSpec.formatType == GeneralLower || floatSpec.formatType == GeneralUpper) { } else { result.status = FudStatus::FormatInvalid; } return result; } } // namespace impl template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg) { FormatSpec boolSpec = formatSpec; if (formatSpec.formatType == FormatType::Unspecified || formatSpec.formatType == FormatType::String) { StringView view{"true"}; if (not arg) { view = StringView{"false"}; } return format(sink, formatMode, boolSpec, view); } boolSpec.formatType = FormatType::Unspecified; return impl::formatIntegral(sink, formatMode, boolSpec, static_cast(arg)); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg) { return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, char arg) { return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int32_t arg) { return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint32_t arg) { return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int64_t arg) { return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint64_t arg) { return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, float arg) { return impl::formatFloat(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, double arg) { return impl::formatFloat(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, long double arg) { FormatResult result{0, FudStatus::NotImplemented}; static_cast(formatMode); static_cast(sink); static_cast(formatSpec); static_cast(arg); return result; } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg) { return format(sink, formatMode, formatSpec, reinterpret_cast(arg)); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const char* arg) { if (arg == nullptr) { return {0, FudStatus::NullPointer}; } auto stringLengthResult = cStringLength(arg); if (stringLengthResult < 0 || stringLengthResult >= SSIZE_MAX) { return {0, FudStatus::StringInvalid}; } auto stringLength = static_cast(stringLengthResult); return format(sink, formatMode, formatSpec, StringView{stringLength, arg}); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg) { /* TODO: implement unicode points, check with formatMode */ FormatResult result{0, FudStatus::Success}; if (formatMode != FormatCharMode::Unchecked) { result.status = FudStatus::NotSupported; return result; } if (formatSpec.formatType != FormatType::Unspecified && formatSpec.formatType != FormatType::String && formatSpec.formatType != FormatType::Escaped) { result.status = FudStatus::FormatInvalid; return result; } if (formatSpec.leadingZero) { result.status = FudStatus::FormatInvalid; return result; } auto widthSpecified = formatSpec.width != FormatSpec::widthUnspecified; auto width = widthSpecified ? formatSpec.width : 0; auto precisionSpecified = formatSpec.precision != FormatSpec::precisionUnspecified; if (precisionSpecified and formatSpec.precision < arg.length()) { arg.m_length = formatSpec.precision; } auto calculatedWidth = arg.length(); auto align = formatSpec.fill.align.value; auto fill = formatSpec.fill.fill; if (align == FormatAlign::Value::Default) { align = FormatAlign::Value::Left; } auto padResult = impl::leftPad(sink, align, width, fill, calculatedWidth); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; if (result.status != FudStatus::Success) { return result; } auto drainViewResult = sink.drain(arg); result.bytesWritten += drainViewResult.bytesWritten; result.status = drainViewResult.status; if (result.status != FudStatus::Success) { return result; } padResult = impl::rightPad(sink, align, width, fill, calculatedWidth); result.bytesWritten += padResult.bytesWritten; result.status = padResult.status; 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