Add builtin function ‘fromJSON’

Fixes #294.
This commit is contained in:
Eelco Dolstra 2014-07-04 13:34:15 +02:00
parent e82951fe23
commit beaf3e90af
6 changed files with 217 additions and 0 deletions

View file

@ -257,6 +257,22 @@ stdenv.mkDerivation {
</varlistentry>
<varlistentry><term><function>builtins.fromJSON</function> <replaceable>e</replaceable></term>
<listitem><para>Convert a JSON string to a Nix
value. For example,
<programlisting>
builtins.fromJSON ''{"x": [1, 2, 3], "y": null}''
</programlisting>
returns the value <literal>{ x = [ 1 2 3 ]; y = null;
}</literal>. Floating point numbers are not
supported.</para></listitem>
</varlistentry>
<varlistentry><term><function>builtins.getAttr</function>
<replaceable>s</replaceable> <replaceable>set</replaceable></term>
@ -762,6 +778,7 @@ in foo</programlisting>
</varlistentry>
<varlistentry><term><function>builtins.toPath</function> <replaceable>s</replaceable></term>
<listitem><para>Convert the string value

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -0,0 +1 @@
true

View file

@ -0,0 +1,32 @@
# RFC 7159, section 13.
builtins.fromJSON
''
{
"Image": {
"Width": 800,
"Height": 600,
"Title": "View from 15th Floor",
"Thumbnail": {
"Url": "http://www.example.com/image/481989943",
"Height": 125,
"Width": 100
},
"Animated" : false,
"IDs": [116, 943, 234, 38793, true ,false,null, -100]
}
}
''
==
{ Image =
{ Width = 800;
Height = 600;
Title = "View from 15th Floor";
Thumbnail =
{ Url = http://www.example.com/image/481989943;
Height = 125;
Width = 100;
};
Animated = false;
IDs = [ 116 943 234 38793 true false null (0-100) ];
};
}