@cplieger/deadset-ts 8.0.0-dev.11 → 8.0.0-dev.13

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 (73) hide show
  1. package/README.md +2 -0
  2. package/THIRD_PARTY_NOTICES.md +27 -0
  3. package/dist/src/build-scripts.js +6 -13
  4. package/dist/src/commands/read.js +156 -0
  5. package/dist/src/commands/shell.js +75 -0
  6. package/dist/src/commands/tools.js +460 -0
  7. package/dist/src/configuration-files.js +11 -25
  8. package/dist/src/conformance-record.js +1 -1
  9. package/dist/src/dependencies.js +67 -115
  10. package/dist/src/load/synthesize.js +12 -5
  11. package/dist/src/manifest.js +27 -14
  12. package/dist/src/roots.js +18 -11
  13. package/dist/src/text/named-files.js +24 -3
  14. package/dist/src/text/yaml.js +464 -173
  15. package/dist/src/vendor/unbash/ansi-c.js +132 -0
  16. package/dist/src/vendor/unbash/arithmetic-parts.js +37 -0
  17. package/dist/src/vendor/unbash/arithmetic.js +591 -0
  18. package/dist/src/vendor/unbash/assignment.js +131 -0
  19. package/dist/src/vendor/unbash/chars.js +48 -0
  20. package/dist/src/vendor/unbash/command.js +41 -0
  21. package/dist/src/vendor/unbash/expansion.js +80 -0
  22. package/dist/src/vendor/unbash/glob.js +268 -0
  23. package/dist/src/vendor/unbash/heredoc.js +27 -0
  24. package/dist/src/vendor/unbash/internal-types.js +1 -0
  25. package/dist/src/vendor/unbash/lexer.js +4274 -0
  26. package/dist/src/vendor/unbash/limits.js +3 -0
  27. package/dist/src/vendor/unbash/parser.js +1366 -0
  28. package/dist/src/vendor/unbash/parts.js +57 -0
  29. package/dist/src/vendor/unbash/substitution-context.js +8 -0
  30. package/dist/src/vendor/unbash/traversal.js +212 -0
  31. package/dist/src/vendor/unbash/types.js +1 -0
  32. package/dist/src/vendor/unbash/word-value.js +61 -0
  33. package/dist/src/vendor/unbash/word.js +40 -0
  34. package/dist/src/workflow-steps.js +111 -133
  35. package/jsr.json +2 -1
  36. package/package.json +9 -7
  37. package/src/build-scripts.ts +6 -13
  38. package/src/commands/read.ts +230 -0
  39. package/src/commands/shell.ts +126 -0
  40. package/src/commands/tools.ts +618 -0
  41. package/src/configuration-files.ts +10 -26
  42. package/src/conformance-record.ts +1 -1
  43. package/src/dependencies.ts +99 -123
  44. package/src/load/synthesize.ts +18 -5
  45. package/src/manifest.ts +43 -25
  46. package/src/roots.ts +20 -13
  47. package/src/text/named-files.ts +26 -3
  48. package/src/text/yaml.ts +503 -179
  49. package/src/vendor/unbash/LICENSE +15 -0
  50. package/src/vendor/unbash/VENDORED.md +16 -0
  51. package/src/vendor/unbash/ansi-c.ts +132 -0
  52. package/src/vendor/unbash/arithmetic-parts.ts +53 -0
  53. package/src/vendor/unbash/arithmetic.ts +641 -0
  54. package/src/vendor/unbash/assignment.ts +163 -0
  55. package/src/vendor/unbash/chars.ts +48 -0
  56. package/src/vendor/unbash/command.ts +47 -0
  57. package/src/vendor/unbash/expansion.ts +144 -0
  58. package/src/vendor/unbash/glob.ts +284 -0
  59. package/src/vendor/unbash/heredoc.ts +34 -0
  60. package/src/vendor/unbash/internal-types.ts +94 -0
  61. package/src/vendor/unbash/lexer.ts +4352 -0
  62. package/src/vendor/unbash/limits.ts +3 -0
  63. package/src/vendor/unbash/parser.ts +1612 -0
  64. package/src/vendor/unbash/parts.ts +96 -0
  65. package/src/vendor/unbash/substitution-context.ts +20 -0
  66. package/src/vendor/unbash/traversal.ts +182 -0
  67. package/src/vendor/unbash/types.ts +660 -0
  68. package/src/vendor/unbash/word-value.ts +64 -0
  69. package/src/vendor/unbash/word.ts +50 -0
  70. package/src/workflow-steps.ts +153 -154
  71. package/dist/src/text/shell.js +0 -159
  72. package/src/text/named-files.inputs.json +0 -1522
  73. package/src/text/shell.ts +0 -161
@@ -0,0 +1,591 @@
1
+ import { ScriptExpansionImpl } from "./expansion.js";
2
+ import { MAX_SYNTAX_NESTING } from "./limits.js";
3
+ import { CH_TAB, CH_NL, CH_SPACE, CH_BANG, CH_DOLLAR, CH_PERCENT, CH_AMP, CH_LPAREN, CH_RPAREN, CH_STAR, CH_PLUS, CH_COMMA, CH_DASH, CH_SLASH, CH_0, CH_9, CH_COLON, CH_LT, CH_EQ, CH_GT, CH_QUESTION, CH_A, CH_Z, CH_LBRACKET, CH_RBRACKET, CH_CARET, CH_UNDERSCORE, CH_a, CH_z, CH_LBRACE, CH_RBRACE, CH_PIPE, CH_TILDE, } from "./chars.js";
4
+ function opPrec(op) {
5
+ switch (op) {
6
+ case ",":
7
+ return 1;
8
+ case "=":
9
+ case "+=":
10
+ case "-=":
11
+ case "*=":
12
+ case "/=":
13
+ case "%=":
14
+ case "<<=":
15
+ case ">>=":
16
+ case "&=":
17
+ case "|=":
18
+ case "^=":
19
+ return 2;
20
+ case "||":
21
+ return 4;
22
+ case "&&":
23
+ return 5;
24
+ case "|":
25
+ return 6;
26
+ case "^":
27
+ return 7;
28
+ case "&":
29
+ return 8;
30
+ case "==":
31
+ case "!=":
32
+ return 9;
33
+ case "<":
34
+ case "<=":
35
+ case ">":
36
+ case ">=":
37
+ return 10;
38
+ case "<<":
39
+ case ">>":
40
+ return 11;
41
+ case "+":
42
+ case "-":
43
+ return 12;
44
+ case "*":
45
+ case "/":
46
+ case "%":
47
+ return 13;
48
+ case "**":
49
+ return 14;
50
+ case "?":
51
+ return 3;
52
+ default:
53
+ return -1;
54
+ }
55
+ }
56
+ function trimEnd(src, start, end) {
57
+ while (end > start) {
58
+ const c = src.charCodeAt(end - 1);
59
+ if (c !== CH_SPACE && c !== CH_TAB && c !== CH_NL)
60
+ break;
61
+ end--;
62
+ }
63
+ return end;
64
+ }
65
+ function opRightAssoc(op) {
66
+ switch (op) {
67
+ case "=":
68
+ case "+=":
69
+ case "-=":
70
+ case "*=":
71
+ case "/=":
72
+ case "%=":
73
+ case "<<=":
74
+ case ">>=":
75
+ case "&=":
76
+ case "|=":
77
+ case "^=":
78
+ case "**":
79
+ return true;
80
+ default:
81
+ return false;
82
+ }
83
+ }
84
+ export function parseArithmeticExpression(src, offset = 0, collector, nestingDepth = 0) {
85
+ let pos = 0;
86
+ const len = src.length;
87
+ const initialWordCount = collector?.embeddedWords.length ?? 0;
88
+ function makeWord(start, end, embedded = false) {
89
+ const node = {
90
+ type: "ArithmeticWord",
91
+ pos: start + offset,
92
+ end: end + offset,
93
+ value: src.slice(start, end),
94
+ parts: undefined,
95
+ };
96
+ if (embedded)
97
+ collector?.embeddedWords.push(node);
98
+ return node;
99
+ }
100
+ function skipWS() {
101
+ while (pos < len) {
102
+ const c = src.charCodeAt(pos);
103
+ if (c === CH_SPACE || c === CH_TAB || c === CH_NL)
104
+ pos++;
105
+ else
106
+ break;
107
+ }
108
+ }
109
+ function tryReadBinOp() {
110
+ if (pos >= len)
111
+ return null;
112
+ const c = src.charCodeAt(pos);
113
+ const nc = pos + 1 < len ? src.charCodeAt(pos + 1) : 0;
114
+ const nnc = pos + 2 < len ? src.charCodeAt(pos + 2) : 0;
115
+ switch (c) {
116
+ case CH_COMMA:
117
+ pos++;
118
+ return ",";
119
+ case CH_EQ:
120
+ if (nc === CH_EQ) {
121
+ pos += 2;
122
+ return "==";
123
+ }
124
+ pos++;
125
+ return "=";
126
+ case CH_BANG:
127
+ if (nc === CH_EQ) {
128
+ pos += 2;
129
+ return "!=";
130
+ }
131
+ return null; // unary
132
+ case CH_LT:
133
+ if (nc === CH_LT) {
134
+ if (nnc === CH_EQ) {
135
+ pos += 3;
136
+ return "<<=";
137
+ }
138
+ pos += 2;
139
+ return "<<";
140
+ }
141
+ if (nc === CH_EQ) {
142
+ pos += 2;
143
+ return "<=";
144
+ }
145
+ pos++;
146
+ return "<";
147
+ case CH_GT:
148
+ if (nc === CH_GT) {
149
+ if (nnc === CH_EQ) {
150
+ pos += 3;
151
+ return ">>=";
152
+ }
153
+ pos += 2;
154
+ return ">>";
155
+ }
156
+ if (nc === CH_EQ) {
157
+ pos += 2;
158
+ return ">=";
159
+ }
160
+ pos++;
161
+ return ">";
162
+ case CH_PLUS:
163
+ if (nc === CH_EQ) {
164
+ pos += 2;
165
+ return "+=";
166
+ }
167
+ if (nc === CH_PLUS)
168
+ return null; // postfix/prefix, not binary
169
+ pos++;
170
+ return "+";
171
+ case CH_DASH:
172
+ if (nc === CH_EQ) {
173
+ pos += 2;
174
+ return "-=";
175
+ }
176
+ if (nc === CH_DASH)
177
+ return null; // postfix/prefix, not binary
178
+ pos++;
179
+ return "-";
180
+ case CH_STAR:
181
+ if (nc === CH_STAR) {
182
+ pos += 2;
183
+ return "**";
184
+ }
185
+ if (nc === CH_EQ) {
186
+ pos += 2;
187
+ return "*=";
188
+ }
189
+ pos++;
190
+ return "*";
191
+ case CH_SLASH:
192
+ if (nc === CH_EQ) {
193
+ pos += 2;
194
+ return "/=";
195
+ }
196
+ pos++;
197
+ return "/";
198
+ case CH_PERCENT:
199
+ if (nc === CH_EQ) {
200
+ pos += 2;
201
+ return "%=";
202
+ }
203
+ pos++;
204
+ return "%";
205
+ case CH_PIPE:
206
+ if (nc === CH_PIPE) {
207
+ pos += 2;
208
+ return "||";
209
+ }
210
+ if (nc === CH_EQ) {
211
+ pos += 2;
212
+ return "|=";
213
+ }
214
+ pos++;
215
+ return "|";
216
+ case CH_AMP:
217
+ if (nc === CH_AMP) {
218
+ pos += 2;
219
+ return "&&";
220
+ }
221
+ if (nc === CH_EQ) {
222
+ pos += 2;
223
+ return "&=";
224
+ }
225
+ pos++;
226
+ return "&";
227
+ case CH_CARET:
228
+ if (nc === CH_EQ) {
229
+ pos += 2;
230
+ return "^=";
231
+ }
232
+ pos++;
233
+ return "^";
234
+ case CH_QUESTION:
235
+ pos++;
236
+ return "?";
237
+ default:
238
+ return null;
239
+ }
240
+ }
241
+ function skipTruncatedUnary() {
242
+ skipWS();
243
+ while (pos < len) {
244
+ const c = src.charCodeAt(pos);
245
+ if (c !== CH_BANG && c !== CH_TILDE && c !== CH_PLUS && c !== CH_DASH)
246
+ break;
247
+ if (src.charCodeAt(pos + 1) === CH_EQ)
248
+ break;
249
+ pos++;
250
+ skipWS();
251
+ }
252
+ if (src.charCodeAt(pos) === CH_LPAREN) {
253
+ let groups = 1;
254
+ pos++;
255
+ while (pos < len && groups > 0) {
256
+ const wordEnd = collector?.findArithmeticWordEnd?.(pos + offset, offset + len) ?? pos + offset;
257
+ if (wordEnd > pos + offset) {
258
+ pos = wordEnd - offset;
259
+ continue;
260
+ }
261
+ const c = src.charCodeAt(pos++);
262
+ if (c === CH_LPAREN)
263
+ groups++;
264
+ else if (c === CH_RPAREN)
265
+ groups--;
266
+ }
267
+ }
268
+ else if (pos < len && src.charCodeAt(pos) !== CH_RPAREN && src.charCodeAt(pos) !== CH_COLON) {
269
+ parseAtom(0);
270
+ }
271
+ skipWS();
272
+ const c = src.charCodeAt(pos);
273
+ if ((c === CH_PLUS || c === CH_DASH) && src.charCodeAt(pos + 1) === c)
274
+ pos += 2;
275
+ }
276
+ function recoverExpression(minPrec) {
277
+ skipWS();
278
+ const start = pos;
279
+ if (start === len)
280
+ return makeWord(start, start);
281
+ const wordCount = collector?.embeddedWords.length ?? 0;
282
+ let ternaries = 0;
283
+ for (;;) {
284
+ skipTruncatedUnary();
285
+ skipWS();
286
+ if (src.charCodeAt(pos) === CH_COLON && ternaries > 0) {
287
+ ternaries--;
288
+ pos++;
289
+ continue;
290
+ }
291
+ const saved = pos;
292
+ const op = tryReadBinOp();
293
+ if (op === null || (ternaries === 0 && opPrec(op) < minPrec)) {
294
+ pos = saved;
295
+ break;
296
+ }
297
+ if (op === "?")
298
+ ternaries++;
299
+ }
300
+ if (collector)
301
+ collector.embeddedWords.length = wordCount;
302
+ return makeWord(start, trimEnd(src, start, pos));
303
+ }
304
+ function parseBinExpr(minPrec, depth) {
305
+ if (depth > MAX_SYNTAX_NESTING)
306
+ return recoverExpression(minPrec);
307
+ let left = parseUnaryExpr(depth);
308
+ while (true) {
309
+ skipWS();
310
+ if (pos >= len)
311
+ break;
312
+ const saved = pos;
313
+ const op = tryReadBinOp();
314
+ if (!op)
315
+ break;
316
+ // Ternary
317
+ if (op === "?") {
318
+ if (opPrec(op) < minPrec) {
319
+ pos = saved;
320
+ break;
321
+ }
322
+ const consequent = parseBinExpr(1, depth + 1);
323
+ skipWS();
324
+ if (pos < len && src.charCodeAt(pos) === CH_COLON)
325
+ pos++;
326
+ const alternate = parseBinExpr(3, depth + 1);
327
+ left = { type: "ArithmeticTernary", pos: left.pos, end: alternate.end, test: left, consequent, alternate };
328
+ continue;
329
+ }
330
+ const prec = opPrec(op);
331
+ if (prec < minPrec) {
332
+ pos = saved;
333
+ break;
334
+ }
335
+ const nextPrec = opRightAssoc(op) ? prec : prec + 1;
336
+ const right = parseBinExpr(nextPrec, depth + 1);
337
+ left = { type: "ArithmeticBinary", pos: left.pos, end: right.end, operator: op, left, right };
338
+ }
339
+ return left;
340
+ }
341
+ function parseUnaryExpr(depth) {
342
+ skipWS();
343
+ if (depth > MAX_SYNTAX_NESTING)
344
+ return recoverExpression(15);
345
+ if (pos >= len)
346
+ return makeWord(pos, pos);
347
+ const start = pos;
348
+ const c = src.charCodeAt(pos);
349
+ const nc = pos + 1 < len ? src.charCodeAt(pos + 1) : 0;
350
+ // Prefix ++ --
351
+ if (c === CH_PLUS && nc === CH_PLUS) {
352
+ pos += 2;
353
+ const operand = parseUnaryExpr(depth + 1);
354
+ return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "++", operand, prefix: true };
355
+ }
356
+ if (c === CH_DASH && nc === CH_DASH) {
357
+ pos += 2;
358
+ const operand = parseUnaryExpr(depth + 1);
359
+ return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "--", operand, prefix: true };
360
+ }
361
+ // Unary ! ~ + -
362
+ if (c === CH_BANG) {
363
+ pos++;
364
+ const operand = parseUnaryExpr(depth + 1);
365
+ return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "!", operand, prefix: true };
366
+ }
367
+ if (c === CH_TILDE) {
368
+ pos++;
369
+ const operand = parseUnaryExpr(depth + 1);
370
+ return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "~", operand, prefix: true };
371
+ }
372
+ if (c === CH_PLUS && nc !== CH_PLUS && nc !== CH_EQ) {
373
+ pos++;
374
+ const operand = parseUnaryExpr(depth + 1);
375
+ return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "+", operand, prefix: true };
376
+ }
377
+ if (c === CH_DASH && nc !== CH_DASH && nc !== CH_EQ) {
378
+ pos++;
379
+ const operand = parseUnaryExpr(depth + 1);
380
+ return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "-", operand, prefix: true };
381
+ }
382
+ return parsePostfixExpr(depth);
383
+ }
384
+ function parsePostfixExpr(depth) {
385
+ const operand = parseAtom(depth);
386
+ skipWS();
387
+ if (pos + 1 < len) {
388
+ const c = src.charCodeAt(pos);
389
+ const nc = src.charCodeAt(pos + 1);
390
+ if (c === CH_PLUS && nc === CH_PLUS) {
391
+ pos += 2;
392
+ return { type: "ArithmeticUnary", pos: operand.pos, end: pos + offset, operator: "++", operand, prefix: false };
393
+ }
394
+ if (c === CH_DASH && nc === CH_DASH) {
395
+ pos += 2;
396
+ return { type: "ArithmeticUnary", pos: operand.pos, end: pos + offset, operator: "--", operand, prefix: false };
397
+ }
398
+ }
399
+ return operand;
400
+ }
401
+ function parseAtom(depth) {
402
+ skipWS();
403
+ if (pos >= len)
404
+ return makeWord(pos, pos);
405
+ const c = src.charCodeAt(pos);
406
+ // Parenthesized expression
407
+ if (c === CH_LPAREN) {
408
+ const start = pos;
409
+ pos++;
410
+ const expr = parseBinExpr(0, depth + 1);
411
+ skipWS();
412
+ if (pos < len && src.charCodeAt(pos) === CH_RPAREN)
413
+ pos++;
414
+ return { type: "ArithmeticGroup", pos: start + offset, end: pos + offset, expression: expr };
415
+ }
416
+ // Dollar expansion
417
+ if (c === CH_DOLLAR) {
418
+ const start = pos;
419
+ const wordCount = collector?.embeddedWords.length ?? 0;
420
+ const atom = readDollarAtom();
421
+ const wordEnd = collector?.findArithmeticWordEnd?.(start + offset, offset + len) ?? pos + offset;
422
+ if (wordEnd > pos + offset) {
423
+ if (collector)
424
+ collector.embeddedWords.length = wordCount;
425
+ pos = wordEnd - offset;
426
+ return makeWord(start, pos, true);
427
+ }
428
+ return atom;
429
+ }
430
+ if (c === 0x60 /* ` */ || c === 0x22 /* " */ || c === 0x27 /* ' */) {
431
+ const start = pos;
432
+ pos = (collector?.findArithmeticWordEnd?.(start + offset, offset + len) ?? start + offset + 1) - offset;
433
+ return makeWord(start, pos, true);
434
+ }
435
+ // Number or variable name
436
+ const start = pos;
437
+ const wordCount = collector?.embeddedWords.length ?? 0;
438
+ const atom = readWordAtom();
439
+ const wordEnd = collector?.findArithmeticWordEnd?.(start + offset, offset + len) ?? pos + offset;
440
+ if (wordEnd > pos + offset) {
441
+ if (collector)
442
+ collector.embeddedWords.length = wordCount;
443
+ pos = wordEnd - offset;
444
+ return makeWord(start, pos, true);
445
+ }
446
+ return atom;
447
+ }
448
+ function readDollarAtom() {
449
+ const start = pos;
450
+ pos++; // skip $
451
+ if (pos >= len)
452
+ return makeWord(start, pos);
453
+ const c = src.charCodeAt(pos);
454
+ if (c === CH_LPAREN) {
455
+ if (pos + 1 < len && src.charCodeAt(pos + 1) === CH_LPAREN) {
456
+ // $(( nested arithmetic ))
457
+ const expansionEnd = collector?.findArithmeticExpansionEnd(start + offset, offset + len) ?? -1;
458
+ if (expansionEnd !== -1) {
459
+ pos = expansionEnd - offset;
460
+ }
461
+ else {
462
+ pos += 2;
463
+ let depth = 1;
464
+ while (pos < len && depth > 0) {
465
+ if (src.charCodeAt(pos) === CH_LPAREN && src.charCodeAt(pos + 1) === CH_LPAREN) {
466
+ depth++;
467
+ pos += 2;
468
+ }
469
+ else if (src.charCodeAt(pos) === CH_RPAREN && src.charCodeAt(pos + 1) === CH_RPAREN) {
470
+ depth--;
471
+ pos += 2;
472
+ }
473
+ else {
474
+ pos++;
475
+ }
476
+ }
477
+ }
478
+ }
479
+ else {
480
+ // $( command substitution )
481
+ pos++; // skip (
482
+ const close = collector?.findClosingParenthesis(pos + offset, offset + len) ?? -1;
483
+ if (close !== -1) {
484
+ pos = close - offset + 1;
485
+ }
486
+ else {
487
+ let depth = 1;
488
+ while (pos < len && depth > 0) {
489
+ const ch = src.charCodeAt(pos++);
490
+ if (ch === CH_LPAREN)
491
+ depth++;
492
+ else if (ch === CH_RPAREN)
493
+ depth--;
494
+ }
495
+ }
496
+ const text = src.slice(start, pos);
497
+ return collector
498
+ ? collector.commandExpansion(start + offset, pos + offset, text)
499
+ : new ScriptExpansionImpl("ArithmeticCommandExpansion", start + offset, pos + offset, text);
500
+ }
501
+ }
502
+ else if (c === CH_LBRACE) {
503
+ // ${ parameter expansion }
504
+ const close = collector?.findClosingBrace(pos + offset + 1, offset + len) ?? -1;
505
+ if (close !== -1) {
506
+ pos = close - offset + 1;
507
+ }
508
+ else {
509
+ pos++;
510
+ let depth = 1;
511
+ while (pos < len && depth > 0) {
512
+ const ch = src.charCodeAt(pos++);
513
+ if (ch === CH_LBRACE)
514
+ depth++;
515
+ else if (ch === CH_RBRACE)
516
+ depth--;
517
+ }
518
+ }
519
+ }
520
+ else {
521
+ // $var
522
+ while (pos < len) {
523
+ const ch = src.charCodeAt(pos);
524
+ if ((ch >= CH_a && ch <= CH_z) ||
525
+ (ch >= CH_A && ch <= CH_Z) ||
526
+ (ch >= CH_0 && ch <= CH_9) ||
527
+ ch === CH_UNDERSCORE)
528
+ pos++;
529
+ else
530
+ break;
531
+ }
532
+ }
533
+ return makeWord(start, pos, c === CH_LPAREN || c === CH_LBRACE);
534
+ }
535
+ function readWordAtom() {
536
+ const start = pos;
537
+ while (pos < len) {
538
+ const c = src.charCodeAt(pos);
539
+ if ((c >= CH_0 && c <= CH_9) ||
540
+ (c >= CH_A && c <= CH_Z) ||
541
+ (c >= CH_a && c <= CH_z) ||
542
+ c === CH_UNDERSCORE ||
543
+ c === 35 // # for base-N literals like 2#101
544
+ ) {
545
+ pos++;
546
+ }
547
+ else
548
+ break;
549
+ }
550
+ // Array subscript: var[expr]
551
+ if (pos > start && pos < len && src.charCodeAt(pos) === CH_LBRACKET) {
552
+ const close = collector?.findClosingBracket?.(pos + offset + 1, offset + len) ?? -1;
553
+ if (close !== -1) {
554
+ pos = close - offset + 1;
555
+ }
556
+ else {
557
+ pos++;
558
+ let depth = 1;
559
+ while (pos < len && depth > 0) {
560
+ const c = src.charCodeAt(pos);
561
+ if (c === CH_LBRACKET)
562
+ depth++;
563
+ else if (c === CH_RBRACKET)
564
+ depth--;
565
+ pos++;
566
+ }
567
+ }
568
+ return makeWord(start, pos, true);
569
+ }
570
+ if (pos === start) {
571
+ // Unknown character — advance to prevent infinite loop
572
+ pos++;
573
+ return makeWord(start, pos);
574
+ }
575
+ return makeWord(start, pos);
576
+ }
577
+ skipWS();
578
+ if (pos >= len)
579
+ return null;
580
+ const start = pos;
581
+ const result = parseBinExpr(0, nestingDepth);
582
+ skipWS();
583
+ if (pos < len) {
584
+ // Unparsed tokens remain: a partial tree would silently drop them, so keep the whole body
585
+ // as one word. With embedded structure its parts still expose nested substitutions.
586
+ if (collector)
587
+ collector.embeddedWords.length = initialWordCount;
588
+ return makeWord(start, trimEnd(src, start, len), collector !== undefined);
589
+ }
590
+ return result;
591
+ }