@cplieger/deadset-ts 8.0.0-dev.12 → 8.0.0-dev.14

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