@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.
@@ -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
+ ];
@@ -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
- const token = tokens[anchorIndex - index];
54
- if (token) {
55
- const key = (token.type === OPERATOR) ? token.value : token.type;
56
- if (key in currNode) {
57
- return matcher(tokens, currNode[key], anchorIndex, index + 1);
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 valid = matcher(tokenlist, refPartsTree, i);
85
- if (valid) {
86
- const toMerge = tokenlist.slice(i - valid + 1, i + 1);
87
- // use the meta properties from the "first" token (right-most token)
88
- token = { ...token };
89
- token.value = toMerge.map(d => d.value).join('');
90
- // adjust the offsets to include all the text
91
- if (token.loc && toMerge[0].loc) {
92
- token.loc[0] = toMerge[0].loc[0];
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.unshift(token);
106
+ finalTokens[finalTokens.length] = token;
97
107
  }
98
- return finalTokens;
108
+ return finalTokens.reverse();
99
109
  }
package/lib/parseRef.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  REF_STRUCT,
10
10
  OPERATOR
11
11
  } from './constants.js';
12
- import { lexersRefs } from './lexerParts.js';
12
+ import { lexersRefs } from './lexers/sets.js';
13
13
  import { getTokens } from './lexer.js';
14
14
 
15
15
  // Liberally split a context string up into parts.