--- /dev/null
+#pragma once
+
+#include <charconv>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <initializer_list>
+#include <ios>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <unordered_map>
+#include <variant>
+#include <vector>
+
+struct JsonValue;
+
+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 final
+{
+ std::variant<JsonString, JsonFloat, JsonNull, std::unique_ptr<JsonArray>,
+ std::unique_ptr<JsonMap>>
+ value;
+ constexpr JsonValue() noexcept : value(nullptr)
+ {
+ }
+ constexpr JsonValue(JsonNull) noexcept : value(nullptr)
+ {
+ }
+ constexpr JsonValue(JsonFloat value) noexcept : value(value)
+ {
+ }
+ JsonValue(JsonString value) noexcept : value(std::move(value))
+ {
+ }
+ JsonValue(std::unique_ptr<JsonArray> value) noexcept
+ : value(std::move(value))
+ {
+ }
+ JsonValue(JsonArray value) noexcept
+ : value(std::make_unique<JsonArray>(std::move(value)))
+ {
+ }
+ JsonValue(std::unique_ptr<JsonMap> value) noexcept
+ : value(std::move(value))
+ {
+ }
+ JsonValue(JsonMap value) noexcept
+ : value(std::make_unique<JsonMap>(std::move(value)))
+ {
+ }
+ /// decode a JsonString from WTF-8 to the encoding logically used in JSON:
+ /// WTF-16 aka. potentially ill-formed UTF-16
+ ///
+ /// https://simonsapin.github.io/wtf-8/
+ class JsonStringDecoder final
+ {
+ private:
+ std::string_view remaining;
+ bool last_was_lead_surrogate = false;
+ std::optional<std::uint16_t> trail_surrogate;
+
+ public:
+ constexpr JsonStringDecoder() noexcept : JsonStringDecoder("")
+ {
+ }
+ constexpr explicit JsonStringDecoder(
+ std::string_view json_string) noexcept
+ : remaining(json_string)
+ {
+ }
+
+ private:
+ [[noreturn]] void throw_wtf8_err()
+ {
+ *this = JsonStringDecoder();
+ throw std::ios_base::failure("Invalid WTF-8");
+ }
+
+ public:
+ constexpr std::optional<std::uint16_t> next()
+ {
+ if (trail_surrogate)
+ {
+ auto retval = *trail_surrogate;
+ trail_surrogate = std::nullopt;
+ return retval;
+ }
+ if (remaining.empty())
+ {
+ return std::nullopt;
+ }
+ std::uint32_t code_point = 0;
+ struct min_max final
+ {
+ std::uint8_t min = 0x80;
+ std::uint8_t max = 0xBF;
+ };
+ auto get = [&]() -> std::uint8_t {
+ if (remaining.empty())
+ {
+ throw_wtf8_err();
+ }
+ std::uint8_t retval = remaining[0];
+ remaining.remove_prefix(1);
+ return retval;
+ };
+ auto cont = [&](min_max min_max = {}) {
+ std::uint8_t v = get();
+ if (v < min_max.min || v > min_max.max)
+ {
+ throw_wtf8_err();
+ }
+ code_point <<= 6;
+ code_point |= v & 0x3F;
+ };
+ std::uint8_t initial = get();
+ if (initial < 0x80)
+ {
+ code_point = initial;
+ }
+ else if (initial < 0xC2)
+ {
+ throw_wtf8_err();
+ }
+ else if (initial < 0xE0)
+ {
+ code_point = initial & 0x1F;
+ cont();
+ }
+ else if (initial == 0xE0)
+ {
+ code_point = initial & 0xF;
+ cont({.min = 0xA0});
+ cont();
+ }
+ else if (initial < 0xF0)
+ {
+ code_point = initial & 0xF;
+ cont();
+ cont();
+ }
+ else if (initial == 0xF0)
+ {
+ code_point = initial & 0x7;
+ cont({.min = 0x90});
+ cont();
+ cont();
+ }
+ else if (initial < 0xF4)
+ {
+ code_point = initial & 0x7;
+ cont();
+ cont();
+ cont();
+ }
+ else if (initial == 0xF4)
+ {
+ code_point = initial & 0x7;
+ cont({.max = 0x8F});
+ cont();
+ cont();
+ }
+ else
+ {
+ throw_wtf8_err();
+ }
+ if (last_was_lead_surrogate && code_point >= 0xDC00 &&
+ code_point <= 0xDFFF)
+ {
+ // got lead surrogate followed by trail surrogate --
+ // invalid in WTF-8
+ throw_wtf8_err();
+ }
+ last_was_lead_surrogate =
+ (code_point >= 0xD800 && code_point <= 0xDBFF);
+ bool is_supplementary_code_point = code_point >= 0x10000;
+ if (is_supplementary_code_point)
+ {
+ auto value = code_point - 0x10000;
+ std::uint16_t retval = (value >> 10) + 0xD800;
+ trail_surrogate = (value & 0x3FF) + 0xDC00;
+ return retval;
+ }
+ else
+ {
+ return code_point;
+ }
+ }
+ };
+ template <typename WriteStringView>
+ void write(WriteStringView &&write_fn) const
+ {
+ struct Visitor final
+ {
+ WriteStringView &write_fn;
+ void write(std::string_view str)
+ {
+ write_fn(str);
+ }
+ void write(char ch)
+ {
+ write_fn(std::string_view(&ch, 1));
+ }
+ void operator()(const JsonString &value)
+ {
+ write('\"');
+ JsonStringDecoder decoder(value);
+ while (auto value_opt = decoder.next())
+ {
+ 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)
+ {
+ write(static_cast<char>(value));
+ }
+ else
+ {
+ static constexpr char hex_digits[] =
+ "0123456789ABCDEF";
+ write("\\u");
+ for (int i = 0; i < 4; i++)
+ {
+ write(hex_digits[value >> 12]);
+ value <<= 4;
+ }
+ }
+ break;
+ }
+ }
+ write('"');
+ }
+ void operator()(JsonFloat value)
+ {
+ if (std::isnan(value))
+ {
+ write("NaN");
+ return;
+ }
+ if (std::signbit(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)
+ {
+ result = std::snprintf(&buf[0], buf.size(), "%1.*e",
+ prec, value);
+ }
+ 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++)
+ {
+ Buf buf;
+ if (try_format(buf, true, 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;
+ }
+ }
+ 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;
+ }
+ }
+ }
+ if (final_buf[0] == '.')
+ {
+ write('0');
+ }
+ write(*final);
+ }
+ void operator()(JsonNull)
+ {
+ write("null");
+ }
+ void operator()(const std::unique_ptr<JsonArray> &value)
+ {
+ write('[');
+ std::string_view sep{};
+ for (auto &i : *value)
+ {
+ write(sep);
+ sep = ",";
+ std::visit(*this, i.value);
+ }
+ write(']');
+ }
+ void operator()(const std::unique_ptr<JsonMap> &value)
+ {
+ write('{');
+ std::string_view sep{};
+ for (auto &[k, v] : *value)
+ {
+ write(sep);
+ sep = ",";
+ operator()(k);
+ write(':');
+ std::visit(*this, v.value);
+ }
+ write('}');
+ }
+ };
+ std::visit(Visitor{.write_fn = write_fn}, value);
+ }
+ friend std::ostream &operator<<(std::ostream &os, const JsonValue &self)
+ {
+ self.write([&](std::string_view str) { os << str; });
+ return os;
+ }
+};
#include "all_benchmarks.h"
#include "harness.h"
+#include "json.h"
#include <charconv>
#include <cstdlib>
#include <functional>
}
enabled_benchmarks->emplace(value.value_or(""));
}},
+ Option{.long_name = "json",
+ .description = "Write the output in JSON format",
+ .value_kind = OptionValueKind::None,
+ .parse_value = [&](auto &, auto) { config.use_json = true; }},
};
OptionsParser parser(options, argv);
auto args = parser.parse();
enabled_benchmarks->erase("list");
if (enabled_benchmarks->empty())
{
- std::cout << "Available Benchmarks:\n";
- for (auto &benchmark : benchmarks)
+ if (config.use_json)
{
- std::cout << benchmark.name() << "\n";
+ JsonArray names;
+ for (auto &benchmark : benchmarks)
+ {
+ names.push_back(benchmark.name());
+ }
+ std::cout << JsonValue(std::move(names)) << std::endl;
+ }
+ else
+ {
+ std::cout << "Available Benchmarks:\n";
+ for (auto &benchmark : benchmarks)
+ {
+ std::cout << benchmark.name() << "\n";
+ }
+ std::cout << std::endl;
}
- std::cout << std::endl;
return 0;
}
std::unordered_set<std::string> unknown_benchmarks =
auto thread_cache = BenchHarnessBase::get_thread_cache();
for (auto &benchmark : benchmarks)
{
+ // FIXME: change output to use JSON when selected
if (enabled_benchmarks && !enabled_benchmarks->count(benchmark.name()))
{
continue;