2016-08-26 19:55:55 +03:00
|
|
|
#include "json.hh"
|
|
|
|
|
|
|
|
#include <iomanip>
|
2022-05-26 20:36:10 +03:00
|
|
|
#include <cstdint>
|
2016-08-26 19:55:55 +03:00
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
|
|
|
void toJSON(std::ostream & str, const char * start, const char * end)
|
|
|
|
{
|
avoid ostream sentries per json string character
we don't have to create an ostream sentry object for every character of a JSON
string we write. format a bunch of characters and flush them to the stream all
at once instead.
this doesn't affect small numbers of string characters, but larger numbers of
total JSON string characters written gain a lot. at 1MB of total string written
we gain almost 30%, at 16MB it's almost a factor of 3x. large numbers of JSON
string characters do occur naturally in a nixos system evaluation to generate
documentation (though this is now somewhat mitigated by caching the largest part
of nixos option docs).
benchmarked with
hyperfine 'nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) {e})"' --warmup 1 -L e 1,4,256,4096,65536
before:
Benchmark 1: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 1)"
Time (mean ± σ): 12.5 ms ± 0.2 ms [User: 9.2 ms, System: 4.0 ms]
Range (min … max): 11.9 ms … 13.1 ms 223 runs
Benchmark 2: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 4)"
Time (mean ± σ): 12.5 ms ± 0.2 ms [User: 9.3 ms, System: 3.8 ms]
Range (min … max): 11.9 ms … 13.2 ms 220 runs
Benchmark 3: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 256)"
Time (mean ± σ): 13.2 ms ± 0.3 ms [User: 9.8 ms, System: 4.0 ms]
Range (min … max): 12.6 ms … 14.3 ms 205 runs
Benchmark 4: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 4096)"
Time (mean ± σ): 24.0 ms ± 0.4 ms [User: 19.4 ms, System: 5.2 ms]
Range (min … max): 22.7 ms … 25.8 ms 119 runs
Benchmark 5: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 65536)"
Time (mean ± σ): 196.0 ms ± 3.7 ms [User: 171.2 ms, System: 25.8 ms]
Range (min … max): 190.6 ms … 201.5 ms 14 runs
after:
Benchmark 1: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 1)"
Time (mean ± σ): 12.4 ms ± 0.3 ms [User: 9.1 ms, System: 4.0 ms]
Range (min … max): 11.7 ms … 13.3 ms 204 runs
Benchmark 2: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 4)"
Time (mean ± σ): 12.4 ms ± 0.2 ms [User: 9.2 ms, System: 3.9 ms]
Range (min … max): 11.8 ms … 13.0 ms 214 runs
Benchmark 3: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 256)"
Time (mean ± σ): 12.6 ms ± 0.2 ms [User: 9.5 ms, System: 3.8 ms]
Range (min … max): 12.1 ms … 13.3 ms 209 runs
Benchmark 4: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 4096)"
Time (mean ± σ): 15.9 ms ± 0.2 ms [User: 11.4 ms, System: 5.1 ms]
Range (min … max): 15.2 ms … 16.4 ms 171 runs
Benchmark 5: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 65536)"
Time (mean ± σ): 69.0 ms ± 0.9 ms [User: 44.3 ms, System: 25.3 ms]
Range (min … max): 67.2 ms … 70.9 ms 42 runs
2021-12-28 23:26:59 +02:00
|
|
|
constexpr size_t BUF_SIZE = 4096;
|
|
|
|
char buf[BUF_SIZE + 7]; // BUF_SIZE + largest single sequence of puts
|
|
|
|
size_t bufPos = 0;
|
|
|
|
|
|
|
|
const auto flush = [&] {
|
|
|
|
str.write(buf, bufPos);
|
|
|
|
bufPos = 0;
|
|
|
|
};
|
|
|
|
const auto put = [&] (char c) {
|
|
|
|
buf[bufPos++] = c;
|
|
|
|
};
|
|
|
|
|
|
|
|
put('"');
|
|
|
|
for (auto i = start; i != end; i++) {
|
|
|
|
if (bufPos >= BUF_SIZE) flush();
|
|
|
|
if (*i == '\"' || *i == '\\') { put('\\'); put(*i); }
|
|
|
|
else if (*i == '\n') { put('\\'); put('n'); }
|
|
|
|
else if (*i == '\r') { put('\\'); put('r'); }
|
|
|
|
else if (*i == '\t') { put('\\'); put('t'); }
|
|
|
|
else if (*i >= 0 && *i < 32) {
|
|
|
|
const char hex[17] = "0123456789abcdef";
|
|
|
|
put('\\');
|
|
|
|
put('u');
|
|
|
|
put(hex[(uint16_t(*i) >> 12) & 0xf]);
|
|
|
|
put(hex[(uint16_t(*i) >> 8) & 0xf]);
|
|
|
|
put(hex[(uint16_t(*i) >> 4) & 0xf]);
|
|
|
|
put(hex[(uint16_t(*i) >> 0) & 0xf]);
|
|
|
|
}
|
|
|
|
else put(*i);
|
|
|
|
}
|
|
|
|
put('"');
|
|
|
|
flush();
|
2016-08-26 19:55:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void toJSON(std::ostream & str, const char * s)
|
|
|
|
{
|
|
|
|
if (!s) str << "null"; else toJSON(str, s, s + strlen(s));
|
|
|
|
}
|
|
|
|
|
2017-04-20 18:34:47 +03:00
|
|
|
template<> void toJSON<int>(std::ostream & str, const int & n) { str << n; }
|
|
|
|
template<> void toJSON<unsigned int>(std::ostream & str, const unsigned int & n) { str << n; }
|
|
|
|
template<> void toJSON<long>(std::ostream & str, const long & n) { str << n; }
|
|
|
|
template<> void toJSON<unsigned long>(std::ostream & str, const unsigned long & n) { str << n; }
|
|
|
|
template<> void toJSON<long long>(std::ostream & str, const long long & n) { str << n; }
|
|
|
|
template<> void toJSON<unsigned long long>(std::ostream & str, const unsigned long long & n) { str << n; }
|
|
|
|
template<> void toJSON<float>(std::ostream & str, const float & n) { str << n; }
|
2018-07-03 19:04:51 +03:00
|
|
|
template<> void toJSON<double>(std::ostream & str, const double & n) { str << n; }
|
2016-08-26 19:55:55 +03:00
|
|
|
|
2017-04-20 18:34:47 +03:00
|
|
|
template<> void toJSON<std::string>(std::ostream & str, const std::string & s)
|
2016-10-06 17:34:28 +03:00
|
|
|
{
|
2017-04-20 18:34:47 +03:00
|
|
|
toJSON(str, s.c_str(), s.c_str() + s.size());
|
2016-10-06 17:34:28 +03:00
|
|
|
}
|
|
|
|
|
2017-04-20 18:34:47 +03:00
|
|
|
template<> void toJSON<bool>(std::ostream & str, const bool & b)
|
2016-08-26 19:55:55 +03:00
|
|
|
{
|
2017-04-20 18:34:47 +03:00
|
|
|
str << (b ? "true" : "false");
|
2016-08-26 19:55:55 +03:00
|
|
|
}
|
|
|
|
|
2017-04-20 18:34:47 +03:00
|
|
|
template<> void toJSON<std::nullptr_t>(std::ostream & str, const std::nullptr_t & b)
|
2016-08-26 19:55:55 +03:00
|
|
|
{
|
2017-04-20 18:34:47 +03:00
|
|
|
str << "null";
|
2016-08-26 19:55:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
JSONWriter::JSONWriter(std::ostream & str, bool indent)
|
|
|
|
: state(new JSONState(str, indent))
|
|
|
|
{
|
2017-07-26 18:21:46 +03:00
|
|
|
state->stack++;
|
2016-08-26 19:55:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
JSONWriter::JSONWriter(JSONState * state)
|
|
|
|
: state(state)
|
|
|
|
{
|
2017-07-26 18:21:46 +03:00
|
|
|
state->stack++;
|
2016-08-26 19:55:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
JSONWriter::~JSONWriter()
|
|
|
|
{
|
2017-07-26 18:21:46 +03:00
|
|
|
if (state) {
|
|
|
|
assertActive();
|
|
|
|
state->stack--;
|
|
|
|
if (state->stack == 0) delete state;
|
|
|
|
}
|
2016-08-26 19:55:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void JSONWriter::comma()
|
|
|
|
{
|
|
|
|
assertActive();
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
} else {
|
|
|
|
state->str << ',';
|
|
|
|
}
|
|
|
|
if (state->indent) indent();
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONWriter::indent()
|
|
|
|
{
|
|
|
|
state->str << '\n' << std::string(state->depth * 2, ' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONList::open()
|
|
|
|
{
|
|
|
|
state->depth++;
|
|
|
|
state->str << '[';
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONList::~JSONList()
|
|
|
|
{
|
|
|
|
state->depth--;
|
|
|
|
if (state->indent && !first) indent();
|
|
|
|
state->str << "]";
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONList JSONList::list()
|
|
|
|
{
|
|
|
|
comma();
|
|
|
|
return JSONList(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONObject JSONList::object()
|
|
|
|
{
|
|
|
|
comma();
|
|
|
|
return JSONObject(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONPlaceholder JSONList::placeholder()
|
|
|
|
{
|
|
|
|
comma();
|
|
|
|
return JSONPlaceholder(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONObject::open()
|
|
|
|
{
|
|
|
|
state->depth++;
|
|
|
|
state->str << '{';
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONObject::~JSONObject()
|
|
|
|
{
|
2017-07-26 18:21:46 +03:00
|
|
|
if (state) {
|
|
|
|
state->depth--;
|
|
|
|
if (state->indent && !first) indent();
|
|
|
|
state->str << "}";
|
|
|
|
}
|
2016-08-26 19:55:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void JSONObject::attr(const std::string & s)
|
|
|
|
{
|
|
|
|
comma();
|
|
|
|
toJSON(state->str, s);
|
|
|
|
state->str << ':';
|
|
|
|
if (state->indent) state->str << ' ';
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONList JSONObject::list(const std::string & name)
|
|
|
|
{
|
|
|
|
attr(name);
|
|
|
|
return JSONList(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONObject JSONObject::object(const std::string & name)
|
|
|
|
{
|
|
|
|
attr(name);
|
|
|
|
return JSONObject(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONPlaceholder JSONObject::placeholder(const std::string & name)
|
|
|
|
{
|
|
|
|
attr(name);
|
|
|
|
return JSONPlaceholder(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONList JSONPlaceholder::list()
|
|
|
|
{
|
|
|
|
assertValid();
|
|
|
|
first = false;
|
|
|
|
return JSONList(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONObject JSONPlaceholder::object()
|
|
|
|
{
|
|
|
|
assertValid();
|
|
|
|
first = false;
|
|
|
|
return JSONObject(state);
|
|
|
|
}
|
|
|
|
|
2019-10-10 00:04:11 +03:00
|
|
|
JSONPlaceholder::~JSONPlaceholder()
|
|
|
|
{
|
2020-06-17 05:15:47 +03:00
|
|
|
assert(!first || std::uncaught_exceptions());
|
2019-10-10 00:04:11 +03:00
|
|
|
}
|
|
|
|
|
2016-08-26 19:55:55 +03:00
|
|
|
}
|