Skip to content

Commit

Permalink
eliminate rune::iter
Browse files Browse the repository at this point in the history
  • Loading branch information
mcy committed Jun 26, 2024
1 parent 48fd00f commit 006641a
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 291 deletions.
2 changes: 1 addition & 1 deletion best/math/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ cc_library(
":overflow",
"//best/container:result",
"//best/meta:guard",
"//best/text:rune",
"//best/text:str",
]
)

Expand Down
86 changes: 46 additions & 40 deletions best/math/conv.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#include "best/math/int.h"
#include "best/math/overflow.h"
#include "best/meta/guard.h"
#include "best/text/rune.h"
#include "best/text/str.h"

//! Number-string conversion primitives.

Expand All @@ -43,36 +43,38 @@ struct atoi_error final {
template <best::integer Int>
constexpr best::result<Int, best::atoi_error> atoi(const string_type auto &str,
uint32_t radix = 10) {
// Adapted slightly from the implementation found in Rust's from_str_radix().
if constexpr (best::is_pretext<decltype(str)>) {
// Adapted slightly from the implementation found in Rust's
// from_str_radix().

if (radix > 36) {
crash_internal::crash("from_digit() radix too large: %u > 36", radix);
}
if (radix > 36) {
crash_internal::crash("from_digit() radix too large: %u > 36", radix);
}

best::rune::iter runes(str);
auto next = runes.next();
auto runes = str.runes();
auto next = runes.next();

bool neg = false;
if (next == '-') {
neg = true;
next = runes.next();
} else if (next == '+') {
next = runes.next();
}
bool neg = false;
if (next == '-') {
neg = true;
next = runes.next();
} else if (next == '+') {
next = runes.next();
}

if (!next) return best::atoi_error{};
if (!next) return best::atoi_error{};

// We make an approximation that the number of digits provided by `str` is
// no larger than its length in code units. The greatest information density
// is when the radix is 16; in this case, if the length is less than or equal
// to the maximum number if nybbles, it will not overflow. However, if it
// is a signed type, we need to subtract off one extra code unit, since
// e.g. `80` will overflow `int8_t`.
size_t total_codes = best::size(str);
size_t maximum_codes_without_overflow =
sizeof(Int) * 2 - best::signed_int<Int>;
size_t cannot_overflow =
radix <= 16 && total_codes <= maximum_codes_without_overflow;
// We make an approximation that the number of digits provided by `str` is
// no larger than its length in code units. The greatest information density
// is when the radix is 16; in this case, if the length is less than or
// equal to the maximum number if nybbles, it will not overflow. However, if
// it is a signed type, we need to subtract off one extra code unit, since
// e.g. `80` will overflow `int8_t`.
size_t total_codes = best::size(str);
size_t maximum_codes_without_overflow =
sizeof(Int) * 2 - best::signed_int<Int>;
size_t cannot_overflow =
radix <= 16 && total_codes <= maximum_codes_without_overflow;

#define BEST_ATOI_LOOP_(result_, op_) \
do { \
Expand All @@ -82,15 +84,15 @@ constexpr best::result<Int, best::atoi_error> atoi(const string_type auto &str,
result_ op_ *digit; \
} while ((next = runes.next()))

if (cannot_overflow) {
Int result = 0;
if (neg) {
BEST_ATOI_LOOP_(result, -=);
} else {
BEST_ATOI_LOOP_(result, +=);
if (cannot_overflow) {
Int result = 0;
if (neg) {
BEST_ATOI_LOOP_(result, -=);
} else {
BEST_ATOI_LOOP_(result, +=);
}
return result;
}
return result;
}

#undef BEST_ATOI_LOOP_
#define BEST_ATOI_LOOP_(result_, op_) \
Expand All @@ -102,15 +104,19 @@ constexpr best::result<Int, best::atoi_error> atoi(const string_type auto &str,
BEST_GUARD(result_.checked().ok_or(best::atoi_error{})); \
} while ((next = runes.next()))

best::overflow<Int> result = 0;
if (neg) {
BEST_ATOI_LOOP_(result, -=);
best::overflow<Int> result = 0;
if (neg) {
BEST_ATOI_LOOP_(result, -=);
} else {
BEST_ATOI_LOOP_(result, +=);
}
return result.wrap();

#undef BEST_ATOI_LOOP_
} else {
BEST_ATOI_LOOP_(result, +=);
return best::atoi<Int>(best::pretext(str), radix);
}
return result.wrap();
}
#undef BEST_ATOI_LOOP_
} // namespace best

#endif // BEST_MATH_CONV_H_
66 changes: 0 additions & 66 deletions best/text/encoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,72 +193,6 @@ template <string_type S1, string_type S2>
constexpr bool same_encoding_code() {
return best::same<code<encoding_type<S1>>, code<encoding_type<S2>>>;
}

template <typename rune = best::rune>
void BestFmt(auto& fmt, const best::string_type auto& str) {
// Taken liberally from Rust's implementation of Formatter::pad().

using iter = rune::template iter<best::encoding_type<decltype(str)>>;

if (fmt.current_spec().method == 'q' || fmt.current_spec().debug) {
// Quoted string.
fmt.write('"');
for (rune r : iter(str)) {
fmt.format("{}", r.escaped());
}
fmt.write('"');
return;
}

const auto& spec = fmt.current_spec();
if (spec.width == 0 && !spec.prec) {
// Fast path.
fmt.write(str);
return;
}

iter it(str);
auto data = it.rest();

if (auto prec = spec.prec) {
size_t max = *prec;
size_t end = 0;
for (auto r : it) {
if (--max == 0) break;
end += r.size(best::encoding_of(str)).ok().value_or(1);
}
data = data[{.end = end}];
}

if (spec.width == 0) {
// No need to pad here!
fmt.write(data, best::encoding_of(str));
return;
}

// Otherwise, we need to figure out the number of characters and potentially
// write some padding.
size_t runes = 0;
for (auto ignored : iter(str)) ignored, ++runes;
if (runes >= spec.width) {
// No need to pad here either!
fmt.write(data, best::encoding_of(str));
return;
}

auto fill = fmt.current_spec().fill;
auto [pre, post] =
fmt.current_spec().compute_padding(runes, fmt.current_spec().Left);
for (size_t i = 0; i < pre; ++i) fmt.write(fill);
fmt.write(data, best::encoding_of(str));
for (size_t i = 0; i < post; ++i) fmt.write(fill);
}
constexpr void BestFmtQuery(auto& query, best::string_type auto*) {
query.requires_debug = false;
query.supports_width = true;
query.supports_prec = true;
query.uses_method = [](auto r) { return r == 'q'; };
}
} // namespace best

#endif // BEST_TEXT_ENCODING_H_
65 changes: 30 additions & 35 deletions best/text/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,6 @@ class formatter final {
/// transcoded as-needed.
void write(rune r);
void write(const best::string_type auto& string);
template <best::encoding E>
void write(best::span<const code<E>> data, const E& enc);

/// # `formatter::format()`
///
Expand Down Expand Up @@ -405,12 +403,14 @@ decltype(auto) make_formattable(const auto& value);
* ////////////////// !!! IMPLEMENTATION DETAILS BELOW !!! ////////////////// *
\* ////////////////////////////////////////////////////////////////////////// */

namespace best {
void formatter::write(const best::string_type auto& string) {
rune::iter it(string);
write(it.rest(), best::encoding_of(string));
}
#include "best/text/internal/format_impls.h"

// Silence a tedious clang-tidy warning.
namespace best::format_internal {
using mark_as_used2 = mark_as_used;
} // namespace best::format_internal

namespace best {
void formatter::format(const best::format_spec& spec,
const best::formattable auto& arg) {
if (spec.pass_through) {
Expand All @@ -422,30 +422,32 @@ void formatter::format(const best::format_spec& spec,
cur_spec_ = old;
}

template <best::encoding E>
void formatter::write(best::span<const code<E>> data, const E& enc) {
if (indent_ == 0) {
out_->push_lossy(data, enc);
return;
}
void formatter::write(const best::string_type auto& string) {
if constexpr (best::is_pretext<decltype(string)>) {
if (indent_ == 0) {
out_->push_lossy(string);
return;
}

rune::iter runes(data, enc);
size_t idx = 0;
size_t watermark = 0;
for (rune r : runes) {
idx += r.size(enc).ok().value_or(1);
if (r != '\n') continue;
if (idx != watermark + 1) {
size_t watermark = 0;
for (auto [idx, r] : string.rune_indices()) {
if (r != '\n') continue;
if (idx != watermark + 1) {
update_indent();
out_->push_lossy(string[{.start = watermark, .end = idx - 1}]);
}

watermark = idx;
out_->push_lossy("\n");
at_new_line_ = true;
}

if (watermark < string.size()) {
update_indent();
out_->push_lossy(data[{.start = watermark, .end = idx - 1}], enc);
out_->push_lossy(string[{.start = watermark}]);
}
watermark = idx;
out_->push_lossy("\n");
at_new_line_ = true;
}
if (watermark != data.size()) {
update_indent();
out_->push_lossy(data[{.start = watermark}], enc);
} else {
write(best::pretext(string));
}
}

Expand Down Expand Up @@ -572,11 +574,4 @@ void eprintln(best::format_template<Args...> templ, const Args&... args) {
}
} // namespace best

#include "best/text/internal/format_impls.h"

// Silence a tedious clang-tidy warning.
namespace best::format_internal {
using mark_as_used2 = mark_as_used;
} // namespace best::format_internal

#endif // BEST_TEXT_FORMAT_H_
64 changes: 64 additions & 0 deletions best/text/internal/format_impls.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,70 @@ constexpr void BestFmtQuery(auto& query, integer auto*) {
query.uses_method = [](auto r) { return str("boxX").contains(r); };
}

void BestFmt(auto& fmt, const best::string_type auto& s) {
// Taken liberally from Rust's implementation of Formatter::pad().
if constexpr (best::is_pretext<decltype(s)>) {
auto str = s;
if (fmt.current_spec().method == 'q' || fmt.current_spec().debug) {
// Quoted string.
fmt.write('"');
for (rune r : str.runes()) {
fmt.format("{}", r.escaped());
}
fmt.write('"');
return;
}

const auto& spec = fmt.current_spec();
if (spec.width == 0 && !spec.prec) {
// Fast path.
fmt.write(str);
return;
}

if (auto prec = spec.prec) {
size_t max = *prec;
auto runes = str.runes();
for (auto r : runes) {
(void)r;
if (--max == 0) break;
}
str = str[{.end = str.size() - runes->rest().size()}];
}

if (spec.width == 0) {
// No need to pad here!
fmt.write(str);
return;
}

// Otherwise, we need to figure out the number of characters and potentially
// write some padding.
size_t runes = str.runes().count();
if (runes >= spec.width) {
// No need to pad here either!
fmt.write(str);
return;
}

auto fill = fmt.current_spec().fill;
auto [pre, post] =
fmt.current_spec().compute_padding(runes, fmt.current_spec().Left);
for (size_t i = 0; i < pre; ++i) fmt.write(fill);
fmt.write(str);
for (size_t i = 0; i < post; ++i) fmt.write(fill);
} else {
BestFmt(fmt, best::pretext(s));
}
}

constexpr void BestFmtQuery(auto& query, best::string_type auto*) {
query.requires_debug = false;
query.supports_width = true;
query.supports_prec = true;
query.uses_method = [](auto r) { return r == 'q'; };
}

// TODO: invent ranges/iterator traits.
template <typename R>
void BestFmt(auto& fmt, const R& range)
Expand Down
Loading

0 comments on commit 006641a

Please sign in to comment.