@borgar/fx 4.11.2 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/benchmark/benchmark.js +48 -0
- package/benchmark/formulas.json +15677 -0
- package/dist/fx.d.ts +3 -0
- package/dist/fx.js +2 -2
- package/docs/API.md +1 -0
- package/lib/fixRanges.spec.js +2 -1
- package/lib/lexer.js +38 -57
- package/lib/lexers/advRangeOp.js +18 -0
- package/lib/lexers/canEndRange.js +25 -0
- package/lib/lexers/lexBoolean.js +36 -0
- package/lib/lexers/lexContext.js +96 -0
- package/lib/lexers/lexError.js +15 -0
- package/lib/lexers/lexFunction.js +36 -0
- package/lib/lexers/lexNamed.js +60 -0
- package/lib/lexers/lexNewLine.js +11 -0
- package/lib/lexers/lexNumber.js +47 -0
- package/lib/lexers/lexOperator.js +25 -0
- package/lib/lexers/lexRange.js +8 -0
- package/lib/lexers/lexRangeA1.js +130 -0
- package/lib/lexers/lexRangeR1C1.js +142 -0
- package/lib/lexers/lexRangeTrim.js +25 -0
- package/lib/lexers/lexRefOp.js +18 -0
- package/lib/lexers/lexString.js +22 -0
- package/lib/lexers/lexStructured.js +25 -0
- package/lib/lexers/lexWhitespace.js +30 -0
- package/lib/lexers/sets.js +38 -0
- package/lib/mergeRefTokens.js +33 -23
- package/lib/parseRef.js +1 -1
- package/lib/parseSRange.js +184 -114
- package/lib/parseStructRef.spec.js +4 -0
- package/lib/parser.js +12 -8
- package/lib/parser.spec.js +12 -0
- package/package.json +12 -10
- package/lib/lexerParts.js +0 -228
package/docs/API.md
CHANGED
|
@@ -383,6 +383,7 @@ The AST Abstract Syntax Tree's format is documented in [AST_format.md](./AST_for
|
|
|
383
383
|
| [options] | `object` | `{}` | Options |
|
|
384
384
|
| [options].allowNamed | `boolean` | `true` | Enable parsing names as well as ranges. |
|
|
385
385
|
| [options].allowTernary | `boolean` | `false` | Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md. |
|
|
386
|
+
| [options].looseRefCalls | `boolean` | `false` | Permits any function call where otherwise only functions that return references would be permitted. |
|
|
386
387
|
| [options].negativeNumbers | `boolean` | `true` | Merges unary minuses with their immediately following number tokens (`-`,`1`) => `-1` (alternatively these will be unary operations in the tree). |
|
|
387
388
|
| [options].permitArrayCalls | `boolean` | `false` | Function calls are allowed as elements of arrays. This is a feature in Google Sheets while Excel does not allow it. |
|
|
388
389
|
| [options].permitArrayRanges | `boolean` | `false` | Ranges are allowed as elements of arrays. This is a feature in Google Sheets while Excel does not allow it. |
|
package/lib/fixRanges.spec.js
CHANGED
|
@@ -6,7 +6,8 @@ import { FUNCTION, FX_PREFIX, OPERATOR, REF_RANGE, REF_STRUCT, REF_TERNARY } fro
|
|
|
6
6
|
|
|
7
7
|
Test.prototype.isFixed = function (expr, expected, options = {}) {
|
|
8
8
|
const result = fixRanges(expr, options);
|
|
9
|
-
this.is(result, expected,
|
|
9
|
+
this.is(result, expected,
|
|
10
|
+
`\x1b[36m${expr} → ${expected} \x1b[37mopts=${JSON.stringify(options)}\x1b[0m`);
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
test('fixRanges basics', t => {
|
package/lib/lexer.js
CHANGED
|
@@ -4,15 +4,14 @@ import {
|
|
|
4
4
|
NUMBER,
|
|
5
5
|
OPERATOR,
|
|
6
6
|
REF_NAMED,
|
|
7
|
-
STRING,
|
|
8
7
|
UNKNOWN,
|
|
9
8
|
WHITESPACE,
|
|
10
9
|
FUNCTION,
|
|
11
10
|
OPERATOR_TRIM,
|
|
12
11
|
REF_RANGE
|
|
13
12
|
} from './constants.js';
|
|
14
|
-
import { lexers } from './lexerParts.js';
|
|
15
13
|
import { mergeRefTokens } from './mergeRefTokens.js';
|
|
14
|
+
import { lexers } from './lexers/sets.js';
|
|
16
15
|
|
|
17
16
|
const isType = (t, type) => t && t.type === type;
|
|
18
17
|
|
|
@@ -71,7 +70,14 @@ function fixRCNames (tokens) {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
export function getTokens (fx, tokenHandlers, options = {}) {
|
|
74
|
-
const opts =
|
|
73
|
+
const opts = { ...defaultOptions, ...options };
|
|
74
|
+
// const opts = {
|
|
75
|
+
// withLocation: options.withLocation ?? false,
|
|
76
|
+
// mergeRefs: options.mergeRefs ?? true,
|
|
77
|
+
// allowTernary: options.allowTernary ?? false,
|
|
78
|
+
// negativeNumbers: options.negativeNumbers ?? true,
|
|
79
|
+
// r1c1: options.r1c1 ?? false
|
|
80
|
+
// };
|
|
75
81
|
const { withLocation, mergeRefs, negativeNumbers } = opts;
|
|
76
82
|
const tokens = [];
|
|
77
83
|
let pos = 0;
|
|
@@ -103,7 +109,8 @@ export function getTokens (fx, tokenHandlers, options = {}) {
|
|
|
103
109
|
token.type = UNKNOWN;
|
|
104
110
|
}
|
|
105
111
|
// push token as normally
|
|
106
|
-
tokens.push(token);
|
|
112
|
+
// tokens.push(token);
|
|
113
|
+
tokens[tokens.length] = token;
|
|
107
114
|
lastToken = token;
|
|
108
115
|
if (token.type !== WHITESPACE && token.type !== NEWLINE) {
|
|
109
116
|
tail1 = tail0;
|
|
@@ -112,77 +119,51 @@ export function getTokens (fx, tokenHandlers, options = {}) {
|
|
|
112
119
|
}
|
|
113
120
|
};
|
|
114
121
|
|
|
115
|
-
if (fx
|
|
116
|
-
const token = {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
122
|
+
if (fx.startsWith('=')) {
|
|
123
|
+
const token = { type: FX_PREFIX, value: '=' };
|
|
124
|
+
if (withLocation) {
|
|
125
|
+
token.loc = [ 0, 1 ];
|
|
126
|
+
}
|
|
121
127
|
pos++;
|
|
122
128
|
pushToken(token);
|
|
123
129
|
}
|
|
124
130
|
|
|
131
|
+
const numHandlers = tokenHandlers.length;
|
|
125
132
|
while (pos < fx.length) {
|
|
126
133
|
const startPos = pos;
|
|
127
|
-
|
|
128
|
-
let
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (t) {
|
|
133
|
-
tokenType = t.type;
|
|
134
|
-
tokenValue = t.value;
|
|
135
|
-
pos += tokenValue.length;
|
|
134
|
+
let token;
|
|
135
|
+
for (let i = 0; i < numHandlers; i++) {
|
|
136
|
+
token = tokenHandlers[i](fx, pos, opts);
|
|
137
|
+
if (token) {
|
|
138
|
+
pos += token.value.length;
|
|
136
139
|
break;
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
142
|
|
|
140
|
-
if (!
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
if (!token) {
|
|
144
|
+
token = {
|
|
145
|
+
type: UNKNOWN,
|
|
146
|
+
value: fx[pos]
|
|
147
|
+
};
|
|
143
148
|
pos++;
|
|
144
149
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
value: tokenValue,
|
|
149
|
-
...(withLocation ? { loc: [ startPos, pos ] } : {})
|
|
150
|
-
};
|
|
150
|
+
if (withLocation) {
|
|
151
|
+
token.loc = [ startPos, pos ];
|
|
152
|
+
}
|
|
151
153
|
|
|
152
154
|
// make a note if we found a let/lambda call
|
|
153
|
-
if (lastToken &&
|
|
154
|
-
|
|
155
|
-
if (lastLC === 'lambda' || lastLC === 'let') {
|
|
155
|
+
if (lastToken && token.value === '(' && lastToken.type === FUNCTION) {
|
|
156
|
+
if (/^l(?:ambda|et)$/i.test(lastToken.value)) {
|
|
156
157
|
letOrLambda++;
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
// make a note if we found a R or C unknown
|
|
160
|
-
if (
|
|
161
|
-
const valLC =
|
|
161
|
+
if (token.type === UNKNOWN && token.value.length === 1) {
|
|
162
|
+
const valLC = token.value.toLowerCase();
|
|
162
163
|
unknownRC += (valLC === 'r' || valLC === 'c') ? 1 : 0;
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
if (tokenType === STRING) {
|
|
167
|
-
const l = tokenValue.length;
|
|
168
|
-
if (tokenValue === '""') {
|
|
169
|
-
// common case that IS terminated
|
|
170
|
-
}
|
|
171
|
-
else if (tokenValue === '"' || tokenValue[l - 1] !== '"') {
|
|
172
|
-
token.unterminated = true;
|
|
173
|
-
}
|
|
174
|
-
else if (tokenValue !== '""' && tokenValue[l - 2] === '"') {
|
|
175
|
-
let p = l - 1;
|
|
176
|
-
while (tokenValue[p] === '"') { p--; }
|
|
177
|
-
const atStart = (p + 1);
|
|
178
|
-
const oddNum = ((l - p + 1) % 2 === 0);
|
|
179
|
-
if (!atStart ^ oddNum) {
|
|
180
|
-
token.unterminated = true;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (negativeNumbers && tokenType === NUMBER) {
|
|
166
|
+
if (negativeNumbers && token.type === NUMBER) {
|
|
186
167
|
const last1 = lastToken;
|
|
187
168
|
// do we have a number preceded by a minus?
|
|
188
169
|
if (last1 && isType(last1, OPERATOR) && last1.value === '-') {
|
|
@@ -193,8 +174,8 @@ export function getTokens (fx, tokenHandlers, options = {}) {
|
|
|
193
174
|
!causesBinaryMinus(tail1)
|
|
194
175
|
) {
|
|
195
176
|
const minus = tokens.pop();
|
|
196
|
-
token.value = '-' +
|
|
197
|
-
if (
|
|
177
|
+
token.value = '-' + token.value;
|
|
178
|
+
if (token.loc) {
|
|
198
179
|
// ensure offsets are up to date
|
|
199
180
|
token.loc[0] = minus.loc[0];
|
|
200
181
|
}
|
|
@@ -221,7 +202,7 @@ export function getTokens (fx, tokenHandlers, options = {}) {
|
|
|
221
202
|
// operators.
|
|
222
203
|
for (const index of trimOps) {
|
|
223
204
|
const before = tokens[index - 1];
|
|
224
|
-
const after = tokens[index
|
|
205
|
+
const after = tokens[index + 1];
|
|
225
206
|
if (before && before.type === REF_RANGE && after && after.type === REF_RANGE) {
|
|
226
207
|
tokens[index].type = OPERATOR;
|
|
227
208
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const PERIOD = 46;
|
|
2
|
+
const COLON = 58;
|
|
3
|
+
|
|
4
|
+
export function advRangeOp (str, pos) {
|
|
5
|
+
const c0 = str.charCodeAt(pos);
|
|
6
|
+
if (c0 === PERIOD) {
|
|
7
|
+
const c1 = str.charCodeAt(pos + 1);
|
|
8
|
+
if (c1 === COLON) {
|
|
9
|
+
return str.charCodeAt(pos + 2) === PERIOD ? 3 : 2;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
else if (c0 === COLON) {
|
|
13
|
+
const c1 = str.charCodeAt(pos + 1);
|
|
14
|
+
return c1 === PERIOD ? 2 : 1;
|
|
15
|
+
}
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// regular: [A-Za-z0-9_\u00a1-\uffff]
|
|
2
|
+
export function canEndRange (str, pos) {
|
|
3
|
+
const c = str.charCodeAt(pos);
|
|
4
|
+
return !(
|
|
5
|
+
(c >= 65 && c <= 90) || // A-Z
|
|
6
|
+
(c >= 97 && c <= 122) || // a-z
|
|
7
|
+
(c >= 48 && c <= 57) || // 0-9
|
|
8
|
+
(c === 95) || // _
|
|
9
|
+
(c > 0xA0) // \u00a1-\uffff
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// partial: [A-Za-z0-9_($.]
|
|
14
|
+
export function canEndPartialRange (str, pos) {
|
|
15
|
+
const c = str.charCodeAt(pos);
|
|
16
|
+
return !(
|
|
17
|
+
(c >= 65 && c <= 90) || // A-Z
|
|
18
|
+
(c >= 97 && c <= 122) || // a-z
|
|
19
|
+
(c >= 48 && c <= 57) || // 0-9
|
|
20
|
+
(c === 95) || // _
|
|
21
|
+
(c === 40) || // (
|
|
22
|
+
(c === 36) || // $
|
|
23
|
+
(c === 46) // .
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BOOLEAN } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
export function lexBoolean (str, pos) {
|
|
4
|
+
// "true" (case insensitive)
|
|
5
|
+
const c0 = str.charCodeAt(pos);
|
|
6
|
+
if (c0 === 84 || c0 === 116) {
|
|
7
|
+
const c1 = str.charCodeAt(pos + 1);
|
|
8
|
+
if (c1 === 82 || c1 === 114) {
|
|
9
|
+
const c2 = str.charCodeAt(pos + 2);
|
|
10
|
+
if (c2 === 85 || c2 === 117) {
|
|
11
|
+
const c3 = str.charCodeAt(pos + 3);
|
|
12
|
+
if (c3 === 69 || c3 === 101) {
|
|
13
|
+
// non char to follow?
|
|
14
|
+
return { type: BOOLEAN, value: str.slice(pos, pos + 4) };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// "false" (case insensitive)
|
|
20
|
+
if (c0 === 70 || c0 === 102) {
|
|
21
|
+
const c1 = str.charCodeAt(pos + 1);
|
|
22
|
+
if (c1 === 65 || c1 === 97) {
|
|
23
|
+
const c2 = str.charCodeAt(pos + 2);
|
|
24
|
+
if (c2 === 76 || c2 === 108) {
|
|
25
|
+
const c3 = str.charCodeAt(pos + 3);
|
|
26
|
+
if (c3 === 83 || c3 === 115) {
|
|
27
|
+
const c4 = str.charCodeAt(pos + 4);
|
|
28
|
+
if (c4 === 69 || c4 === 101) {
|
|
29
|
+
// non char to follow?
|
|
30
|
+
return { type: BOOLEAN, value: str.slice(pos, pos + 5) };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { CONTEXT, CONTEXT_QUOTE } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
const QUOT_SINGLE = 39; // '
|
|
4
|
+
const BR_OPEN = 91; // [
|
|
5
|
+
const BR_CLOSE = 93; // ]
|
|
6
|
+
const EXCL = 33; // !
|
|
7
|
+
|
|
8
|
+
// xlsx xml uses a variant of the syntax that has external references in
|
|
9
|
+
// bracets. Any of: [1]Sheet1!A1, '[1]Sheet one'!A1, [1]!named
|
|
10
|
+
export function lexContext (str, pos, options) {
|
|
11
|
+
const c0 = str.charCodeAt(pos);
|
|
12
|
+
let br1;
|
|
13
|
+
let br2;
|
|
14
|
+
// quoted context: '(?:''|[^'])*('|$)(?=!)
|
|
15
|
+
if (c0 === QUOT_SINGLE) {
|
|
16
|
+
const start = pos;
|
|
17
|
+
pos++;
|
|
18
|
+
while (pos < str.length) {
|
|
19
|
+
const c = str.charCodeAt(pos);
|
|
20
|
+
if (c === BR_OPEN) {
|
|
21
|
+
if (br1) { return; } // only 1 allowed
|
|
22
|
+
br1 = pos;
|
|
23
|
+
}
|
|
24
|
+
else if (c === BR_CLOSE) {
|
|
25
|
+
if (br2) { return; } // only 1 allowed
|
|
26
|
+
br2 = pos;
|
|
27
|
+
}
|
|
28
|
+
else if (c === QUOT_SINGLE) {
|
|
29
|
+
pos++;
|
|
30
|
+
if (str.charCodeAt(pos) !== QUOT_SINGLE) {
|
|
31
|
+
let valid = br1 == null && br2 == null;
|
|
32
|
+
if (options.xlsx && (br1 === start + 1) && (br2 === pos - 2)) {
|
|
33
|
+
valid = true;
|
|
34
|
+
}
|
|
35
|
+
if ((br1 >= start + 1) && (br2 < pos - 2) && (br2 > br1 + 1)) {
|
|
36
|
+
valid = true;
|
|
37
|
+
}
|
|
38
|
+
if (valid && str.charCodeAt(pos) === EXCL) {
|
|
39
|
+
return { type: CONTEXT_QUOTE, value: str.slice(start, pos) };
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
pos++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// unquoted context
|
|
48
|
+
else if (c0 !== EXCL) {
|
|
49
|
+
const start = pos;
|
|
50
|
+
while (pos < str.length) {
|
|
51
|
+
const c = str.charCodeAt(pos);
|
|
52
|
+
if (c === BR_OPEN) {
|
|
53
|
+
if (br1) { return; } // only 1 allowed
|
|
54
|
+
br1 = pos;
|
|
55
|
+
}
|
|
56
|
+
else if (c === BR_CLOSE) {
|
|
57
|
+
if (br2) { return; } // only 1 allowed
|
|
58
|
+
br2 = pos;
|
|
59
|
+
}
|
|
60
|
+
else if (c === EXCL) {
|
|
61
|
+
let valid = br1 == null && br2 == null;
|
|
62
|
+
if (options.xlsx && (br1 === start) && (br2 === pos - 1)) {
|
|
63
|
+
valid = true;
|
|
64
|
+
}
|
|
65
|
+
if ((br1 >= start) && (br2 < pos - 1) && (br2 > br1 + 1)) {
|
|
66
|
+
valid = true;
|
|
67
|
+
}
|
|
68
|
+
if (valid) {
|
|
69
|
+
return { type: CONTEXT, value: str.slice(start, pos) };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (
|
|
73
|
+
(br1 == null || br2 != null) &&
|
|
74
|
+
// [0-9A-Za-z._¡¤§¨ª\u00ad¯-\uffff]
|
|
75
|
+
!(
|
|
76
|
+
(c >= 65 && c <= 90) || // A-Z
|
|
77
|
+
(c >= 97 && c <= 122) || // a-z
|
|
78
|
+
(c >= 48 && c <= 57) || // 0-9
|
|
79
|
+
(c === 46) || // .
|
|
80
|
+
(c === 95) || // _
|
|
81
|
+
(c === 161) || // ¡
|
|
82
|
+
(c === 164) || // ¤
|
|
83
|
+
(c === 167) || // §
|
|
84
|
+
(c === 168) || // ¨
|
|
85
|
+
(c === 170) || // ª
|
|
86
|
+
(c === 173) || // \u00ad
|
|
87
|
+
(c >= 175) // ¯-\uffff
|
|
88
|
+
)
|
|
89
|
+
) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// 0-9A-Za-z._¡¤§¨ª\u00ad¯-\uffff
|
|
93
|
+
pos++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* eslint-disable no-mixed-operators */
|
|
2
|
+
import { ERROR } from '../constants.js';
|
|
3
|
+
|
|
4
|
+
const re_ERROR = /#(?:NAME\?|FIELD!|CALC!|VALUE!|REF!|DIV\/0!|NULL!|NUM!|N\/A|GETTING_DATA\b|SPILL!|UNKNOWN!|SYNTAX\?|ERROR!|CONNECT!|BLOCKED!|EXTERNAL!)/iy;
|
|
5
|
+
const HASH = 35;
|
|
6
|
+
|
|
7
|
+
export function lexError (str, pos) {
|
|
8
|
+
if (str.charCodeAt(pos) === HASH) {
|
|
9
|
+
re_ERROR.lastIndex = pos;
|
|
10
|
+
const m = re_ERROR.exec(str);
|
|
11
|
+
if (m) {
|
|
12
|
+
return { type: ERROR, value: m[0] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { FUNCTION } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
const PAREN_OPEN = 40;
|
|
4
|
+
|
|
5
|
+
// [A-Za-z_]+[A-Za-z\d_.]*(?=\()
|
|
6
|
+
export function lexFunction (str, pos) {
|
|
7
|
+
const start = pos;
|
|
8
|
+
// starts with: a-zA-Z_
|
|
9
|
+
let c = str.charCodeAt(pos);
|
|
10
|
+
if (
|
|
11
|
+
(c < 65 || c > 90) && // A-Z
|
|
12
|
+
(c < 97 || c > 122) && // a-z
|
|
13
|
+
(c !== 95) // _
|
|
14
|
+
) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
pos++;
|
|
18
|
+
// has any number of: a-zA-Z0-9_.
|
|
19
|
+
do {
|
|
20
|
+
c = str.charCodeAt(pos);
|
|
21
|
+
if (
|
|
22
|
+
(c < 65 || c > 90) && // A-Z
|
|
23
|
+
(c < 97 || c > 122) && // a-z
|
|
24
|
+
(c < 48 || c > 57) && // 0-9
|
|
25
|
+
(c !== 95) && // _
|
|
26
|
+
(c !== 46) // .
|
|
27
|
+
) {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
pos++;
|
|
31
|
+
} while (pos < str.length);
|
|
32
|
+
// followed by a (
|
|
33
|
+
if (str.charCodeAt(pos) === PAREN_OPEN) {
|
|
34
|
+
return { type: FUNCTION, value: str.slice(start, pos) };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
import { REF_NAMED } from '../constants.js';
|
|
3
|
+
|
|
4
|
+
// The advertized named ranges rules are a bit off from what Excel seems to do.
|
|
5
|
+
// In the "extended range" of chars, it looks like it allows most things above
|
|
6
|
+
// U+00B0 with the range between U+00A0-U+00AF rather random:
|
|
7
|
+
// /^[a-zA-Z\\_¡¤§¨ª\u00ad¯\u00b0-\uffff][a-zA-Z0-9\\_.?¡¤§¨ª\u00ad¯\u00b0-\uffff]{0,254}/
|
|
8
|
+
//
|
|
9
|
+
// I've simplified to allowing everything above U+00A1:
|
|
10
|
+
// /^[a-zA-Z\\_\u00a1-\uffff][a-zA-Z0-9\\_.?\u00a1-\uffff]{0,254}/
|
|
11
|
+
export function lexNamed (str, pos) {
|
|
12
|
+
const start = pos;
|
|
13
|
+
// starts with: [a-zA-Z\\_\u00a1-\uffff]
|
|
14
|
+
const s = str.charCodeAt(pos);
|
|
15
|
+
if (
|
|
16
|
+
(s >= 65 && s <= 90) || // A-Z
|
|
17
|
+
(s >= 97 && s <= 122) || // a-z
|
|
18
|
+
(s === 95) || // _
|
|
19
|
+
(s === 92) || // \
|
|
20
|
+
(s > 0xA0) // \u00a1-\uffff
|
|
21
|
+
) {
|
|
22
|
+
pos++;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// has any number of: [a-zA-Z0-9\\_.?\u00a1-\uffff]
|
|
28
|
+
let c;
|
|
29
|
+
do {
|
|
30
|
+
c = str.charCodeAt(pos);
|
|
31
|
+
if (
|
|
32
|
+
(c >= 65 && c <= 90) || // A-Z
|
|
33
|
+
(c >= 97 && c <= 122) || // a-z
|
|
34
|
+
(c >= 48 && c <= 57) || // 0-9
|
|
35
|
+
(c === 95) || // _
|
|
36
|
+
(c === 92) || // \
|
|
37
|
+
(c === 46) || // .
|
|
38
|
+
(c === 63) || // ?
|
|
39
|
+
(c > 0xA0) // \u00a1-\uffff
|
|
40
|
+
) {
|
|
41
|
+
pos++;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
} while (isFinite(c));
|
|
47
|
+
|
|
48
|
+
const len = pos - start;
|
|
49
|
+
if (len && len < 255) {
|
|
50
|
+
// names starting with \ must be at least 3 char long
|
|
51
|
+
if (s === 92 && len < 3) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// single characters R and C are forbidden as names
|
|
55
|
+
if (len === 1 && (s === 114 || s === 82 || s === 99 || s === 67)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
return { type: REF_NAMED, value: str.slice(start, pos) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { NUMBER } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
function advDigits (str, pos) {
|
|
4
|
+
const start = pos;
|
|
5
|
+
do {
|
|
6
|
+
const c = str.charCodeAt(pos);
|
|
7
|
+
if (c < 48 || c > 57) { // 0-9
|
|
8
|
+
break;
|
|
9
|
+
}
|
|
10
|
+
pos++;
|
|
11
|
+
}
|
|
12
|
+
while (pos < str.length);
|
|
13
|
+
return pos - start;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// \d+(\.\d+)?(?:[eE][+-]?\d+)?
|
|
17
|
+
export function lexNumber (str, pos) {
|
|
18
|
+
const start = pos;
|
|
19
|
+
|
|
20
|
+
// integer
|
|
21
|
+
const lead = advDigits(str, pos);
|
|
22
|
+
if (!lead) { return; }
|
|
23
|
+
pos += lead;
|
|
24
|
+
|
|
25
|
+
// optional fraction part
|
|
26
|
+
const c0 = str.charCodeAt(pos);
|
|
27
|
+
if (c0 === 46) { // .
|
|
28
|
+
pos++;
|
|
29
|
+
const frac = advDigits(str, pos);
|
|
30
|
+
if (!frac) { return; }
|
|
31
|
+
pos += frac;
|
|
32
|
+
}
|
|
33
|
+
// optional exponent part
|
|
34
|
+
const c1 = str.charCodeAt(pos);
|
|
35
|
+
if (c1 === 69 || c1 === 101) { // E e
|
|
36
|
+
pos++;
|
|
37
|
+
const sign = str.charCodeAt(pos);
|
|
38
|
+
if (sign === 43 || sign === 45) { // + -
|
|
39
|
+
pos++;
|
|
40
|
+
}
|
|
41
|
+
const exp = advDigits(str, pos);
|
|
42
|
+
if (!exp) { return; }
|
|
43
|
+
pos += exp;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { type: NUMBER, value: str.slice(start, pos) };
|
|
47
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { OPERATOR } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
export function lexOperator (str, pos) {
|
|
4
|
+
const c0 = str.charCodeAt(pos);
|
|
5
|
+
const c1 = str.charCodeAt(pos + 1);
|
|
6
|
+
if (
|
|
7
|
+
(c0 === 60 && c1 === 61) || // <=
|
|
8
|
+
(c0 === 62 && c1 === 61) || // >=
|
|
9
|
+
(c0 === 60 && c1 === 62) // <>
|
|
10
|
+
) {
|
|
11
|
+
return { type: OPERATOR, value: str.slice(pos, pos + 2) };
|
|
12
|
+
}
|
|
13
|
+
if (
|
|
14
|
+
// { } ! # % &
|
|
15
|
+
c0 === 123 || c0 === 125 || c0 === 33 || c0 === 35 || c0 === 37 || c0 === 38 ||
|
|
16
|
+
// ( ) * + , -
|
|
17
|
+
c0 === 40 || c0 === 41 || c0 === 42 || c0 === 43 || c0 === 44 || c0 === 45 ||
|
|
18
|
+
// / : ; < = >
|
|
19
|
+
c0 === 47 || c0 === 58 || c0 === 59 || c0 === 60 || c0 === 61 || c0 === 62 ||
|
|
20
|
+
// @ ^
|
|
21
|
+
c0 === 64 || c0 === 94
|
|
22
|
+
) {
|
|
23
|
+
return { type: OPERATOR, value: str[pos] };
|
|
24
|
+
}
|
|
25
|
+
}
|