summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2024-10-25 01:09:10 -0500
committerDominick Allen <djallen@librehumanitas.org>2024-10-25 01:09:10 -0500
commit11968f674a7de34fb7de744598a8086330cd88a3 (patch)
treebe5b885ef47c8ceab50f558c8aef6632fefc94d1 /include
parent512d026de016f2720060d264adec02e56123851d (diff)
Get working string formatting for unsigned numbers.
Diffstat (limited to 'include')
-rw-r--r--include/fud_fixed_vector.hpp276
-rw-r--r--include/fud_format.hpp873
-rw-r--r--include/fud_string.hpp13
-rw-r--r--include/fud_string_view.hpp6
4 files changed, 962 insertions, 206 deletions
diff --git a/include/fud_fixed_vector.hpp b/include/fud_fixed_vector.hpp
new file mode 100644
index 0000000..d0c0f0e
--- /dev/null
+++ b/include/fud_fixed_vector.hpp
@@ -0,0 +1,276 @@
+/*
+ * 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_FIXED_VECTOR_HPP
+#define FUD_FIXED_VECTOR_HPP
+
+#include "fud_assert.hpp"
+#include "fud_config.hpp"
+#include "fud_option.hpp"
+#include "fud_span.hpp"
+
+#include <cstddef>
+
+namespace fud {
+
+template <typename T, size_t Size>
+class FixedVector {
+ static_assert(Size > 0);
+ using ValueType = T;
+
+ private:
+ T m_data[Size]; // NOLINT(cppcoreguidelines-avoid-c-arrays)
+ size_t m_length;
+
+ public:
+ [[nodiscard]] constexpr size_t size() const
+ {
+ return Size;
+ }
+
+ constexpr T* data() noexcept
+ {
+ return m_data;
+ }
+
+ constexpr const T* data() const noexcept
+ {
+ return m_data;
+ }
+
+ constexpr T* begin() noexcept
+ {
+ return m_data;
+ }
+
+ constexpr const T* begin() const noexcept
+ {
+ return m_data;
+ }
+
+ constexpr T* end() noexcept
+ {
+ return m_data + Size;
+ }
+
+ constexpr const T* end() const noexcept
+ {
+ return m_data + Size;
+ }
+
+ constexpr T& operator[](size_t index)
+ {
+ if constexpr (fudBoundsChecking) {
+ fudAssert(m_data != nullptr);
+ fudAssert(index < m_length);
+ }
+ return m_data[index];
+ }
+
+ constexpr const T& operator[](size_t index) const
+ {
+ if constexpr (fudBoundsChecking) {
+ fudAssert(m_data != nullptr);
+ fudAssert(index < m_length);
+ }
+ return m_data[index];
+ }
+
+ constexpr bool operator==(const FixedVector<T, Size>&) const noexcept = default;
+
+ constexpr auto operator<=>(const FixedVector<T, Size>& other) const noexcept = default;
+
+ Span<T, Size> span()
+ {
+ return Span<T, Size>{data(), Size};
+ }
+
+ FudStatus resize(size_t count)
+ {
+ if (count == m_length) {
+ return FudStatus::Success;
+ }
+
+ if (count < m_length) {
+ m_length = count;
+ return FudStatus::Success;
+ }
+
+ for (size_t index = m_length; index < count; ++index) {
+ m_data = std::move(T());
+ }
+
+ m_length = count;
+ return FudStatus::Success;
+ }
+
+ FudStatus clear()
+ {
+ m_length = 0;
+ return FudStatus::Success;
+ }
+
+ constexpr Option<T&> front()
+ {
+ if (m_length > 0) {
+ return m_data[0];
+ }
+ return NullOpt;
+ }
+
+ constexpr Option<const T&> front() const
+ {
+ if (m_length > 0) {
+ return m_data[0];
+ }
+ return NullOpt;
+ }
+
+ constexpr Option<T&> back()
+ {
+ if (m_length > 0) {
+ return m_data[m_length - 1];
+ }
+ return NullOpt;
+ }
+
+ constexpr Option<const T&> back() const
+ {
+ if (m_length > 0) {
+ return m_data[m_length - 1];
+ }
+ return NullOpt;
+ }
+
+ FudStatus pushBack(const T& value)
+ {
+ if (m_length == Size) {
+ return FudStatus::Full;
+ }
+
+ m_data[m_length] = value;
+ m_length++;
+ return FudStatus::Success;
+ }
+
+ FudStatus pushBack(T&& value)
+ {
+ if (m_length == Size) {
+ return FudStatus::Full;
+ }
+
+ m_data[m_length] = std::move(value);
+ m_length++;
+ return FudStatus::Success;
+ }
+
+ Result<T, FudStatus> popBack()
+ {
+ if (m_data == nullptr) {
+ return FudStatus::ObjectInvalid;
+ }
+ if (m_length == 0) {
+ return FudStatus::Empty;
+ }
+ auto result{std::move(m_data[m_length - 1])};
+ m_length--;
+ return result;
+ }
+
+ FudStatus eraseBack()
+ {
+ if (m_data == nullptr) {
+ return FudStatus::ObjectInvalid;
+ }
+ if (m_length == 0) {
+ return FudStatus::Empty;
+ }
+ m_length--;
+ return FudStatus::Success;
+ }
+
+ FudStatus insert(size_t index, const T& value)
+ {
+ if (index > m_length) {
+ return FudStatus::IndexInvalid;
+ }
+ if (index == m_length) {
+ return pushBack(value);
+ }
+ if (m_length == Size) {
+ return FudStatus::Full;
+ }
+
+ m_data[m_length] = std::move(m_data[m_length - 1]);
+
+ for (size_t backIndex = m_length - 1; backIndex > index; --backIndex) {
+ m_data[backIndex] = std::move(m_data[backIndex - 1]);
+ }
+ m_data[index] = value;
+ m_length++;
+ return FudStatus::Success;
+ }
+
+ FudStatus insert(size_t index, T&& value)
+ {
+ if (index > m_length) {
+ return FudStatus::IndexInvalid;
+ }
+ if (index == m_length) {
+ return pushBack(std::move(value));
+ }
+ if (m_length == Size) {
+ return FudStatus::Full;
+ }
+
+ m_data[m_length] = std::move(m_data[m_length - 1]);
+
+ for (size_t backIndex = m_length - 1; backIndex > index; --backIndex) {
+ m_data[backIndex] = std::move(m_data[backIndex - 1]);
+ }
+ m_data[index] = std::move(value);
+ m_length++;
+ return FudStatus::Success;
+ }
+
+ FudStatus erase(size_t index)
+ {
+ if (index >= m_length) {
+ return FudStatus::IndexInvalid;
+ }
+
+ for (size_t fwdIndex = index; fwdIndex + 1 < m_length; fwdIndex++) {
+ m_data[fwdIndex] = std::move(m_data[fwdIndex + 1]);
+ }
+
+ m_length--;
+ return FudStatus::Success;
+ }
+
+ private:
+ FudStatus cleanup() noexcept
+ {
+ auto status = clear();
+
+ m_length = 0;
+ return status;
+ }
+};
+
+} // namespace fud
+
+#endif
diff --git a/include/fud_format.hpp b/include/fud_format.hpp
index 1d09422..c42cd03 100644
--- a/include/fud_format.hpp
+++ b/include/fud_format.hpp
@@ -19,6 +19,7 @@
#define FUD_FORMAT_HPP
// #include "fud_assert.hpp"
+#include "fud_array.hpp"
#include "fud_option.hpp"
#include "fud_result.hpp"
#include "fud_status.hpp"
@@ -27,12 +28,17 @@
#include "fud_string_view.hpp"
#include "fud_utf8.hpp"
+#include <concepts>
#include <cstdint>
#include <limits>
#include <variant>
namespace fud {
+// TODO: constants file?
+constexpr size_t bitsPerOctal = 3;
+constexpr size_t maxIntCharCount = bitsPerOctal * sizeof(uint64_t) + 4;
+
struct FormatString {
template <size_t N>
consteval FormatString(const char (&input)[N]) : m_size{N - 1}, m_data{input}
@@ -199,8 +205,9 @@ struct FormatSpec {
bool hasLocale : 1 = false;
- [[nodiscard]] constexpr bool takesPosition() const {
- return position == positionUnspecified;
+ [[nodiscard]] constexpr bool takesPosition() const
+ {
+ return position != positionUnspecified;
}
static Result<FormatSpec, FudStatus> parse(StringView formatView, size_t& specLength);
@@ -216,7 +223,6 @@ struct FormatHandle {
} // namespace impl
using FormatArgument = std::variant<
- std::monostate,
bool,
utf8,
int32_t,
@@ -240,6 +246,11 @@ struct FormatArguments {
return Size;
}
+ constexpr const FormatArgument& operator[](size_t index) const
+ {
+ return arguments[index];
+ }
+
template <typename... Args>
static auto makeFormatArguments(Args&&... args) -> FormatArguments
{
@@ -266,7 +277,7 @@ struct FormatResult {
};
/* TODO : require concept of a sink that takes pushBack() -> FudStatus */
-/* TODO : sink concept also requires append() -> FudStatus */
+/* TODO : sink concept also requires drain() -> FudStatus */
template <typename Sink>
FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt);
@@ -274,58 +285,48 @@ 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);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, utf8 arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, int32_t arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int32_t arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, uint32_t arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint32_t arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, int64_t arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int64_t arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, uint64_t arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint64_t arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, float arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, float arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, double arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, double arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, long double arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, long double arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, const utf8* arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, StringView arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, const void* arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const void* arg);
template <typename Sink>
-FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, impl::FormatHandle arg);
+FormatResult format(Sink& sink, FormatCharMode formatMode, const 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);
+FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, const FormatArguments<Size>& args);
} // namespace impl
@@ -339,16 +340,6 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Arg
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);
}
@@ -367,150 +358,95 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt)
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;
-}
+size_t findSpec(StringView scanView);
-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;
+template <size_t Size>
+Result<uint32_t, FudStatus> getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments<Size>& args);
- bool sawOpenBracket = false;
- bool sawCloseBracket = false;
- while (index < length) {
- auto letter = data[index];
+template <size_t Size>
+Result<FormatSpec, FudStatus> vFormatPrepareSpec(
+ StringView& scanView,
+ const FormatArguments<Size>& args,
+ const uint32_t& argIndex,
+ bool& firstSpec,
+ bool& takesPosition);
- 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;
- }
+template <size_t Size>
+Result<FormatSpec, FudStatus> vFormatPrepareFirstSpec(
+ StringView& scanView,
+ const FormatArguments<Size>& args,
+ const uint32_t& argIndex,
+ bool& takesPosition);
- if (nesting != 0 || sawOpenBracket || sawCloseBracket) {
- return FudStatus::FormatInvalid;
- }
+template <size_t Size>
+FudStatus vFormatPreparePositionalSpec(FormatSpec& formatSpec, const FormatArguments<Size>& args);
- return numGroups;
-}
+template <size_t Size>
+FudStatus vFormatPrepareNonPositionalSpec(
+ FormatSpec& formatSpec,
+ const FormatArguments<Size>& args,
+ const uint32_t& argIndex);
-// NOLINTBEGIN(readability-function-cognitive-complexity)
-Result<FormatInfo, FudStatus> validateSpecHelperV2(FormatString fmt)
+template <typename Sink, size_t Size>
+FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, const FormatArguments<Size>& args)
{
- 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;
+ 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;
+ }
- while (index < length) {
- auto inSpec = nesting > 0;
- auto letter = data[index];
+ 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;
+ }
- 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;
+ 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;
}
-
- if (nesting < 0 || nesting > 2) {
- return FudStatus::FormatInvalid;
+ 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<uint8_t>(formatSpec.takesWidth);
+ argIndex += static_cast<uint8_t>(formatSpec.takesPrecision);
}
- ++index;
- }
-
- if (nesting != 0 || sawOpenBracket || sawCloseBracket) {
- return FudStatus::FormatInvalid;
}
- return formatInfo;
+ return result;
}
-// NOLINTEND(readability-function-cognitive-complexity)
size_t findSpec(StringView scanView)
{
@@ -533,80 +469,605 @@ size_t findSpec(StringView scanView)
}
template <size_t Size>
-Result<FormatSpec, FudStatus> vFormatPrepareArgs(
+Result<uint32_t, FudStatus> getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments<Size>& args)
+{
+ static_assert(not std::is_signed_v<utf8>);
+
+ if (not formatSpec.takesPosition() || not formatSpec.takesWidth) {
+ return FudStatus::ArgumentInvalid;
+ }
+
+ if (index >= Size) {
+ return FudStatus::FormatInvalid;
+ }
+
+ if (std::holds_alternative<utf8>(args[index])) {
+ return std::get<utf8>(args[index]);
+ }
+ if (std::holds_alternative<int32_t>(args[index])) {
+ auto value = std::get<int32_t>(args[index]);
+ if (value < 0) {
+ return FudStatus::FormatInvalid;
+ }
+ return static_cast<uint32_t>(value);
+ }
+ if (std::holds_alternative<int64_t>(args[index])) {
+ auto value = std::get<int64_t>(args[index]);
+ if (value < 0 || value > std::numeric_limits<uint32_t>::max()) {
+ return FudStatus::FormatInvalid;
+ }
+ return static_cast<uint32_t>(value);
+ }
+ if (std::holds_alternative<uint32_t>(args[index])) {
+ return std::get<uint32_t>(args[index]);
+ }
+ if (std::holds_alternative<uint32_t>(args[index])) {
+ auto value = std::get<uint64_t>(args[index]);
+ if (value > std::numeric_limits<uint32_t>::max()) {
+ return FudStatus::FormatInvalid;
+ }
+ return static_cast<uint32_t>(value);
+ }
+
+ return FudStatus::FormatInvalid;
+}
+
+template <size_t Size>
+Result<FormatSpec, FudStatus> vFormatPrepareSpec(
StringView& scanView,
- FormatArguments<Size>& args,
- size_t& argIndex,
+ const FormatArguments<Size>& args,
+ const uint32_t& argIndex,
bool& firstSpec,
bool& takesPosition)
{
+ if (firstSpec) {
+ firstSpec = false;
+ return vFormatPrepareFirstSpec(scanView, args, argIndex, takesPosition);
+ }
+
size_t specLength{0};
auto formatSpecResult{FormatSpec::parse(scanView, specLength)};
if (formatSpecResult.isError()) {
return formatSpecResult;
}
fudAssert(specLength <= scanView.length());
- auto formatSpec{formatSpecResult.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) {
+ auto formatSpec{formatSpecResult.getOkay()};
+
+ if (takesPosition != formatSpec.takesPosition() || (takesPosition && formatSpec.position >= Size)) {
return FudStatus::FormatInvalid;
}
- if (formatSpec.takesWidth) {
- if (takesPosition) {
- auto widthPosition = formatSpec.width;
- if (widthPosition >= args.size()) {
- return FudStatus::FormatInvalid;
- }
+
+ if (takesPosition) {
+ auto status = vFormatPreparePositionalSpec(formatSpec, args);
+ if (status != FudStatus::Success) {
+ return status;
+ }
+ } else {
+ auto status = vFormatPrepareNonPositionalSpec(formatSpec, args, argIndex);
+ if (status != FudStatus::Success) {
+ return status;
}
}
- // auto formatResult{format(sink,
- scanView.advanceUnsafe(specLength);
+
+ scanView.advanceUnsafe(specLength);
return formatSpec;
}
-template <typename Sink, size_t Size>
-FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, FormatArguments<Size>& args)
+template <size_t Size>
+Result<FormatSpec, FudStatus> vFormatPrepareFirstSpec(
+ StringView& scanView,
+ const FormatArguments<Size>& args,
+ const uint32_t& argIndex,
+ bool& takesPosition)
{
- 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);
+ 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) {
- result.status = status;
- return result;
+ return status;
}
- result.bytesWritten += specIndex;
- scanView.advanceUnsafe(specIndex);
- if (specIndex == scanView.length()) {
- return result;
+ } else {
+ auto status = vFormatPrepareNonPositionalSpec(formatSpec, args, argIndex);
+ if (status != FudStatus::Success) {
+ return status;
}
+ }
+
+ scanView.advanceUnsafe(specLength);
+
+ return formatSpec;
+}
- auto specResult{vFormatPrepareArgs(scanView, args, argIndex, firstSpec, takesPosition)};
+template <size_t Size>
+FudStatus vFormatPreparePositionalSpec(FormatSpec& formatSpec, const FormatArguments<Size>& 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 <size_t Size>
+FudStatus vFormatPrepareNonPositionalSpec(
+ FormatSpec& formatSpec,
+ const FormatArguments<Size>& 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 <typename T>
+ requires std::is_integral_v<T>
+[[nodiscard]] StringView validateFormatType(
+ FormatResult& result,
+ FormatType formatType,
+ Radix& radix,
+ bool& uppercase,
+ T arg)
+{
+ StringView prefix{""};
+ switch (formatType) {
+ case FormatType::Unspecified:
+ break;
+ case FormatType::BinaryLower:
+ prefix = StringView{"0b"};
+ radix = Radix::Binary;
+ break;
+ case FormatType::BinaryUpper:
+ prefix = StringView{"0B"};
+ radix = Radix::Binary;
+ uppercase = true;
+ break;
+ case FormatType::Character:
+ if constexpr (std::is_signed_v<T>) {
+ if (arg < 0) {
+ result.status = FudStatus::FormatInvalid;
+ }
+ }
+ if (arg > std::numeric_limits<utf8>::max()) {
+ result.status = FudStatus::FormatInvalid;
+ break;
+ }
+ break;
+ case FormatType::Decimal:
+ break;
+ case FormatType::Octal:
+ prefix = StringView{"o"};
+ radix = Radix::Octal;
+ break;
+ case FormatType::HexLower:
+ prefix = StringView{"0x"};
+ radix = Radix::Hexadecimal;
+ break;
+ case FormatType::HexUpper:
+ prefix = StringView{"0X"};
+ radix = Radix::Hexadecimal;
+ uppercase = true;
+ break;
+ case FormatType::FloatHexLower:
+ case FormatType::FloatHexUpper:
+ case FormatType::String:
+ case FormatType::Escaped:
+ case FormatType::ScientificLower:
+ case FormatType::ScientificUpper:
+ case FormatType::FixedLower:
+ case FormatType::FixedUpper:
+ case FormatType::GeneralLower:
+ case FormatType::GeneralUpper:
+ result.status = FudStatus::FormatInvalid;
+ default:
+ result.status = FudStatus::Failure;
+ }
+ return prefix;
+}
+
+inline FudStatus fillUnsignedBuffer(
+ Array<utf8, maxIntCharCount>& buffer,
+ uint64_t value,
+ uint8_t& bufferLength,
+ Radix radix,
+ bool uppercase)
+{
+ static_assert(maxIntCharCount < std::numeric_limits<uint8_t>::max());
+ bufferLength = 0;
+
+ constexpr Array<uint8_t, static_cast<size_t>(Radix::Hexadecimal)> lower{
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}};
+ constexpr Array<uint8_t, static_cast<size_t>(Radix::Hexadecimal)> upper{
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}};
+ const auto& lookup = uppercase ? upper : lower;
+
+ if (value == 0) {
+ buffer[0] = '0';
+ bufferLength = 1;
+ return FudStatus::Success;
+ }
+
+ static_assert(std::is_same_v<std::underlying_type_t<Radix>, uint8_t>);
+ auto radixValue = static_cast<uint8_t>(radix);
+ while (value > 0 && bufferLength < maxIntCharCount) {
+ auto digit = static_cast<uint8_t>(value % radixValue);
+ buffer[bufferLength] = lookup[digit];
+ value /= radixValue;
+ bufferLength++;
+ }
+
+ if (value > 0 || bufferLength > maxIntCharCount) {
+ return FudStatus::Failure;
+ }
+
+ // TODO: implement fud_algorithm reverse
+ for (size_t idx = 0; idx < bufferLength / 2; ++idx) {
+ auto rhsIndex = bufferLength - idx - 1;
+ std::swap(buffer[idx], buffer[rhsIndex]);
+ }
+
+ return FudStatus::Success;
+}
+
+template <typename Sink>
+[[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<utf8, BigBlockSize>::constFill(fill);
+
+ constexpr auto bufferSizes{Array<size_t, 4>{{BigBlockSize, MediumBlockSize, SmallBlockSize, SmallestBlockSize}}};
+
+ for (auto bufferSize : bufferSizes) {
+ while (count > bufferSize) {
+ auto drainResult = sink.drain(StringView{bufferSize, backingBuffer.data()});
+ result.bytesWritten += drainResult.bytesWritten;
+ result.status = drainResult.status;
+ if (result.status != FudStatus::Success) {
+ return result;
+ }
+ count -= result.bytesWritten;
+ }
+ }
+
+ if (count > 0) {
+ fudAssert(count < backingBuffer.size());
+ auto drainResult = sink.drain(StringView{count, backingBuffer.data()});
+ result.bytesWritten += drainResult.bytesWritten;
+ result.status = drainResult.status;
+ }
+
+ return result;
+}
+
+template <typename Sink, typename T>
+ requires std::is_integral_v<T> and std::is_scalar_v<T> /* and std::is_unsigned_v<T> */
+[[nodiscard]] FormatResult formatUnsigned(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg)
+{
+ FormatResult result{0, FudStatus::Success};
+ if (formatSpec.takesPrecision || formatSpec.precision != FormatSpec::precisionUnspecified) {
+ result.status = FudStatus::FormatInvalid;
+ return result;
+ }
+ auto fill = formatSpec.fill.fill;
+ auto align = formatSpec.fill.align.value;
+ bool padZero = false;
+ if (align == FormatAlign::Value::Default) {
+ align = FormatAlign::Value::Right;
+ fill = formatSpec.leadingZero ? '0' : fill;
+ padZero = formatSpec.leadingZero;
+ }
+ auto width = formatSpec.width;
+ if (width == FormatSpec::widthUnspecified) {
+ width = 0;
+ }
+
+ auto radix = Radix::Decimal;
+ bool uppercase = false;
+
+ auto prefix = validateFormatType(result, formatSpec.formatType, radix, uppercase, arg);
+ if (result.status != FudStatus::Success) {
+ return result;
+ }
+
+ if (formatSpec.formatType == FormatType::Character) {
+ return format(sink, formatMode, formatSpec, static_cast<utf8>(arg));
+ }
+
+ if (not formatSpec.alternateForm) {
+ prefix = StringView{""};
+ }
+
+ auto calculatedWidth = prefix.length();
+
+ Array<utf8, maxIntCharCount> buffer{};
+ uint8_t bufferLength = 0;
+
+ // TODO: constexpr if for signed values
+ result.status = fillUnsignedBuffer(buffer, static_cast<uint64_t>(arg), bufferLength, radix, uppercase);
+
+ if (result.status != FudStatus::Success) {
+ return result;
+ }
+
+ calculatedWidth += bufferLength;
+
+ StringView sign{""};
+ switch (formatSpec.formatSign) {
+ case FormatSign::Plus:
+ sign = StringView{"+"};
+ break;
+ case FormatSign::Space:
+ sign = StringView{" "};
+ break;
+ case FormatSign::Minus:
+ case FormatSign::Default:
+ default:
+ if constexpr (std::is_signed_v<T>) {
+ if (arg < 0) {
+ sign = StringView{"-"};
+ }
+ }
+ break;
+ }
+ calculatedWidth += sign.length();
+
+ if (align == FormatAlign::Value::Right and width > calculatedWidth) {
+ auto leftPadSize = width - calculatedWidth;
+ auto padResult = fillPad(sink, fill, leftPadSize);
+ result.bytesWritten += padResult.bytesWritten;
+ result.status = padResult.status;
+ } else if (align == FormatAlign::Value::Center and width > calculatedWidth) {
+ auto leftPadSize = (width - calculatedWidth) / 2;
+ auto padResult = fillPad(sink, fill, leftPadSize);
+ result.bytesWritten += padResult.bytesWritten;
+ result.status = padResult.status;
+ }
+
+ if (result.status != FudStatus::Success) {
+ return result;
+ }
+
+ if (sign.length() > 0) {
+ auto drainResult = sink.drain(sign);
+ result.bytesWritten += drainResult.bytesWritten;
+ result.status = drainResult.status;
+ }
+
+ if (result.status != FudStatus::Success) {
+ return result;
+ }
+
+ if (prefix.length() > 0) {
+ auto drainResult = sink.drain(sign);
+ result.bytesWritten += drainResult.bytesWritten;
+ result.status = drainResult.status;
+ }
+
+ if (result.status != FudStatus::Success) {
+ return result;
+ }
+
+ if (padZero) {
+ auto leftPadSize = width - calculatedWidth;
+ auto padResult = fillPad(sink, '0', leftPadSize);
+ result.bytesWritten += padResult.bytesWritten;
+ result.status = padResult.status;
+ }
+
+ if (result.status != FudStatus::Success) {
+ return result;
+ }
+
+ auto drainNumberResult = sink.drain(StringView{bufferLength, buffer.data()});
+ result.bytesWritten += drainNumberResult.bytesWritten;
+ result.status = drainNumberResult.status;
+
+ if (result.status != FudStatus::Success) {
+ return result;
+ }
+
+ if (align == FormatAlign::Value::Left and width > calculatedWidth) {
+ auto rightPadSize = width - calculatedWidth;
+ auto padResult = fillPad(sink, fill, rightPadSize);
+ result.bytesWritten += padResult.bytesWritten;
+ result.status = padResult.status;
+ } else if (align == FormatAlign::Value::Center and width > calculatedWidth) {
+ auto leftPadSize = (width - calculatedWidth) / 2;
+ auto rightPadSize = width - leftPadSize;
+ auto padResult = fillPad(sink, fill, rightPadSize);
+ result.bytesWritten += padResult.bytesWritten;
+ result.status = padResult.status;
}
- static_cast<void>(formatMode);
- static_cast<void>(args);
return result;
}
} // namespace impl
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int32_t arg)
+{
+ return impl::formatUnsigned(sink, formatMode, formatSpec, arg);
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint32_t arg)
+{
+ return impl::formatUnsigned(sink, formatMode, formatSpec, arg);
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int64_t arg)
+{
+ return impl::formatUnsigned(sink, formatMode, formatSpec, arg);
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint64_t arg)
+{
+ return impl::formatUnsigned(sink, formatMode, formatSpec, arg);
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, float arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, double arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, long double arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const void* arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
+template <typename Sink>
+FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, impl::FormatHandle arg)
+{
+ FormatResult result{0, FudStatus::NotImplemented};
+ static_cast<void>(sink);
+ static_cast<void>(formatMode);
+ static_cast<void>(formatSpec);
+ static_cast<void>(arg);
+ return result;
+}
+
namespace fudTest {
inline void test()
{
+ StringView prefix{0, ""};
String sink{};
static_cast<void>(format(sink, FormatCharMode::Unchecked, "HELL YEAH"));
diff --git a/include/fud_string.hpp b/include/fud_string.hpp
index 60a328f..0213524 100644
--- a/include/fud_string.hpp
+++ b/include/fud_string.hpp
@@ -38,6 +38,11 @@ constexpr size_t SSO_BUF_SIZE = SSO_BUF_LENGTH + 1;
using StringResult = Result<String, FudStatus>;
+struct DrainResult {
+ size_t bytesWritten;
+ FudStatus status;
+};
+
class String {
public:
static StringResult makeFromCString(const char* cString);
@@ -212,12 +217,20 @@ class String {
FudStatus append(StringView source);
+ DrainResult drain(const char* source);
+
+ DrainResult drain(const String& source);
+
+ DrainResult drain(StringView source);
+
[[nodiscard]] StringResult catenate(const String& rhs) const;
[[nodiscard]] StringResult catenate(const char* rhs) const;
[[nodiscard]] bool compare(const String& rhs) const;
+ FudStatus clear();
+
const utf8* begin() const;
const utf8* end() const;
diff --git a/include/fud_string_view.hpp b/include/fud_string_view.hpp
index 2ced639..718b3d7 100644
--- a/include/fud_string_view.hpp
+++ b/include/fud_string_view.hpp
@@ -45,6 +45,12 @@ struct StringView {
{
}
+ template <size_t N>
+ StringView(const char (&input)[N]) : m_length{N - 1}, m_data{reinterpret_cast<const utf8*>(input)}
+ {
+ static_assert(N > 0);
+ }
+
StringView(size_t strLen, const char* strData) :
m_length(strLen), // line break
m_data{reinterpret_cast<const utf8*>(strData)} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)