@borgar/fx 4.12.0 → 5.0.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/dist/index-BMr6cTgc.d.cts +1444 -0
- package/dist/index-BMr6cTgc.d.ts +1444 -0
- package/dist/index.cjs +3054 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2984 -0
- package/dist/index.js.map +1 -0
- package/dist/xlsx/index.cjs +3120 -0
- package/dist/xlsx/index.cjs.map +1 -0
- package/dist/xlsx/index.d.cts +55 -0
- package/dist/xlsx/index.d.ts +55 -0
- package/dist/xlsx/index.js +3049 -0
- package/dist/xlsx/index.js.map +1 -0
- package/docs/API.md +2959 -718
- package/docs/AST_format.md +2 -2
- package/eslint.config.mjs +40 -0
- package/lib/a1.spec.ts +32 -0
- package/lib/a1.ts +26 -0
- package/lib/addA1RangeBounds.ts +50 -0
- package/lib/addTokenMeta.spec.ts +166 -0
- package/lib/{addTokenMeta.js → addTokenMeta.ts} +53 -33
- package/lib/astTypes.ts +211 -0
- package/lib/cloneToken.ts +29 -0
- package/lib/{constants.js → constants.ts} +6 -3
- package/lib/fixRanges.spec.ts +220 -0
- package/lib/fixRanges.ts +260 -0
- package/lib/fromCol.spec.ts +15 -0
- package/lib/{fromCol.js → fromCol.ts} +1 -1
- package/lib/index.spec.ts +119 -0
- package/lib/index.ts +76 -0
- package/lib/isNodeType.ts +151 -0
- package/lib/isType.spec.ts +208 -0
- package/lib/{isType.js → isType.ts} +26 -25
- package/lib/lexers/advRangeOp.ts +18 -0
- package/lib/lexers/canEndRange.ts +25 -0
- package/lib/lexers/lexBoolean.ts +55 -0
- package/lib/lexers/lexContext.ts +104 -0
- package/lib/lexers/lexError.ts +15 -0
- package/lib/lexers/lexFunction.ts +37 -0
- package/lib/lexers/lexNameFuncCntx.ts +112 -0
- package/lib/lexers/lexNamed.ts +60 -0
- package/lib/lexers/lexNewLine.ts +12 -0
- package/lib/lexers/lexNumber.ts +48 -0
- package/lib/lexers/lexOperator.ts +26 -0
- package/lib/lexers/lexRange.ts +15 -0
- package/lib/lexers/lexRangeA1.ts +134 -0
- package/lib/lexers/lexRangeR1C1.ts +146 -0
- package/lib/lexers/lexRangeTrim.ts +26 -0
- package/lib/lexers/lexRefOp.ts +19 -0
- package/lib/lexers/lexString.ts +22 -0
- package/lib/lexers/lexStructured.ts +25 -0
- package/lib/lexers/lexWhitespace.ts +31 -0
- package/lib/lexers/sets.ts +51 -0
- package/lib/mergeRefTokens.spec.ts +141 -0
- package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +47 -32
- package/lib/nodeTypes.ts +54 -0
- package/lib/parse.spec.ts +1410 -0
- package/lib/{parser.js → parse.ts} +81 -63
- package/lib/parseA1Range.spec.ts +233 -0
- package/lib/parseA1Range.ts +206 -0
- package/lib/parseA1Ref.spec.ts +337 -0
- package/lib/parseA1Ref.ts +115 -0
- package/lib/parseR1C1Range.ts +191 -0
- package/lib/parseR1C1Ref.spec.ts +323 -0
- package/lib/parseR1C1Ref.ts +127 -0
- package/lib/parseRef.spec.ts +90 -0
- package/lib/parseRef.ts +240 -0
- package/lib/parseSRange.ts +240 -0
- package/lib/parseStructRef.spec.ts +168 -0
- package/lib/parseStructRef.ts +76 -0
- package/lib/stringifyA1Range.spec.ts +72 -0
- package/lib/stringifyA1Range.ts +72 -0
- package/lib/stringifyA1Ref.spec.ts +64 -0
- package/lib/stringifyA1Ref.ts +59 -0
- package/lib/{stringifyPrefix.js → stringifyPrefix.ts} +17 -2
- package/lib/stringifyR1C1Range.spec.ts +92 -0
- package/lib/stringifyR1C1Range.ts +73 -0
- package/lib/stringifyR1C1Ref.spec.ts +63 -0
- package/lib/stringifyR1C1Ref.ts +67 -0
- package/lib/stringifyStructRef.spec.ts +124 -0
- package/lib/stringifyStructRef.ts +113 -0
- package/lib/stringifyTokens.ts +15 -0
- package/lib/toCol.spec.ts +11 -0
- package/lib/{toCol.js → toCol.ts} +4 -4
- package/lib/tokenTypes.ts +76 -0
- package/lib/tokenize-srefs.spec.ts +429 -0
- package/lib/tokenize.spec.ts +2103 -0
- package/lib/tokenize.ts +346 -0
- package/lib/translate.spec.ts +35 -0
- package/lib/translateToA1.spec.ts +247 -0
- package/lib/translateToA1.ts +231 -0
- package/lib/translateToR1C1.spec.ts +227 -0
- package/lib/translateToR1C1.ts +145 -0
- package/lib/types.ts +179 -0
- package/lib/xlsx/index.spec.ts +27 -0
- package/lib/xlsx/index.ts +32 -0
- package/package.json +46 -30
- package/tsconfig.json +28 -0
- package/typedoc-ignore-links.ts +17 -0
- package/typedoc.json +41 -0
- package/.eslintrc +0 -22
- package/dist/fx.d.ts +0 -823
- package/dist/fx.js +0 -2
- package/dist/package.json +0 -1
- package/lib/a1.js +0 -348
- package/lib/a1.spec.js +0 -458
- package/lib/addTokenMeta.spec.js +0 -153
- package/lib/astTypes.js +0 -96
- package/lib/extraTypes.js +0 -74
- package/lib/fixRanges.js +0 -104
- package/lib/fixRanges.spec.js +0 -170
- package/lib/fromCol.spec.js +0 -11
- package/lib/index.js +0 -134
- package/lib/index.spec.js +0 -67
- package/lib/isType.spec.js +0 -168
- package/lib/lexer-srefs.spec.js +0 -324
- package/lib/lexer.js +0 -283
- package/lib/lexer.spec.js +0 -1953
- package/lib/lexerParts.js +0 -228
- package/lib/mergeRefTokens.spec.js +0 -121
- package/lib/package.json +0 -1
- package/lib/parseRef.js +0 -157
- package/lib/parseRef.spec.js +0 -71
- package/lib/parseSRange.js +0 -167
- package/lib/parseStructRef.js +0 -48
- package/lib/parseStructRef.spec.js +0 -164
- package/lib/parser.spec.js +0 -1208
- package/lib/rc.js +0 -341
- package/lib/rc.spec.js +0 -403
- package/lib/stringifyStructRef.js +0 -80
- package/lib/stringifyStructRef.spec.js +0 -182
- package/lib/toCol.spec.js +0 -11
- package/lib/translate-toA1.spec.js +0 -214
- package/lib/translate-toRC.spec.js +0 -197
- package/lib/translate.js +0 -239
- package/lib/translate.spec.js +0 -21
- package/rollup.config.mjs +0 -22
- package/tsd.json +0 -12
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { CONTEXT, FUNCTION, REF_NAMED, UNKNOWN } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
import { lexContextUnquoted } from './lexContext.ts';
|
|
4
|
+
|
|
5
|
+
const BR_OPEN = 91; // [
|
|
6
|
+
const PAREN_OPEN = 40;
|
|
7
|
+
const EXCL = 33; // !
|
|
8
|
+
const OFFS = 32;
|
|
9
|
+
|
|
10
|
+
// build a map of characters to allow-bitmasks
|
|
11
|
+
const ALLOWED = new Uint8Array(180 - OFFS);
|
|
12
|
+
const OK_NAME_0 = 0b000001;
|
|
13
|
+
const OK_FUNC_0 = 0b000010;
|
|
14
|
+
const OK_CNTX_0 = 0b000100;
|
|
15
|
+
const OK_NAME_N = 0b001000;
|
|
16
|
+
const OK_FUNC_N = 0b010000;
|
|
17
|
+
const OK_CNTX_N = 0b100000;
|
|
18
|
+
const OK_0 = OK_NAME_0 | OK_FUNC_0 | OK_CNTX_0;
|
|
19
|
+
const OK_N = OK_NAME_N | OK_FUNC_N | OK_CNTX_N;
|
|
20
|
+
const OK_HIGHCHAR = OK_NAME_0 | OK_NAME_N | OK_CNTX_0 | OK_CNTX_N;
|
|
21
|
+
for (let c = OFFS; c < 180; c++) {
|
|
22
|
+
const char = String.fromCharCode(c);
|
|
23
|
+
const n0 = /^[a-zA-Z_\\\u00a1-\uffff]$/.test(char);
|
|
24
|
+
const f0 = /^[a-zA-Z_]$/.test(char);
|
|
25
|
+
const nN = /^[a-zA-Z0-9_.\\?\u00a1-\uffff]$/.test(char);
|
|
26
|
+
const fN = /^[a-zA-Z0-9_.]$/.test(char);
|
|
27
|
+
const cX = /^[a-zA-Z0-9_.¡¤§¨ª\u00ad¯-\uffff]$/.test(char);
|
|
28
|
+
ALLOWED[c - OFFS] = (
|
|
29
|
+
(n0 ? OK_NAME_0 : 0) |
|
|
30
|
+
(nN ? OK_NAME_N : 0) |
|
|
31
|
+
(f0 ? OK_FUNC_0 : 0) |
|
|
32
|
+
(fN ? OK_FUNC_N : 0) |
|
|
33
|
+
(cX ? OK_CNTX_0 : 0) |
|
|
34
|
+
(cX ? OK_CNTX_N : 0)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function nameOrUnknown (str, s, start, pos, name) {
|
|
39
|
+
const len = pos - start;
|
|
40
|
+
if (name && len && len < 255) {
|
|
41
|
+
// names starting with \ must be at least 3 char long
|
|
42
|
+
if (s === 92 && len < 3) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// single characters R and C are forbidden as names
|
|
46
|
+
if (len === 1 && (s === 114 || s === 82 || s === 99 || s === 67)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
return { type: REF_NAMED, value: str.slice(start, pos) };
|
|
50
|
+
}
|
|
51
|
+
return { type: UNKNOWN, value: str.slice(start, pos) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function lexNameFuncCntx (
|
|
55
|
+
str: string,
|
|
56
|
+
pos: number,
|
|
57
|
+
opts: { xlsx: boolean }
|
|
58
|
+
): Token | undefined {
|
|
59
|
+
const start = pos;
|
|
60
|
+
|
|
61
|
+
const s = str.charCodeAt(pos);
|
|
62
|
+
const a = s > 180 ? OK_HIGHCHAR : ALLOWED[s - OFFS];
|
|
63
|
+
// name: [a-zA-Z_\\\u00a1-\uffff]
|
|
64
|
+
// func: [a-zA-Z_]
|
|
65
|
+
// cntx: [a-zA-Z_0-9.¡¤§¨ª\u00ad¯-\uffff]
|
|
66
|
+
if (((a & OK_CNTX_0) && !(a & OK_NAME_0) && !(a & OK_FUNC_0)) || s === BR_OPEN) {
|
|
67
|
+
// its a context so delegate to that lexer
|
|
68
|
+
return lexContextUnquoted(str, pos, opts);
|
|
69
|
+
}
|
|
70
|
+
if (!(a & OK_0)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let name = (a & OK_NAME_0) ? 1 : 0;
|
|
74
|
+
let func = (a & OK_FUNC_0) ? 1 : 0;
|
|
75
|
+
let cntx = (a & OK_CNTX_0) ? 1 : 0;
|
|
76
|
+
pos++;
|
|
77
|
+
|
|
78
|
+
let c: number;
|
|
79
|
+
do {
|
|
80
|
+
c = str.charCodeAt(pos);
|
|
81
|
+
const a = s > 180 ? OK_HIGHCHAR : ALLOWED[c - OFFS] ?? 0;
|
|
82
|
+
if (a & OK_N) {
|
|
83
|
+
// name: [a-zA-Z_0-9.\\?\u00a1-\uffff]
|
|
84
|
+
// func: [a-zA-Z_0-9.]
|
|
85
|
+
// cntx: [a-zA-Z_0-9.¡¤§¨ª\u00ad¯-\uffff]
|
|
86
|
+
if (name && !(a & OK_NAME_N)) {
|
|
87
|
+
name = 0;
|
|
88
|
+
}
|
|
89
|
+
if (func && !(a & OK_FUNC_N)) {
|
|
90
|
+
func = 0;
|
|
91
|
+
}
|
|
92
|
+
if (cntx && !(a & OK_CNTX_N)) {
|
|
93
|
+
cntx = 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
if (c === PAREN_OPEN && func) {
|
|
98
|
+
return { type: FUNCTION, value: str.slice(start, pos) };
|
|
99
|
+
}
|
|
100
|
+
else if (c === EXCL && cntx) {
|
|
101
|
+
return { type: CONTEXT, value: str.slice(start, pos) };
|
|
102
|
+
}
|
|
103
|
+
return nameOrUnknown(str, s, start, pos, name);
|
|
104
|
+
}
|
|
105
|
+
pos++;
|
|
106
|
+
}
|
|
107
|
+
while ((name || func || cntx) && pos < str.length);
|
|
108
|
+
|
|
109
|
+
if (start !== pos) {
|
|
110
|
+
return nameOrUnknown(str, s, start, pos, name);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { REF_NAMED } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
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: string, pos: number): Token | undefined {
|
|
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: number;
|
|
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,12 @@
|
|
|
1
|
+
import { NEWLINE } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
export function lexNewLine (str: string, pos: number): Token | undefined {
|
|
5
|
+
const start = pos;
|
|
6
|
+
while (str.charCodeAt(pos) === 10) {
|
|
7
|
+
pos++;
|
|
8
|
+
}
|
|
9
|
+
if (pos !== start) {
|
|
10
|
+
return { type: NEWLINE, value: str.slice(start, pos) };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { NUMBER } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
function advDigits (str: string, pos: number): number {
|
|
5
|
+
const start = pos;
|
|
6
|
+
do {
|
|
7
|
+
const c = str.charCodeAt(pos);
|
|
8
|
+
if (c < 48 || c > 57) { // 0-9
|
|
9
|
+
break;
|
|
10
|
+
}
|
|
11
|
+
pos++;
|
|
12
|
+
}
|
|
13
|
+
while (pos < str.length);
|
|
14
|
+
return pos - start;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// \d+(\.\d+)?(?:[eE][+-]?\d+)?
|
|
18
|
+
export function lexNumber (str: string, pos: number): Token | undefined {
|
|
19
|
+
const start = pos;
|
|
20
|
+
|
|
21
|
+
// integer
|
|
22
|
+
const lead = advDigits(str, pos);
|
|
23
|
+
if (!lead) { return; }
|
|
24
|
+
pos += lead;
|
|
25
|
+
|
|
26
|
+
// optional fraction part
|
|
27
|
+
const c0 = str.charCodeAt(pos);
|
|
28
|
+
if (c0 === 46) { // .
|
|
29
|
+
pos++;
|
|
30
|
+
const frac = advDigits(str, pos);
|
|
31
|
+
if (!frac) { return; }
|
|
32
|
+
pos += frac;
|
|
33
|
+
}
|
|
34
|
+
// optional exponent part
|
|
35
|
+
const c1 = str.charCodeAt(pos);
|
|
36
|
+
if (c1 === 69 || c1 === 101) { // E e
|
|
37
|
+
pos++;
|
|
38
|
+
const sign = str.charCodeAt(pos);
|
|
39
|
+
if (sign === 43 || sign === 45) { // + -
|
|
40
|
+
pos++;
|
|
41
|
+
}
|
|
42
|
+
const exp = advDigits(str, pos);
|
|
43
|
+
if (!exp) { return; }
|
|
44
|
+
pos += exp;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { type: NUMBER, value: str.slice(start, pos) };
|
|
48
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OPERATOR } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
export function lexOperator (str: string, pos: number): Token | undefined {
|
|
5
|
+
const c0 = str.charCodeAt(pos);
|
|
6
|
+
const c1 = str.charCodeAt(pos + 1);
|
|
7
|
+
if (
|
|
8
|
+
(c0 === 60 && c1 === 61) || // <=
|
|
9
|
+
(c0 === 62 && c1 === 61) || // >=
|
|
10
|
+
(c0 === 60 && c1 === 62) // <>
|
|
11
|
+
) {
|
|
12
|
+
return { type: OPERATOR, value: str.slice(pos, pos + 2) };
|
|
13
|
+
}
|
|
14
|
+
if (
|
|
15
|
+
// { } ! # % &
|
|
16
|
+
c0 === 123 || c0 === 125 || c0 === 33 || c0 === 35 || c0 === 37 || c0 === 38 ||
|
|
17
|
+
// ( ) * + , -
|
|
18
|
+
(c0 >= 40 && c0 <= 45) ||
|
|
19
|
+
// / : ; < = >
|
|
20
|
+
c0 === 47 || (c0 >= 58 && c0 <= 62) ||
|
|
21
|
+
// @ ^
|
|
22
|
+
c0 === 64 || c0 === 94
|
|
23
|
+
) {
|
|
24
|
+
return { type: OPERATOR, value: str[pos] };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Token } from '../types.ts';
|
|
2
|
+
import { lexRangeA1 } from './lexRangeA1.ts';
|
|
3
|
+
import { lexRangeR1C1 } from './lexRangeR1C1.ts';
|
|
4
|
+
|
|
5
|
+
type LexRangeOptions = {
|
|
6
|
+
allowTernary: boolean,
|
|
7
|
+
mergeRefs: boolean,
|
|
8
|
+
r1c1: boolean
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function lexRange (str: string, pos: number, options: LexRangeOptions): Token | undefined {
|
|
12
|
+
return options.r1c1
|
|
13
|
+
? lexRangeR1C1(str, pos, options)
|
|
14
|
+
: lexRangeA1(str, pos, options);
|
|
15
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { REF_RANGE, REF_BEAM, REF_TERNARY, MAX_COLS, MAX_ROWS } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
import { advRangeOp } from './advRangeOp.ts';
|
|
4
|
+
import { canEndRange, canEndPartialRange } from './canEndRange.ts';
|
|
5
|
+
|
|
6
|
+
function advA1Col (str: string, pos: number): number {
|
|
7
|
+
// [A-Z]{1,3}
|
|
8
|
+
const start = pos;
|
|
9
|
+
if (str.charCodeAt(pos) === 36) { // $
|
|
10
|
+
pos++;
|
|
11
|
+
}
|
|
12
|
+
const stop = pos + 3;
|
|
13
|
+
let col = 0;
|
|
14
|
+
do {
|
|
15
|
+
const c = str.charCodeAt(pos);
|
|
16
|
+
if (c >= 65 && c <= 90) { // A-Z
|
|
17
|
+
col = 26 * col + c - 64;
|
|
18
|
+
pos++;
|
|
19
|
+
}
|
|
20
|
+
else if (c >= 97 && c <= 122) { // a-z
|
|
21
|
+
col = 26 * col + c - 96;
|
|
22
|
+
pos++;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
while (pos < stop && pos < str.length);
|
|
29
|
+
return (col && col <= MAX_COLS + 1) ? pos - start : 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function advA1Row (str: string, pos: number): number {
|
|
33
|
+
// [1-9][0-9]{0,6}
|
|
34
|
+
const start = pos;
|
|
35
|
+
if (str.charCodeAt(pos) === 36) { // $
|
|
36
|
+
pos++;
|
|
37
|
+
}
|
|
38
|
+
const stop = pos + 7;
|
|
39
|
+
let row = 0;
|
|
40
|
+
let c = str.charCodeAt(pos);
|
|
41
|
+
if (c >= 49 && c <= 57) { // 1-9
|
|
42
|
+
row = row * 10 + c - 48;
|
|
43
|
+
pos++;
|
|
44
|
+
do {
|
|
45
|
+
c = str.charCodeAt(pos);
|
|
46
|
+
if (c >= 48 && c <= 57) { // 0-9
|
|
47
|
+
row = row * 10 + c - 48;
|
|
48
|
+
pos++;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
while (pos < stop && pos < str.length);
|
|
55
|
+
}
|
|
56
|
+
return (row && row <= MAX_ROWS + 1) ? pos - start : 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function lexRangeA1 (
|
|
60
|
+
str: string,
|
|
61
|
+
pos: number,
|
|
62
|
+
options: { mergeRefs: boolean, allowTernary: boolean }
|
|
63
|
+
): Token | undefined {
|
|
64
|
+
let p = pos;
|
|
65
|
+
const left = advA1Col(str, p);
|
|
66
|
+
let right = 0;
|
|
67
|
+
let bottom = 0;
|
|
68
|
+
if (left) {
|
|
69
|
+
// TLBR: could be A1:A1
|
|
70
|
+
// TL R: could be A1:A (if allowTernary)
|
|
71
|
+
// TLB : could be A1:1 (if allowTernary)
|
|
72
|
+
// LBR: could be A:A1 (if allowTernary)
|
|
73
|
+
// L R: could be A:A
|
|
74
|
+
p += left;
|
|
75
|
+
const top = advA1Row(str, p);
|
|
76
|
+
p += top;
|
|
77
|
+
const op = advRangeOp(str, p);
|
|
78
|
+
const preOp = p;
|
|
79
|
+
if (op) {
|
|
80
|
+
p += op;
|
|
81
|
+
right = advA1Col(str, p);
|
|
82
|
+
p += right;
|
|
83
|
+
bottom = advA1Row(str, p);
|
|
84
|
+
p += bottom;
|
|
85
|
+
if (top && bottom && right) {
|
|
86
|
+
if (canEndRange(str, p) && options.mergeRefs) {
|
|
87
|
+
return { type: REF_RANGE, value: str.slice(pos, p) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (!top && !bottom) {
|
|
91
|
+
if (canEndRange(str, p)) {
|
|
92
|
+
return { type: REF_BEAM, value: str.slice(pos, p) };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (options.allowTernary && (bottom || right)) {
|
|
96
|
+
if (canEndPartialRange(str, p)) {
|
|
97
|
+
return { type: REF_TERNARY, value: str.slice(pos, p) };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// LT : this is A1
|
|
102
|
+
if (top && canEndRange(str, preOp)) {
|
|
103
|
+
return { type: REF_RANGE, value: str.slice(pos, preOp) };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// T B : could be 1:1
|
|
108
|
+
// T BR: could be 1:A1 (if allowTernary)
|
|
109
|
+
const top = advA1Row(str, p);
|
|
110
|
+
if (top) {
|
|
111
|
+
p += top;
|
|
112
|
+
const op = advRangeOp(str, p);
|
|
113
|
+
if (op) {
|
|
114
|
+
p += op;
|
|
115
|
+
right = advA1Col(str, p);
|
|
116
|
+
if (right) {
|
|
117
|
+
p += right;
|
|
118
|
+
}
|
|
119
|
+
bottom = advA1Row(str, p);
|
|
120
|
+
p += bottom;
|
|
121
|
+
if (right && bottom && options.allowTernary) {
|
|
122
|
+
if (canEndPartialRange(str, p)) {
|
|
123
|
+
return { type: REF_TERNARY, value: str.slice(pos, p) };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!right && bottom) {
|
|
127
|
+
if (canEndRange(str, p)) {
|
|
128
|
+
return { type: REF_BEAM, value: str.slice(pos, p) };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { REF_RANGE, REF_BEAM, REF_TERNARY, MAX_COLS, MAX_ROWS } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
import { advRangeOp } from './advRangeOp.ts';
|
|
4
|
+
import { canEndRange } from './canEndRange.ts';
|
|
5
|
+
|
|
6
|
+
const BR_OPEN = 91; // [
|
|
7
|
+
const BR_CLOSE = 93; // ]
|
|
8
|
+
const UC_R = 82;
|
|
9
|
+
const LC_R = 114;
|
|
10
|
+
const UC_C = 67;
|
|
11
|
+
const LC_C = 99;
|
|
12
|
+
const PLUS = 43;
|
|
13
|
+
const MINUS = 45;
|
|
14
|
+
|
|
15
|
+
// C
|
|
16
|
+
// C\[[+-]?\d+\]
|
|
17
|
+
// C[1-9][0-9]{0,4}
|
|
18
|
+
// R
|
|
19
|
+
// R\[[+-]?\d+\]
|
|
20
|
+
// R[1-9][0-9]{0,6}
|
|
21
|
+
function lexR1C1Part (str: string, pos: number, isRow: boolean = false): number {
|
|
22
|
+
const start = pos;
|
|
23
|
+
const c0 = str.charCodeAt(pos);
|
|
24
|
+
if ((isRow ? c0 === UC_R || c0 === LC_R : c0 === UC_C || c0 === LC_C)) {
|
|
25
|
+
pos++;
|
|
26
|
+
let digits = 0;
|
|
27
|
+
let value = 0;
|
|
28
|
+
let stop = str.length;
|
|
29
|
+
const c1 = str.charCodeAt(pos);
|
|
30
|
+
let c;
|
|
31
|
+
let sign = 1;
|
|
32
|
+
const relative = c1 === BR_OPEN;
|
|
33
|
+
if (relative) {
|
|
34
|
+
stop = Math.min(stop, pos + (isRow ? 8 : 6));
|
|
35
|
+
pos++;
|
|
36
|
+
// allow +-
|
|
37
|
+
c = str.charCodeAt(pos);
|
|
38
|
+
if (c === PLUS || c === MINUS) {
|
|
39
|
+
pos++;
|
|
40
|
+
stop++;
|
|
41
|
+
sign = c === MINUS ? -1 : 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (c1 < 49 || c1 > 57 || isNaN(c1)) {
|
|
45
|
+
// char must be 1-9, or part is either just "R" or "C"
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
do {
|
|
50
|
+
const c = str.charCodeAt(pos);
|
|
51
|
+
if (c >= 48 && c <= 57) { // 0-9
|
|
52
|
+
value = value * 10 + c - 48;
|
|
53
|
+
digits++;
|
|
54
|
+
pos++;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
while (pos < stop);
|
|
61
|
+
|
|
62
|
+
const MAX = isRow ? MAX_ROWS : MAX_COLS;
|
|
63
|
+
if (relative) {
|
|
64
|
+
const c = str.charCodeAt(pos);
|
|
65
|
+
if (c !== BR_CLOSE) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
// isRow: next char must not be a number!
|
|
69
|
+
pos++;
|
|
70
|
+
value *= sign;
|
|
71
|
+
return (digits && (-MAX <= value) && (value <= MAX))
|
|
72
|
+
? pos - start
|
|
73
|
+
: 0;
|
|
74
|
+
}
|
|
75
|
+
// isRow: next char must not be a number!
|
|
76
|
+
return (digits && value <= (MAX + 1)) ? pos - start : 0;
|
|
77
|
+
}
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function lexRangeR1C1 (
|
|
82
|
+
str: string,
|
|
83
|
+
pos: number,
|
|
84
|
+
options: { allowTernary: boolean }
|
|
85
|
+
): Token | undefined {
|
|
86
|
+
let p = pos;
|
|
87
|
+
// C1
|
|
88
|
+
// C1:C1
|
|
89
|
+
// C1:R1C1 --partial
|
|
90
|
+
// R1
|
|
91
|
+
// R1:R1
|
|
92
|
+
// R1:R1C1 --partial
|
|
93
|
+
// R1C1
|
|
94
|
+
// R1C1:C1 --partial
|
|
95
|
+
// R1C1:R1 --partial
|
|
96
|
+
const r1 = lexR1C1Part(str, p, true);
|
|
97
|
+
p += r1;
|
|
98
|
+
const c1 = lexR1C1Part(str, p);
|
|
99
|
+
p += c1;
|
|
100
|
+
if (c1 || r1) {
|
|
101
|
+
const op = advRangeOp(str, p);
|
|
102
|
+
const preOp = p;
|
|
103
|
+
if (op) {
|
|
104
|
+
p += op;
|
|
105
|
+
const r2 = lexR1C1Part(str, p, true); // R1
|
|
106
|
+
p += r2;
|
|
107
|
+
const c2 = lexR1C1Part(str, p); // C1
|
|
108
|
+
p += c2;
|
|
109
|
+
|
|
110
|
+
// C1:R2C2 --partial
|
|
111
|
+
// R1:R2C2 --partial
|
|
112
|
+
// R1C1:C2 --partial
|
|
113
|
+
// R1C1:R2 --partial
|
|
114
|
+
if (
|
|
115
|
+
(r1 && !c1 && r2 && c2) ||
|
|
116
|
+
(!r1 && c1 && r2 && c2) ||
|
|
117
|
+
(r1 && c1 && r2 && !c2) ||
|
|
118
|
+
(r1 && c1 && !r2 && c2)
|
|
119
|
+
) {
|
|
120
|
+
if (options.allowTernary && canEndRange(str, p)) {
|
|
121
|
+
return { type: REF_TERNARY, value: str.slice(pos, p) };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// C1:C2 -- beam
|
|
125
|
+
// R1:R2 -- beam
|
|
126
|
+
else if (
|
|
127
|
+
(c1 && c2 && !r1 && !r2) ||
|
|
128
|
+
(!c1 && !c2 && r1 && r2)
|
|
129
|
+
) {
|
|
130
|
+
if (canEndRange(str, p)) {
|
|
131
|
+
return { type: REF_BEAM, value: str.slice(pos, p) };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Note: we do not capture R1C1:R1C1, mergeRefTokens will join the parts
|
|
135
|
+
}
|
|
136
|
+
// R1
|
|
137
|
+
// C1
|
|
138
|
+
// R1C1
|
|
139
|
+
if (canEndRange(str, preOp)) {
|
|
140
|
+
return {
|
|
141
|
+
type: (r1 && c1) ? REF_RANGE : REF_BEAM,
|
|
142
|
+
value: str.slice(pos, preOp)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OPERATOR_TRIM } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
const PERIOD = 46;
|
|
5
|
+
const COLON = 58;
|
|
6
|
+
|
|
7
|
+
export function lexRangeTrim (str: string, pos: number): Token | undefined {
|
|
8
|
+
const c0 = str.charCodeAt(pos);
|
|
9
|
+
if (c0 === PERIOD || c0 === COLON) {
|
|
10
|
+
const c1 = str.charCodeAt(pos + 1);
|
|
11
|
+
if (c0 !== c1) {
|
|
12
|
+
if (c1 === COLON) {
|
|
13
|
+
return {
|
|
14
|
+
type: OPERATOR_TRIM,
|
|
15
|
+
value: str.slice(pos, pos + (str.charCodeAt(pos + 2) === PERIOD ? 3 : 2))
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
else if (c1 === PERIOD) {
|
|
19
|
+
return {
|
|
20
|
+
type: OPERATOR_TRIM,
|
|
21
|
+
value: str.slice(pos, pos + 2)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OPERATOR } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
import { advRangeOp } from './advRangeOp.ts';
|
|
4
|
+
|
|
5
|
+
const EXCL = 33; // !
|
|
6
|
+
|
|
7
|
+
export function lexRefOp (str: string, pos: number, opts: { r1c1: boolean }): Token | undefined {
|
|
8
|
+
// in R1C1 mode we only allow [ '!' ]
|
|
9
|
+
if (str.charCodeAt(pos) === EXCL) {
|
|
10
|
+
return { type: OPERATOR, value: str[pos] };
|
|
11
|
+
}
|
|
12
|
+
if (!opts.r1c1) {
|
|
13
|
+
// in A1 mode we allow [ '!' ] + [ ':', '.:', ':.', '.:.']
|
|
14
|
+
const opLen = advRangeOp(str, pos);
|
|
15
|
+
if (opLen) {
|
|
16
|
+
return { type: OPERATOR, value: str.slice(pos, pos + opLen) };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { STRING } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
const QUOT = 34;
|
|
5
|
+
|
|
6
|
+
export function lexString (str: string, pos: number): Token | undefined {
|
|
7
|
+
const start = pos;
|
|
8
|
+
if (str.charCodeAt(pos) === QUOT) {
|
|
9
|
+
pos++;
|
|
10
|
+
while (pos < str.length) {
|
|
11
|
+
const c = str.charCodeAt(pos);
|
|
12
|
+
if (c === QUOT) {
|
|
13
|
+
pos++;
|
|
14
|
+
if (str.charCodeAt(pos) !== QUOT) {
|
|
15
|
+
return { type: STRING, value: str.slice(start, pos) };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
pos++;
|
|
19
|
+
}
|
|
20
|
+
return { type: STRING, value: str.slice(start, pos), unterminated: true };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { parseSRange } from '../parseSRange.ts';
|
|
2
|
+
import { REF_STRUCT } from '../constants.ts';
|
|
3
|
+
import { isWS } from './lexWhitespace.ts';
|
|
4
|
+
import type { Token } from '../types.ts';
|
|
5
|
+
|
|
6
|
+
const EXCL = 33; // !
|
|
7
|
+
|
|
8
|
+
export function lexStructured (str: string, pos: number): Token | undefined {
|
|
9
|
+
const structData = parseSRange(str, pos);
|
|
10
|
+
if (structData && structData.length) {
|
|
11
|
+
// we have a match for a valid SR
|
|
12
|
+
let i = structData.length;
|
|
13
|
+
// skip tailing whitespace
|
|
14
|
+
while (isWS(str.charCodeAt(pos + i))) {
|
|
15
|
+
i++;
|
|
16
|
+
}
|
|
17
|
+
// and ensure that it isn't followed by a !
|
|
18
|
+
if (str.charCodeAt(pos + i) !== EXCL) {
|
|
19
|
+
return {
|
|
20
|
+
type: REF_STRUCT,
|
|
21
|
+
value: structData.token
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { WHITESPACE } from '../constants.ts';
|
|
2
|
+
import type { Token } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
export function isWS (c) {
|
|
5
|
+
return (
|
|
6
|
+
c === 0x9 ||
|
|
7
|
+
c === 0xB ||
|
|
8
|
+
c === 0xC ||
|
|
9
|
+
c === 0xD ||
|
|
10
|
+
c === 0x20 ||
|
|
11
|
+
c === 0xA0 ||
|
|
12
|
+
c === 0x1680 ||
|
|
13
|
+
c === 0x2028 ||
|
|
14
|
+
c === 0x2029 ||
|
|
15
|
+
c === 0x202f ||
|
|
16
|
+
c === 0x205f ||
|
|
17
|
+
c === 0x3000 ||
|
|
18
|
+
c === 0xfeff ||
|
|
19
|
+
(c >= 0x2000 && c <= 0x200a)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function lexWhitespace (str: string, pos: number): Token | undefined {
|
|
24
|
+
const start = pos;
|
|
25
|
+
while (isWS(str.charCodeAt(pos))) {
|
|
26
|
+
pos++;
|
|
27
|
+
}
|
|
28
|
+
if (pos !== start) {
|
|
29
|
+
return { type: WHITESPACE, value: str.slice(start, pos) };
|
|
30
|
+
}
|
|
31
|
+
}
|