@borgar/fx 3.1.0 → 4.0.0-rc.2

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/lexerParts.js CHANGED
@@ -9,14 +9,16 @@ import {
9
9
  STRING,
10
10
  CONTEXT,
11
11
  CONTEXT_QUOTE,
12
- RANGE,
13
- RANGE_BEAM,
14
- RANGE_NAMED,
15
- RANGE_TERNARY,
12
+ REF_RANGE,
13
+ REF_BEAM,
14
+ REF_NAMED,
15
+ REF_TERNARY,
16
+ REF_STRUCT,
16
17
  MAX_COLS,
17
18
  MAX_ROWS
18
19
  } from './constants.js';
19
20
  import { fromCol } from './a1.js';
21
+ import { parseSRange } from './sr.js';
20
22
 
21
23
  const re_ERROR = /^#(NAME\?|FIELD!|CALC!|VALUE!|REF!|DIV\/0!|NULL!|NUM!|N\/A|GETTING_DATA\b|SPILL!|UNKNOWN!|FIELD\b|CALC\b|SYNTAX\?|ERROR!|CONNECT!|BLOCKED!|EXTERNAL!)/i;
22
24
  const re_OPERATOR = /^(<=|>=|<>|[-+/*^%&<>=]|[{},;]|[()]|@|:|!|#)/;
@@ -50,7 +52,7 @@ const re_RCPARTIAL = new RegExp(`^(${rPart}${cPart}(:${cPart}|:${rPart})(?![[\\d
50
52
  // eslint-disable-next-line
51
53
  // const re_NAMED = /^[a-zA-Z\\_¡¤§¨ª\u00ad¯\u00b0-\uffff][a-zA-Z0-9\\_.?¡¤§¨ª\u00ad¯\u00b0-\uffff]{0,254}/i;
52
54
  // I've simplified to allowing everything above U+00A1:
53
- const re_NAMED = /^[a-zA-Z\\_\u00a1-\uffff][a-zA-Z0-9\\_.?\u00a1-\uffff]{0,254}/i;
55
+ const re_NAMED = /^(?![CR]\b)[a-zA-Z\\_\u00a1-\uffff][a-zA-Z0-9\\_.?\u00a1-\uffff]{0,254}/i;
54
56
 
55
57
  function makeHandler (type, re) {
56
58
  return str => {
@@ -61,6 +63,26 @@ function makeHandler (type, re) {
61
63
  };
62
64
  }
63
65
 
66
+ function lexStructured (str) {
67
+ const structData = parseSRange(str);
68
+ if (structData) {
69
+ // we have a match for a valid SR
70
+ let i = structData.length;
71
+ // skip tailing whitespace
72
+ while (str[i] === ' ') {
73
+ i++;
74
+ }
75
+ // and ensure that it isn't followed by a !
76
+ if (str[i] !== '!') {
77
+ return {
78
+ type: REF_STRUCT,
79
+ value: structData.token
80
+ };
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+
64
86
  const reRCNums = /([RC])(\[?)(-?\d+)/gi;
65
87
  const reA1Nums = /(\d+|[a-zA-Z]+)/gi;
66
88
  function lexRange (str, options) {
@@ -68,13 +90,13 @@ function lexRange (str, options) {
68
90
  if (options.r1c1) {
69
91
  // RC notation
70
92
  if (options.allowTernary && (m = re_RCPARTIAL.exec(str))) {
71
- t = { type: RANGE_TERNARY, value: m[0] };
93
+ t = { type: REF_TERNARY, value: m[0] };
72
94
  }
73
95
  else if ((m = re_RCRANGE.exec(str))) {
74
- t = { type: RANGE, value: m[0] };
96
+ t = { type: REF_RANGE, value: m[0] };
75
97
  }
76
98
  else if ((m = re_RCROW.exec(str)) || (m = re_RCCOL.exec(str))) {
77
- t = { type: RANGE_BEAM, value: m[0] };
99
+ t = { type: REF_BEAM, value: m[0] };
78
100
  }
79
101
  if (t) {
80
102
  reRCNums.lastIndex = 0;
@@ -91,13 +113,13 @@ function lexRange (str, options) {
91
113
  else {
92
114
  // A1 notation
93
115
  if (options.allowTernary && (m = re_A1PARTIAL.exec(str))) {
94
- t = { type: RANGE_TERNARY, value: m[0] };
116
+ t = { type: REF_TERNARY, value: m[0] };
95
117
  }
96
118
  else if ((m = re_A1COL.exec(str)) || (m = re_A1ROW.exec(str))) {
97
- t = { type: RANGE_BEAM, value: m[0] };
119
+ t = { type: REF_BEAM, value: m[0] };
98
120
  }
99
121
  else if ((m = re_A1RANGE.exec(str))) {
100
- t = { type: RANGE, value: m[0] };
122
+ t = { type: REF_RANGE, value: m[0] };
101
123
  }
102
124
  if (t) {
103
125
  reA1Nums.lastIndex = 0;
@@ -117,7 +139,7 @@ function lexRange (str, options) {
117
139
  }
118
140
 
119
141
  function lexRefOp (s, opts) {
120
- // in RC mode we only allow !
142
+ // in R1C1 mode we only allow !
121
143
  if (opts.r1c1) {
122
144
  return (s[0] === '!')
123
145
  ? { type: OPERATOR, value: s[0] }
@@ -140,8 +162,9 @@ export const lexers = [
140
162
  makeHandler(CONTEXT_QUOTE, re_CONTEXT_QUOTE),
141
163
  makeHandler(CONTEXT, re_CONTEXT),
142
164
  lexRange,
165
+ lexStructured,
143
166
  makeHandler(NUMBER, re_NUMBER),
144
- makeHandler(RANGE_NAMED, re_NAMED)
167
+ makeHandler(REF_NAMED, re_NAMED)
145
168
  ];
146
169
 
147
170
  export const lexersRefs = [
@@ -149,5 +172,6 @@ export const lexersRefs = [
149
172
  makeHandler(CONTEXT_QUOTE, re_CONTEXT_QUOTE),
150
173
  makeHandler(CONTEXT, re_CONTEXT),
151
174
  lexRange,
152
- makeHandler(RANGE_NAMED, re_NAMED)
175
+ lexStructured,
176
+ makeHandler(REF_NAMED, re_NAMED)
153
177
  ];
@@ -1,23 +1,27 @@
1
- import { CONTEXT, CONTEXT_QUOTE, RANGE, RANGE_NAMED, RANGE_BEAM, RANGE_TERNARY, OPERATOR } from './constants.js';
1
+ import { CONTEXT, CONTEXT_QUOTE, REF_RANGE, REF_NAMED, REF_BEAM, REF_TERNARY, OPERATOR, REF_STRUCT } from './constants.js';
2
2
 
3
3
  const END = '$';
4
4
 
5
5
  const validRunsMerge = [
6
- [ RANGE, ':', RANGE ],
7
- [ RANGE ],
8
- [ RANGE_BEAM ],
9
- [ RANGE_TERNARY ],
10
- [ CONTEXT, '!', RANGE, ':', RANGE ],
11
- [ CONTEXT, '!', RANGE ],
12
- [ CONTEXT, '!', RANGE_BEAM ],
13
- [ CONTEXT, '!', RANGE_TERNARY ],
14
- [ CONTEXT_QUOTE, '!', RANGE, ':', RANGE ],
15
- [ CONTEXT_QUOTE, '!', RANGE ],
16
- [ CONTEXT_QUOTE, '!', RANGE_BEAM ],
17
- [ CONTEXT_QUOTE, '!', RANGE_TERNARY ],
18
- [ RANGE_NAMED ],
19
- [ CONTEXT, '!', RANGE_NAMED ],
20
- [ CONTEXT_QUOTE, '!', RANGE_NAMED ]
6
+ [ REF_RANGE, ':', REF_RANGE ],
7
+ [ REF_RANGE ],
8
+ [ REF_BEAM ],
9
+ [ REF_TERNARY ],
10
+ [ CONTEXT, '!', REF_RANGE, ':', REF_RANGE ],
11
+ [ CONTEXT, '!', REF_RANGE ],
12
+ [ CONTEXT, '!', REF_BEAM ],
13
+ [ CONTEXT, '!', REF_TERNARY ],
14
+ [ CONTEXT_QUOTE, '!', REF_RANGE, ':', REF_RANGE ],
15
+ [ CONTEXT_QUOTE, '!', REF_RANGE ],
16
+ [ CONTEXT_QUOTE, '!', REF_BEAM ],
17
+ [ CONTEXT_QUOTE, '!', REF_TERNARY ],
18
+ [ REF_NAMED ],
19
+ [ CONTEXT, '!', REF_NAMED ],
20
+ [ CONTEXT_QUOTE, '!', REF_NAMED ],
21
+ [ REF_STRUCT ],
22
+ [ REF_NAMED, REF_STRUCT ],
23
+ [ CONTEXT, '!', REF_NAMED, REF_STRUCT ],
24
+ [ CONTEXT_QUOTE, '!', REF_NAMED, REF_STRUCT ]
21
25
  ];
22
26
 
23
27
  // valid token runs are converted to a tree structure
@@ -52,22 +56,31 @@ const matcher = (tokens, currNode, anchorIndex, index = 0) => {
52
56
  return 0;
53
57
  };
54
58
 
55
- // merge reference tokens as possible in a list of tokens
56
- export function mergeRefTokens (tokens) {
59
+ /**
60
+ * Merges context with reference tokens as possible in a list of tokens.
61
+ *
62
+ * When given a tokenlist, this function returns a new list with ranges returned
63
+ * as whole references (`Sheet1!A1:B2`) rather than separate tokens for each
64
+ * part: (`Sheet1`,`!`,`A1`,`:`,`B2`).
65
+ *
66
+ * @param {Array<Object>} tokenlist An array of tokens (from `tokenize()`)
67
+ * @return {Array} A new list of tokens with range parts merged.
68
+ */
69
+ export function mergeRefTokens (tokenlist) {
57
70
  const finalTokens = [];
58
71
  // this seeks backwards because it's really the range part
59
72
  // that controls what can be joined.
60
- for (let i = tokens.length - 1; i >= 0; i--) {
61
- let token = tokens[i];
62
- const valid = matcher(tokens, refPartsTree, i);
73
+ for (let i = tokenlist.length - 1; i >= 0; i--) {
74
+ let token = tokenlist[i];
75
+ const valid = matcher(tokenlist, refPartsTree, i);
63
76
  if (valid) {
64
- const toMerge = tokens.slice(i - valid + 1, i + 1);
77
+ const toMerge = tokenlist.slice(i - valid + 1, i + 1);
65
78
  // use the meta properties from the "first" token (right-most token)
66
79
  token = { ...token };
67
80
  token.value = toMerge.map(d => d.value).join('');
68
- // adjust the range to include all the text
69
- if (token.range && toMerge[0].range) {
70
- token.range[0] = toMerge[0].range[0];
81
+ // adjust the offsets to include all the text
82
+ if (token.loc && toMerge[0].loc) {
83
+ token.loc[0] = toMerge[0].loc[0];
71
84
  }
72
85
  i -= valid - 1;
73
86
  }
@@ -1,23 +1,23 @@
1
- import { CONTEXT, FUNCTION, FX_PREFIX, OPERATOR, RANGE, RANGE_BEAM, RANGE_NAMED, RANGE_TERNARY, UNKNOWN } from './constants.js';
1
+ import { CONTEXT, FUNCTION, FX_PREFIX, OPERATOR, REF_RANGE, REF_BEAM, REF_NAMED, REF_TERNARY, UNKNOWN } from './constants.js';
2
2
  import { test } from 'tape';
3
3
  import { mergeRefTokens } from './mergeRefTokens.js';
4
4
  import { tokenize } from './lexer.js';
5
5
 
6
6
  test('mergeRefTokens basics', t => {
7
- const list = tokenize('=SUM([Wb1]Sheet1!A1:B2)', { mergeRanges: false, emitRanges: true });
7
+ const list = tokenize('=SUM([Wb1]Sheet1!A1:B2)', { mergeRefs: false, withLocation: true });
8
8
 
9
9
  t.deepEqual(list, [
10
- { type: FX_PREFIX, value: '=', range: [ 0, 1 ] },
11
- { type: FUNCTION, value: 'SUM', range: [ 1, 4 ] },
12
- { type: OPERATOR, value: '(', range: [ 4, 5 ] },
10
+ { type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
11
+ { type: FUNCTION, value: 'SUM', loc: [ 1, 4 ] },
12
+ { type: OPERATOR, value: '(', loc: [ 4, 5 ] },
13
13
 
14
- { type: CONTEXT, value: '[Wb1]Sheet1', range: [ 5, 16 ] },
15
- { type: OPERATOR, value: '!', range: [ 16, 17 ] },
16
- { type: RANGE, value: 'A1', range: [ 17, 19 ] },
17
- { type: OPERATOR, value: ':', range: [ 19, 20 ] },
18
- { type: RANGE, value: 'B2', range: [ 20, 22 ] },
14
+ { type: CONTEXT, value: '[Wb1]Sheet1', loc: [ 5, 16 ] },
15
+ { type: OPERATOR, value: '!', loc: [ 16, 17 ] },
16
+ { type: REF_RANGE, value: 'A1', loc: [ 17, 19 ] },
17
+ { type: OPERATOR, value: ':', loc: [ 19, 20 ] },
18
+ { type: REF_RANGE, value: 'B2', loc: [ 20, 22 ] },
19
19
 
20
- { type: OPERATOR, value: ')', range: [ 22, 23 ] }
20
+ { type: OPERATOR, value: ')', loc: [ 22, 23 ] }
21
21
  ]);
22
22
 
23
23
  // set IDs on all tokens about to be joined
@@ -29,89 +29,89 @@ test('mergeRefTokens basics', t => {
29
29
 
30
30
  const mergedList = mergeRefTokens(list);
31
31
  t.deepEqual(mergedList, [
32
- { type: FX_PREFIX, value: '=', range: [ 0, 1 ] },
33
- { type: FUNCTION, value: 'SUM', range: [ 1, 4 ] },
34
- { type: OPERATOR, value: '(', range: [ 4, 5 ] },
35
- { type: RANGE,
32
+ { type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
33
+ { type: FUNCTION, value: 'SUM', loc: [ 1, 4 ] },
34
+ { type: OPERATOR, value: '(', loc: [ 4, 5 ] },
35
+ { type: REF_RANGE,
36
36
  id: 'id5', // token has the id of the first one
37
37
  value: '[Wb1]Sheet1!A1:B2',
38
- range: [ 5, 22 ] },
39
- { type: OPERATOR, value: ')', range: [ 22, 23 ] }
38
+ loc: [ 5, 22 ] },
39
+ { type: OPERATOR, value: ')', loc: [ 22, 23 ] }
40
40
  ]);
41
41
 
42
42
  t.end();
43
43
  });
44
44
 
45
45
  test('mergeRefTokens cases', t => {
46
- const opts = { mergeRanges: true, allowTernary: true };
46
+ const opts = { mergeRefs: true, allowTernary: true };
47
47
  t.deepEqual(tokenize('A1', opts), [
48
- { type: RANGE, value: 'A1' }
48
+ { type: REF_RANGE, value: 'A1' }
49
49
  ]);
50
50
  t.deepEqual(tokenize('A1:A1', opts), [
51
- { type: RANGE, value: 'A1:A1' }
51
+ { type: REF_RANGE, value: 'A1:A1' }
52
52
  ]);
53
53
  t.deepEqual(tokenize('A:A', opts), [
54
- { type: RANGE_BEAM, value: 'A:A' }
54
+ { type: REF_BEAM, value: 'A:A' }
55
55
  ]);
56
56
  t.deepEqual(tokenize('A1:A', opts), [
57
- { type: RANGE_TERNARY, value: 'A1:A' }
57
+ { type: REF_TERNARY, value: 'A1:A' }
58
58
  ]);
59
59
 
60
60
  t.deepEqual(tokenize('\'Sheet1\'!A1', opts), [
61
- { type: RANGE, value: '\'Sheet1\'!A1' }
61
+ { type: REF_RANGE, value: '\'Sheet1\'!A1' }
62
62
  ]);
63
63
  t.deepEqual(tokenize('\'Sheet1\'!A:A', opts), [
64
- { type: RANGE_BEAM, value: '\'Sheet1\'!A:A' }
64
+ { type: REF_BEAM, value: '\'Sheet1\'!A:A' }
65
65
  ]);
66
66
  t.deepEqual(tokenize('\'Sheet1\'!A1:A', opts), [
67
- { type: RANGE_TERNARY, value: '\'Sheet1\'!A1:A' }
67
+ { type: REF_TERNARY, value: '\'Sheet1\'!A1:A' }
68
68
  ]);
69
69
  t.deepEqual(tokenize('\'Sheet1\'!A1:A', opts), [
70
- { type: RANGE_TERNARY, value: '\'Sheet1\'!A1:A' }
70
+ { type: REF_TERNARY, value: '\'Sheet1\'!A1:A' }
71
71
  ]);
72
72
 
73
73
  t.deepEqual(tokenize('Sheet1!A1', opts), [
74
- { type: RANGE, value: 'Sheet1!A1' }
74
+ { type: REF_RANGE, value: 'Sheet1!A1' }
75
75
  ]);
76
76
  t.deepEqual(tokenize('Sheet1!A:A', opts), [
77
- { type: RANGE_BEAM, value: 'Sheet1!A:A' }
77
+ { type: REF_BEAM, value: 'Sheet1!A:A' }
78
78
  ]);
79
79
  t.deepEqual(tokenize('Sheet1!A1:A', opts), [
80
- { type: RANGE_TERNARY, value: 'Sheet1!A1:A' }
80
+ { type: REF_TERNARY, value: 'Sheet1!A1:A' }
81
81
  ]);
82
82
  t.deepEqual(tokenize('Sheet1!A1:A', opts), [
83
- { type: RANGE_TERNARY, value: 'Sheet1!A1:A' }
83
+ { type: REF_TERNARY, value: 'Sheet1!A1:A' }
84
84
  ]);
85
85
 
86
86
  t.deepEqual(tokenize('[WB]Sheet1!A1', opts), [
87
- { type: RANGE, value: '[WB]Sheet1!A1' }
87
+ { type: REF_RANGE, value: '[WB]Sheet1!A1' }
88
88
  ]);
89
89
  t.deepEqual(tokenize('[WB]Sheet1!A:A', opts), [
90
- { type: RANGE_BEAM, value: '[WB]Sheet1!A:A' }
90
+ { type: REF_BEAM, value: '[WB]Sheet1!A:A' }
91
91
  ]);
92
92
  t.deepEqual(tokenize('[WB]Sheet1!A1:A', opts), [
93
- { type: RANGE_TERNARY, value: '[WB]Sheet1!A1:A' }
93
+ { type: REF_TERNARY, value: '[WB]Sheet1!A1:A' }
94
94
  ]);
95
95
  t.deepEqual(tokenize('[WB]Sheet1!A1:A', opts), [
96
- { type: RANGE_TERNARY, value: '[WB]Sheet1!A1:A' }
96
+ { type: REF_TERNARY, value: '[WB]Sheet1!A1:A' }
97
97
  ]);
98
98
 
99
99
  t.deepEqual(tokenize('foo', opts), [
100
- { type: RANGE_NAMED, value: 'foo' }
100
+ { type: REF_NAMED, value: 'foo' }
101
101
  ]);
102
102
  t.deepEqual(tokenize('\'quoted\'!foo', opts), [
103
- { type: RANGE_NAMED, value: '\'quoted\'!foo' }
103
+ { type: REF_NAMED, value: '\'quoted\'!foo' }
104
104
  ]);
105
105
  t.deepEqual(tokenize('Sheet1!foo', opts), [
106
- { type: RANGE_NAMED, value: 'Sheet1!foo' }
106
+ { type: REF_NAMED, value: 'Sheet1!foo' }
107
107
  ]);
108
108
  t.deepEqual(tokenize('[path]!foo', opts), [
109
109
  { type: UNKNOWN, value: '[path]' },
110
110
  { type: OPERATOR, value: '!' },
111
- { type: RANGE_NAMED, value: 'foo' }
111
+ { type: REF_NAMED, value: 'foo' }
112
112
  ]);
113
113
  t.deepEqual(tokenize('[path]prefix!foo', opts), [
114
- { type: RANGE_NAMED, value: '[path]prefix!foo' }
114
+ { type: REF_NAMED, value: '[path]prefix!foo' }
115
115
  ]);
116
116
 
117
117
  t.end();
package/lib/parseRef.js CHANGED
@@ -2,10 +2,11 @@ import {
2
2
  FX_PREFIX,
3
3
  CONTEXT,
4
4
  CONTEXT_QUOTE,
5
- RANGE,
6
- RANGE_TERNARY,
7
- RANGE_NAMED,
8
- RANGE_BEAM,
5
+ REF_RANGE,
6
+ REF_TERNARY,
7
+ REF_NAMED,
8
+ REF_BEAM,
9
+ REF_STRUCT,
9
10
  OPERATOR
10
11
  } from './constants.js';
11
12
  import { lexersRefs } from './lexerParts.js';
@@ -23,16 +24,17 @@ function splitContext (contextString) {
23
24
  const unquote = d => d.slice(1, -1).replace(/''/g, "'");
24
25
 
25
26
  const pRangeOp = t => t && t.value === ':' && {};
26
- const pRange = t => t && t.type === RANGE && { r0: t.value };
27
- const pPartial = t => t && t.type === RANGE_TERNARY && { r0: t.value };
28
- const pRange2 = t => t && t.type === RANGE && { r1: t.value };
27
+ const pRange = t => t && t.type === REF_RANGE && { r0: t.value };
28
+ const pPartial = t => t && t.type === REF_TERNARY && { r0: t.value };
29
+ const pRange2 = t => t && t.type === REF_RANGE && { r1: t.value };
29
30
  const pBang = t => t && t.type === OPERATOR && t.value === '!' && {};
30
- const pBeam = t => t && t.type === RANGE_BEAM && { r0: t.value };
31
+ const pBeam = t => t && t.type === REF_BEAM && { r0: t.value };
32
+ const pStrucured = t => t && t.type === REF_STRUCT && { struct: t.value };
31
33
  const pContext = t => {
32
34
  if (t && t.type === CONTEXT) { return splitContext(t.value); }
33
35
  if (t && t.type === CONTEXT_QUOTE) { return splitContext(unquote(t.value)); }
34
36
  };
35
- const pNamed = t => t && t.type === RANGE_NAMED && { name: t.value };
37
+ const pNamed = t => t && t.type === REF_NAMED && { name: t.value };
36
38
 
37
39
  const validRuns = [
38
40
  [ pPartial ],
@@ -47,13 +49,16 @@ const validRuns = [
47
49
 
48
50
  const validRunsNamed = validRuns.concat([
49
51
  [ pNamed ],
50
- [ pContext, pBang, pNamed ]
52
+ [ pContext, pBang, pNamed ],
53
+ [ pStrucured ],
54
+ [ pNamed, pStrucured ],
55
+ [ pContext, pBang, pNamed, pStrucured ]
51
56
  ]);
52
57
 
53
58
  export function parseRef (ref, opts) {
54
59
  const options = {
55
- emitRanges: false,
56
- mergeRanges: false,
60
+ withLocation: false,
61
+ mergeRefs: false,
57
62
  allowTernary: false,
58
63
  allowNamed: true,
59
64
  r1c1: false,