Update jsonata version

This commit is contained in:
Nick O'Leary 2016-11-18 16:38:48 +00:00
parent eeaff6b553
commit eaa4b76ede
2 changed files with 150 additions and 151 deletions

View File

@ -4,7 +4,6 @@
* This project is licensed under the MIT License, see LICENSE * This project is licensed under the MIT License, see LICENSE
*/ */
'use strict';
/** /**
* @module JSONata * @module JSONata
* @description JSON query and transformation language * @description JSON query and transformation language
@ -17,6 +16,8 @@
* @returns {{evaluate: evaluate, assign: assign}} Evaluated expression * @returns {{evaluate: evaluate, assign: assign}} Evaluated expression
*/ */
var jsonata = (function() { var jsonata = (function() {
'use strict';
var operators = { var operators = {
'.': 75, '.': 75,
'[': 80, '[': 80,
@ -141,7 +142,7 @@ var jsonata = (function() {
position += 4; position += 4;
} else { } else {
throw { throw {
message: "The escape sequence \\u must be followed by 4 hex digits at column " + position, message: "The escape sequence \\u must be followed by 4 hex digits",
stack: (new Error()).stack, stack: (new Error()).stack,
position: position position: position
}; };
@ -149,7 +150,7 @@ var jsonata = (function() {
} else { } else {
// illegal escape sequence // illegal escape sequence
throw { throw {
message: 'unsupported escape sequence: \\' + currentChar + ' at column ' + position, message: 'unsupported escape sequence: \\' + currentChar,
stack: (new Error()).stack, stack: (new Error()).stack,
position: position, position: position,
token: currentChar token: currentChar
@ -165,7 +166,7 @@ var jsonata = (function() {
position++; position++;
} }
throw { throw {
message: 'no terminating quote found in string literal at column ' + position, message: 'no terminating quote found in string literal',
stack: (new Error()).stack, stack: (new Error()).stack,
position: position position: position
}; };
@ -180,7 +181,7 @@ var jsonata = (function() {
return create('number', num); return create('number', num);
} else { } else {
throw { throw {
message: 'Number out of range: ' + match[0] + ' at column ' + position, message: 'Number out of range: ' + match[0],
stack: (new Error()).stack, stack: (new Error()).stack,
position: position, position: position,
token: match[0] token: match[0]
@ -270,7 +271,7 @@ var jsonata = (function() {
// unexpected end of buffer // unexpected end of buffer
msg = "Syntax error: expected '" + id + "' before end of expression"; msg = "Syntax error: expected '" + id + "' before end of expression";
} else { } else {
msg = "Syntax error: expected '" + id + "', got '" + node.id + "' at column " + node.position; msg = "Syntax error: expected '" + id + "', got '" + node.id;
} }
throw { throw {
message: msg , message: msg ,
@ -297,7 +298,7 @@ var jsonata = (function() {
symbol = symbol_table[value]; symbol = symbol_table[value];
if (!symbol) { if (!symbol) {
throw { throw {
message: "Unknown operator: " + value + " at column " + next_token.position, message: "Unknown operator: " + value,
stack: (new Error()).stack, stack: (new Error()).stack,
position: next_token.position, position: next_token.position,
token: value token: value
@ -310,10 +311,10 @@ var jsonata = (function() {
type = "literal"; type = "literal";
symbol = symbol_table["(literal)"]; symbol = symbol_table["(literal)"];
break; break;
/* istanbul ignore next */ /* istanbul ignore next */
default: default:
throw { throw {
message: "Unexpected token:" + value + " at column " + next_token.position, message: "Unexpected token:" + value,
stack: (new Error()).stack, stack: (new Error()).stack,
position: next_token.position, position: next_token.position,
token: value token: value
@ -483,28 +484,7 @@ var jsonata = (function() {
return this; return this;
}); });
// object constructor // array constructor
prefix("{", function () {
var a = [];
if (node.id !== "}") {
for (;;) {
var n = expression(0);
advance(":");
var v = expression(0);
a.push([n, v]); // holds an array of name/value expression pairs
if (node.id !== ",") {
break;
}
advance(",");
}
}
advance("}");
this.lhs = a;
this.type = "unary";
return this;
});
// array constructor
prefix("[", function () { prefix("[", function () {
var a = []; var a = [];
if (node.id !== "]") { if (node.id !== "]") {
@ -532,21 +512,57 @@ var jsonata = (function() {
// filter - predicate or array index // filter - predicate or array index
infix("[", operators['['], function (left) { infix("[", operators['['], function (left) {
this.lhs = left; if(node.id === "]") {
this.rhs = expression(operators[']']); // empty predicate means maintain singleton arrays in the output
this.type = 'binary'; var step = left;
advance("]"); while(step && step.type === 'binary' && step.value === '[') {
return this; step = step.lhs;
}
step.keepArray = true;
advance("]");
return left;
} else {
this.lhs = left;
this.rhs = expression(operators[']']);
this.type = 'binary';
advance("]");
return this;
}
}); });
// aggregator var objectParser = function (left) {
infix("{", operators['{'], function (left) { var a = [];
this.lhs = left; if (node.id !== "}") {
this.rhs = expression(operators['}']); for (;;) {
this.type = 'binary'; var n = expression(0);
advance(":");
var v = expression(0);
a.push([n, v]); // holds an array of name/value expression pairs
if (node.id !== ",") {
break;
}
advance(",");
}
}
advance("}"); advance("}");
if(typeof left === 'undefined') {
// NUD - unary prefix form
this.lhs = a;
this.type = "unary";
} else {
// LED - binary infix form
this.lhs = left;
this.rhs = a;
this.type = 'binary';
}
return this; return this;
}); };
// object constructor
prefix("{", objectParser);
// object grouping
infix("{", operators['{'], objectParser);
// if/then/else ternary operator ?: // if/then/else ternary operator ?:
infix("?", operators['?'], function (left) { infix("?", operators['?'], function (left) {
@ -600,13 +616,16 @@ var jsonata = (function() {
case 'binary': case 'binary':
switch (expr.value) { switch (expr.value) {
case '.': case '.':
var step = post_parse(expr.lhs); var lstep = post_parse(expr.lhs);
if (Array.isArray(step)) { if (lstep.type === 'path') {
Array.prototype.push.apply(result, step); Array.prototype.push.apply(result, lstep);
} else { } else {
result.push(step); result.push(lstep);
}
var rest = post_parse(expr.rhs);
if(rest.type !== 'path') {
rest = [rest];
} }
var rest = [post_parse(expr.rhs)];
Array.prototype.push.apply(result, rest); Array.prototype.push.apply(result, rest);
result.type = 'path'; result.type = 'path';
break; break;
@ -615,31 +634,41 @@ var jsonata = (function() {
// LHS is a step or a predicated step // LHS is a step or a predicated step
// RHS is the predicate expr // RHS is the predicate expr
result = post_parse(expr.lhs); result = post_parse(expr.lhs);
if (typeof result.aggregate !== 'undefined') { var step = result;
if(result.type === 'path') {
step = result[result.length - 1];
}
if (typeof step.group !== 'undefined') {
throw { throw {
message: 'A predicate cannot follow an aggregate in a step. Error at column: ' + expr.position, message: 'A predicate cannot follow a grouping expression in a step. Error at column: ' + expr.position,
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position position: expr.position
}; };
} }
if (typeof result.predicate === 'undefined') { if (typeof step.predicate === 'undefined') {
result.predicate = []; step.predicate = [];
} }
result.predicate.push(post_parse(expr.rhs)); step.predicate.push(post_parse(expr.rhs));
break; break;
case '{': case '{':
// aggregate // group-by
// LHS is a step or a predicated step // LHS is a step or a predicated step
// RHS is the predicate expr // RHS is the object constructor expr
result = post_parse(expr.lhs); result = post_parse(expr.lhs);
if (typeof result.aggregate !== 'undefined') { if (typeof result.group !== 'undefined') {
throw { throw {
message: 'Each step can only have one aggregator. Error at column: ' + expr.position, message: 'Each step can only have one grouping expression. Error at column: ' + expr.position,
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position position: expr.position
}; };
} }
result.aggregate = post_parse(expr.rhs); // object constructor - process each pair
result.group = {
lhs: expr.rhs.map(function (pair) {
return [post_parse(pair[0]), post_parse(pair[1])];
}),
position: expr.position
};
break; break;
default: default:
result = {type: expr.type, value: expr.value, position: expr.position}; result = {type: expr.type, value: expr.value, position: expr.position};
@ -700,6 +729,9 @@ var jsonata = (function() {
// if so, need to mark the block as one that needs to create a new frame // if so, need to mark the block as one that needs to create a new frame
break; break;
case 'name': case 'name':
result = [expr];
result.type = 'path';
break;
case 'literal': case 'literal':
case 'wildcard': case 'wildcard':
case 'descendant': case 'descendant':
@ -716,7 +748,7 @@ var jsonata = (function() {
result = expr; result = expr;
} else { } else {
throw { throw {
message: "Syntax error: " + expr.value + " at column " + expr.position, message: "Syntax error: " + expr.value,
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.value token: expr.value
@ -724,7 +756,7 @@ var jsonata = (function() {
} }
break; break;
default: default:
var reason = "Unknown expression type: " + expr.value + " at column " + expr.position; var reason = "Unknown expression type: " + expr.value;
/* istanbul ignore else */ /* istanbul ignore else */
if (expr.id === '(end)') { if (expr.id === '(end)') {
reason = "Syntax error: unexpected end of expression"; reason = "Syntax error: unexpected end of expression";
@ -747,7 +779,7 @@ var jsonata = (function() {
var expr = expression(0); var expr = expression(0);
if (node.id !== '(end)') { if (node.id !== '(end)') {
throw { throw {
message: "Syntax error: " + node.value + " at column " + node.position, message: "Syntax error: " + node.value,
stack: (new Error()).stack, stack: (new Error()).stack,
position: node.position, position: node.position,
token: node.value token: node.value
@ -755,12 +787,6 @@ var jsonata = (function() {
} }
expr = post_parse(expr); expr = post_parse(expr);
// a single name token is a single step location path
if (expr.type === 'name') {
expr = [expr];
expr.type = 'path';
}
return expr; return expr;
}; };
@ -865,8 +891,8 @@ var jsonata = (function() {
if (expr.hasOwnProperty('predicate')) { if (expr.hasOwnProperty('predicate')) {
result = applyPredicates(expr.predicate, result, environment); result = applyPredicates(expr.predicate, result, environment);
} }
if (expr.hasOwnProperty('aggregate')) { if (expr.hasOwnProperty('group')) {
result = applyAggregate(expr.aggregate, result, environment); result = evaluateGroupExpression(expr.group, result, environment);
} }
var exitCallback = environment.lookup('__evaluate_exit'); var exitCallback = environment.lookup('__evaluate_exit');
@ -887,6 +913,7 @@ var jsonata = (function() {
function evaluatePath(expr, input, environment) { function evaluatePath(expr, input, environment) {
var result; var result;
var inputSequence; var inputSequence;
var keepSingletonArray = false;
// expr is an array of steps // expr is an array of steps
// if the first step is a variable reference ($...), including root reference ($$), // if the first step is a variable reference ($...), including root reference ($$),
// then the path is absolute rather than relative // then the path is absolute rather than relative
@ -898,7 +925,11 @@ var jsonata = (function() {
} }
// evaluate each step in turn // evaluate each step in turn
expr.forEach(function (step) { for(var ii = 0; ii < expr.length; ii++) {
var step = expr[ii];
if(step.keepArray === true) {
keepSingletonArray = true;
}
var resultSequence = []; var resultSequence = [];
result = undefined; result = undefined;
// if input is not an array, make it so // if input is not an array, make it so
@ -913,35 +944,36 @@ var jsonata = (function() {
if (expr.length > 1 && step.type === 'literal') { if (expr.length > 1 && step.type === 'literal') {
step.type = 'name'; step.type = 'name';
} }
if (step.value === '{') { inputSequence.forEach(function (item) {
if(typeof input !== 'undefined') { var res = evaluate(step, item, environment);
result = evaluateGroupExpression(step, inputSequence, environment); if (typeof res !== 'undefined') {
} if (Array.isArray(res) && (step.value !== '[' )) {
} else { // is res an array - if so, flatten it into the parent array
inputSequence.forEach(function (item) { res.forEach(function (innerRes) {
var res = evaluate(step, item, environment); if (typeof innerRes !== 'undefined') {
if (typeof res !== 'undefined') { resultSequence.push(innerRes);
if (Array.isArray(res)) { }
// is res an array - if so, flatten it into the parent array });
res.forEach(function (innerRes) { } else {
if (typeof innerRes !== 'undefined') { resultSequence.push(res);
resultSequence.push(innerRes);
}
});
} else {
resultSequence.push(res);
}
} }
});
if (resultSequence.length == 1) {
result = resultSequence[0];
} else if (resultSequence.length > 1) {
result = resultSequence;
} }
});
if (resultSequence.length == 1) {
if(keepSingletonArray) {
result = resultSequence;
} else {
result = resultSequence[0];
}
} else if (resultSequence.length > 1) {
result = resultSequence;
} }
if(typeof result === 'undefined') {
break;
}
input = result; input = result;
}); }
return result; return result;
} }
@ -1016,35 +1048,6 @@ var jsonata = (function() {
return result; return result;
} }
/**
* Apply aggregate to input data
* @param {Object} expr - JSONata expression
* @param {Object} input - Input data to evaluate against
* @param {Object} environment - Environment
* @returns {{}} Result after applying aggregate
*/
function applyAggregate(expr, input, environment) {
var result = {};
// this is effectively a 'reduce' HOF (fold left)
// if the input is a singleton, then just return this as the result
// otherwise iterate over the input array and aggregate the result
if (Array.isArray(input)) {
// create a new frame to limit the scope
var aggEnv = createFrame(environment);
// the variable $@ will hold the aggregated value, initialize this to the first array item
aggEnv.bind('_', input[0]);
// loop over the remainder of the array
for (var index = 1; index < input.length; index++) {
var reduce = evaluate(expr, input[index], aggEnv);
aggEnv.bind('_', reduce);
}
result = aggEnv.lookup('_');
} else {
result = input;
}
return result;
}
/** /**
* Evaluate binary expression against input data * Evaluate binary expression against input data
* @param {Object} expr - JSONata expression * @param {Object} expr - JSONata expression
@ -1108,7 +1111,7 @@ var jsonata = (function() {
result = -result; result = -result;
} else { } else {
throw { throw {
message: "Cannot negate a non-numeric value: " + result + " at column " + expr.position, message: "Cannot negate a non-numeric value: " + result,
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.value, token: expr.value,
@ -1122,11 +1125,10 @@ var jsonata = (function() {
expr.lhs.forEach(function (item) { expr.lhs.forEach(function (item) {
var value = evaluate(item, input, environment); var value = evaluate(item, input, environment);
if (typeof value !== 'undefined') { if (typeof value !== 'undefined') {
if (item.value === '..') { if(item.value === '[') {
// array generated by the range operator - merge into results
result = functionAppend(result, value);
} else {
result.push(value); result.push(value);
} else {
result = functionAppend(result, value);
} }
} }
}); });
@ -1289,7 +1291,7 @@ var jsonata = (function() {
if (!isNumeric(lhs)) { if (!isNumeric(lhs)) {
throw { throw {
message: 'LHS of ' + expr.value + ' operator must evaluate to a number at column ' + expr.position, message: 'LHS of ' + expr.value + ' operator must evaluate to a number',
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.value, token: expr.value,
@ -1298,7 +1300,7 @@ var jsonata = (function() {
} }
if (!isNumeric(rhs)) { if (!isNumeric(rhs)) {
throw { throw {
message: 'RHS of ' + expr.value + ' operator must evaluate to a number at column ' + expr.position, message: 'RHS of ' + expr.value + ' operator must evaluate to a number',
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.value, token: expr.value,
@ -1413,11 +1415,11 @@ var jsonata = (function() {
switch (expr.value) { switch (expr.value) {
case 'and': case 'and':
result = functionBoolean(evaluate(expr.lhs, input, environment)) && result = functionBoolean(evaluate(expr.lhs, input, environment)) &&
functionBoolean(evaluate(expr.rhs, input, environment)); functionBoolean(evaluate(expr.rhs, input, environment));
break; break;
case 'or': case 'or':
result = functionBoolean(evaluate(expr.lhs, input, environment)) || result = functionBoolean(evaluate(expr.lhs, input, environment)) ||
functionBoolean(evaluate(expr.rhs, input, environment)); functionBoolean(evaluate(expr.rhs, input, environment));
break; break;
} }
return result; return result;
@ -1468,7 +1470,7 @@ var jsonata = (function() {
// key has to be a string // key has to be a string
if (typeof key !== 'string') { if (typeof key !== 'string') {
throw { throw {
message: 'Key in object structure must evaluate to a string. Got: ' + key + ' at column ' + expr.position, message: 'Key in object structure must evaluate to a string. Got: ' + key,
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
value: key value: key
@ -1518,7 +1520,7 @@ var jsonata = (function() {
if (!Number.isInteger(lhs)) { if (!Number.isInteger(lhs)) {
throw { throw {
message: 'LHS of range operator (..) must evaluate to an integer at column ' + expr.position, message: 'LHS of range operator (..) must evaluate to an integer',
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.value, token: expr.value,
@ -1527,7 +1529,7 @@ var jsonata = (function() {
} }
if (!Number.isInteger(rhs)) { if (!Number.isInteger(rhs)) {
throw { throw {
message: 'RHS of range operator (..) must evaluate to an integer at column ' + expr.position, message: 'RHS of range operator (..) must evaluate to an integer',
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.value, token: expr.value,
@ -1555,11 +1557,11 @@ var jsonata = (function() {
var value = evaluate(expr.rhs, input, environment); var value = evaluate(expr.rhs, input, environment);
if (expr.lhs.type !== 'variable') { if (expr.lhs.type !== 'variable') {
throw { throw {
message: "Left hand side of := must be a variable name (start with $) at column " + expr.position, message: "Left hand side of := must be a variable name (start with $)",
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.value, token: expr.value,
value: expr.lhs.value value: expr.lhs.type === 'path' ? expr.lhs[0].value : expr.lhs.value
}; };
} }
environment.bind(expr.lhs.value, value); environment.bind(expr.lhs.value, value);
@ -1645,13 +1647,13 @@ var jsonata = (function() {
// evaluate it generically first, then check that it is a function. Throw error if not. // evaluate it generically first, then check that it is a function. Throw error if not.
var proc = evaluate(expr.procedure, input, environment); var proc = evaluate(expr.procedure, input, environment);
if (typeof proc === 'undefined' && expr.procedure.type === 'name' && environment.lookup(expr.procedure.value)) { if (typeof proc === 'undefined' && expr.procedure.type === 'path' && environment.lookup(expr.procedure[0].value)) {
// help the user out here if they simply forgot the leading $ // help the user out here if they simply forgot the leading $
throw { throw {
message: 'Attempted to invoke a non-function at column ' + expr.position + '. Did you mean \'$' + expr.procedure.value + '\'?', message: 'Attempted to invoke a non-function. Did you mean \'$' + expr.procedure[0].value + '\'?',
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.procedure.value token: expr.procedure[0].value
}; };
} }
// apply the procedure // apply the procedure
@ -1673,7 +1675,7 @@ var jsonata = (function() {
// add the position field to the error // add the position field to the error
err.position = expr.position; err.position = expr.position;
// and the function identifier // and the function identifier
err.token = expr.procedure.value; err.token = expr.procedure.type === 'path' ? expr.procedure[0].value : expr.procedure.value;
throw err; throw err;
} }
return result; return result;
@ -1745,13 +1747,13 @@ var jsonata = (function() {
}); });
// lookup the procedure // lookup the procedure
var proc = evaluate(expr.procedure, input, environment); var proc = evaluate(expr.procedure, input, environment);
if (typeof proc === 'undefined' && expr.procedure.type === 'name' && environment.lookup(expr.procedure.value)) { if (typeof proc === 'undefined' && expr.procedure.type === 'path' && environment.lookup(expr.procedure[0].value)) {
// help the user out here if they simply forgot the leading $ // help the user out here if they simply forgot the leading $
throw { throw {
message: 'Attempted to partially apply a non-function at column ' + expr.position + '. Did you mean \'$' + expr.procedure.value + '\'?', message: 'Attempted to partially apply a non-function. Did you mean \'$' + expr.procedure[0].value + '\'?',
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.procedure.value token: expr.procedure[0].value
}; };
} }
if (proc && proc.lambda) { if (proc && proc.lambda) {
@ -1760,10 +1762,10 @@ var jsonata = (function() {
result = partialApplyNativeFunction(proc, evaluatedArgs); result = partialApplyNativeFunction(proc, evaluatedArgs);
} else { } else {
throw { throw {
message: 'Attempted to partially apply a non-function at column ' + expr.position, message: 'Attempted to partially apply a non-function',
stack: (new Error()).stack, stack: (new Error()).stack,
position: expr.position, position: expr.position,
token: expr.procedure.value token: expr.procedure.type === 'path' ? expr.procedure[0].value : expr.procedure.value
}; };
} }
return result; return result;
@ -2097,8 +2099,8 @@ var jsonata = (function() {
} else } else
str = JSON.stringify(arg, function (key, val) { str = JSON.stringify(arg, function (key, val) {
return (typeof val !== 'undefined' && val !== null && val.toPrecision && isNumeric(val)) ? Number(val.toPrecision(13)) : return (typeof val !== 'undefined' && val !== null && val.toPrecision && isNumeric(val)) ? Number(val.toPrecision(13)) :
(val && isLambda(val)) ? '' : (val && isLambda(val)) ? '' :
(typeof val === 'function') ? '' : val; (typeof val === 'function') ? '' : val;
}); });
return str; return str;
} }
@ -2788,9 +2790,6 @@ var jsonata = (function() {
}, },
assign: function (name, value) { assign: function (name, value) {
environment.bind(name, value); environment.bind(name, value);
},
ast: function() {
return ast;
} }
}; };
} }

View File

@ -40,7 +40,7 @@
"fs.notify":"0.0.4", "fs.notify":"0.0.4",
"i18next":"1.10.6", "i18next":"1.10.6",
"is-utf8":"0.2.1", "is-utf8":"0.2.1",
"jsonata":"1.0.7", "jsonata":"1.0.10",
"media-typer": "0.3.0", "media-typer": "0.3.0",
"mqtt": "1.14.1", "mqtt": "1.14.1",
"mustache": "2.2.1", "mustache": "2.2.1",