summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/fud_format.hpp531
-rw-r--r--include/fud_result.hpp32
-rw-r--r--include/fud_string_view.hpp2
3 files changed, 483 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
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