commit
message
[Parser, Sheet] checking for try-catch-formula inside performAction so we can assign result to this.$
author
Ben Vogt <[email protected]>
date
2017-09-04 20:10:23
stats
3 file(s) changed,
271 insertions(+),
122 deletions(-)
files
src/Parser/Parser.ts
src/Sheet.ts
tests/SheetFormulaTest.ts
1diff --git a/src/Parser/Parser.ts b/src/Parser/Parser.ts
2index a2e9121..6b2a9bd 100644
3--- a/src/Parser/Parser.ts
4+++ b/src/Parser/Parser.ts
5@@ -1,7 +1,13 @@
6 import {
7 ObjectFromPairs
8 } from "../Utilities/ObjectFromPairs";
9-import {ParseError} from "../Errors";
10+import {
11+ PARSE_ERROR,
12+ ParseError
13+} from "../Errors";
14+import {
15+ Formulas
16+} from "../Formulas";
17
18 // Rules represent the Regular Expressions that will be used in sequence to match a given input to the Parser.
19 const WHITE_SPACE_RULE = /^(?:\s+)/; // rule 0
20@@ -455,142 +461,235 @@ let Parser = (function () {
21 * @param reduceActionToPerform - the ReduceAction to perform with the current virtual stack. Since this function
22 * is only called in one place, this should always be action[1] in that context.
23 * @param virtualStack - Array of values to use in action.
24+ * @param catchOnFailure - If we are performing an action that could result in a failure, and we cant to catch and
25+ * assign the error thrown, this should be set to true.
26 * @returns {number|boolean|string}
27 */
28- performAction: function (rawValueOfReduceOriginToken, sharedStateYY, reduceActionToPerform, virtualStack : Array<any>) {
29+ performAction: function (rawValueOfReduceOriginToken, sharedStateYY, reduceActionToPerform, virtualStack : Array<any>, catchOnFailure : boolean) {
30 // For context, this function is only called with `apply`, so `this` is `yyval`.
31
32 const vsl = virtualStack.length - 1;
33- switch (reduceActionToPerform) {
34- case ReduceActions.RETURN_LAST:
35- return virtualStack[vsl - 1];
36- case ReduceActions.CALL_VARIABLE:
37- this.$ = sharedStateYY.handler.helper.callVariable.call(this, virtualStack[vsl]);
38- break;
39- case ReduceActions.TIME_CALL_TRUE:
40- this.$ = sharedStateYY.handler.time.call(sharedStateYY.obj, virtualStack[vsl], true);
41- break;
42- case ReduceActions.TIME_CALL:
43- this.$ = sharedStateYY.handler.time.call(sharedStateYY.obj, virtualStack[vsl]);
44- break;
45- case ReduceActions.AS_NUMBER:
46- this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl]);
47- break;
48- case ReduceActions.AS_STRING:
49- this.$ = sharedStateYY.handler.helper.string(virtualStack[vsl]);
50- break;
51- case ReduceActions.AMPERSAND:
52- this.$ = sharedStateYY.handler.helper.specialMatch('&', virtualStack[vsl - 2], virtualStack[vsl]);
53- break;
54- case ReduceActions.EQUALS:
55- this.$ = sharedStateYY.handler.helper.logicMatch('=', virtualStack[vsl - 2], virtualStack[vsl]);
56- break;
57- case ReduceActions.PLUS:
58- this.$ = sharedStateYY.handler.helper.mathMatch('+', virtualStack[vsl - 2], virtualStack[vsl]);
59- break;
60- case ReduceActions.LAST_NUMBER:
61- this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl - 1]);
62- break;
63- case ReduceActions.LTE:
64- this.$ = sharedStateYY.handler.helper.logicMatch('<=', virtualStack[vsl - 3], virtualStack[vsl]);
65- break;
66- case ReduceActions.GTE:
67- this.$ = sharedStateYY.handler.helper.logicMatch('>=', virtualStack[vsl - 3], virtualStack[vsl]);
68- break;
69- case ReduceActions.NOT_EQ:
70- this.$ = sharedStateYY.handler.helper.logicMatch('<>', virtualStack[vsl - 3], virtualStack[vsl]);
71- break;
72- case ReduceActions.NOT:
73- this.$ = sharedStateYY.handler.helper.logicMatch('NOT', virtualStack[vsl - 2], virtualStack[vsl]);
74- break;
75- case ReduceActions.GT:
76- this.$ = sharedStateYY.handler.helper.logicMatch('>', virtualStack[vsl - 2], virtualStack[vsl]);
77- break;
78- case ReduceActions.LT:
79- this.$ = sharedStateYY.handler.helper.logicMatch('<', virtualStack[vsl - 2], virtualStack[vsl]);
80- break;
81- case ReduceActions.MINUS:
82- this.$ = sharedStateYY.handler.helper.mathMatch('-', virtualStack[vsl - 2], virtualStack[vsl]);
83- break;
84- case ReduceActions.MULTIPLY:
85- this.$ = sharedStateYY.handler.helper.mathMatch('*', virtualStack[vsl - 2], virtualStack[vsl]);
86- break;
87- case ReduceActions.DIVIDE:
88- this.$ = sharedStateYY.handler.helper.mathMatch('/', virtualStack[vsl - 2], virtualStack[vsl]);
89- break;
90- case ReduceActions.TO_POWER:
91- this.$ = sharedStateYY.handler.helper.mathMatch('^', virtualStack[vsl - 2], virtualStack[vsl]);
92- break;
93- case ReduceActions.INVERT_NUM:
94- this.$ = sharedStateYY.handler.helper.numberInverted(virtualStack[vsl]);
95- if (isNaN(this.$)) {
96- this.$ = 0;
97- }
98- break;
99- case ReduceActions.TO_NUMBER_NAN_AS_ZERO:
100- this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl]);
101- if (isNaN(this.$)) {
102- this.$ = 0;
103- }
104- break;
105- case ReduceActions.CALL_FUNCTION_LAST_BLANK:
106- this.$ = sharedStateYY.handler.helper.callFunction.call(this, virtualStack[vsl - 2], '');
107- break;
108- case ReduceActions.CALL_FUNCTION_LAST_TWO_IN_STACK:
109- this.$ = sharedStateYY.handler.helper.callFunction.call(this, virtualStack[vsl - 3], virtualStack[vsl - 1]);
110- break;
111- case ReduceActions.FIXED_CELL_VAL:
112- this.$ = sharedStateYY.handler.helper.fixedCellValue.call(sharedStateYY.obj, virtualStack[vsl]);
113- break;
114- case ReduceActions.FIXED_CELL_RANGE_VAL:
115- this.$ = sharedStateYY.handler.helper.fixedCellRangeValue.call(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
116- break;
117- case ReduceActions.CELL_VALUE:
118- this.$ = sharedStateYY.handler.helper.cellValue.call(sharedStateYY.obj, virtualStack[vsl]);
119- break;
120- case ReduceActions.CELL_RANGE_VALUE:
121- this.$ = sharedStateYY.handler.helper.cellRangeValue.call(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
122- break;
123- case ReduceActions.ENSURE_IS_ARRAY:
124- if (sharedStateYY.handler.utils.isArray(virtualStack[vsl])) {
125- this.$ = virtualStack[vsl];
126- } else {
127+ try {
128+ switch (reduceActionToPerform) {
129+ case ReduceActions.RETURN_LAST:
130+ return virtualStack[vsl - 1];
131+ case ReduceActions.CALL_VARIABLE:
132+ this.$ = sharedStateYY.handler.helper.callVariable.call(this, virtualStack[vsl]);
133+ break;
134+ case ReduceActions.TIME_CALL_TRUE:
135+ this.$ = sharedStateYY.handler.time.call(sharedStateYY.obj, virtualStack[vsl], true);
136+ break;
137+ case ReduceActions.TIME_CALL:
138+ this.$ = sharedStateYY.handler.time.call(sharedStateYY.obj, virtualStack[vsl]);
139+ break;
140+ case ReduceActions.AS_NUMBER:
141+ this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl]);
142+ break;
143+ case ReduceActions.AS_STRING:
144+ this.$ = sharedStateYY.handler.helper.string(virtualStack[vsl]);
145+ break;
146+ case ReduceActions.AMPERSAND:
147+ this.$ = sharedStateYY.handler.helper.specialMatch('&', virtualStack[vsl - 2], virtualStack[vsl]);
148+ break;
149+ case ReduceActions.EQUALS:
150+ this.$ = sharedStateYY.handler.helper.logicMatch('=', virtualStack[vsl - 2], virtualStack[vsl]);
151+ break;
152+ case ReduceActions.PLUS:
153+ this.$ = sharedStateYY.handler.helper.mathMatch('+', virtualStack[vsl - 2], virtualStack[vsl]);
154+ break;
155+ case ReduceActions.LAST_NUMBER:
156+ this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl - 1]);
157+ break;
158+ case ReduceActions.LTE:
159+ this.$ = sharedStateYY.handler.helper.logicMatch('<=', virtualStack[vsl - 3], virtualStack[vsl]);
160+ break;
161+ case ReduceActions.GTE:
162+ this.$ = sharedStateYY.handler.helper.logicMatch('>=', virtualStack[vsl - 3], virtualStack[vsl]);
163+ break;
164+ case ReduceActions.NOT_EQ:
165+ this.$ = sharedStateYY.handler.helper.logicMatch('<>', virtualStack[vsl - 3], virtualStack[vsl]);
166+ break;
167+ case ReduceActions.NOT:
168+ this.$ = sharedStateYY.handler.helper.logicMatch('NOT', virtualStack[vsl - 2], virtualStack[vsl]);
169+ break;
170+ case ReduceActions.GT:
171+ this.$ = sharedStateYY.handler.helper.logicMatch('>', virtualStack[vsl - 2], virtualStack[vsl]);
172+ break;
173+ case ReduceActions.LT:
174+ this.$ = sharedStateYY.handler.helper.logicMatch('<', virtualStack[vsl - 2], virtualStack[vsl]);
175+ break;
176+ case ReduceActions.MINUS:
177+ this.$ = sharedStateYY.handler.helper.mathMatch('-', virtualStack[vsl - 2], virtualStack[vsl]);
178+ break;
179+ case ReduceActions.MULTIPLY:
180+ this.$ = sharedStateYY.handler.helper.mathMatch('*', virtualStack[vsl - 2], virtualStack[vsl]);
181+ break;
182+ case ReduceActions.DIVIDE:
183+ this.$ = sharedStateYY.handler.helper.mathMatch('/', virtualStack[vsl - 2], virtualStack[vsl]);
184+ break;
185+ case ReduceActions.TO_POWER:
186+ this.$ = sharedStateYY.handler.helper.mathMatch('^', virtualStack[vsl - 2], virtualStack[vsl]);
187+ break;
188+ case ReduceActions.INVERT_NUM:
189+ this.$ = sharedStateYY.handler.helper.numberInverted(virtualStack[vsl]);
190+ if (isNaN(this.$)) {
191+ this.$ = 0;
192+ }
193+ break;
194+ case ReduceActions.TO_NUMBER_NAN_AS_ZERO:
195+ this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl]);
196+ if (isNaN(this.$)) {
197+ this.$ = 0;
198+ }
199+ break;
200+ case ReduceActions.CALL_FUNCTION_LAST_BLANK:
201+ this.$ = sharedStateYY.handler.helper.callFunction.call(this, virtualStack[vsl - 2], '');
202+ break;
203+ case ReduceActions.CALL_FUNCTION_LAST_TWO_IN_STACK:
204+ this.$ = sharedStateYY.handler.helper.callFunction.call(this, virtualStack[vsl - 3], virtualStack[vsl - 1]);
205+ break;
206+ case ReduceActions.FIXED_CELL_VAL:
207+ this.$ = sharedStateYY.handler.helper.fixedCellValue.call(sharedStateYY.obj, virtualStack[vsl]);
208+ break;
209+ case ReduceActions.FIXED_CELL_RANGE_VAL:
210+ this.$ = sharedStateYY.handler.helper.fixedCellRangeValue.call(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
211+ break;
212+ case ReduceActions.CELL_VALUE:
213+ this.$ = sharedStateYY.handler.helper.cellValue.call(sharedStateYY.obj, virtualStack[vsl]);
214+ break;
215+ case ReduceActions.CELL_RANGE_VALUE:
216+ this.$ = sharedStateYY.handler.helper.cellRangeValue.call(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
217+ break;
218+ case ReduceActions.ENSURE_IS_ARRAY:
219+ if (sharedStateYY.handler.utils.isArray(virtualStack[vsl])) {
220+ this.$ = virtualStack[vsl];
221+ } else {
222+ this.$ = [virtualStack[vsl]];
223+ }
224+ break;
225+ case ReduceActions.ENSURE_YYTEXT_ARRAY:
226+ let result = [],
227+ arr = eval("[" + rawValueOfReduceOriginToken + "]");
228+ arr.forEach(function (item) {
229+ result.push(item);
230+ });
231+ this.$ = result;
232+ break;
233+ case ReduceActions.REDUCE_INT:
234+ case ReduceActions.REDUCE_PERCENT:
235+ virtualStack[vsl - 2].push(virtualStack[vsl]);
236+ this.$ = virtualStack[vsl - 2];
237+ break;
238+ case ReduceActions.WRAP_CURRENT_INDEX_TOKEN_AS_ARRAY:
239 this.$ = [virtualStack[vsl]];
240+ break;
241+ case ReduceActions.ENSURE_LAST_TWO_IN_ARRAY_AND_PUSH:
242+ this.$ = (sharedStateYY.handler.utils.isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
243+ this.$.push(virtualStack[vsl]);
244+ break;
245+ case ReduceActions.REFLEXIVE_REDUCE:
246+ this.$ = virtualStack[vsl];
247+ break;
248+ case ReduceActions.REDUCE_FLOAT:
249+ this.$ = parseFloat(virtualStack[vsl - 2] + '.' + virtualStack[vsl]);
250+ break;
251+ case ReduceActions.REDUCE_PREV_AS_PERCENT:
252+ this.$ = virtualStack[vsl - 1] * 0.01;
253+ break;
254+ case ReduceActions.REDUCE_LAST_THREE_A:
255+ case ReduceActions.REDUCE_LAST_THREE_B:
256+ this.$ = virtualStack[vsl - 2] + virtualStack[vsl - 1] + virtualStack[vsl];
257+ break;
258+ }
259+ } catch (e) {
260+ if (catchOnFailure) {
261+ // NOTE: I'm not sure if some of these ReduceAction map correctly in the case of an error.
262+ switch (reduceActionToPerform) {
263+ case ReduceActions.RETURN_LAST:
264+ return virtualStack[vsl - 1];
265+ case ReduceActions.CALL_VARIABLE:
266+ case ReduceActions.TIME_CALL_TRUE:
267+ case ReduceActions.TIME_CALL:
268+ case ReduceActions.AS_NUMBER:
269+ case ReduceActions.AS_STRING:
270+ case ReduceActions.AMPERSAND:
271+ case ReduceActions.EQUALS:
272+ case ReduceActions.PLUS:
273+ case ReduceActions.LAST_NUMBER:
274+ case ReduceActions.LTE:
275+ case ReduceActions.GTE:
276+ case ReduceActions.NOT_EQ:
277+ case ReduceActions.NOT:
278+ case ReduceActions.GT:
279+ case ReduceActions.LT:
280+ case ReduceActions.MINUS:
281+ case ReduceActions.MULTIPLY:
282+ case ReduceActions.DIVIDE:
283+ case ReduceActions.TO_POWER:
284+ case ReduceActions.CALL_FUNCTION_LAST_BLANK:
285+ case ReduceActions.CALL_FUNCTION_LAST_TWO_IN_STACK:
286+ case ReduceActions.FIXED_CELL_VAL:
287+ case ReduceActions.FIXED_CELL_RANGE_VAL:
288+ case ReduceActions.CELL_VALUE:
289+ case ReduceActions.CELL_RANGE_VALUE:
290+ this.$ = e;
291+ break;
292+ case ReduceActions.INVERT_NUM:
293+ this.$ = e;
294+ if (isNaN(this.$)) {
295+ this.$ = 0;
296+ }
297+ break;
298+ case ReduceActions.TO_NUMBER_NAN_AS_ZERO:
299+ this.$ = e;
300+ if (isNaN(this.$)) {
301+ this.$ = 0;
302+ }
303+ break;
304+ case ReduceActions.ENSURE_IS_ARRAY:
305+ if (sharedStateYY.handler.utils.isArray(virtualStack[vsl])) {
306+ this.$ = virtualStack[vsl];
307+ } else {
308+ this.$ = [virtualStack[vsl]];
309+ }
310+ break;
311+ case ReduceActions.ENSURE_YYTEXT_ARRAY:
312+ let result = [],
313+ arr = eval("[" + rawValueOfReduceOriginToken + "]");
314+ arr.forEach(function (item) {
315+ result.push(item);
316+ });
317+ this.$ = result;
318+ break;
319+ case ReduceActions.REDUCE_INT:
320+ case ReduceActions.REDUCE_PERCENT:
321+ virtualStack[vsl - 2].push(virtualStack[vsl]);
322+ this.$ = virtualStack[vsl - 2];
323+ break;
324+ case ReduceActions.WRAP_CURRENT_INDEX_TOKEN_AS_ARRAY:
325+ this.$ = [virtualStack[vsl]];
326+ break;
327+ case ReduceActions.ENSURE_LAST_TWO_IN_ARRAY_AND_PUSH:
328+ this.$ = (sharedStateYY.handler.utils.isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
329+ this.$.push(virtualStack[vsl]);
330+ break;
331+ case ReduceActions.REFLEXIVE_REDUCE:
332+ this.$ = virtualStack[vsl];
333+ break;
334+ case ReduceActions.REDUCE_FLOAT:
335+ this.$ = parseFloat(virtualStack[vsl - 2] + '.' + virtualStack[vsl]);
336+ break;
337+ case ReduceActions.REDUCE_PREV_AS_PERCENT:
338+ this.$ = virtualStack[vsl - 1] * 0.01;
339+ break;
340+ case ReduceActions.REDUCE_LAST_THREE_A:
341+ case ReduceActions.REDUCE_LAST_THREE_B:
342+ this.$ = virtualStack[vsl - 2] + virtualStack[vsl - 1] + virtualStack[vsl];
343+ break;
344 }
345- break;
346- case ReduceActions.ENSURE_YYTEXT_ARRAY:
347- let result = [],
348- arr = eval("[" + rawValueOfReduceOriginToken + "]");
349- arr.forEach(function (item) {
350- result.push(item);
351- });
352- this.$ = result;
353- break;
354- case ReduceActions.REDUCE_INT:
355- case ReduceActions.REDUCE_PERCENT:
356- virtualStack[vsl - 2].push(virtualStack[vsl]);
357- this.$ = virtualStack[vsl - 2];
358- break;
359- case ReduceActions.WRAP_CURRENT_INDEX_TOKEN_AS_ARRAY:
360- this.$ = [virtualStack[vsl]];
361- break;
362- case ReduceActions.ENSURE_LAST_TWO_IN_ARRAY_AND_PUSH:
363- this.$ = (sharedStateYY.handler.utils.isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
364- this.$.push(virtualStack[vsl]);
365- break;
366- case ReduceActions.REFLEXIVE_REDUCE:
367- this.$ = virtualStack[vsl];
368- break;
369- case ReduceActions.REDUCE_FLOAT:
370- this.$ = parseFloat(virtualStack[vsl - 2] + '.' + virtualStack[vsl]);
371- break;
372- case ReduceActions.REDUCE_PREV_AS_PERCENT:
373- this.$ = virtualStack[vsl - 1] * 0.01;
374- break;
375- case ReduceActions.REDUCE_LAST_THREE_A:
376- case ReduceActions.REDUCE_LAST_THREE_B:
377- this.$ = virtualStack[vsl - 2] + virtualStack[vsl - 1] + virtualStack[vsl];
378- break;
379+ } else {
380+ throw e;
381+ }
382 }
383 },
384 /**
385@@ -1194,7 +1293,7 @@ let Parser = (function () {
386 if (hash.recoverable) {
387 this.trace(str);
388 } else {
389- throw new Error(str);
390+ throw new ParseError(str);
391 }
392 },
393 parse: function parse(input) {
394@@ -1395,6 +1494,11 @@ let Parser = (function () {
395 locationStack.push(lexer.yylloc);
396 stack.push(action[1]); // push state
397 symbol = null;
398+
399+ if (Formulas.isTryCatchFormula(lexer.yytext)) {
400+ catchFailuresOn = true;
401+ }
402+
403 if (!preErrorSymbol) { // normal execution/no error
404 yyleng = lexer.yyleng;
405 yytext = lexer.yytext;
406@@ -1427,7 +1531,8 @@ let Parser = (function () {
407 if (ranges) {
408 yyval._$.range = [locationStack[locationStack.length - (lengthToReduceStackBy || 1)].range[0], locationStack[locationStack.length - 1].range[1]];
409 }
410- result = this.performAction.apply(yyval, [yytext, sharedState.yy, action[1], semanticValueStack].concat(args));
411+ // If we are inside of a formula that should catch errors, then catch and return them.
412+ result = this.performAction.apply(yyval, [yytext, sharedState.yy, action[1], semanticValueStack, catchFailuresOn].concat(args));
413
414 if (typeof result !== 'undefined') {
415 return result;
416diff --git a/src/Sheet.ts b/src/Sheet.ts
417index de3d952..5468b00 100644
418--- a/src/Sheet.ts
419+++ b/src/Sheet.ts
420@@ -532,8 +532,8 @@ let Sheet = (function () {
421 });
422 error = new RefError("Reference does not exist.");
423 }
424- } catch (ex) {
425- error = ex;
426+ } catch (e) {
427+ error = e;
428 }
429
430 return {
431diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
432index 8baa2ce..b046a33 100644
433--- a/tests/SheetFormulaTest.ts
434+++ b/tests/SheetFormulaTest.ts
435@@ -977,6 +977,9 @@ test("Sheet ERROR.TYPE", function(){
436 // Divide by zero error should be caught by formula
437 assertFormulaEquals('=ERROR.TYPE(1/0)', 2);
438
439+ // NA error should also be caught by formula
440+ assertFormulaEquals('=ERROR.TYPE(NA())', 7);
441+
442 // Parse error should bubble up to cell
443 assertFormulaEqualsError('=ERROR.TYPE(10e)', PARSE_ERROR);
444 });