commit
message
[IFERROR, ISFORMULA] formulas added and tested
author
Ben Vogt <[email protected]>
date
2017-09-09 13:59:50
stats
12 file(s) changed,
459 insertions(+),
216 deletions(-)
files
DOCS.md
TODO.md
dist/Errors.js
dist/Formulas.js
dist/Formulas/AllFormulas.js
dist/Formulas/Info.js
dist/Parser/Parser.js
dist/Sheet.js
src/Formulas/AllFormulas.ts
src/Formulas/Info.ts
tests/Formulas/InfoTest.ts
tests/SheetFormulaTest.ts
1diff --git a/DOCS.md b/DOCS.md
2index 737ce5a..4ef6754 100644
3--- a/DOCS.md
4+++ b/DOCS.md
5@@ -679,7 +679,7 @@
6 ```
7 Returns the number corresponding to an error value occurring in a different cell. With the aid of this number, an error message text can be generated. If an error occurs, the function returns a logical or numerical value.
8 @param value - Contains either the addrereference of the cell in which the error occurs, or the error directly. Eg: `=ERRORTYPE(NA())`
9-@constructor TODO: This formula, while written correctly in javascript, needs to be called inside of a try-catch-block inside the ParserSheet. Otherwise the errors thrown by nested formulas break through. Eg: `=ERRORTYPE(NA())`, NA bubbles up. Once this is done, we should test it inside SheetFormulaTest.ts
10+@constructor
11 ```
12
13 ### ISBLANK
14@@ -697,7 +697,7 @@
15 Returns TRUE if the value refers to any error value except #NA. You can use this function to control error values in certain cells. If an error occurs, the function returns a logical or numerical value.
16 @param value - Any value or expression in which a test is performed to determine whether an error value not equal to #NA is present.
17 @returns {boolean}
18-@constructor TODO: This formula needs to be called from inside a try-catch-block in the SheetParser, like ERROR.TYPE.
19+@constructor
20 ```
21
22 ### ISERROR
23@@ -706,7 +706,7 @@
24 Tests if the cells contain general error values. ISERROR recognizes the #NA error value. If an error occurs, the function returns a logical or numerical value.
25 @param value - is any value where a test is performed to determine whether it is an error value.
26 @returns {boolean}
27-@constructor TODO: This formula needs to be called from inside a try-catch-block in the SheetParser, like ERROR.TYPE.
28+@constructor
29 ```
30
31 ### ISNA
32@@ -715,17 +715,17 @@
33 Returns TRUE if a cell contains the #NA (value not available) error value. If an error occurs, the function returns a logical or numerical value.
34 @param value - The value or expression to be tested.
35 @returns {boolean}
36-@constructor TODO: This formula needs to be called from inside a try-catch-block in the SheetParser, like ERROR.TYPE.
37+@constructor
38 ```
39
40 ### IFERROR
41
42 ```
43- Returns the first argument if no error value is present, otherwise returns the second argument if provided, or a blank if the second argument is absent.
44+ Returns the first argument if no error value is present, otherwise returns the second argument if provided, or a blank if the second argument is absent. Blank value is `null`.
45 @param value - Value to check for error.
46 @param valueIfError - [OPTIONAL] - Value to return if no error is present in the first argument.
47 @returns {any}
48-@constructor TODO: This formula needs to be called from inside a try-catch-block in the SheetParser, like ERROR.TYPE.
49+@constructor
50 ```
51
52 ### TYPE
53@@ -752,6 +752,15 @@
54 @param cell - Cell, defaults to the cell calling this formula, when used in the context of a spreadsheet.
55 @constructor
56 ```
57+
58+### ISFORMULA
59+
60+```
61+ Returns TRUE if a cell is a formula cell. Must be given a reference.
62+@param value - To check.
63+@returns {boolean}
64+@constructor
65+```
66 ## Logical
67
68
69diff --git a/TODO.md b/TODO.md
70index 1fdf193..317b2cd 100644
71--- a/TODO.md
72+++ b/TODO.md
73@@ -13,24 +13,15 @@ For example 64 tbs to a qt.
74 For example `=#N/A` should force an error to be thrown inside the cell.
75
76
77-### Formula's use of type-conversion could be standardized
78-We could give each function a type-array that matches the arguments, and could be used to map to the TypeConverter functions. For example if a formula looks like `THING(a: number, b: string, c: array<numbers>)` it could have a property called `typeArray = ["number", "string", "array<numbers>"]` and then for each argument it uses a map to see which TypeConverter function it should use to ensure type on those arguments. This would allow us to test these type-converters more efficiently, rather than testing to be sure each formula converted the types properly.
79-
80-
81 ### Fix documentation regular expression, it is butchering URLs.
82
83
84-### Cells/Sheet/Parser should be able to parse raw errors in the format `#N/A`
85-All errors should be able to be input/thrown in this way.
86-
87-
88 ### Parser/Sheet should be able to be initialized with js range notation (`[]`) or regular range notation (`{}`)
89
90
91 ### Meta-Formulas to write
92 Many of these formulas can be written by allowing the Sheet and Parser to return Cell objects in addition to primitive types. There are some memory issues with doing this. If a user calls something like `ISNA(A1:A99999)` we really only need the first cell. So we should return cell objects in some cases, but it would be easier in most cases to have context aware formulas, so if they need a cell, or a reference, we simply skip looking up a reference, and instead return a reference, or just a single cell. One way to do this would be to have formula functions, and then on the side have formula args. So before we lookup a large range of cells, we can check to see if it needs all of them, or if it just cares about the first one. So for `ISNA` we could look at `FormulaArgs.ISNA[0]` to get `Value` so we know that it needs only a single argument that is not an array, so if we call it with `ISNA(A1:A99999)`, it would really only lookup `A1`. This might be premature optimization however.
93
94-* ISFORMULA - Requires changes to Parser/Sheet to fetch a cell, and check the formula field to see if it contains a formula.
95 * CELL - Requires changes to Parser/Sheet so that the raw cell is returned to the function. The raw cell should contain all information necessary for returning specified info.
96 * ADDRESS - In order to implement this, cells need to be aware of their sheet.
97 * COLUMNS
98diff --git a/dist/Errors.js b/dist/Errors.js
99index 1123f4a..cb943c8 100644
100--- a/dist/Errors.js
101+++ b/dist/Errors.js
102@@ -24,6 +24,8 @@ var NUM_ERROR = "#NUM!";
103 exports.NUM_ERROR = NUM_ERROR;
104 var NA_ERROR = "#N/A";
105 exports.NA_ERROR = NA_ERROR;
106+var PARSE_ERROR = "#ERROR";
107+exports.PARSE_ERROR = PARSE_ERROR;
108 var NullError = (function (_super) {
109 __extends(NullError, _super);
110 function NullError(message) {
111@@ -94,3 +96,13 @@ var NAError = (function (_super) {
112 return NAError;
113 }(Error));
114 exports.NAError = NAError;
115+var ParseError = (function (_super) {
116+ __extends(ParseError, _super);
117+ function ParseError(message) {
118+ var _this = _super.call(this, message) || this;
119+ _this.name = PARSE_ERROR;
120+ return _this;
121+ }
122+ return ParseError;
123+}(Error));
124+exports.ParseError = ParseError;
125diff --git a/dist/Formulas.js b/dist/Formulas.js
126index 630ae31..1098425 100644
127--- a/dist/Formulas.js
128+++ b/dist/Formulas.js
129@@ -1,6 +1,7 @@
130 "use strict";
131 exports.__esModule = true;
132 var AllFormulas = require("./Formulas/AllFormulas");
133+var AllFormulas_1 = require("./Formulas/AllFormulas");
134 var Formulas = {
135 exists: function (fn) {
136 return ((fn in AllFormulas) || (fn in AllFormulas.__COMPLEX));
137@@ -12,6 +13,9 @@ var Formulas = {
138 if (fn in AllFormulas.__COMPLEX) {
139 return AllFormulas.__COMPLEX[fn];
140 }
141+ },
142+ isTryCatchFormula: function (fn) {
143+ return AllFormulas_1.__TRY_CATCH_FORMULAS[fn] !== undefined;
144 }
145 };
146 exports.Formulas = Formulas;
147diff --git a/dist/Formulas/AllFormulas.js b/dist/Formulas/AllFormulas.js
148index bb6c529..750ec6a 100644
149--- a/dist/Formulas/AllFormulas.js
150+++ b/dist/Formulas/AllFormulas.js
151@@ -98,6 +98,7 @@ exports.IFERROR = Info_1.IFERROR;
152 exports.TYPE = Info_1.TYPE;
153 exports.COLUMN = Info_1.COLUMN;
154 exports.ROW = Info_1.ROW;
155+exports.ISFORMULA = Info_1.ISFORMULA;
156 var Lookup_1 = require("./Lookup");
157 exports.CHOOSE = Lookup_1.CHOOSE;
158 var Convert_1 = require("./Convert");
159@@ -252,3 +253,12 @@ var __COMPLEX = {
160 "RANK.EQ": Statistical_1.RANK$EQ
161 };
162 exports.__COMPLEX = __COMPLEX;
163+var __TRY_CATCH_FORMULAS = {
164+ "ERROR.TYPE": Info_1.ERRORTYPE,
165+ "ERRORTYPE": Info_1.ERRORTYPE,
166+ "ISERR": Info_1.ISERR,
167+ "ISERROR": Info_1.ISERROR,
168+ "ISNA": Info_1.ISNA,
169+ "IFERROR": Info_1.IFERROR
170+};
171+exports.__TRY_CATCH_FORMULAS = __TRY_CATCH_FORMULAS;
172diff --git a/dist/Formulas/Info.js b/dist/Formulas/Info.js
173index 178afae..4f9fceb 100644
174--- a/dist/Formulas/Info.js
175+++ b/dist/Formulas/Info.js
176@@ -9,7 +9,7 @@ var Cell_1 = require("../Cell");
177 * @constructor
178 */
179 var NA = function () {
180- ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "NA");
181+ ArgsChecker_1.ArgsChecker.checkLength(arguments, 0, "NA");
182 throw new Errors_1.NAError("NA Error thrown.");
183 };
184 exports.NA = NA;
185@@ -139,13 +139,15 @@ exports.ISREF = ISREF;
186 * @param value - Contains either the address/reference of the cell in which the error occurs, or the error directly.
187 * Eg: `=ERRORTYPE(NA())`
188 * @constructor
189- * TODO: This formula, while written correctly in javascript, needs to be called inside of a try-catch-block inside the
190- * Parser/Sheet. Otherwise the errors thrown by nested formulas break through. Eg: `=ERRORTYPE(NA())`, NA bubbles up.
191- * Once this is done, we should test it inside SheetFormulaTest.ts
192 */
193 var ERRORTYPE = function (value) {
194 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "ERRORTYPE");
195- value = TypeConverter_1.TypeConverter.firstValue(value);
196+ try {
197+ value = TypeConverter_1.TypeConverter.firstValue(value);
198+ }
199+ catch (e) {
200+ value = e;
201+ }
202 if (value instanceof Cell_1.Cell) {
203 if (value.hasError()) {
204 value = value.getError();
205@@ -202,10 +204,15 @@ exports.ISBLANK = ISBLANK;
206 * #N/A is present.
207 * @returns {boolean}
208 * @constructor
209- * TODO: This formula needs to be called from inside a try-catch-block in the Sheet/Parser, like ERROR.TYPE.
210 */
211 var ISERR = function (value) {
212 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "ISERR");
213+ try {
214+ value = TypeConverter_1.TypeConverter.firstValue(value);
215+ }
216+ catch (e) {
217+ return true;
218+ }
219 if (value instanceof Cell_1.Cell) {
220 if (value.hasError()) {
221 return value.getError().name !== Errors_1.NA_ERROR;
222@@ -224,7 +231,6 @@ exports.ISERR = ISERR;
223 * @param value - is any value where a test is performed to determine whether it is an error value.
224 * @returns {boolean}
225 * @constructor
226- * TODO: This formula needs to be called from inside a try-catch-block in the Sheet/Parser, like ERROR.TYPE.
227 */
228 var ISERROR = function (value) {
229 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "ISERROR");
230@@ -237,7 +243,7 @@ var ISERROR = function (value) {
231 if (value instanceof Cell_1.Cell) {
232 return value.hasError();
233 }
234- return value instanceof Error;
235+ return (value instanceof Error);
236 };
237 exports.ISERROR = ISERROR;
238 /**
239@@ -246,7 +252,6 @@ exports.ISERROR = ISERROR;
240 * @param value - The value or expression to be tested.
241 * @returns {boolean}
242 * @constructor
243- * TODO: This formula needs to be called from inside a try-catch-block in the Sheet/Parser, like ERROR.TYPE.
244 */
245 var ISNA = function (value) {
246 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "ISNA");
247@@ -269,19 +274,24 @@ var ISNA = function (value) {
248 exports.ISNA = ISNA;
249 /**
250 * Returns the first argument if no error value is present, otherwise returns the second argument if provided, or a
251- * blank if the second argument is absent.
252+ * blank if the second argument is absent. Blank value is `null`.
253 * @param value - Value to check for error.
254 * @param valueIfError - [OPTIONAL] - Value to return if no error is present in the first argument.
255 * @returns {any}
256 * @constructor
257- * TODO: This formula needs to be called from inside a try-catch-block in the Sheet/Parser, like ERROR.TYPE.
258 */
259 var IFERROR = function (value, valueIfError) {
260 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 1, 2, "IFERROR");
261- if (value instanceof Cell_1.Cell && valueIfError === undefined) {
262- return ISERROR(value) ? new Cell_1.Cell(value.getId()) : value;
263+ if (value instanceof Cell_1.Cell) {
264+ if (value.hasError()) {
265+ return null;
266+ }
267+ return value;
268+ }
269+ if (!ISERROR(value)) {
270+ return value;
271 }
272- return ISERROR(value) ? valueIfError : value;
273+ return null;
274 };
275 exports.IFERROR = IFERROR;
276 /**
277@@ -346,3 +356,22 @@ var ROW = function (cell) {
278 return cell.getRow() + 1;
279 };
280 exports.ROW = ROW;
281+/**
282+ * Returns TRUE if a cell is a formula cell. Must be given a reference.
283+ * @param value - To check.
284+ * @returns {boolean}
285+ * @constructor
286+ */
287+var ISFORMULA = function (value) {
288+ ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "ISFORMULA");
289+ if (value instanceof Array) {
290+ if (value.length === 0) {
291+ throw new Errors_1.RefError("Reference does not exist.");
292+ }
293+ }
294+ if (!(value instanceof Cell_1.Cell)) {
295+ throw new Errors_1.NAError("Argument must be a range");
296+ }
297+ return value.hasFormula();
298+};
299+exports.ISFORMULA = ISFORMULA;
300diff --git a/dist/Parser/Parser.js b/dist/Parser/Parser.js
301index 35152ae..804cf21 100644
302--- a/dist/Parser/Parser.js
303+++ b/dist/Parser/Parser.js
304@@ -1,6 +1,8 @@
305 "use strict";
306 exports.__esModule = true;
307 var ObjectFromPairs_1 = require("../Utilities/ObjectFromPairs");
308+var Errors_1 = require("../Errors");
309+var Formulas_1 = require("../Formulas");
310 // Rules represent the Regular Expressions that will be used in sequence to match a given input to the Parser.
311 var WHITE_SPACE_RULE = /^(?:\s+)/; // rule 0
312 var DOUBLE_QUOTES_RULE = /^(?:"(\\["]|[^"])*")/; // rule 1
313@@ -390,141 +392,236 @@ var Parser = (function () {
314 * @param reduceActionToPerform - the ReduceAction to perform with the current virtual stack. Since this function
315 * is only called in one place, this should always be action[1] in that context.
316 * @param virtualStack - Array of values to use in action.
317+ * @param catchOnFailure - If we are performing an action that could result in a failure, and we cant to catch and
318+ * assign the error thrown, this should be set to true.
319 * @returns {number|boolean|string}
320 */
321- performAction: function (rawValueOfReduceOriginToken, sharedStateYY, reduceActionToPerform, virtualStack) {
322+ performAction: function (rawValueOfReduceOriginToken, sharedStateYY, reduceActionToPerform, virtualStack, catchOnFailure) {
323 // For context, this function is only called with `apply`, so `this` is `yyval`.
324 var vsl = virtualStack.length - 1;
325- switch (reduceActionToPerform) {
326- case 1 /* RETURN_LAST */:
327- return virtualStack[vsl - 1];
328- case 2 /* CALL_VARIABLE */:
329- this.$ = sharedStateYY.handler.helper.callVariable.call(this, virtualStack[vsl]);
330- break;
331- case 3 /* TIME_CALL_TRUE */:
332- this.$ = sharedStateYY.handler.time.call(sharedStateYY.obj, virtualStack[vsl], true);
333- break;
334- case 4 /* TIME_CALL */:
335- this.$ = sharedStateYY.handler.time.call(sharedStateYY.obj, virtualStack[vsl]);
336- break;
337- case 5 /* AS_NUMBER */:
338- this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl]);
339- break;
340- case 6 /* AS_STRING */:
341- this.$ = sharedStateYY.handler.helper.string(virtualStack[vsl]);
342- break;
343- case 7 /* AMPERSAND */:
344- this.$ = sharedStateYY.handler.helper.specialMatch('&', virtualStack[vsl - 2], virtualStack[vsl]);
345- break;
346- case 8 /* EQUALS */:
347- this.$ = sharedStateYY.handler.helper.logicMatch('=', virtualStack[vsl - 2], virtualStack[vsl]);
348- break;
349- case 9 /* PLUS */:
350- this.$ = sharedStateYY.handler.helper.mathMatch('+', virtualStack[vsl - 2], virtualStack[vsl]);
351- break;
352- case 10 /* LAST_NUMBER */:
353- this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl - 1]);
354- break;
355- case 11 /* LTE */:
356- this.$ = sharedStateYY.handler.helper.logicMatch('<=', virtualStack[vsl - 3], virtualStack[vsl]);
357- break;
358- case 12 /* GTE */:
359- this.$ = sharedStateYY.handler.helper.logicMatch('>=', virtualStack[vsl - 3], virtualStack[vsl]);
360- break;
361- case 13 /* NOT_EQ */:
362- this.$ = sharedStateYY.handler.helper.logicMatch('<>', virtualStack[vsl - 3], virtualStack[vsl]);
363- break;
364- case 14 /* NOT */:
365- this.$ = sharedStateYY.handler.helper.logicMatch('NOT', virtualStack[vsl - 2], virtualStack[vsl]);
366- break;
367- case 15 /* GT */:
368- this.$ = sharedStateYY.handler.helper.logicMatch('>', virtualStack[vsl - 2], virtualStack[vsl]);
369- break;
370- case 16 /* LT */:
371- this.$ = sharedStateYY.handler.helper.logicMatch('<', virtualStack[vsl - 2], virtualStack[vsl]);
372- break;
373- case 17 /* MINUS */:
374- this.$ = sharedStateYY.handler.helper.mathMatch('-', virtualStack[vsl - 2], virtualStack[vsl]);
375- break;
376- case 18 /* MULTIPLY */:
377- this.$ = sharedStateYY.handler.helper.mathMatch('*', virtualStack[vsl - 2], virtualStack[vsl]);
378- break;
379- case 19 /* DIVIDE */:
380- this.$ = sharedStateYY.handler.helper.mathMatch('/', virtualStack[vsl - 2], virtualStack[vsl]);
381- break;
382- case 20 /* TO_POWER */:
383- this.$ = sharedStateYY.handler.helper.mathMatch('^', virtualStack[vsl - 2], virtualStack[vsl]);
384- break;
385- case 21 /* INVERT_NUM */:
386- this.$ = sharedStateYY.handler.helper.numberInverted(virtualStack[vsl]);
387- if (isNaN(this.$)) {
388- this.$ = 0;
389- }
390- break;
391- case 22 /* TO_NUMBER_NAN_AS_ZERO */:
392- this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl]);
393- if (isNaN(this.$)) {
394- this.$ = 0;
395- }
396- break;
397- case 23 /* CALL_FUNCTION_LAST_BLANK */:
398- this.$ = sharedStateYY.handler.helper.callFunction.call(this, virtualStack[vsl - 2], '');
399- break;
400- case 24 /* CALL_FUNCTION_LAST_TWO_IN_STACK */:
401- this.$ = sharedStateYY.handler.helper.callFunction.call(this, virtualStack[vsl - 3], virtualStack[vsl - 1]);
402- break;
403- case 28 /* FIXED_CELL_VAL */:
404- this.$ = sharedStateYY.handler.helper.fixedCellValue.call(sharedStateYY.obj, virtualStack[vsl]);
405- break;
406- case 29 /* FIXED_CELL_RANGE_VAL */:
407- this.$ = sharedStateYY.handler.helper.fixedCellRangeValue.call(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
408- break;
409- case 30 /* CELL_VALUE */:
410- this.$ = sharedStateYY.handler.helper.cellValue.call(sharedStateYY.obj, virtualStack[vsl]);
411- break;
412- case 31 /* CELL_RANGE_VALUE */:
413- this.$ = sharedStateYY.handler.helper.cellRangeValue.call(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
414- break;
415- case 32 /* ENSURE_IS_ARRAY */:
416- if (sharedStateYY.handler.utils.isArray(virtualStack[vsl])) {
417- this.$ = virtualStack[vsl];
418- }
419- else {
420+ try {
421+ switch (reduceActionToPerform) {
422+ case 1 /* RETURN_LAST */:
423+ return virtualStack[vsl - 1];
424+ case 2 /* CALL_VARIABLE */:
425+ this.$ = sharedStateYY.handler.helper.callVariable.call(this, virtualStack[vsl]);
426+ break;
427+ case 3 /* TIME_CALL_TRUE */:
428+ this.$ = sharedStateYY.handler.time.call(sharedStateYY.obj, virtualStack[vsl], true);
429+ break;
430+ case 4 /* TIME_CALL */:
431+ this.$ = sharedStateYY.handler.time.call(sharedStateYY.obj, virtualStack[vsl]);
432+ break;
433+ case 5 /* AS_NUMBER */:
434+ this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl]);
435+ break;
436+ case 6 /* AS_STRING */:
437+ this.$ = sharedStateYY.handler.helper.string(virtualStack[vsl]);
438+ break;
439+ case 7 /* AMPERSAND */:
440+ this.$ = sharedStateYY.handler.helper.specialMatch('&', virtualStack[vsl - 2], virtualStack[vsl]);
441+ break;
442+ case 8 /* EQUALS */:
443+ this.$ = sharedStateYY.handler.helper.logicMatch('=', virtualStack[vsl - 2], virtualStack[vsl]);
444+ break;
445+ case 9 /* PLUS */:
446+ this.$ = sharedStateYY.handler.helper.mathMatch('+', virtualStack[vsl - 2], virtualStack[vsl]);
447+ break;
448+ case 10 /* LAST_NUMBER */:
449+ this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl - 1]);
450+ break;
451+ case 11 /* LTE */:
452+ this.$ = sharedStateYY.handler.helper.logicMatch('<=', virtualStack[vsl - 3], virtualStack[vsl]);
453+ break;
454+ case 12 /* GTE */:
455+ this.$ = sharedStateYY.handler.helper.logicMatch('>=', virtualStack[vsl - 3], virtualStack[vsl]);
456+ break;
457+ case 13 /* NOT_EQ */:
458+ this.$ = sharedStateYY.handler.helper.logicMatch('<>', virtualStack[vsl - 3], virtualStack[vsl]);
459+ break;
460+ case 14 /* NOT */:
461+ this.$ = sharedStateYY.handler.helper.logicMatch('NOT', virtualStack[vsl - 2], virtualStack[vsl]);
462+ break;
463+ case 15 /* GT */:
464+ this.$ = sharedStateYY.handler.helper.logicMatch('>', virtualStack[vsl - 2], virtualStack[vsl]);
465+ break;
466+ case 16 /* LT */:
467+ this.$ = sharedStateYY.handler.helper.logicMatch('<', virtualStack[vsl - 2], virtualStack[vsl]);
468+ break;
469+ case 17 /* MINUS */:
470+ this.$ = sharedStateYY.handler.helper.mathMatch('-', virtualStack[vsl - 2], virtualStack[vsl]);
471+ break;
472+ case 18 /* MULTIPLY */:
473+ this.$ = sharedStateYY.handler.helper.mathMatch('*', virtualStack[vsl - 2], virtualStack[vsl]);
474+ break;
475+ case 19 /* DIVIDE */:
476+ this.$ = sharedStateYY.handler.helper.mathMatch('/', virtualStack[vsl - 2], virtualStack[vsl]);
477+ break;
478+ case 20 /* TO_POWER */:
479+ this.$ = sharedStateYY.handler.helper.mathMatch('^', virtualStack[vsl - 2], virtualStack[vsl]);
480+ break;
481+ case 21 /* INVERT_NUM */:
482+ this.$ = sharedStateYY.handler.helper.numberInverted(virtualStack[vsl]);
483+ if (isNaN(this.$)) {
484+ this.$ = 0;
485+ }
486+ break;
487+ case 22 /* TO_NUMBER_NAN_AS_ZERO */:
488+ this.$ = sharedStateYY.handler.helper.number(virtualStack[vsl]);
489+ if (isNaN(this.$)) {
490+ this.$ = 0;
491+ }
492+ break;
493+ case 23 /* CALL_FUNCTION_LAST_BLANK */:
494+ this.$ = sharedStateYY.handler.helper.callFunction.call(this, virtualStack[vsl - 2], '');
495+ break;
496+ case 24 /* CALL_FUNCTION_LAST_TWO_IN_STACK */:
497+ this.$ = sharedStateYY.handler.helper.callFunction.call(this, virtualStack[vsl - 3], virtualStack[vsl - 1]);
498+ break;
499+ case 28 /* FIXED_CELL_VAL */:
500+ this.$ = sharedStateYY.handler.helper.fixedCellValue.call(sharedStateYY.obj, virtualStack[vsl]);
501+ break;
502+ case 29 /* FIXED_CELL_RANGE_VAL */:
503+ this.$ = sharedStateYY.handler.helper.fixedCellRangeValue.call(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
504+ break;
505+ case 30 /* CELL_VALUE */:
506+ this.$ = sharedStateYY.handler.helper.cellValue.call(sharedStateYY.obj, virtualStack[vsl]);
507+ break;
508+ case 31 /* CELL_RANGE_VALUE */:
509+ this.$ = sharedStateYY.handler.helper.cellRangeValue.call(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
510+ break;
511+ case 32 /* ENSURE_IS_ARRAY */:
512+ if (sharedStateYY.handler.utils.isArray(virtualStack[vsl])) {
513+ this.$ = virtualStack[vsl];
514+ }
515+ else {
516+ this.$ = [virtualStack[vsl]];
517+ }
518+ break;
519+ case 33 /* ENSURE_YYTEXT_ARRAY */:
520+ var result_1 = [], arr = eval("[" + rawValueOfReduceOriginToken + "]");
521+ arr.forEach(function (item) {
522+ result_1.push(item);
523+ });
524+ this.$ = result_1;
525+ break;
526+ case 34 /* REDUCE_INT */:
527+ case 35 /* REDUCE_PERCENT */:
528+ virtualStack[vsl - 2].push(virtualStack[vsl]);
529+ this.$ = virtualStack[vsl - 2];
530+ break;
531+ case 36 /* WRAP_CURRENT_INDEX_TOKEN_AS_ARRAY */:
532 this.$ = [virtualStack[vsl]];
533+ break;
534+ case 37 /* ENSURE_LAST_TWO_IN_ARRAY_AND_PUSH */:
535+ this.$ = (sharedStateYY.handler.utils.isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
536+ this.$.push(virtualStack[vsl]);
537+ break;
538+ case 38 /* REFLEXIVE_REDUCE */:
539+ this.$ = virtualStack[vsl];
540+ break;
541+ case 39 /* REDUCE_FLOAT */:
542+ this.$ = parseFloat(virtualStack[vsl - 2] + '.' + virtualStack[vsl]);
543+ break;
544+ case 40 /* REDUCE_PREV_AS_PERCENT */:
545+ this.$ = virtualStack[vsl - 1] * 0.01;
546+ break;
547+ case 41 /* REDUCE_LAST_THREE_A */:
548+ case 42 /* REDUCE_LAST_THREE_B */:
549+ this.$ = virtualStack[vsl - 2] + virtualStack[vsl - 1] + virtualStack[vsl];
550+ break;
551+ }
552+ }
553+ catch (e) {
554+ if (catchOnFailure) {
555+ // NOTE: I'm not sure if some of these ReduceAction map correctly in the case of an error.
556+ switch (reduceActionToPerform) {
557+ case 1 /* RETURN_LAST */:
558+ return virtualStack[vsl - 1];
559+ case 2 /* CALL_VARIABLE */:
560+ case 3 /* TIME_CALL_TRUE */:
561+ case 4 /* TIME_CALL */:
562+ case 5 /* AS_NUMBER */:
563+ case 6 /* AS_STRING */:
564+ case 7 /* AMPERSAND */:
565+ case 8 /* EQUALS */:
566+ case 9 /* PLUS */:
567+ case 10 /* LAST_NUMBER */:
568+ case 11 /* LTE */:
569+ case 12 /* GTE */:
570+ case 13 /* NOT_EQ */:
571+ case 14 /* NOT */:
572+ case 15 /* GT */:
573+ case 16 /* LT */:
574+ case 17 /* MINUS */:
575+ case 18 /* MULTIPLY */:
576+ case 19 /* DIVIDE */:
577+ case 20 /* TO_POWER */:
578+ case 23 /* CALL_FUNCTION_LAST_BLANK */:
579+ case 24 /* CALL_FUNCTION_LAST_TWO_IN_STACK */:
580+ case 28 /* FIXED_CELL_VAL */:
581+ case 29 /* FIXED_CELL_RANGE_VAL */:
582+ case 30 /* CELL_VALUE */:
583+ case 31 /* CELL_RANGE_VALUE */:
584+ this.$ = e;
585+ break;
586+ case 21 /* INVERT_NUM */:
587+ this.$ = e;
588+ if (isNaN(this.$)) {
589+ this.$ = 0;
590+ }
591+ break;
592+ case 22 /* TO_NUMBER_NAN_AS_ZERO */:
593+ this.$ = e;
594+ if (isNaN(this.$)) {
595+ this.$ = 0;
596+ }
597+ break;
598+ case 32 /* ENSURE_IS_ARRAY */:
599+ if (sharedStateYY.handler.utils.isArray(virtualStack[vsl])) {
600+ this.$ = virtualStack[vsl];
601+ }
602+ else {
603+ this.$ = [virtualStack[vsl]];
604+ }
605+ break;
606+ case 33 /* ENSURE_YYTEXT_ARRAY */:
607+ var result_2 = [], arr = eval("[" + rawValueOfReduceOriginToken + "]");
608+ arr.forEach(function (item) {
609+ result_2.push(item);
610+ });
611+ this.$ = result_2;
612+ break;
613+ case 34 /* REDUCE_INT */:
614+ case 35 /* REDUCE_PERCENT */:
615+ virtualStack[vsl - 2].push(virtualStack[vsl]);
616+ this.$ = virtualStack[vsl - 2];
617+ break;
618+ case 36 /* WRAP_CURRENT_INDEX_TOKEN_AS_ARRAY */:
619+ this.$ = [virtualStack[vsl]];
620+ break;
621+ case 37 /* ENSURE_LAST_TWO_IN_ARRAY_AND_PUSH */:
622+ this.$ = (sharedStateYY.handler.utils.isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
623+ this.$.push(virtualStack[vsl]);
624+ break;
625+ case 38 /* REFLEXIVE_REDUCE */:
626+ this.$ = virtualStack[vsl];
627+ break;
628+ case 39 /* REDUCE_FLOAT */:
629+ this.$ = parseFloat(virtualStack[vsl - 2] + '.' + virtualStack[vsl]);
630+ break;
631+ case 40 /* REDUCE_PREV_AS_PERCENT */:
632+ this.$ = virtualStack[vsl - 1] * 0.01;
633+ break;
634+ case 41 /* REDUCE_LAST_THREE_A */:
635+ case 42 /* REDUCE_LAST_THREE_B */:
636+ this.$ = virtualStack[vsl - 2] + virtualStack[vsl - 1] + virtualStack[vsl];
637+ break;
638 }
639- break;
640- case 33 /* ENSURE_YYTEXT_ARRAY */:
641- var result_1 = [], arr = eval("[" + rawValueOfReduceOriginToken + "]");
642- arr.forEach(function (item) {
643- result_1.push(item);
644- });
645- this.$ = result_1;
646- break;
647- case 34 /* REDUCE_INT */:
648- case 35 /* REDUCE_PERCENT */:
649- virtualStack[vsl - 2].push(virtualStack[vsl]);
650- this.$ = virtualStack[vsl - 2];
651- break;
652- case 36 /* WRAP_CURRENT_INDEX_TOKEN_AS_ARRAY */:
653- this.$ = [virtualStack[vsl]];
654- break;
655- case 37 /* ENSURE_LAST_TWO_IN_ARRAY_AND_PUSH */:
656- this.$ = (sharedStateYY.handler.utils.isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
657- this.$.push(virtualStack[vsl]);
658- break;
659- case 38 /* REFLEXIVE_REDUCE */:
660- this.$ = virtualStack[vsl];
661- break;
662- case 39 /* REDUCE_FLOAT */:
663- this.$ = parseFloat(virtualStack[vsl - 2] + '.' + virtualStack[vsl]);
664- break;
665- case 40 /* REDUCE_PREV_AS_PERCENT */:
666- this.$ = virtualStack[vsl - 1] * 0.01;
667- break;
668- case 41 /* REDUCE_LAST_THREE_A */:
669- case 42 /* REDUCE_LAST_THREE_B */:
670- this.$ = virtualStack[vsl - 2] + virtualStack[vsl - 1] + virtualStack[vsl];
671- break;
672+ }
673+ else {
674+ throw e;
675+ }
676 }
677 },
678 /**
679@@ -1129,7 +1226,7 @@ var Parser = (function () {
680 this.trace(str);
681 }
682 else {
683- throw new Error(str);
684+ throw new Errors_1.ParseError(str);
685 }
686 },
687 parse: function parse(input) {
688@@ -1184,7 +1281,7 @@ var Parser = (function () {
689 var symbol, preErrorSymbol, state, action, result, yyval = {
690 $: undefined,
691 _$: undefined
692- }, p, newState, expected;
693+ }, p, newState, expected, catchFailuresOn = false;
694 while (true) {
695 // retrieve state number from top of stack
696 state = stack[stack.length - 1];
697@@ -1261,7 +1358,7 @@ var Parser = (function () {
698 // just recovered from another error
699 if (recovering == 3) {
700 if (symbol === EOF || preErrorSymbol === EOF) {
701- throw new Error(errStr || 'Parsing halted while starting to recover from another error.');
702+ throw new Errors_1.ParseError(errStr || 'Parsing halted while starting to recover from another error.');
703 }
704 // discard current lookahead and grab another
705 yyleng = lexer.yyleng;
706@@ -1272,7 +1369,7 @@ var Parser = (function () {
707 }
708 // try to recover from error
709 if (error_rule_depth === false) {
710- throw new Error(errStr || 'Parsing halted. No suitable error recovery rule available.');
711+ throw new Errors_1.ParseError(errStr || 'Parsing halted. No suitable error recovery rule available.');
712 }
713 popStack(error_rule_depth);
714 preErrorSymbol = (symbol == TERROR ? null : symbol); // save the lookahead token
715@@ -1283,7 +1380,7 @@ var Parser = (function () {
716 }
717 // this shouldn't happen, unless resolve defaults are off
718 if (action[0] instanceof Array && action.length > 1) {
719- throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
720+ throw new Errors_1.ParseError('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
721 }
722 // LexActions are always:
723 // Shift: continue to process tokens.
724@@ -1296,6 +1393,9 @@ var Parser = (function () {
725 locationStack.push(lexer.yylloc);
726 stack.push(action[1]); // push state
727 symbol = null;
728+ if (Formulas_1.Formulas.isTryCatchFormula(lexer.yytext)) {
729+ catchFailuresOn = true;
730+ }
731 if (!preErrorSymbol) {
732 yyleng = lexer.yyleng;
733 yytext = lexer.yytext;
734@@ -1326,7 +1426,8 @@ var Parser = (function () {
735 if (ranges) {
736 yyval._$.range = [locationStack[locationStack.length - (lengthToReduceStackBy || 1)].range[0], locationStack[locationStack.length - 1].range[1]];
737 }
738- result = this.performAction.apply(yyval, [yytext, sharedState.yy, action[1], semanticValueStack].concat(args));
739+ // If we are inside of a formula that should catch errors, then catch and return them.
740+ result = this.performAction.apply(yyval, [yytext, sharedState.yy, action[1], semanticValueStack, catchFailuresOn].concat(args));
741 if (typeof result !== 'undefined') {
742 return result;
743 }
744@@ -1358,7 +1459,7 @@ var Parser = (function () {
745 this.yy.parser.parseError(str, hash);
746 }
747 else {
748- throw new Error(str);
749+ throw new Errors_1.ParseError(str);
750 }
751 },
752 // resets the lexer, sets new input
753diff --git a/dist/Sheet.js b/dist/Sheet.js
754index 9c4844b..e05fd23 100644
755--- a/dist/Sheet.js
756+++ b/dist/Sheet.js
757@@ -39,11 +39,11 @@ var Sheet = (function () {
758 newParser.yy.obj = obj;
759 };
760 newParser.yy.parseError = function (str, hash) {
761- throw {
762+ throw new Errors_1.ParseError(JSON.stringify({
763 name: 'Parser error',
764 message: str,
765 prop: hash
766- };
767+ }));
768 };
769 newParser.yy.handler = handler;
770 return newParser;
771@@ -183,12 +183,6 @@ var Sheet = (function () {
772 isFunction: function (value) {
773 return value instanceof Function;
774 },
775- isNull: function (value) {
776- return value === null;
777- },
778- isSet: function (value) {
779- return !utils.isNull(value);
780- },
781 toNum: function (chr) {
782 chr = utils.clearFormula(chr);
783 var base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
784@@ -386,9 +380,7 @@ var Sheet = (function () {
785 throw new Errors_1.NameError("Unknown variable: '" + str + "'.");
786 },
787 cellValue: function (cellId) {
788- var value, origin = this, cell = instance.matrix.getCell(cellId);
789- // get value, defaulting to undefined
790- value = cell.isBlank() ? undefined : cell.getValue();
791+ var origin = this, cell = instance.matrix.getCell(cellId);
792 //update dependencies
793 instance.matrix.getCell(origin).updateDependencies([cellId]);
794 // check references error
795@@ -397,10 +389,6 @@ var Sheet = (function () {
796 throw new Errors_1.RefError("Reference does not exist.");
797 }
798 }
799- // check if any error occurs
800- if (!cell.isBlank() && cell.getError()) {
801- throw cell.getError();
802- }
803 return cell;
804 },
805 cellRangeValue: function (start, end) {
806@@ -441,11 +429,17 @@ var Sheet = (function () {
807 instance.matrix.getCell(id).setError(new Errors_1.RefError("Reference does not exist"));
808 instance.matrix.getCell(id).clearValue();
809 });
810- throw new Errors_1.RefError("Reference does not exist.");
811+ error = new Errors_1.RefError("Reference does not exist.");
812 }
813 }
814- catch (ex) {
815- error = ex;
816+ catch (e) {
817+ error = e;
818+ }
819+ if (result instanceof Error) {
820+ return {
821+ error: result,
822+ result: null
823+ };
824 }
825 return {
826 error: error,
827diff --git a/src/Formulas/AllFormulas.ts b/src/Formulas/AllFormulas.ts
828index b67327c..fa91fa8 100644
829--- a/src/Formulas/AllFormulas.ts
830+++ b/src/Formulas/AllFormulas.ts
831@@ -97,7 +97,8 @@ import {
832 IFERROR,
833 TYPE,
834 COLUMN,
835- ROW
836+ ROW,
837+ ISFORMULA
838 } from "./Info";
839 import {
840 CHOOSE
841@@ -503,5 +504,6 @@ export {
842 TO_DATE,
843 TO_DOLLARS,
844 TO_PERCENT,
845- TO_TEXT
846+ TO_TEXT,
847+ ISFORMULA
848 }
849\ No newline at end of file
850diff --git a/src/Formulas/Info.ts b/src/Formulas/Info.ts
851index 19e557d..2416c42 100644
852--- a/src/Formulas/Info.ts
853+++ b/src/Formulas/Info.ts
854@@ -289,7 +289,7 @@ let ISNA = function (value) {
855
856 /**
857 * Returns the first argument if no error value is present, otherwise returns the second argument if provided, or a
858- * blank if the second argument is absent.
859+ * blank if the second argument is absent. Blank value is `null`.
860 * @param value - Value to check for error.
861 * @param valueIfError - [OPTIONAL] - Value to return if no error is present in the first argument.
862 * @returns {any}
863@@ -299,14 +299,14 @@ let IFERROR = function (value, valueIfError?) {
864 ArgsChecker.checkLengthWithin(arguments, 1, 2, "IFERROR");
865 if (value instanceof Cell) {
866 if (value.hasError()) {
867- return;
868+ return null;
869 }
870 return value;
871 }
872 if (!ISERROR(value)) {
873 return value;
874 }
875- return;
876+ return null;
877 };
878
879
880@@ -375,6 +375,26 @@ let ROW = function (cell) {
881 };
882
883
884+/**
885+ * Returns TRUE if a cell is a formula cell. Must be given a reference.
886+ * @param value - To check.
887+ * @returns {boolean}
888+ * @constructor
889+ */
890+let ISFORMULA = function (value) {
891+ ArgsChecker.checkLength(arguments, 1, "ISFORMULA");
892+ if (value instanceof Array) {
893+ if (value.length === 0) {
894+ throw new RefError("Reference does not exist.");
895+ }
896+ }
897+ if (!(value instanceof Cell)) {
898+ throw new NAError("Argument must be a range");
899+ }
900+ return value.hasFormula();
901+};
902+
903+
904 export {
905 NA,
906 ISTEXT,
907@@ -393,5 +413,6 @@ export {
908 IFERROR,
909 TYPE,
910 COLUMN,
911- ROW
912+ ROW,
913+ ISFORMULA
914 }
915\ No newline at end of file
916diff --git a/tests/Formulas/InfoTest.ts b/tests/Formulas/InfoTest.ts
917index 0d9dd65..fc38402 100644
918--- a/tests/Formulas/InfoTest.ts
919+++ b/tests/Formulas/InfoTest.ts
920@@ -16,7 +16,8 @@ import {
921 IFERROR,
922 TYPE,
923 COLUMN,
924- ROW
925+ ROW,
926+ ISFORMULA
927 } from "../../src/Formulas/Info";
928 import * as ERRORS from "../../src/Errors";
929 import {
930@@ -230,13 +231,13 @@ test("ISNA", function(){
931 test("IFERROR", function(){
932 let errorCell = new Cell("A1");
933 errorCell.setError(new NAError("err"));
934- assertEquals(IFERROR(errorCell, 10), undefined);
935- assertEquals(IFERROR(new NAError("err")), undefined);
936+ assertEquals(IFERROR(errorCell, 10), null);
937+ assertEquals(IFERROR(new NAError("err")), null);
938 assertEquals(IFERROR(10), 10);
939 assertEquals(IFERROR(Cell.BuildFrom("A1", 10), "abc"), Cell.BuildFrom("A1", 10));
940 assertEquals(IFERROR(new Cell("A1")), new Cell("A1"));
941 catchAndAssertEquals(function() {
942- IFERROR.apply(this, [])
943+ IFERROR.apply(this, []);
944 }, ERRORS.NA_ERROR);
945 });
946
947@@ -285,3 +286,25 @@ test("ROW", function(){
948 ROW.apply(this, [])
949 }, ERRORS.NA_ERROR);
950 });
951+
952+test("ISFORMULA", function(){
953+ let c = new Cell("A1");
954+ c.setValue("=SUM(10, 10)");
955+ assertEquals(ISFORMULA(c), true);
956+ assertEquals(ISFORMULA(new Cell("M5")), false);
957+ catchAndAssertEquals(function() {
958+ ISFORMULA.apply(this, [])
959+ }, ERRORS.NA_ERROR);
960+ catchAndAssertEquals(function() {
961+ ISFORMULA(10);
962+ }, ERRORS.NA_ERROR);
963+ catchAndAssertEquals(function() {
964+ ISFORMULA("str");
965+ }, ERRORS.NA_ERROR);
966+ catchAndAssertEquals(function() {
967+ ISFORMULA([]);
968+ }, ERRORS.REF_ERROR);
969+ catchAndAssertEquals(function() {
970+ ISFORMULA(false);
971+ }, ERRORS.NA_ERROR);
972+});
973diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
974index 4249f09..77ddeb8 100644
975--- a/tests/SheetFormulaTest.ts
976+++ b/tests/SheetFormulaTest.ts
977@@ -8,8 +8,11 @@ import {
978 import {
979 DIV_ZERO_ERROR,
980 VALUE_ERROR,
981- NA_ERROR, PARSE_ERROR
982+ NA_ERROR, PARSE_ERROR, REF_ERROR
983 } from "../src/Errors";
984+import {
985+ Cell
986+} from "../src/Cell";
987
988 function assertFormulaEqualsError(formula: string, errorString: string) {
989 let sheet = new Sheet();
990@@ -884,7 +887,21 @@ test("Sheet ISNA", function(){
991
992 test("Sheet IFERROR", function(){
993 assertFormulaEquals('=IFERROR(10)', 10);
994- assertFormulaEquals('=IFERROR(NA())', undefined);
995+ assertFormulaEquals('=IFERROR(NA())', null);
996+ assertFormulaEquals('=IFERROR(NOTAFUNCTION())', null);
997+ assertFormulaEquals('=IFERROR(1/0)', null);
998+ assertFormulaEquals('=IFERROR(M7)', new Cell("M7"));
999+ assertFormulaEquals('=IFERROR([])', null);
1000+});
1001+
1002+test("Sheet ISFORMULA", function(){
1003+ assertFormulaEqualsError('=ISFORMULA(10)', NA_ERROR);
1004+ assertFormulaEqualsError('=ISFORMULA(false)', NA_ERROR);
1005+ assertFormulaEqualsError('=ISFORMULA("str")', NA_ERROR);
1006+ assertFormulaEqualsError('=ISFORMULA([])', REF_ERROR);
1007+ assertFormulaEqualsError('=ISFORMULA([10])', NA_ERROR);
1008+ assertFormulaEqualsDependsOnReference('D1', "=SUM(10, 5)", '=ISFORMULA(D1)', true);
1009+ assertFormulaEquals('=ISFORMULA(M7)', false);
1010 });
1011
1012 test("Sheet TYPE", function(){