Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: NixOS/nix
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 04bbfa692f4a
Choose a base ref
...
head repository: NixOS/nix
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 72a50756bb78
Choose a head ref
  • 3 commits
  • 1 file changed
  • 2 contributors

Commits on Jan 9, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f1fac0b View commit details
  2. Copy the full SHA
    a350d0b View commit details

Commits on Jan 10, 2020

  1. Merge pull request #3307 from yorickvP/yorickvp/nlohmann-fromJSON

    builtins.fromJSON: use nlohmann/json parser instead of custom parser
    edolstra authored Jan 10, 2020
    Copy the full SHA
    72a5075 View commit details
Showing with 121 additions and 191 deletions.
  1. +121 −191 src/libexpr/json-to-value.cc
312 changes: 121 additions & 191 deletions src/libexpr/json-to-value.cc
Original file line number Diff line number Diff line change
@@ -1,232 +1,162 @@
#include "json-to-value.hh"

#include <cstring>

namespace nix {
#include <variant>
#include <nlohmann/json.hpp>
#include <nlohmann/detail/exceptions.hpp>

using json = nlohmann::json;
using std::unique_ptr;

static void skipWhitespace(const char * & s)
{
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
}
namespace nix {

// for more information, refer to
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
class JSONSax : nlohmann::json_sax<json> {
class JSONState {
protected:
unique_ptr<JSONState> parent;
Value * v;
public:
virtual unique_ptr<JSONState> resolve(EvalState &)
{
throw std::logic_error("tried to close toplevel json parser state");
};
explicit JSONState(unique_ptr<JSONState>&& p) : parent(std::move(p)), v(nullptr) {};
explicit JSONState(Value* v) : v(v) {};
JSONState(JSONState& p) = delete;
Value& value(EvalState & state)
{
if (v == nullptr)
v = state.allocValue();
return *v;
};
virtual ~JSONState() {};
virtual void add() {};
};

class JSONObjectState : public JSONState {
using JSONState::JSONState;
ValueMap attrs = ValueMap();
virtual unique_ptr<JSONState> resolve(EvalState & state) override
{
Value& v = parent->value(state);
state.mkAttrs(v, attrs.size());
for (auto & i : attrs)
v.attrs->push_back(Attr(i.first, i.second));
return std::move(parent);
}
virtual void add() override { v = nullptr; };
public:
void key(string_t& name, EvalState & state)
{
attrs[state.symbols.create(name)] = &value(state);
}
};

class JSONListState : public JSONState {
ValueVector values = ValueVector();
virtual unique_ptr<JSONState> resolve(EvalState & state) override
{
Value& v = parent->value(state);
state.mkList(v, values.size());
for (size_t n = 0; n < values.size(); ++n) {
v.listElems()[n] = values[n];
}
return std::move(parent);
}
virtual void add() override {
values.push_back(v);
v = nullptr;
};
public:
JSONListState(unique_ptr<JSONState>&& p, std::size_t reserve) : JSONState(std::move(p))
{
values.reserve(reserve);
}
};

/*
Parse an unicode escape sequence (4 hex characters following \u) in JSON string
*/
static string parseUnicodeEscapeSequence(const char * & s)
{
int codepoint = 0;
EvalState & state;
unique_ptr<JSONState> rs;

const auto factors = { 12u, 8u, 4u, 0u };
for (const auto factor : factors)
template<typename T, typename... Args> inline bool handle_value(T f, Args... args)
{
if (!*s) throw JSONParseError("got end-of-string in JSON string while parsing \\u sequence");

if (*s >= '0' and *s <= '9') {
codepoint += static_cast<int>((static_cast<unsigned int>(*s) - 0x30u) << factor);
} else if (*s >= 'A' and *s <= 'F') {
codepoint += static_cast<int>((static_cast<unsigned int>(*s) - 0x37u) << factor);
} else if (*s >= 'a' and *s <= 'f') {
codepoint += static_cast<int>((static_cast<unsigned int>(*s) - 0x57u) << factor);
} else {
throw JSONParseError(format("illegal character '%1%' in \\u escape sequence.") % *s);
}
s++;
f(rs->value(state), args...);
rs->add();
return true;
}

if ((codepoint > 0xd7ff && codepoint < 0xe000) || codepoint > 0x10ffff) {
throw JSONParseError("Unicode escape sequence is not a Unicode scalar value");
}
public:
JSONSax(EvalState & state, Value & v) : state(state), rs(new JSONState(&v)) {};

// taken from cpptoml.h
std::string result;
// See Table 3-6 of the Unicode standard
if (codepoint <= 0x7f)
bool null()
{
// 1-byte codepoints: 00000000 0xxxxxxx
// repr: 0xxxxxxx
result += static_cast<char>(codepoint & 0x7f);
return handle_value(mkNull);
}
else if (codepoint <= 0x7ff)

bool boolean(bool val)
{
// 2-byte codepoints: 00000yyy yyxxxxxx
// repr: 110yyyyy 10xxxxxx
//
// 0x1f = 00011111
// 0xc0 = 11000000
//
result += static_cast<char>(0xc0 | ((codepoint >> 6) & 0x1f));
//
// 0x80 = 10000000
// 0x3f = 00111111
//
result += static_cast<char>(0x80 | (codepoint & 0x3f));
return handle_value(mkBool, val);
}
else if (codepoint <= 0xffff)

bool number_integer(number_integer_t val)
{
// 3-byte codepoints: zzzzyyyy yyxxxxxx
// repr: 1110zzzz 10yyyyyy 10xxxxxx
//
// 0xe0 = 11100000
// 0x0f = 00001111
//
result += static_cast<char>(0xe0 | ((codepoint >> 12) & 0x0f));
result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x1f));
result += static_cast<char>(0x80 | (codepoint & 0x3f));
return handle_value(mkInt, val);
}
else

bool number_unsigned(number_unsigned_t val)
{
// 4-byte codepoints: 000uuuuu zzzzyyyy yyxxxxxx
// repr: 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
//
// 0xf0 = 11110000
// 0x07 = 00000111
//
result += static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07));
result += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f));
result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f));
result += static_cast<char>(0x80 | (codepoint & 0x3f));
return handle_value(mkInt, val);
}
return result;
}


static string parseJSONString(const char * & s)
{
string res;
if (*s++ != '"') throw JSONParseError("expected JSON string");
while (*s != '"') {
if (!*s) throw JSONParseError("got end-of-string in JSON string");
if (*s == '\\') {
s++;
if (*s == '"') res += '"';
else if (*s == '\\') res += '\\';
else if (*s == '/') res += '/';
else if (*s == 'b') res += '\b';
else if (*s == 'f') res += '\f';
else if (*s == 'n') res += '\n';
else if (*s == 'r') res += '\r';
else if (*s == 't') res += '\t';
else if (*s == 'u') {
res += parseUnicodeEscapeSequence(++s);
// to neuter the outside s++
s--;
} else throw JSONParseError("invalid escaped character in JSON string");
s++;
} else
res += *s++;
bool number_float(number_float_t val, const string_t& s)
{
return handle_value(mkFloat, val);
}
s++;
return res;
}


static void parseJSON(EvalState & state, const char * & s, Value & v)
{
skipWhitespace(s);

if (!*s) throw JSONParseError("expected JSON value");

if (*s == '[') {
s++;
ValueVector values;
values.reserve(128);
skipWhitespace(s);
while (1) {
if (values.empty() && *s == ']') break;
Value * v2 = state.allocValue();
parseJSON(state, s, *v2);
values.push_back(v2);
skipWhitespace(s);
if (*s == ']') break;
if (*s != ',') throw JSONParseError("expected ',' or ']' after JSON array element");
s++;
}
s++;
state.mkList(v, values.size());
for (size_t n = 0; n < values.size(); ++n)
v.listElems()[n] = values[n];
bool string(string_t& val)
{
return handle_value<void(Value&, const char*)>(mkString, val.c_str());
}

else if (*s == '{') {
s++;
ValueMap attrs;
while (1) {
skipWhitespace(s);
if (attrs.empty() && *s == '}') break;
string name = parseJSONString(s);
skipWhitespace(s);
if (*s != ':') throw JSONParseError("expected ':' in JSON object");
s++;
Value * v2 = state.allocValue();
parseJSON(state, s, *v2);
attrs[state.symbols.create(name)] = v2;
skipWhitespace(s);
if (*s == '}') break;
if (*s != ',') throw JSONParseError("expected ',' or '}' after JSON member");
s++;
}
state.mkAttrs(v, attrs.size());
for (auto & i : attrs)
v.attrs->push_back(Attr(i.first, i.second));
v.attrs->sort();
s++;
bool start_object(std::size_t len)
{
rs = std::make_unique<JSONObjectState>(std::move(rs));
return true;
}

else if (*s == '"') {
mkString(v, parseJSONString(s));
bool key(string_t& name)
{
dynamic_cast<JSONObjectState*>(rs.get())->key(name, state);
return true;
}

else if (isdigit(*s) || *s == '-' || *s == '.' ) {
// Buffer into a string first, then use built-in C++ conversions
std::string tmp_number;
ValueType number_type = tInt;

while (isdigit(*s) || *s == '-' || *s == '.' || *s == 'e' || *s == 'E') {
if (*s == '.' || *s == 'e' || *s == 'E')
number_type = tFloat;
tmp_number += *s++;
}

try {
if (number_type == tFloat)
mkFloat(v, stod(tmp_number));
else
mkInt(v, stol(tmp_number));
} catch (std::invalid_argument & e) {
throw JSONParseError("invalid JSON number");
} catch (std::out_of_range & e) {
throw JSONParseError("out-of-range JSON number");
}
bool end_object() {
rs = rs->resolve(state);
rs->add();
return true;
}

else if (strncmp(s, "true", 4) == 0) {
s += 4;
mkBool(v, true);
bool end_array() {
return end_object();
}

else if (strncmp(s, "false", 5) == 0) {
s += 5;
mkBool(v, false);
bool start_array(size_t len) {
rs = std::make_unique<JSONListState>(std::move(rs),
len != std::numeric_limits<size_t>::max() ? len : 128);
return true;
}

else if (strncmp(s, "null", 4) == 0) {
s += 4;
mkNull(v);
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) {
throw JSONParseError(ex.what());
}

else throw JSONParseError("unrecognised JSON value");
}

};

void parseJSON(EvalState & state, const string & s_, Value & v)
{
const char * s = s_.c_str();
parseJSON(state, s, v);
skipWhitespace(s);
if (*s) throw JSONParseError(format("expected end-of-string while parsing JSON value: %1%") % s);
JSONSax parser(state, v);
bool res = json::sax_parse(s_, &parser);
if (!res)
throw JSONParseError("Invalid JSON Value");
}


}