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
*/
'use strict';
/**
* @module JSONata
* @description JSON query and transformation language
@ -17,6 +16,8 @@
* @returns {{evaluate: evaluate, assign: assign}} Evaluated expression
*/
var jsonata = (function() {
'use strict';
var operators = {
'.': 75,
'[': 80,
@ -141,7 +142,7 @@ var jsonata = (function() {
position += 4;
} else {
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,
position: position
};
@ -149,7 +150,7 @@ var jsonata = (function() {
} else {
// illegal escape sequence
throw {
message: 'unsupported escape sequence: \\' + currentChar + ' at column ' + position,
message: 'unsupported escape sequence: \\' + currentChar,
stack: (new Error()).stack,
position: position,
token: currentChar
@ -165,7 +166,7 @@ var jsonata = (function() {
position++;
}
throw {
message: 'no terminating quote found in string literal at column ' + position,
message: 'no terminating quote found in string literal',
stack: (new Error()).stack,
position: position
};
@ -180,7 +181,7 @@ var jsonata = (function() {
return create('number', num);
} else {
throw {
message: 'Number out of range: ' + match[0] + ' at column ' + position,
message: 'Number out of range: ' + match[0],
stack: (new Error()).stack,
position: position,
token: match[0]
@ -270,7 +271,7 @@ var jsonata = (function() {
// unexpected end of buffer
msg = "Syntax error: expected '" + id + "' before end of expression";
} else {
msg = "Syntax error: expected '" + id + "', got '" + node.id + "' at column " + node.position;
msg = "Syntax error: expected '" + id + "', got '" + node.id;
}
throw {
message: msg ,
@ -297,7 +298,7 @@ var jsonata = (function() {
symbol = symbol_table[value];
if (!symbol) {
throw {
message: "Unknown operator: " + value + " at column " + next_token.position,
message: "Unknown operator: " + value,
stack: (new Error()).stack,
position: next_token.position,
token: value
@ -310,10 +311,10 @@ var jsonata = (function() {
type = "literal";
symbol = symbol_table["(literal)"];
break;
/* istanbul ignore next */
/* istanbul ignore next */
default:
throw {
message: "Unexpected token:" + value + " at column " + next_token.position,
message: "Unexpected token:" + value,
stack: (new Error()).stack,
position: next_token.position,
token: value
@ -483,28 +484,7 @@ var jsonata = (function() {
return this;
});
// object 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
// array constructor
prefix("[", function () {
var a = [];
if (node.id !== "]") {
@ -532,21 +512,57 @@ var jsonata = (function() {
// filter - predicate or array index
infix("[", operators['['], function (left) {
this.lhs = left;
this.rhs = expression(operators[']']);
this.type = 'binary';
advance("]");
return this;
if(node.id === "]") {
// empty predicate means maintain singleton arrays in the output
var step = left;
while(step && step.type === 'binary' && step.value === '[') {
step = step.lhs;
}
step.keepArray = true;
advance("]");
return left;
} else {
this.lhs = left;
this.rhs = expression(operators[']']);
this.type = 'binary';
advance("]");
return this;
}
});
// aggregator
infix("{", operators['{'], function (left) {
this.lhs = left;
this.rhs = expression(operators['}']);
this.type = 'binary';
var objectParser = function (left) {
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("}");
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;
});
};
// object constructor
prefix("{", objectParser);
// object grouping
infix("{", operators['{'], objectParser);
// if/then/else ternary operator ?:
infix("?", operators['?'], function (left) {
@ -600,13 +616,16 @@ var jsonata = (function() {
case 'binary':
switch (expr.value) {
case '.':
var step = post_parse(expr.lhs);
if (Array.isArray(step)) {
Array.prototype.push.apply(result, step);
var lstep = post_parse(expr.lhs);
if (lstep.type === 'path') {
Array.prototype.push.apply(result, lstep);
} 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);
result.type = 'path';
break;
@ -615,31 +634,41 @@ var jsonata = (function() {
// LHS is a step or a predicated step
// RHS is the predicate expr
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 {
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,
position: expr.position
};
}
if (typeof result.predicate === 'undefined') {
result.predicate = [];
if (typeof step.predicate === 'undefined') {
step.predicate = [];
}
result.predicate.push(post_parse(expr.rhs));
step.predicate.push(post_parse(expr.rhs));
break;
case '{':
// aggregate
// group-by
// LHS is a step or a predicated step
// RHS is the predicate expr
// RHS is the object constructor expr
result = post_parse(expr.lhs);
if (typeof result.aggregate !== 'undefined') {
if (typeof result.group !== 'undefined') {
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,
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;
default:
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
break;
case 'name':
result = [expr];
result.type = 'path';
break;
case 'literal':
case 'wildcard':
case 'descendant':
@ -716,7 +748,7 @@ var jsonata = (function() {
result = expr;
} else {
throw {
message: "Syntax error: " + expr.value + " at column " + expr.position,
message: "Syntax error: " + expr.value,
stack: (new Error()).stack,
position: expr.position,
token: expr.value
@ -724,7 +756,7 @@ var jsonata = (function() {
}
break;
default:
var reason = "Unknown expression type: " + expr.value + " at column " + expr.position;
var reason = "Unknown expression type: " + expr.value;
/* istanbul ignore else */
if (expr.id === '(end)') {
reason = "Syntax error: unexpected end of expression";
@ -747,7 +779,7 @@ var jsonata = (function() {
var expr = expression(0);
if (node.id !== '(end)') {
throw {
message: "Syntax error: " + node.value + " at column " + node.position,
message: "Syntax error: " + node.value,
stack: (new Error()).stack,
position: node.position,
token: node.value
@ -755,12 +787,6 @@ var jsonata = (function() {
}
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;
};
@ -865,8 +891,8 @@ var jsonata = (function() {
if (expr.hasOwnProperty('predicate')) {
result = applyPredicates(expr.predicate, result, environment);
}
if (expr.hasOwnProperty('aggregate')) {
result = applyAggregate(expr.aggregate, result, environment);
if (expr.hasOwnProperty('group')) {
result = evaluateGroupExpression(expr.group, result, environment);
}
var exitCallback = environment.lookup('__evaluate_exit');
@ -887,6 +913,7 @@ var jsonata = (function() {
function evaluatePath(expr, input, environment) {
var result;
var inputSequence;
var keepSingletonArray = false;
// expr is an array of steps
// if the first step is a variable reference ($...), including root reference ($$),
// then the path is absolute rather than relative
@ -898,7 +925,11 @@ var jsonata = (function() {
}
// 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 = [];
result = undefined;
// if input is not an array, make it so
@ -913,35 +944,36 @@ var jsonata = (function() {
if (expr.length > 1 && step.type === 'literal') {
step.type = 'name';
}
if (step.value === '{') {
if(typeof input !== 'undefined') {
result = evaluateGroupExpression(step, inputSequence, environment);
}
} else {
inputSequence.forEach(function (item) {
var res = evaluate(step, item, environment);
if (typeof res !== 'undefined') {
if (Array.isArray(res)) {
// is res an array - if so, flatten it into the parent array
res.forEach(function (innerRes) {
if (typeof innerRes !== 'undefined') {
resultSequence.push(innerRes);
}
});
} else {
resultSequence.push(res);
}
inputSequence.forEach(function (item) {
var res = evaluate(step, item, environment);
if (typeof res !== 'undefined') {
if (Array.isArray(res) && (step.value !== '[' )) {
// is res an array - if so, flatten it into the parent array
res.forEach(function (innerRes) {
if (typeof innerRes !== 'undefined') {
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;
});
}
return result;
}
@ -1016,35 +1048,6 @@ var jsonata = (function() {
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
* @param {Object} expr - JSONata expression
@ -1108,7 +1111,7 @@ var jsonata = (function() {
result = -result;
} else {
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,
position: expr.position,
token: expr.value,
@ -1122,11 +1125,10 @@ var jsonata = (function() {
expr.lhs.forEach(function (item) {
var value = evaluate(item, input, environment);
if (typeof value !== 'undefined') {
if (item.value === '..') {
// array generated by the range operator - merge into results
result = functionAppend(result, value);
} else {
if(item.value === '[') {
result.push(value);
} else {
result = functionAppend(result, value);
}
}
});
@ -1289,7 +1291,7 @@ var jsonata = (function() {
if (!isNumeric(lhs)) {
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,
position: expr.position,
token: expr.value,
@ -1298,7 +1300,7 @@ var jsonata = (function() {
}
if (!isNumeric(rhs)) {
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,
position: expr.position,
token: expr.value,
@ -1413,11 +1415,11 @@ var jsonata = (function() {
switch (expr.value) {
case 'and':
result = functionBoolean(evaluate(expr.lhs, input, environment)) &&
functionBoolean(evaluate(expr.rhs, input, environment));
functionBoolean(evaluate(expr.rhs, input, environment));
break;
case 'or':
result = functionBoolean(evaluate(expr.lhs, input, environment)) ||
functionBoolean(evaluate(expr.rhs, input, environment));
functionBoolean(evaluate(expr.rhs, input, environment));
break;
}
return result;
@ -1468,7 +1470,7 @@ var jsonata = (function() {
// key has to be a string
if (typeof key !== 'string') {
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,
position: expr.position,
value: key
@ -1518,7 +1520,7 @@ var jsonata = (function() {
if (!Number.isInteger(lhs)) {
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,
position: expr.position,
token: expr.value,
@ -1527,7 +1529,7 @@ var jsonata = (function() {
}
if (!Number.isInteger(rhs)) {
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,
position: expr.position,
token: expr.value,
@ -1555,11 +1557,11 @@ var jsonata = (function() {
var value = evaluate(expr.rhs, input, environment);
if (expr.lhs.type !== 'variable') {
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,
position: expr.position,
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);
@ -1645,13 +1647,13 @@ var jsonata = (function() {
// evaluate it generically first, then check that it is a function. Throw error if not.
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 $
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,
position: expr.position,
token: expr.procedure.value
token: expr.procedure[0].value
};
}
// apply the procedure
@ -1673,7 +1675,7 @@ var jsonata = (function() {
// add the position field to the error
err.position = expr.position;
// and the function identifier
err.token = expr.procedure.value;
err.token = expr.procedure.type === 'path' ? expr.procedure[0].value : expr.procedure.value;
throw err;
}
return result;
@ -1745,13 +1747,13 @@ var jsonata = (function() {
});
// lookup the procedure
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 $
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,
position: expr.position,
token: expr.procedure.value
token: expr.procedure[0].value
};
}
if (proc && proc.lambda) {
@ -1760,10 +1762,10 @@ var jsonata = (function() {
result = partialApplyNativeFunction(proc, evaluatedArgs);
} else {
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,
position: expr.position,
token: expr.procedure.value
token: expr.procedure.type === 'path' ? expr.procedure[0].value : expr.procedure.value
};
}
return result;
@ -2097,8 +2099,8 @@ var jsonata = (function() {
} else
str = JSON.stringify(arg, function (key, val) {
return (typeof val !== 'undefined' && val !== null && val.toPrecision && isNumeric(val)) ? Number(val.toPrecision(13)) :
(val && isLambda(val)) ? '' :
(typeof val === 'function') ? '' : val;
(val && isLambda(val)) ? '' :
(typeof val === 'function') ? '' : val;
});
return str;
}
@ -2788,9 +2790,6 @@ var jsonata = (function() {
},
assign: function (name, value) {
environment.bind(name, value);
},
ast: function() {
return ast;
}
};
}

View File

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