commit
message
[Parser, Sheet, MoreUtils] refactoring static utility functions
author
Ben Vogt <[email protected]>
date
2017-12-20 00:47:44
stats
4 file(s) changed,
281 insertions(+),
209 deletions(-)
files
src/Parser/Parser.ts
src/Sheet.ts
src/Utilities/MoreUtils.ts
src/Utilities/TypeConverter.ts
1diff --git a/src/Parser/Parser.ts b/src/Parser/Parser.ts
2index 6c9494e..1f720ca 100644
3--- a/src/Parser/Parser.ts
4+++ b/src/Parser/Parser.ts
5@@ -1,5 +1,5 @@
6 import {
7- constructErrorByName,
8+ constructErrorByName, DivZeroError,
9 ParseError
10 } from "../Errors";
11 import {
12@@ -19,7 +19,118 @@ import {
13 RuleIndex,
14 Symbol
15 } from "./ParserConstants"
16-import {isUndefined} from "../Utilities/MoreUtils";
17+import {
18+ isArray,
19+ isUndefined,
20+ string
21+} from "../Utilities/MoreUtils";
22+import {TypeConverter} from "../Utilities/TypeConverter";
23+
24+
25+/**
26+ * Performs logical operations on two values.
27+ * @param type of logic operation
28+ * @param exp1
29+ * @param exp2
30+ * @returns {boolean}
31+ */
32+function logicMatch(type, exp1, exp2) {
33+ let result;
34+
35+ switch (type) {
36+ case '=':
37+ result = (exp1 === exp2);
38+ break;
39+
40+ case '>':
41+ result = (exp1 > exp2);
42+ break;
43+
44+ case '<':
45+ result = (exp1 < exp2);
46+ break;
47+
48+ case '>=':
49+ result = (exp1 >= exp2);
50+ break;
51+
52+ case '<=':
53+ result = (exp1 <= exp2);
54+ break;
55+
56+ case '<>':
57+ result = (exp1 != exp2);
58+ break;
59+
60+ case 'NOT':
61+ result = (exp1 != exp2);
62+ break;
63+ }
64+
65+ return result;
66+}
67+
68+/**
69+ * Performs math operations on two values.
70+ * @param type
71+ * @param number1
72+ * @param number2
73+ * @returns {number}
74+ */
75+function mathMatch(type, number1, number2) {
76+ let result;
77+
78+ number1 = TypeConverter.valueToNumber(number1);
79+ number2 = TypeConverter.valueToNumber(number2);
80+
81+ switch (type) {
82+ case '+':
83+ result = number1 + number2;
84+ break;
85+ case '-':
86+ result = number1 - number2;
87+ break;
88+ case '/':
89+ if (number2 === 0) {
90+ throw new DivZeroError("Evaluation caused divide by zero error.");
91+ }
92+ if (number2 !== 0 && number1 === 0) {
93+ result = 0;
94+ }
95+ result = number1 / number2;
96+ if (result == Infinity) {
97+ throw new DivZeroError("Evaluation caused divide by zero error.");
98+ } else if (isNaN(result)) {
99+ throw new DivZeroError("Evaluation caused divide by zero error.");
100+ }
101+ break;
102+ case '*':
103+ result = number1 * number2;
104+ break;
105+ case '^':
106+ result = Math.pow(number1, number2);
107+ break;
108+ }
109+ return result;
110+}
111+
112+/**
113+ * Performs special operations on two values. Currently only concatenation.
114+ * @param type
115+ * @param exp1
116+ * @param exp2
117+ * @returns {any}
118+ */
119+function specialMatch(type, exp1, exp2) {
120+ let result;
121+
122+ switch (type) {
123+ case '&':
124+ result = exp1.toString() + exp2.toString();
125+ break;
126+ }
127+ return result;
128+}
129
130 let Parser = (function () {
131 let parser = {
132@@ -51,58 +162,58 @@ let Parser = (function () {
133 this.$ = sharedStateYY.handler.callVariable.call(this, virtualStack[vsl]);
134 break;
135 case ReduceActions.AS_NUMBER:
136- this.$ = sharedStateYY.handler.number(virtualStack[vsl]);
137+ this.$ = TypeConverter.valueToNumber(virtualStack[vsl]);
138 break;
139 case ReduceActions.AS_STRING:
140- this.$ = sharedStateYY.handler.string(virtualStack[vsl]);
141+ this.$ = string(virtualStack[vsl]);
142 break;
143 case ReduceActions.AMPERSAND:
144- this.$ = sharedStateYY.handler.specialMatch('&', virtualStack[vsl - 2], virtualStack[vsl]);
145+ this.$ = specialMatch('&', virtualStack[vsl - 2], virtualStack[vsl]);
146 break;
147 case ReduceActions.EQUALS:
148- this.$ = sharedStateYY.handler.logicMatch('=', virtualStack[vsl - 2], virtualStack[vsl]);
149+ this.$ = logicMatch('=', virtualStack[vsl - 2], virtualStack[vsl]);
150 break;
151 case ReduceActions.PLUS:
152- this.$ = sharedStateYY.handler.mathMatch('+', virtualStack[vsl - 2], virtualStack[vsl]);
153+ this.$ = mathMatch('+', virtualStack[vsl - 2], virtualStack[vsl]);
154 break;
155 case ReduceActions.LAST_NUMBER:
156- this.$ = sharedStateYY.handler.number(virtualStack[vsl - 1]);
157+ this.$ = TypeConverter.valueToNumber(virtualStack[vsl - 1]);
158 break;
159 case ReduceActions.LTE:
160- this.$ = sharedStateYY.handler.logicMatch('<=', virtualStack[vsl - 3], virtualStack[vsl]);
161+ this.$ = logicMatch('<=', virtualStack[vsl - 3], virtualStack[vsl]);
162 break;
163 case ReduceActions.GTE:
164- this.$ = sharedStateYY.handler.logicMatch('>=', virtualStack[vsl - 3], virtualStack[vsl]);
165+ this.$ = logicMatch('>=', virtualStack[vsl - 3], virtualStack[vsl]);
166 break;
167 case ReduceActions.NOT_EQ:
168- this.$ = sharedStateYY.handler.logicMatch('<>', virtualStack[vsl - 3], virtualStack[vsl]);
169+ this.$ = logicMatch('<>', virtualStack[vsl - 3], virtualStack[vsl]);
170 break;
171 case ReduceActions.GT:
172- this.$ = sharedStateYY.handler.logicMatch('>', virtualStack[vsl - 2], virtualStack[vsl]);
173+ this.$ = logicMatch('>', virtualStack[vsl - 2], virtualStack[vsl]);
174 break;
175 case ReduceActions.LT:
176- this.$ = sharedStateYY.handler.logicMatch('<', virtualStack[vsl - 2], virtualStack[vsl]);
177+ this.$ = logicMatch('<', virtualStack[vsl - 2], virtualStack[vsl]);
178 break;
179 case ReduceActions.MINUS:
180- this.$ = sharedStateYY.handler.mathMatch('-', virtualStack[vsl - 2], virtualStack[vsl]);
181+ this.$ = mathMatch('-', virtualStack[vsl - 2], virtualStack[vsl]);
182 break;
183 case ReduceActions.MULTIPLY:
184- this.$ = sharedStateYY.handler.mathMatch('*', virtualStack[vsl - 2], virtualStack[vsl]);
185+ this.$ = mathMatch('*', virtualStack[vsl - 2], virtualStack[vsl]);
186 break;
187 case ReduceActions.DIVIDE:
188- this.$ = sharedStateYY.handler.mathMatch('/', virtualStack[vsl - 2], virtualStack[vsl]);
189+ this.$ = mathMatch('/', virtualStack[vsl - 2], virtualStack[vsl]);
190 break;
191 case ReduceActions.TO_POWER:
192- this.$ = sharedStateYY.handler.mathMatch('^', virtualStack[vsl - 2], virtualStack[vsl]);
193+ this.$ = mathMatch('^', virtualStack[vsl - 2], virtualStack[vsl]);
194 break;
195 case ReduceActions.INVERT_NUM:
196- this.$ = sharedStateYY.handler.numberInverted(virtualStack[vsl]);
197+ this.$ = TypeConverter.valueToInvertedNumber(virtualStack[vsl]);
198 if (isNaN(this.$)) {
199 this.$ = 0;
200 }
201 break;
202 case ReduceActions.TO_NUMBER_NAN_AS_ZERO:
203- this.$ = sharedStateYY.handler.number(virtualStack[vsl]);
204+ this.$ = TypeConverter.valueToNumber(virtualStack[vsl]);
205 if (isNaN(this.$)) {
206 this.$ = 0;
207 }
208@@ -126,7 +237,7 @@ let Parser = (function () {
209 this.$ = sharedStateYY.handler.cellRangeValue(sharedStateYY.obj, virtualStack[vsl - 2], virtualStack[vsl]);
210 break;
211 case ReduceActions.ENSURE_IS_ARRAY:
212- if (sharedStateYY.handler.isArray(virtualStack[vsl])) {
213+ if (isArray(virtualStack[vsl])) {
214 this.$ = virtualStack[vsl];
215 } else {
216 this.$ = [virtualStack[vsl]];
217@@ -149,7 +260,7 @@ let Parser = (function () {
218 this.$ = [virtualStack[vsl]];
219 break;
220 case ReduceActions.ENSURE_LAST_TWO_IN_ARRAY_AND_PUSH:
221- this.$ = (sharedStateYY.handler.isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
222+ this.$ = (isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
223 this.$.push(virtualStack[vsl]);
224 break;
225 case ReduceActions.REFLEXIVE_REDUCE:
226@@ -212,7 +323,7 @@ let Parser = (function () {
227 }
228 break;
229 case ReduceActions.ENSURE_IS_ARRAY:
230- if (sharedStateYY.handler.isArray(virtualStack[vsl])) {
231+ if (isArray(virtualStack[vsl])) {
232 this.$ = virtualStack[vsl];
233 } else {
234 this.$ = [virtualStack[vsl]];
235@@ -235,7 +346,7 @@ let Parser = (function () {
236 this.$ = [virtualStack[vsl]];
237 break;
238 case ReduceActions.ENSURE_LAST_TWO_IN_ARRAY_AND_PUSH:
239- this.$ = (sharedStateYY.handler.isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
240+ this.$ = (isArray(virtualStack[vsl - 2]) ? virtualStack[vsl - 2] : [virtualStack[vsl - 2]]);
241 this.$.push(virtualStack[vsl]);
242 break;
243 case ReduceActions.REFLEXIVE_REDUCE:
244diff --git a/src/Sheet.ts b/src/Sheet.ts
245index e7c37e9..98168dc 100644
246--- a/src/Sheet.ts
247+++ b/src/Sheet.ts
248@@ -1,9 +1,12 @@
249 import {Cell} from "./Cell";
250-import {DivZeroError, NameError, RefError} from "./Errors";
251+import {NameError, RefError} from "./Errors";
252 import {DataStore} from "./Parser/DataStore";
253 import {FormulaParser} from "./Parser/Parser";
254-import {TypeConverter} from "./Utilities/TypeConverter";
255 import {Formulas} from "./Formulas";
256+import {
257+ isFunction, characterToNumber, numberToCharacter, convertXYtoA1Coordinates,
258+ A1toRowColCoordinates
259+} from "./Utilities/MoreUtils";
260
261 // TODO: Document.
262 class Sheet {
263@@ -21,64 +24,6 @@ class Sheet {
264 this.dataStore = new DataStore();
265 }
266
267- isArray (value) {
268- return value instanceof Array;
269- };
270-
271- isFunction (value) {
272- return value instanceof Function;
273- };
274-
275- toNum (chr) {
276- chr = this.clearFormula(chr);
277- let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
278-
279- for (i = 0, j = chr.length - 1; i < chr.length; i += 1, j -= 1) {
280- result += Math.pow(base.length, j) * (base.indexOf(chr[i]) + 1);
281- }
282-
283- if (result) {
284- --result;
285- }
286-
287- return result;
288- };
289-
290- toChar (num) {
291- let s = '';
292-
293- while (num >= 0) {
294- s = String.fromCharCode(num % 26 + 97) + s;
295- num = Math.floor(num / 26) - 1;
296- }
297-
298- return s.toUpperCase();
299- };
300-
301- XYtoA1 (x, y) {
302- function numberToLetters(num) {
303- let mod = num % 26,
304- pow = num / 26 | 0,
305- out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
306- return pow ? numberToLetters(pow) + out : out;
307- }
308- return numberToLetters(x+1) + (y+1).toString();
309- };
310-
311- cellCoords (cell) {
312- let num = cell.match(/\d+$/),
313- alpha = cell.replace(num, '');
314-
315- return {
316- row: parseInt(num[0], 10) - 1,
317- col: this.toNum(alpha)
318- };
319- };
320-
321- clearFormula (formula) {
322- return formula.replace(/\$/g, '');
323- };
324-
325 iterateCells (origin, startCell, endCell, callback?) {
326 let result = {
327 index: [], // list of cell index: A1, A2, A3
328@@ -121,7 +66,7 @@ class Sheet {
329
330 for (let column = cols.start; column <= cols.end; column++) {
331 for (let row = rows.start; row <= rows.end; row++) {
332- let cellIndex = this.toChar(column) + (row + 1),
333+ let cellIndex = numberToCharacter(column) + (row + 1),
334 cellValue = this.cellValue(origin, cellIndex);
335
336 result.index.push(cellIndex);
337@@ -129,117 +74,13 @@ class Sheet {
338 }
339 }
340
341- if (this.isFunction(callback)) {
342+ if (isFunction(callback)) {
343 return callback.apply(callback, [result]);
344 } else {
345 return result;
346 }
347 }
348
349- sort(rev) {
350- return function (a, b) {
351- return ((a < b) ? -1 : ((a > b) ? 1 : 0)) * (rev ? -1 : 1);
352- }
353- }
354-
355-
356- number(x) {
357- return TypeConverter.valueToNumber(x);
358- }
359-
360- string(str) {
361- return str.substring(1, str.length - 1);
362- }
363-
364- numberInverted (num) {
365- return this.number(num) * (-1);
366- }
367-
368- specialMatch(type, exp1, exp2) {
369- let result;
370-
371- switch (type) {
372- case '&':
373- result = exp1.toString() + exp2.toString();
374- break;
375- }
376- return result;
377- }
378-
379- logicMatch(type, exp1, exp2) {
380- let result;
381-
382- switch (type) {
383- case '=':
384- result = (exp1 === exp2);
385- break;
386-
387- case '>':
388- result = (exp1 > exp2);
389- break;
390-
391- case '<':
392- result = (exp1 < exp2);
393- break;
394-
395- case '>=':
396- result = (exp1 >= exp2);
397- break;
398-
399- case '<=':
400- result = (exp1 <= exp2);
401- break;
402-
403- case '<>':
404- result = (exp1 != exp2);
405- break;
406-
407- case 'NOT':
408- result = (exp1 != exp2);
409- break;
410- }
411-
412- return result;
413- };
414-
415- mathMatch(type, number1, number2) {
416- let result;
417-
418- number1 = this.number(number1);
419- number2 = this.number(number2);
420-
421- switch (type) {
422- case '+':
423- result = number1 + number2;
424- break;
425- case '-':
426- result = number1 - number2;
427- break;
428- case '/':
429- if (number2 === 0) {
430- throw new DivZeroError("Evaluation caused divide by zero error.");
431- }
432- if (number2 !== 0 && number1 === 0) {
433- result = 0;
434- }
435- result = number1 / number2;
436- if (result == Infinity) {
437- throw new DivZeroError("Evaluation caused divide by zero error.");
438- } else if (isNaN(result)) {
439- throw new DivZeroError("Evaluation caused divide by zero error.");
440- }
441- break;
442- case '*':
443- result = number1 * number2;
444- break;
445- case '^':
446- result = Math.pow(number1, number2);
447- break;
448- }
449-
450- return result;
451- }
452-
453 callFunction(fn, args) {
454 fn = fn.toUpperCase();
455 args = args || [];
456@@ -279,8 +120,8 @@ class Sheet {
457 }
458
459 cellRangeValue(origin, start: string, end: string) {
460- let coordsStart = this.cellCoords(start),
461- coordsEnd = this.cellCoords(end);
462+ let coordsStart = A1toRowColCoordinates(start),
463+ coordsEnd = A1toRowColCoordinates(end);
464
465 // iterate cells to get values and indexes
466 let cells = this.iterateCells(origin, coordsStart, coordsEnd),
467@@ -384,7 +225,7 @@ class Sheet {
468 for (let y = 0; y < input.length; y++) {
469 for (let x = 0; x < input[0].length; x++) {
470 // set the cell here
471- let id = this.XYtoA1(x, y);
472+ let id = convertXYtoA1Coordinates(x, y);
473 this.setCell(id, input[y][x].toString());
474 }
475 }
476diff --git a/src/Utilities/MoreUtils.ts b/src/Utilities/MoreUtils.ts
477index cb0e2b4..a5bb31f 100644
478--- a/src/Utilities/MoreUtils.ts
479+++ b/src/Utilities/MoreUtils.ts
480@@ -16,6 +16,100 @@ function isDefined(value : any) : boolean {
481 return value !== undefined;
482 }
483
484+/**
485+ * Returns true if value is an instance of a Array.
486+ * @param value
487+ * @returns {boolean}
488+ */
489+function isArray(value) : boolean {
490+ return value instanceof Array;
491+}
492+
493+/**
494+ * Returns true if value is instance of a Function.
495+ * @param value
496+ * @returns {boolean}
497+ */
498+function isFunction(value) : boolean {
499+ return value instanceof Function;
500+}
501+
502+/**
503+ * Alphabetical character to number.
504+ * @param chr
505+ * @returns {number}
506+ */
507+function characterToNumber(chr) {
508+ chr = chr.replace(/\$/g, '');
509+ let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
510+
511+ for (i = 0, j = chr.length - 1; i < chr.length; i += 1, j -= 1) {
512+ result += Math.pow(base.length, j) * (base.indexOf(chr[i]) + 1);
513+ }
514+
515+ if (result) {
516+ --result;
517+ }
518+
519+ return result;
520+}
521+
522+/**
523+ * Converts a number to an alphabetical character.
524+ * @param num
525+ * @returns {string}
526+ */
527+function numberToCharacter(num) {
528+ let s = '';
529+
530+ while (num >= 0) {
531+ s = String.fromCharCode(num % 26 + 97) + s;
532+ num = Math.floor(num / 26) - 1;
533+ }
534+
535+ return s.toUpperCase();
536+}
537+
538+/**
539+ * Converts a quoted string to an un-quoted string: `"hey"` to `hey`
540+ * @param str
541+ * @returns {string}
542+ */
543+function string(str){
544+ return str.substring(1, str.length - 1);
545+}
546+
547+
548+/**
549+ * Converts XY coordinates (eg {0, 0}) to A1 coordinates (eg {A1}).
550+ * @param x
551+ * @param y
552+ * @returns {string}
553+ */
554+function convertXYtoA1Coordinates(x, y) {
555+ function numberToLetters(num) {
556+ let mod = num % 26,
557+ pow = num / 26 | 0,
558+ out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
559+ return pow ? numberToLetters(pow) + out : out;
560+ }
561+ return numberToLetters(x+1) + (y+1).toString();
562+}
563+
564+/**
565+ * Returns RowCol coordinates of an A1 cellId
566+ * @param cellId
567+ * @returns {Object}
568+ */
569+function A1toRowColCoordinates(cellId) {
570+ let num = cellId.match(/\d+$/),
571+ alpha = cellId.replace(num, '');
572+
573+ return {
574+ row: parseInt(num[0], 10) - 1,
575+ col: characterToNumber(alpha)
576+ };
577+}
578
579 /**
580 * Class for building formatted strings with commas, forced number of leading and trailing zeros, and arbitrary leading
581@@ -181,7 +275,6 @@ class NumberStringBuilder {
582 }
583 }
584
585-
586 // Inserting commas if necessary.
587 if (this.shouldUseComma) {
588 integerPart = integerPart.split("").reverse().map(function (digit, index) {
589@@ -211,5 +304,12 @@ class NumberStringBuilder {
590 export {
591 isDefined,
592 isUndefined,
593+ isArray,
594+ isFunction,
595+ string,
596+ numberToCharacter,
597+ convertXYtoA1Coordinates,
598+ A1toRowColCoordinates,
599+ characterToNumber,
600 NumberStringBuilder
601 }
602\ No newline at end of file
603diff --git a/src/Utilities/TypeConverter.ts b/src/Utilities/TypeConverter.ts
604index 0875701..9d79c02 100644
605--- a/src/Utilities/TypeConverter.ts
606+++ b/src/Utilities/TypeConverter.ts
607@@ -457,6 +457,15 @@ class TypeConverter {
608 }
609 }
610
611+ /**
612+ * Converts any value to an inverted number or throws an error if it cannot coerce it to the number type
613+ * @param value to convert
614+ * @returns {number} to return. Will always return a number or throw an error. Never returns undefined.
615+ */
616+ public static valueToInvertedNumber(value: any) {
617+ return TypeConverter.valueToNumber(value) * (-1);
618+ }
619+
620 /**
621 * Converts any value to a number or throws an error if it cannot coerce it to the number type
622 * @param value to convert