@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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/* eslint-disable no-mixed-operators */
|
|
2
|
+
import { REF_RANGE, REF_BEAM, REF_TERNARY, MAX_COLS, MAX_ROWS } from '../constants.js';
|
|
3
|
+
import { advRangeOp } from './advRangeOp.js';
|
|
4
|
+
import { canEndRange, canEndPartialRange } from './canEndRange.js';
|
|
5
|
+
|
|
6
|
+
function advA1Col (str, pos) {
|
|
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, pos) {
|
|
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 (str, pos, options) {
|
|
60
|
+
let p = pos;
|
|
61
|
+
const left = advA1Col(str, p);
|
|
62
|
+
let right = 0;
|
|
63
|
+
let bottom = 0;
|
|
64
|
+
if (left) {
|
|
65
|
+
// TLBR: could be A1:A1
|
|
66
|
+
// TL R: could be A1:A (if allowTernary)
|
|
67
|
+
// TLB : could be A1:1 (if allowTernary)
|
|
68
|
+
// LBR: could be A:A1 (if allowTernary)
|
|
69
|
+
// L R: could be A:A
|
|
70
|
+
p += left;
|
|
71
|
+
const top = advA1Row(str, p);
|
|
72
|
+
p += top;
|
|
73
|
+
const op = advRangeOp(str, p);
|
|
74
|
+
const preOp = p;
|
|
75
|
+
if (op) {
|
|
76
|
+
p += op;
|
|
77
|
+
right = advA1Col(str, p);
|
|
78
|
+
p += right;
|
|
79
|
+
bottom = advA1Row(str, p);
|
|
80
|
+
p += bottom;
|
|
81
|
+
if (top && bottom && right) {
|
|
82
|
+
if (canEndRange(str, p) && options.mergeRefs) {
|
|
83
|
+
return { type: REF_RANGE, value: str.slice(pos, p) };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (!top && !bottom) {
|
|
87
|
+
if (canEndRange(str, p)) {
|
|
88
|
+
return { type: REF_BEAM, value: str.slice(pos, p) };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (options.allowTernary && (bottom || right)) {
|
|
92
|
+
if (canEndPartialRange(str, p)) {
|
|
93
|
+
return { type: REF_TERNARY, value: str.slice(pos, p) };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// LT : this is A1
|
|
98
|
+
if (top && canEndRange(str, preOp)) {
|
|
99
|
+
return { type: REF_RANGE, value: str.slice(pos, preOp) };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// T B : could be 1:1
|
|
104
|
+
// T BR: could be 1:A1 (if allowTernary)
|
|
105
|
+
const top = advA1Row(str, p);
|
|
106
|
+
if (top) {
|
|
107
|
+
p += top;
|
|
108
|
+
const op = advRangeOp(str, p);
|
|
109
|
+
if (op) {
|
|
110
|
+
p += op;
|
|
111
|
+
right = advA1Col(str, p);
|
|
112
|
+
if (right) {
|
|
113
|
+
p += right;
|
|
114
|
+
}
|
|
115
|
+
bottom = advA1Row(str, p);
|
|
116
|
+
p += bottom;
|
|
117
|
+
if (right && bottom && options.allowTernary) {
|
|
118
|
+
if (canEndPartialRange(str, p)) {
|
|
119
|
+
return { type: REF_TERNARY, value: str.slice(pos, p) };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (!right && bottom) {
|
|
123
|
+
if (canEndRange(str, p)) {
|
|
124
|
+
return { type: REF_BEAM, value: str.slice(pos, p) };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/* eslint-disable no-mixed-operators */
|
|
2
|
+
import { REF_RANGE, REF_BEAM, REF_TERNARY, MAX_COLS, MAX_ROWS } from '../constants.js';
|
|
3
|
+
import { advRangeOp } from './advRangeOp.js';
|
|
4
|
+
import { canEndRange } from './canEndRange.js';
|
|
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, pos, isRow = false) {
|
|
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 (str, pos, options) {
|
|
82
|
+
let p = pos;
|
|
83
|
+
// C1
|
|
84
|
+
// C1:C1
|
|
85
|
+
// C1:R1C1 --partial
|
|
86
|
+
// R1
|
|
87
|
+
// R1:R1
|
|
88
|
+
// R1:R1C1 --partial
|
|
89
|
+
// R1C1
|
|
90
|
+
// R1C1:C1 --partial
|
|
91
|
+
// R1C1:R1 --partial
|
|
92
|
+
const r1 = lexR1C1Part(str, p, true);
|
|
93
|
+
p += r1;
|
|
94
|
+
const c1 = lexR1C1Part(str, p);
|
|
95
|
+
p += c1;
|
|
96
|
+
if (c1 || r1) {
|
|
97
|
+
const op = advRangeOp(str, p);
|
|
98
|
+
const preOp = p;
|
|
99
|
+
if (op) {
|
|
100
|
+
p += op;
|
|
101
|
+
const r2 = lexR1C1Part(str, p, true); // R1
|
|
102
|
+
p += r2;
|
|
103
|
+
const c2 = lexR1C1Part(str, p); // C1
|
|
104
|
+
p += c2;
|
|
105
|
+
|
|
106
|
+
// C1:R2C2 --partial
|
|
107
|
+
// R1:R2C2 --partial
|
|
108
|
+
// R1C1:C2 --partial
|
|
109
|
+
// R1C1:R2 --partial
|
|
110
|
+
if (
|
|
111
|
+
(r1 && !c1 && r2 && c2) ||
|
|
112
|
+
(!r1 && c1 && r2 && c2) ||
|
|
113
|
+
(r1 && c1 && r2 && !c2) ||
|
|
114
|
+
(r1 && c1 && !r2 && c2)
|
|
115
|
+
) {
|
|
116
|
+
if (options.allowTernary && canEndRange(str, p)) {
|
|
117
|
+
return { type: REF_TERNARY, value: str.slice(pos, p) };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// C1:C2 -- beam
|
|
121
|
+
// R1:R2 -- beam
|
|
122
|
+
else if (
|
|
123
|
+
(c1 && c2 && !r1 && !r2) ||
|
|
124
|
+
(!c1 && !c2 && r1 && r2)
|
|
125
|
+
) {
|
|
126
|
+
if (canEndRange(str, p)) {
|
|
127
|
+
return { type: REF_BEAM, value: str.slice(pos, p) };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Note: we do not capture R1C1:R1C1, mergeRefTokens will join the parts
|
|
131
|
+
}
|
|
132
|
+
// R1
|
|
133
|
+
// C1
|
|
134
|
+
// R1C1
|
|
135
|
+
if (canEndRange(str, preOp)) {
|
|
136
|
+
return {
|
|
137
|
+
type: (r1 && c1) ? REF_RANGE : REF_BEAM,
|
|
138
|
+
value: str.slice(pos, preOp)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { OPERATOR_TRIM } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
const PERIOD = 46;
|
|
4
|
+
const COLON = 58;
|
|
5
|
+
|
|
6
|
+
export function lexRangeTrim (str, pos) {
|
|
7
|
+
const c0 = str.charCodeAt(pos);
|
|
8
|
+
if (c0 === PERIOD || c0 === COLON) {
|
|
9
|
+
const c1 = str.charCodeAt(pos + 1);
|
|
10
|
+
if (c0 !== c1) {
|
|
11
|
+
if (c1 === COLON) {
|
|
12
|
+
return {
|
|
13
|
+
type: OPERATOR_TRIM,
|
|
14
|
+
value: str.slice(pos, pos + (str.charCodeAt(pos + 2) === PERIOD ? 3 : 2))
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
else if (c1 === PERIOD) {
|
|
18
|
+
return {
|
|
19
|
+
type: OPERATOR_TRIM,
|
|
20
|
+
value: str.slice(pos, pos + 2)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { OPERATOR } from '../constants.js';
|
|
2
|
+
import { advRangeOp } from './advRangeOp.js';
|
|
3
|
+
|
|
4
|
+
const EXCL = 33; // !
|
|
5
|
+
|
|
6
|
+
export function lexRefOp (str, pos, opts) {
|
|
7
|
+
// in R1C1 mode we only allow [ '!' ]
|
|
8
|
+
if (str.charCodeAt(pos) === EXCL) {
|
|
9
|
+
return { type: OPERATOR, value: str[pos] };
|
|
10
|
+
}
|
|
11
|
+
if (!opts.r1c1) {
|
|
12
|
+
// in A1 mode we allow [ '!' ] + [ ':', '.:', ':.', '.:.']
|
|
13
|
+
const opLen = advRangeOp(str, pos);
|
|
14
|
+
if (opLen) {
|
|
15
|
+
return { type: OPERATOR, value: str.slice(pos, pos + opLen) };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* eslint-disable no-mixed-operators */
|
|
2
|
+
import { STRING } from '../constants.js';
|
|
3
|
+
|
|
4
|
+
const QUOT = 34;
|
|
5
|
+
|
|
6
|
+
export function lexString (str, pos) {
|
|
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
|
+
/* eslint-disable no-mixed-operators */
|
|
2
|
+
import { parseSRange } from '../parseSRange.js';
|
|
3
|
+
import { REF_STRUCT } from '../constants.js';
|
|
4
|
+
import { isWS } from './lexWhitespace.js';
|
|
5
|
+
|
|
6
|
+
const EXCL = 33; // !
|
|
7
|
+
|
|
8
|
+
export function lexStructured (str, pos) {
|
|
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,30 @@
|
|
|
1
|
+
import { WHITESPACE } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
export function isWS (c) {
|
|
4
|
+
return (
|
|
5
|
+
c === 0x9 ||
|
|
6
|
+
c === 0xB ||
|
|
7
|
+
c === 0xC ||
|
|
8
|
+
c === 0xD ||
|
|
9
|
+
c === 0x20 ||
|
|
10
|
+
c === 0xA0 ||
|
|
11
|
+
c === 0x1680 ||
|
|
12
|
+
c === 0x2028 ||
|
|
13
|
+
c === 0x2029 ||
|
|
14
|
+
c === 0x202f ||
|
|
15
|
+
c === 0x205f ||
|
|
16
|
+
c === 0x3000 ||
|
|
17
|
+
c === 0xfeff ||
|
|
18
|
+
(c >= 0x2000 && c <= 0x200a)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function lexWhitespace (str, pos) {
|
|
23
|
+
const start = pos;
|
|
24
|
+
while (isWS(str.charCodeAt(pos))) {
|
|
25
|
+
pos++;
|
|
26
|
+
}
|
|
27
|
+
if (pos !== start) {
|
|
28
|
+
return { type: WHITESPACE, value: str.slice(start, pos) };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { lexError } from './lexError.js';
|
|
2
|
+
import { lexRangeTrim } from './lexRangeTrim.js';
|
|
3
|
+
import { lexOperator } from './lexOperator.js';
|
|
4
|
+
import { lexFunction } from './lexFunction.js';
|
|
5
|
+
import { lexBoolean } from './lexBoolean.js';
|
|
6
|
+
import { lexNewLine } from './lexNewLine.js';
|
|
7
|
+
import { lexWhitespace } from './lexWhitespace.js';
|
|
8
|
+
import { lexString } from './lexString.js';
|
|
9
|
+
import { lexContext } from './lexContext.js';
|
|
10
|
+
import { lexRange } from './lexRange.js';
|
|
11
|
+
import { lexStructured } from './lexStructured.js';
|
|
12
|
+
import { lexNumber } from './lexNumber.js';
|
|
13
|
+
import { lexNamed } from './lexNamed.js';
|
|
14
|
+
import { lexRefOp } from './lexRefOp.js';
|
|
15
|
+
|
|
16
|
+
export const lexers = [
|
|
17
|
+
lexError,
|
|
18
|
+
lexRangeTrim,
|
|
19
|
+
lexOperator,
|
|
20
|
+
lexFunction,
|
|
21
|
+
lexBoolean,
|
|
22
|
+
lexNewLine,
|
|
23
|
+
lexWhitespace,
|
|
24
|
+
lexString,
|
|
25
|
+
lexContext,
|
|
26
|
+
lexRange,
|
|
27
|
+
lexStructured,
|
|
28
|
+
lexNumber,
|
|
29
|
+
lexNamed
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const lexersRefs = [
|
|
33
|
+
lexRefOp,
|
|
34
|
+
lexContext,
|
|
35
|
+
lexRange,
|
|
36
|
+
lexStructured,
|
|
37
|
+
lexNamed
|
|
38
|
+
];
|
package/lib/mergeRefTokens.js
CHANGED
|
@@ -50,19 +50,23 @@ validRunsMerge.forEach(run => packList(run.concat().reverse(), refPartsTree));
|
|
|
50
50
|
// attempt to match a backwards run of tokens from a given point
|
|
51
51
|
// to a path in the tree
|
|
52
52
|
const matcher = (tokens, currNode, anchorIndex, index = 0) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
let i = index;
|
|
54
|
+
let node = currNode;
|
|
55
|
+
const max = tokens.length - index;
|
|
56
|
+
// keep walking as long as the next backward token matches a child key
|
|
57
|
+
while (i <= max) {
|
|
58
|
+
const token = tokens[anchorIndex - i];
|
|
59
|
+
if (token) {
|
|
60
|
+
const key = (token.type === OPERATOR) ? token.value : token.type;
|
|
61
|
+
if (key in node) {
|
|
62
|
+
node = node[key];
|
|
63
|
+
i += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
58
66
|
}
|
|
67
|
+
// can't advance further; accept only if current node is a terminal
|
|
68
|
+
return node[END] ? i : 0;
|
|
59
69
|
}
|
|
60
|
-
if (currNode[END]) {
|
|
61
|
-
// we may end here so this is a match
|
|
62
|
-
return index;
|
|
63
|
-
}
|
|
64
|
-
// no match
|
|
65
|
-
return 0;
|
|
66
70
|
};
|
|
67
71
|
|
|
68
72
|
/**
|
|
@@ -81,19 +85,25 @@ export function mergeRefTokens (tokenlist) {
|
|
|
81
85
|
// that controls what can be joined.
|
|
82
86
|
for (let i = tokenlist.length - 1; i >= 0; i--) {
|
|
83
87
|
let token = tokenlist[i];
|
|
84
|
-
const
|
|
85
|
-
if
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
const type = token.type;
|
|
89
|
+
// Quick check if token type could even start a valid run
|
|
90
|
+
if (type === REF_RANGE || type === REF_BEAM || type === REF_TERNARY ||
|
|
91
|
+
type === REF_NAMED || type === REF_STRUCT) {
|
|
92
|
+
const valid = matcher(tokenlist, refPartsTree, i);
|
|
93
|
+
if (valid > 1) {
|
|
94
|
+
token = { ...token, value: '' };
|
|
95
|
+
const start = i - valid + 1;
|
|
96
|
+
for (let j = start; j <= i; j++) {
|
|
97
|
+
token.value += tokenlist[j].value;
|
|
98
|
+
}
|
|
99
|
+
// adjust the offsets to include all the text
|
|
100
|
+
if (token.loc && tokenlist[start].loc) {
|
|
101
|
+
token.loc[0] = tokenlist[start].loc[0];
|
|
102
|
+
}
|
|
103
|
+
i -= valid - 1;
|
|
93
104
|
}
|
|
94
|
-
i -= valid - 1;
|
|
95
105
|
}
|
|
96
|
-
finalTokens.
|
|
106
|
+
finalTokens[finalTokens.length] = token;
|
|
97
107
|
}
|
|
98
|
-
return finalTokens;
|
|
108
|
+
return finalTokens.reverse();
|
|
99
109
|
}
|
package/lib/parseRef.js
CHANGED