@borgar/fx 4.7.1 → 4.9.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/fx.d.ts +86 -1
- package/dist/fx.js +1 -1
- package/docs/API.md +171 -7
- package/docs/AST_format.md +45 -15
- package/lib/astTypes.js +96 -0
- package/lib/constants.js +3 -0
- package/lib/fixRanges.js +3 -2
- package/lib/lexer.js +51 -0
- package/lib/lexer.spec.js +63 -0
- package/lib/lexerParts.js +5 -0
- package/lib/parser.js +235 -33
- package/lib/parser.spec.js +380 -89
- package/lib/sr.js +5 -3
- package/lib/sr.spec.js +50 -0
- package/package.json +1 -1
package/docs/AST_format.md
CHANGED
|
@@ -6,7 +6,7 @@ This document specifies the core AST node types that support the Excel grammar.
|
|
|
6
6
|
|
|
7
7
|
All AST nodes are represented by `Node` objects. They may have any prototype inheritance but implement the following basic interface:
|
|
8
8
|
|
|
9
|
-
```
|
|
9
|
+
```ts
|
|
10
10
|
interface Node {
|
|
11
11
|
type: string;
|
|
12
12
|
loc?: Location | null;
|
|
@@ -17,7 +17,7 @@ The `type` field is a string representing the AST variant type. Each subtype of
|
|
|
17
17
|
|
|
18
18
|
The `loc` field represents the source location information of the node. If the node contains no information about the source location, the field is `null`; otherwise it is an array consisting of a two numbers: A start offset (the position of the first character of the parsed source region) and an end offset (the position of the first character after the parsed source region):
|
|
19
19
|
|
|
20
|
-
```
|
|
20
|
+
```ts
|
|
21
21
|
interface Location extends Array<number> {
|
|
22
22
|
0: number;
|
|
23
23
|
1: number;
|
|
@@ -26,30 +26,31 @@ interface Location extends Array<number> {
|
|
|
26
26
|
|
|
27
27
|
## Identifier
|
|
28
28
|
|
|
29
|
-
```
|
|
29
|
+
```ts
|
|
30
30
|
interface Identifier extends Node {
|
|
31
31
|
type: "Identifier";
|
|
32
32
|
name: string;
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
An identifier. These
|
|
36
|
+
An identifier. These appear on `CallExpression`, `LambdaExpression`, and `LetExpression` and will always be a static string representing the name of a function call or parameter.
|
|
37
37
|
|
|
38
38
|
## ReferenceIdentifier
|
|
39
39
|
|
|
40
|
-
```
|
|
40
|
+
```ts
|
|
41
41
|
interface ReferenceIdentifier extends Node {
|
|
42
42
|
type: "ReferenceIdentifier";
|
|
43
43
|
value: string;
|
|
44
|
+
kind: "name" | "range" | "beam" | "table";
|
|
44
45
|
}
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
An identifier for a range or a named. The
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
## Literal
|
|
51
52
|
|
|
52
|
-
```
|
|
53
|
+
```ts
|
|
53
54
|
interface Literal extends Node {
|
|
54
55
|
type: "Literal";
|
|
55
56
|
raw: string;
|
|
@@ -61,7 +62,7 @@ A literal token. Captures numbers, strings, and booleans. Literal errors have th
|
|
|
61
62
|
|
|
62
63
|
## ErrorLiteral
|
|
63
64
|
|
|
64
|
-
```
|
|
65
|
+
```ts
|
|
65
66
|
interface ErrorLiteral extends Node {
|
|
66
67
|
type: "ErrorLiteral";
|
|
67
68
|
raw: string;
|
|
@@ -73,7 +74,7 @@ An Error expression.
|
|
|
73
74
|
|
|
74
75
|
## UnaryExpression
|
|
75
76
|
|
|
76
|
-
```
|
|
77
|
+
```ts
|
|
77
78
|
interface UnaryExpression extends Node {
|
|
78
79
|
type: "UnaryExpression";
|
|
79
80
|
operator: UnaryOperator;
|
|
@@ -85,7 +86,7 @@ A unary operator expression.
|
|
|
85
86
|
|
|
86
87
|
### UnaryOperator
|
|
87
88
|
|
|
88
|
-
```
|
|
89
|
+
```ts
|
|
89
90
|
type UnaryOperator = (
|
|
90
91
|
"+" | "-" | "%" | "#" | "@"
|
|
91
92
|
)
|
|
@@ -95,7 +96,7 @@ A unary operator token.
|
|
|
95
96
|
|
|
96
97
|
## BinaryExpression
|
|
97
98
|
|
|
98
|
-
```
|
|
99
|
+
```ts
|
|
99
100
|
interface BinaryExpression extends Node {
|
|
100
101
|
type: "BinaryExpression";
|
|
101
102
|
operator: BinaryOperator;
|
|
@@ -107,7 +108,7 @@ A binary operator expression.
|
|
|
107
108
|
|
|
108
109
|
### BinaryOperator
|
|
109
110
|
|
|
110
|
-
```
|
|
111
|
+
```ts
|
|
111
112
|
type BinaryOperator = (
|
|
112
113
|
"=" | "<" | ">" | "<=" | ">=" | "<>" |
|
|
113
114
|
"-" | "+" | "*" | "/" | "^" |
|
|
@@ -120,7 +121,7 @@ A binary operator token. Note that Excels union operator is whitespace so a pars
|
|
|
120
121
|
|
|
121
122
|
## CallExpression
|
|
122
123
|
|
|
123
|
-
```
|
|
124
|
+
```ts
|
|
124
125
|
interface CallExpression extends Node {
|
|
125
126
|
type: "CallExpression";
|
|
126
127
|
callee: Identifier;
|
|
@@ -132,13 +133,42 @@ A function call expression.
|
|
|
132
133
|
|
|
133
134
|
## ArrayExpression
|
|
134
135
|
|
|
135
|
-
```
|
|
136
|
+
```ts
|
|
136
137
|
interface ArrayExpression extends Node {
|
|
137
138
|
type: "ArrayExpression";
|
|
138
|
-
elements: Array<Array<Literal |
|
|
139
|
+
elements: Array<Array<ReferenceIdentifier | Literal | ErrorLiteral | CallExpression>>;
|
|
139
140
|
}
|
|
140
141
|
```
|
|
141
142
|
|
|
142
143
|
An array expression. Excel does not have empty or sparse arrays and restricts array elements to literals. Google Sheets allows `ReferenceIdentifier`s as elements of arrays, the fx parser as an option for this but it is off by default.
|
|
143
144
|
|
|
145
|
+
## LambdaExpression
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
interface LambdaExpression extends Node {
|
|
149
|
+
type: "LambdaExpression";
|
|
150
|
+
params: Array<Identifier>;
|
|
151
|
+
body: null | Node;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## LetExpression
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
interface LetExpression extends Node {
|
|
159
|
+
type: "LetExpression";
|
|
160
|
+
declarations: Array<LetDeclarator>;
|
|
161
|
+
body: null | Node;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## LetDeclarator
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
interface LetDeclarator extends Node {
|
|
169
|
+
type: "LetDeclarator";
|
|
170
|
+
id: Identifier;
|
|
171
|
+
init: null | Node;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
144
174
|
|
package/lib/astTypes.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* eslint-disable jsdoc/require-property-description */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {number[]} SourceLocation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Node} Identifier
|
|
9
|
+
* @property {"Identifier"} type
|
|
10
|
+
* @property {SourceLocation} [loc]
|
|
11
|
+
* @property {string} name
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Node} ReferenceIdentifier
|
|
16
|
+
* @property {"ReferenceIdentifier"} type
|
|
17
|
+
* @property {SourceLocation} [loc]
|
|
18
|
+
* @property {string} value
|
|
19
|
+
* @property {"name" | "range" | "beam" | "table"} kind
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Node} Literal
|
|
24
|
+
* @property {"Literal"} type
|
|
25
|
+
* @property {SourceLocation} [loc]
|
|
26
|
+
* @property {string} raw
|
|
27
|
+
* @property {string | number | boolean} value
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Node} ErrorLiteral
|
|
32
|
+
* @property {"ErrorLiteral"} type
|
|
33
|
+
* @property {SourceLocation} [loc]
|
|
34
|
+
* @property {string} raw
|
|
35
|
+
* @property {string} value
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Node} UnaryExpression
|
|
40
|
+
* @property {"UnaryExpression"} type
|
|
41
|
+
* @property {SourceLocation} [loc]
|
|
42
|
+
* @property {"+" | "-" | "%" | "#" | "@"} operator
|
|
43
|
+
* @property {AstExpression[]} arguments
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {Node} BinaryExpression
|
|
48
|
+
* @property {"BinaryExpression"} type
|
|
49
|
+
* @property {SourceLocation} [loc]
|
|
50
|
+
* @property {"=" | "<" | ">" | "<=" | ">=" | "<>" | "-" | "+" | "*" | "/" | "^" | ":" | " " | "," | "&"} operator
|
|
51
|
+
* @property {AstExpression[]} arguments
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {Node} CallExpression
|
|
56
|
+
* @property {"CallExpression"} type
|
|
57
|
+
* @property {SourceLocation} [loc]
|
|
58
|
+
* @property {Identifier} callee
|
|
59
|
+
* @property {AstExpression[]} arguments
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
// FIXME: the awkward naming is because tooling fails, fix tooling :)
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {Node} MatrixExpression
|
|
65
|
+
* @property {"ArrayExpression"} type
|
|
66
|
+
* @property {SourceLocation} [loc]
|
|
67
|
+
* @property {Array<Array<ReferenceIdentifier | Literal | ErrorLiteral | CallExpression>>} arguments
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {Node} LambdaExpression
|
|
72
|
+
* @property {"LambdaExpression"} type
|
|
73
|
+
* @property {SourceLocation} [loc]
|
|
74
|
+
* @property {Identifier[]} params
|
|
75
|
+
* @property {null | AstExpression} body
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @typedef {Node} LetExpression
|
|
80
|
+
* @property {"LetExpression"} type
|
|
81
|
+
* @property {SourceLocation} [loc]
|
|
82
|
+
* @property {LetDeclarator[]} declarations
|
|
83
|
+
* @property {null | AstExpression} body
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @typedef {Node} LetDeclarator
|
|
88
|
+
* @property {"LetDeclarator"} type
|
|
89
|
+
* @property {SourceLocation} [loc]
|
|
90
|
+
* @property {Identifier} id
|
|
91
|
+
* @property {null | AstExpression} init
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @typedef {ReferenceIdentifier | Literal | ErrorLiteral | UnaryExpression | BinaryExpression | CallExpression | MatrixExpression | LambdaExpression | LetExpression} AstExpression
|
|
96
|
+
*/
|
package/lib/constants.js
CHANGED
|
@@ -22,8 +22,11 @@ export const REFERENCE = 'ReferenceIdentifier';
|
|
|
22
22
|
export const LITERAL = 'Literal';
|
|
23
23
|
export const ERROR_LITERAL = 'ErrorLiteral';
|
|
24
24
|
export const CALL = 'CallExpression';
|
|
25
|
+
export const LAMBDA = 'LambdaExpression';
|
|
26
|
+
export const LET = 'LetExpression';
|
|
25
27
|
export const ARRAY = 'ArrayExpression';
|
|
26
28
|
export const IDENTIFIER = 'Identifier';
|
|
29
|
+
export const LET_DECL = 'LetDeclarator';
|
|
27
30
|
|
|
28
31
|
export const MAX_COLS = (2 ** 14) - 1; // 16383
|
|
29
32
|
export const MAX_ROWS = (2 ** 20) - 1; // 1048575
|
package/lib/fixRanges.js
CHANGED
|
@@ -44,6 +44,7 @@ import { REF_STRUCT } from './constants.js';
|
|
|
44
44
|
* @param {object} [options={}] Options
|
|
45
45
|
* @param {boolean} [options.addBounds=false] Fill in any undefined bounds of range objects. Top to 0, bottom to 1048575, left to 0, and right to 16383.
|
|
46
46
|
* @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
|
|
47
|
+
* @param {boolean} [options.thisRow=false] Enforces using the `[#This Row]` instead of the `@` shorthand when serializing structured ranges.
|
|
47
48
|
* @returns {(string | Array<Token>)} A formula string or token list (depending on which was input)
|
|
48
49
|
*/
|
|
49
50
|
export function fixRanges (formula, options = { addBounds: false }) {
|
|
@@ -55,7 +56,7 @@ export function fixRanges (formula, options = { addBounds: false }) {
|
|
|
55
56
|
if (!Array.isArray(formula)) {
|
|
56
57
|
throw new Error('fixRanges expects an array of tokens');
|
|
57
58
|
}
|
|
58
|
-
const { addBounds, r1c1, xlsx } = options;
|
|
59
|
+
const { addBounds, r1c1, xlsx, thisRow } = options;
|
|
59
60
|
if (r1c1) {
|
|
60
61
|
throw new Error('fixRanges does not have an R1C1 mode');
|
|
61
62
|
}
|
|
@@ -68,7 +69,7 @@ export function fixRanges (formula, options = { addBounds: false }) {
|
|
|
68
69
|
let offsetDelta = 0;
|
|
69
70
|
if (token.type === REF_STRUCT) {
|
|
70
71
|
const sref = parseStructRef(token.value, { xlsx });
|
|
71
|
-
const newValue = stringifyStructRef(sref, { xlsx });
|
|
72
|
+
const newValue = stringifyStructRef(sref, { xlsx, thisRow });
|
|
72
73
|
offsetDelta = newValue.length - token.value.length;
|
|
73
74
|
token.value = newValue;
|
|
74
75
|
}
|
package/lib/lexer.js
CHANGED
|
@@ -38,11 +38,43 @@ const causesBinaryMinus = token => {
|
|
|
38
38
|
);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
function fixRCNames (tokens) {
|
|
42
|
+
let withinCall = 0;
|
|
43
|
+
let parenDepth = 0;
|
|
44
|
+
let lastToken;
|
|
45
|
+
for (const token of tokens) {
|
|
46
|
+
if (token.type === OPERATOR) {
|
|
47
|
+
if (token.value === '(') {
|
|
48
|
+
parenDepth++;
|
|
49
|
+
if (lastToken.type === FUNCTION) {
|
|
50
|
+
const v = lastToken.value.toLowerCase();
|
|
51
|
+
if (v === 'lambda' || v === 'let') {
|
|
52
|
+
withinCall = parenDepth;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (token.value === ')') {
|
|
57
|
+
parenDepth--;
|
|
58
|
+
if (parenDepth < withinCall) {
|
|
59
|
+
withinCall = 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (withinCall && token.type === UNKNOWN && /^[rc]$/.test(token.value)) {
|
|
64
|
+
token.type = REF_NAMED;
|
|
65
|
+
}
|
|
66
|
+
lastToken = token;
|
|
67
|
+
}
|
|
68
|
+
return tokens;
|
|
69
|
+
}
|
|
70
|
+
|
|
41
71
|
export function getTokens (fx, tokenHandlers, options = {}) {
|
|
42
72
|
const opts = Object.assign({}, defaultOptions, options);
|
|
43
73
|
const { withLocation, mergeRefs, negativeNumbers } = opts;
|
|
44
74
|
const tokens = [];
|
|
45
75
|
let pos = 0;
|
|
76
|
+
let letOrLambda = 0;
|
|
77
|
+
let unknownRC = 0;
|
|
46
78
|
|
|
47
79
|
let tail0 = null; // last non-whitespace token
|
|
48
80
|
let tail1 = null; // penultimate non-whitespace token
|
|
@@ -110,6 +142,19 @@ export function getTokens (fx, tokenHandlers, options = {}) {
|
|
|
110
142
|
...(withLocation ? { loc: [ startPos, pos ] } : {})
|
|
111
143
|
};
|
|
112
144
|
|
|
145
|
+
// make a note if we found a let/lambda call
|
|
146
|
+
if (lastToken && lastToken.type === FUNCTION && tokenValue === '(') {
|
|
147
|
+
const lastLC = lastToken.value.toLowerCase();
|
|
148
|
+
if (lastLC === 'lambda' || lastLC === 'let') {
|
|
149
|
+
letOrLambda++;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// make a note if we found a R or C unknown
|
|
153
|
+
if (tokenType === UNKNOWN) {
|
|
154
|
+
const valLC = tokenValue.toLowerCase();
|
|
155
|
+
unknownRC += (valLC === 'r' || valLC === 'c') ? 1 : 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
113
158
|
// check for termination
|
|
114
159
|
if (tokenType === STRING) {
|
|
115
160
|
const l = tokenValue.length;
|
|
@@ -157,6 +202,12 @@ export function getTokens (fx, tokenHandlers, options = {}) {
|
|
|
157
202
|
pushToken(token);
|
|
158
203
|
}
|
|
159
204
|
|
|
205
|
+
// if we encountered both a LAMBDA/LET call, and unknown 'r' or 'c' tokens
|
|
206
|
+
// we'll turn the unknown tokens into names within the call.
|
|
207
|
+
if (unknownRC && letOrLambda) {
|
|
208
|
+
fixRCNames(tokens);
|
|
209
|
+
}
|
|
210
|
+
|
|
160
211
|
if (mergeRefs) {
|
|
161
212
|
return mergeRefTokens(tokens);
|
|
162
213
|
}
|
package/lib/lexer.spec.js
CHANGED
|
@@ -1462,6 +1462,18 @@ test('unknowns, named ranges and functions', t => {
|
|
|
1462
1462
|
{ type: FX_PREFIX, value: '=' },
|
|
1463
1463
|
{ type: REF_NAMED, value: '\\foo' }
|
|
1464
1464
|
]);
|
|
1465
|
+
t.isTokens('=\\fo', [
|
|
1466
|
+
{ type: FX_PREFIX, value: '=' },
|
|
1467
|
+
{ type: REF_NAMED, value: '\\fo' }
|
|
1468
|
+
]);
|
|
1469
|
+
t.isTokens('=\\f', [
|
|
1470
|
+
{ type: FX_PREFIX, value: '=' },
|
|
1471
|
+
{ type: UNKNOWN, value: '\\f' }
|
|
1472
|
+
]);
|
|
1473
|
+
t.isTokens('=\\', [
|
|
1474
|
+
{ type: FX_PREFIX, value: '=' },
|
|
1475
|
+
{ type: UNKNOWN, value: '\\' }
|
|
1476
|
+
]);
|
|
1465
1477
|
t.isTokens('=æði', [
|
|
1466
1478
|
{ type: FX_PREFIX, value: '=' },
|
|
1467
1479
|
{ type: REF_NAMED, value: 'æði' }
|
|
@@ -1769,3 +1781,54 @@ test('tokenize external refs syntax from XLSX files', t => {
|
|
|
1769
1781
|
|
|
1770
1782
|
t.end();
|
|
1771
1783
|
});
|
|
1784
|
+
|
|
1785
|
+
test('tokenize r and c as names within LET and LAMBDA calls', t => {
|
|
1786
|
+
t.isTokens('=c*(LAMBDA(r,c,r*c)+r)+r', [
|
|
1787
|
+
{ type: FX_PREFIX, value: '=' },
|
|
1788
|
+
{ type: UNKNOWN, value: 'c' },
|
|
1789
|
+
{ type: OPERATOR, value: '*' },
|
|
1790
|
+
{ type: OPERATOR, value: '(' },
|
|
1791
|
+
{ type: FUNCTION, value: 'LAMBDA' },
|
|
1792
|
+
{ type: OPERATOR, value: '(' },
|
|
1793
|
+
{ type: REF_NAMED, value: 'r' },
|
|
1794
|
+
{ type: OPERATOR, value: ',' },
|
|
1795
|
+
{ type: REF_NAMED, value: 'c' },
|
|
1796
|
+
{ type: OPERATOR, value: ',' },
|
|
1797
|
+
{ type: REF_NAMED, value: 'r' },
|
|
1798
|
+
{ type: OPERATOR, value: '*' },
|
|
1799
|
+
{ type: REF_NAMED, value: 'c' },
|
|
1800
|
+
{ type: OPERATOR, value: ')' },
|
|
1801
|
+
{ type: OPERATOR, value: '+' },
|
|
1802
|
+
{ type: UNKNOWN, value: 'r' },
|
|
1803
|
+
{ type: OPERATOR, value: ')' },
|
|
1804
|
+
{ type: OPERATOR, value: '+' },
|
|
1805
|
+
{ type: UNKNOWN, value: 'r' }
|
|
1806
|
+
]);
|
|
1807
|
+
t.isTokens('=c*(LET(r,A1,c,B2,r*c)+r)+r', [
|
|
1808
|
+
{ type: FX_PREFIX, value: '=' },
|
|
1809
|
+
{ type: UNKNOWN, value: 'c' },
|
|
1810
|
+
{ type: OPERATOR, value: '*' },
|
|
1811
|
+
{ type: OPERATOR, value: '(' },
|
|
1812
|
+
{ type: FUNCTION, value: 'LET' },
|
|
1813
|
+
{ type: OPERATOR, value: '(' },
|
|
1814
|
+
{ type: REF_NAMED, value: 'r' },
|
|
1815
|
+
{ type: OPERATOR, value: ',' },
|
|
1816
|
+
{ type: REF_RANGE, value: 'A1' },
|
|
1817
|
+
{ type: OPERATOR, value: ',' },
|
|
1818
|
+
{ type: REF_NAMED, value: 'c' },
|
|
1819
|
+
{ type: OPERATOR, value: ',' },
|
|
1820
|
+
{ type: REF_RANGE, value: 'B2' },
|
|
1821
|
+
{ type: OPERATOR, value: ',' },
|
|
1822
|
+
{ type: REF_NAMED, value: 'r' },
|
|
1823
|
+
{ type: OPERATOR, value: '*' },
|
|
1824
|
+
{ type: REF_NAMED, value: 'c' },
|
|
1825
|
+
{ type: OPERATOR, value: ')' },
|
|
1826
|
+
{ type: OPERATOR, value: '+' },
|
|
1827
|
+
{ type: UNKNOWN, value: 'r' },
|
|
1828
|
+
{ type: OPERATOR, value: ')' },
|
|
1829
|
+
{ type: OPERATOR, value: '+' },
|
|
1830
|
+
{ type: UNKNOWN, value: 'r' }
|
|
1831
|
+
]);
|
|
1832
|
+
|
|
1833
|
+
t.end();
|
|
1834
|
+
});
|
package/lib/lexerParts.js
CHANGED
|
@@ -67,6 +67,11 @@ function lexNamed (str) {
|
|
|
67
67
|
const m = re_NAMED.exec(str);
|
|
68
68
|
if (m) {
|
|
69
69
|
const lc = m[0].toLowerCase();
|
|
70
|
+
// names starting with \ must be at least 3 char long
|
|
71
|
+
if (lc[0] === '\\' && m[0].length < 3) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// single characters R and C are forbidden as names
|
|
70
75
|
if (lc === 'r' || lc === 'c') {
|
|
71
76
|
return null;
|
|
72
77
|
}
|