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