spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
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 });