@borgar/fx
Version:
Utilities for working with Excel formulas
1,729 lines (1,626 loc) • 57.3 kB
JavaScript
import { test, Test } from 'tape';
import {
FX_PREFIX, UNKNOWN,
OPERATOR, BOOLEAN, ERROR, NUMBER, FUNCTION, WHITESPACE, STRING,
REF_RANGE, REF_BEAM, REF_NAMED, REF_TERNARY, CONTEXT, CONTEXT_QUOTE, NEWLINE
} from './constants.js';
import { tokenize } from './lexer.js';
Test.prototype.isTokens = function isTokens (expr, result, opts) {
this.deepEqual(tokenize(expr, { negativeNumbers: false, ...opts }), result, expr);
};
Test.prototype.isTokensNeg = function isTokensNeg (expr, result, opts) {
this.deepEqual(tokenize(expr, { ...opts, negativeNumbers: true }), result, expr);
};
test('tokenize operators', t => {
t.isTokens('=1>1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '>' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1>=1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '>=' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1=1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '=' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1<>1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '<>' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1<=1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '<=' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1<1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '<' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1+1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1*1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '*' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1/1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '/' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1^1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '^' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=1&1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '&' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('="A"="B"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"A"' },
{ type: OPERATOR, value: '=' },
{ type: STRING, value: '"B"' }
]);
t.isTokens('=A1:INDIRECT("B2",TRUE)', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: ':' },
{ type: FUNCTION, value: 'INDIRECT' },
{ type: OPERATOR, value: '(' },
{ type: STRING, value: '"B2"' },
{ type: OPERATOR, value: ',' },
{ type: BOOLEAN, value: 'TRUE' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=123%', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '123' },
{ type: OPERATOR, value: '%' }
]);
t.isTokens('=Sheet1!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
t.isTokens('=Sheet1!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'Sheet1!A1' }
]);
t.isTokens('=(A1:C1,A2:C2)', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '(' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'C1' },
{ type: OPERATOR, value: ',' },
{ type: REF_RANGE, value: 'A2' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'C2' },
{ type: OPERATOR, value: ')' }
], { mergeRefs: false });
t.isTokens('=(A1:C1,A2:C2)', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '(' },
{ type: REF_RANGE, value: 'A1:C1' },
{ type: OPERATOR, value: ',' },
{ type: REF_RANGE, value: 'A2:C2' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=(A1:C1 A2:C2)', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '(' },
{ type: REF_RANGE, value: 'A1:C1' },
{ type: WHITESPACE, value: ' ' }, // INTERSECT
{ type: REF_RANGE, value: 'A2:C2' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=(A1:C1 A2:C2)', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '(' },
{ type: REF_RANGE, value: 'A1:C1' },
{ type: WHITESPACE, value: ' ' }, // INTERSECT
{ type: REF_RANGE, value: 'A2:C2' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('={1,2,3}', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '{' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ',' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: ',' },
{ type: NUMBER, value: '3' },
{ type: OPERATOR, value: '}' }
]);
t.isTokens('={1;2;3}', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '{' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ';' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: ';' },
{ type: NUMBER, value: '3' },
{ type: OPERATOR, value: '}' }
]);
t.isTokens('={1,2;3}', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '{' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ',' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: ';' },
{ type: NUMBER, value: '3' },
{ type: OPERATOR, value: '}' }
]);
t.isTokens('={"A",33;TRUE,123}', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '{' },
{ type: STRING, value: '"A"' },
{ type: OPERATOR, value: ',' },
{ type: NUMBER, value: '33' },
{ type: OPERATOR, value: ';' },
{ type: BOOLEAN, value: 'TRUE' },
{ type: OPERATOR, value: ',' },
{ type: NUMBER, value: '123' },
{ type: OPERATOR, value: '}' }
]);
t.isTokens('={A1:B2}', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '{' },
{ type: REF_RANGE, value: 'A1:B2' },
{ type: OPERATOR, value: '}' }
]);
t.isTokens('={A1:B2,C3:D4}', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '{' },
{ type: REF_RANGE, value: 'A1:B2' },
{ type: OPERATOR, value: ',' },
{ type: REF_RANGE, value: 'C3:D4' },
{ type: OPERATOR, value: '}' }
]);
t.end();
});
test('tokenize functions', t => {
t.isTokens('=TODAY()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'TODAY' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=ToDaY()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'ToDaY' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=SUM(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=N()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'N' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=TRUE()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'TRUE' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=FALSE()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'FALSE' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=@SUM(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '@' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=SUM(1, 2)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ',' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=SUM(1, SUM(2, 3))', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ',' },
{ type: WHITESPACE, value: ' ' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: ',' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '3' },
{ type: OPERATOR, value: ')' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=INDIRECT("A1",TRUE)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'INDIRECT' },
{ type: OPERATOR, value: '(' },
{ type: STRING, value: '"A1"' },
{ type: OPERATOR, value: ',' },
{ type: BOOLEAN, value: 'TRUE' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=BINOM.DIST.REF_RANGE(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'BINOM.DIST.REF_RANGE' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=OCT2BIN(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'OCT2BIN' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=TEST_FUNC(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'TEST_FUNC' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=_xlfn.FOO(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: '_xlfn.FOO' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=_FOO(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: '_FOO' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=\\FOO(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: '\\FOO' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=9FOO(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '9' },
{ type: FUNCTION, value: 'FOO' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
t.end();
});
test('tokenize numbers', t => {
t.isTokens('=0', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '0' }
]);
t.isTokens('=+0', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '0' }
]);
t.isTokens('=+1', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '1' }
]);
t.isTokens('=-0', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '0' }
]);
t.isTokens('=1123', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1123' }
]);
t.isTokens('=-1123', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1123' }
]);
t.isTokens('=1.5', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.5' }
]);
t.isTokens('=-1.5', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1.5' }
]);
t.isTokens('=1234.5678', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1234.5678' }
]);
t.isTokens('=-1234.5678', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1234.5678' }
]);
t.isTokens('=1E-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1E-1' }
]);
t.isTokens('=1.5E-10', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.5E-10' }
]);
t.isTokens('=1.55E+100', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.55E+100' }
]);
t.isTokens('=1.55e+100', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.55e+100' }
]);
t.end();
});
test('tokenize negative numbers', t => {
t.isTokensNeg('=-0', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-0' }
]);
t.isTokensNeg('=-1123', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1123' }
]);
t.isTokensNeg('=-1.5', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1.5' }
]);
t.isTokensNeg('=-1234.5678', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1234.5678' }
]);
t.isTokensNeg('=1E-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1E-1' }
]);
t.isTokensNeg('=-1E-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1E-1' }
]);
t.isTokensNeg('=1.5E-10', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.5E-10' }
]);
t.isTokensNeg('=-1.5E-10', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1.5E-10' }
]);
t.isTokensNeg('-1', [
{ type: NUMBER, value: '-1' }
]);
//
t.isTokensNeg('=1-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('1--1', [
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '-1' }
]);
t.isTokensNeg('1 - -1', [
{ type: NUMBER, value: '1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '-1' }
]);
t.isTokensNeg('1 - - 1', [
{ type: NUMBER, value: '1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('1 \n - \n -1', [
{ type: NUMBER, value: '1' },
{ type: WHITESPACE, value: ' ' },
{ type: NEWLINE, value: '\n' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: NEWLINE, value: '\n' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '-1' }
]);
t.isTokensNeg('-(-1)', [
{ type: OPERATOR, value: '-' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '-1' },
{ type: OPERATOR, value: ')' }
]);
t.isTokensNeg('-( -1 )', [
{ type: OPERATOR, value: '-' },
{ type: OPERATOR, value: '(' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '-1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: ')' }
]);
t.isTokensNeg('=true-1', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'true' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=true -1', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'true' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=true - 1', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'true' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=#VALUE!-1', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#VALUE!' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=#VALUE! -1', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#VALUE!' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=SUM(-1) -1', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '-1' },
{ type: OPERATOR, value: ')' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=SUM( -1)-1', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '-1' },
{ type: OPERATOR, value: ')' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=A1-1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=A1 -1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=foo-1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'foo' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=foo -1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'foo' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('="true"-1', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"true"' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('="true" -1', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"true"' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=SUM(1)-1', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('={1, 2, 3}-4', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '{' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ',' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: ',' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '3' },
{ type: OPERATOR, value: '}' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '4' }
]);
t.isTokensNeg('=10%-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '10' },
{ type: OPERATOR, value: '%' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.isTokensNeg('=A1#-1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: '#' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
t.end();
});
test('tokenize simple equations', t => {
t.isTokens('=1 + 2', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '+' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '2' }
]);
t.isTokens('=1+2', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '2' }
]);
t.isTokens('=1.1+2.2', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '2.2' }
]);
t.isTokens('=(1 + 2) - 3', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '+' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: ')' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '3' }
]);
t.isTokens(' = ( 1.1+2 ) - 3 ', [
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '=' }, // FX_PREFIX?
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '(' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '1.1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '2' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: ')' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '3' },
{ type: WHITESPACE, value: ' ' }
]);
t.isTokens('=1+2*3', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: '*' },
{ type: NUMBER, value: '3' }
]);
t.isTokens('= 1+2*3', [
{ type: FX_PREFIX, value: '=' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: '*' },
{ type: NUMBER, value: '3' }
]);
t.isTokens('=1%', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '%' }
]);
t.isTokens('=-1%', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '%' }
]);
t.isTokens('=-(1 + 2)%', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '+' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '2' },
{ type: OPERATOR, value: ')' },
{ type: OPERATOR, value: '%' }
]);
t.end();
});
test('tokenize R1C1 style references', t => {
// How R1C1 ranges are merged doesn't make a whole lot of sense.
// A "unit" in A1 is (A1 or A:A or 1:1), this can be merged as
// not all of these make sense but are "theoretically possible"
t.isTokens('=R', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R' }
], { r1c1: true });
t.isTokens('=R:R', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R:R' }
], { r1c1: true });
t.isTokens('=R1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R1' }
], { r1c1: true });
t.isTokens('=R1:R1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R1:R1' }
], { r1c1: true });
t.isTokens('=R[1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[1]' }
], { r1c1: true });
t.isTokens('=R[1]:R[1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[1]:R[1]' }
], { r1c1: true });
t.isTokens('=R[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[-1]' }
], { r1c1: true });
t.isTokens('=R[-1]:R[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[-1]:R[-1]' }
], { r1c1: true });
t.isTokens('=C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C' }
], { r1c1: true });
t.isTokens('=C:C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C:C' }
], { r1c1: true });
t.isTokens('=C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C1' }
], { r1c1: true });
t.isTokens('=C1:C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C1:C1' }
], { r1c1: true });
t.isTokens('=C[1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[1]' }
], { r1c1: true });
t.isTokens('=C[1]:C[1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[1]:C[1]' }
], { r1c1: true });
t.isTokens('=C[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[-1]' }
], { r1c1: true });
t.isTokens('=C[-1]:C[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[-1]:C[-1]' }
], { r1c1: true });
t.isTokens('=RC', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC' }
], { r1c1: true });
t.isTokens('=RC:RC', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC:RC' }
], { r1c1: true });
t.isTokens('=R1C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R1C1' }
], { r1c1: true });
t.isTokens('=R1C1:R1C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R1C1:R1C1' }
], { r1c1: true });
t.isTokens('=R[2]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[2]C' }
], { r1c1: true });
t.isTokens('=R[2]C:R[2]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[2]C:R[2]C' }
], { r1c1: true });
t.isTokens('=R[-2]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[-2]C' }
], { r1c1: true });
t.isTokens('=R[-2]C:R[-2]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[-2]C:R[-2]C' }
], { r1c1: true });
t.isTokens('=RC[3]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC[3]' }
], { r1c1: true });
t.isTokens('=RC[3]:RC[3]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC[3]:RC[3]' }
], { r1c1: true });
t.isTokens('=RC[-3]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC[-3]' }
], { r1c1: true });
t.isTokens('=RC[-3]:RC[-3]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC[-3]:RC[-3]' }
], { r1c1: true });
t.isTokens('=R[2]C[2]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[2]C[2]' }
], { r1c1: true });
t.isTokens('=R[2]C[2]:R[2]C[2]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[2]C[2]:R[2]C[2]' }
], { r1c1: true });
t.isTokens('=R[-2]C[-2]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[-2]C[-2]' }
], { r1c1: true });
t.isTokens('=R[-2]C[-2]:R[-1]C[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[-2]C[-2]:R[-1]C[-1]' }
], { r1c1: true });
t.isTokens('=[filename]Sheetname!R[-2]C:R[-1]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '[filename]Sheetname!R[-2]C:R[-1]C' }
], { r1c1: true });
t.isTokens('=[filename]Sheetname!R[-2]C:R[-1]C', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: '[filename]Sheetname' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'R[-2]C' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'R[-1]C' }
], { mergeRefs: false, r1c1: true });
t.isTokens('=R[-2]C[-2]:R[-1]C[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[-2]C[-2]:R[-1]C[-1]' }
], { r1c1: true });
t.isTokens('=R[-2]:R1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[-2]:R1' }
], { r1c1: true });
// should not be merged
t.isTokens('=R:C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R' },
{ type: OPERATOR, value: ':' },
{ type: REF_BEAM, value: 'C' }
], { r1c1: true });
t.isTokens('=C[1]:R[-2]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[1]' },
{ type: OPERATOR, value: ':' },
{ type: REF_BEAM, value: 'R[-2]' }
], { r1c1: true });
t.isTokens('=R1:RC', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'RC' }
], { r1c1: true });
t.isTokens('=RC:C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC' },
{ type: OPERATOR, value: ':' },
{ type: REF_BEAM, value: 'C1' }
], { r1c1: true });
t.end();
});
test('tokenize A1 style references', t => {
t.isTokens('=A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' }
]);
t.isTokens('=C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'C1' }
]);
t.isTokens('=R1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R1' }
]);
t.isTokens('=$A$1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '$A$1' }
]);
t.isTokens('=A$1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A$1' }
]);
t.isTokens('=$A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '$A1' }
]);
t.isTokens('=A10:A20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10:A20' }
]);
t.isTokens('=A10.:A20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10.:A20' }
]);
t.isTokens('=A10:.A20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10:.A20' }
]);
t.isTokens('=A10.:.A20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10.:.A20' }
]);
t.isTokens('=A10:E20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10:E20' }
]);
t.isTokens('=A1:C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1:C1' }
]);
t.isTokens('=5:5', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '5:5' }
]);
t.isTokens('=5.:5', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '5.:5' }
]);
t.isTokens('=5:.5', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '5:.5' }
]);
t.isTokens('=5.:.5', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '5.:.5' }
]);
t.isTokens('=15:15', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '15:15' }
]);
t.isTokens('=H:H', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'H:H' }
]);
t.isTokens('=H.:H', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'H.:H' }
]);
t.isTokens('=H:.H', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'H:.H' }
]);
t.isTokens('=H.:.H', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'H.:.H' }
]);
t.isTokens('=AA:JJ', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'AA:JJ' }
]);
t.isTokens('=XFD:XFF', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'XFD' },
{ type: OPERATOR, value: ':' },
{ type: REF_NAMED, value: 'XFF' }
]);
t.isTokens('=Sheetname!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'Sheetname!A1' }
]);
t.isTokens('=Sheetname!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheetname' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
t.isTokens('=Sheet1!A1:B2', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'Sheet1!A1:B2' }
]);
t.isTokens('=Sheet1!A1:B2', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'B2' }
], { mergeRefs: false });
t.isTokens("='Sheet name'!A1:B2", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: "'Sheet name'!A1:B2" }
]);
t.isTokens("='Sheet name'!A1:B2", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'Sheet name'" },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'B2' }
], { mergeRefs: false });
t.isTokens("='Sheets'' name'!A1:B2", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: "'Sheets'' name'!A1:B2" }
]);
t.isTokens("='Sheets'' name'!A1:B2", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'Sheets'' name'" },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'B2' }
], { mergeRefs: false });
t.isTokens('=[filename]Sheetname!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '[filename]Sheetname!A1' }
]);
t.isTokens('=[filename]Sheetname!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: '[filename]Sheetname' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
t.isTokens("='[filename]Sheets'' name'!A1:B2", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: "'[filename]Sheets'' name'!A1:B2" }
]);
t.isTokens("='[filename]Sheets'' name'!A1:B2", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'[filename]Sheets'' name'" },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'B2' }
], { mergeRefs: false });
t.isTokens("='Run forest, run!'!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '\'Run forest, run!\'!A1' }
]);
t.isTokens("='Run forest, run!'!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'Run forest, run!'" },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
t.isTokens("='foo'''!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: "'foo'''!A1" }
]);
t.isTokens("='foo'''!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'foo'''" },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
t.isTokens("='foo'''''!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: "'foo'''''!A1" }
]);
t.isTokens("='foo'''''!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'foo'''''" },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
t.isTokens('=[15]Sheet32!X4', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '[15]Sheet32!X4' }
]);
// illegal syntax
t.isTokens('=[15]!named', [
{ type: FX_PREFIX, value: '=' },
{ type: UNKNOWN, value: '[' },
{ type: NUMBER, value: '15' },
{ type: UNKNOWN, value: ']' },
{ type: OPERATOR, value: '!' },
{ type: REF_NAMED, value: 'named' }
]);
t.isTokens('=filename!named', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'filename!named' }
]);
t.isTokens('=filename!named', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'filename' },
{ type: OPERATOR, value: '!' },
{ type: REF_NAMED, value: 'named' }
], { mergeRefs: false });
t.isTokens('=[15]Sheet32!X4', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: '[15]Sheet32' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'X4' }
], { mergeRefs: false });
// largest possible A1 ref (in Excel)
t.isTokens('=XFD1048576', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'XFD1048576' }
]);
t.isTokens('=XFD1048577', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'XFD1048577' }
]);
t.isTokens('=XFE1048577', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'XFE1048577' }
]);
t.isTokens('=pensioneligibilitypartner1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'pensioneligibilitypartner1' }
]);
//
t.isTokens("='D:\\Reports\\Sales.xlsx'!namedrange", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'D:\\Reports\\Sales.xlsx'" },
{ type: OPERATOR, value: '!' },
{ type: REF_NAMED, value: 'namedrange' }
], { mergeRefs: false });
t.isTokens("='D:\\Reports\\Sales.xlsx'!namedrange", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: "'D:\\Reports\\Sales.xlsx'!namedrange" }
]);
t.isTokens('=Sales.xlsx!namedrange', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sales.xlsx' },
{ type: OPERATOR, value: '!' },
{ type: REF_NAMED, value: 'namedrange' }
], { mergeRefs: false });
t.isTokens('=Sales.xlsx!namedrange', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'Sales.xlsx!namedrange' }
]);
t.isTokens('=Sheet1!A:A', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'Sheet1!A:A' }
]);
t.isTokens('=Sheet1!A:A', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: REF_BEAM, value: 'A:A' }
], { mergeRefs: false });
t.isTokens('=Sheet1!A:A:B:B', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'Sheet1!A:A' },
{ type: OPERATOR, value: ':' },
{ type: REF_BEAM, value: 'B:B' }
]);
t.isTokens('=Sheet1!A:A:B:B', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: REF_BEAM, value: 'A:A' },
{ type: OPERATOR, value: ':' },
{ type: REF_BEAM, value: 'B:B' }
], { mergeRefs: false });
t.isTokens('=Sheet1!A.:.A:B.:.B', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: REF_BEAM, value: 'A.:.A' },
{ type: OPERATOR, value: ':' },
{ type: REF_BEAM, value: 'B.:.B' }
], { mergeRefs: false });
t.isTokens('=Sheet1!#REF!:A1', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: ERROR, value: '#REF!' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'A1' }
]);
t.end();
});
test('tokenize errors', t => {
t.isTokens('=#NAME?', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#NAME?' }
]);
t.isTokens('=#VALUE!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#VALUE!' }
]);
t.isTokens('=#REF!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#REF!' }
]);
t.isTokens('=#DIV/0!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#DIV/0!' }
]);
t.isTokens('=#NULL!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#NULL!' }
]);
t.isTokens('=#NUM!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#NUM!' }
]);
t.isTokens('=#N/A', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#N/A' }
]);
t.isTokens('=#GETTING_DATA', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#GETTING_DATA' }
]);
t.isTokens('=#SPILL!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#SPILL!' }
]);
t.isTokens('=#UNKNOWN!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#UNKNOWN!' }
]);
t.isTokens('=#FIELD!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#FIELD!' }
]);
t.isTokens('=#CALC!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#CALC!' }
]);
t.isTokens('=#SYNTAX?', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#SYNTAX?' }
]);
t.isTokens('=#ERROR!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#ERROR!' }
]);
t.isTokens('=#CONNECT!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#CONNECT!' }
]);
t.isTokens('=#BLOCKED!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#BLOCKED!' }
]);
t.isTokens('=#EXTERNAL!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#EXTERNAL!' }
]);
// TODO: is this really what should happen?
t.isTokens('=#NONSENSE!', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '#' },
{ type: CONTEXT, value: 'NONSENSE' },
{ type: OPERATOR, value: '!' }
]);
t.end();
});
test('tokenize booleans', t => {
t.isTokens('=true', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'true' }
]);
t.isTokens('=tRuE', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'tRuE' }
]);
t.isTokens('=TRUE', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'TRUE' }
]);
t.isTokens('=false', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'false' }
]);
t.isTokens('=fAlSe', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'fAlSe' }
]);
t.isTokens('=FALSE', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'FALSE' }
]);
t.end();
});
test('tokenize strings', t => {
t.isTokens('=""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""' }
]);
t.isTokens('=""""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""""' }
]);
t.isTokens('="data"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"data"' }
]);
t.isTokens('="data""data"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"data""data"' }
]);
t.isTokens('="data"&"data"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"data"' },
{ type: OPERATOR, value: '&' },
{ type: STRING, value: '"data"' }
]);
t.isTokens('="data"&"data"&"data"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"data"' },
{ type: OPERATOR, value: '&' },
{ type: STRING, value: '"data"' },
{ type: OPERATOR, value: '&' },
{ type: STRING, value: '"data"' }
]);
// we should be able to highlight things that are still being typed
// so open-ended STRING, PATH_BRACE, and PATH_QUOTE tokens at the
// end of output are tagged.
t.isTokens('="incomple', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"incomple', unterminated: true }
]);
t.isTokens('="', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"', unterminated: true }
]);
t.isTokens('=""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""' }
]);
t.isTokens('="""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"""', unterminated: true }
]);
t.isTokens('=""""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""""' }
]);
t.isTokens('="""""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"""""', unterminated: true }
]);
t.isTokens('=""""""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""""""' }
]);
t.isTokens('="aa""ss', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"aa""ss', unterminated: true }
]);
t.isTokens('="aa""ss"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"aa""ss"' }
]);
t.isTokens('="aa""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"aa""', unterminated: true }
]);
t.isTokens('="aa"""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"aa"""' }
]);
t.end();
});
test('unknown and unary minus mess with offsets', t => {
t.isTokens('=-1', [
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
{ type: OPERATOR, value: '-', loc: [ 1, 2 ] },
{ type: NUMBER, value: '1', loc: [ 2, 3 ] }
], { withLocation: true });
t.isTokens('=-1', [
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
{ type: NUMBER, value: '-1', loc: [ 1, 3 ] }
], { withLocation: true, negativeNumbers: true });
t.isTokens('=$C', [
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
{ type: UNKNOWN, value: '$C', loc: [ 1, 3 ] }
], { withLocation: true });
t.isTokens('=$C.foo', [
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
{ type: UNKNOWN, value: '$C.foo', loc: [ 1, 7 ] }
], { withLocation: true });
t.end();
});
test('unknowns, named ranges and functions', t => {
t.isTokens('=foo', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'foo' }
]);
t.isTokens('=_foo', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: '_foo' }
]);
t.isTokens('=\\foo', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: '\\foo' }
]);
t.isTokens('=\\fo', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: '\\fo' }
]);
t.isTokens('=\\f', [
{ type: FX_PREFIX, value: '=' },
{ type: UNKNOWN, value: '\\f' }
]);
t.isTokens('=\\', [
{ type: FX_PREFIX, value: '=' },
{ type: UNKNOWN, value: '\\' }
]);
t.isTokens('=æði', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'æði' }
]);
t.isTokens('=らーめん', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'らーめん' }
]);
t.isTokens('=@foo', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '@' },
{ type: REF_NAMED, value: 'foo' }
]);
t.isTokens('=9æði', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '9' },
{ type: REF_NAMED, value: 'æði' }
]);
t.isTokens('=¢mah¢', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: '¢mah¢' }
]);
t.isTokens('=~mah~', [
{ type: FX_PREFIX, value: '=' },
{ type: UNKNOWN, value: '~mah~' }
]);
t.isTokens('=$foo', [
{ type: FX_PREFIX, value: '=' },
{ type: UNKNOWN, value: '$foo' }
]);
t.isTokens('=$zzzz12', [
{ type: FX_PREFIX, value: '=' },
{ type: UNKNOWN, value: '$zzzz12' }
]);
t.isTokens('=~zzzz12()', [
{ type: FX_PREFIX, value: '=' },
{ type: UNKNOWN, value: '~zzzz12' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
t.isTokens('=zzzz~12()', [
{ type: FX_PREFIX, value: '=' },
{ type: UNKNOWN, value: 'zzzz~' },
{ type: NUMBER, value: '12' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
t.end();
});
test('tokenize partial ranges', t => {
const opts = { allowTernary: true };
t.isTokens('1:D$1', [
{ type: REF_TERNARY, value: '1:D$1' }
], opts);
t.isTokens('1:D$1', [
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'D$1' }
]);
t.isTokens('B2:B', [
{ type: REF_TERNARY, value: 'B2:B' }
], opts);
t.isTokens('B2:B', [
{ type: REF_RANGE, value: 'B2' },
{ type: OPERATOR, value: ':' },
{ type: REF_NAMED, value: 'B' }
]);
// form 1
t.isTokens('1:A1', [
{ type: REF_TERNARY, value: '1:A1' }
], opts);
t.isTokens('$1:A1', [
{ type: REF_TERNARY, value: '$1:A1' }
], opts);
t.isTokens('1:$A1', [
{ type: REF_TERNARY, value: '1:$A1' }
], opts);
t.isTokens('1:A$1', [
{ type: REF_TERNARY, value: '1:A$1' }
], opts);
t.isTokens('1:$A$1', [
{ type: REF_TERNARY, value: '1:$A$1' }
], opts);
t.isTokens('$1:A$1', [
{ type: REF_TERNARY, value: '$1:A$1' }
], opts);
t.isTokens('$1:$A1', [
{ type: REF_TERNARY, value: '$1:$A1' }
], opts);
t.isTokens('$1:$A$1', [
{ type: REF_TERNARY, value: '$1:$A$1' }
], opts);
// form 2
t.isTokens('A1:1', [
{ type: REF_TERNARY, value: 'A1:1' }
], opts);
t.isTokens('A1:$1', [
{ type: REF_TERNARY, value: 'A1:$1' }
], opts);
t.isTokens('$A1:1', [
{ type: REF_TERNARY, value: '$A1:1' }
], opts);
t.isTokens('A$1:1', [
{ type: REF_TERNARY, value: 'A$1:1' }
], opts);
t.isTokens('$A$1:1', [
{ type: REF_TERNARY, value: '$A$1:1' }
], opts);
t.isTokens('A$1:$1', [
{ type: REF_TERNARY, value: 'A$1:$1' }
], opts);
t.isTokens('$A1:$1', [
{ type: REF_TERNARY, value: '$A1:$1' }
], opts);
t.isTokens('$A$1:$1', [
{ type: REF_TERNARY, value: '$A$1:$1' }
], opts);
// form 3
t.isTokens('A:A1', [
{ type: REF_TERNARY, value: 'A:A1' }
], opts);
t.isTokens('$A:A1', [
{ type: REF_TERNARY, value: '$A:A1' }
], opts);
t.isTokens('A:$A1', [
{ type: REF_TERNARY, value: 'A:$A1' }
], opts);
t.isTokens('A:A$1', [
{ type: REF_TERNARY, value: 'A:A$1' }
], opts);
t.isTokens('A:$A$1', [
{ type: REF_TERNARY, value: 'A:$A$1' }
], opts);
t.isTokens('$A:A$1', [
{ type: REF_TERNARY, value: '$A:A$1' }
], opts);
t.isTokens('$A:$A1', [
{ type: REF_TERNARY, value: '$A:$A1' }
], opts);
t.isTokens('$A:$A$1', [
{ type: REF_TERNARY, value: '$A:$A$1' }
], opts);
// form 4
t.isTokens('A1:A', [
{ type: REF_TERNARY, value: 'A1:A' }
], opts);
t.isTokens('A1:$A', [
{ type: REF_TERNARY, value: 'A1:$A' }
], opts);
t.isTokens('$A1:A', [
{ type: REF_TERNARY, value: '$A1:A' }
], opts);
t.isTokens('A$1:A', [
{ type: REF_TERNARY, value: 'A$1:A' }
], opts);
t.isTokens('$A$1:A', [
{ type: REF_TERNARY, value: '$A$1:A' }
], opts);
t.isTokens('A$1:$A', [
{ type: REF_TERNARY, value: 'A$1:$A' }
], opts);
t.isTokens('$A1:$A', [
{ type: REF_TERNARY, value: '$A1:$A' }
], opts);
t.isTokens('$A$1:$A', [
{ type: REF_TERNARY, value: '$A$1:$A' }
], opts);
t.isTokens('=A10:A+B1:2', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_TERNARY, value: 'A10:A' },
{ type: OPERATOR, value: '+' },
{ type: REF_TERNARY, value: 'B1:2' }
], opts);
t.isTokens('=SUM(A:A$10,3:B$2)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: REF_TERNARY, value: 'A:A$10' },
{ type: OPERATOR, value: ',' },
{ type: REF_TERNARY, value: '3:B$2' },
{ type: OPERATOR, value: ')' }
], opts);
t.isTokens('$A$10:$12', [
{ type: REF_TERNARY, value: '$A$10:$12' }
], opts);
t.isTokens('1:D$1', [
{ type: REF_TERNARY, value: '1:D$1' }
], opts);
t.isTokens('=A1:IF()', [
{ type: FX_PREFIX, value: '=' },