summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2024-10-24 09:04:35 -0500
committerDominick Allen <djallen@librehumanitas.org>2024-10-24 09:04:35 -0500
commit512d026de016f2720060d264adec02e56123851d (patch)
tree2c48965d968db70e37ae82e287dc80bd205b2d4a
parent5cc7cbc3704ec255eb5d0ac53b2cc0fcb1221d63 (diff)
As always, formatting is a pain.
-rw-r--r--include/fud_format.hpp531
-rw-r--r--include/fud_result.hpp32
-rw-r--r--include/fud_string_view.hpp2
-rw-r--r--source/fud_assert.cpp4
-rw-r--r--source/fud_format.cpp7
-rw-r--r--test/test_format.cpp173
6 files changed, 617 insertions, 132 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
diff --git a/include/fud_result.hpp b/include/fud_result.hpp
index 877c49c..95f3e5c 100644
--- a/include/fud_result.hpp
+++ b/include/fud_result.hpp
@@ -104,21 +104,53 @@ class [[nodiscard]] Result {
return std::get<T>(m_value);
}
+ [[nodiscard]] constexpr const T& getOkayOr(const T& alternative) const&
+ {
+ if (!isOkay()) {
+ return alternative;
+ }
+ return std::get<T>(m_value);
+ }
+
[[nodiscard]] constexpr const E& getError() const&
{
return std::get<E>(m_value);
}
+ [[nodiscard]] constexpr const E& getErrorOr(const E& alternative) const&
+ {
+ if (!isError()) {
+ return alternative;
+ }
+ return std::get<E>(m_value);
+ }
+
[[nodiscard]] constexpr T&& takeOkay()
{
return std::move(std::get<T>(m_value));
}
+ [[nodiscard]] constexpr T&& takeOkayOr(T&& alternative)
+ {
+ if (!isOkay()) {
+ return std::move(alternative);
+ }
+ return std::move(std::get<T>(m_value));
+ }
+
[[nodiscard]] constexpr E&& takeError()
{
return std::move(std::get<E>(m_value));
}
+ [[nodiscard]] constexpr E&& takeErrorOr(E&& alternative)
+ {
+ if (!isError()) {
+ return std::move(alternative);
+ }
+ return std::move(std::get<E>(m_value));
+ }
+
private:
constexpr Result() : m_value()
{
diff --git a/include/fud_string_view.hpp b/include/fud_string_view.hpp
index 0852645..2ced639 100644
--- a/include/fud_string_view.hpp
+++ b/include/fud_string_view.hpp
@@ -103,6 +103,8 @@ struct StringView {
size_t m_length{0};
const utf8* m_data{nullptr};
+
+ auto operator<=>(const StringView& rhs) const noexcept = default;
};
} // namespace fud
diff --git a/source/fud_assert.cpp b/source/fud_assert.cpp
index ccc90a5..8811464 100644
--- a/source/fud_assert.cpp
+++ b/source/fud_assert.cpp
@@ -10,8 +10,8 @@
namespace fud {
-constexpr std::size_t BITS_PER_OCTAL = 3;
-constexpr auto MAX_LINE_CHARS = BITS_PER_OCTAL * sizeof(decltype(std::source_location{}.line())) + 3;
+// constexpr std::size_t BITS_PER_OCTAL = 3;
+// constexpr auto MAX_LINE_CHARS = BITS_PER_OCTAL * sizeof(decltype(std::source_location{}.line())) + 3;
[[noreturn]] void assertFail(const char* assertion, const std::source_location sourceLocation)
{
diff --git a/source/fud_format.cpp b/source/fud_format.cpp
index 04e611e..6bd5aae 100644
--- a/source/fud_format.cpp
+++ b/source/fud_format.cpp
@@ -23,12 +23,9 @@ FudStatus getFormatSign(StringView& formatView, FormatSpec& spec);
} // namespace impl
-Result<FormatSpec, FudStatus> FormatSpec::make(StringView formatView, size_t& specLength)
+Result<FormatSpec, FudStatus> FormatSpec::parse(StringView formatView, size_t& specLength)
{
- static_cast<void>(formatView);
- static_cast<void>(specLength);
-
- if (formatView[0] != FormatSpec::openBracket) {
+ if (formatView.length() < 2 || formatView[0] != FormatSpec::openBracket) {
return FudStatus::ArgumentInvalid;
}
diff --git a/test/test_format.cpp b/test/test_format.cpp
index 68c94cd..f0ea93f 100644
--- a/test/test_format.cpp
+++ b/test/test_format.cpp
@@ -15,8 +15,8 @@
* limitations under the License.
*/
-#include "fud_string_view.hpp"
#include "fud_format.hpp"
+#include "fud_string_view.hpp"
#include "gtest/gtest.h"
@@ -26,17 +26,20 @@ TEST(FormatTest, BasePositionalTest)
{
size_t length = 0;
- auto formatSpecResult = FormatSpec::make(StringView{" {1:}"}, length);
+ auto formatSpecResult = FormatSpec::parse(StringView{" {1:}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.takeError(), FudStatus::ArgumentInvalid);
- formatSpecResult = FormatSpec::make(StringView{"{1}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
auto formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(formatSpec.position, 1);
EXPECT_EQ(length, 3);
- formatSpecResult = FormatSpec::make(StringView{"{1:}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{""}, length);
+ ASSERT_EQ(formatSpecResult.takeErrorOr(FudStatus::NotImplemented), FudStatus::ArgumentInvalid);
+
+ formatSpecResult = FormatSpec::parse(StringView{"{1:}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 4);
@@ -49,14 +52,14 @@ TEST(FormatTest, BasePositionalTest)
EXPECT_FALSE(formatSpec.alternateForm);
EXPECT_FALSE(formatSpec.leadingZero);
EXPECT_FALSE(formatSpec.hasLocale);
- EXPECT_TRUE(std::holds_alternative<std::monostate>(formatSpec.formatType));
+ EXPECT_EQ(formatSpec.formatType, FormatType::Unspecified);
}
TEST(FormatTest, AlignTest)
{
size_t length = 0;
- auto formatSpecResult = FormatSpec::make(StringView{"{1:<}"}, length);
+ auto formatSpecResult = FormatSpec::parse(StringView{"{1:<}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
auto formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(formatSpec.position, 1);
@@ -68,16 +71,16 @@ TEST(FormatTest, AlignTest)
EXPECT_FALSE(formatSpec.alternateForm);
EXPECT_FALSE(formatSpec.leadingZero);
EXPECT_FALSE(formatSpec.hasLocale);
- EXPECT_TRUE(std::holds_alternative<std::monostate>(formatSpec.formatType));
+ EXPECT_EQ(formatSpec.formatType, FormatType::Unspecified);
- formatSpecResult = FormatSpec::make(StringView{"{1:<<}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:<<}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(formatSpec.position, 1);
EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left);
EXPECT_EQ(formatSpec.fill.fill, '<');
- formatSpecResult = FormatSpec::make(StringView{"{:<<}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{:<<}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 5);
@@ -85,7 +88,7 @@ TEST(FormatTest, AlignTest)
EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left);
EXPECT_EQ(formatSpec.fill.fill, '<');
- formatSpecResult = FormatSpec::make(StringView{"{1:_<}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:_<}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 6);
@@ -98,7 +101,7 @@ TEST(FormatTest, SpecialTest)
{
size_t length = 0;
- auto formatSpecResult = FormatSpec::make(StringView{"{1:_< }"}, length);
+ auto formatSpecResult = FormatSpec::parse(StringView{"{1:_< }"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
auto formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 7);
@@ -109,7 +112,7 @@ TEST(FormatTest, SpecialTest)
EXPECT_FALSE(formatSpec.alternateForm);
EXPECT_FALSE(formatSpec.leadingZero);
- formatSpecResult = FormatSpec::make(StringView{"{1:+}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:+}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
EXPECT_EQ(length, 5);
formatSpec = formatSpecResult.takeOkay();
@@ -118,7 +121,7 @@ TEST(FormatTest, SpecialTest)
EXPECT_FALSE(formatSpec.alternateForm);
EXPECT_FALSE(formatSpec.leadingZero);
- formatSpecResult = FormatSpec::make(StringView{"{1:-}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:-}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 5);
@@ -127,7 +130,7 @@ TEST(FormatTest, SpecialTest)
EXPECT_FALSE(formatSpec.alternateForm);
EXPECT_FALSE(formatSpec.leadingZero);
- formatSpecResult = FormatSpec::make(StringView{"{1:_<#}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:_<#}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 7);
@@ -138,7 +141,7 @@ TEST(FormatTest, SpecialTest)
EXPECT_TRUE(formatSpec.alternateForm);
EXPECT_FALSE(formatSpec.leadingZero);
- formatSpecResult = FormatSpec::make(StringView{"{1:_<0}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:_<0}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 7);
@@ -149,7 +152,7 @@ TEST(FormatTest, SpecialTest)
EXPECT_FALSE(formatSpec.alternateForm);
EXPECT_TRUE(formatSpec.leadingZero);
- formatSpecResult = FormatSpec::make(StringView{"{1: #}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1: #}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 6);
@@ -160,15 +163,15 @@ TEST(FormatTest, SpecialTest)
EXPECT_TRUE(formatSpec.alternateForm);
EXPECT_FALSE(formatSpec.leadingZero);
- formatSpecResult = FormatSpec::make(StringView{"{1:# }"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:# }"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid);
- formatSpecResult = FormatSpec::make(StringView{"{1:##}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:##}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid);
- formatSpecResult = FormatSpec::make(StringView{"{1: 0}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1: 0}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 6);
@@ -179,19 +182,19 @@ TEST(FormatTest, SpecialTest)
EXPECT_FALSE(formatSpec.alternateForm);
EXPECT_TRUE(formatSpec.leadingZero);
- formatSpecResult = FormatSpec::make(StringView{"{1:0 }"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:0 }"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid);
- formatSpecResult = FormatSpec::make(StringView{"{1:0#}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:0#}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid);
- formatSpecResult = FormatSpec::make(StringView{"{1:#00}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:#00}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid);
- formatSpecResult = FormatSpec::make(StringView{"{1: #0}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1: #0}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 7);
@@ -207,7 +210,7 @@ TEST(FormatTest, WidthTest)
{
size_t length = 0;
- auto formatSpecResult = FormatSpec::make(StringView{"{1:1}"}, length);
+ auto formatSpecResult = FormatSpec::parse(StringView{"{1:1}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
auto formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 5);
@@ -215,7 +218,7 @@ TEST(FormatTest, WidthTest)
EXPECT_FALSE(formatSpec.takesWidth);
EXPECT_EQ(formatSpec.width, 1);
- formatSpecResult = FormatSpec::make(StringView{"{1:543}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:543}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 7);
@@ -223,7 +226,7 @@ TEST(FormatTest, WidthTest)
EXPECT_FALSE(formatSpec.takesWidth);
EXPECT_EQ(formatSpec.width, 543);
- formatSpecResult = FormatSpec::make(StringView{"{:543}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{:543}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 6);
@@ -232,16 +235,16 @@ TEST(FormatTest, WidthTest)
EXPECT_EQ(formatSpec.width, 543);
// leading zero
- formatSpecResult = FormatSpec::make(StringView{"{1:00}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:00}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid);
// #x100000000 4294967296
- formatSpecResult = FormatSpec::make(StringView{"{1:4294967296}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:4294967296}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError);
- formatSpecResult = FormatSpec::make(StringView{"{1:{1}}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:{1}}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 7);
@@ -250,15 +253,15 @@ TEST(FormatTest, WidthTest)
EXPECT_EQ(formatSpec.width, 1);
// #x10000 65536
- formatSpecResult = FormatSpec::make(StringView{"{1:{65536}}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:{65536}}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError);
- formatSpecResult = FormatSpec::make(StringView{"{:{1}}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{:{1}}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid);
- formatSpecResult = FormatSpec::make(StringView{"{:{}}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{:{}}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 5);
@@ -271,7 +274,7 @@ TEST(FormatTest, PrecisionTest)
{
size_t length = 0;
- auto formatSpecResult = FormatSpec::make(StringView{"{1:.1}"}, length);
+ auto formatSpecResult = FormatSpec::parse(StringView{"{1:.1}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
auto formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 6);
@@ -279,7 +282,7 @@ TEST(FormatTest, PrecisionTest)
EXPECT_FALSE(formatSpec.takesPrecision);
EXPECT_EQ(formatSpec.precision, 1);
- formatSpecResult = FormatSpec::make(StringView{"{1:.543}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:.543}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 8);
@@ -287,7 +290,7 @@ TEST(FormatTest, PrecisionTest)
EXPECT_FALSE(formatSpec.takesPrecision);
EXPECT_EQ(formatSpec.precision, 543);
- formatSpecResult = FormatSpec::make(StringView{"{:.543}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{:.543}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 7);
@@ -296,16 +299,16 @@ TEST(FormatTest, PrecisionTest)
EXPECT_EQ(formatSpec.precision, 543);
// leading zero
- formatSpecResult = FormatSpec::make(StringView{"{1:00}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:00}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid);
// #x100000000 4294967296
- formatSpecResult = FormatSpec::make(StringView{"{1:.4294967296}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:.4294967296}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError);
- formatSpecResult = FormatSpec::make(StringView{"{1:.{1}}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:.{1}}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 8);
@@ -314,15 +317,15 @@ TEST(FormatTest, PrecisionTest)
EXPECT_EQ(formatSpec.precision, 1);
// #x10000 65536
- formatSpecResult = FormatSpec::make(StringView{"{1:.{65536}}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{1:.{65536}}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError);
- formatSpecResult = FormatSpec::make(StringView{"{:.{1}}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{:.{1}}"}, length);
ASSERT_TRUE(formatSpecResult.isError());
EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid);
- formatSpecResult = FormatSpec::make(StringView{"{:.{}}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{:.{}}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 6);
@@ -335,14 +338,14 @@ TEST(FormatTest, LocaleTest)
{
size_t length = 0;
- auto formatSpecResult = FormatSpec::make(StringView{"{1:L}"}, length);
+ auto formatSpecResult = FormatSpec::parse(StringView{"{1:L}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
auto formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 5);
EXPECT_EQ(formatSpec.position, 1);
EXPECT_TRUE(formatSpec.hasLocale);
- formatSpecResult = FormatSpec::make(StringView{"{:L}"}, length);
+ formatSpecResult = FormatSpec::parse(StringView{"{:L}"}, length);
ASSERT_TRUE(formatSpecResult.isOkay());
formatSpec = formatSpecResult.takeOkay();
EXPECT_EQ(length, 4);
@@ -350,4 +353,88 @@ TEST(FormatTest, LocaleTest)
EXPECT_TRUE(formatSpec.hasLocale);
}
+TEST(FormatTest, FormatTest)
+{
+ auto lengthArray{Array<size_t, 17>::constFill(0U)};
+ const Array<FormatSpecResult, 17> formatTypeResults{
+ FormatSpec::parse(StringView{"{:s}"}, lengthArray[0x00]),
+ FormatSpec::parse(StringView{"{:?}"}, lengthArray[0x01]),
+ FormatSpec::parse(StringView{"{:b}"}, lengthArray[0x02]),
+ FormatSpec::parse(StringView{"{:B}"}, lengthArray[0x03]),
+ FormatSpec::parse(StringView{"{:c}"}, lengthArray[0x04]),
+ FormatSpec::parse(StringView{"{:d}"}, lengthArray[0x05]),
+ FormatSpec::parse(StringView{"{:o}"}, lengthArray[0x06]),
+ FormatSpec::parse(StringView{"{:x}"}, lengthArray[0x07]),
+ FormatSpec::parse(StringView{"{:X}"}, lengthArray[0x08]),
+ FormatSpec::parse(StringView{"{:a}"}, lengthArray[0x09]),
+ FormatSpec::parse(StringView{"{:A}"}, lengthArray[0x0A]),
+ FormatSpec::parse(StringView{"{:e}"}, lengthArray[0x0B]),
+ FormatSpec::parse(StringView{"{:E}"}, lengthArray[0x0C]),
+ FormatSpec::parse(StringView{"{:f}"}, lengthArray[0x0D]),
+ FormatSpec::parse(StringView{"{:F}"}, lengthArray[0x0E]),
+ FormatSpec::parse(StringView{"{:g}"}, lengthArray[0x0F]),
+ FormatSpec::parse(StringView{"{:G}"}, lengthArray[0x10])};
+ const Array<FormatType, 17> formatTypes{
+ FormatType::String,
+ FormatType::Escaped,
+ FormatType::BinaryLower,
+ FormatType::BinaryUpper,
+ FormatType::Character,
+ FormatType::Decimal,
+ FormatType::Octal,
+ FormatType::HexLower,
+ FormatType::HexUpper,
+ FormatType::FloatHexLower,
+ FormatType::FloatHexUpper,
+ FormatType::ScientificLower,
+ FormatType::ScientificUpper,
+ FormatType::FixedLower,
+ FormatType::FixedUpper,
+ FormatType::GeneralLower,
+ FormatType::GeneralUpper,
+ };
+ for (size_t index = 0; index < lengthArray.size(); ++index) {
+ const auto& formatSpecResult = formatTypeResults[index];
+ const auto& length = lengthArray[index];
+ ASSERT_TRUE(formatSpecResult.isOkay());
+ auto formatSpec = formatSpecResult.getOkay();
+ EXPECT_EQ(length, 4);
+ EXPECT_EQ(formatSpec.position, FormatSpec::positionUnspecified);
+ EXPECT_EQ(formatSpec.formatType, formatTypes[index]);
+ }
+
+ size_t length = 0;
+
+ auto formatSpecResult = FormatSpec::parse(StringView{"{:N}"}, length);
+ ASSERT_TRUE(formatSpecResult.isError());
+ EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid);
+}
+
+TEST(FormatTest, ValidateSpecHelperTest)
+{
+ constexpr size_t invalid = 0xFFFF;
+ EXPECT_EQ(impl::validateSpecHelper("").getOkayOr(invalid), 0);
+ EXPECT_EQ(impl::validateSpecHelper("{}").getOkayOr(invalid), 1);
+ EXPECT_EQ(impl::validateSpecHelper("{{{}").getOkayOr(invalid), 1);
+ EXPECT_EQ(impl::validateSpecHelper("{}}}").getOkayOr(invalid), 1);
+ EXPECT_EQ(impl::validateSpecHelper("{{{}}}").getOkayOr(invalid), 1);
+ EXPECT_EQ(impl::validateSpecHelper("}").getErrorOr(FudStatus::Failure), FudStatus::FormatInvalid);
+ EXPECT_EQ(impl::validateSpecHelper("{}}").getErrorOr(FudStatus::Failure), FudStatus::FormatInvalid);
+ EXPECT_EQ(impl::validateSpecHelper("{").getErrorOr(FudStatus::Failure), FudStatus::FormatInvalid);
+ EXPECT_EQ(impl::validateSpecHelper("{{}").getErrorOr(FudStatus::Failure), FudStatus::FormatInvalid);
+ EXPECT_EQ(impl::validateSpecHelper("{{}").getOkayOr(invalid), invalid);
+ EXPECT_EQ(impl::validateSpecHelper("{{{}}} {} {{ }} {{{}}}").getOkayOr(invalid), 3);
+ EXPECT_EQ(impl::validateSpecHelper("{{{}}} {} {{ }} {{ }}}").getOkayOr(invalid), invalid);
+ EXPECT_EQ(
+ impl::validateSpecHelper("{{{}}} {} {{ }} {{{ }}").getErrorOr(FudStatus::Failure),
+ FudStatus::FormatInvalid);
+}
+
+TEST(FormatTest, OneArgFormatTest)
+{
+ String sink{};
+ auto formatResult = format(sink, FormatCharMode::Unchecked, "Test {:X}", 42U);
+ EXPECT_TRUE(formatResult.isOkay());
+}
+
} // namespace fud