# fast-formula-parser **Repository Path**: y1667888902/fast-formula-parser ## Basic Information - **Project Name**: fast-formula-parser - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-01-26 - **Last Updated**: 2024-01-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![GitHub](https://img.shields.io/github/license/lesterlyu/fast-formula-parser) [![npm (tag)](https://img.shields.io/npm/v/fast-formula-parser/latest)](https://www.npmjs.com/package/fast-formula-parser) [![npm](https://img.shields.io/npm/dt/fast-formula-parser)](https://www.npmjs.com/package/fast-formula-parser) [![Coverage Status](https://coveralls.io/repos/github/LesterLyu/fast-formula-parser/badge.svg?branch=master)](https://coveralls.io/github/LesterLyu/fast-formula-parser?branch=master) [![Build Status](https://travis-ci.com/LesterLyu/fast-formula-parser.svg?branch=master)](https://travis-ci.com/LesterLyu/fast-formula-parser) ## [A Fast Excel Formula Parser & Evaluator](https://github.com/LesterLyu/fast-formula-parser) A fast and reliable excel formula parser in javascript. Using **LL(1)** parser. ### [Demo](https://lesterlyu.github.io/#/demo/fast-formula-parser) ### [Documentation](https://lesterlyu.github.io/fast-formula-parser/index.html) ### [Grammar Diagram](https://lesterlyu.github.io/fast-formula-parser/generated_diagrams.html) ### Supports 280 Formulas ``` ABS, ACOS, ACOSH, ACOT, ACOTH, ADDRESS, AND, ARABIC, AREAS, ASC, ASIN, ASINH, ATAN, ATAN2, ATANH, AVEDEV, AVERAGE, AVERAGEA, AVERAGEIF, BAHTTEXT, BASE, BESSELI, BESSELJ, BESSELK, BESSELY, BETA.DIST, BETA.INV, BIN2DEC, BIN2HEX, BIN2OCT, BINOM.DIST, BINOM.DIST.RANGE, BINOM.INV, BITAND, BITLSHIFT, BITOR, BITRSHIFT, BITXOR, CEILING, CEILING.MATH, CEILING.PRECISE, CHAR, CHISQ.DIST, CHISQ.DIST.RT, CHISQ.INV, CHISQ.INV.RT, CHISQ.TEST, CLEAN, CODE, COLUMN, COLUMNS, COMBIN, COMBINA, COMPLEX, CONCAT, CONCATENATE, CONFIDENCE.NORM, CONFIDENCE.T, CORREL, COS, COSH, COT, COTH, COUNT, COUNTIF, COVARIANCE.P, COVARIANCE.S, CSC, CSCH, DATE, DATEDIF, DATEVALUE, DAY, DAYS, DAYS360, DBCS, DEC2BIN, DEC2HEX, DEC2OCT, DECIMAL, DEGREES, DELTA, DEVSQ, DOLLAR, EDATE, ENCODEURL, EOMONTH, ERF, ERFC, ERROR.TYPE, EVEN, EXACT, EXP, EXPON.DIST, F.DIST, F.DIST.RT, F.INV, F.INV.RT, F.TEST, FACT, FACTDOUBLE, FALSE, FIND, FINDB, FISHER, FISHERINV, FIXED, FLOOR, FLOOR.MATH, FLOOR.PRECISE, FORECAST, FORECAST.LINEAR, FREQUENCY, GAMMA, GAMMA.DIST, GAMMA.INV, GAMMALN, GAMMALN.PRECISE, GAUSS, GCD, GEOMEAN, GESTEP, GROWTH, HARMEAN, HEX2BIN, HEX2DEC, HEX2OCT, HLOOKUP, HOUR, HYPGEOM.DIST, IF, IFERROR, IFNA, IFS, IMABS, IMAGINARY, IMARGUMENT, IMCONJUGATE, IMCOS, IMCOSH, IMCOT, IMCSC, IMCSCH, IMDIV, IMEXP, IMLN, IMLOG10, IMLOG2, IMPOWER, IMPRODUCT, IMREAL, IMSEC, IMSECH, IMSIN, IMSINH, IMSQRT, IMSUB, IMSUM, IMTAN, INDEX, INT, INTERCEPT, ISBLANK, ISERR, ISERROR, ISEVEN, ISLOGICAL, ISNA, ISNONTEXT, ISNUMBER, ISO.CEILING, ISOWEEKNUM, ISREF, ISTEXT, KURT, LCM, LEFT, LEFTB, LN, LOG, LOG10, LOGNORM.DIST, LOGNORM.INV, LOWER, MDETERM, MID, MIDB, MINUTE, MMULT, MOD, MONTH, MROUND, MULTINOMIAL, MUNIT, N, NA, NEGBINOM.DIST, NETWORKDAYS, NETWORKDAYS.INTL, NORM.DIST, NORM.INV, NORM.S.DIST, NORM.S.INV, NOT, NOW, NUMBERVALUE, OCT2BIN, OCT2DEC, OCT2HEX, ODD, OR, PHI, PI, POISSON.DIST, POWER, PRODUCT, PROPER, QUOTIENT, RADIANS, RAND, RANDBETWEEN, REPLACE, REPLACEB, REPT, RIGHT, RIGHTB, ROMAN, ROUND, ROUNDDOWN, ROUNDUP, ROW, ROWS, SEARCH, SEARCHB, SEC, SECH, SECOND, SERIESSUM, SIGN, SIN, SINH, SQRT, SQRTPI, STANDARDIZE, SUM, SUMIF, SUMPRODUCT, SUMSQ, SUMX2MY2, SUMX2PY2, SUMXMY2, T, T.DIST, T.DIST.2T, T.DIST.RT, T.INV, T.INV.2T, TAN, TANH, TEXT, TIME, TIMEVALUE, TODAY, TRANSPOSE, TRIM, TRUE, TRUNC, TYPE, UNICHAR, UNICODE, VLOOKUP, WEBSERVICE, WEEKDAY, WEEKNUM, WEIBULL.DIST, WORKDAY, WORKDAY.INTL, XOR, YEAR, YEARFRAC ``` ### Size: 291KB Minified, 81KB Gzipped+Minified ### Background Inspired by [XLParser](https://github.com/spreadsheetlab/XLParser/blob/master/src/XLParser/ExcelFormulaGrammar.cs) and the paper ["A Grammar for Spreadsheet Formulas Evaluated on Two Large Datasets" by Efthimia Aivaloglou, David Hoepelman and Felienne Hermans](https://fenia266781730.files.wordpress.com/2019/01/07335408.pdf). Note: The grammar in my implementation is different from theirs. My implementation gets rid of ambiguities to boost the performance. ### What is not supported: - [External reference](https://support.office.com/en-ie/article/create-an-external-reference-link-to-a-cell-range-in-another-workbook-c98d1803-dd75-4668-ac6a-d7cca2a9b95f) - Anything with `[` and `]` - Ambiguous old styles - Sheet name contains `:`, e.g. `SUM('1003:1856'!D6)` - Sheet name with space that is not quoted, e.g. `I am a sheet!A1` - `SUM(Sheet2:Sheet3!A1:C3)` - You tell me ### Performance - The expected performance is at least 3x faster than the optimized [formula-parser](https://github.com/LesterLyu/formula-parser). ### Dependency - [Chevrotain](https://github.com/SAP/chevrotain) , thanks to this great parser building toolkit. ### [Examples](https://github.com/LesterLyu/fast-formula-parser/blob/master/examples/example.js) - Install ```sh npm i fast-formula-parser # or using yarn yarn add fast-formula-parser ``` - Import ```js const FormulaParser = require('fast-formula-parser'); const {FormulaHelpers, Types, FormulaError, MAX_ROW, MAX_COLUMN} = FormulaParser; // or import FormulaParser, {FormulaHelpers, Types, FormulaError, MAX_ROW, MAX_COLUMN} from 'fast-formula-parser'; ``` UMD minified build is also provides: ```html ``` - Basic Usage ```js const data = [ // A B C [1, 2, 3], // row 1 [4, 5, 6] // row 2 ]; const parser = new FormulaParser({ // External functions, this will override internal functions with same name functions: { CHAR: (number) => { number = FormulaHelpers.accept(number, Types.NUMBER); if (number > 255 || number < 1) throw FormulaError.VALUE; return String.fromCharCode(number); } }, // Variable used in formulas (defined name) // Should only return range reference or cell reference onVariable: (name, sheetName) => { // If it is a range reference (A1:B2) return { sheet: 'sheet name', from: { row: 1, col: 1, }, to: { row: 2, col: 2, } }; // If it is a cell reference (A1) return { sheet: 'sheet name', row: 1, col: 1 } }, // retrieve cell value onCell: ({sheet, row, col}) => { // using 1-based index // return the cell value, see possible types in next section. return data[row - 1][col - 1]; }, // retrieve range values onRange: (ref) => { // using 1-based index // Be careful when ref.to.col is MAX_COLUMN or ref.to.row is MAX_ROW, this will result in // unnecessary loops in this approach. const arr = []; for (let row = ref.from.row; row <= ref.to.row; row++) { const innerArr = []; if (data[row - 1]) { for (let col = ref.from.col; col <= ref.to.col; col++) { innerArr.push(data[row - 1][col - 1]); } } arr.push(innerArr); } return arr; } }); // position is required for evaluating certain formulas, e.g. ROW() const position = {row: 1, col: 1, sheet: 'Sheet1'}; // parse the formula, the position of where the formula is located is required // for some functions. console.log(parser.parse('SUM(A:C)', position)); // print 21 // you can specify if the return value can be an array, this is helpful when dealing // with an array formula console.log(parser.parse('MMULT({1,5;2,3},{1,2;2,3})', position, true)); // print [ [ 11, 17 ], [ 8, 13 ] ] ``` - Custom Async functions > Remember to use `await parser.parseAsync(...)` instead of `parser.parse(...)` ```js const position = {row: 1, col: 1, sheet: 'Sheet1'}; const parser = new FormulaParser({ onCell: ref => { return 1; }, functions: { DEMO_FUNC: async () => { return [[1,2,3],[4,5,6]]; } }, }); console.log(await parser.parseAsync('A1 + IMPORT_CSV())', position)); // print 2 console.log(await parser.parseAsync('SUM(DEMO_FUNC(), 1))', position)); // print 22 ``` - Custom function requires parser context (e.g. location of the formula) ```js const position = {row: 1, col: 1, sheet: 'Sheet1'}; const parser = new FormulaParser({ functionsNeedContext: { // the first argument is the context // the followings are the arguments passed to the function ROW_PLUS_COL: (context, ...args) => { return context.position.row + context.position.col; } }, }); console.log(await parser.parseAsync('SUM(ROW_PLUS_COL(), 1)', position)); // print 3 ``` - Parse Formula Dependency > This is helpful for building `dependency graph/tree`. ```js import {DepParser} from 'fast-formula-parser'; const depParser = new DepParser({ // onVariable is the only thing you need provide if the formula contains variables onVariable: variable => { return 'VAR1' === variable ? {from: {row: 1, col: 1}, to: {row: 2, col: 2}} : {row: 1, col: 1}; } }); // position of the formula should be provided const position = {row: 1, col: 1, sheet: 'Sheet1'}; // Return an array of references (range reference or cell reference) // This gives [{row: 1, col: 1, sheet: 'Sheet1'}] depParser.parse('A1+1', position); // This gives [{sheet: 'Sheet1', from: {row: 1, col: 1}, to: {row: 3, col: 3}}] depParser.parse('A1:C3', position); // This gives [{from: {row: 1, col: 1}, to: {row: 2, col: 2}}] depParser.parse('VAR1 + 1', position); // Complex formula depParser.parse('IF(MONTH($K$1)<>MONTH($K$1-(WEEKDAY($K$1,1)-(start_day-1))-IF((WEEKDAY($K$1,1)-(start_day-1))<=0,7,0)+(ROW(O5)-ROW($K$3))*7+(COLUMN(O5)-COLUMN($K$3)+1)),"",$K$1-(WEEKDAY($K$1,1)-(start_day-1))-IF((WEEKDAY($K$1,1)-(start_day-1))<=0,7,0)+(ROW(O5)-ROW($K$3))*7+(COLUMN(O5)-COLUMN($K$3)+1))', position); // This gives the following result const result = [ { "col": 11, "row": 1, "sheet": "Sheet1", }, { "col": 1, "row": 1, "sheet": "Sheet1", }, { "col": 15, "row": 5, "sheet": "Sheet1", }, { "col": 11, "row": 3, "sheet": "Sheet1", }, ]; ``` ### Formula data types in JavaScript > The following data types are used in excel formulas and these are the only valid data types a formula or a function can return. - Number (date uses number): `1234` - String: `'some string'` - Boolean: `true`, `false` - Array: `[[1, 2, true, 'str']]` - Range Reference: (1-based index) ```js const ref = { sheet: String, from: { row: Number, col: Number, }, to: { row: Number, col: Number, }, } ``` - Cell Reference: (1-based index) ```js const ref = { sheet: String, row: Number, col: Number, } ``` - [Union (e.g. (A1:C3, E1:G6))](https://github.com/LesterLyu/fast-formula-parser/blob/master/grammar/type/collection.js) - [FormulaError](https://lesterlyu.github.io/fast-formula-parser/FormulaError.html) - `FormulaError.DIV0`: `#DIV/0!` - `FormulaError.NA`: `#N/A` - `FormulaError.NAME`: `#NAME?` - `FormulaError.NULL`: `#NULL!` - `FormulaError.NUM`: `#NUM!` - `FormulaError.REF`: `#REF!` - `FormulaError.VALUE`: `#VALUE!` ### Types Definition > Comming soon ### Error handling - Lexing/Parsing Error > Error location is available at `error.details.errorLocation` ```js try { parser.parse('SUM(1))', position); } catch (e) { console.log(e); // #ERROR!: // SUM(1)) // ^ // Error at position 1:7 // Redundant input, expecting EOF but found: ) expect(e).to.be.instanceof(FormulaError); expect(e.details.errorLocation.line).to.eq(1); expect(e.details.errorLocation.column).to.eq(7); expect(e.name).to.eq('#ERROR!'); expect(e.details.name).to.eq('NotAllInputParsedException'); } ``` - Error from internal/external functions or unexpected error from the parser > The error will be wrapped into `FormulaError`. The exact error is in `error.details`. ```js const parser = new FormulaParser({ functions: { BAD_FN: () => { throw new SyntaxError(); } } }); try { parser.parse('SUM(1))', position); } catch (e) { expect(e).to.be.instanceof(FormulaError); expect(e.name).to.eq('#ERROR!'); expect(e.details.name).to.eq('SyntaxError'); } ``` ### Thanks - [![JetBrains](https://raw.githubusercontent.com/LesterLyu/fast-formula-parser/master/logos/jetbrains-variant-4.svg)](https://www.jetbrains.com/?from=fast-formula-parser)