Skip to content

Commit 42ad469

Browse files
andrew-colemanmattbaileyuk
authored andcommitted
add $eval function
Signed-off-by: andrew-coleman <andrew_coleman@uk.ibm.com>
1 parent d157114 commit 42ad469

File tree

9 files changed

+179
-25
lines changed

9 files changed

+179
-25
lines changed

src/jsonata.js

Lines changed: 97 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
* @description JSON query and transformation language
1010
*/
1111

12-
var regeneratorRuntime = require("regenerator-runtime");
12+
// istanbul ignore else
13+
if(typeof module !== 'undefined') {
14+
var regeneratorRuntime = require("regenerator-runtime");
15+
}
1316

1417
/**
1518
* jsonata
@@ -2542,7 +2545,12 @@ var jsonata = (function() {
25422545
return comp === 1;
25432546
};
25442547

2545-
result = yield * functionSort(lhs, comparator);
2548+
var focus = {
2549+
environment: environment,
2550+
input: input
2551+
};
2552+
// the `focus` is passed in as the `this` for the invoked function
2553+
result = yield * functionSort.apply(focus, [lhs, comparator]);
25462554

25472555
return result;
25482556
}
@@ -2572,7 +2580,7 @@ var jsonata = (function() {
25722580
position: expr.position
25732581
};
25742582
}
2575-
var result = yield * apply(cloneFunction, [obj], null);
2583+
var result = yield * apply(cloneFunction, [obj], null, environment);
25762584
var matches = yield * evaluate(expr.pattern, result, environment);
25772585
if(typeof matches !== 'undefined') {
25782586
if(!Array.isArray(matches)) {
@@ -2666,9 +2674,9 @@ var jsonata = (function() {
26662674
// this is function chaining (func1 ~> func2)
26672675
// λ($f, $g) { λ($x){ $g($f($x)) } }
26682676
var chain = yield * evaluate(chainAST, null, environment);
2669-
result = yield * apply(chain, [lhs, func], null);
2677+
result = yield * apply(chain, [lhs, func], null, environment);
26702678
} else {
2671-
result = yield * apply(func, [lhs], null);
2679+
result = yield * apply(func, [lhs], null, environment);
26722680
}
26732681

26742682
}
@@ -2713,7 +2721,6 @@ var jsonata = (function() {
27132721
* @param {Object} expr - JSONata expression
27142722
* @param {Object} input - Input data to evaluate against
27152723
* @param {Object} environment - Environment
2716-
* @param {Object} [applyto] - LHS of ~> operator
27172724
* @returns {*} Evaluated input data
27182725
*/
27192726
function* evaluateFunction(expr, input, environment) {
@@ -2742,7 +2749,7 @@ var jsonata = (function() {
27422749
}
27432750
// apply the procedure
27442751
try {
2745-
result = yield * apply(proc, evaluatedArgs, input);
2752+
result = yield * apply(proc, evaluatedArgs, input, environment);
27462753
} catch (err) {
27472754
// add the position field to the error
27482755
err.position = expr.position;
@@ -2757,12 +2764,13 @@ var jsonata = (function() {
27572764
* Apply procedure or function
27582765
* @param {Object} proc - Procedure
27592766
* @param {Array} args - Arguments
2760-
* @param {Object} self - Self
2767+
* @param {Object} input - input
2768+
* @param {Object} environment - environment
27612769
* @returns {*} Result of procedure
27622770
*/
2763-
function* apply(proc, args, self) {
2771+
function* apply(proc, args, input, environment) {
27642772
var result;
2765-
result = yield * applyInner(proc, args, self);
2773+
result = yield * applyInner(proc, args, input, environment);
27662774
while(isLambda(result) && result.thunk === true) {
27672775
// trampoline loop - this gets invoked as a result of tail-call optimization
27682776
// the function returned a tail-call thunk
@@ -2773,7 +2781,7 @@ var jsonata = (function() {
27732781
evaluatedArgs.push(yield * evaluate(result.body.arguments[ii], result.input, result.environment));
27742782
}
27752783

2776-
result = yield * applyInner(next, evaluatedArgs, self);
2784+
result = yield * applyInner(next, evaluatedArgs, input, environment);
27772785
}
27782786
return result;
27792787
}
@@ -2782,26 +2790,37 @@ var jsonata = (function() {
27822790
* Apply procedure or function
27832791
* @param {Object} proc - Procedure
27842792
* @param {Array} args - Arguments
2785-
* @param {Object} self - Self
2793+
* @param {Object} input - input
2794+
* @param {Object} environment - environment
27862795
* @returns {*} Result of procedure
27872796
*/
2788-
function* applyInner(proc, args, self) {
2797+
function* applyInner(proc, args, input, environment) {
27892798
var result;
27902799
var validatedArgs = args;
27912800
if(proc) {
2792-
validatedArgs = validateArguments(proc.signature, args, self);
2801+
validatedArgs = validateArguments(proc.signature, args, input);
27932802
}
2803+
27942804
if (isLambda(proc)) {
27952805
result = yield * applyProcedure(proc, validatedArgs);
27962806
} else if (proc && proc._jsonata_function === true) {
2797-
result = proc.implementation.apply(self, validatedArgs);
2807+
var focus = {
2808+
environment: environment,
2809+
input: input
2810+
};
2811+
// the `focus` is passed in as the `this` for the invoked function
2812+
result = proc.implementation.apply(focus, validatedArgs);
27982813
// `proc.implementation` might be a generator function
27992814
// and `result` might be a generator - if so, yield
28002815
if(isIterable(result)) {
28012816
result = yield *result;
28022817
}
28032818
} else if (typeof proc === 'function') {
2804-
result = proc.apply(self, validatedArgs);
2819+
// typically these are functions that are returned by the invocation of plugin functions
2820+
// the `input` is being passed in as the `this` for the invoked function
2821+
// this is so that functions that return objects containing functions can chain
2822+
// e.g. $func().next().next()
2823+
result = proc.apply(input, validatedArgs);
28052824
/* istanbul ignore next */
28062825
if(isIterable(result)) {
28072826
result = yield *result;
@@ -2987,7 +3006,10 @@ var jsonata = (function() {
29873006
return env.lookup(sigArg.trim());
29883007
});
29893008

2990-
var result = proc.apply(null, args);
3009+
var focus = {
3010+
environment: env
3011+
};
3012+
var result = proc.apply(focus, args);
29913013
if(isIterable(result)) {
29923014
result = yield * result;
29933015
}
@@ -3384,6 +3406,8 @@ var jsonata = (function() {
33843406
return undefined;
33853407
}
33863408

3409+
var environment = this.environment;
3410+
33873411
// pattern cannot be an empty string
33883412
if(pattern === '') {
33893413
throw {
@@ -3478,7 +3502,7 @@ var jsonata = (function() {
34783502
if (typeof matches !== 'undefined') {
34793503
while (typeof matches !== 'undefined' && (typeof limit === 'undefined' || count < limit)) {
34803504
result += str.substring(position, matches.start);
3481-
var replacedWith = yield * apply(replacer, [matches], null);
3505+
var replacedWith = yield * apply(replacer, [matches], null, environment);
34823506
// check replacedWith is a string
34833507
if(typeof replacedWith === 'string') {
34843508
result += replacedWith;
@@ -4310,7 +4334,7 @@ var jsonata = (function() {
43104334
func_args.push(arr);
43114335
}
43124336
// invoke func
4313-
var res = yield * apply(func, func_args, null);
4337+
var res = yield * apply(func, func_args, null, this.environment);
43144338
if(typeof res !== 'undefined') {
43154339
result.push(res);
43164340
}
@@ -4338,7 +4362,7 @@ var jsonata = (function() {
43384362
for(var i = 0; i < arr.length; i++) {
43394363
var entry = arr[i];
43404364
// invoke func
4341-
var res = yield * apply(func, [entry, i, arr], null);
4365+
var res = yield * apply(func, [entry, i, arr], null, this.environment);
43424366
if(functionBoolean(res)) {
43434367
result.push(entry);
43444368
}
@@ -4403,7 +4427,7 @@ var jsonata = (function() {
44034427
}
44044428

44054429
while (index < sequence.length) {
4406-
result = yield * apply(func, [result, sequence[index]], null);
4430+
result = yield * apply(func, [result, sequence[index]], null, this.environment);
44074431
index++;
44084432
}
44094433

@@ -4572,7 +4596,7 @@ var jsonata = (function() {
45724596
for(var key in obj) {
45734597
var func_args = [obj[key], key];
45744598
// invoke func
4575-
result.push(yield * apply(func, func_args, null));
4599+
result.push(yield * apply(func, func_args, null, this.environment));
45764600
}
45774601

45784602
return result;
@@ -4591,6 +4615,8 @@ var jsonata = (function() {
45914615
return undefined;
45924616
}
45934617

4618+
var environment = this.environment;
4619+
45944620
if(arr.length <= 1) {
45954621
return arr;
45964622
}
@@ -4614,7 +4640,7 @@ var jsonata = (function() {
46144640
comp = comparator;
46154641
} else {
46164642
comp = function* (a, b) {
4617-
return yield * apply(comparator, [a, b], null);
4643+
return yield * apply(comparator, [a, b], null, environment);
46184644
};
46194645
}
46204646

@@ -4699,7 +4725,7 @@ var jsonata = (function() {
46994725
for(var item in arg) {
47004726
var entry = arg[item];
47014727
// invoke func
4702-
var res = yield * apply(func, [entry, item, arg], null);
4728+
var res = yield * apply(func, [entry, item, arg], null, this.environment);
47034729
if(functionBoolean(res)) {
47044730
result[item] = entry;
47054731
}
@@ -4713,6 +4739,49 @@ var jsonata = (function() {
47134739
return result;
47144740
}
47154741

4742+
/**
4743+
* parses and evaluates the supplied expression
4744+
* @param {string} expr - expression to evaluate
4745+
* @returns {*} - result of evaluating the expression
4746+
*/
4747+
function* functionEval(expr, focus) {
4748+
// undefined inputs always return undefined
4749+
if(typeof expr === 'undefined') {
4750+
return undefined;
4751+
}
4752+
var input = this.input;
4753+
if(typeof focus !== 'undefined') {
4754+
input = focus;
4755+
}
4756+
4757+
try {
4758+
var ast = parser(expr, false);
4759+
} catch(err) {
4760+
// error parsing the expression passed to $eval
4761+
populateMessage(err);
4762+
throw {
4763+
stack: (new Error()).stack,
4764+
code: "D3120",
4765+
value: err.message,
4766+
error: err
4767+
};
4768+
}
4769+
try {
4770+
var result = yield* evaluate(ast, input, this.environment);
4771+
} catch(err) {
4772+
// error evaluating the expression passed to $eval
4773+
populateMessage(err);
4774+
throw {
4775+
stack: (new Error()).stack,
4776+
code: "D3121",
4777+
value:err.message,
4778+
error: err
4779+
};
4780+
}
4781+
4782+
return result;
4783+
}
4784+
47164785
// Regular expression to match an ISO 8601 formatted timestamp
47174786
var iso8601regex = new RegExp('^\\d{4}(-[01]\\d)*(-[0-3]\\d)*(T[0-2]\\d:[0-5]\\d:[0-5]\\d)*(\\.\\d+)?([+-][0-2]\\d:?[0-5]\\d|Z)?$');
47184787

@@ -4839,6 +4908,7 @@ var jsonata = (function() {
48394908
staticFrame.bind('shuffle', defineFunction(functionShuffle, '<a:a>'));
48404909
staticFrame.bind('base64encode', defineFunction(functionBase64encode, '<s-:s>'));
48414910
staticFrame.bind('base64decode', defineFunction(functionBase64decode, '<s-:s>'));
4911+
staticFrame.bind('eval', defineFunction(functionEval, '<s-x?:x>'));
48424912
staticFrame.bind('toMillis', defineFunction(functionToMillis, '<s-:n>'));
48434913
staticFrame.bind('fromMillis', defineFunction(functionFromMillis, '<n-:s>'));
48444914
staticFrame.bind('clone', defineFunction(functionClone, '<(oa)-:o>'));
@@ -4934,7 +5004,9 @@ var jsonata = (function() {
49345004
"D3092": "A sub-picture that contains a 'percent' or 'per-mille' character must not contain a character treated as an 'exponent-separator'",
49355005
"D3093": "The exponent part of the sub-picture must comprise only of one or more characters that are members of the 'decimal digit family'",
49365006
"D3100": "The radix of the formatBase function must be between 2 and 36. It was given {{value}}",
4937-
"D3110": "The argument of the toMillis function must be an ISO 8601 formatted timestamp. Given {{value}}"
5007+
"D3110": "The argument of the toMillis function must be an ISO 8601 formatted timestamp. Given {{value}}",
5008+
"D3120": "Syntax error in expression passed to function eval: {{value}}",
5009+
"D3121": "Dynamic error evaluating the expression passed to function eval: {{value}}"
49385010
};
49395011

49405012
/**
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"expr": "$eval('[1,2,3]')",
3+
"dataset": "dataset5",
4+
"bindings": {},
5+
"result": [1,2,3]
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"expr": "$eval(nothing)",
3+
"dataset": "dataset5",
4+
"bindings": {},
5+
"undefinedResult": true
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"expr": "$eval('[1,$string(2),3]')",
3+
"dataset": "dataset5",
4+
"bindings": {},
5+
"result": [1,"2",3]
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"expr": "$eval('Account.Order.Product.Quantity ~> $sum()')",
3+
"dataset": "dataset5",
4+
"bindings": {},
5+
"result": 8
6+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"expr": "Account.Order.Product.{'Name': `Product Name`, 'Total': $eval('Price * Quantity')}",
3+
"dataset": "dataset5",
4+
"bindings": {},
5+
"result": [
6+
{
7+
"Name": "Bowler Hat",
8+
"Total": 68.9
9+
},
10+
{
11+
"Name": "Trilby hat",
12+
"Total": 21.67
13+
},
14+
{
15+
"Name": "Bowler Hat",
16+
"Total": 137.8
17+
},
18+
{
19+
"Name": "Cloak",
20+
"Total": 107.99
21+
}
22+
]
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"expr": "Account.Order.Product.{'Name': `Product Name`, 'Volume': $eval('Width*Height*Depth', Description)}",
3+
"dataset": "dataset5",
4+
"bindings": {},
5+
"result": [
6+
{
7+
"Name": "Bowler Hat",
8+
"Volume": 12600000
9+
},
10+
{
11+
"Name": "Trilby hat",
12+
"Volume": 12600000
13+
},
14+
{
15+
"Name": "Bowler Hat",
16+
"Volume": 12600000
17+
},
18+
{
19+
"Name": "Cloak",
20+
"Volume": 126000
21+
}
22+
]
23+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"expr": "$eval('[1,string(2),3]')",
3+
"dataset": "dataset5",
4+
"bindings": {},
5+
"code": "D3121"
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"expr": "$eval('[1,#string(2),3]')",
3+
"dataset": "dataset5",
4+
"bindings": {},
5+
"code": "D3120"
6+
}

0 commit comments

Comments
 (0)