spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[ParseEngine] simple number version working
author
Ben Vogt <[email protected]>
date
2017-12-09 20:35:17
stats
2 file(s) changed, 327 insertions(+), 94 deletions(-)
files
src/Parser/ParseEngine.ts
tests/Parser/ParseEngineTest.ts
  1diff --git a/src/Parser/ParseEngine.ts b/src/Parser/ParseEngine.ts
  2index 7b73fd2..42f5c22 100644
  3--- a/src/Parser/ParseEngine.ts
  4+++ b/src/Parser/ParseEngine.ts
  5@@ -327,7 +327,8 @@ const enum ReduceActions {
  6   START_ARRAY = 28,
  7   INVERT_NUMBER = 29,
  8   EXPRESSION = 30,
  9-  AS_ARRAY = 31
 10+  AS_ARRAY = 31,
 11+  REFLEXIVE_REDUCE = 31
 12 };
 13 
 14 /**
 15@@ -360,6 +361,9 @@ class ReductionPair {
 16 
 17 const enum Tree {
 18   START = 0,
 19+  NUMBER = 1,
 20+  STRING = 2,
 21+  BOOLEAN = 3,
 22   VARIABLE = 4,
 23   ERROR = 5,
 24   FORMULA = 6,
 25@@ -396,15 +400,15 @@ const enum Tree {
 26  */
 27 let productions : Array<ReductionPair> = [];
 28 productions[ReduceActions.NO_ACTION] = null;
 29-productions[ReduceActions.RETURN_LAST] = new ReductionPair(3, 2);
 30+productions[ReduceActions.RETURN_LAST] = new ReductionPair(Tree.NUMBER, 2);
 31 productions[ReduceActions.CALL_VARIABLE] = new ReductionPair(Tree.VARIABLE, 1);
 32-productions[ReduceActions.AS_NUMBER] = new ReductionPair(Tree.VARIABLE, 1);
 33+productions[ReduceActions.AS_NUMBER] = new ReductionPair(Tree.NUMBER, 1);
 34 productions[ReduceActions.INVERT_NUMBER] = new ReductionPair(Tree.VARIABLE, 1);
 35 productions[ReduceActions.AS_STRING] = new ReductionPair(Tree.VARIABLE, 1);
 36 productions[ReduceActions.AMPERSAND] = new ReductionPair(Tree.AMPERSAND, 3);
 37 productions[ReduceActions.EQUALS] = new ReductionPair(Tree.EQUALS, 3);
 38 productions[ReduceActions.PLUS] = new ReductionPair(Tree.PLUS, 3);
 39-productions[ReduceActions.LAST_NUMBER] = new ReductionPair(Tree.VARIABLE, 3);
 40+productions[ReduceActions.LAST_NUMBER] = new ReductionPair(Tree.NUMBER, 3);
 41 productions[ReduceActions.LTE] = new ReductionPair(Tree.VARIABLE, 4);
 42 productions[ReduceActions.GTE] = new ReductionPair(Tree.VARIABLE, 4);
 43 productions[ReduceActions.NOT_EQ] = new ReductionPair(Tree.VARIABLE, 4);
 44@@ -422,6 +426,7 @@ productions[ReduceActions.CELL_RANGE_VALUE] = new ReductionPair(Tree.VARIABLE, 3
 45 productions[ReduceActions.PERCENT] = new ReductionPair(Tree.VARIABLE, 3);
 46 productions[ReduceActions.AS_ERROR] = new ReductionPair(Tree.ERROR, 1);
 47 productions[ReduceActions.AS_ARRAY] = new ReductionPair(Tree.VARIABLE, 1);
 48+productions[ReduceActions.REFLEXIVE_REDUCE] = new ReductionPair(Tree.VARIABLE, 1);
 49 const PRODUCTIONS = productions;
 50 
 51 
 52@@ -434,85 +439,16 @@ let table = [];
 53 // All functions in the spreadsheet start with a 0-token.
 54 // `=`
 55 table[Tree.START] = ObjectBuilder
 56-  .add(Symbol.NUMBER, Tree.VARIABLE)
 57+  .add(Symbol.NUMBER, Tree.NUMBER)
 58   .add(Symbol.WHITE_SPACE, Tree.START)
 59   .add(Symbol.END, Tree.TERMINATE)
 60   .build();
 61-table[Tree.VARIABLE] = ObjectBuilder
 62-  .add(Symbol.PLUS, [SHIFT, ReduceActions.PLUS])
 63-  .add(Symbol.MINUS, [SHIFT, ReduceActions.MINUS])
 64-  .add(Symbol.ASTERISK, [SHIFT, ReduceActions.MULTIPLY])
 65-  .add(Symbol.DIVIDE, [SHIFT, ReduceActions.DIVIDE])
 66-  .add(Symbol.CARROT, [SHIFT, ReduceActions.TO_POWER])
 67-  .add(Symbol.PERCENT, [REDUCE, ReduceActions.PERCENT])
 68-  .add(Symbol.AMPERSAND, [SHIFT, ReduceActions.AMPERSAND])
 69-  .add(Symbol.WHITE_SPACE, Tree.TERMINATE)
 70-  .add(Symbol.END, Tree.TERMINATE)
 71-  .build();
 72-table[Tree.PLUS] = ObjectBuilder
 73-  .add(Symbol.VARIABLE, null)
 74-  .add(Symbol.CELL_REF, null)
 75-  .add(Symbol.FIXED_CELL_REF, null)
 76-  .add(Symbol.POUND, null)
 77-  .add(Symbol.FORMULA, null)
 78-  .add(Symbol.OPEN_PAREN, null)
 79-  .add(Symbol.OPEN_ARRAY, null)
 80-  .add(Symbol.WHITE_SPACE, null)
 81-  .build();
 82-table[Tree.MINUS] = ObjectBuilder
 83-  .add(Symbol.VARIABLE, null)
 84-  .add(Symbol.CELL_REF, null)
 85-  .add(Symbol.FIXED_CELL_REF, null)
 86-  .add(Symbol.POUND, null)
 87-  .add(Symbol.FORMULA, null)
 88-  .add(Symbol.OPEN_PAREN, null)
 89-  .add(Symbol.OPEN_ARRAY, null)
 90-  .add(Symbol.WHITE_SPACE, null)
 91-  .build();
 92-table[Tree.ASTERISK] = ObjectBuilder
 93-  .add(Symbol.VARIABLE, null)
 94-  .add(Symbol.CELL_REF, null)
 95-  .add(Symbol.FIXED_CELL_REF, null)
 96-  .add(Symbol.POUND, null)
 97-  .add(Symbol.FORMULA, null)
 98-  .add(Symbol.OPEN_PAREN, null)
 99-  .add(Symbol.OPEN_ARRAY, null)
100-  .add(Symbol.WHITE_SPACE, null)
101-  .build();
102-table[Tree.SLASH] = ObjectBuilder
103-  .add(Symbol.VARIABLE, null)
104-  .add(Symbol.CELL_REF, null)
105-  .add(Symbol.FIXED_CELL_REF, null)
106-  .add(Symbol.POUND, null)
107-  .add(Symbol.FORMULA, null)
108-  .add(Symbol.OPEN_PAREN, null)
109-  .add(Symbol.OPEN_ARRAY, null)
110-  .add(Symbol.WHITE_SPACE, null)
111-  .build();
112-table[Tree.CARROT] = ObjectBuilder
113-  .add(Symbol.VARIABLE, null)
114-  .add(Symbol.CELL_REF, null)
115-  .add(Symbol.FIXED_CELL_REF, null)
116-  .add(Symbol.POUND, null)
117-  .add(Symbol.FORMULA, null)
118-  .add(Symbol.OPEN_PAREN, null)
119-  .add(Symbol.OPEN_ARRAY, null)
120-  .add(Symbol.WHITE_SPACE, null)
121+table[Tree.NUMBER] = ObjectBuilder
122+  .add(Symbol.WHITE_SPACE, Tree.NUMBER)
123+  .add(Symbol.END, [REDUCE, ReduceActions.AS_NUMBER])
124   .build();
125-table[Tree.AMPERSAND] = ObjectBuilder
126-  .add(Symbol.VARIABLE, null)
127-  .add(Symbol.CELL_REF, null)
128-  .add(Symbol.FIXED_CELL_REF, null)
129-  .add(Symbol.POUND, null)
130-  .add(Symbol.FORMULA, null)
131-  .add(Symbol.OPEN_PAREN, null)
132-  .add(Symbol.OPEN_ARRAY, null)
133-  .add(Symbol.WHITE_SPACE, null)
134-  .build();
135-table[Tree.EXPRESSION] = null;
136-table[Tree.INVERT_NEXT] = null;
137 table[Tree.TERMINATE] = ObjectBuilder
138-  .add(Symbol.END, [ACCEPT, ReduceActions.RETURN_LAST])
139+  .add(Symbol.END, [REDUCE, ReduceActions.RETURN_LAST])
140   .build();
141 const ACTION_TABLE = table;
142 
143@@ -621,6 +557,9 @@ let Parser = (function () {
144           case ReduceActions.AS_ERROR:
145             this.$ = constructErrorByName(virtualStack[vsl]);
146             break;
147+          case ReduceActions.REFLEXIVE_REDUCE:
148+            this.$ = virtualStack[vsl];
149+            break;
150         }
151       } catch (e) {
152         if (catchOnFailure) {
153@@ -767,11 +706,30 @@ let Parser = (function () {
154           }
155           // read action for current state and first input
156           action = ACTION_TABLE[state] && ACTION_TABLE[state][symbol];
157-          console.log(action, state, symbol);
158         }
159 
160-        // handle parse error
161-        if (typeof action === 'undefined' || !action.length || !action[0]) {
162+        console.log("STEP");
163+        console.log({
164+          text: lexer.match,
165+          token: SYMBOL_INDEX_TO_NAME[symbol] || symbol,
166+          symbol: symbol,
167+          tokenIndex: symbol,
168+          line: lexer.yylineno,
169+          loc: yyloc,
170+          state: state,
171+          stack: stack,
172+          semanticValueStack: semanticValueStack
173+        });
174+
175+        if (typeof action == "number") {
176+          stack.push(symbol);
177+          semanticValueStack.push(lexer.yytext);
178+          locationStack.push(lexer.yylloc);
179+          stack.push(action);
180+          symbol = null;
181+          continue;
182+          // handle parse error
183+        } else if (typeof action === 'undefined' || !action.length || !action[0]) {
184           let error_rule_depth;
185           let errStr = '';
186 
187@@ -1109,7 +1067,6 @@ let Parser = (function () {
188         }
189         if (match) {
190           token = this.test_match(match, rules[index]);
191-          console.log("match", match, "index", index, "token", token);
192           if (token !== false) {
193             return token;
194           }
195@@ -1166,7 +1123,7 @@ let Parser = (function () {
196           case 8:
197             return ReduceActions.CALL_FUNCTION_LAST_BLANK;
198           case 9:
199-            return ReduceActions.AS_NUMBER;
200+            return Symbol.NUMBER;
201           case 12:
202             return ReduceActions.FIXED_CELL_RANGE_VAL;
203           case 13:
204diff --git a/tests/Parser/ParseEngineTest.ts b/tests/Parser/ParseEngineTest.ts
205index a00d64c..6ff8809 100644
206--- a/tests/Parser/ParseEngineTest.ts
207+++ b/tests/Parser/ParseEngineTest.ts
208@@ -1,6 +1,10 @@
209 import {
210   Parser
211 } from "../../src/Parser/ParseEngine";
212+import {TypeConverter} from "../../src/Utilities/TypeConverter";
213+import {DivZeroError, NameError} from "../../src/Errors";
214+import {Formulas} from "../../src/Formulas";
215+import {assertEquals, test} from "../Utils/Asserts";
216 
217 
218 let FormulaParser = function(handler) {
219@@ -31,7 +35,286 @@ let FormulaParser = function(handler) {
220   return newParser;
221 };
222 
223-let parser = FormulaParser({});
224+let utils = {
225+  isArray: function (value) {
226+    return value instanceof Array;
227+  },
228+
229+  isFunction: function (value) {
230+    return value instanceof Function;
231+  },
232+
233+  toNum: function (chr) {
234+    chr = utils.clearFormula(chr);
235+    let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
236+
237+    for (i = 0, j = chr.length - 1; i < chr.length; i += 1, j -= 1) {
238+      result += Math.pow(base.length, j) * (base.indexOf(chr[i]) + 1);
239+    }
240+
241+    if (result) {
242+      --result;
243+    }
244+
245+    return result;
246+  },
247+
248+  toChar: function (num) {
249+    let s = '';
250+
251+    while (num >= 0) {
252+      s = String.fromCharCode(num % 26 + 97) + s;
253+      num = Math.floor(num / 26) - 1;
254+    }
255+
256+    return s.toUpperCase();
257+  },
258+  XYtoA1: function (x, y) {
259+    function numberToLetters(num) {
260+      let mod = num % 26,
261+        pow = num / 26 | 0,
262+        out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
263+      return pow ? numberToLetters(pow) + out : out;
264+    }
265+    return numberToLetters(x+1) + (y+1).toString();
266+  },
267+  cellCoords: function (cell) {
268+    let num = cell.match(/\d+$/),
269+      alpha = cell.replace(num, '');
270+
271+    return {
272+      row: parseInt(num[0], 10) - 1,
273+      col: utils.toNum(alpha)
274+    };
275+  },
276+
277+  clearFormula: function (formula) {
278+    return formula.replace(/\$/g, '');
279+  },
280+
281+  iterateCells: function (startCell, endCell, callback) {
282+    let result = {
283+      index: [], // list of cell index: A1, A2, A3
284+      value: []  // list of cell value
285+    };
286+
287+    let cols = {
288+      start: 0,
289+      end: 0
290+    };
291+
292+    if (endCell.col >= startCell.col) {
293+      cols = {
294+        start: startCell.col,
295+        end: endCell.col
296+      };
297+    } else {
298+      cols = {
299+        start: endCell.col,
300+        end: startCell.col
301+      };
302+    }
303+
304+    let rows = {
305+      start: 0,
306+      end: 0
307+    };
308+
309+    if (endCell.row >= startCell.row) {
310+      rows = {
311+        start: startCell.row,
312+        end: endCell.row
313+      };
314+    } else {
315+      rows = {
316+        start: endCell.row,
317+        end: startCell.row
318+      };
319+    }
320+
321+    for (let column = cols.start; column <= cols.end; column++) {
322+      for (let row = rows.start; row <= rows.end; row++) {
323+        let cellIndex = utils.toChar(column) + (row + 1),
324+          cellValue = helper.cellValue.call(this, cellIndex);
325+
326+        result.index.push(cellIndex);
327+        result.value.push(cellValue);
328+      }
329+    }
330+
331+    if (utils.isFunction(callback)) {
332+      return callback.apply(callback, [result]);
333+    } else {
334+      return result;
335+    }
336+  },
337+
338+  sort: function (rev) {
339+    return function (a, b) {
340+      return ((a < b) ? -1 : ((a > b) ? 1 : 0)) * (rev ? -1 : 1);
341+    }
342+  }
343+};
344+
345+let helper = {
346+  /**
347+   * Is the value a number or can the value be interpreted as a number
348+   */
349+  number: function (x) {
350+    return TypeConverter.valueToNumber(x);
351+  },
352+
353+  string: function (str) {
354+    return str.substring(1, str.length - 1);
355+  },
356+
357+  numberInverted: function (num) {
358+    return this.number(num) * (-1);
359+  },
360+
361+  specialMatch: function (type, exp1, exp2) {
362+    let result;
363+
364+    switch (type) {
365+      case '&':
366+        result = exp1.toString() + exp2.toString();
367+        break;
368+    }
369+    return result;
370+  },
371+
372+  logicMatch: function (type, exp1, exp2) {
373+    let result;
374+
375+    switch (type) {
376+      case '=':
377+        result = (exp1 === exp2);
378+        break;
379+
380+      case '>':
381+        result = (exp1 > exp2);
382+        break;
383+
384+      case '<':
385+        result = (exp1 < exp2);
386+        break;
387+
388+      case '>=':
389+        result = (exp1 >= exp2);
390+        break;
391+
392+      case '<=':
393+        result = (exp1 === exp2);
394+        break;
395+
396+      case '<>':
397+        result = (exp1 != exp2);
398+        break;
399+
400+      case 'NOT':
401+        result = (exp1 != exp2);
402+        break;
403+    }
404+
405+    return result;
406+  },
407+
408+  mathMatch: function (type, number1, number2) {
409+    let result;
410+
411+    number1 = helper.number(number1);
412+    number2 = helper.number(number2);
413+
414+    switch (type) {
415+      case '+':
416+        result = number1 + number2;
417+        break;
418+      case '-':
419+        result = number1 - number2;
420+        break;
421+      case '/':
422+        if (number2 === 0) {
423+          throw new DivZeroError("Evaluation caused divide by zero error.");
424+        }
425+        if (number2 !== 0 && number1 === 0) {
426+          result = 0;
427+        }
428+        result = number1 / number2;
429+        if (result == Infinity) {
430+          throw new DivZeroError("Evaluation caused divide by zero error.");
431+        } else if (isNaN(result)) {
432+          throw new DivZeroError("Evaluation caused divide by zero error.");
433+        }
434+        break;
435+      case '*':
436+        result = number1 * number2;
437+        break;
438+      case '^':
439+        result = Math.pow(number1, number2);
440+        break;
441+    }
442+
443+    return result;
444+  },
445+
446+  callFunction: function (fn, args) {
447+    fn = fn.toUpperCase();
448+    args = args || [];
449+    let formulas = {
450+      "SUM": function(...args) {
451+        return 10;
452+      }
453+    };
454+    if (fn in formulas) {
455+      return formulas[fn].apply(this, args);
456+    }
457+
458+    throw new NameError("Unknown function: '" + fn + "'.");
459+  },
460+
461+  callVariable: function (args) {
462+    args = args || [];
463+    let str = args.shift(); // the first in args is the name of the function to call.
464+
465+    if (str) {
466+      str = str.toUpperCase();
467+      if (Formulas.exists(str)) {
468+        return Formulas.get(str).apply(this, args);
469+      }
470+    }
471+
472+    throw new NameError("Unknown variable: '" + str + "'.");
473+  },
474+
475+  cellValue: function (cellId) {
476+
477+  },
478+
479+  cellRangeValue: function (start: string, end: string) {
480+
481+  },
482+
483+  fixedCellValue: function (id) {
484+    id = id.replace(/\$/g, '');
485+    return helper.cellValue.call(this, id);
486+  },
487+
488+  fixedCellRangeValue: function (start, end) {
489+    start = start.replace(/\$/g, '');
490+    end = end.replace(/\$/g, '');
491+
492+    return helper.cellRangeValue.call(this, start, end);
493+  }
494+};
495+
496+
497+let parser = FormulaParser({
498+  helper: helper,
499+  utils: utils
500+});
501 parser.setObj("A1");
502 
503-console.log(parser.parse('5'));
504\ No newline at end of file
505+
506+test("Declare number", function () {
507+  assertEquals(parser.parse('5'), 5);
508+});
509\ No newline at end of file