@cplieger/deadset-ts 8.0.0-dev.12 → 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 +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,163 @@
1
+ // Code generated by scripts/vendor-unbash.ts from unbash 6.0.0 (968c14992a43dc06ce29c9f51858abd4a87851ea). DO NOT EDIT.
2
+ // @ts-nocheck
3
+ import { Lexer, LexContext, Token } from "./lexer.ts";
4
+ import { skipLineContinuations } from "./chars.ts";
5
+ import { arrayPartsResolver, computeBareWordParts, embeddedPartsResolver, wordPartsResolver } from "./parts.ts";
6
+ import type { ArrayValue, Assignment, Word } from "./internal-types.ts";
7
+ import { WordImpl } from "./word.ts";
8
+ import type { SubstitutionContext } from "./substitution-context.ts";
9
+
10
+ class ArrayValueImpl implements ArrayValue {
11
+ type = "ArrayValue" as const;
12
+ pos: number;
13
+ end: number;
14
+ #source: string;
15
+ #depth: number;
16
+ #context: SubstitutionContext;
17
+ #elements: Word[] | undefined;
18
+
19
+ constructor(source: string, pos: number, end: number, depth: number, context: SubstitutionContext) {
20
+ this.pos = pos;
21
+ this.end = end;
22
+ this.#source = source;
23
+ this.#depth = depth;
24
+ this.#context = context;
25
+ }
26
+
27
+ get elements(): Word[] {
28
+ if (this.#elements) return this.#elements;
29
+ const lexer = new Lexer(this.#source, this.pos + 1, this.end - 1, false, this.#context);
30
+ lexer._nestingDepth = this.#depth;
31
+ const elements: Word[] = [];
32
+ while (lexer.peek(LexContext.ArrayElement).token !== Token.EOF) {
33
+ const token = lexer.next(LexContext.ArrayElement);
34
+ if (token.token !== Token.Word && token.token !== Token.Assignment) continue;
35
+ const text = token.raw ? token.value : this.#source.slice(token.pos, token.end);
36
+ const indexed = text.charCodeAt(0) === 91;
37
+ const context = lexer.getSubstitutionContext();
38
+ const source = !indexed && token.raw && token.keywordEligible ? undefined : this.#source;
39
+ const resolver =
40
+ source === undefined
41
+ ? computeBareWordParts
42
+ : indexed
43
+ ? arrayPartsResolver(context)
44
+ : wordPartsResolver(context);
45
+ elements.push(new WordImpl(text, token.pos, token.end, source, resolver, this.#depth));
46
+ }
47
+ this.#elements = elements;
48
+ return elements;
49
+ }
50
+
51
+ toJSON(): ArrayValue {
52
+ return { type: this.type, pos: this.pos, end: this.end, elements: this.elements };
53
+ }
54
+ }
55
+
56
+ export class AssignmentImpl implements Assignment {
57
+ type = "Assignment" as const;
58
+ pos: number;
59
+ end: number;
60
+ text: string;
61
+ #source: string;
62
+ #depth: number;
63
+ #context: SubstitutionContext;
64
+ #operatorPos: number;
65
+ #name: string | null = null;
66
+ #append: boolean | undefined;
67
+ #index: Word | undefined;
68
+ #value: Word | ArrayValue | null = null;
69
+
70
+ constructor(
71
+ text: string,
72
+ pos: number,
73
+ end: number,
74
+ source: string,
75
+ operatorPos: number,
76
+ depth: number,
77
+ context: SubstitutionContext,
78
+ ) {
79
+ this.pos = pos;
80
+ this.end = end;
81
+ this.text = text;
82
+ this.#source = source;
83
+ this.#operatorPos = operatorPos;
84
+ this.#depth = depth;
85
+ this.#context = context;
86
+ }
87
+
88
+ get name(): string {
89
+ return this.#name ?? this.resolveTarget();
90
+ }
91
+
92
+ get append(): boolean | undefined {
93
+ if (this.#name === null) this.resolveTarget();
94
+ return this.#append;
95
+ }
96
+
97
+ get index(): Word | undefined {
98
+ if (this.#name === null) this.resolveTarget();
99
+ return this.#index;
100
+ }
101
+
102
+ get value(): Word | ArrayValue {
103
+ if (this.#value !== null) return this.#value;
104
+ const start = this.#operatorPos + 1;
105
+ const arrayStart = skipLineContinuations(this.#source, start, this.end);
106
+ this.#value =
107
+ this.#source.charCodeAt(arrayStart) === 40 && this.#source.charCodeAt(this.end - 1) === 41
108
+ ? new ArrayValueImpl(this.#source, arrayStart, this.end, this.#depth, this.#context)
109
+ : new WordImpl(
110
+ this.#source.slice(start, this.end),
111
+ start,
112
+ this.end,
113
+ this.#source,
114
+ wordPartsResolver(this.#context),
115
+ this.#depth,
116
+ );
117
+ return this.#value;
118
+ }
119
+
120
+ private resolveTarget(): string {
121
+ const equal = this.#operatorPos - this.pos;
122
+ let nameEnd = equal;
123
+ let appendPos = equal;
124
+ while (appendPos >= 2 && this.text.charCodeAt(appendPos - 2) === 92 && this.text.charCodeAt(appendPos - 1) === 10)
125
+ appendPos -= 2;
126
+ if (this.text.charCodeAt(appendPos - 1) === 43) {
127
+ this.#append = true;
128
+ nameEnd = appendPos - 1;
129
+ }
130
+ const bracket = this.text.indexOf("[");
131
+ if (bracket > 0 && bracket < nameEnd) {
132
+ const close = this.text.lastIndexOf("]", equal);
133
+ if (close > bracket) {
134
+ const start = this.pos + bracket + 1;
135
+ const end = this.pos + close;
136
+ this.#index = new WordImpl(
137
+ this.#source.slice(start, end),
138
+ start,
139
+ end,
140
+ this.#source,
141
+ embeddedPartsResolver(this.#context),
142
+ this.#depth,
143
+ );
144
+ nameEnd = bracket;
145
+ }
146
+ }
147
+ const name = this.text.slice(0, nameEnd);
148
+ return (this.#name = name.includes("\\\n") ? name.split("\\\n").join("") : name);
149
+ }
150
+
151
+ toJSON(): Assignment {
152
+ return {
153
+ type: this.type,
154
+ pos: this.pos,
155
+ end: this.end,
156
+ text: this.text,
157
+ name: this.name,
158
+ append: this.append,
159
+ index: this.index,
160
+ value: this.value,
161
+ };
162
+ }
163
+ }
@@ -0,0 +1,48 @@
1
+ // Code generated by scripts/vendor-unbash.ts from unbash 6.0.0 (968c14992a43dc06ce29c9f51858abd4a87851ea). DO NOT EDIT.
2
+ // @ts-nocheck
3
+ export const CH_TAB = 9;
4
+ export const CH_NL = 10;
5
+ export const CH_SPACE = 32;
6
+ export const CH_BANG = 33;
7
+ export const CH_DQUOTE = 34;
8
+ export const CH_HASH = 35;
9
+ export const CH_DOLLAR = 36;
10
+ export const CH_PERCENT = 37;
11
+ export const CH_AMP = 38;
12
+ export const CH_SQUOTE = 39;
13
+ export const CH_LPAREN = 40;
14
+ export const CH_RPAREN = 41;
15
+ export const CH_STAR = 42;
16
+ export const CH_PLUS = 43;
17
+ export const CH_COMMA = 44;
18
+ export const CH_DASH = 45;
19
+ export const CH_DOT = 46;
20
+ export const CH_SLASH = 47;
21
+ export const CH_0 = 48;
22
+ export const CH_9 = 57;
23
+ export const CH_COLON = 58;
24
+ export const CH_SEMI = 59;
25
+ export const CH_LT = 60;
26
+ export const CH_EQ = 61;
27
+ export const CH_GT = 62;
28
+ export const CH_QUESTION = 63;
29
+ export const CH_AT = 64;
30
+ export const CH_A = 65;
31
+ export const CH_Z = 90;
32
+ export const CH_LBRACKET = 91;
33
+ export const CH_BACKSLASH = 92;
34
+ export const CH_RBRACKET = 93;
35
+ export const CH_CARET = 94;
36
+ export const CH_UNDERSCORE = 95;
37
+ export const CH_BACKTICK = 96;
38
+ export const CH_a = 97;
39
+ export const CH_z = 122;
40
+ export const CH_LBRACE = 123;
41
+ export const CH_PIPE = 124;
42
+ export const CH_RBRACE = 125;
43
+ export const CH_TILDE = 126;
44
+
45
+ export function skipLineContinuations(source: string, pos: number, end: number): number {
46
+ while (pos + 1 < end && source.charCodeAt(pos) === CH_BACKSLASH && source.charCodeAt(pos + 1) === CH_NL) pos += 2;
47
+ return pos;
48
+ }
@@ -0,0 +1,47 @@
1
+ // Code generated by scripts/vendor-unbash.ts from unbash 6.0.0 (968c14992a43dc06ce29c9f51858abd4a87851ea). DO NOT EDIT.
2
+ // @ts-nocheck
3
+ import type { Command, CommandArgument, Redirection, Word } from "./internal-types.ts";
4
+
5
+ const isArgument = (item: CommandArgument | Redirection): item is CommandArgument =>
6
+ item.type === "Word" || item.type === "Assignment";
7
+
8
+ export class CommandImpl implements Command {
9
+ type = "Command" as const;
10
+ pos: number;
11
+ end: number;
12
+ name: Word | undefined;
13
+ prefix: Command["prefix"];
14
+ suffix: Command["suffix"];
15
+ #args: CommandArgument[] | undefined;
16
+ #redirects: Redirection[] | undefined;
17
+
18
+ constructor(pos: number, end: number, name: Word | undefined, prefix: Command["prefix"], suffix: Command["suffix"]) {
19
+ this.pos = pos;
20
+ this.end = end;
21
+ this.name = name;
22
+ this.prefix = prefix;
23
+ this.suffix = suffix;
24
+ }
25
+
26
+ get args(): CommandArgument[] {
27
+ if (this.#args === undefined) {
28
+ const suffix = this.suffix;
29
+ this.#args = suffix.every(isArgument) ? suffix : suffix.filter(isArgument);
30
+ }
31
+ return this.#args;
32
+ }
33
+
34
+ get redirects(): Redirection[] {
35
+ if (this.#redirects === undefined) {
36
+ const redirects: Redirection[] = [];
37
+ for (const item of this.prefix) if (item.type !== "Assignment") redirects.push(item);
38
+ for (const item of this.suffix) if (!isArgument(item)) redirects.push(item);
39
+ this.#redirects = redirects;
40
+ }
41
+ return this.#redirects;
42
+ }
43
+
44
+ toJSON(): Omit<Command, "args" | "redirects"> {
45
+ return { type: this.type, pos: this.pos, end: this.end, name: this.name, prefix: this.prefix, suffix: this.suffix };
46
+ }
47
+ }
@@ -0,0 +1,144 @@
1
+ // Code generated by scripts/vendor-unbash.ts from unbash 6.0.0 (968c14992a43dc06ce29c9f51858abd4a87851ea). DO NOT EDIT.
2
+ // @ts-nocheck
3
+ import type {
4
+ ArithmeticExpression,
5
+ ArithmeticExpansionPart,
6
+ ParsedScript,
7
+ ProcessSubstitutionPart,
8
+ } from "./internal-types.ts";
9
+ import type { parseRegion } from "./parser.ts";
10
+ import type { parseArithmeticWithParts } from "./arithmetic-parts.ts";
11
+ import type { SubstitutionContext } from "./substitution-context.ts";
12
+
13
+ type ScriptExpansionType = "CommandExpansion" | "ProcessSubstitution" | "ArithmeticCommandExpansion";
14
+
15
+ // Where a deferred script parses: in place over [start, end) of `source`, so every node is born
16
+ // with absolute pos/end and nested substitutions compose by re-lexing the original on demand. A
17
+ // decoded backtick body is its own source: its positions index that string, so it parses with no
18
+ // shared context and the script carries the string as `source`.
19
+ export interface ScriptWindow {
20
+ readonly source: string;
21
+ readonly start: number;
22
+ readonly end: number;
23
+ readonly depth: number;
24
+ readonly parenBoundary: boolean;
25
+ readonly context: SubstitutionContext | undefined;
26
+ }
27
+
28
+ // The window is held only until the script resolves; a part constructed without one has no script.
29
+ export class ScriptExpansionImpl<T extends ScriptExpansionType> {
30
+ static _resolveScript: typeof parseRegion;
31
+
32
+ type: T;
33
+ pos: number;
34
+ end: number;
35
+ text: string;
36
+ #window: ScriptWindow | undefined;
37
+ #script: ParsedScript | undefined = undefined;
38
+
39
+ constructor(type: T, pos: number, end: number, text: string, window?: ScriptWindow) {
40
+ this.type = type;
41
+ this.pos = pos;
42
+ this.end = end;
43
+ this.text = text;
44
+ this.#window = window;
45
+ }
46
+
47
+ get script(): ParsedScript | undefined {
48
+ const window = this.#window;
49
+ if (window !== undefined) {
50
+ const script = ScriptExpansionImpl._resolveScript(
51
+ window.source,
52
+ window.start,
53
+ window.end,
54
+ window.depth,
55
+ window.parenBoundary,
56
+ window.context,
57
+ );
58
+ if (window.context === undefined) script.source = window.source;
59
+ this.script = script;
60
+ }
61
+ return this.#script;
62
+ }
63
+
64
+ set script(script: ParsedScript | undefined) {
65
+ this.#script = script;
66
+ this.#window = undefined;
67
+ }
68
+
69
+ toJSON(): { type: T; pos: number; end: number; text: string; script: ParsedScript | undefined } {
70
+ return { type: this.type, pos: this.pos, end: this.end, text: this.text, script: this.script };
71
+ }
72
+ }
73
+
74
+ export class ProcessSubstitutionImpl extends ScriptExpansionImpl<"ProcessSubstitution"> {
75
+ operator: "<" | ">";
76
+
77
+ constructor(pos: number, end: number, text: string, operator: "<" | ">", window: ScriptWindow | undefined) {
78
+ super("ProcessSubstitution", pos, end, text, window);
79
+ this.operator = operator;
80
+ }
81
+
82
+ toJSON(): ProcessSubstitutionPart {
83
+ return {
84
+ type: this.type,
85
+ pos: this.pos,
86
+ end: this.end,
87
+ text: this.text,
88
+ operator: this.operator,
89
+ script: this.script,
90
+ };
91
+ }
92
+ }
93
+
94
+ // Where a deferred arithmetic expression parses: `body` is the expression text at `offset` in
95
+ // `source`, which embedded words and substitutions re-lex in place.
96
+ export interface ArithmeticWindow {
97
+ readonly source: string;
98
+ readonly body: string;
99
+ readonly offset: number;
100
+ readonly depth: number;
101
+ readonly context: SubstitutionContext;
102
+ }
103
+
104
+ export class ArithmeticExpansionImpl implements ArithmeticExpansionPart {
105
+ static _resolveExpression: typeof parseArithmeticWithParts;
106
+
107
+ type = "ArithmeticExpansion" as const;
108
+ pos: number;
109
+ end: number;
110
+ text: string;
111
+ #window: ArithmeticWindow | undefined;
112
+ #expression: ArithmeticExpression | undefined = undefined;
113
+
114
+ constructor(pos: number, end: number, text: string, window: ArithmeticWindow) {
115
+ this.pos = pos;
116
+ this.end = end;
117
+ this.text = text;
118
+ this.#window = window;
119
+ }
120
+
121
+ get expression(): ArithmeticExpression | undefined {
122
+ const window = this.#window;
123
+ if (window !== undefined) {
124
+ this.expression = ArithmeticExpansionImpl._resolveExpression(
125
+ window.body,
126
+ window.offset,
127
+ window.source,
128
+ window.depth,
129
+ window.context,
130
+ window.depth + 1,
131
+ );
132
+ }
133
+ return this.#expression;
134
+ }
135
+
136
+ set expression(expression: ArithmeticExpression | undefined) {
137
+ this.#expression = expression;
138
+ this.#window = undefined;
139
+ }
140
+
141
+ toJSON(): ArithmeticExpansionPart {
142
+ return { type: this.type, pos: this.pos, end: this.end, text: this.text, expression: this.expression };
143
+ }
144
+ }
@@ -0,0 +1,284 @@
1
+ // Code generated by scripts/vendor-unbash.ts from unbash 6.0.0 (968c14992a43dc06ce29c9f51858abd4a87851ea). DO NOT EDIT.
2
+ // @ts-nocheck
3
+ import type { GlobPart, WordPart } from "./internal-types.ts";
4
+ import { unescapeBareValue, wordPartsValue } from "./word-value.ts";
5
+ import {
6
+ CH_BACKSLASH,
7
+ CH_BANG,
8
+ CH_CARET,
9
+ CH_COLON,
10
+ CH_DASH,
11
+ CH_DOT,
12
+ CH_EQ,
13
+ CH_LBRACKET,
14
+ CH_LPAREN,
15
+ CH_NL,
16
+ CH_QUESTION,
17
+ CH_RBRACKET,
18
+ CH_STAR,
19
+ skipLineContinuations,
20
+ } from "./chars.ts";
21
+
22
+ export function hasGlobCharacters(text: string): boolean {
23
+ return /[*?[]/.test(text);
24
+ }
25
+
26
+ const bracketSyntax = new Uint8Array(128);
27
+ bracketSyntax[CH_BANG] = 1;
28
+ bracketSyntax[CH_CARET] = 1;
29
+ bracketSyntax[CH_LBRACKET] = 1;
30
+ bracketSyntax[CH_RBRACKET] = 1;
31
+ bracketSyntax[CH_COLON] = 1;
32
+ bracketSyntax[CH_DOT] = 1;
33
+ bracketSyntax[CH_EQ] = 1;
34
+ bracketSyntax[CH_DASH] = 1;
35
+ bracketSyntax[CH_BACKSLASH] = 1;
36
+
37
+ // Bracket extents follow Bash's matcher (lib/glob/sm_loop.c, BRACKMATCH and PARSE_SUBBRACKET on
38
+ // the path where no member matches): `[:name:]` and `[=c=]` end at the first `:]` or `=]` unless a
39
+ // raw `]` comes first, `[.sym.]` ends at the first `.]` even past a `]`, a class or equivalence
40
+ // never starts a range, and a `-` range end consumes one member, be it an escape, a plain `[` or a
41
+ // collating symbol (also behind an escaped `[`). Quoted and expanded parts are single opaque
42
+ // members; Bash additionally lets quoted `]`, `:`, `=`, `.` and `[` take part in symbol and range
43
+ // scanning after re-escaping them, which this scan leaves out.
44
+ function bracketEnds(
45
+ source: string,
46
+ start: number,
47
+ end: number,
48
+ parts: WordPart[] | null | undefined,
49
+ offset: number,
50
+ ): Map<number, number> | null {
51
+ let close = end - 1;
52
+ while (close > start && source.charCodeAt(close) !== CH_RBRACKET) close--;
53
+ if (close === start) return null;
54
+
55
+ // Tokens: each special character, and runs of plain members (continuations vanish, quoted or
56
+ // expanded parts are plain). Only specials change an extent, so a run acts as one member except
57
+ // as a range end, which consumes just its first member.
58
+ const positions: number[] = [];
59
+ const kinds: number[] = [];
60
+ const members: number[] = [];
61
+ let partIndex = 0;
62
+ let symbols = false;
63
+ for (let pos = start; pos < end; pos++) {
64
+ while (parts && partIndex < parts.length && parts[partIndex].end <= pos + offset) partIndex++;
65
+ const part = parts?.[partIndex];
66
+ const tokenPos = pos;
67
+ let kind = 0;
68
+ if (part && part.pos <= pos + offset && part.type !== "Literal") {
69
+ pos = part.end - offset - 1;
70
+ } else {
71
+ kind = source.charCodeAt(pos);
72
+ if (kind === CH_BACKSLASH && pos + 1 < end && source.charCodeAt(pos + 1) === CH_NL) {
73
+ pos++;
74
+ continue;
75
+ }
76
+ if (kind >= 128 || !bracketSyntax[kind]) kind = 0;
77
+ }
78
+ const last = kinds.length - 1;
79
+ if (kind === 0 && last >= 0 && kinds[last] === 0) {
80
+ members[last]++;
81
+ continue;
82
+ }
83
+ if (last >= 0 && kinds[last] === CH_LBRACKET && (kind === CH_COLON || kind === CH_EQ || kind === CH_DOT)) {
84
+ symbols = true;
85
+ }
86
+ positions.push(tokenPos);
87
+ kinds.push(kind);
88
+ members.push(1);
89
+ }
90
+ const count = kinds.length;
91
+
92
+ // PARSE_SUBBRACKET memoized per opener: symbolEnd[t] is the token after the `X]` closing the
93
+ // symbol that `[X` opens at token t, or -1. One right-to-left pass tracks the nearest raw `]`
94
+ // and the nearest `:]`, `=]` and `.]` beyond the opener. Escaped characters are separate tokens,
95
+ // so a `\]` still stops a `:` or `=` scan as it does in Bash.
96
+ let symbolEnd: Int32Array | undefined;
97
+ if (symbols) {
98
+ symbolEnd = new Int32Array(count).fill(-1);
99
+ let close = -1;
100
+ let colon = -1;
101
+ let eq = -1;
102
+ let dot = -1;
103
+ for (let i = count - 1; i >= 0; i--) {
104
+ // The scan for the opener at i starts two tokens in, so fold token i + 2 in first.
105
+ const kind = kinds[i + 2];
106
+ if (kind === CH_RBRACKET) close = i + 2;
107
+ else if (kinds[i + 3] === CH_RBRACKET) {
108
+ if (kind === CH_COLON) colon = i + 2;
109
+ else if (kind === CH_EQ) eq = i + 2;
110
+ else if (kind === CH_DOT) dot = i + 2;
111
+ }
112
+ if (kinds[i] !== CH_LBRACKET) continue;
113
+ const type = kinds[i + 1];
114
+ const found = type === CH_COLON ? colon : type === CH_EQ ? eq : type === CH_DOT ? dot : -1;
115
+ if (found !== -1 && (type === CH_DOT || close === -1 || close > found)) symbolEnd[i] = found + 2;
116
+ }
117
+ }
118
+ const subBracket = (open: number): number => (symbolEnd === undefined ? -1 : symbolEnd[open]);
119
+
120
+ // endFrom[t]: the token after the `]` closing a bracket whose next member starts at token t, or
121
+ // -1 when the pattern runs out and the opening `[` stays literal.
122
+ const endFrom = new Int32Array(count + 2).fill(-1);
123
+ const resume = (next: number, range: boolean): number => {
124
+ if (next >= count) return -1;
125
+ if (range && kinds[next] === CH_DASH && next + 1 < count && kinds[next + 1] !== CH_RBRACKET) {
126
+ let last = next + 1;
127
+ if (kinds[last] === CH_BACKSLASH) {
128
+ if (last + 1 >= count) return -1;
129
+ last++;
130
+ }
131
+ let after = last + 1;
132
+ if (kinds[last] === CH_LBRACKET && kinds[last + 1] === CH_DOT) {
133
+ const symbol = subBracket(last);
134
+ if (symbol !== -1) after = symbol;
135
+ } else if (kinds[last] === 0 && members[last] > 1) {
136
+ // The run's later members stay members, and its last one may start the next range.
137
+ return endFrom[last];
138
+ }
139
+ if (after >= count) return -1;
140
+ return kinds[after] === CH_RBRACKET ? after + 1 : endFrom[after];
141
+ }
142
+ return kinds[next] === CH_RBRACKET ? next + 1 : endFrom[next];
143
+ };
144
+ for (let t = count - 1; t >= 0; t--) {
145
+ const kind = kinds[t];
146
+ let after = t + 1;
147
+ let range = true;
148
+ if (kind === CH_BACKSLASH) {
149
+ if (t + 1 >= count) continue;
150
+ after = t + 2;
151
+ } else if (kind === CH_LBRACKET) {
152
+ const type = kinds[t + 1];
153
+ if (type === CH_COLON || type === CH_EQ || type === CH_DOT) {
154
+ const symbol = subBracket(t);
155
+ if (symbol !== -1) {
156
+ after = symbol;
157
+ range = type === CH_DOT;
158
+ }
159
+ }
160
+ }
161
+ endFrom[t] = resume(after, range);
162
+ }
163
+
164
+ const ends = new Map<number, number>();
165
+ for (let t = 0; t < count; t++) {
166
+ if (kinds[t] !== CH_LBRACKET) continue;
167
+ let first = t + 1;
168
+ if (kinds[first] === CH_BANG || kinds[first] === CH_CARET) first++;
169
+ if (first >= count) continue;
170
+ const close = kinds[first] === CH_RBRACKET ? resume(first + 1, true) : endFrom[first];
171
+ if (close !== -1) ends.set(positions[t], positions[close - 1] + 1);
172
+ }
173
+ return ends;
174
+ }
175
+
176
+ function appendRange(
177
+ source: string,
178
+ start: number,
179
+ end: number,
180
+ parts: WordPart[] | null | undefined,
181
+ index: number,
182
+ offset: number,
183
+ result: WordPart[],
184
+ ): number {
185
+ while (start < end) {
186
+ while (parts && index < parts.length && parts[index].end <= start + offset) index++;
187
+ const part = parts?.[index];
188
+ if (part && part.pos === start + offset && part.end <= end + offset) {
189
+ result.push(part);
190
+ start = part.end - offset;
191
+ index++;
192
+ continue;
193
+ }
194
+ const stop = part ? Math.min(end, (part.pos > start + offset ? part.pos : part.end) - offset) : end;
195
+ const text = source.slice(start, stop);
196
+ result.push({ type: "Literal", pos: start + offset, end: stop + offset, text, value: unescapeBareValue(text) });
197
+ start = stop;
198
+ }
199
+ return index;
200
+ }
201
+
202
+ export function parseGlobParts(
203
+ source: string,
204
+ start: number,
205
+ end: number,
206
+ parts: WordPart[] | null | undefined,
207
+ offset = 0,
208
+ ): WordPart[] | undefined {
209
+ let result: WordPart[] | undefined;
210
+ let brackets: Map<number, number> | null | undefined;
211
+ let partIndex = 0;
212
+ let emittedIndex = 0;
213
+ let literalStart = start;
214
+ for (let pos = start; pos < end; pos++) {
215
+ while (parts && partIndex < parts.length && parts[partIndex].end <= pos + offset) partIndex++;
216
+ const part = parts?.[partIndex];
217
+ if (part && part.pos <= pos + offset && part.type !== "Literal") {
218
+ pos = part.end - offset - 1;
219
+ continue;
220
+ }
221
+ const character = source.charCodeAt(pos);
222
+ if (character === CH_BACKSLASH) {
223
+ pos++;
224
+ continue;
225
+ }
226
+
227
+ let globEnd: number;
228
+ if (character === CH_STAR || character === CH_QUESTION) {
229
+ const limit = part ? Math.min(end, part.end - offset) : end;
230
+ // Embedded-word scans can leave extglob operators inside Literal parts.
231
+ const next = skipLineContinuations(source, pos + 1, limit);
232
+ if (next < limit && source.charCodeAt(next) === CH_LPAREN) continue;
233
+ globEnd = pos + 1;
234
+ if (character === CH_STAR) {
235
+ while (globEnd < limit && source.charCodeAt(globEnd) === CH_STAR) {
236
+ const next = skipLineContinuations(source, globEnd + 1, limit);
237
+ if (next < limit && source.charCodeAt(next) === CH_LPAREN) break;
238
+ globEnd++;
239
+ }
240
+ }
241
+ } else if (character === CH_LBRACKET) {
242
+ if (brackets === undefined) brackets = bracketEnds(source, pos, end, parts, offset);
243
+ const close = brackets?.get(pos);
244
+ if (close === undefined) continue;
245
+ globEnd = close;
246
+ } else {
247
+ continue;
248
+ }
249
+
250
+ if (!result) result = [];
251
+ emittedIndex = appendRange(source, literalStart, pos, parts, emittedIndex, offset, result);
252
+ const text = source.slice(pos, globEnd);
253
+ let glob: GlobPart;
254
+ if (character === CH_LBRACKET) {
255
+ const interior: WordPart[] = [];
256
+ emittedIndex = appendRange(source, pos + 1, globEnd - 1, parts, emittedIndex, offset, interior);
257
+ glob = {
258
+ type: "Glob",
259
+ kind: "bracket",
260
+ pos: pos + offset,
261
+ end: globEnd + offset,
262
+ text,
263
+ value: "[" + wordPartsValue(interior) + "]",
264
+ parts: interior,
265
+ };
266
+ } else {
267
+ glob = {
268
+ type: "Glob",
269
+ kind: character === CH_STAR ? "star" : "question",
270
+ pos: pos + offset,
271
+ end: globEnd + offset,
272
+ text,
273
+ value: text,
274
+ parts: undefined,
275
+ };
276
+ }
277
+ result.push(glob);
278
+ literalStart = globEnd;
279
+ pos = globEnd - 1;
280
+ }
281
+ if (!result) return parts ?? undefined;
282
+ appendRange(source, literalStart, end, parts, emittedIndex, offset, result);
283
+ return result;
284
+ }