diff options
Diffstat (limited to 'include/fud_format.hpp')
-rw-r--r-- | include/fud_format.hpp | 531 |
1 files changed, 449 insertions, 82 deletions
diff --git a/include/fud_format.hpp b/include/fud_format.hpp index 2576eca..1d09422 100644 --- a/include/fud_format.hpp +++ b/include/fud_format.hpp @@ -19,20 +19,37 @@ #define FUD_FORMAT_HPP // #include "fud_assert.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_option.hpp" #include "fud_utf8.hpp" #include <cstdint> -#include <variant> #include <limits> +#include <variant> namespace fud { -struct FormatAlign -{ +struct FormatString { + template <size_t N> + 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<const utf8*>(m_data)}; + } + + size_t m_size; + const char* m_data; +}; + +struct FormatAlign { enum class Value : utf8 { Left = '<', @@ -41,7 +58,8 @@ struct FormatAlign Default = std::numeric_limits<utf8>::max() }; - constexpr static FormatAlign makeDefault() noexcept { + constexpr static FormatAlign makeDefault() noexcept + { return {Value::Default}; } @@ -65,7 +83,8 @@ struct FormatAlign return formatAlign; } - Value operator()() const { + Value operator()() const + { return value; } @@ -76,11 +95,13 @@ struct FormatFill { FormatAlign align; utf8 fill; - constexpr static FormatFill make() noexcept { - return FormatFill {FormatAlign::makeDefault(), ' '}; + constexpr static FormatFill make() noexcept + { + return FormatFill{FormatAlign::makeDefault(), ' '}; } - constexpr static Result<Option<FormatFill>, FudStatus> make(StringView formatView, size_t& length) noexcept { + constexpr static Result<Option<FormatFill>, FudStatus> make(StringView formatView, size_t& length) noexcept + { using RetType = Result<Option<FormatFill>, FudStatus>; if (formatView.length() < 1) { return RetType::okay(NullOpt); @@ -122,74 +143,8 @@ enum class FormatSign : uint8_t Default = std::numeric_limits<utf8>::max() }; -enum class FormatStringType : uint8_t -{ - String, - Escaped, -}; - -enum class FormatIntegerType : uint8_t -{ - BinaryLower, - BinaryUpper, - Character, - Decimal, - Octal, - HexLower, - HexUpper, -}; - -enum class FormatCharacterType : uint8_t -{ - BinaryLower, - BinaryUpper, - Character, - Decimal, - Octal, - HexLower, - HexUpper, -}; - -enum class FormatBoolType : uint8_t -{ - BinaryLower, - BinaryUpper, - Character, - Decimal, - Octal, - HexLower, - HexUpper, -}; - -enum class FormatFloatingType : uint8_t +enum class FormatType : utf8 { - FloatHexLower, - FloatHexUpper, - ScientificLower, - ScientificUpper, - Fixed, - GeneralLower, - GeneralUpper -}; - -enum class FormatPointerType : uint8_t -{ - HexLower, - HexUpper -}; - -/* -using FormatType = std::variant< // break - std::monostate, - FormatStringType, - FormatIntegerType, - FormatCharacterType, - FormatBoolType, - FormatFloatingType, - FormatPointerType>; -*/ - -enum class FormatType : utf8 { Unspecified = '\0', String = 's', Escaped = '?', @@ -234,19 +189,431 @@ struct FormatSpec { FormatType formatType{}; - bool takesWidth{false}; + bool takesWidth : 1 = false; + + bool takesPrecision : 1 = false; + + bool alternateForm : 1 = false; - bool takesPrecision{false}; + bool leadingZero : 1 = false; - bool alternateForm{false}; + bool hasLocale : 1 = false; - bool leadingZero{false}; + [[nodiscard]] constexpr bool takesPosition() const { + return position == positionUnspecified; + } + + static Result<FormatSpec, FudStatus> parse(StringView formatView, size_t& specLength); +}; + +namespace impl { + +struct FormatHandle { + void* object; + auto operator<=>(const FormatHandle& rhs) const noexcept = default; +}; + +} // namespace impl - bool hasLocale{false}; +using FormatArgument = std::variant< + std::monostate, + bool, + utf8, + int32_t, + uint32_t, + int64_t, + uint64_t, + float, + double, + long double, + const utf8*, + StringView, + const void*, + impl::FormatHandle>; + +template <size_t Size> +struct FormatArguments { + Array<FormatArgument, Size> arguments; + + constexpr size_t size() const + { + return Size; + } - static Result<FormatSpec, FudStatus> make(StringView formatView, size_t& specLength); + template <typename... Args> + static auto makeFormatArguments(Args&&... args) -> FormatArguments + { + return FormatArguments{Array<FormatArgument, Size>{{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 append() -> FudStatus */ +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt); + +template <typename Sink, typename... Args> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Args&&... args); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, bool arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, utf8 arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, int32_t arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, uint32_t arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, int64_t arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, uint64_t arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, float arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, double arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, long double arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, const utf8* arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, StringView arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, const void* arg); + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, impl::FormatHandle arg); + +namespace impl { + +Result<size_t, FudStatus> validateSpecHelper(FormatString fmt); + +struct FormatInfo { + uint16_t numArgs{0}; + uint16_t maxArg{0}; + bool positional{false}; +}; + +Result<FormatInfo, FudStatus> validateSpecHelperV2(FormatString fmt); + +template <typename Sink, size_t Size> +FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, FormatArguments<Size>& args); + +} // namespace impl + +template <typename Sink, typename... Args> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Args&&... args) +{ + static_cast<void>(sink); + if (formatMode != FormatCharMode::Unchecked) { + return {0, FudStatus::NotImplemented}; + } + + constexpr size_t numArgs = sizeof...(args); + auto formatArguments{FormatArguments<numArgs>::makeFormatArguments(std::forward<Args>(args)...)}; + static_cast<void>(formatArguments); + + auto numSpecsResult = impl::validateSpecHelper(fmt); + if (numSpecsResult.isError()) { + return {0, numSpecsResult.takeError()}; + } + + if (numSpecsResult.getOkay() < numArgs) { + return {0, FudStatus::FormatInvalid}; + } + + return impl::vFormat(sink, formatMode, fmt, formatArguments); +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt) +{ + static_cast<void>(sink); + static_cast<void>(fmt); + if (formatMode != FormatCharMode::Unchecked) { + return {0, FudStatus::NotImplemented}; + } + + return {0, FudStatus::NotImplemented}; +} + +namespace impl { + +bool nestingHelper(bool& sawBracket, int& nesting, int increment) +{ + auto inSpec = nesting > 0; + if (not inSpec && not sawBracket) { + sawBracket = true; + return false; + } + if (not inSpec && sawBracket) { + sawBracket = false; + return false; + } + + nesting += increment; + return true; +} + +Result<size_t, FudStatus> validateSpecHelper(FormatString fmt) +{ + auto scanView{fmt.view()}; + const auto length = scanView.length(); + const auto* data = scanView.data(); + int nesting = 0; + size_t index = 0; + size_t numGroups = 0; + + bool sawOpenBracket = false; + bool sawCloseBracket = false; + while (index < length) { + auto letter = data[index]; + + if (letter == FormatSpec::openBracket) { + auto incremented = nestingHelper(sawOpenBracket, nesting, 1); + if (incremented) { + numGroups++; + } + } else if (letter == FormatSpec::closeBracket) { + nestingHelper(sawCloseBracket, nesting, -1); + if (sawOpenBracket) { + sawOpenBracket = false; + sawCloseBracket = false; + numGroups++; + } + } else { + if (sawOpenBracket) { + nesting++; + numGroups++; + } + sawOpenBracket = false; + sawCloseBracket = false; + } + if (nesting < 0 || nesting > 2) { + return FudStatus::FormatInvalid; + } + ++index; + } + + if (nesting != 0 || sawOpenBracket || sawCloseBracket) { + return FudStatus::FormatInvalid; + } + + return numGroups; +} + +// NOLINTBEGIN(readability-function-cognitive-complexity) +Result<FormatInfo, FudStatus> validateSpecHelperV2(FormatString fmt) +{ + auto scanView{fmt.view()}; + const auto length = scanView.length(); + const auto* data = scanView.data(); + int nesting = 0; + size_t index = 0; + FormatInfo formatInfo{}; + + bool sawOpenBracket = false; + bool sawCloseBracket = false; + bool enteredGroup = false; + + bool firstGroup = true; + + while (index < length) { + auto inSpec = nesting > 0; + auto letter = data[index]; + + if (letter == FormatSpec::openBracket) { + if (not inSpec) { + sawOpenBracket = not sawOpenBracket; + } else { + nesting++; + formatInfo.numArgs++; + enteredGroup = true; + } + } else if (letter == FormatSpec::closeBracket) { + if (not inSpec) { + sawCloseBracket = not sawCloseBracket; + } else { + nesting--; + } + if (sawOpenBracket) { + sawOpenBracket = false; + sawCloseBracket = false; + enteredGroup = false; + formatInfo.numArgs++; + } + } else if (enteredGroup) { + nesting++; + formatInfo.numArgs++; + enteredGroup = false; + sawOpenBracket = false; + sawCloseBracket = false; + + if (classify::isDigit(letter)) { + if (length <= index) { + return FudStatus::FormatInvalid; + } + StringView specView{length - index, data + index}; + auto positionResult = fromString<decltype(formatInfo.maxArg)>(specView, Radix::Decimal); + if (positionResult.isError()) + { + return positionResult.takeError(); + } + auto positionValue = positionResult.takeOkay(); + if (not firstGroup && not formatInfo.positional) { + return FudStatus::FormatInvalid; + } + formatInfo.maxArg = std::max(formatInfo.maxArg, positionValue.value); + formatInfo.numArgs++; + firstGroup = false; + + } + } + + if (nesting < 0 || nesting > 2) { + return FudStatus::FormatInvalid; + } + ++index; + } + + if (nesting != 0 || sawOpenBracket || sawCloseBracket) { + return FudStatus::FormatInvalid; + } + + return formatInfo; +} +// NOLINTEND(readability-function-cognitive-complexity) + +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 <size_t Size> +Result<FormatSpec, FudStatus> vFormatPrepareArgs( + StringView& scanView, + FormatArguments<Size>& args, + size_t& argIndex, + bool& firstSpec, + 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()}; + bool specTakesPosition = formatSpec.position != FormatSpec::positionUnspecified; + if (firstSpec) { + takesPosition = specTakesPosition; + firstSpec = false; + } else if (takesPosition != specTakesPosition) { + return FudStatus::FormatInvalid; + } else if (takesPosition && formatSpec.position >= Size) { + return FudStatus::FormatInvalid; + } + if (formatSpec.takesWidth) { + if (takesPosition) { + auto widthPosition = formatSpec.width; + if (widthPosition >= args.size()) { + return FudStatus::FormatInvalid; + } + } + } + // auto formatResult{format(sink, + scanView.advanceUnsafe(specLength); + + return formatSpec; +} + +template <typename Sink, size_t Size> +FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, FormatArguments<Size>& args) +{ + FormatResult result{0, FudStatus::Failure}; + auto scanView = fmt.view(); + bool firstSpec = false; + bool takesPosition = false; + size_t argIndex = 0; + while (scanView.length() > 0) { + auto specIndex = findSpec(scanView); + fudAssert(specIndex <= scanView.length()); + StringView run{specIndex, scanView.data()}; + auto status = sink.append(run); + if (status != FudStatus::Success) { + result.status = status; + return result; + } + result.bytesWritten += specIndex; + scanView.advanceUnsafe(specIndex); + if (specIndex == scanView.length()) { + return result; + } + + auto specResult{vFormatPrepareArgs(scanView, args, argIndex, firstSpec, takesPosition)}; + } + + static_cast<void>(formatMode); + static_cast<void>(args); + return result; +} + +} // namespace impl + +namespace fudTest { + +inline void test() +{ + String sink{}; + + static_cast<void>(format(sink, FormatCharMode::Unchecked, "HELL YEAH")); + static_cast<void>(format(sink, FormatCharMode::Unchecked, "HELL YEAH", 1, true, "ye")); +} +} // namespace fudTest + } // namespace fud #endif |