spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[Parser, TODO.md] documenting work to allow formulas to catch errors, references, etc, by handling Cell objects
author
Ben Vogt <[email protected]>
date
2017-07-08 15:08:02
stats
3 file(s) changed, 108 insertions(+), 58 deletions(-)
files
TODO.md
dist/parser.js
src/Parser.ts
  1diff --git a/TODO.md b/TODO.md
  2index 3823d59..c1460f9 100644
  3--- a/TODO.md
  4+++ b/TODO.md
  5@@ -5,7 +5,7 @@
  6 Instead of having non-primitives, (i.e. Date, DateTime, Time, Dollar), cells should have formats based on the highest-order type that was used during the compilation and execution of a cell's dependency. For example, `DATE` might return a number, but the cell that called `DATE` would be aware of it calling a formula that returns an non-primitive type, and would display the returned number as a Date. If you're using `DATE` in conjunction with `DOLLAR` it would still display the returned value as a Date. The hierarchy would look like: [Date, DateTime, Time, Dollar, number, boolean, string]. Advantages to this would include not having to cast down when using primitive operators, and flexibility in display. It would also simplify the types themselves, by having types be constants and just having helpers to convert, display, and do normal operations with them.
  7 
  8 
  9-### Sheet should automatically parse some values, unless told otherwises.
 10+### Sheet should automatically parse some values, unless told otherwise.
 11 When entering raw values into cells, if the value is a string, the Sheet should automatically attempt to convert to a number. For example, `= 10e2` should be be evaluated with a RegEx and converted to a number. See `Sheet.helper.number`.
 12 
 13 
 14@@ -20,34 +20,41 @@ For example the CHOOSE formula can't be parsed: `=CHOOSE(2, [1, 2, 3])`.
 15 For example 64 tbs to a qt.
 16 
 17 
 18+### Raw input of errors should be allowed.
 19+For example `=#N/A` should force an error to be thrown inside the cell.
 20+
 21+
 22 ### Fix documentation regular expression, it is butchering URLs.
 23 
 24 
 25-### Formulas to write
 26+### Meta-Formulas to write
 27+In general, many of these formulas can be written by allowing the Sheet and Parser to return Cell objects in addition to primitive types.
 28 
 29-* ERROR.TYPE - Requires changes to Parser.ts
 30-* ISBLANK - Requires changes to Parser.ts
 31-* ISERR - Requires changes to Parser.ts
 32-* ISERROR - Requires changes to Parser.ts
 33-* ISFORMULA - Requires changes to Parser.ts
 34-* ISNA - Requires changes to Parser.ts
 35-* ISREF - Requires changes to Parser.ts
 36-* TYPE - Requires changes to Parser.ts
 37-* CELL - Requires changes to Parser.ts
 38-* IFERROR - Requires changes to Parser.ts
 39-* ADDRESS - Requires changes to Parser.ts
 40-* COLUMN - Requires changes to Parser.ts
 41-* COLUMNS - Requires changes to Parser.ts
 42-* HLOOKUP - Requires changes to Parser.ts
 43-* INDEX - Requires changes to Parser.ts
 44-* INDIRECT - Requires changes to Parser.ts
 45-* LOOKUP - Requires changes to Parser.ts
 46-* MATCH - Requires changes to Parser.ts
 47-* OFFSET - Requires changes to Parser.ts
 48-* ROW - Requires changes to Parser.ts
 49-* ROWS - Requires changes to Parser.ts
 50-* VLOOKUP - Requires changes to Parser.ts
 51-* COUNTBLANK - Requires changes to
 52+* ISREF - Should be handled at a Parser/Sheet level. If the Parser or Sheet is able to fetch a cell -- even if it's empty -- this function should return true.
 53+* ERROR.TYPE - Fetch actual cell to check for error, which requires changes in Parser and Sheet. If the returned cell doesn't have an error, then it should throw NA.
 54+* ISBLANK - Requires changes to Parser/Sheet. If we fetch a cell, and it is "blank", return true. Could be implemented by adding a field to cells indicating if they're blank. Right now empty cells are causing errors because they don't exist. We should allow users to fetch empty cells, they should just be considered blank, or undefined, or null.
 55+* ISERR - Requires changes to Parser/Sheet to either wrap enclosed functions in try-catch, or fetch actual cell value, and check it's error field.
 56+* ISERROR - See ISERR.
 57+* ISFORMULA - Requires changes to Parser/Sheet to fetch a cell, and check the formula field to see if it contains a formula.
 58+* ISNA - Requires changes to Parser/Sheet for similar reasons to ISERR; check reference cell value or error field.
 59+* TYPE - Requires changes to Parser/Sheet to allow for values or cells to be returned to the function. If it's a value, return the value type. If it's a cell, return the value or error inside it.
 60+* 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.
 61+* IFERROR - Requires changes to Parser/Sheet for similar reasons to ISERR.
 62+* ADDRESS - Requires changes to Parser/Sheet
 63+* COLUMN - Requires changes to Parser/Sheet
 64+* COLUMNS - Requires changes to Parser/Sheet
 65+* HLOOKUP - Requires changes to Parser/Sheet
 66+* INDEX - Requires changes to Parser/Sheet
 67+* INDIRECT - Requires changes to Parser/Sheet
 68+* LOOKUP - Requires changes to Parser/Sheet
 69+* MATCH - Requires changes to Parser/Sheet
 70+* OFFSET - Requires changes to Parser/Sheet
 71+* ROW - Requires changes to Parser/Sheet
 72+* ROWS - Requires changes to Parser/Sheet
 73+* VLOOKUP - Requires changes to Parser/Sheet
 74+* COUNTBLANK - Requires changes to to Parser/Sheet so when we iterate through a range to return an array, we call a special function that accumulates all values, black/null/undefined or otherwise.
 75+
 76+### Formulas to write
 77 * SERIESSUM
 78 * SUBTOTAL
 79 * TO_DATE - Contingent upon cells having display formats derived from type-hierarchy
 80diff --git a/dist/parser.js b/dist/parser.js
 81index becf7b6..58653eb 100644
 82--- a/dist/parser.js
 83+++ b/dist/parser.js
 84@@ -235,9 +235,11 @@ var Parser = (function () {
 85                     }
 86                     break;
 87                 case 23:
 88+                    // console.log("message from parser: 'calling function with no args': ", $$[$0 - 2]);
 89                     this.$ = yy.handler.helper.callFunction.call(this, $$[$0 - 2], '');
 90                     break;
 91                 case 24:
 92+                    // console.log("message from parser: 'calling function w/ args': ", $$[$0 - 3], $$[$0 - 1]);
 93                     this.$ = yy.handler.helper.callFunction.call(this, $$[$0 - 3], $$[$0 - 1]);
 94                     break;
 95                 case 28:
 96@@ -1201,7 +1203,7 @@ var Parser = (function () {
 97                             }
 98                             else if (this._backtrack) {
 99                                 match = false;
100-                                continue; // rule action called reject() implying a rule MISmatch.
101+                                continue; // rule action called reject() implying a rule mis-match.
102                             }
103                             else {
104                                 // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
105@@ -1371,11 +1373,13 @@ var Parser = (function () {
106             rules: [/^(?:\s+)/,
107                 /^(?:"(\\["]|[^"])*")/,
108                 /^(?:'(\\[']|[^'])*')/,
109+                // Changed from /^(?:[A-Za-z]{1,}[A-Za-z_0-9]+(?=[(]))/
110                 /^(?:[A-Za-z.]{1,}[A-Za-z_0-9]+(?=[(]))/,
111                 /^(?:([0]?[1-9]|1[0-2])[:][0-5][0-9]([:][0-5][0-9])?[ ]?(AM|am|aM|Am|PM|pm|pM|Pm))/,
112                 /^(?:([0]?[0-9]|1[0-9]|2[0-3])[:][0-5][0-9]([:][0-5][0-9])?)/,
113                 /^(?:\$[A-Za-z]+\$[0-9]+)/,
114                 /^(?:[A-Za-z]+[0-9]+)/,
115+                // Changed from /^(?:[A-Za-z.]+(?=[(]))/
116                 /^(?:[A-Za-z.]+(?=[(]))/,
117                 /^(?:[A-Za-z]{1,}[A-Za-z_0-9]+)/,
118                 /^(?:[A-Za-z_]+)/,
119diff --git a/src/Parser.ts b/src/Parser.ts
120index 8035127..26a2e90 100644
121--- a/src/Parser.ts
122+++ b/src/Parser.ts
123@@ -234,9 +234,11 @@ var Parser = (function () {
124           }
125           break;
126         case 23:
127+          // console.log("message from parser: 'calling function with no args': ", $$[$0 - 2]);
128           this.$ = yy.handler.helper.callFunction.call(this, $$[$0 - 2], '');
129           break;
130         case 24:
131+          // console.log("message from parser: 'calling function w/ args': ", $$[$0 - 3], $$[$0 - 1]);
132           this.$ = yy.handler.helper.callFunction.call(this, $$[$0 - 3], $$[$0 - 1]);
133           break;
134         case 28:
135@@ -1027,7 +1029,7 @@ var Parser = (function () {
136         }
137       },
138 
139-// resets the lexer, sets new input
140+      // resets the lexer, sets new input
141       setInput: function (input, yy) {
142         this.yy = yy || this.yy || {};
143         this._input = input;
144@@ -1048,7 +1050,7 @@ var Parser = (function () {
145         return this;
146       },
147 
148-// consumes and returns one char from the input
149+      // consumes and returns one char from the input
150       input: function () {
151         var ch = this._input[0];
152         this.yytext += ch;
153@@ -1071,7 +1073,7 @@ var Parser = (function () {
154         return ch;
155       },
156 
157-// unshifts one char (or a string) into the input
158+      // unshifts one char (or a string) into the input
159       unput: function (ch) {
160         var len = ch.length;
161         var lines = ch.split(/(?:\r\n?|\n)/g);
162@@ -1106,13 +1108,13 @@ var Parser = (function () {
163         return this;
164       },
165 
166-// When called from action, caches matched text and appends it on next action
167+      // When called from action, caches matched text and appends it on next action
168       more: function () {
169         this._more = true;
170         return this;
171       },
172 
173-// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
174+      // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
175       reject: function () {
176         if (this.options.backtrack_lexer) {
177           this._backtrack = true;
178@@ -1127,18 +1129,18 @@ var Parser = (function () {
179         return this;
180       },
181 
182-// retain first n characters of the match
183+      // retain first n characters of the match
184       less: function (n) {
185         this.unput(this.match.slice(n));
186       },
187 
188-// displays already matched input, i.e. for error messages
189+      // displays already matched input, i.e. for error messages
190       pastInput: function () {
191         var past = this.matched.substr(0, this.matched.length - this.match.length);
192         return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, "");
193       },
194 
195-// displays upcoming input, i.e. for error messages
196+      // displays upcoming input, i.e. for error messages
197       upcomingInput: function () {
198         var next = this.match;
199         if (next.length < 20) {
200@@ -1147,14 +1149,14 @@ var Parser = (function () {
201         return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
202       },
203 
204-// displays the character position where the lexing error occurred, i.e. for error messages
205+      // displays the character position where the lexing error occurred, i.e. for error messages
206       showPosition: function () {
207         var pre = this.pastInput();
208         var c = new Array(pre.length + 1).join("-");
209         return pre + this.upcomingInput() + "\n" + c + "^";
210       },
211 
212-// test the lexed token: return FALSE when not a match, otherwise return token
213+      // test the lexed token: return FALSE when not a match, otherwise return token
214       test_match: function (match, indexed_rule) {
215         var token,
216           lines,
217@@ -1226,7 +1228,7 @@ var Parser = (function () {
218         return false;
219       },
220 
221-// return next match in input
222+      // return next match in input
223       next: function () {
224         if (this.done) {
225           return this.EOF;
226@@ -1255,7 +1257,7 @@ var Parser = (function () {
227                 return token;
228               } else if (this._backtrack) {
229                 match = false;
230-                continue; // rule action called reject() implying a rule MISmatch.
231+                continue; // rule action called reject() implying a rule mis-match.
232               } else {
233                 // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
234                 return false;
235@@ -1284,7 +1286,7 @@ var Parser = (function () {
236         }
237       },
238 
239-// return next match that has a token
240+      // return next match that has a token
241       lex: function lex() {
242         var r = this.next();
243         if (r) {
244@@ -1294,12 +1296,12 @@ var Parser = (function () {
245         }
246       },
247 
248-// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
249+      // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
250       begin: function begin(condition) {
251         this.conditionStack.push(condition);
252       },
253 
254-// pop the previously active lexer condition state off the condition stack
255+      // pop the previously active lexer condition state off the condition stack
256       popState: function popState() {
257         var n = this.conditionStack.length - 1;
258         if (n > 0) {
259@@ -1309,7 +1311,7 @@ var Parser = (function () {
260         }
261       },
262 
263-// produce the lexer rule set which is active for the currently active lexer condition state
264+      // produce the lexer rule set which is active for the currently active lexer condition state
265       _currentRules: function _currentRules() {
266         if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
267           return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
268@@ -1318,7 +1320,7 @@ var Parser = (function () {
269         }
270       },
271 
272-// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
273+      // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
274       topState: function topState(n) {
275         n = this.conditionStack.length - 1 - Math.abs(n || 0);
276         if (n >= 0) {
277@@ -1328,12 +1330,12 @@ var Parser = (function () {
278         }
279       },
280 
281-// alias for begin(condition)
282+      // alias for begin(condition)
283       pushState: function pushState(condition) {
284         this.begin(condition);
285       },
286 
287-// return the number of states currently on the stack
288+      // return the number of states currently on the stack
289       stateStackSize: function stateStackSize() {
290         return this.conditionStack.length;
291       },
292@@ -1422,43 +1424,45 @@ var Parser = (function () {
293         }
294       },
295       // NOTE: Alterations made in some regular-expressions to allow for formulas containing dot-notation. Eg: F.INV
296-      rules: [/^(?:\s+)/,
297-        /^(?:"(\\["]|[^"])*")/,
298-        /^(?:'(\\[']|[^'])*')/,
299-        /^(?:[A-Za-z.]{1,}[A-Za-z_0-9]+(?=[(]))/, // Changed from /^(?:[A-Za-z]{1,}[A-Za-z_0-9]+(?=[(]))/
300-        /^(?:([0]?[1-9]|1[0-2])[:][0-5][0-9]([:][0-5][0-9])?[ ]?(AM|am|aM|Am|PM|pm|pM|Pm))/,
301-        /^(?:([0]?[0-9]|1[0-9]|2[0-3])[:][0-5][0-9]([:][0-5][0-9])?)/,
302-        /^(?:\$[A-Za-z]+\$[0-9]+)/,
303-        /^(?:[A-Za-z]+[0-9]+)/,
304-        /^(?:[A-Za-z.]+(?=[(]))/, //Changed from /^(?:[A-Za-z.]+(?=[(]))/
305-        /^(?:[A-Za-z]{1,}[A-Za-z_0-9]+)/,
306-        /^(?:[A-Za-z_]+)/,
307-        /^(?:[0-9]+)/,
308-        /^(?:\[(.*)?\])/,
309-        /^(?:\$)/,
310-        /^(?:&)/,
311-        /^(?: )/,
312-        /^(?:[.])/,
313-        /^(?::)/,
314-        /^(?:;)/,
315-        /^(?:,)/,
316-        /^(?:\*)/,
317-        /^(?:\/)/,
318-        /^(?:-)/,
319-        /^(?:\+)/,
320-        /^(?:\^)/,
321-        /^(?:\()/,
322-        /^(?:\))/,
323-        /^(?:>)/,
324-        /^(?:<)/,
325-        /^(?:NOT\b)/,
326-        /^(?:")/,
327-        /^(?:')/,
328-        /^(?:!)/,
329-        /^(?:=)/,
330-        /^(?:%)/,
331-        /^(?:[#])/,
332-        /^(?:$)/],
333+      rules: [/^(?:\s+)/, // rule 0
334+        /^(?:"(\\["]|[^"])*")/, // rule 1
335+        /^(?:'(\\[']|[^'])*')/, // rule 2
336+        // Changed from /^(?:[A-Za-z]{1,}[A-Za-z_0-9]+(?=[(]))/
337+        /^(?:[A-Za-z.]{1,}[A-Za-z_0-9]+(?=[(]))/, // rule 3
338+        /^(?:([0]?[1-9]|1[0-2])[:][0-5][0-9]([:][0-5][0-9])?[ ]?(AM|am|aM|Am|PM|pm|pM|Pm))/, // rule 4
339+        /^(?:([0]?[0-9]|1[0-9]|2[0-3])[:][0-5][0-9]([:][0-5][0-9])?)/, // rule 5
340+        /^(?:\$[A-Za-z]+\$[0-9]+)/, // rule 6
341+        /^(?:[A-Za-z]+[0-9]+)/, // rule 7
342+        // Changed from /^(?:[A-Za-z.]+(?=[(]))/
343+        /^(?:[A-Za-z.]+(?=[(]))/, // rule 8
344+        /^(?:[A-Za-z]{1,}[A-Za-z_0-9]+)/, // rule 9
345+        /^(?:[A-Za-z_]+)/, // rule 10
346+        /^(?:[0-9]+)/, // rule 11
347+        /^(?:\[(.*)?\])/, // rule 12
348+        /^(?:\$)/, // rule 13
349+        /^(?:&)/, // rule 14
350+        /^(?: )/, // rule 15
351+        /^(?:[.])/, // rule 16
352+        /^(?::)/, // rule 17
353+        /^(?:;)/, // rule 18
354+        /^(?:,)/, // rule 19
355+        /^(?:\*)/, // rule 20
356+        /^(?:\/)/, // rule 21
357+        /^(?:-)/, // rule 22
358+        /^(?:\+)/, // rule 23
359+        /^(?:\^)/, // rule 24
360+        /^(?:\()/, // rule 25
361+        /^(?:\))/, // rule 26
362+        /^(?:>)/, // rule 27
363+        /^(?:<)/, // rule 28
364+        /^(?:NOT\b)/, // rule 29
365+        /^(?:")/, // rule 30
366+        /^(?:')/, // rule 31
367+        /^(?:!)/, // rule 32
368+        /^(?:=)/, // rule 33
369+        /^(?:%)/, // rule 34
370+        /^(?:[#])/, // rule 35
371+        /^(?:$)/], // rule 36
372       conditions: {
373         "INITIAL": {
374           "rules": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36],