diff options
author | Eelco Dolstra <eelco.dolstra@logicblox.com> | 2014-07-04 13:34:15 +0200 |
---|---|---|
committer | Eelco Dolstra <eelco.dolstra@logicblox.com> | 2014-07-04 13:34:15 +0200 |
commit | beaf3e90aff14664b98f2c7ab7387c9fa4354fd1 (patch) | |
tree | cc1bab6524c7e616697efbae788e4b997a3213c4 /src/libexpr | |
parent | e82951fe23daa961ef18b0c5cc9ba1f5d8906186 (diff) |
Add builtin function ‘fromJSON’
Fixes #294.
Diffstat (limited to 'src/libexpr')
-rw-r--r-- | src/libexpr/json-to-value.cc | 144 | ||||
-rw-r--r-- | src/libexpr/json-to-value.hh | 13 | ||||
-rw-r--r-- | src/libexpr/primops.cc | 10 |
3 files changed, 167 insertions, 0 deletions
diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc new file mode 100644 index 000000000..7f60e7cc6 --- /dev/null +++ b/src/libexpr/json-to-value.cc @@ -0,0 +1,144 @@ +#include "config.h" +#include "json-to-value.hh" + +#include <cstring> + +namespace nix { + + +static void skipWhitespace(const char * & s) +{ + while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; +} + + +#if HAVE_BOEHMGC +typedef std::vector<Value *, gc_allocator<Value *> > ValueVector; +#else +typedef std::vector<Value *> ValueVector; +#endif + + +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 == '/') 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') throw JSONParseError("\\u characters in JSON strings are currently not supported"); + else throw JSONParseError("invalid escaped character in JSON string"); + s++; + } else + res += *s++; + } + 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.list.elems[n] = values[n]; + } + + else if (*s == '{') { + s++; + state.mkAttrs(v, 1); + while (1) { + skipWhitespace(s); + if (v.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); + v.attrs->push_back(Attr(state.symbols.create(name), v2)); + skipWhitespace(s); + if (*s == '}') break; + if (*s != ',') throw JSONParseError("expected `,' or `}' after JSON member"); + s++; + } + v.attrs->sort(); + s++; + } + + else if (*s == '"') { + mkString(v, parseJSONString(s)); + } + + else if (isdigit(*s) || *s == '-') { + bool neg = false; + if (*s == '-') { + neg = true; + if (!*++s) throw JSONParseError("unexpected end of JSON number"); + } + NixInt n = 0; + // FIXME: detect overflow + while (isdigit(*s)) n = n * 10 + (*s++ - '0'); + if (*s == '.' || *s == 'e') throw JSONParseError("floating point JSON numbers are not supported"); + mkInt(v, neg ? -n : n); + } + + else if (strncmp(s, "true", 4) == 0) { + s += 4; + mkBool(v, true); + } + + else if (strncmp(s, "false", 5) == 0) { + s += 5; + mkBool(v, false); + } + + else if (strncmp(s, "null", 4) == 0) { + s += 4; + mkNull(v); + } + + 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); +} + + +} diff --git a/src/libexpr/json-to-value.hh b/src/libexpr/json-to-value.hh new file mode 100644 index 000000000..33f35b16c --- /dev/null +++ b/src/libexpr/json-to-value.hh @@ -0,0 +1,13 @@ +#pragma once + +#include "eval.hh" + +#include <string> + +namespace nix { + +MakeError(JSONParseError, EvalError) + +void parseJSON(EvalState & state, const string & s, Value & v); + +} diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index ff82f36b5..01c7ca444 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -6,6 +6,7 @@ #include "archive.hh" #include "value-to-xml.hh" #include "value-to-json.hh" +#include "json-to-value.hh" #include "names.hh" #include "eval-inline.hh" @@ -775,6 +776,14 @@ static void prim_toJSON(EvalState & state, const Pos & pos, Value * * args, Valu } +/* Parse a JSON string to a value. */ +static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + string s = state.forceStringNoCtx(*args[0], pos); + parseJSON(state, s, v); +} + + /* Store a string in the Nix store as a source file that can be used as an input by derivations. */ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1396,6 +1405,7 @@ void EvalState::createBaseEnv() // Creating files addPrimOp("__toXML", 1, prim_toXML); addPrimOp("__toJSON", 1, prim_toJSON); + addPrimOp("__fromJSON", 1, prim_fromJSON); addPrimOp("__toFile", 2, prim_toFile); addPrimOp("__filterSource", 2, prim_filterSource); |