#include "harness.h"
+#include "json.h"
#include <atomic>
#include <chrono>
#include <cmath>
#include <shared_mutex>
#include <thread>
#include <variant>
+#include <vector>
#ifdef NDEBUG // assert needs to work even in release mode
#undef NDEBUG
}
};
-void BenchHarnessBase::base_run(
- const Config &config,
+struct BenchmarkResultInner final
+{
+ struct Result final
+ {
+ std::chrono::duration<double> total_dur;
+ std::chrono::duration<double> iter_dur;
+ operator JsonValue() const
+ {
+ return JsonValue::Object{
+ {"total_dur", total_dur.count()},
+ {"iter_dur", iter_dur.count()},
+ };
+ }
+ };
+ std::string name;
+ JsonValue config_json;
+ std::uint64_t iteration_count;
+ Result average;
+ std::vector<Result> threads;
+};
+
+struct BenchmarkResultImpl final : public BenchmarkResult
+{
+ BenchmarkResultInner inner;
+ BenchmarkResultImpl(BenchmarkResultInner inner) : inner(std::move(inner))
+ {
+ }
+ virtual void print() const override
+ {
+ std::cout << inner.name << ":\n";
+ if (inner.threads.size() > 1)
+ {
+ for (std::size_t i = 0; i < inner.threads.size(); i++)
+ {
+ std::cout << "Thread #" << i << " took "
+ << WriteDuration{inner.threads[i].total_dur}
+ << " for " << inner.iteration_count
+ << " iterations -- "
+ << WriteDuration{inner.threads[i].iter_dur}
+ << "/iter.\n";
+ }
+ }
+ std::cout << "Average elapsed time: "
+ << WriteDuration{inner.average.total_dur} << " for "
+ << inner.iteration_count << " iterations -- "
+ << WriteDuration{inner.average.iter_dur} << "/iter.\n"
+ << std::endl;
+ }
+ virtual operator JsonValue() const override
+ {
+ return JsonValue::Object{
+ {"name", inner.name},
+ {"config", inner.config_json},
+ {"iteration_count", inner.iteration_count},
+ {"average", inner.average},
+ {"threads", inner.threads},
+ };
+ }
+};
+
+std::shared_ptr<BenchmarkResult> BenchHarnessBase::base_run(
+ const Config &config, const std::string &name,
void (*fn)(BenchHarnessBase *bench_harness_base,
std::uint64_t iteration_count, std::uint32_t thread_num))
{
if (config.iteration_count)
{
iteration_count = *config.iteration_count;
+ assert(iteration_count > 0);
run(iteration_count);
}
else
total_elapsed += i;
}
auto target_average_elapsed = std::chrono::milliseconds(500);
- if (total_elapsed > thread_count * target_average_elapsed)
+ if (total_elapsed > thread_count * target_average_elapsed ||
+ iteration_count >= (1ULL << 63))
{
break;
}
}
}
steady_clock::duration total_elapsed{};
+ BenchmarkResultInner retval = {
+ .name = name,
+ .config_json = config,
+ .iteration_count = iteration_count,
+ .average = {},
+ .threads = {},
+ };
for (std::uint32_t thread_num = 0; thread_num < thread_count; thread_num++)
{
total_elapsed += elapsed[thread_num];
- if (thread_count > 1)
- {
- auto dur = std::chrono::duration<double>(elapsed[thread_num]);
- std::cout << "Thread #" << thread_num << " took "
- << WriteDuration{dur} << " for " << iteration_count
- << " iterations -- "
- << WriteDuration{dur / iteration_count} << "/iter.\n";
- }
+ auto dur = std::chrono::duration<double>(elapsed[thread_num]);
+ retval.threads.push_back({
+ .total_dur = dur,
+ .iter_dur = dur / iteration_count,
+ });
}
auto total = std::chrono::duration<double>(total_elapsed);
- std::cout << "Average elapsed time: "
- << WriteDuration{total / thread_count} << " for "
- << iteration_count << " iterations -- "
- << WriteDuration{total / thread_count / iteration_count}
- << "/iter.\n"
- << std::endl;
+ retval.average = {
+ .total_dur = total / thread_count,
+ .iter_dur = total / thread_count / iteration_count,
+ };
+ return std::make_shared<BenchmarkResultImpl>(retval);
}
std::shared_ptr<void> BenchHarnessBase::get_thread_cache()
#pragma once
+#include "json.h"
#include <cstdint>
#include <functional>
#include <memory>
std::uint32_t log2_stride = 0;
static constexpr std::uint32_t max_sum_log2_mem_loc_count_and_stride = 28;
bool use_json = false;
+ operator JsonValue() const
+ {
+ return JsonValue::Object{
+ {"thread_count", thread_count},
+ {"iteration_count", iteration_count},
+ {"log2_memory_location_count", log2_memory_location_count},
+ {"log2_stride", log2_stride},
+ {"use_json", use_json},
+ };
+ }
};
template <typename Fn, typename Input> class BenchHarness;
+struct BenchmarkResult
+{
+ BenchmarkResult() = default;
+ virtual ~BenchmarkResult() = default;
+ virtual void print() const = 0;
+ virtual operator JsonValue() const = 0;
+};
+
class BenchHarnessBase
{
template <typename Fn, typename Input> friend class BenchHarness;
std::shared_ptr<void> thread_cache;
class ThreadCache;
friend class ThreadCache;
- void base_run(const Config &config,
- void (*fn)(BenchHarnessBase *bench_harness_base,
- std::uint64_t iteration_count,
- std::uint32_t thread_num));
+ std::shared_ptr<BenchmarkResult> base_run(
+ const Config &config, const std::string &name,
+ void (*fn)(BenchHarnessBase *bench_harness_base,
+ std::uint64_t iteration_count, std::uint32_t thread_num));
public:
static std::shared_ptr<void> get_thread_cache();
: fn(std::move(fn)), input(std::move(input))
{
}
- void run(const Config &config)
+ std::shared_ptr<BenchmarkResult> run(const Config &config,
+ const std::string &name)
{
- base_run(config, [](BenchHarnessBase *bench_harness_base,
- std::uint64_t iteration_count,
- std::uint32_t thread_num) {
- auto self = static_cast<BenchHarness *>(bench_harness_base);
- auto &fn = self->fn;
- // copy for repeatability, also so optimization barrier is on copy,
- // not self
- auto input = self->input;
- for (std::uint64_t i = 0; i < iteration_count; i++)
- {
- // optimization barrier
- asm("" : : "r"(std::addressof(input)) : "memory");
-
- if constexpr (std::is_void_v<std::invoke_result_t<
- Fn &, Input, decltype(i),
- decltype(thread_num)>>)
+ return base_run(
+ config, name,
+ [](BenchHarnessBase *bench_harness_base,
+ std::uint64_t iteration_count, std::uint32_t thread_num) {
+ auto self = static_cast<BenchHarness *>(bench_harness_base);
+ auto &fn = self->fn;
+ // copy for repeatability, also so optimization barrier is on
+ // copy, not self
+ auto input = self->input;
+ for (std::uint64_t i = 0; i < iteration_count; i++)
{
- fn(input, i, thread_num);
- }
- else
- {
- auto output = fn(input, i, thread_num);
-
// optimization barrier
- asm("" : : "r"(std::addressof(output)) : "memory");
+ asm("" : : "r"(std::addressof(input)) : "memory");
+
+ if constexpr (std::is_void_v<std::invoke_result_t<
+ Fn &, Input, decltype(i),
+ decltype(thread_num)>>)
+ {
+ fn(input, i, thread_num);
+ }
+ else
+ {
+ auto output = fn(input, i, thread_num);
+
+ // optimization barrier
+ asm("" : : "r"(std::addressof(output)) : "memory");
+ }
}
- }
- });
+ });
}
};
{
private:
std::string m_name;
- std::function<void(const Config &config)> m_run;
+ std::function<std::shared_ptr<BenchmarkResult>(const Config &config,
+ const std::string &name)>
+ m_run;
public:
template <typename Fn, typename Input>
explicit Benchmark(Fn fn, Input input, std::string name)
- : m_name(std::move(name)), m_run([fn, input](const Config &config) {
- return BenchHarness(std::move(fn), std::move(input)).run(config);
+ : m_name(std::move(name)),
+ m_run([fn, input](const Config &config, const std::string &name) {
+ return BenchHarness(std::move(fn), std::move(input))
+ .run(config, name);
})
{
}
- void run(const Config &config)
+ std::shared_ptr<BenchmarkResult> run(const Config &config) const
{
- return m_run(config);
+ return m_run(config, m_name);
}
const std::string &name() const
{
#pragma once
+#include <cassert>
#include <charconv>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
+#include <exception>
+#include <functional>
#include <initializer_list>
#include <ios>
+#include <iterator>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
#include <system_error>
+#include <type_traits>
#include <unordered_map>
#include <variant>
#include <vector>
-struct JsonValue;
+enum class ResolveResult : bool
+{
+ Finished = false,
+ MoreToResolve = true,
+};
+
+template <typename T> class LazyVec final
+{
+ private:
+ struct Internals
+ {
+ std::vector<T> resolved_values;
+ explicit Internals(std::vector<T> &&resolved_values)
+ : resolved_values(std::move(resolved_values))
+ {
+ }
+ explicit Internals(std::initializer_list<T> resolved_values = {})
+ : resolved_values(resolved_values)
+ {
+ }
+ virtual ~Internals() = default;
+ virtual ResolveResult resolve_more()
+ {
+ return ResolveResult::Finished;
+ }
+ };
+ template <
+ typename Iterator, typename Sentinel,
+ typename = std::enable_if_t<
+ std::is_base_of_v<
+ std::input_iterator_tag,
+ typename std::iterator_traits<Iterator>::iterator_category> &&
+ std::is_same_v<
+ const typename std::iterator_traits<Iterator>::value_type,
+ const T>>>
+ struct LazyInternalsIter final : public Internals
+ {
+ struct State final
+ {
+ Iterator cur;
+ Sentinel end;
+ State(Iterator &&cur, Sentinel &&end) noexcept
+ : cur(std::move(cur)), end(std::move(end))
+ {
+ }
+ };
+ std::optional<State> state;
+ LazyInternalsIter(Iterator &&cur, Sentinel &&end) noexcept
+ : state(std::in_place, std::move(cur), std::move(end))
+ {
+ if (state->cur == state->end)
+ {
+ state = std::nullopt;
+ }
+ }
+ LazyInternalsIter(const LazyInternalsIter &) = delete;
+ LazyInternalsIter &operator=(const LazyInternalsIter &) = delete;
+ virtual ResolveResult resolve_more() override
+ {
+ if (!state)
+ {
+ return ResolveResult::Finished;
+ }
+ this->resolved_values.emplace_back(*state->cur++);
+ if (state->cur == state->end)
+ {
+ state = std::nullopt;
+ return ResolveResult::Finished;
+ }
+ return ResolveResult::MoreToResolve;
+ }
+ };
+ template <
+ typename Container,
+ typename Iterator = decltype(std::begin(std::declval<Container>())),
+ typename Sentinel = decltype(std::end(std::declval<Container>())),
+ typename = decltype(LazyInternalsIter<Iterator, Sentinel>::cur)>
+ struct LazyInternalsContainer final : public Internals
+ {
+ struct State final
+ {
+ Container container;
+ Iterator cur; // must come after container
+ Sentinel end; // must come after container
+ explicit State(Container &&container) noexcept
+ : container(std::move(container)),
+ cur(std::begin(this->container)),
+ end(std::begin(this->container))
+ {
+ }
+ State(const State &) = delete;
+ State &operator=(const State &) = delete;
+ };
+ std::optional<State> state;
+ explicit LazyInternalsContainer(Container &&container) noexcept
+ : state(std::in_place, std::move(container))
+ {
+ if (state->cur == state->end)
+ {
+ state = std::nullopt;
+ }
+ }
+ virtual ResolveResult resolve_more() override
+ {
+ if (!state)
+ {
+ return ResolveResult::Finished;
+ }
+ this->resolved_values.emplace_back(*state->cur++);
+ if (state->cur == state->end)
+ {
+ state = std::nullopt;
+ return ResolveResult::Finished;
+ }
+ return ResolveResult::MoreToResolve;
+ }
+ };
+ template <typename Fn,
+ typename = std::enable_if_t<std::is_convertible_v<
+ decltype(std::declval<Fn &>()()), std::optional<T>>>>
+ struct LazyInternalsFn final : public Internals
+ {
+ std::optional<Fn> fn;
+ explicit LazyInternalsFn(Fn &&fn) noexcept
+ : fn(std::in_place, std::move(fn))
+ {
+ }
+ virtual ResolveResult resolve_more() override
+ {
+ if (!fn)
+ {
+ return ResolveResult::Finished;
+ }
+ if (std::optional<T> value = (*fn)())
+ {
+ this->resolved_values.emplace_back(std::move(*value));
+ return ResolveResult::MoreToResolve;
+ }
+ fn = std::nullopt;
+ return ResolveResult::Finished;
+ }
+ };
+ std::shared_ptr<Internals> internals;
+
+ public:
+ LazyVec() noexcept : internals(std::make_shared<Internals>())
+ {
+ }
+ LazyVec(std::vector<T> values) noexcept
+ : internals(std::make_shared<Internals>(std::move(values)))
+ {
+ }
+ LazyVec(std::initializer_list<T> values) noexcept
+ : internals(std::make_shared<Internals>(values))
+ {
+ }
+ template <typename Iterator, typename Sentinel,
+ typename = decltype(LazyInternalsIter<Iterator, Sentinel>::cur)>
+ LazyVec(Iterator begin, Sentinel end) noexcept
+ : internals(std::make_shared<LazyInternalsIter<Iterator, Sentinel>>(
+ std::move(begin), std::move(end)))
+ {
+ }
+ template <typename Container, typename = decltype(LazyInternalsContainer<
+ Container>::container)>
+ LazyVec(Container container) noexcept
+ : internals(std::make_shared<LazyInternalsContainer<Container>>(
+ std::move(container)))
+ {
+ }
+ template <typename Fn, typename = void,
+ typename = decltype(LazyInternalsFn<Fn>::fn)>
+ LazyVec(Fn fn) noexcept
+ : internals(std::make_shared<LazyInternalsFn<Fn>>(std::move(fn)))
+ {
+ }
+
+ private:
+ static bool resolve_at(const std::shared_ptr<Internals> &internals,
+ std::size_t index) noexcept
+ {
+ while (index >= internals->resolved_values.size())
+ {
+ switch (internals->resolve_more())
+ {
+ case ResolveResult::Finished:
+ goto end;
+ case ResolveResult::MoreToResolve:
+ continue;
+ }
+ }
+ end:
+ return index < internals->resolved_values.size();
+ }
+
+ public:
+ bool resolve_at(std::size_t index) noexcept
+ {
+ return resolve_at(internals, index);
+ }
+ const T &at(std::size_t index) noexcept
+ {
+ if (!resolve_at(index))
+ {
+ assert(!"index out of bounds");
+ std::terminate();
+ }
+ return internals->resolved_values[index];
+ }
+ const T &operator[](std::size_t index) noexcept
+ {
+ return at(index);
+ }
+ std::size_t size_lower_bound() noexcept
+ {
+ return internals->resolved_values.size();
+ }
+ ResolveResult resolve_more()
+ {
+ return internals->resolve_more();
+ }
+ const std::vector<T> &resolve()
+ {
+ while (true)
+ {
+ switch (resolve_more())
+ {
+ case ResolveResult::Finished:
+ return internals->resolved_values;
+ case ResolveResult::MoreToResolve:
+ continue;
+ }
+ }
+ }
+ class const_iterator final
+ {
+ friend class LazyVec;
+
+ private:
+ std::shared_ptr<Internals> internals;
+ std::size_t index = 0;
+ bool resolve() const noexcept
+ {
+ assert(internals);
+ if (!internals)
+ {
+ std::terminate();
+ }
+ return resolve_at(internals, index);
+ }
+ explicit const_iterator(
+ const std::shared_ptr<Internals> &internals) noexcept
+ : internals(internals)
+ {
+ }
+
+ public:
+ using difference_type = std::ptrdiff_t;
+ using value_type = T;
+ using pointer = const T *;
+ using reference = const T &;
+ using iterator_category = std::forward_iterator_tag;
+
+ const_iterator() noexcept = default;
+ const_iterator &operator++()
+ {
+ assert(internals);
+ ++index;
+ return *this;
+ }
+ const_iterator operator++(int)
+ {
+ auto retval = *this;
+ operator++();
+ return retval;
+ }
+ pointer operator->() const noexcept
+ {
+ if (at_end())
+ {
+ assert(!"tried to dereference an end() iterator");
+ std::terminate();
+ }
+ return std::addressof(internals->resolved_values[index]);
+ }
+ reference operator*() const noexcept
+ {
+ return *operator->();
+ }
+ bool at_end() const noexcept
+ {
+ return !resolve();
+ }
+ bool operator==(const const_iterator &rhs) const noexcept
+ {
+ if (rhs.internals)
+ {
+ if (internals)
+ {
+ assert(internals == rhs.internals);
+ return index == rhs.index;
+ }
+ return rhs.at_end();
+ }
+ if (internals)
+ {
+ return at_end();
+ }
+ return true;
+ }
+ bool operator!=(const const_iterator &rhs) const noexcept
+ {
+ return !operator==(rhs);
+ }
+ };
+ const_iterator begin() const noexcept
+ {
+ return const_iterator(internals);
+ }
+ const_iterator cbegin() const noexcept
+ {
+ return const_iterator(internals);
+ }
+ const_iterator end() const noexcept
+ {
+ return const_iterator();
+ }
+ const_iterator cend() const noexcept
+ {
+ return const_iterator();
+ }
+ template <typename R, typename Fn> LazyVec<R> filter_map(Fn &&fn) noexcept
+ {
+ struct FilterMapper final
+ {
+ const_iterator iter;
+ Fn fn;
+ std::optional<R> operator()() noexcept
+ {
+ while (iter != const_iterator())
+ {
+ if (std::optional<R> retval = fn(*iter++))
+ {
+ return retval;
+ }
+ }
+ return std::nullopt;
+ }
+ };
+ return LazyVec<R>(FilterMapper{.iter = begin(), .fn = std::move(fn)});
+ }
+ template <typename R, typename Fn> LazyVec<R> map(Fn fn) noexcept
+ {
+ return filter_map<R>(
+ [fn](const T &value) -> std::optional<R> { return fn(value); });
+ }
+ template <typename Fn> LazyVec filter(Fn fn) noexcept
+ {
+ return filter_map<T>([fn](const T &value) -> std::optional<T> {
+ if (fn(value))
+ return value;
+ return std::nullopt;
+ });
+ }
+};
-using JsonString = std::string;
-using JsonFloat = double;
-using JsonNull = std::nullptr_t;
-using JsonArray = std::vector<JsonValue>;
-using JsonMap = std::vector<std::pair<std::string, JsonValue>>;
+struct JsonValue;
struct JsonValue final
{
- std::variant<JsonString, JsonFloat, JsonNull, std::unique_ptr<JsonArray>,
- std::unique_ptr<JsonMap>>
- value;
+ using String = std::string;
+ using Number = double;
+ using Object = LazyVec<std::pair<String, JsonValue>>;
+ using Array = LazyVec<JsonValue>;
+ using Bool = bool;
+ using Null = std::nullptr_t;
+ std::variant<String, Number, Object, Array, Bool, Null> value;
constexpr JsonValue() noexcept : value(nullptr)
{
}
- constexpr JsonValue(JsonNull) noexcept : value(nullptr)
+ JsonValue(String value) noexcept : value(std::move(value))
+ {
+ }
+ JsonValue(const char *value) noexcept : value(std::string(value))
{
}
- constexpr JsonValue(JsonFloat value) noexcept : value(value)
+ JsonValue(std::string_view value) noexcept : value(std::string(value))
{
}
- JsonValue(JsonString value) noexcept : value(std::move(value))
+ constexpr JsonValue(Number value) noexcept : value(value)
{
}
- JsonValue(std::unique_ptr<JsonArray> value) noexcept
- : value(std::move(value))
+ JsonValue(Object value) noexcept : value(std::move(value))
{
}
- JsonValue(JsonArray value) noexcept
- : value(std::make_unique<JsonArray>(std::move(value)))
+ JsonValue(Array value) noexcept : value(std::move(value))
{
}
- JsonValue(std::unique_ptr<JsonMap> value) noexcept
- : value(std::move(value))
+ constexpr JsonValue(Bool value) noexcept : value(value)
{
}
- JsonValue(JsonMap value) noexcept
- : value(std::make_unique<JsonMap>(std::move(value)))
+ constexpr JsonValue(Null) noexcept : value(nullptr)
+ {
+ }
+ constexpr JsonValue(char) noexcept = delete;
+ constexpr JsonValue(char16_t) noexcept = delete;
+ constexpr JsonValue(char32_t) noexcept = delete;
+#define JSON_VALUE_NUM(T) \
+ constexpr JsonValue(T value) noexcept \
+ : JsonValue(static_cast<double>(value)) \
+ { \
+ }
+ JSON_VALUE_NUM(float)
+ JSON_VALUE_NUM(long double)
+ JSON_VALUE_NUM(unsigned char)
+ JSON_VALUE_NUM(signed char)
+ JSON_VALUE_NUM(unsigned short)
+ JSON_VALUE_NUM(short)
+ JSON_VALUE_NUM(unsigned int)
+ JSON_VALUE_NUM(int)
+ JSON_VALUE_NUM(unsigned long)
+ JSON_VALUE_NUM(long)
+ JSON_VALUE_NUM(unsigned long long)
+ JSON_VALUE_NUM(long long)
+#undef JSON_VALUE_NUM
+ template <typename T, typename = decltype(JsonValue(std::declval<T &&>()))>
+ constexpr JsonValue(std::optional<T> value) noexcept : value(nullptr)
+ {
+ if (value)
+ {
+ *this = JsonValue(std::move(*value));
+ }
+ }
+ template <typename T,
+ typename = decltype(JsonValue(std::declval<const T &>()))>
+ JsonValue(LazyVec<T> value) noexcept
+ : value(value.template map<JsonValue>(
+ [](const T &value) { return JsonValue(value); }))
+ {
+ }
+ template <typename T,
+ typename = decltype(JsonValue(std::declval<const T &>()))>
+ JsonValue(std::vector<T> value) noexcept
+ : JsonValue(LazyVec<T>(std::move(value)))
{
}
/// decode a JsonString from WTF-8 to the encoding logically used in JSON:
}
}
};
- template <typename WriteStringView>
- void write(WriteStringView &&write_fn) const
+
+ private:
+ template <typename WriteStringView> struct Visitor final
{
- struct Visitor final
+ WriteStringView &write_fn;
+ bool pretty;
+ std::size_t indent = 0;
+ void write(std::string_view str)
{
- WriteStringView &write_fn;
- void write(std::string_view str)
- {
- write_fn(str);
- }
- void write(char ch)
+ write_fn(str);
+ }
+ void write(char ch)
+ {
+ write_fn(std::string_view(&ch, 1));
+ }
+ void write_indent()
+ {
+ for (std::size_t i = 0; i < indent; i++)
{
- write_fn(std::string_view(&ch, 1));
+ write(" ");
}
- void operator()(const JsonString &value)
+ }
+ void operator()(const String &value)
+ {
+ write('\"');
+ JsonStringDecoder decoder(value);
+ while (auto value_opt = decoder.next())
{
- write('\"');
- JsonStringDecoder decoder(value);
- while (auto value_opt = decoder.next())
+ std::uint16_t value = *value_opt;
+ switch (value)
{
- std::uint16_t value = *value_opt;
- switch (value)
+ case '\"':
+ case '\\':
+ write('\\');
+ write(static_cast<char>(value));
+ break;
+ case '\b':
+ write("\\b");
+ break;
+ case '\f':
+ write("\\f");
+ break;
+ case '\n':
+ write("\\n");
+ break;
+ case '\r':
+ write("\\r");
+ break;
+ case '\t':
+ write("\\t");
+ break;
+ default:
+ if (value >= 0x20 && value <= 0x7E)
{
- case '\"':
- case '\\':
- write('\\');
write(static_cast<char>(value));
- break;
- case '\b':
- write("\\b");
- break;
- case '\f':
- write("\\f");
- break;
- case '\n':
- write("\\n");
- break;
- case '\r':
- write("\\r");
- break;
- case '\t':
- write("\\t");
- break;
- default:
- if (value >= 0x20 && value <= 0x7E)
- {
- write(static_cast<char>(value));
- }
- else
+ }
+ else
+ {
+ static constexpr char hex_digits[] =
+ "0123456789ABCDEF";
+ write("\\u");
+ for (int i = 0; i < 4; i++)
{
- static constexpr char hex_digits[] =
- "0123456789ABCDEF";
- write("\\u");
- for (int i = 0; i < 4; i++)
- {
- write(hex_digits[value >> 12]);
- value <<= 4;
- }
+ write(hex_digits[value >> 12]);
+ value <<= 4;
}
- break;
}
+ break;
}
- write('"');
}
- void operator()(JsonFloat value)
+ write('"');
+ }
+ void operator()(Number value)
+ {
+ if (std::isnan(value))
+ {
+ write("NaN");
+ return;
+ }
+ if (std::signbit(value))
{
- if (std::isnan(value))
+ write('-');
+ value = -value;
+ }
+ if (std::isinf(value))
+ {
+ write("Infinity");
+ return;
+ }
+ if (value == 0)
+ {
+ write("0");
+ return;
+ }
+ using Buf = std::array<char, 32>;
+ auto try_format = [&](Buf &buf, bool e_format, int prec) -> bool {
+ int result;
+ if (e_format)
{
- write("NaN");
- return;
+ result = std::snprintf(&buf[0], buf.size(), "%1.*e", prec,
+ value);
}
- if (std::signbit(value))
+ else
{
- write('-');
- value = -value;
+ result = std::snprintf(&buf[0], buf.size(), "%1.*f", prec,
+ value);
}
- if (std::isinf(value))
+ if (result <= 0)
+ return false;
+ double parsed_value = std::strtod(&buf[0], nullptr);
+ if (parsed_value != value)
{
- write("Infinity");
- return;
+ // not precise enough
+ return false;
}
- if (value == 0)
+ return true;
+ };
+ Buf final_buf = {};
+ std::optional<std::string_view> final;
+ std::size_t end_prec = final_buf.size();
+ for (std::size_t prec = 0;
+ prec < final_buf.size() && prec < end_prec; prec++)
+ {
+ Buf buf;
+ if (try_format(buf, true, prec))
{
- write("0");
- return;
- }
- using Buf = std::array<char, 32>;
- auto try_format = [&](Buf &buf, bool e_format,
- int prec) -> bool {
- int result;
- if (e_format)
+ std::string_view str(&buf[0]);
+ if (!final || str.size() < final->size())
{
- result = std::snprintf(&buf[0], buf.size(), "%1.*e",
- prec, value);
+ final_buf = buf;
+ final = std::string_view(&final_buf[0], str.size());
+ end_prec = prec + 3;
}
- else
- {
- result = std::snprintf(&buf[0], buf.size(), "%1.*f",
- prec, value);
- }
- if (result <= 0)
- return false;
- double parsed_value = std::strtod(&buf[0], nullptr);
- if (parsed_value != value)
- {
- // not precise enough
- return false;
- }
- return true;
- };
- Buf final_buf = {};
- std::optional<std::string_view> final;
- std::size_t end_prec = final_buf.size();
- for (std::size_t prec = 0;
- prec < final_buf.size() && prec < end_prec; prec++)
+ }
+ if (try_format(buf, false, prec))
{
- Buf buf;
- if (try_format(buf, true, prec))
+ std::string_view str(&buf[0]);
+ if (!final || str.size() < final->size())
{
- std::string_view str(&buf[0]);
- if (!final || str.size() < final->size())
- {
- final_buf = buf;
- final =
- std::string_view(&final_buf[0], str.size());
- end_prec = prec + 3;
- }
- }
- if (try_format(buf, false, prec))
- {
- std::string_view str(&buf[0]);
- if (!final || str.size() < final->size())
- {
- final_buf = buf;
- final =
- std::string_view(&final_buf[0], str.size());
- end_prec = prec + 3;
- }
+ final_buf = buf;
+ final = std::string_view(&final_buf[0], str.size());
+ end_prec = prec + 3;
}
}
- if (final_buf[0] == '.')
- {
- write('0');
- }
- write(*final);
}
- void operator()(JsonNull)
+ if (final_buf[0] == '.')
{
- write("null");
+ write('0');
}
- void operator()(const std::unique_ptr<JsonArray> &value)
+ write(*final);
+ }
+ void operator()(Bool value)
+ {
+ write(value ? "true" : "false");
+ }
+ void operator()(Null)
+ {
+ write("null");
+ }
+ template <typename T, typename Fn>
+ void write_container_inner(const LazyVec<T> &value, Fn fn)
+ {
+ indent++;
+ std::string_view sep{};
+ bool any = false;
+ for (auto &v : value)
{
- write('[');
- std::string_view sep{};
- for (auto &i : *value)
+ any = true;
+ write(sep);
+ if (pretty)
{
- write(sep);
- sep = ",";
- std::visit(*this, i.value);
+ write('\n');
+ write_indent();
}
- write(']');
+ sep = ",";
+ fn(v);
}
- void operator()(const std::unique_ptr<JsonMap> &value)
+ indent--;
+ if (pretty && any)
{
- write('{');
- std::string_view sep{};
- for (auto &[k, v] : *value)
- {
- write(sep);
- sep = ",";
- operator()(k);
- write(':');
- std::visit(*this, v.value);
- }
- write('}');
+ write('\n');
+ write_indent();
}
- };
- std::visit(Visitor{.write_fn = write_fn}, value);
+ }
+ void operator()(const Array &value)
+ {
+ write('[');
+ write_container_inner(value, [&](const JsonValue &value) {
+ std::visit(*this, value.value);
+ });
+ write(']');
+ }
+ void operator()(const Object &value)
+ {
+ write('{');
+ write_container_inner(
+ value, [&](const std::pair<String, JsonValue> &value) {
+ operator()(value.first);
+ write(':');
+ if (pretty)
+ {
+ write(' ');
+ }
+ std::visit(*this, value.second.value);
+ });
+ write('}');
+ }
+ };
+
+ public:
+ template <typename WriteStringView>
+ void write(WriteStringView &&write_fn, bool pretty = false) const
+ {
+ std::visit(
+ Visitor<WriteStringView>{
+ .write_fn = write_fn,
+ .pretty = pretty,
+ },
+ value);
}
+ struct PrettyJsonValue;
+ PrettyJsonValue pretty(bool pretty = true) const noexcept;
friend std::ostream &operator<<(std::ostream &os, const JsonValue &self)
{
self.write([&](std::string_view str) { os << str; });
return os;
}
};
+
+struct JsonValue::PrettyJsonValue final
+{
+ JsonValue value;
+ bool pretty;
+ friend std::ostream &operator<<(std::ostream &os,
+ const PrettyJsonValue &self)
+ {
+ self.value.write([&](std::string_view str) { os << str; },
+ self.pretty);
+ return os;
+ }
+};
+
+inline auto JsonValue::pretty(bool pretty) const noexcept -> PrettyJsonValue
+{
+ return {.value = *this, .pretty = pretty};
+}
\ No newline at end of file