f7
f7 is a spreadsheet formula execution library
git clone https://git.vogt.world/f7.git
Log | Files | README.md | LICENSE.md
← All files
name: src/test/js/execution/TranspilationVisitorTest.ts
-rw-r--r--
20109
  1import { assert } from "chai";
  2import { it, describe } from "../testutils/TestUtils";
  3import { DivException } from "../../../main/js/errors/DivException";
  4import { NAException } from "../../../main/js/errors/NAException";
  5import { NameException } from "../../../main/js/errors/NameException";
  6import { NullException } from "../../../main/js/errors/NullException";
  7import { NumException } from "../../../main/js/errors/NumException";
  8import { ParseException } from "../../../main/js/errors/ParseException";
  9import { RefException } from "../../../main/js/errors/RefException";
 10import { ValueException } from "../../../main/js/errors/ValueException";
 11import { TranspilationVisitor } from "../../../main/js/execution/TranspilationVisitor";
 12import { FormulaName } from "../../../main/js/formulas/FormulaName";
 13import { BinaryOperationNode } from "../../../main/js/models/nodes/BinaryOperationNode";
 14import { CellQuery } from "../../../main/js/models/nodes/CellQuery";
 15import { ErrorNode } from "../../../main/js/models/nodes/ErrorNode";
 16import { FormulaNode } from "../../../main/js/models/nodes/FormulaNode";
 17import { LogicalNode } from "../../../main/js/models/nodes/LogicalNode";
 18import { MultiRangeNode } from "../../../main/js/models/nodes/MultiRangeNode";
 19import { Node } from "../../../main/js/models/nodes/Node";
 20import { NumberNode } from "../../../main/js/models/nodes/NumberNode";
 21import { RangeNode } from "../../../main/js/models/nodes/RangeNode";
 22import { TextNode } from "../../../main/js/models/nodes/TextNode";
 23import { UnaryMinusOperationNode } from "../../../main/js/models/nodes/UnaryMinusOperationNode";
 24import { UnaryPercentOperationNode } from "../../../main/js/models/nodes/UnaryPercentOperationNode";
 25import { UnaryPlusOperationNode } from "../../../main/js/models/nodes/UnaryPlusOperationNode";
 26import { VariableNode } from "../../../main/js/models/nodes/VariableNode";
 27import * as antlr4 from "antlr4";
 28import { F7Lexer } from "../../../main/js/antlr/F7Lexer";
 29import { F7Parser } from "../../../main/js/antlr/F7Parser";
 30
 31describe("TranspilationVisitor", function () {
 32  it("should transpile numbers", function () {
 33    run("10.0e2", new NumberNode(10.0e2));
 34    run("10.0e-2", new NumberNode(10.0e-2));
 35    run("81739821", new NumberNode(81739821));
 36    run("1.00000000001", new NumberNode(1.00000000001));
 37    run("199", new NumberNode(199));
 38    run("201", new NumberNode(201));
 39    run("9187312.222", new NumberNode(9187312.222));
 40  });
 41
 42  it("should transpile text", function () {
 43    run('"Hello Friend!"', new TextNode("Hello Friend!"));
 44    run('""', new TextNode(""));
 45  });
 46
 47  it("should transpile formulas without arguments", function () {
 48    run("RAND()", new FormulaNode(FormulaName.RAND.toString()));
 49    run("TRUE()", new FormulaNode(FormulaName.TRUE.toString()));
 50    run("FALSE()", new FormulaNode(FormulaName.FALSE.toString()));
 51  });
 52
 53  it("should transpile formulas with arguments", function () {
 54    run(
 55      "POW(2, 6)",
 56      new FormulaNode(FormulaName.POW.toString(), [new NumberNode(2), new NumberNode(6)])
 57    );
 58    run("SUM(1.829173)", new FormulaNode(FormulaName.SUM.toString(), [new NumberNode(1.829173)]));
 59  });
 60
 61  it("should transpile variables", function () {
 62    run(
 63      "My_Special_Variable_That_Might_Exist",
 64      new VariableNode("My_Special_Variable_That_Might_Exist")
 65    );
 66    run(
 67      "VARIABLE_THAT_IS_LONGER_THAN_TWO_HUNDRED_AND_FIFTY_FIVE_CHARACTERS_OLD_MCDONALD_HAD_A_FARM_OLD_MCDONALD_HAD_A_FARM_OLD_MCDONALD_HAD_A_FARM_OLD_MCDONALD_HAD_A_FARM_OLD_MCDONALD_HAD_A_FARM_OLD_MCDONALD_HAD_A_FARM_OLD_MCDONALD_HAD_A_FARM_OLD_MCDONALD_HAD_A_FA",
 68      new ErrorNode(new ParseException())
 69    );
 70  });
 71
 72  it("should transpile logical nodes", function () {
 73    run("TRUE", new LogicalNode(true));
 74    run("True", new LogicalNode(true));
 75    run("tRuE", new LogicalNode(true));
 76    run("FALSE", new LogicalNode(false));
 77    run("False", new LogicalNode(false));
 78    run("fAlSe", new LogicalNode(false));
 79    run("false", new LogicalNode(false));
 80  });
 81
 82  it("should transpile errors", function () {
 83    run("#NUM!", new ErrorNode(new NumException()));
 84    run("#DIV/0!", new ErrorNode(new DivException()));
 85    run("#VALUE!", new ErrorNode(new ValueException()));
 86    run("#REF!", new ErrorNode(new RefException()));
 87    run("#NAME?", new ErrorNode(new NameException()));
 88    run("#NUM!", new ErrorNode(new NumException()));
 89    run("#NULL!", new ErrorNode(new NullException()));
 90    run("#N/A", new ErrorNode(new NAException()));
 91    run("#ERROR!", new ErrorNode(new ParseException()));
 92  });
 93
 94  it("should transpile addition operations", function () {
 95    run("1 + 1", new BinaryOperationNode(new NumberNode(1), "+", new NumberNode(1)));
 96    run("1 + 0", new BinaryOperationNode(new NumberNode(1), "+", new NumberNode(0)));
 97    run("1 + 2.12121", new BinaryOperationNode(new NumberNode(1), "+", new NumberNode(2.12121)));
 98    run(
 99      "1e10 + 2.12121",
100      new BinaryOperationNode(new NumberNode(1e10), "+", new NumberNode(2.12121))
101    );
102  });
103
104  it("should transpile addition operations with error literals", function () {
105    run(
106      "3 + #VALUE!",
107      new BinaryOperationNode(new NumberNode(3), "+", new ErrorNode(new ValueException()))
108    );
109    run(
110      "3 + #DIV/0!",
111      new BinaryOperationNode(new NumberNode(3), "+", new ErrorNode(new DivException()))
112    );
113    run(
114      "3 + #NUM!",
115      new BinaryOperationNode(new NumberNode(3), "+", new ErrorNode(new NumException()))
116    );
117    run(
118      "3 + #NAME?",
119      new BinaryOperationNode(new NumberNode(3), "+", new ErrorNode(new NameException()))
120    );
121    run(
122      "3 + #NULL!",
123      new BinaryOperationNode(new NumberNode(3), "+", new ErrorNode(new NullException()))
124    );
125    run(
126      "3 + #N/A",
127      new BinaryOperationNode(new NumberNode(3), "+", new ErrorNode(new NAException()))
128    );
129    run(
130      "3 + #REF!",
131      new BinaryOperationNode(new NumberNode(3), "+", new ErrorNode(new RefException()))
132    );
133    run(
134      "3 + #ERROR!",
135      new BinaryOperationNode(new NumberNode(3), "+", new ErrorNode(new ParseException()))
136    );
137  });
138
139  it("should transpile subtraction operations", function () {
140    run("1 - 1", new BinaryOperationNode(new NumberNode(1), "-", new NumberNode(1)));
141    run("3 - 1", new BinaryOperationNode(new NumberNode(3), "-", new NumberNode(1)));
142    run(
143      "3.1e3 - 4.2e10",
144      new BinaryOperationNode(new NumberNode(3.1e3), "-", new NumberNode(4.2e10))
145    );
146  });
147
148  it("should transpile subtraction operations with error literals", function () {
149    run(
150      "3 - #VALUE!",
151      new BinaryOperationNode(new NumberNode(3), "-", new ErrorNode(new ValueException()))
152    );
153    run(
154      "3 - #DIV/0!",
155      new BinaryOperationNode(new NumberNode(3), "-", new ErrorNode(new DivException()))
156    );
157    run(
158      "3 - #NUM!",
159      new BinaryOperationNode(new NumberNode(3), "-", new ErrorNode(new NumException()))
160    );
161    run(
162      "3 - #NAME?",
163      new BinaryOperationNode(new NumberNode(3), "-", new ErrorNode(new NameException()))
164    );
165    run(
166      "3 - #NULL!",
167      new BinaryOperationNode(new NumberNode(3), "-", new ErrorNode(new NullException()))
168    );
169    run(
170      "3 - #N/A",
171      new BinaryOperationNode(new NumberNode(3), "-", new ErrorNode(new NAException()))
172    );
173    run(
174      "3 - #REF!",
175      new BinaryOperationNode(new NumberNode(3), "-", new ErrorNode(new RefException()))
176    );
177    run(
178      "3 - #ERROR!",
179      new BinaryOperationNode(new NumberNode(3), "-", new ErrorNode(new ParseException()))
180    );
181  });
182
183  it("should transpile multiplication operations", function () {
184    run("3 * 4", new BinaryOperationNode(new NumberNode(3), "*", new NumberNode(4)));
185    run("3 * 1", new BinaryOperationNode(new NumberNode(3), "*", new NumberNode(1)));
186    run("3.1e3 * 2", new BinaryOperationNode(new NumberNode(3.1e3), "*", new NumberNode(2)));
187    run("3.1e3 * 0", new BinaryOperationNode(new NumberNode(3.1e3), "*", new NumberNode(0)));
188  });
189
190  it("should transpile multiplication operations with error literals", function () {
191    run(
192      "3 * #VALUE!",
193      new BinaryOperationNode(new NumberNode(3), "*", new ErrorNode(new ValueException()))
194    );
195    run(
196      "3 * #DIV/0!",
197      new BinaryOperationNode(new NumberNode(3), "*", new ErrorNode(new DivException()))
198    );
199    run(
200      "3 * #NUM!",
201      new BinaryOperationNode(new NumberNode(3), "*", new ErrorNode(new NumException()))
202    );
203    run(
204      "3 * #NAME?",
205      new BinaryOperationNode(new NumberNode(3), "*", new ErrorNode(new NameException()))
206    );
207    run(
208      "3 * #NULL!",
209      new BinaryOperationNode(new NumberNode(3), "*", new ErrorNode(new NullException()))
210    );
211    run(
212      "3 * #N/A",
213      new BinaryOperationNode(new NumberNode(3), "*", new ErrorNode(new NAException()))
214    );
215    run(
216      "3 * #REF!",
217      new BinaryOperationNode(new NumberNode(3), "*", new ErrorNode(new RefException()))
218    );
219    run(
220      "3 * #ERROR!",
221      new BinaryOperationNode(new NumberNode(3), "*", new ErrorNode(new ParseException()))
222    );
223  });
224
225  it("should transpile division operations", function () {
226    run("3 / 4", new BinaryOperationNode(new NumberNode(3), "/", new NumberNode(4)));
227    run("4 / 3", new BinaryOperationNode(new NumberNode(4), "/", new NumberNode(3)));
228    run("3e3 / 2", new BinaryOperationNode(new NumberNode(3e3), "/", new NumberNode(2)));
229    run("10 / 0", new BinaryOperationNode(new NumberNode(10), "/", new NumberNode(0)));
230  });
231
232  it("should transpile division operations with error literals", function () {
233    run(
234      "3 / #VALUE!",
235      new BinaryOperationNode(new NumberNode(3), "/", new ErrorNode(new ValueException()))
236    );
237    run(
238      "3 / #DIV/0!",
239      new BinaryOperationNode(new NumberNode(3), "/", new ErrorNode(new DivException()))
240    );
241    run(
242      "3 / #NUM!",
243      new BinaryOperationNode(new NumberNode(3), "/", new ErrorNode(new NumException()))
244    );
245    run(
246      "3 / #NAME?",
247      new BinaryOperationNode(new NumberNode(3), "/", new ErrorNode(new NameException()))
248    );
249    run(
250      "3 / #NULL!",
251      new BinaryOperationNode(new NumberNode(3), "/", new ErrorNode(new NullException()))
252    );
253    run(
254      "3 / #N/A",
255      new BinaryOperationNode(new NumberNode(3), "/", new ErrorNode(new NAException()))
256    );
257    run(
258      "3 / #REF!",
259      new BinaryOperationNode(new NumberNode(3), "/", new ErrorNode(new RefException()))
260    );
261    run(
262      "3 / #ERROR!",
263      new BinaryOperationNode(new NumberNode(3), "/", new ErrorNode(new ParseException()))
264    );
265  });
266
267  it("should transpile exponent operations", function () {
268    run(
269      "-2^3",
270      new BinaryOperationNode(
271        new UnaryMinusOperationNode(new NumberNode(2)),
272        "^",
273        new NumberNode(3)
274      )
275    );
276    run(
277      "(-2)^3",
278      new BinaryOperationNode(
279        new UnaryMinusOperationNode(new NumberNode(2)),
280        "^",
281        new NumberNode(3)
282      )
283    );
284    run(
285      "-(2)^3",
286      new BinaryOperationNode(
287        new UnaryMinusOperationNode(new NumberNode(2)),
288        "^",
289        new NumberNode(3)
290      )
291    );
292    run(
293      "--(-2)^3",
294      new BinaryOperationNode(
295        new UnaryMinusOperationNode(
296          new UnaryMinusOperationNode(new UnaryMinusOperationNode(new NumberNode(2)))
297        ),
298        "^",
299        new NumberNode(3)
300      )
301    );
302    run(
303      "11 -2^3",
304      new BinaryOperationNode(
305        new NumberNode(11),
306        "-",
307        new BinaryOperationNode(new NumberNode(2), "^", new NumberNode(3))
308      )
309    );
310    run(
311      "(-2) ^ 3",
312      new BinaryOperationNode(
313        new UnaryMinusOperationNode(new NumberNode(2)),
314        "^",
315        new NumberNode(3)
316      )
317    );
318    run(
319      "(-3) ^ 2",
320      new BinaryOperationNode(
321        new UnaryMinusOperationNode(new NumberNode(3)),
322        "^",
323        new NumberNode(2)
324      )
325    );
326  });
327
328  it("should transpile concat operations", function () {
329    run("1 & 1", new BinaryOperationNode(new NumberNode(1), "&", new NumberNode(1)));
330    run('"One" & 0', new BinaryOperationNode(new TextNode("One"), "&", new NumberNode(0)));
331  });
332
333  it("should transpile concat operations with error literals", function () {
334    run(
335      "1 & #DIV/0!",
336      new BinaryOperationNode(new NumberNode(1), "&", new ErrorNode(new DivException()))
337    );
338  });
339
340  it("should transpile unary minus operations", function () {
341    run("-22", new UnaryMinusOperationNode(new NumberNode(22)));
342    run('-"Thing"', new UnaryMinusOperationNode(new TextNode("Thing")));
343    run("--22", new UnaryMinusOperationNode(new UnaryMinusOperationNode(new NumberNode(22))));
344    run(
345      "---22",
346      new UnaryMinusOperationNode(
347        new UnaryMinusOperationNode(new UnaryMinusOperationNode(new NumberNode(22)))
348      )
349    );
350    run(
351      "-(2 * 4)",
352      new UnaryMinusOperationNode(
353        new BinaryOperationNode(new NumberNode(2), "*", new NumberNode(4))
354      )
355    );
356  });
357
358  it("should transpile unary percent operations", function () {
359    run("88%", new UnaryPercentOperationNode(new NumberNode(88)));
360    run(
361      "(8 * 8)%",
362      new UnaryPercentOperationNode(
363        new BinaryOperationNode(new NumberNode(8), "*", new NumberNode(8))
364      )
365    );
366  });
367
368  it("should transpile to error when unary percent operation contains more than one percent sign", function () {
369    run("88%%", new ErrorNode(new ParseException()));
370    run("88%%%", new ErrorNode(new ParseException()));
371    run("88% % % %", new ErrorNode(new ParseException()));
372  });
373
374  it("should transpile unary plus operations", function () {
375    run("+22", new UnaryPlusOperationNode(new NumberNode(22)));
376  });
377
378  it("should transpile ranges with single-quoted sheet names", function () {
379    run(
380      "'Sheet Name'!D11",
381      new RangeNode(
382        CellQuery.builder()
383          .setSheet("'Sheet Name'")
384          .columnsBetween(3, 3)
385          .rowsBetween(10, 10)
386          .build()
387      )
388    );
389    run(
390      "'Sheet Name'!A1:A2",
391      new RangeNode(
392        CellQuery.builder().setSheet("'Sheet Name'").columnsBetween(0, 0).rowsBetween(0, 1).build()
393      )
394    );
395    run(
396      "'Sheet Name'!3:7",
397      new RangeNode(
398        CellQuery.builder()
399          .setSheet("'Sheet Name'")
400          .rowsBetween(2, 6)
401          .openColumnsStartingAtZero()
402          .build()
403      )
404    );
405    run(
406      "'Sheet Name'!C:A",
407      new RangeNode(
408        CellQuery.builder()
409          .setSheet("'Sheet Name'")
410          .columnsBetween(0, 2)
411          .openRowsStartingAtZero()
412          .build()
413      )
414    );
415    run(
416      "'Sheet Name'!D3:7",
417      new RangeNode(
418        CellQuery.builder()
419          .setSheet("'Sheet Name'")
420          .rowsBetween(2, 6)
421          .openColumnsStartingAt("D")
422          .build()
423      )
424    );
425  });
426
427  it("should transpile cell range with single cell", function () {
428    run("A4", new RangeNode(CellQuery.builder().columnsBetween(0, 0).rowsBetween(3, 3).build()));
429    run("D11", new RangeNode(CellQuery.builder().columnsBetween(3, 3).rowsBetween(10, 10).build()));
430  });
431
432  it("should transpile cell range with bi-cell (beginning and end)", function () {
433    run("A1:B2", new RangeNode(CellQuery.builder().columnsBetween(0, 1).rowsBetween(0, 1).build()));
434    run("B2:A1", new RangeNode(CellQuery.builder().columnsBetween(0, 1).rowsBetween(0, 1).build()));
435    run("B2:D4", new RangeNode(CellQuery.builder().columnsBetween(1, 3).rowsBetween(1, 3).build()));
436    run("B4:B4", new RangeNode(CellQuery.builder().columnsBetween(1, 1).rowsBetween(3, 3).build()));
437    run("B3:B4", new RangeNode(CellQuery.builder().columnsBetween(1, 1).rowsBetween(2, 3).build()));
438    run("B4:B3", new RangeNode(CellQuery.builder().columnsBetween(1, 1).rowsBetween(2, 3).build()));
439  });
440
441  it("should transpile cell ranges that are column-wise (eg: A:D, M:M)", function () {
442    run(
443      "A:C",
444      new RangeNode(CellQuery.builder().columnsBetween(0, 2).openRowsStartingAtZero().build())
445    );
446    run(
447      "C:A",
448      new RangeNode(CellQuery.builder().columnsBetween(0, 2).openRowsStartingAtZero().build())
449    );
450    run(
451      "B:M",
452      new RangeNode(CellQuery.builder().columnsBetween(1, 12).openRowsStartingAtZero().build())
453    );
454    run(
455      "M:B",
456      new RangeNode(CellQuery.builder().columnsBetween(1, 12).openRowsStartingAtZero().build())
457    );
458    run(
459      "M:M",
460      new RangeNode(CellQuery.builder().columnsBetween(12, 12).openRowsStartingAtZero().build())
461    );
462  });
463
464  it("should transpile cell ranges that are row-wise (eg: 1:8, 4:4)", function () {
465    run(
466      "3:7",
467      new RangeNode(CellQuery.builder().rowsBetween(2, 6).openColumnsStartingAtZero().build())
468    );
469    run(
470      "1:100",
471      new RangeNode(CellQuery.builder().rowsBetween(0, 99).openColumnsStartingAtZero().build())
472    );
473    run(
474      "7:3",
475      new RangeNode(CellQuery.builder().rowsBetween(2, 6).openColumnsStartingAtZero().build())
476    );
477    run(
478      "100:1",
479      new RangeNode(CellQuery.builder().rowsBetween(0, 99).openColumnsStartingAtZero().build())
480    );
481  });
482
483  it("should transpile cell ranges that are row-wise with column offset (eg: A2:9)", function () {
484    run(
485      "D3:7",
486      new RangeNode(CellQuery.builder().rowsBetween(2, 6).openColumnsStartingAt("D").build())
487    );
488    run(
489      "D7:3",
490      new RangeNode(CellQuery.builder().rowsBetween(2, 6).openColumnsStartingAt("D").build())
491    );
492    run(
493      "E5:10",
494      new RangeNode(CellQuery.builder().rowsBetween(4, 9).openColumnsStartingAtNumber(4).build())
495    );
496    run(
497      "D7:7",
498      new RangeNode(CellQuery.builder().rowsBetween(6, 6).openColumnsStartingAt("D").build())
499    );
500  });
501
502  it("should transpile cell ranges that are column-wise with row offset (eg: D1:M)", function () {
503    run(
504      "B3:D",
505      new RangeNode(CellQuery.builder().columnsBetween(1, 3).openRowsStartingAtNumber(2).build())
506    );
507    run(
508      "D3:B",
509      new RangeNode(CellQuery.builder().columnsBetween(1, 3).openRowsStartingAtNumber(2).build())
510    );
511    run(
512      "B33:B",
513      new RangeNode(CellQuery.builder().columnsBetween(1, 1).openRowsStartingAtNumber(32).build())
514    );
515  });
516
517  it("should transpile cell ranges that are multi-cell", function () {
518    run(
519      "C5:B4:D2:G7:L11",
520      new MultiRangeNode([
521        new RangeNode(CellQuery.builder().columnsBetween(1, 2).rowsBetween(3, 4).build()),
522        new RangeNode(CellQuery.builder().columnsBetween(3, 6).rowsBetween(1, 6).build()),
523        new RangeNode(CellQuery.builder().columnsBetween(11, 11).rowsBetween(10, 10).build()),
524      ])
525    );
526    run(
527      "A4:B5:C6:D7:E8:F9",
528      new MultiRangeNode([
529        new RangeNode(CellQuery.builder().columnsBetween(0, 1).rowsBetween(3, 4).build()),
530        new RangeNode(CellQuery.builder().columnsBetween(2, 3).rowsBetween(5, 6).build()),
531        new RangeNode(CellQuery.builder().columnsBetween(4, 5).rowsBetween(7, 8).build()),
532      ])
533    );
534    run(
535      "SUM(A1:B2:C3:D4:E5:F6)",
536      new FormulaNode(FormulaName.SUM.toString(), [
537        new MultiRangeNode([
538          new RangeNode(CellQuery.builder().columnsBetween(0, 1).rowsBetween(0, 1).build()),
539          new RangeNode(CellQuery.builder().columnsBetween(2, 3).rowsBetween(2, 3).build()),
540          new RangeNode(CellQuery.builder().columnsBetween(4, 5).rowsBetween(4, 5).build()),
541        ]),
542      ])
543    );
544  });
545
546  /**
547   * Just run the test, asserting that the result is equal to the expected node.
548   * @param input - input string.
549   * @param expected  - expected node.
550   */
551  function run(input: string, expected: Node) {
552    try {
553      const chars = new antlr4.InputStream(input);
554      const lexer: any = new F7Lexer(chars);
555      const tokens = new antlr4.CommonTokenStream(lexer);
556      const parser: any = new F7Parser(tokens);
557      parser.buildParseTrees = true;
558      const tree = parser.start().block();
559      assert.deepEqual(new TranspilationVisitor().visit(tree), expected);
560    } catch (e) {
561      assert.deepEqual(new ErrorNode(e), expected);
562    }
563  }
564});