@borgar/fx
Version:
Utilities for working with Excel formulas
1,568 lines (1,465 loc) • 72.3 kB
text/typescript
import { describe, test, expect } from 'vitest';
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.ts';
import { tokenize, tokenizeXlsx } from './tokenize.ts';
function isTokens (expr: string, result: any[], opts?: any) {
expect(tokenize(expr, { negativeNumbers: false, ...opts })).toEqual(result);
}
function isTokensNeg (expr: string, result: any[], opts?: any) {
expect(tokenize(expr, { ...opts, negativeNumbers: true })).toEqual(result);
}
describe('lexer', () => {
describe('operators', () => {
test('basic comparison operators', () => {
isTokens('=1>1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '>' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1>=1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '>=' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1=1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '=' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1<>1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '<>' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1<=1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '<=' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1<1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '<' },
{ type: NUMBER, value: '1' }
]);
});
test('arithmetic operators', () => {
isTokens('=1+1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1*1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '*' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1/1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '/' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1^1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '^' },
{ type: NUMBER, value: '1' }
]);
isTokens('=1&1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '&' },
{ type: NUMBER, value: '1' }
]);
});
test('string equality and references', () => {
isTokens('="A"="B"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"A"' },
{ type: OPERATOR, value: '=' },
{ type: STRING, value: '"B"' }
]);
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: ')' }
]);
});
test('percentage and sheet references', () => {
isTokens('=123%', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '123' },
{ type: OPERATOR, value: '%' }
]);
isTokens('=Sheet1!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
isTokens('=Sheet1!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'Sheet1!A1' }
]);
});
test('range union and intersection', () => {
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 });
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: ')' }
]);
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: ')' }
]);
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: ')' }
]);
});
test('array literals', () => {
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: '}' }
]);
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: '}' }
]);
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: '}' }
]);
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: '}' }
]);
isTokens('={A1:B2}', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '{' },
{ type: REF_RANGE, value: 'A1:B2' },
{ type: OPERATOR, value: '}' }
]);
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: '}' }
]);
});
});
describe('functions', () => {
test('simple functions', () => {
isTokens('=TODAY()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'TODAY' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
isTokens('=ToDaY()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'ToDaY' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
isTokens('=SUM(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
isTokens('=N()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'N' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
});
test('boolean functions', () => {
isTokens('=TRUE()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'TRUE' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
isTokens('=FALSE()', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'FALSE' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
});
test('implicit intersection operator', () => {
isTokens('=@SUM(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '@' },
{ type: FUNCTION, value: 'SUM' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
});
test('function with arguments and whitespace', () => {
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: ')' }
]);
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: ')' }
]);
});
test('special function names', () => {
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: ')' }
]);
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: ')' }
]);
isTokens('=OCT2BIN(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'OCT2BIN' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
isTokens('=TEST_FUNC(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: 'TEST_FUNC' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
isTokens('=_xlfn.FOO(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: '_xlfn.FOO' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
isTokens('=_FOO(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: FUNCTION, value: '_FOO' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
});
test('named range vs function disambiguation', () => {
isTokens('=\\FOO(1)', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: '\\FOO' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: ')' }
]);
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: ')' }
]);
});
});
describe('numbers', () => {
test('integers', () => {
isTokens('=0', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '0' }
]);
isTokens('=+0', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '0' }
]);
isTokens('=+1', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '1' }
]);
isTokens('=-0', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '0' }
]);
isTokens('=1123', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1123' }
]);
isTokens('=-1123', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1123' }
]);
});
test('decimals', () => {
isTokens('=1.5', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.5' }
]);
isTokens('=-1.5', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1.5' }
]);
isTokens('=1234.5678', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1234.5678' }
]);
isTokens('=-1234.5678', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1234.5678' }
]);
});
test('scientific notation', () => {
isTokens('=1E-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1E-1' }
]);
isTokens('=1.5E-10', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.5E-10' }
]);
isTokens('=1.55E+100', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.55E+100' }
]);
isTokens('=1.55e+100', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.55e+100' }
]);
});
});
describe('negative numbers', () => {
test('basic negative numbers', () => {
isTokensNeg('=-0', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-0' }
]);
isTokensNeg('=-1123', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1123' }
]);
isTokensNeg('=-1.5', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1.5' }
]);
isTokensNeg('=-1234.5678', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1234.5678' }
]);
});
test('negative scientific notation', () => {
isTokensNeg('=1E-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1E-1' }
]);
isTokensNeg('=-1E-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1E-1' }
]);
isTokensNeg('=1.5E-10', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.5E-10' }
]);
isTokensNeg('=-1.5E-10', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '-1.5E-10' }
]);
isTokensNeg('-1', [
{ type: NUMBER, value: '-1' }
]);
});
test('negative number context sensitivity', () => {
isTokensNeg('=1-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('1--1', [
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '-1' }
]);
isTokensNeg('1 - -1', [
{ type: NUMBER, value: '1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '-1' }
]);
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' }
]);
});
test('negative numbers with newlines', () => {
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' }
]);
});
test('negative numbers in parentheses', () => {
isTokensNeg('-(-1)', [
{ type: OPERATOR, value: '-' },
{ type: OPERATOR, value: '(' },
{ type: NUMBER, value: '-1' },
{ type: OPERATOR, value: ')' }
]);
isTokensNeg('-( -1 )', [
{ type: OPERATOR, value: '-' },
{ type: OPERATOR, value: '(' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '-1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: ')' }
]);
});
test('negative numbers after other tokens', () => {
isTokensNeg('=true-1', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'true' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('=true -1', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'true' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('=true - 1', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'true' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('=#VALUE!-1', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#VALUE!' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('=#VALUE! -1', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#VALUE!' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
});
test('negative numbers with functions and references', () => {
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' }
]);
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' }
]);
isTokensNeg('=A1-1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('=A1 -1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('=foo-1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'foo' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('=foo -1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'foo' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('="true"-1', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"true"' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('="true" -1', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"true"' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
});
test('negative numbers with complex expressions', () => {
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' }
]);
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' }
]);
isTokensNeg('=10%-1', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '10' },
{ type: OPERATOR, value: '%' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
isTokensNeg('=A1#-1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' },
{ type: OPERATOR, value: '#' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' }
]);
});
});
describe('simple equations', () => {
test('basic arithmetic with spacing', () => {
isTokens('=1 + 2', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: WHITESPACE, value: ' ' },
{ type: OPERATOR, value: '+' },
{ type: WHITESPACE, value: ' ' },
{ type: NUMBER, value: '2' }
]);
isTokens('=1+2', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '2' }
]);
isTokens('=1.1+2.2', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1.1' },
{ type: OPERATOR, value: '+' },
{ type: NUMBER, value: '2.2' }
]);
});
test('parentheses and operator precedence', () => {
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' }
]);
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: ' ' }
]);
});
test('multiplication and formula prefix', () => {
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' }
]);
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' }
]);
});
test('percentage operator', () => {
isTokens('=1%', [
{ type: FX_PREFIX, value: '=' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '%' }
]);
isTokens('=-1%', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '-' },
{ type: NUMBER, value: '1' },
{ type: OPERATOR, value: '%' }
]);
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: '%' }
]);
});
});
describe('R1C1 style references', () => {
test('basic row and column references', () => {
isTokens('=R', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R' }
], { r1c1: true });
isTokens('=R:R', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R:R' }
], { r1c1: true });
isTokens('=R1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R1' }
], { r1c1: true });
isTokens('=R1:R1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R1:R1' }
], { r1c1: true });
isTokens('=C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C' }
], { r1c1: true });
isTokens('=C:C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C:C' }
], { r1c1: true });
isTokens('=C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C1' }
], { r1c1: true });
isTokens('=C1:C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C1:C1' }
], { r1c1: true });
});
test('relative references with brackets', () => {
isTokens('=R[1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[1]' }
], { r1c1: true });
isTokens('=R[1]:R[1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[1]:R[1]' }
], { r1c1: true });
isTokens('=R[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[-1]' }
], { r1c1: true });
isTokens('=R[-1]:R[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[-1]:R[-1]' }
], { r1c1: true });
isTokens('=C[1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[1]' }
], { r1c1: true });
isTokens('=C[1]:C[1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[1]:C[1]' }
], { r1c1: true });
isTokens('=C[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[-1]' }
], { r1c1: true });
isTokens('=C[-1]:C[-1]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'C[-1]:C[-1]' }
], { r1c1: true });
});
test('cell references', () => {
isTokens('=RC', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC' }
], { r1c1: true });
isTokens('=RC:RC', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC:RC' }
], { r1c1: true });
isTokens('=R1C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R1C1' }
], { r1c1: true });
isTokens('=R1C1:R1C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R1C1:R1C1' }
], { r1c1: true });
});
test('mixed absolute and relative references', () => {
isTokens('=R[2]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[2]C' }
], { r1c1: true });
isTokens('=R[2]C:R[2]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[2]C:R[2]C' }
], { r1c1: true });
isTokens('=R[-2]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[-2]C' }
], { r1c1: true });
isTokens('=R[-2]C:R[-2]C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[-2]C:R[-2]C' }
], { r1c1: true });
isTokens('=RC[3]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC[3]' }
], { r1c1: true });
isTokens('=RC[3]:RC[3]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC[3]:RC[3]' }
], { r1c1: true });
isTokens('=RC[-3]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC[-3]' }
], { r1c1: true });
isTokens('=RC[-3]:RC[-3]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC[-3]:RC[-3]' }
], { r1c1: true });
});
test('complex relative references', () => {
isTokens('=R[2]C[2]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[2]C[2]' }
], { r1c1: true });
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 });
isTokens('=R[-2]C[-2]', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R[-2]C[-2]' }
], { r1c1: true });
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 });
});
test('external references', () => {
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 });
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 });
});
test('ranges and mixed types', () => {
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 });
isTokens('=R[-2]:R1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R[-2]:R1' }
], { r1c1: true });
});
test('incompatible range combinations', () => {
isTokens('=R:C', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R' },
{ type: OPERATOR, value: ':' },
{ type: REF_BEAM, value: 'C' }
], { r1c1: true });
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 });
isTokens('=R1:RC', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'R1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'RC' }
], { r1c1: true });
isTokens('=RC:C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'RC' },
{ type: OPERATOR, value: ':' },
{ type: REF_BEAM, value: 'C1' }
], { r1c1: true });
});
});
describe('A1 style references', () => {
test('basic cell references', () => {
isTokens('=A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1' }
]);
isTokens('=C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'C1' }
]);
isTokens('=R1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'R1' }
]);
});
test('absolute references', () => {
isTokens('=$A$1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '$A$1' }
]);
isTokens('=A$1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A$1' }
]);
isTokens('=$A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '$A1' }
]);
});
test('ranges', () => {
isTokens('=A10:A20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10:A20' }
]);
isTokens('=A10:E20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10:E20' }
]);
isTokens('=A1:C1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1:C1' }
]);
isTokens('=A1:C1:D1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A1:C1' },
{ type: OPERATOR, value: ':' },
{ type: REF_RANGE, value: 'D1' }
]);
});
test('spill range syntax', () => {
isTokens('=A10.:A20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10.:A20' }
]);
isTokens('=A10:.A20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10:.A20' }
]);
isTokens('=A10.:.A20', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'A10.:.A20' }
]);
});
test('row and column references', () => {
isTokens('=5:5', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '5:5' }
]);
isTokens('=A:A', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'A:A' }
]);
isTokens('=$A:$A', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '$A:$A' }
]);
isTokens('=1:5', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '1:5' }
]);
isTokens('=A:E', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'A:E' }
]);
isTokens('=$1:$5', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '$1:$5' }
]);
isTokens('=$A:$E', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: '$A:$E' }
]);
});
test('sheet references', () => {
isTokens('=Sheet1!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
isTokens('=Sheet1!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'Sheet1!A1' }
]);
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 });
isTokens('=Sheet1!A1:B2', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'Sheet1!A1:B2' }
]);
});
test('quoted sheet names', () => {
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 });
isTokens("='Run forest, run!'!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '\'Run forest, run!\'!A1' }
]);
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 });
isTokens("='foo'''!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: "'foo'''!A1" }
]);
isTokens("='foo'''!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'foo'''" },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
isTokens("='foo'''''!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: "'foo'''''!A1" }
]);
isTokens("='foo'''''!A1", [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT_QUOTE, value: "'foo'''''" },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
});
test('external workbook references', () => {
isTokens('=[filename]Sheetname!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '[filename]Sheetname!A1' }
]);
isTokens('=[filename]Sheetname!A1', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: '[filename]Sheetname' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
], { mergeRefs: false });
isTokens("='[filename]Sheets'' name'!A1:B2", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: "'[filename]Sheets'' name'!A1:B2" }
]);
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 });
isTokens('=[15]Sheet32!X4', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: '[15]Sheet32!X4' }
]);
isTokens('=[15]Sheet32!X4', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: '[15]Sheet32' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'X4' }
], { mergeRefs: false });
});
test('illegal syntax handling', () => {
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' }
]);
isTokens('=filename!named', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'filename!named' }
]);
isTokens('=filename!named', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'filename' },
{ type: OPERATOR, value: '!' },
{ type: REF_NAMED, value: 'named' }
], { mergeRefs: false });
});
test('maximum reference bounds', () => {
isTokens('=XFD1048576', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_RANGE, value: 'XFD1048576' }
]);
isTokens('=XFD1048577', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'XFD1048577' }
]);
isTokens('=XFE1048577', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'XFE1048577' }
]);
isTokens('=pensioneligibilitypartner1', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'pensioneligibilitypartner1' }
]);
});
test('file path references', () => {
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 });
isTokens("='D:\\Reports\\Sales.xlsx'!namedrange", [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: "'D:\\Reports\\Sales.xlsx'!namedrange" }
]);
isTokens('=Sales.xlsx!namedrange', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sales.xlsx' },
{ type: OPERATOR, value: '!' },
{ type: REF_NAMED, value: 'namedrange' }
], { mergeRefs: false });
isTokens('=Sales.xlsx!namedrange', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_NAMED, value: 'Sales.xlsx!namedrange' }
]);
});
test('column and row beam references with sheets', () => {
isTokens('=Sheet1!A:A', [
{ type: FX_PREFIX, value: '=' },
{ type: REF_BEAM, value: 'Sheet1!A:A' }
]);
isTokens('=Sheet1!A:A', [
{ type: FX_PREFIX, value: '=' },
{ type: CONTEXT, value: 'Sheet1' },
{ type: OPERATOR, value: '!' },
{ type: REF_BEAM, value: 'A:A' }
], { mergeRefs: false });
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' }
]);
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 });
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 });
});
test('error references', () => {
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' }
]);
});
});
describe('errors', () => {
test('standard errors', () => {
isTokens('=#NAME?', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#NAME?' }
]);
isTokens('=#VALUE!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#VALUE!' }
]);
isTokens('=#REF!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#REF!' }
]);
isTokens('=#DIV/0!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#DIV/0!' }
]);
isTokens('=#NULL!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#NULL!' }
]);
isTokens('=#NUM!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#NUM!' }
]);
isTokens('=#N/A', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#N/A' }
]);
});
test('dynamic array and advanced errors', () => {
isTokens('=#GETTING_DATA', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#GETTING_DATA' }
]);
isTokens('=#SPILL!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#SPILL!' }
]);
isTokens('=#UNKNOWN!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#UNKNOWN!' }
]);
isTokens('=#FIELD!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#FIELD!' }
]);
isTokens('=#CALC!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#CALC!' }
]);
isTokens('=#SYNTAX?', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#SYNTAX?' }
]);
isTokens('=#ERROR!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#ERROR!' }
]);
isTokens('=#CONNECT!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#CONNECT!' }
]);
isTokens('=#BLOCKED!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#BLOCKED!' }
]);
isTokens('=#EXTERNAL!', [
{ type: FX_PREFIX, value: '=' },
{ type: ERROR, value: '#EXTERNAL!' }
]);
});
test('unrecognized error syntax', () => {
isTokens('=#NONSENSE!', [
{ type: FX_PREFIX, value: '=' },
{ type: OPERATOR, value: '#' },
{ type: CONTEXT, value: 'NONSENSE' },
{ type: OPERATOR, value: '!' }
]);
});
});
describe('booleans', () => {
test('true values', () => {
isTokens('=true', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'true' }
]);
isTokens('=tRuE', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'tRuE' }
]);
isTokens('=TRUE', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'TRUE' }
]);
isTokens('true!A1', [
{ type: BOOLEAN, value: 'true' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
]);
isTokens('truesheet!A1', [
{ type: REF_RANGE, value: 'truesheet!A1' }
]);
isTokens('true()', [
{ type: FUNCTION, value: 'true' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
});
test('false values', () => {
isTokens('=false', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'false' }
]);
isTokens('=fAlSe', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'fAlSe' }
]);
isTokens('=FALSE', [
{ type: FX_PREFIX, value: '=' },
{ type: BOOLEAN, value: 'FALSE' }
]);
isTokens('false!A1', [
{ type: BOOLEAN, value: 'false' },
{ type: OPERATOR, value: '!' },
{ type: REF_RANGE, value: 'A1' }
]);
isTokens('falsesheet!A1', [
{ type: REF_RANGE, value: 'falsesheet!A1' }
]);
isTokens('false()', [
{ type: FUNCTION, value: 'false' },
{ type: OPERATOR, value: '(' },
{ type: OPERATOR, value: ')' }
]);
});
});
describe('strings', () => {
test('basic strings', () => {
isTokens('=""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""' }
]);
isTokens('=""""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""""' }
]);
isTokens('="data"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"data"' }
]);
isTokens('="data""data"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"data""data"' }
]);
});
test('string concatenation', () => {
isTokens('="data"&"data"', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"data"' },
{ type: OPERATOR, value: '&' },
{ type: STRING, value: '"data"' }
]);
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"' }
]);
});
test('unterminated strings', () => {
isTokens('="incomple', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"incomple', unterminated: true }
]);
isTokens('="', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"', unterminated: true }
]);
isTokens('=""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""' }
]);
isTokens('="""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"""', unterminated: true }
]);
isTokens('=""""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""""' }
]);
isTokens('="""""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '"""""', unterminated: true }
]);
isTokens('=""""""', [
{ type: FX_PREFIX, value: '=' },
{ type: STRING, value: '""""""' }
]);
});
test('escaped quotes', () => {
isTokens('