@borgar/fx 4.12.0 → 5.0.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.
Files changed (139) hide show
  1. package/dist/index-BMr6cTgc.d.cts +1444 -0
  2. package/dist/index-BMr6cTgc.d.ts +1444 -0
  3. package/dist/index.cjs +3054 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +2984 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/xlsx/index.cjs +3120 -0
  10. package/dist/xlsx/index.cjs.map +1 -0
  11. package/dist/xlsx/index.d.cts +55 -0
  12. package/dist/xlsx/index.d.ts +55 -0
  13. package/dist/xlsx/index.js +3049 -0
  14. package/dist/xlsx/index.js.map +1 -0
  15. package/docs/API.md +2959 -718
  16. package/docs/AST_format.md +2 -2
  17. package/eslint.config.mjs +40 -0
  18. package/lib/a1.spec.ts +32 -0
  19. package/lib/a1.ts +26 -0
  20. package/lib/addA1RangeBounds.ts +50 -0
  21. package/lib/addTokenMeta.spec.ts +166 -0
  22. package/lib/{addTokenMeta.js → addTokenMeta.ts} +53 -33
  23. package/lib/astTypes.ts +211 -0
  24. package/lib/cloneToken.ts +29 -0
  25. package/lib/{constants.js → constants.ts} +6 -3
  26. package/lib/fixRanges.spec.ts +220 -0
  27. package/lib/fixRanges.ts +260 -0
  28. package/lib/fromCol.spec.ts +15 -0
  29. package/lib/{fromCol.js → fromCol.ts} +1 -1
  30. package/lib/index.spec.ts +119 -0
  31. package/lib/index.ts +76 -0
  32. package/lib/isNodeType.ts +151 -0
  33. package/lib/isType.spec.ts +208 -0
  34. package/lib/{isType.js → isType.ts} +26 -25
  35. package/lib/lexers/advRangeOp.ts +18 -0
  36. package/lib/lexers/canEndRange.ts +25 -0
  37. package/lib/lexers/lexBoolean.ts +55 -0
  38. package/lib/lexers/lexContext.ts +104 -0
  39. package/lib/lexers/lexError.ts +15 -0
  40. package/lib/lexers/lexFunction.ts +37 -0
  41. package/lib/lexers/lexNameFuncCntx.ts +112 -0
  42. package/lib/lexers/lexNamed.ts +60 -0
  43. package/lib/lexers/lexNewLine.ts +12 -0
  44. package/lib/lexers/lexNumber.ts +48 -0
  45. package/lib/lexers/lexOperator.ts +26 -0
  46. package/lib/lexers/lexRange.ts +15 -0
  47. package/lib/lexers/lexRangeA1.ts +134 -0
  48. package/lib/lexers/lexRangeR1C1.ts +146 -0
  49. package/lib/lexers/lexRangeTrim.ts +26 -0
  50. package/lib/lexers/lexRefOp.ts +19 -0
  51. package/lib/lexers/lexString.ts +22 -0
  52. package/lib/lexers/lexStructured.ts +25 -0
  53. package/lib/lexers/lexWhitespace.ts +31 -0
  54. package/lib/lexers/sets.ts +51 -0
  55. package/lib/mergeRefTokens.spec.ts +141 -0
  56. package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +47 -32
  57. package/lib/nodeTypes.ts +54 -0
  58. package/lib/parse.spec.ts +1410 -0
  59. package/lib/{parser.js → parse.ts} +81 -63
  60. package/lib/parseA1Range.spec.ts +233 -0
  61. package/lib/parseA1Range.ts +206 -0
  62. package/lib/parseA1Ref.spec.ts +337 -0
  63. package/lib/parseA1Ref.ts +115 -0
  64. package/lib/parseR1C1Range.ts +191 -0
  65. package/lib/parseR1C1Ref.spec.ts +323 -0
  66. package/lib/parseR1C1Ref.ts +127 -0
  67. package/lib/parseRef.spec.ts +90 -0
  68. package/lib/parseRef.ts +240 -0
  69. package/lib/parseSRange.ts +240 -0
  70. package/lib/parseStructRef.spec.ts +168 -0
  71. package/lib/parseStructRef.ts +76 -0
  72. package/lib/stringifyA1Range.spec.ts +72 -0
  73. package/lib/stringifyA1Range.ts +72 -0
  74. package/lib/stringifyA1Ref.spec.ts +64 -0
  75. package/lib/stringifyA1Ref.ts +59 -0
  76. package/lib/{stringifyPrefix.js → stringifyPrefix.ts} +17 -2
  77. package/lib/stringifyR1C1Range.spec.ts +92 -0
  78. package/lib/stringifyR1C1Range.ts +73 -0
  79. package/lib/stringifyR1C1Ref.spec.ts +63 -0
  80. package/lib/stringifyR1C1Ref.ts +67 -0
  81. package/lib/stringifyStructRef.spec.ts +124 -0
  82. package/lib/stringifyStructRef.ts +113 -0
  83. package/lib/stringifyTokens.ts +15 -0
  84. package/lib/toCol.spec.ts +11 -0
  85. package/lib/{toCol.js → toCol.ts} +4 -4
  86. package/lib/tokenTypes.ts +76 -0
  87. package/lib/tokenize-srefs.spec.ts +429 -0
  88. package/lib/tokenize.spec.ts +2103 -0
  89. package/lib/tokenize.ts +346 -0
  90. package/lib/translate.spec.ts +35 -0
  91. package/lib/translateToA1.spec.ts +247 -0
  92. package/lib/translateToA1.ts +231 -0
  93. package/lib/translateToR1C1.spec.ts +227 -0
  94. package/lib/translateToR1C1.ts +145 -0
  95. package/lib/types.ts +179 -0
  96. package/lib/xlsx/index.spec.ts +27 -0
  97. package/lib/xlsx/index.ts +32 -0
  98. package/package.json +46 -30
  99. package/tsconfig.json +28 -0
  100. package/typedoc-ignore-links.ts +17 -0
  101. package/typedoc.json +41 -0
  102. package/.eslintrc +0 -22
  103. package/dist/fx.d.ts +0 -823
  104. package/dist/fx.js +0 -2
  105. package/dist/package.json +0 -1
  106. package/lib/a1.js +0 -348
  107. package/lib/a1.spec.js +0 -458
  108. package/lib/addTokenMeta.spec.js +0 -153
  109. package/lib/astTypes.js +0 -96
  110. package/lib/extraTypes.js +0 -74
  111. package/lib/fixRanges.js +0 -104
  112. package/lib/fixRanges.spec.js +0 -170
  113. package/lib/fromCol.spec.js +0 -11
  114. package/lib/index.js +0 -134
  115. package/lib/index.spec.js +0 -67
  116. package/lib/isType.spec.js +0 -168
  117. package/lib/lexer-srefs.spec.js +0 -324
  118. package/lib/lexer.js +0 -283
  119. package/lib/lexer.spec.js +0 -1953
  120. package/lib/lexerParts.js +0 -228
  121. package/lib/mergeRefTokens.spec.js +0 -121
  122. package/lib/package.json +0 -1
  123. package/lib/parseRef.js +0 -157
  124. package/lib/parseRef.spec.js +0 -71
  125. package/lib/parseSRange.js +0 -167
  126. package/lib/parseStructRef.js +0 -48
  127. package/lib/parseStructRef.spec.js +0 -164
  128. package/lib/parser.spec.js +0 -1208
  129. package/lib/rc.js +0 -341
  130. package/lib/rc.spec.js +0 -403
  131. package/lib/stringifyStructRef.js +0 -80
  132. package/lib/stringifyStructRef.spec.js +0 -182
  133. package/lib/toCol.spec.js +0 -11
  134. package/lib/translate-toA1.spec.js +0 -214
  135. package/lib/translate-toRC.spec.js +0 -197
  136. package/lib/translate.js +0 -239
  137. package/lib/translate.spec.js +0 -21
  138. package/rollup.config.mjs +0 -22
  139. package/tsd.json +0 -12
@@ -0,0 +1,90 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { splitPrefix } from './parseRef.ts';
3
+
4
+ function testStr (str: string, opt: boolean, expected: any) {
5
+ expect(splitPrefix(str, opt)).toEqual(expected);
6
+ }
7
+
8
+ describe('splitPrefix', () => {
9
+ describe('with simple mode (opt = true)', () => {
10
+ test('fully bracketed references', () => {
11
+ testStr('[foo][bar][baz]', true, [ 'foo', 'bar', 'baz' ]);
12
+ testStr('[foo][bar]', true, [ 'foo', 'bar' ]);
13
+ testStr('[foo]', true, [ 'foo' ]);
14
+ });
15
+
16
+ test('mixed bracketed and unbracketed references', () => {
17
+ testStr('foo[bar][baz]', true, [ 'foo', 'bar', 'baz' ]);
18
+ testStr('[foo]bar[baz]', true, [ 'foo', 'bar', 'baz' ]);
19
+ testStr('[foo][bar]baz', true, [ 'foo', 'bar', 'baz' ]);
20
+ testStr('foo[bar]baz', true, [ 'foo', 'bar', 'baz' ]);
21
+ testStr('[foo]bar', true, [ 'foo', 'bar' ]);
22
+ testStr('foo[bar]', true, [ 'foo', 'bar' ]);
23
+ });
24
+
25
+ test('unbracketed references', () => {
26
+ testStr('foo', true, [ 'foo' ]);
27
+ });
28
+ });
29
+
30
+ describe('with detailed mode (opt = false)', () => {
31
+ test('fully bracketed references', () => {
32
+ testStr('[foo][bar][baz]', false, [
33
+ { value: 'foo', braced: true },
34
+ { value: 'bar', braced: true },
35
+ { value: 'baz', braced: true }
36
+ ]);
37
+
38
+ testStr('[foo][bar]', false, [
39
+ { value: 'foo', braced: true },
40
+ { value: 'bar', braced: true }
41
+ ]);
42
+
43
+ testStr('[foo]', false, [
44
+ { value: 'foo', braced: true }
45
+ ]);
46
+ });
47
+
48
+ test('mixed bracketed and unbracketed references', () => {
49
+ testStr('foo[bar][baz]', false, [
50
+ { value: 'foo', braced: false },
51
+ { value: 'bar', braced: true },
52
+ { value: 'baz', braced: true }
53
+ ]);
54
+
55
+ testStr('[foo]bar[baz]', false, [
56
+ { value: 'foo', braced: true },
57
+ { value: 'bar', braced: false },
58
+ { value: 'baz', braced: true }
59
+ ]);
60
+
61
+ testStr('[foo][bar]baz', false, [
62
+ { value: 'foo', braced: true },
63
+ { value: 'bar', braced: true },
64
+ { value: 'baz', braced: false }
65
+ ]);
66
+
67
+ testStr('foo[bar]baz', false, [
68
+ { value: 'foo', braced: false },
69
+ { value: 'bar', braced: true },
70
+ { value: 'baz', braced: false }
71
+ ]);
72
+
73
+ testStr('[foo]bar', false, [
74
+ { value: 'foo', braced: true },
75
+ { value: 'bar', braced: false }
76
+ ]);
77
+
78
+ testStr('foo[bar]', false, [
79
+ { value: 'foo', braced: false },
80
+ { value: 'bar', braced: true }
81
+ ]);
82
+ });
83
+
84
+ test('unbracketed references', () => {
85
+ testStr('foo', false, [
86
+ { value: 'foo', braced: false }
87
+ ]);
88
+ });
89
+ });
90
+ });
@@ -0,0 +1,240 @@
1
+ import {
2
+ FX_PREFIX,
3
+ CONTEXT,
4
+ CONTEXT_QUOTE,
5
+ REF_RANGE,
6
+ REF_TERNARY,
7
+ REF_NAMED,
8
+ REF_BEAM,
9
+ REF_STRUCT,
10
+ OPERATOR
11
+ } from './constants.ts';
12
+ import { lexersRefs } from './lexers/sets.ts';
13
+ import { getTokens } from './tokenize.ts';
14
+ import type { Token } from './types.ts';
15
+
16
+ // Liberally split a context string up into parts.
17
+ // Permits any combination of braced and unbraced items.
18
+ export function splitPrefix (str: string, stringsOnly = false) {
19
+ let inBrace = false;
20
+ let currStr = '';
21
+ const parts = [];
22
+ const flush = () => {
23
+ if (currStr) {
24
+ parts.push(
25
+ stringsOnly
26
+ ? currStr
27
+ : { value: currStr, braced: inBrace }
28
+ );
29
+ }
30
+ currStr = '';
31
+ };
32
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
33
+ for (let i = 0; i < str.length; i++) {
34
+ const char = str[i];
35
+ if (char === '[') {
36
+ flush();
37
+ inBrace = true;
38
+ }
39
+ else if (char === ']') {
40
+ flush();
41
+ inBrace = false;
42
+ }
43
+ else {
44
+ currStr += char;
45
+ }
46
+ }
47
+ flush();
48
+ return parts;
49
+ }
50
+
51
+ export function splitContext (contextString, data, xlsx) {
52
+ const ctx = splitPrefix(contextString, !xlsx);
53
+ if (xlsx) {
54
+ if (ctx.length > 1) {
55
+ data.workbookName = ctx[ctx.length - 2].value;
56
+ data.sheetName = ctx[ctx.length - 1].value;
57
+ }
58
+ else if (ctx.length === 1) {
59
+ const item = ctx[0];
60
+ if (item.braced) {
61
+ data.workbookName = item.value;
62
+ }
63
+ else {
64
+ data.sheetName = item.value;
65
+ }
66
+ }
67
+ }
68
+ else {
69
+ data.context = ctx;
70
+ }
71
+ }
72
+
73
+ type RefParseData = {
74
+ operator: string,
75
+ r0: string,
76
+ r1: string,
77
+ name: string,
78
+ struct: string,
79
+ };
80
+ type RefParserPart = (t: Token | undefined, data: Partial<RefParseData>, xlsx?: boolean) => 1 | undefined;
81
+
82
+ const unquote = d => d.slice(1, -1).replace(/''/g, "'");
83
+
84
+ const pRangeOp: RefParserPart = (t, data) => {
85
+ const value = t?.value;
86
+ if (value === ':' || value === '.:' || value === ':.' || value === '.:.') {
87
+ data.operator = value;
88
+ return 1;
89
+ }
90
+ };
91
+ const pRange: RefParserPart = (t, data) => {
92
+ if (t?.type === REF_RANGE) {
93
+ data.r0 = t.value;
94
+ return 1;
95
+ }
96
+ };
97
+ const pPartial: RefParserPart = (t, data) => {
98
+ if (t?.type === REF_TERNARY) {
99
+ data.r0 = t.value;
100
+ return 1;
101
+ }
102
+ };
103
+ const pRange2: RefParserPart = (t, data) => {
104
+ if (t?.type === REF_RANGE) {
105
+ data.r1 = t.value;
106
+ return 1;
107
+ }
108
+ };
109
+ const pBang: RefParserPart = t => {
110
+ if (t?.type === OPERATOR && t.value === '!') {
111
+ return 1;
112
+ }
113
+ };
114
+ const pBeam: RefParserPart = (t, data) => {
115
+ if (t?.type === REF_BEAM) {
116
+ data.r0 = t.value;
117
+ return 1;
118
+ }
119
+ };
120
+ const pStrucured: RefParserPart = (t, data) => {
121
+ if (t.type === REF_STRUCT) {
122
+ data.struct = t.value;
123
+ return 1;
124
+ }
125
+ };
126
+ const pContext: RefParserPart = (t, data, xlsx) => {
127
+ const type = t?.type;
128
+ if (type === CONTEXT) {
129
+ splitContext(t.value, data, xlsx);
130
+ return 1;
131
+ }
132
+ if (type === CONTEXT_QUOTE) {
133
+ splitContext(unquote(t.value), data, xlsx);
134
+ return 1;
135
+ }
136
+ };
137
+ const pNamed: RefParserPart = (t, data) => {
138
+ if (t?.type === REF_NAMED) {
139
+ data.name = t.value;
140
+ return 1;
141
+ }
142
+ };
143
+
144
+ const validRuns = [
145
+ [ pPartial ],
146
+ [ pRange, pRangeOp, pRange2 ],
147
+ [ pRange ],
148
+ [ pBeam ],
149
+ [ pContext, pBang, pPartial ],
150
+ [ pContext, pBang, pRange, pRangeOp, pRange2 ],
151
+ [ pContext, pBang, pRange ],
152
+ [ pContext, pBang, pBeam ]
153
+ ];
154
+
155
+ const validRunsNamed = validRuns.concat([
156
+ [ pNamed ],
157
+ [ pContext, pBang, pNamed ],
158
+ [ pStrucured ],
159
+ [ pNamed, pStrucured ],
160
+ [ pContext, pBang, pNamed, pStrucured ]
161
+ ]);
162
+
163
+ type ParseRefOptions = {
164
+ withLocation?: boolean,
165
+ mergeRefs?: boolean,
166
+ allowTernary?: boolean,
167
+ allowNamed?: boolean,
168
+ r1c1?: boolean,
169
+ };
170
+ export type RefParseDataXls = RefParseData & { workbookName: string, sheetName: string };
171
+ export type RefParseDataCtx = RefParseData & { context: string[] };
172
+
173
+ export function parseRefCtx (ref: string, opts: ParseRefOptions = {}): RefParseDataCtx | null {
174
+ const options = {
175
+ withLocation: opts.withLocation ?? false,
176
+ mergeRefs: opts.mergeRefs ?? false,
177
+ allowTernary: opts.allowTernary ?? false,
178
+ allowNamed: opts.allowNamed ?? true,
179
+ r1c1: opts.r1c1 ?? false
180
+ };
181
+ const tokens = getTokens(ref, lexersRefs, options);
182
+
183
+ // discard the "="-prefix if it is there
184
+ if (tokens.length && tokens[0].type === FX_PREFIX) {
185
+ tokens.shift();
186
+ }
187
+ const runs = options.allowNamed ? validRunsNamed : validRuns;
188
+ for (const run of runs) {
189
+ // const len = run.length;
190
+ if (run.length === tokens.length) {
191
+ const data: RefParseDataCtx = {
192
+ context: [],
193
+ r0: '',
194
+ r1: '',
195
+ name: '',
196
+ struct: '',
197
+ operator: ''
198
+ };
199
+ const valid = run.every((parse, i) => parse(tokens[i], data, false));
200
+ if (valid) {
201
+ return data;
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ export function parseRefXlsx (ref: string, opts: ParseRefOptions = {}): RefParseDataXls | null {
208
+ const options = {
209
+ withLocation: opts.withLocation ?? false,
210
+ mergeRefs: opts.mergeRefs ?? false,
211
+ allowTernary: opts.allowTernary ?? false,
212
+ allowNamed: opts.allowNamed ?? true,
213
+ r1c1: opts.r1c1 ?? false,
214
+ xlsx: true
215
+ };
216
+ const tokens = getTokens(ref, lexersRefs, options);
217
+
218
+ // discard the "="-prefix if it is there
219
+ if (tokens.length && tokens[0].type === FX_PREFIX) {
220
+ tokens.shift();
221
+ }
222
+ const runs = options.allowNamed ? validRunsNamed : validRuns;
223
+ for (const run of runs) {
224
+ if (run.length === tokens.length) {
225
+ const data: RefParseDataXls = {
226
+ workbookName: '',
227
+ sheetName: '',
228
+ r0: '',
229
+ r1: '',
230
+ name: '',
231
+ struct: '',
232
+ operator: ''
233
+ };
234
+ const valid = run.every((parse, i) => parse(tokens[i], data, true));
235
+ if (valid) {
236
+ return data as any;
237
+ }
238
+ }
239
+ }
240
+ }
@@ -0,0 +1,240 @@
1
+ import { isWS } from './lexers/lexWhitespace.ts';
2
+
3
+ const AT = 64; // @
4
+ const BR_CLOSE = 93; // ]
5
+ const BR_OPEN = 91; // [
6
+ const COLON = 58; // :
7
+ const COMMA = 44; // ,
8
+ const HASH = 35; // #
9
+ const QUOT_SINGLE = 39; // '
10
+
11
+ const keyTerms = {
12
+ 'headers': 1,
13
+ 'data': 2,
14
+ 'totals': 4,
15
+ 'all': 8,
16
+ 'this row': 16,
17
+ '@': 16
18
+ };
19
+
20
+ // only combinations allowed are: #data + (#headers | #totals | #data)
21
+ const fz = (...a: string[]) => Object.freeze(a);
22
+ const sectionMap = {
23
+ // no terms
24
+ 0: fz(),
25
+ // single term
26
+ 1: fz('headers'),
27
+ 2: fz('data'),
28
+ 4: fz('totals'),
29
+ 8: fz('all'),
30
+ 16: fz('this row'),
31
+ // headers+data
32
+ 3: fz('headers', 'data'),
33
+ // totals+data
34
+ 6: fz('data', 'totals')
35
+ };
36
+
37
+ function matchKeyword (str: string, pos: number): number {
38
+ let p = pos;
39
+ if (str.charCodeAt(p++) !== BR_OPEN) {
40
+ return;
41
+ }
42
+ if (str.charCodeAt(p++) !== HASH) {
43
+ return;
44
+ }
45
+ do {
46
+ const c = str.charCodeAt(p);
47
+ if (
48
+ (c >= 65 && c <= 90) || // A-Z
49
+ (c >= 97 && c <= 122) || // a-z
50
+ (c === 32) // space
51
+ ) {
52
+ p++;
53
+ }
54
+ else {
55
+ break;
56
+ }
57
+ }
58
+ while (p < pos + 11); // max length: '[#this row'
59
+ if (str.charCodeAt(p++) !== BR_CLOSE) {
60
+ return;
61
+ }
62
+ return p - pos;
63
+ }
64
+
65
+ function skipWhitespace (str: string, pos: number): number {
66
+ let p = pos;
67
+ while (isWS(str.charCodeAt(p))) { p++; }
68
+ return p - pos;
69
+ }
70
+
71
+ function matchColumn (str: string, pos: number, allowUnbraced = true): [ string, string ] {
72
+ let p = pos;
73
+ let column = '';
74
+ if (str.charCodeAt(p) === BR_OPEN) {
75
+ p++;
76
+ let c;
77
+ do {
78
+ c = str.charCodeAt(p);
79
+ if (c === QUOT_SINGLE) {
80
+ p++;
81
+ c = str.charCodeAt(p);
82
+ // Allowed set: '#@[]
83
+ if (c === QUOT_SINGLE || c === HASH || c === AT || c === BR_OPEN || c === BR_CLOSE) {
84
+ column += String.fromCharCode(c);
85
+ p++;
86
+ }
87
+ else {
88
+ return;
89
+ }
90
+ }
91
+ // Allowed set is all chars BUT: '#@[]
92
+ else if (c === QUOT_SINGLE || c === HASH || c === AT || c === BR_OPEN) {
93
+ return;
94
+ }
95
+ else if (c === BR_CLOSE) {
96
+ p++;
97
+ return [ str.slice(pos, p), column ];
98
+ }
99
+ else {
100
+ column += String.fromCharCode(c);
101
+ p++;
102
+ }
103
+ }
104
+ while (p < str.length);
105
+ }
106
+ else if (allowUnbraced) {
107
+ let c;
108
+ do {
109
+ c = str.charCodeAt(p);
110
+ // Allowed set is all chars BUT: '#@[]:
111
+ if (c === QUOT_SINGLE || c === HASH || c === AT || c === BR_OPEN || c === BR_CLOSE || c === COLON) {
112
+ break;
113
+ }
114
+ else {
115
+ column += String.fromCharCode(c);
116
+ p++;
117
+ }
118
+ }
119
+ while (p < str.length);
120
+ if (p !== pos) {
121
+ return [ column, column ];
122
+ }
123
+ }
124
+ }
125
+
126
+ export type SRange = {
127
+ columns: string[],
128
+ sections: string[],
129
+ length: number,
130
+ token: string
131
+ };
132
+
133
+ export function parseSRange (str: string, pos: number = 0): SRange {
134
+ const columns: string[] = [];
135
+ const start = pos;
136
+ let m;
137
+ let terms = 0;
138
+
139
+ // structured refs start with a [
140
+ if (str.charCodeAt(pos) !== BR_OPEN) {
141
+ return;
142
+ }
143
+
144
+ // simple keyword: [#keyword]
145
+ if ((m = matchKeyword(str, pos))) {
146
+ const k = str.slice(pos + 2, pos + m - 1);
147
+ pos += m;
148
+ const term = keyTerms[k.toLowerCase()];
149
+ if (!term) { return; }
150
+ terms |= term;
151
+ }
152
+ // simple column: [column]
153
+ else if ((m = matchColumn(str, pos, false))) {
154
+ pos += m[0].length;
155
+ if (m[1]) {
156
+ columns.push(m[1]);
157
+ }
158
+ }
159
+ // use the "normal" method
160
+ // [[#keyword]]
161
+ // [[column]]
162
+ // [@]
163
+ // [@column]
164
+ // [@[column]]
165
+ // [@column:column]
166
+ // [@column:[column]]
167
+ // [@[column]:column]
168
+ // [@[column]:[column]]
169
+ // [column:column]
170
+ // [column:[column]]
171
+ // [[column]:column]
172
+ // [[column]:[column]]
173
+ // [[#keyword],column]
174
+ // [[#keyword],column:column]
175
+ // [[#keyword],[#keyword],column:column]
176
+ // ...
177
+ else {
178
+ let expect_more = true;
179
+ pos++; // skip open brace
180
+ pos += skipWhitespace(str, pos);
181
+ // match keywords as we find them
182
+ while (expect_more && (m = matchKeyword(str, pos))) {
183
+ const k = str.slice(pos + 2, pos + m - 1);
184
+ const term = keyTerms[k.toLowerCase()];
185
+ if (!term) { return; }
186
+ terms |= term;
187
+ pos += m;
188
+ pos += skipWhitespace(str, pos);
189
+ expect_more = str.charCodeAt(pos) === COMMA;
190
+ if (expect_more) {
191
+ pos++;
192
+ pos += skipWhitespace(str, pos);
193
+ }
194
+ }
195
+ // is there an @ specifier?
196
+ if (expect_more && (str.charCodeAt(pos) === AT)) {
197
+ terms |= keyTerms['@'];
198
+ pos += 1;
199
+ expect_more = str.charCodeAt(pos) !== BR_CLOSE;
200
+ }
201
+ // not all keyword terms may be combined
202
+ if (!sectionMap[terms]) {
203
+ return;
204
+ }
205
+ // column definitions
206
+ const leftCol = expect_more && matchColumn(str, pos, true);
207
+ if (leftCol) {
208
+ pos += leftCol[0].length;
209
+ columns.push(leftCol[1]);
210
+ if (str.charCodeAt(pos) === COLON) {
211
+ pos++;
212
+ const rightCol = matchColumn(str, pos, true);
213
+ if (rightCol) {
214
+ pos += rightCol[0].length;
215
+ columns.push(rightCol[1]);
216
+ }
217
+ else {
218
+ return;
219
+ }
220
+ }
221
+ expect_more = false;
222
+ }
223
+ // advance ws
224
+ pos += skipWhitespace(str, pos);
225
+ // close the ref
226
+ if (expect_more || str.charCodeAt(pos) !== BR_CLOSE) {
227
+ return;
228
+ }
229
+ // step over the closing ]
230
+ pos++;
231
+ }
232
+
233
+ const sections: string[] = sectionMap[terms];
234
+ return {
235
+ columns,
236
+ sections: sections ? sections.concat() : sections,
237
+ length: pos - start,
238
+ token: str.slice(start, pos)
239
+ };
240
+ }