spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[SERIESSUM] work-in-progress, requires changes to Parser.ts to allow array token to follow coma
author
Ben Vogt <[email protected]>
date
2017-09-09 23:37:57
stats
6 file(s) changed, 62 insertions(+), 7 deletions(-)
files
TODO.md
src/Formulas/AllFormulas.ts
src/Formulas/Math.ts
src/Parser/Parser.ts
tests/Formulas/MathTest.ts
tests/SheetFormulaTest.ts
  1diff --git a/TODO.md b/TODO.md
  2index bba2802..a45b253 100644
  3--- a/TODO.md
  4+++ b/TODO.md
  5@@ -16,6 +16,10 @@ For example `=#N/A` should force an error to be thrown inside the cell.
  6 ### Parser/Sheet should be able to be initialized with js range notation (`[]`) or regular range notation (`{}`)
  7 
  8 
  9+### Parser should be able to parse arrays without `eval`
 10+Right now, arrays and reference literals in a formula are parsed using JS `eval`. This means, if we have references inside, or non-JS parsing values like TRUE or FALSE, they will cause ReferenceErrors. For example, `=SUM([M1, 10])` would throw `[ReferenceError: M1 is not defined]` because M1 is not a variables. Instead of using `eval`, we should parse the opening of an array, and the closeing of an array, and use recursion to see how deep we are, evaluating the tokens inside in the sam way we parse formulas and functions.
 11+
 12+
 13 ### Meta-Formulas to write
 14 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.
 15 
 16@@ -31,7 +35,6 @@ Many of these formulas can be written by allowing the Sheet and Parser to return
 17 
 18 
 19 ### Formulas to write
 20-* SERIESSUM
 21 * SUBTOTAL
 22 * CRITBINOM
 23 * F.DIST.RT
 24diff --git a/src/Formulas/AllFormulas.ts b/src/Formulas/AllFormulas.ts
 25index 800378f..c0d5276 100644
 26--- a/src/Formulas/AllFormulas.ts
 27+++ b/src/Formulas/AllFormulas.ts
 28@@ -72,7 +72,8 @@ import {
 29   MROUND,
 30   FACTDOUBLE,
 31   UNARY_PERCENT,
 32-  MULTINOMIAL
 33+  MULTINOMIAL,
 34+  SERIESSUM
 35 } from "./Math";
 36 import {
 37   FREQUENCY,
 38@@ -511,5 +512,6 @@ export {
 39   ISFORMULA,
 40   ADDRESS,
 41   COLUMNS,
 42-  ROWS
 43+  ROWS,
 44+  SERIESSUM
 45 }
 46\ No newline at end of file
 47diff --git a/src/Formulas/Math.ts b/src/Formulas/Math.ts
 48index 667e1ef..bc535b6 100644
 49--- a/src/Formulas/Math.ts
 50+++ b/src/Formulas/Math.ts
 51@@ -1383,6 +1383,31 @@ let MULTINOMIAL = function (...values) {
 52   return _fact(sum) / divisor;
 53 };
 54 
 55+
 56+/**
 57+ * Returns a sum of powers of the number x in accordance with the following formula.
 58+ * @param x - The number as an independent variable.
 59+ * @param n - The starting power.
 60+ * @param m - The number to increment by
 61+ * @param coefficients - A series of coefficients. For each coefficient the series sum is extended by one section. You
 62+ * can only enter coefficients using cell references.
 63+ * @returns {number}
 64+ * @constructor
 65+ */
 66+let SERIESSUM = function (x, n, m, coefficients) {
 67+  ArgsChecker.checkLength(arguments, 4, "SERIESSUM");
 68+  x = TypeConverter.firstValueAsNumber(x);
 69+  n = TypeConverter.firstValueAsNumber(n);
 70+  m = TypeConverter.firstValueAsNumber(m);
 71+  coefficients =  Filter.flattenAndThrow(coefficients).map(TypeConverter.valueToNumber);
 72+  let result = coefficients[0] * Math.pow(x, n);
 73+  for (let i = 1; i < coefficients.length; i++) {
 74+    result += coefficients[i] * Math.pow(x, n + i * m);
 75+  }
 76+  return result;
 77+};
 78+
 79+
 80 export {
 81   ABS,
 82   ACOS,
 83@@ -1457,5 +1482,6 @@ export {
 84   MROUND,
 85   FACTDOUBLE,
 86   UNARY_PERCENT,
 87-  MULTINOMIAL
 88+  MULTINOMIAL,
 89+  SERIESSUM
 90 }
 91\ No newline at end of file
 92diff --git a/src/Parser/Parser.ts b/src/Parser/Parser.ts
 93index 6b2a9bd..c2a6f6c 100644
 94--- a/src/Parser/Parser.ts
 95+++ b/src/Parser/Parser.ts
 96@@ -1240,7 +1240,7 @@ let Parser = (function () {
 97         32, $V9,
 98         34, $Va,
 99         36, $Vb,
100-        12, $Vd,
101+        12, $Vd
102       ]),
103       ObjectFromPairs.of([
104         2, 13,
105@@ -1261,6 +1261,9 @@ let Parser = (function () {
106         34, $Va,
107         36, $Vb,
108         12, $Vd,
109+        // NOTE: Ben this is where you are. When the parser captures an entire array, it should be able to reduce the
110+        // array, and continue on parsing normally. So we should have [LexActions.REDUCE, X]
111+        29, [LexActions.REDUCE, 1]
112       ]),
113       extendRules($Vu, [LexActions.REDUCE, 42]),
114       extendRules($Vt, [LexActions.REDUCE, 34], ObjectFromPairs.of([
115diff --git a/tests/Formulas/MathTest.ts b/tests/Formulas/MathTest.ts
116index b28e3bc..2102b4b 100644
117--- a/tests/Formulas/MathTest.ts
118+++ b/tests/Formulas/MathTest.ts
119@@ -72,7 +72,8 @@ import {
120   MROUND,
121   FACTDOUBLE,
122   UNARY_PERCENT,
123-  MULTINOMIAL
124+  MULTINOMIAL,
125+  SERIESSUM
126 } from "../../src/Formulas/Math";
127 import * as ERRORS from "../../src/Errors";
128 import {
129@@ -1513,4 +1514,19 @@ test("MULTINOMIAL", function(){
130   catchAndAssertEquals(function() {
131     MULTINOMIAL.apply(this, []);
132   }, ERRORS.NA_ERROR);
133+});
134+
135+
136+test("SERIESSUM", function(){
137+  assertEquals(SERIESSUM(1, 0, 1, [4, 5, 6]), 15);
138+  assertEquals(SERIESSUM(1, 0, 1, [4, 5, 6, 10, 22]), 47);
139+  assertEquals(SERIESSUM(3, 0, 2, [4, 5, 6]), 535);
140+  assertEquals(SERIESSUM(3, 0, 2, [4, 5, 6, 10]), 7825);
141+  assertEquals(SERIESSUM(3, 2, 2, [4, 5, 6, 10]), 70425);
142+  catchAndAssertEquals(function() {
143+    SERIESSUM.apply(this, [1, 0, 1, [4, 5, 6], 10])
144+  }, ERRORS.NA_ERROR);
145+  catchAndAssertEquals(function() {
146+    SERIESSUM.apply(this, [1, 0, 1])
147+  }, ERRORS.NA_ERROR);
148 });
149\ No newline at end of file
150diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
151index 2d99ed9..b9bd912 100644
152--- a/tests/SheetFormulaTest.ts
153+++ b/tests/SheetFormulaTest.ts
154@@ -1046,6 +1046,10 @@ test("Sheet ROWS", function(){
155   assertFormulaEquals('=ROWS(B1:M44)', 44);
156 });
157 
158+test("Sheet SERIESSUM", function(){
159+  assertFormulaEquals('=SERIESSUM(1, 0, 1, [4, 5, 6])', 15);
160+});
161+
162 test("Sheet parsing error", function(){
163   assertFormulaEqualsError('= 10e', PARSE_ERROR);
164   assertFormulaEqualsError('= SUM(', PARSE_ERROR);