Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 58 additions & 35 deletions src/jsonata.js
Original file line number Diff line number Diff line change
Expand Up @@ -1234,13 +1234,22 @@ var jsonata = (function() {
}
}
// apply the procedure
var procName = expr.procedure.type === 'path' ? expr.procedure.steps[0].value : expr.procedure.value;
try {
if(typeof proc === 'object') {
proc.token = procName;
proc.position = expr.position;
}
result = yield * apply(proc, evaluatedArgs, input, environment);
} catch (err) {
// add the position field to the error
err.position = expr.position;
// and the function identifier
err.token = expr.procedure.type === 'path' ? expr.procedure.steps[0].value : expr.procedure.value;
if(!err.position) {
// add the position field to the error
err.position = expr.position;
}
if (!err.token) {
// and the function identifier
err.token = procName;
}
throw err;
}
return result;
Expand All @@ -1262,6 +1271,10 @@ var jsonata = (function() {
// the function returned a tail-call thunk
// unpack it, evaluate its arguments, and apply the tail call
var next = yield * evaluate(result.body.procedure, result.input, result.environment);
if(result.body.procedure.type === 'variable') {
next.token = result.body.procedure.value;
}
next.position = result.body.procedure.position;
var evaluatedArgs = [];
for(var ii = 0; ii < result.body.arguments.length; ii++) {
evaluatedArgs.push(yield * evaluate(result.body.arguments[ii], result.input, result.environment));
Expand All @@ -1282,40 +1295,50 @@ var jsonata = (function() {
*/
function* applyInner(proc, args, input, environment) {
var result;
var validatedArgs = args;
if(proc) {
validatedArgs = validateArguments(proc.signature, args, input);
}
try {
var validatedArgs = args;
if (proc) {
validatedArgs = validateArguments(proc.signature, args, input);
}

if (isLambda(proc)) {
result = yield * applyProcedure(proc, validatedArgs);
} else if (proc && proc._jsonata_function === true) {
var focus = {
environment: environment,
input: input
};
// the `focus` is passed in as the `this` for the invoked function
result = proc.implementation.apply(focus, validatedArgs);
// `proc.implementation` might be a generator function
// and `result` might be a generator - if so, yield
if(isIterable(result)) {
result = yield *result;
if (isLambda(proc)) {
result = yield* applyProcedure(proc, validatedArgs);
} else if (proc && proc._jsonata_function === true) {
var focus = {
environment: environment,
input: input
};
// the `focus` is passed in as the `this` for the invoked function
result = proc.implementation.apply(focus, validatedArgs);
// `proc.implementation` might be a generator function
// and `result` might be a generator - if so, yield
if (isIterable(result)) {
result = yield* result;
}
} else if (typeof proc === 'function') {
// typically these are functions that are returned by the invocation of plugin functions
// the `input` is being passed in as the `this` for the invoked function
// this is so that functions that return objects containing functions can chain
// e.g. $func().next().next()
result = proc.apply(input, validatedArgs);
/* istanbul ignore next */
if (isIterable(result)) {
result = yield* result;
}
} else {
throw {
code: "T1006",
stack: (new Error()).stack
};
}
} else if (typeof proc === 'function') {
// typically these are functions that are returned by the invocation of plugin functions
// the `input` is being passed in as the `this` for the invoked function
// this is so that functions that return objects containing functions can chain
// e.g. $func().next().next()
result = proc.apply(input, validatedArgs);
/* istanbul ignore next */
if(isIterable(result)) {
result = yield *result;
} catch(err) {
if(proc) {
if (typeof err.token == 'undefined' && typeof proc.token !== 'undefined') {
err.token = proc.token;
}
err.position = proc.position;
}
} else {
throw {
code: "T1006",
stack: (new Error()).stack
};
throw err;
}
return result;
}
Expand Down
8 changes: 4 additions & 4 deletions test/implementation-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ describe("Tests that use the $clone() function", () => {
expr.evaluate(testdata2);
})
.to.throw()
.to.deep.contain({ position: 21, code: 'T2013' });
.to.deep.contain({ code: 'T2013' });
});
});
});
Expand Down Expand Up @@ -829,20 +829,20 @@ describe("Tests that include infinite recursion", () => {
expr.evaluate();
})
.to.throw()
.to.deep.contain({ position: 46, code: "U1001" });
.to.deep.contain({ token: "inf", position: 32, code: "U1001" });
});
});

describe("stack overflow - infinite recursive function - tail call", function() {
this.timeout(5000);
it("should throw error", function() {
expect(function() {
var expr = jsonata("(" + " $inf := function(){$inf()};" + " $inf()" + ")");
var expr = jsonata("( $inf := function(){$inf()}; $inf())");
timeboxExpression(expr, 1000, 500);
expr.evaluate();
})
.to.throw()
.to.deep.contain({ position: 37, code: "U1001" });
.to.deep.contain({ token: "inf", code: "U1001" });
});
});
});
Expand Down
5 changes: 5 additions & 0 deletions test/run-test-suite-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ describe("JSONata Test Suite - async mode", () => {
// Second is that a (defined) result was provided. In this case,
// we do a deep equality check against the expected result.
return expect(jsonataPromise(expr, dataset, testcase.bindings)).to.eventually.deep.equal(testcase.result);
} else if ("error" in testcase) {
// If an error was expected,
// we do a deep equality check against the expected error structure.
return expect(jsonataPromise(expr, dataset, testcase.bindings)).to.be.rejected
.and.eventually.have.property('code', testcase.error.code);
} else if ("code" in testcase) {
// Finally, if a `code` field was specified, we expected the
// evaluation to fail and include the specified code in the
Expand Down
8 changes: 8 additions & 0 deletions test/run-test-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ describe("JSONata Test Suite", () => {
// we do a deep equality check against the expected result.
let result = expr.evaluate(dataset, testcase.bindings);
expect(result).to.deep.equal(testcase.result);
} else if ("error" in testcase) {
// If an error was expected,
// we do a deep equality check against the expected error structure.
expect(function() {
expr.evaluate(dataset, testcase.bindings);
})
.to.throw()
.to.deep.contain(testcase.error);
} else if ("code" in testcase) {
// Finally, if a `code` field was specified, we expected the
// evaluation to fail and include the specified code in the
Expand Down
9 changes: 9 additions & 0 deletions test/test-suite/groups/errors/case025.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"expr": "( $A := function(){$min(2, 3)}; $A() )",
"dataset": null,
"bindings": {},
"error": {
"code": "T0410",
"token": "min"
}
}
9 changes: 9 additions & 0 deletions test/test-suite/groups/errors/case026.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"expr": "( $B := function(){''}; $A := function(){2 + $B()}; $A() )",
"dataset": null,
"bindings": {},
"error": {
"code": "T2002",
"token": "+"
}
}