@borgar/fx 4.4.0 → 4.6.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/lib/a1.js CHANGED
@@ -5,6 +5,7 @@ import { stringifyPrefix, stringifyPrefixAlt } from './stringifyPrefix.js';
5
5
  const clamp = (min, val, max) => Math.min(Math.max(val, min), max);
6
6
  const toColStr = (c, a) => (a ? '$' : '') + toCol(c);
7
7
  const toRowStr = (r, a) => (a ? '$' : '') + toRow(r);
8
+ const charFrom = String.fromCharCode;
8
9
 
9
10
  /**
10
11
  * Convert a column string representation to a 0 based
@@ -15,7 +16,7 @@ const toRowStr = (r, a) => (a ? '$' : '') + toRow(r);
15
16
  * return garbage.
16
17
  *
17
18
  * @param {string} columnString The column string identifier
18
- * @return {number} Zero based column index number
19
+ * @returns {number} Zero based column index number
19
20
  */
20
21
  export function fromCol (columnString) {
21
22
  const x = (columnString || '');
@@ -47,13 +48,17 @@ export function fromCol (columnString) {
47
48
  * return garbage.
48
49
  *
49
50
  * @param {number} columnIndex Zero based column index number
50
- * @return {string} The column string identifier
51
+ * @returns {string} The column string identifier
51
52
  */
52
53
  export function toCol (columnIndex) {
53
54
  return (
54
- (columnIndex >= 702 ? String.fromCharCode((((columnIndex - 702) / 676) - 0) % 26 + 65) : '') +
55
- (columnIndex >= 26 ? String.fromCharCode(Math.floor(((columnIndex / 26) - 1) % 26 + 65)) : '') +
56
- String.fromCharCode((columnIndex % 26 + 65))
55
+ (columnIndex >= 702
56
+ ? charFrom(((((columnIndex - 702) / 676) - 0) % 26) + 65)
57
+ : '') +
58
+ (columnIndex >= 26
59
+ ? charFrom(((((columnIndex / 26) - 1) % 26) + 65))
60
+ : '') +
61
+ charFrom((columnIndex % 26) + 65)
57
62
  );
58
63
  }
59
64
 
@@ -80,8 +85,8 @@ export function toAbsolute (range) {
80
85
  *
81
86
  * @private
82
87
  * @see parseA1Ref
83
- * @param {Object} range A range object
84
- * @return {string} An A1-style string represenation of a range
88
+ * @param {RangeA1} range A range object
89
+ * @returns {string} An A1-style string represenation of a range
85
90
  */
86
91
  export function toA1 (range) {
87
92
  let { top, left, bottom, right } = range;
@@ -102,27 +107,31 @@ export function toA1 (range) {
102
107
  right = clamp(0, right | 0, MAX_COLS);
103
108
  }
104
109
  // A:A
105
- if ((top === 0 && bottom >= MAX_ROWS) || (noTop && noBottom)) {
110
+ const allRows = top === 0 && bottom >= MAX_ROWS;
111
+ const haveAbsCol = ($left && !noLeft) || ($right && !noRight);
112
+ if ((allRows && !noLeft && !noRight && (!haveAbsCol || left === right)) || (noTop && noBottom)) {
106
113
  return toColStr(left, $left) + ':' + toColStr(right, $right);
107
114
  }
108
115
  // 1:1
109
- else if ((left === 0 && right >= MAX_COLS) || (noLeft && noRight)) {
116
+ const allCols = left === 0 && right >= MAX_COLS;
117
+ const haveAbsRow = ($top && !noTop) || ($bottom && !noBottom);
118
+ if ((allCols && !noTop && !noBottom && (!haveAbsRow || top === bottom)) || (noLeft && noRight)) {
110
119
  return toRowStr(top, $top) + ':' + toRowStr(bottom, $bottom);
111
120
  }
112
121
  // A1:1
113
- else if (!noLeft && !noTop && !noRight && noBottom) {
122
+ if (!noLeft && !noTop && !noRight && noBottom) {
114
123
  return toColStr(left, $left) + toRowStr(top, $top) + ':' + toColStr(right, $right);
115
124
  }
116
125
  // A:A1 => A1:1
117
- else if (!noLeft && noTop && !noRight && !noBottom) {
126
+ if (!noLeft && noTop && !noRight && !noBottom) {
118
127
  return toColStr(left, $left) + toRowStr(bottom, $bottom) + ':' + toColStr(right, $right);
119
128
  }
120
129
  // A1:A
121
- else if (!noLeft && !noTop && noRight && !noBottom) {
130
+ if (!noLeft && !noTop && noRight && !noBottom) {
122
131
  return toColStr(left, $left) + toRowStr(top, $top) + ':' + toRowStr(bottom, $bottom);
123
132
  }
124
133
  // A:A1 => A1:A
125
- else if (noLeft && !noTop && !noRight && !noBottom) {
134
+ if (noLeft && !noTop && !noRight && !noBottom) {
126
135
  return toColStr(right, $right) + toRowStr(top, $top) + ':' + toRowStr(bottom, $bottom);
127
136
  }
128
137
  // A1:A1
@@ -154,9 +163,9 @@ function splitA1 (str) {
154
163
  * @private
155
164
  * @see parseA1Ref
156
165
  * @param {string} rangeString A range string
157
- * @return {(Object|null)} An object representing a valid reference or null if it is invalid.
166
+ * @returns {(RangeA1|null)} An object representing a valid range or null if it is invalid.
158
167
  */
159
- export function fromA1 (rangeStr) {
168
+ export function fromA1 (rangeString) {
160
169
  let top = null;
161
170
  let left = null;
162
171
  let bottom = null;
@@ -165,7 +174,7 @@ export function fromA1 (rangeStr) {
165
174
  let $left = false;
166
175
  let $bottom = false;
167
176
  let $right = false;
168
- const [ part1, part2, part3 ] = rangeStr.split(':');
177
+ const [ part1, part2, part3 ] = rangeString.split(':');
169
178
  if (part3) {
170
179
  return null;
171
180
  }
@@ -241,11 +250,11 @@ export function fromA1 (rangeStr) {
241
250
  * syntax does not specify:
242
251
  *
243
252
  * @param {string} refString An A1-style reference string
244
- * @param {Object} [options={}] Options
253
+ * @param {object} [options={}] Options
245
254
  * @param {boolean} [options.allowNamed=true] Enable parsing names as well as ranges.
246
255
  * @param {boolean} [options.allowTernary=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.
247
256
  * @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)
248
- * @return {(Object|null)} An object representing a valid reference or null if it is invalid.
257
+ * @returns {(ReferenceA1|null)} An object representing a valid reference or null if it is invalid.
249
258
  */
250
259
  export function parseA1Ref (refString, { allowNamed = true, allowTernary = false, xlsx = false } = {}) {
251
260
  const d = parseRef(refString, { allowNamed, allowTernary, xlsx, r1c1: false });
@@ -287,10 +296,10 @@ export function parseA1Ref (refString, { allowNamed = true, allowTernary = false
287
296
  * // => 'Sheet1!A$1:$B2'
288
297
  * ```
289
298
  *
290
- * @param {Object} refObject A reference object
291
- * @param {Object} [options={}] Options
299
+ * @param {ReferenceA1} refObject A reference object
300
+ * @param {object} [options={}] Options
292
301
  * @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)
293
- * @return {Object} The reference in A1-style string format
302
+ * @returns {string} The reference in A1-style string format
294
303
  */
295
304
  export function stringifyA1Ref (refObject, { xlsx = false } = {}) {
296
305
  const prefix = xlsx
@@ -332,8 +341,8 @@ export function stringifyA1Ref (refObject, { xlsx = false } = {}) {
332
341
  * // }
333
342
  * ```
334
343
  *
335
- * @param {Object} range The range part of a reference object.
336
- * @return {Object} same range with missing bounds filled in.
344
+ * @param {RangeA1} range The range part of a reference object.
345
+ * @returns {RangeA1} same range with missing bounds filled in.
337
346
  */
338
347
  export function addA1RangeBounds (range) {
339
348
  if (range.top == null) {
@@ -121,13 +121,13 @@ function addContext (ref, sheetName, workbookName) {
121
121
  *
122
122
  * All will be tagged with `.error` (boolean `true`).
123
123
  *
124
- * @param {Array<Object>} tokenlist An array of tokens (from `tokenize()`)
125
- * @param {Object} [context={}] A contest used to match `A1` to `Sheet1!A1`.
124
+ * @param {Array<Token>} tokenlist An array of tokens (from `tokenize()`)
125
+ * @param {object} [context={}] A contest used to match `A1` to `Sheet1!A1`.
126
126
  * @param {string} [context.sheetName=''] An implied sheet name ('Sheet1')
127
127
  * @param {string} [context.workbookName=''] An implied workbook name ('report.xlsx')
128
- * @return {Array<Object>} The input array with the enchanced tokens
128
+ * @returns {Array<TokenEnhanced>} The input array with the enchanced tokens
129
129
  */
130
- export function addTokenMeta (tokens, { sheetName = '', workbookName = '' } = {}) {
130
+ export function addTokenMeta (tokenlist, { sheetName = '', workbookName = '' } = {}) {
131
131
  const parenStack = [];
132
132
  let arrayStart = null;
133
133
  const uid = getIDer();
@@ -135,7 +135,7 @@ export function addTokenMeta (tokens, { sheetName = '', workbookName = '' } = {}
135
135
 
136
136
  const getCurrDepth = () => parenStack.length + (arrayStart ? 1 : 0);
137
137
 
138
- tokens.forEach((token, i) => {
138
+ tokenlist.forEach((token, i) => {
139
139
  token.index = i;
140
140
  token.depth = getCurrDepth();
141
141
  if (token.value === '(') {
@@ -202,5 +202,5 @@ export function addTokenMeta (tokens, { sheetName = '', workbookName = '' } = {}
202
202
  token.error = true;
203
203
  }
204
204
  });
205
- return tokens;
205
+ return tokenlist;
206
206
  }
package/lib/constants.js CHANGED
@@ -25,5 +25,5 @@ export const CALL = 'CallExpression';
25
25
  export const ARRAY = 'ArrayExpression';
26
26
  export const IDENTIFIER = 'Identifier';
27
27
 
28
- export const MAX_COLS = 2 ** 14 - 1; // 16383
29
- export const MAX_ROWS = 2 ** 20 - 1; // 1048575
28
+ export const MAX_COLS = (2 ** 14) - 1; // 16383
29
+ export const MAX_ROWS = (2 ** 20) - 1; // 1048575
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @typedef {object} Token A formula language token.
3
+ * @augments Record<string,any>
4
+ * @property {string} type The type of the token
5
+ * @property {string} value The value of the token
6
+ * @property {boolean} [unterminated] Signifies an unterminated string token
7
+ * @property {Array<number>} [loc] Source position offsets to the token
8
+ */
9
+
10
+ /**
11
+ * @typedef {object} TokenEnhanced A token with extra meta data.
12
+ * @augments Token
13
+ * @property {number} index A zero based position in a token list
14
+ * @property {string} [groupId] The ID of a group which this token belongs (e.g. matching parens)
15
+ * @property {number} [depth] This token's level of nesting inside parentheses
16
+ * @property {boolean} [error] Token is of unknown type or a paren without a match
17
+ */
18
+
19
+ /**
20
+ * @typedef {Record<string, any>} RangeR1C1 A range in R1C1 style coordinates.
21
+ * @property {number | null} [r0] Top row of the range
22
+ * @property {number | null} [r1] Bottom row of the range
23
+ * @property {number | null} [c0] Left column of the range
24
+ * @property {number | null} [c1] Right column of the range
25
+ * @property {boolean | null} [$r0] Signifies that r0 is an absolute value
26
+ * @property {boolean | null} [$r1] Signifies that r1 is an absolute value
27
+ * @property {boolean | null} [$c0] Signifies that c0 is an absolute value
28
+ * @property {boolean | null} [$c1] Signifies that c1 is an absolute value
29
+ */
30
+
31
+ /**
32
+ * @typedef {Record<string, any>} RangeA1 A range in A1 style coordinates.
33
+ * @property {number | null} [top] Top row of the range
34
+ * @property {number | null} [bottom] Bottom row of the range
35
+ * @property {number | null} [left] Left column of the range
36
+ * @property {number | null} [right] Right column of the range
37
+ * @property {boolean | null} [$top] Signifies that top is a "locked" value
38
+ * @property {boolean | null} [$bottom] Signifies that bottom is a "locked" value
39
+ * @property {boolean | null} [$left] Signifies that left is a "locked" value
40
+ * @property {boolean | null} [$right] Signifies that right is a "locked" value
41
+ */
42
+
43
+ /**
44
+ * @typedef {Record<string, any>} ReferenceA1
45
+ * A reference containing an A1 style range. See [Prefixes.md] for
46
+ * documentation on how scopes work in Fx.
47
+ * @property {Array<string>} [context] A collection of scopes for the reference
48
+ * @property {string} [workbookName] A context workbook scope
49
+ * @property {string} [sheetName] A context sheet scope
50
+ * @property {RangeA1} [range] The reference's range
51
+ */
52
+
53
+ /**
54
+ * @typedef {Record<string, any>} ReferenceR1C1
55
+ * A reference containing a R1C1 style range. See [Prefixes.md] for
56
+ * documentation on how scopes work in Fx.
57
+ * @property {Array<string>} [context] A collection of scopes for the reference
58
+ * @property {string} [workbookName] A context workbook scope
59
+ * @property {string} [sheetName] A context sheet scope
60
+ * @property {RangeR1C1} [range] The reference's range
61
+ */
62
+
63
+ /**
64
+ * @typedef {Record<string, any>} ReferenceStruct
65
+ * A reference containing a table slice definition. See [Prefixes.md] for
66
+ * documentation on how scopes work in Fx.
67
+ * @property {Array<string>} [context] A collection of scopes for the reference
68
+ * @property {string} [workbookName] A context workbook scope
69
+ * @property {string} [sheetName] A context sheet scope
70
+ * @property {Array<string>} [sections] The sections this reference targets
71
+ * @property {Array<string>} [columns] The sections this reference targets
72
+ * @property {string} [table] The table this reference targets
73
+ */
package/lib/fixRanges.js CHANGED
@@ -40,19 +40,19 @@ import { REF_STRUCT } from './constants.js';
40
40
  * Returns the same formula with the ranges updated. If an array of tokens was
41
41
  * supplied, then a new array is returned.
42
42
  *
43
- * @param {(string | Array<Object>)} formula A string (an Excel formula) or a token list that should be adjusted.
44
- * @param {Object} [options={}] Options
43
+ * @param {(string | Array<Token>)} formula A string (an Excel formula) or a token list that should be adjusted.
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
- * @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
47
+ * @returns {(string | Array<Token>)} A formula string or token list (depending on which was input)
48
48
  */
49
- export function fixRanges (tokens, options = { addBounds: false }) {
50
- if (typeof tokens === 'string') {
51
- return fixRanges(tokenize(tokens, options), options)
49
+ export function fixRanges (formula, options = { addBounds: false }) {
50
+ if (typeof formula === 'string') {
51
+ return fixRanges(tokenize(formula, options), options)
52
52
  .map(d => d.value)
53
53
  .join('');
54
54
  }
55
- if (!Array.isArray(tokens)) {
55
+ if (!Array.isArray(formula)) {
56
56
  throw new Error('fixRanges expects an array of tokens');
57
57
  }
58
58
  const { addBounds, r1c1, xlsx } = options;
@@ -60,7 +60,7 @@ export function fixRanges (tokens, options = { addBounds: false }) {
60
60
  throw new Error('fixRanges does not have an R1C1 mode');
61
61
  }
62
62
  let offsetSkew = 0;
63
- return tokens.map(t => {
63
+ return formula.map(t => {
64
64
  const token = { ...t };
65
65
  if (t.loc) {
66
66
  token.loc = [ ...t.loc ];
package/lib/isType.js CHANGED
@@ -12,8 +12,8 @@ import {
12
12
  * (`A1` or `A1:B2`), REF_TERNARY (`A1:A`, `A1:1`, `1:A1`, or `A:A1`), or
13
13
  * REF_BEAM (`A:A` or `1:1`). In all other cases `false` is returned.
14
14
  *
15
- * @param {Object} token A token
16
- * @return {boolean} True if the specified token is range, False otherwise.
15
+ * @param {any} token A token
16
+ * @returns {boolean} True if the specified token is range, False otherwise.
17
17
  */
18
18
  export function isRange (token) {
19
19
  return !!token && (
@@ -30,8 +30,8 @@ export function isRange (token) {
30
30
  * REF_TERNARY (`A1:A`, `A1:1`, `1:A1`, or `A:A1`), REF_BEAM (`A:A` or `1:1`),
31
31
  * or REF_NAMED (`myrange`). In all other cases `false` is returned.
32
32
  *
33
- * @param {Object} token The token
34
- * @return {boolean} True if the specified token is reference, False otherwise.
33
+ * @param {any} token The token
34
+ * @returns {boolean} True if the specified token is reference, False otherwise.
35
35
  */
36
36
  export function isReference (token) {
37
37
  return !!token && (
@@ -50,8 +50,8 @@ export function isReference (token) {
50
50
  * ERROR (`#VALUE!`), NUMBER (123.4), or STRING (`"lorem ipsum"`). In all other
51
51
  * cases `false` is returned.
52
52
  *
53
- * @param {Object} token The token
54
- * @return {boolean} True if the specified token is literal, False otherwise.
53
+ * @param {any} token The token
54
+ * @returns {boolean} True if the specified token is literal, False otherwise.
55
55
  */
56
56
  export function isLiteral (token) {
57
57
  return !!token && (
@@ -68,8 +68,8 @@ export function isLiteral (token) {
68
68
  * Returns `true` if the input is a token of type ERROR (`#VALUE!`). In all
69
69
  * other cases `false` is returned.
70
70
  *
71
- * @param {Object} token The token
72
- * @return {boolean} True if the specified token is error, False otherwise.
71
+ * @param {any} token The token
72
+ * @returns {boolean} True if the specified token is error, False otherwise.
73
73
  */
74
74
  export function isError (token) {
75
75
  return !!token && token.type === ERROR;
@@ -81,8 +81,8 @@ export function isError (token) {
81
81
  * Returns `true` if the input is a token of type WHITESPACE (` `) or
82
82
  * NEWLINE (`\n`). In all other cases `false` is returned.
83
83
  *
84
- * @param {Object} token The token
85
- * @return {boolean} True if the specified token is whitespace, False otherwise.
84
+ * @param {any} token The token
85
+ * @returns {boolean} True if the specified token is whitespace, False otherwise.
86
86
  */
87
87
  export function isWhitespace (token) {
88
88
  return !!token && (
@@ -97,8 +97,8 @@ export function isWhitespace (token) {
97
97
  * Returns `true` if the input is a token of type FUNCTION.
98
98
  * In all other cases `false` is returned.
99
99
  *
100
- * @param {Object} token The token
101
- * @return {boolean} True if the specified token is function, False otherwise.
100
+ * @param {any} token The token
101
+ * @returns {boolean} True if the specified token is function, False otherwise.
102
102
  */
103
103
  export function isFunction (token) {
104
104
  return !!token && token.type === FUNCTION;
@@ -108,8 +108,8 @@ export function isFunction (token) {
108
108
  * Returns `true` if the input is a token of type FX_PREFIX (leading `=` in
109
109
  * formula). In all other cases `false` is returned.
110
110
  *
111
- * @param {Object} token The token
112
- * @return {boolean} True if the specified token is effects prefix, False otherwise.
111
+ * @param {any} token The token
112
+ * @returns {boolean} True if the specified token is effects prefix, False otherwise.
113
113
  */
114
114
  export function isFxPrefix (token) {
115
115
  return !!token && token.type === FX_PREFIX;
@@ -121,8 +121,8 @@ export function isFxPrefix (token) {
121
121
  * Returns `true` if the input is a token of type OPERATOR (`+` or `:`). In all
122
122
  * other cases `false` is returned.
123
123
  *
124
- * @param {Object} token The token
125
- * @return {boolean} True if the specified token is operator, False otherwise.
124
+ * @param {any} token The token
125
+ * @returns {boolean} True if the specified token is operator, False otherwise.
126
126
  */
127
127
  export function isOperator (token) {
128
128
  return !!token && token.type === OPERATOR;
@@ -315,5 +315,10 @@ test('tokenize structured references (merges on)', t => {
315
315
  { type: FX_PREFIX, value: '=' },
316
316
  { type: REF_STRUCT, value: '\'Sales - May2020.xlsx\'!Table1[ [#Data], [#Totals], [Surf]:[Rod] ]' }
317
317
  ]);
318
+
319
+ t.isTokens('=[myworkbook.xlsx]Sheet1!TMP8w0habhr[#All]', [
320
+ { type: FX_PREFIX, value: '=' },
321
+ { type: REF_STRUCT, value: '[myworkbook.xlsx]Sheet1!TMP8w0habhr[#All]' }
322
+ ]);
318
323
  t.end();
319
324
  });
package/lib/lexer.js CHANGED
@@ -196,14 +196,14 @@ export function getTokens (fx, tokenHandlers, options = {}) {
196
196
  *
197
197
  * @see tokenTypes
198
198
  * @param {string} formula An Excel formula string (an Excel expression) or an array of tokens.
199
- * @param {Object} [options={}] Options
199
+ * @param {object} [options={}] Options
200
200
  * @param {boolean} [options.allowTernary=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.
201
201
  * @param {boolean} [options.negativeNumbers=true] Merges unary minuses with their immediately following number tokens (`-`,`1`) => `-1` (alternatively these will be unary operations in the tree).
202
202
  * @param {boolean} [options.r1c1=false] Ranges are expected to be in the R1C1 style format rather than the more popular A1 style.
203
203
  * @param {boolean} [options.withLocation=true] Nodes will include source position offsets to the tokens: `{ loc: [ start, end ] }`
204
204
  * @param {boolean} [options.mergeRefs=true] Should ranges be returned as whole references (`Sheet1!A1:B2`) or as separate tokens for each part: (`Sheet1`,`!`,`A1`,`:`,`B2`). This is the same as calling [`mergeRefTokens`](#mergeRefTokens)
205
205
  * @param {boolean} [options.xlsx=false] Enables a `[1]Sheet1!A1` or `[1]!name` syntax form for external workbooks found only in XLSX files.
206
- * @return {Array<Object>} An AST of nodes
206
+ * @returns {Array<Token>} An AST of nodes
207
207
  */
208
208
  export function tokenize (formula, options = {}) {
209
209
  return getTokens(formula, lexers, options);
package/lib/lexerParts.js CHANGED
@@ -34,16 +34,17 @@ const re_CONTEXT_QUOTE = /^'(?:''|[^'])*('|$)(?=!)/;
34
34
  const rngPart = '\\$?[A-Z]{1,3}\\$?[1-9][0-9]{0,6}';
35
35
  const colPart = '\\$?[A-Z]{1,3}';
36
36
  const rowPart = '\\$?[1-9][0-9]{0,6}';
37
- const re_A1COL = new RegExp(`^${colPart}:${colPart}`, 'i');
38
- const re_A1ROW = new RegExp(`^${rowPart}:${rowPart}`, 'i');
39
- const re_A1RANGE = new RegExp(`^${rngPart}`, 'i');
37
+ const nextNotChar = '(?![a-z0-9_\\u00a1-\\uffff])';
38
+ const re_A1COL = new RegExp(`^${colPart}:${colPart}${nextNotChar}`, 'i');
39
+ const re_A1ROW = new RegExp(`^${rowPart}:${rowPart}${nextNotChar}`, 'i');
40
+ const re_A1RANGE = new RegExp(`^${rngPart}${nextNotChar}`, 'i');
40
41
  const re_A1PARTIAL = new RegExp(`^((${colPart}|${rowPart}):${rngPart}|${rngPart}:(${colPart}|${rowPart}))(?![\\w($.])`, 'i');
41
42
  const rPart = '(?:R(?:\\[[+-]?\\d+\\]|[1-9][0-9]{0,6})?)';
42
43
  const cPart = '(?:C(?:\\[[+-]?\\d+\\]|[1-9][0-9]{0,4})?)';
43
- const re_RCCOL = new RegExp(`^${cPart}(:${cPart})?(?=\\W|$)`, 'i');
44
- const re_RCROW = new RegExp(`^${rPart}(:${rPart})?(?=\\W|$)`, 'i');
45
- const re_RCRANGE = new RegExp(`^(?:(?=[RC])${rPart}${cPart})`, 'i');
46
- const re_RCPARTIAL = new RegExp(`^(${rPart}${cPart}(:${cPart}|:${rPart})(?![[\\d])|(${rPart}|${cPart})(:${rPart}${cPart}))(?=\\W|$)`, 'i');
44
+ const re_RCCOL = new RegExp(`^${cPart}(:${cPart})?${nextNotChar}`, 'i');
45
+ const re_RCROW = new RegExp(`^${rPart}(:${rPart})?${nextNotChar}`, 'i');
46
+ const re_RCRANGE = new RegExp(`^(?:(?=[RC])${rPart}${cPart})${nextNotChar}`, 'i');
47
+ const re_RCPARTIAL = new RegExp(`^(${rPart}${cPart}(:${cPart}|:${rPart})(?![[\\d])|(${rPart}|${cPart})(:${rPart}${cPart}))${nextNotChar}`, 'i');
47
48
 
48
49
  // The advertized named ranges rules are a bit off from what Excel seems to do:
49
50
  // in the "extended range" of chars, it looks like it allows most things above
@@ -51,7 +52,7 @@ const re_RCPARTIAL = new RegExp(`^(${rPart}${cPart}(:${cPart}|:${rPart})(?![[\\d
51
52
  // eslint-disable-next-line
52
53
  // const re_NAMED = /^[a-zA-Z\\_¡¤§¨ª\u00ad¯\u00b0-\uffff][a-zA-Z0-9\\_.?¡¤§¨ª\u00ad¯\u00b0-\uffff]{0,254}/i;
53
54
  // I've simplified to allowing everything above U+00A1:
54
- const re_NAMED = /^(?![CR]\b)[a-zA-Z\\_\u00a1-\uffff][a-zA-Z0-9\\_.?\u00a1-\uffff]{0,254}/i;
55
+ const re_NAMED = /^[a-zA-Z\\_\u00a1-\uffff][a-zA-Z0-9\\_.?\u00a1-\uffff]{0,254}/i;
55
56
 
56
57
  function makeHandler (type, re) {
57
58
  return str => {
@@ -62,6 +63,17 @@ function makeHandler (type, re) {
62
63
  };
63
64
  }
64
65
 
66
+ function lexNamed (str) {
67
+ const m = re_NAMED.exec(str);
68
+ if (m) {
69
+ const lc = m[0].toLowerCase();
70
+ if (lc === 'r' || lc === 'c') {
71
+ return null;
72
+ }
73
+ return { type: REF_NAMED, value: m[0] };
74
+ }
75
+ }
76
+
65
77
  const re_QUOTED_VALUE = /^'(?:[^[\]]+?)?(?:\[(.+?)\])?(?:[^[\]]+?)'$/;
66
78
  const re_QUOTED_VALUE_XLSX = /^'\[(.+?)\]'$/;
67
79
  function lexContext (str, options) {
@@ -130,9 +142,9 @@ function lexRange (str, options) {
130
142
  if (t) {
131
143
  reRCNums.lastIndex = 0;
132
144
  while ((m = reRCNums.exec(t.value)) !== null) {
133
- const x = (m[1] === 'R' ? MAX_ROWS : MAX_COLS) + (m[2] ? 1 : 0);
145
+ const x = (m[1] === 'R' ? MAX_ROWS : MAX_COLS) + (m[2] ? 0 : 1);
134
146
  const val = parseInt(m[3], 10);
135
- if (val >= x || val <= -x) {
147
+ if (val > x || val < -x) {
136
148
  return null;
137
149
  }
138
150
  }
@@ -192,7 +204,7 @@ export const lexers = [
192
204
  lexRange,
193
205
  lexStructured,
194
206
  makeHandler(NUMBER, re_NUMBER),
195
- makeHandler(REF_NAMED, re_NAMED)
207
+ lexNamed
196
208
  ];
197
209
 
198
210
  export const lexersRefs = [
@@ -200,5 +212,5 @@ export const lexersRefs = [
200
212
  lexContext,
201
213
  lexRange,
202
214
  lexStructured,
203
- makeHandler(REF_NAMED, re_NAMED)
215
+ lexNamed
204
216
  ];
@@ -63,8 +63,8 @@ const matcher = (tokens, currNode, anchorIndex, index = 0) => {
63
63
  * as whole references (`Sheet1!A1:B2`) rather than separate tokens for each
64
64
  * part: (`Sheet1`,`!`,`A1`,`:`,`B2`).
65
65
  *
66
- * @param {Array<Object>} tokenlist An array of tokens (from `tokenize()`)
67
- * @return {Array} A new list of tokens with range parts merged.
66
+ * @param {Array<Token>} tokenlist An array of tokens (from `tokenize()`)
67
+ * @returns {Array<Token>} A new list of tokens with range parts merged.
68
68
  */
69
69
  export function mergeRefTokens (tokenlist) {
70
70
  const finalTokens = [];
@@ -21,40 +21,51 @@ test('splitPrefix', t => {
21
21
  testStr('[foo][bar][baz]', false, [
22
22
  { value: 'foo', braced: true },
23
23
  { value: 'bar', braced: true },
24
- { value: 'baz', braced: true } ]);
24
+ { value: 'baz', braced: true }
25
+ ]);
25
26
  testStr('foo[bar][baz]', false, [
26
27
  { value: 'foo', braced: false },
27
28
  { value: 'bar', braced: true },
28
- { value: 'baz', braced: true } ]);
29
+ { value: 'baz', braced: true }
30
+ ]);
29
31
  testStr('[foo]bar[baz]', false, [
30
32
  { value: 'foo', braced: true },
31
33
  { value: 'bar', braced: false },
32
- { value: 'baz', braced: true } ]);
34
+ { value: 'baz', braced: true }
35
+ ]);
33
36
  testStr('[foo][bar]baz', false, [
34
37
  { value: 'foo', braced: true },
35
38
  { value: 'bar', braced: true },
36
- { value: 'baz', braced: false } ]);
39
+ { value: 'baz', braced: false }
40
+ ]);
37
41
  testStr('foo[bar]baz', false, [
38
42
  { value: 'foo', braced: false },
39
43
  { value: 'bar', braced: true },
40
- { value: 'baz', braced: false } ]);
44
+ { value: 'baz', braced: false }
45
+ ]);
41
46
  testStr('[foo]bar[baz]', false, [
42
47
  { value: 'foo', braced: true },
43
48
  { value: 'bar', braced: false },
44
- { value: 'baz', braced: true } ]);
49
+ { value: 'baz', braced: true }
50
+ ]);
45
51
  testStr('[foo]bar', false, [
46
52
  { value: 'foo', braced: true },
47
- { value: 'bar', braced: false } ]);
53
+ { value: 'bar', braced: false }
54
+ ]);
48
55
  testStr('foo[bar]', false, [
49
56
  { value: 'foo', braced: false },
50
- { value: 'bar', braced: true } ]);
57
+ { value: 'bar', braced: true }
58
+ ]);
51
59
  testStr('[foo][bar]', false, [
52
60
  { value: 'foo', braced: true },
53
- { value: 'bar', braced: true } ]);
61
+ { value: 'bar', braced: true }
62
+ ]);
54
63
  testStr('[foo]', false, [
55
- { value: 'foo', braced: true } ]);
64
+ { value: 'foo', braced: true }
65
+ ]);
56
66
  testStr('foo', false, [
57
- { value: 'foo', braced: false } ]);
67
+ { value: 'foo', braced: false }
68
+ ]);
58
69
 
59
70
  t.end();
60
71
  });
package/lib/parser.js CHANGED
@@ -478,8 +478,8 @@ prefix('{', function () {
478
478
  * [AST_format.md](./AST_format.md)
479
479
  *
480
480
  * @see nodeTypes
481
- * @param {(string | Array<Object>)} formula An Excel formula string (an Excel expression) or an array of tokens.
482
- * @param {Object} [options={}] Options
481
+ * @param {(string | Array<Token>)} formula An Excel formula string (an Excel expression) or an array of tokens.
482
+ * @param {object} [options={}] Options
483
483
  * @param {boolean} [options.allowNamed=true] Enable parsing names as well as ranges.
484
484
  * @param {boolean} [options.allowTernary=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.
485
485
  * @param {boolean} [options.negativeNumbers=true] Merges unary minuses with their immediately following number tokens (`-`,`1`) => `-1` (alternatively these will be unary operations in the tree).
@@ -488,18 +488,18 @@ prefix('{', function () {
488
488
  * @param {boolean} [options.r1c1=false] Ranges are expected to be in the R1C1 style format rather than the more popular A1 style.
489
489
  * @param {boolean} [options.withLocation=false] Nodes will include source position offsets to the tokens: `{ loc: [ start, end ] }`
490
490
  * @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)
491
- * @return {Object} An AST of nodes
491
+ * @returns {object} An AST of nodes
492
492
  */
493
- export function parse (source, options) {
494
- if (typeof source === 'string') {
495
- tokens = tokenize(source, {
493
+ export function parse (formula, options) {
494
+ if (typeof formula === 'string') {
495
+ tokens = tokenize(formula, {
496
496
  withLocation: false,
497
497
  ...options,
498
498
  mergeRefs: true
499
499
  });
500
500
  }
501
- else if (Array.isArray(source)) {
502
- tokens = source;
501
+ else if (Array.isArray(formula)) {
502
+ tokens = formula;
503
503
  }
504
504
  else {
505
505
  throw new Error('Parse requires a string or array of tokens.');