@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,112 @@
1
+ import { CONTEXT, FUNCTION, REF_NAMED, UNKNOWN } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+ import { lexContextUnquoted } from './lexContext.ts';
4
+
5
+ const BR_OPEN = 91; // [
6
+ const PAREN_OPEN = 40;
7
+ const EXCL = 33; // !
8
+ const OFFS = 32;
9
+
10
+ // build a map of characters to allow-bitmasks
11
+ const ALLOWED = new Uint8Array(180 - OFFS);
12
+ const OK_NAME_0 = 0b000001;
13
+ const OK_FUNC_0 = 0b000010;
14
+ const OK_CNTX_0 = 0b000100;
15
+ const OK_NAME_N = 0b001000;
16
+ const OK_FUNC_N = 0b010000;
17
+ const OK_CNTX_N = 0b100000;
18
+ const OK_0 = OK_NAME_0 | OK_FUNC_0 | OK_CNTX_0;
19
+ const OK_N = OK_NAME_N | OK_FUNC_N | OK_CNTX_N;
20
+ const OK_HIGHCHAR = OK_NAME_0 | OK_NAME_N | OK_CNTX_0 | OK_CNTX_N;
21
+ for (let c = OFFS; c < 180; c++) {
22
+ const char = String.fromCharCode(c);
23
+ const n0 = /^[a-zA-Z_\\\u00a1-\uffff]$/.test(char);
24
+ const f0 = /^[a-zA-Z_]$/.test(char);
25
+ const nN = /^[a-zA-Z0-9_.\\?\u00a1-\uffff]$/.test(char);
26
+ const fN = /^[a-zA-Z0-9_.]$/.test(char);
27
+ const cX = /^[a-zA-Z0-9_.¡¤§¨ª\u00ad¯-\uffff]$/.test(char);
28
+ ALLOWED[c - OFFS] = (
29
+ (n0 ? OK_NAME_0 : 0) |
30
+ (nN ? OK_NAME_N : 0) |
31
+ (f0 ? OK_FUNC_0 : 0) |
32
+ (fN ? OK_FUNC_N : 0) |
33
+ (cX ? OK_CNTX_0 : 0) |
34
+ (cX ? OK_CNTX_N : 0)
35
+ );
36
+ }
37
+
38
+ function nameOrUnknown (str, s, start, pos, name) {
39
+ const len = pos - start;
40
+ if (name && len && len < 255) {
41
+ // names starting with \ must be at least 3 char long
42
+ if (s === 92 && len < 3) {
43
+ return;
44
+ }
45
+ // single characters R and C are forbidden as names
46
+ if (len === 1 && (s === 114 || s === 82 || s === 99 || s === 67)) {
47
+ return;
48
+ }
49
+ return { type: REF_NAMED, value: str.slice(start, pos) };
50
+ }
51
+ return { type: UNKNOWN, value: str.slice(start, pos) };
52
+ }
53
+
54
+ export function lexNameFuncCntx (
55
+ str: string,
56
+ pos: number,
57
+ opts: { xlsx: boolean }
58
+ ): Token | undefined {
59
+ const start = pos;
60
+
61
+ const s = str.charCodeAt(pos);
62
+ const a = s > 180 ? OK_HIGHCHAR : ALLOWED[s - OFFS];
63
+ // name: [a-zA-Z_\\\u00a1-\uffff]
64
+ // func: [a-zA-Z_]
65
+ // cntx: [a-zA-Z_0-9.¡¤§¨ª\u00ad¯-\uffff]
66
+ if (((a & OK_CNTX_0) && !(a & OK_NAME_0) && !(a & OK_FUNC_0)) || s === BR_OPEN) {
67
+ // its a context so delegate to that lexer
68
+ return lexContextUnquoted(str, pos, opts);
69
+ }
70
+ if (!(a & OK_0)) {
71
+ return;
72
+ }
73
+ let name = (a & OK_NAME_0) ? 1 : 0;
74
+ let func = (a & OK_FUNC_0) ? 1 : 0;
75
+ let cntx = (a & OK_CNTX_0) ? 1 : 0;
76
+ pos++;
77
+
78
+ let c: number;
79
+ do {
80
+ c = str.charCodeAt(pos);
81
+ const a = s > 180 ? OK_HIGHCHAR : ALLOWED[c - OFFS] ?? 0;
82
+ if (a & OK_N) {
83
+ // name: [a-zA-Z_0-9.\\?\u00a1-\uffff]
84
+ // func: [a-zA-Z_0-9.]
85
+ // cntx: [a-zA-Z_0-9.¡¤§¨ª\u00ad¯-\uffff]
86
+ if (name && !(a & OK_NAME_N)) {
87
+ name = 0;
88
+ }
89
+ if (func && !(a & OK_FUNC_N)) {
90
+ func = 0;
91
+ }
92
+ if (cntx && !(a & OK_CNTX_N)) {
93
+ cntx = 0;
94
+ }
95
+ }
96
+ else {
97
+ if (c === PAREN_OPEN && func) {
98
+ return { type: FUNCTION, value: str.slice(start, pos) };
99
+ }
100
+ else if (c === EXCL && cntx) {
101
+ return { type: CONTEXT, value: str.slice(start, pos) };
102
+ }
103
+ return nameOrUnknown(str, s, start, pos, name);
104
+ }
105
+ pos++;
106
+ }
107
+ while ((name || func || cntx) && pos < str.length);
108
+
109
+ if (start !== pos) {
110
+ return nameOrUnknown(str, s, start, pos, name);
111
+ }
112
+ }
@@ -0,0 +1,60 @@
1
+ import { REF_NAMED } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+
4
+ // The advertized named ranges rules are a bit off from what Excel seems to do.
5
+ // In the "extended range" of chars, it looks like it allows most things above
6
+ // U+00B0 with the range between U+00A0-U+00AF rather random:
7
+ // /^[a-zA-Z\\_¡¤§¨ª\u00ad¯\u00b0-\uffff][a-zA-Z0-9\\_.?¡¤§¨ª\u00ad¯\u00b0-\uffff]{0,254}/
8
+ //
9
+ // I've simplified to allowing everything above U+00A1:
10
+ // /^[a-zA-Z\\_\u00a1-\uffff][a-zA-Z0-9\\_.?\u00a1-\uffff]{0,254}/
11
+ export function lexNamed (str: string, pos: number): Token | undefined {
12
+ const start = pos;
13
+ // starts with: [a-zA-Z\\_\u00a1-\uffff]
14
+ const s = str.charCodeAt(pos);
15
+ if (
16
+ (s >= 65 && s <= 90) || // A-Z
17
+ (s >= 97 && s <= 122) || // a-z
18
+ (s === 95) || // _
19
+ (s === 92) || // \
20
+ (s > 0xA0) // \u00a1-\uffff
21
+ ) {
22
+ pos++;
23
+ }
24
+ else {
25
+ return;
26
+ }
27
+ // has any number of: [a-zA-Z0-9\\_.?\u00a1-\uffff]
28
+ let c: number;
29
+ do {
30
+ c = str.charCodeAt(pos);
31
+ if (
32
+ (c >= 65 && c <= 90) || // A-Z
33
+ (c >= 97 && c <= 122) || // a-z
34
+ (c >= 48 && c <= 57) || // 0-9
35
+ (c === 95) || // _
36
+ (c === 92) || // \
37
+ (c === 46) || // .
38
+ (c === 63) || // ?
39
+ (c > 0xA0) // \u00a1-\uffff
40
+ ) {
41
+ pos++;
42
+ }
43
+ else {
44
+ break;
45
+ }
46
+ } while (isFinite(c));
47
+
48
+ const len = pos - start;
49
+ if (len && len < 255) {
50
+ // names starting with \ must be at least 3 char long
51
+ if (s === 92 && len < 3) {
52
+ return;
53
+ }
54
+ // single characters R and C are forbidden as names
55
+ if (len === 1 && (s === 114 || s === 82 || s === 99 || s === 67)) {
56
+ return;
57
+ }
58
+ return { type: REF_NAMED, value: str.slice(start, pos) };
59
+ }
60
+ }
@@ -0,0 +1,12 @@
1
+ import { NEWLINE } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+
4
+ export function lexNewLine (str: string, pos: number): Token | undefined {
5
+ const start = pos;
6
+ while (str.charCodeAt(pos) === 10) {
7
+ pos++;
8
+ }
9
+ if (pos !== start) {
10
+ return { type: NEWLINE, value: str.slice(start, pos) };
11
+ }
12
+ }
@@ -0,0 +1,48 @@
1
+ import { NUMBER } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+
4
+ function advDigits (str: string, pos: number): number {
5
+ const start = pos;
6
+ do {
7
+ const c = str.charCodeAt(pos);
8
+ if (c < 48 || c > 57) { // 0-9
9
+ break;
10
+ }
11
+ pos++;
12
+ }
13
+ while (pos < str.length);
14
+ return pos - start;
15
+ }
16
+
17
+ // \d+(\.\d+)?(?:[eE][+-]?\d+)?
18
+ export function lexNumber (str: string, pos: number): Token | undefined {
19
+ const start = pos;
20
+
21
+ // integer
22
+ const lead = advDigits(str, pos);
23
+ if (!lead) { return; }
24
+ pos += lead;
25
+
26
+ // optional fraction part
27
+ const c0 = str.charCodeAt(pos);
28
+ if (c0 === 46) { // .
29
+ pos++;
30
+ const frac = advDigits(str, pos);
31
+ if (!frac) { return; }
32
+ pos += frac;
33
+ }
34
+ // optional exponent part
35
+ const c1 = str.charCodeAt(pos);
36
+ if (c1 === 69 || c1 === 101) { // E e
37
+ pos++;
38
+ const sign = str.charCodeAt(pos);
39
+ if (sign === 43 || sign === 45) { // + -
40
+ pos++;
41
+ }
42
+ const exp = advDigits(str, pos);
43
+ if (!exp) { return; }
44
+ pos += exp;
45
+ }
46
+
47
+ return { type: NUMBER, value: str.slice(start, pos) };
48
+ }
@@ -0,0 +1,26 @@
1
+ import { OPERATOR } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+
4
+ export function lexOperator (str: string, pos: number): Token | undefined {
5
+ const c0 = str.charCodeAt(pos);
6
+ const c1 = str.charCodeAt(pos + 1);
7
+ if (
8
+ (c0 === 60 && c1 === 61) || // <=
9
+ (c0 === 62 && c1 === 61) || // >=
10
+ (c0 === 60 && c1 === 62) // <>
11
+ ) {
12
+ return { type: OPERATOR, value: str.slice(pos, pos + 2) };
13
+ }
14
+ if (
15
+ // { } ! # % &
16
+ c0 === 123 || c0 === 125 || c0 === 33 || c0 === 35 || c0 === 37 || c0 === 38 ||
17
+ // ( ) * + , -
18
+ (c0 >= 40 && c0 <= 45) ||
19
+ // / : ; < = >
20
+ c0 === 47 || (c0 >= 58 && c0 <= 62) ||
21
+ // @ ^
22
+ c0 === 64 || c0 === 94
23
+ ) {
24
+ return { type: OPERATOR, value: str[pos] };
25
+ }
26
+ }
@@ -0,0 +1,15 @@
1
+ import type { Token } from '../types.ts';
2
+ import { lexRangeA1 } from './lexRangeA1.ts';
3
+ import { lexRangeR1C1 } from './lexRangeR1C1.ts';
4
+
5
+ type LexRangeOptions = {
6
+ allowTernary: boolean,
7
+ mergeRefs: boolean,
8
+ r1c1: boolean
9
+ };
10
+
11
+ export function lexRange (str: string, pos: number, options: LexRangeOptions): Token | undefined {
12
+ return options.r1c1
13
+ ? lexRangeR1C1(str, pos, options)
14
+ : lexRangeA1(str, pos, options);
15
+ }
@@ -0,0 +1,134 @@
1
+ import { REF_RANGE, REF_BEAM, REF_TERNARY, MAX_COLS, MAX_ROWS } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+ import { advRangeOp } from './advRangeOp.ts';
4
+ import { canEndRange, canEndPartialRange } from './canEndRange.ts';
5
+
6
+ function advA1Col (str: string, pos: number): number {
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: string, pos: number): number {
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 (
60
+ str: string,
61
+ pos: number,
62
+ options: { mergeRefs: boolean, allowTernary: boolean }
63
+ ): Token | undefined {
64
+ let p = pos;
65
+ const left = advA1Col(str, p);
66
+ let right = 0;
67
+ let bottom = 0;
68
+ if (left) {
69
+ // TLBR: could be A1:A1
70
+ // TL R: could be A1:A (if allowTernary)
71
+ // TLB : could be A1:1 (if allowTernary)
72
+ // LBR: could be A:A1 (if allowTernary)
73
+ // L R: could be A:A
74
+ p += left;
75
+ const top = advA1Row(str, p);
76
+ p += top;
77
+ const op = advRangeOp(str, p);
78
+ const preOp = p;
79
+ if (op) {
80
+ p += op;
81
+ right = advA1Col(str, p);
82
+ p += right;
83
+ bottom = advA1Row(str, p);
84
+ p += bottom;
85
+ if (top && bottom && right) {
86
+ if (canEndRange(str, p) && options.mergeRefs) {
87
+ return { type: REF_RANGE, value: str.slice(pos, p) };
88
+ }
89
+ }
90
+ else if (!top && !bottom) {
91
+ if (canEndRange(str, p)) {
92
+ return { type: REF_BEAM, value: str.slice(pos, p) };
93
+ }
94
+ }
95
+ else if (options.allowTernary && (bottom || right)) {
96
+ if (canEndPartialRange(str, p)) {
97
+ return { type: REF_TERNARY, value: str.slice(pos, p) };
98
+ }
99
+ }
100
+ }
101
+ // LT : this is A1
102
+ if (top && canEndRange(str, preOp)) {
103
+ return { type: REF_RANGE, value: str.slice(pos, preOp) };
104
+ }
105
+ }
106
+ else {
107
+ // T B : could be 1:1
108
+ // T BR: could be 1:A1 (if allowTernary)
109
+ const top = advA1Row(str, p);
110
+ if (top) {
111
+ p += top;
112
+ const op = advRangeOp(str, p);
113
+ if (op) {
114
+ p += op;
115
+ right = advA1Col(str, p);
116
+ if (right) {
117
+ p += right;
118
+ }
119
+ bottom = advA1Row(str, p);
120
+ p += bottom;
121
+ if (right && bottom && options.allowTernary) {
122
+ if (canEndPartialRange(str, p)) {
123
+ return { type: REF_TERNARY, value: str.slice(pos, p) };
124
+ }
125
+ }
126
+ if (!right && bottom) {
127
+ if (canEndRange(str, p)) {
128
+ return { type: REF_BEAM, value: str.slice(pos, p) };
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
@@ -0,0 +1,146 @@
1
+ import { REF_RANGE, REF_BEAM, REF_TERNARY, MAX_COLS, MAX_ROWS } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+ import { advRangeOp } from './advRangeOp.ts';
4
+ import { canEndRange } from './canEndRange.ts';
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: string, pos: number, isRow: boolean = false): number {
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 (
82
+ str: string,
83
+ pos: number,
84
+ options: { allowTernary: boolean }
85
+ ): Token | undefined {
86
+ let p = pos;
87
+ // C1
88
+ // C1:C1
89
+ // C1:R1C1 --partial
90
+ // R1
91
+ // R1:R1
92
+ // R1:R1C1 --partial
93
+ // R1C1
94
+ // R1C1:C1 --partial
95
+ // R1C1:R1 --partial
96
+ const r1 = lexR1C1Part(str, p, true);
97
+ p += r1;
98
+ const c1 = lexR1C1Part(str, p);
99
+ p += c1;
100
+ if (c1 || r1) {
101
+ const op = advRangeOp(str, p);
102
+ const preOp = p;
103
+ if (op) {
104
+ p += op;
105
+ const r2 = lexR1C1Part(str, p, true); // R1
106
+ p += r2;
107
+ const c2 = lexR1C1Part(str, p); // C1
108
+ p += c2;
109
+
110
+ // C1:R2C2 --partial
111
+ // R1:R2C2 --partial
112
+ // R1C1:C2 --partial
113
+ // R1C1:R2 --partial
114
+ if (
115
+ (r1 && !c1 && r2 && c2) ||
116
+ (!r1 && c1 && r2 && c2) ||
117
+ (r1 && c1 && r2 && !c2) ||
118
+ (r1 && c1 && !r2 && c2)
119
+ ) {
120
+ if (options.allowTernary && canEndRange(str, p)) {
121
+ return { type: REF_TERNARY, value: str.slice(pos, p) };
122
+ }
123
+ }
124
+ // C1:C2 -- beam
125
+ // R1:R2 -- beam
126
+ else if (
127
+ (c1 && c2 && !r1 && !r2) ||
128
+ (!c1 && !c2 && r1 && r2)
129
+ ) {
130
+ if (canEndRange(str, p)) {
131
+ return { type: REF_BEAM, value: str.slice(pos, p) };
132
+ }
133
+ }
134
+ // Note: we do not capture R1C1:R1C1, mergeRefTokens will join the parts
135
+ }
136
+ // R1
137
+ // C1
138
+ // R1C1
139
+ if (canEndRange(str, preOp)) {
140
+ return {
141
+ type: (r1 && c1) ? REF_RANGE : REF_BEAM,
142
+ value: str.slice(pos, preOp)
143
+ };
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,26 @@
1
+ import { OPERATOR_TRIM } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+
4
+ const PERIOD = 46;
5
+ const COLON = 58;
6
+
7
+ export function lexRangeTrim (str: string, pos: number): Token | undefined {
8
+ const c0 = str.charCodeAt(pos);
9
+ if (c0 === PERIOD || c0 === COLON) {
10
+ const c1 = str.charCodeAt(pos + 1);
11
+ if (c0 !== c1) {
12
+ if (c1 === COLON) {
13
+ return {
14
+ type: OPERATOR_TRIM,
15
+ value: str.slice(pos, pos + (str.charCodeAt(pos + 2) === PERIOD ? 3 : 2))
16
+ };
17
+ }
18
+ else if (c1 === PERIOD) {
19
+ return {
20
+ type: OPERATOR_TRIM,
21
+ value: str.slice(pos, pos + 2)
22
+ };
23
+ }
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,19 @@
1
+ import { OPERATOR } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+ import { advRangeOp } from './advRangeOp.ts';
4
+
5
+ const EXCL = 33; // !
6
+
7
+ export function lexRefOp (str: string, pos: number, opts: { r1c1: boolean }): Token | undefined {
8
+ // in R1C1 mode we only allow [ '!' ]
9
+ if (str.charCodeAt(pos) === EXCL) {
10
+ return { type: OPERATOR, value: str[pos] };
11
+ }
12
+ if (!opts.r1c1) {
13
+ // in A1 mode we allow [ '!' ] + [ ':', '.:', ':.', '.:.']
14
+ const opLen = advRangeOp(str, pos);
15
+ if (opLen) {
16
+ return { type: OPERATOR, value: str.slice(pos, pos + opLen) };
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,22 @@
1
+ import { STRING } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+
4
+ const QUOT = 34;
5
+
6
+ export function lexString (str: string, pos: number): Token | undefined {
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
+ import { parseSRange } from '../parseSRange.ts';
2
+ import { REF_STRUCT } from '../constants.ts';
3
+ import { isWS } from './lexWhitespace.ts';
4
+ import type { Token } from '../types.ts';
5
+
6
+ const EXCL = 33; // !
7
+
8
+ export function lexStructured (str: string, pos: number): Token | undefined {
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,31 @@
1
+ import { WHITESPACE } from '../constants.ts';
2
+ import type { Token } from '../types.ts';
3
+
4
+ export function isWS (c) {
5
+ return (
6
+ c === 0x9 ||
7
+ c === 0xB ||
8
+ c === 0xC ||
9
+ c === 0xD ||
10
+ c === 0x20 ||
11
+ c === 0xA0 ||
12
+ c === 0x1680 ||
13
+ c === 0x2028 ||
14
+ c === 0x2029 ||
15
+ c === 0x202f ||
16
+ c === 0x205f ||
17
+ c === 0x3000 ||
18
+ c === 0xfeff ||
19
+ (c >= 0x2000 && c <= 0x200a)
20
+ );
21
+ }
22
+
23
+ export function lexWhitespace (str: string, pos: number): Token | undefined {
24
+ const start = pos;
25
+ while (isWS(str.charCodeAt(pos))) {
26
+ pos++;
27
+ }
28
+ if (pos !== start) {
29
+ return { type: WHITESPACE, value: str.slice(start, pos) };
30
+ }
31
+ }