@sema-agent/core 7.27.0 → 7.28.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 (35) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/agents/builtin-agents.d.ts +6 -15
  3. package/dist/agents/cascade.d.ts +29 -33
  4. package/dist/agents/observer.d.ts +18 -20
  5. package/dist/agents/subagent.d.ts +58 -70
  6. package/dist/agents/tool-filter.d.ts +10 -12
  7. package/dist/agents/verify.d.ts +44 -49
  8. package/dist/brain/input-too-long.d.ts +10 -12
  9. package/dist/brain/media-degrade.d.ts +4 -5
  10. package/dist/brain/reasoning.d.ts +10 -12
  11. package/dist/brain/repetition.d.ts +7 -9
  12. package/dist/brain/request-params.d.ts +20 -25
  13. package/dist/brain/route-adjudicator.d.ts +4 -4
  14. package/dist/brain/route-conformance.d.ts +2 -2
  15. package/dist/brain/status-sink.d.ts +12 -14
  16. package/dist/brain/terminal-cause.d.ts +12 -15
  17. package/dist/brain/timeout.d.ts +17 -19
  18. package/dist/core/memory-engine/consolidation.d.ts +11 -15
  19. package/dist/core/memory-engine/engine.d.ts +45 -54
  20. package/dist/core/memory-engine/tools.d.ts +17 -23
  21. package/dist/core/permission-rule-model.js +5 -3
  22. package/dist/shell/lex.d.ts +279 -0
  23. package/dist/shell/lex.js +1253 -0
  24. package/dist/tools/fs/bash-program-position.d.ts +2 -4
  25. package/dist/tools/fs/bash-program-position.js +2 -1
  26. package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -2
  27. package/dist/tools/fs/bash-readonly-classifier.js +1 -1
  28. package/dist/tools/fs/shell-read-check.js +1 -1
  29. package/dist/tools/fs/shell-read-establish.js +11 -1
  30. package/dist/tools/fs/shell-read-plan.d.ts +7 -2
  31. package/dist/tools/fs/shell-read-plan.js +48 -10
  32. package/package.json +2 -2
  33. package/wire-changes/core@7.28.0.json +594 -0
  34. package/dist/tools/fs/bash-lexer.d.ts +0 -152
  35. package/dist/tools/fs/bash-lexer.js +0 -627
@@ -0,0 +1,1253 @@
1
+ export const SHELL_LEX_DIALECT = "bash";
2
+ export const QUOTE_KINDS = ["'", '"', "$'", '$"', "\\"];
3
+ export const EXPANSION_KINDS = ["parameter", "command", "process", "arithmetic", "tilde", "brace", "glob"];
4
+ export const UNRESOLVED_REASONS = ["ansi_c_escape", "locale_string", "parameter_text", "subscript_text", "compound_array_value"];
5
+ export const UNRESOLVED_VIEW = {
6
+ ansi_c_escape: "refuse",
7
+ locale_string: "fold",
8
+ parameter_text: "refuse",
9
+ subscript_text: "refuse",
10
+ compound_array_value: "refuse",
11
+ };
12
+ export const UNRESOLVED_SENTENCE = {
13
+ ansi_c_escape: "an ANSI-C quoted word carries an escape this lexer does not fold",
14
+ locale_string: "a locale-translated string carries text this lexer does not read",
15
+ parameter_text: "a parameter expansion carries text this lexer does not read",
16
+ subscript_text: "a subscript carries text this lexer does not read",
17
+ compound_array_value: "a compound assignment value is a list this lexer does not read",
18
+ };
19
+ export const PARSE_FAILURE_REASONS = [
20
+ "unterminated_single_quote",
21
+ "unterminated_double_quote",
22
+ "unterminated_ansi_c",
23
+ "unterminated_subscript",
24
+ "unterminated_compound_value",
25
+ "unterminated_command_substitution",
26
+ "unterminated_process_substitution",
27
+ "unterminated_parameter_expansion",
28
+ "unterminated_heredoc",
29
+ "undelimited_substitution",
30
+ "redirect_without_target",
31
+ "heredoc_without_delimiter",
32
+ "heredoc_delimiter_expands",
33
+ "background_operator",
34
+ ];
35
+ export const PARSE_FAILURE_SENTENCE = {
36
+ unterminated_single_quote: "a single-quoted word never closes",
37
+ unterminated_double_quote: "a double-quoted word never closes",
38
+ unterminated_ansi_c: "an ANSI-C quoted word never closes",
39
+ unterminated_subscript: "a subscript never closes",
40
+ unterminated_compound_value: "a compound assignment value never closes",
41
+ unterminated_command_substitution: "a command substitution never terminates",
42
+ unterminated_process_substitution: "a process substitution never terminates",
43
+ unterminated_parameter_expansion: "a parameter expansion never terminates",
44
+ unterminated_heredoc: "a here-document body never terminates",
45
+ undelimited_substitution: "a command substitution carries a construct this lexer cannot delimit",
46
+ redirect_without_target: "a redirection names no target",
47
+ heredoc_without_delimiter: "a here-document operator names no delimiter",
48
+ heredoc_delimiter_expands: "a here-document delimiter is not a plain word",
49
+ background_operator: "`&` backgrounding is not allowed — a backgrounded process outlives the command",
50
+ };
51
+ export const BASH_ONLY_CONSTRUCTS = [
52
+ "ansi_c_quoting",
53
+ "locale_string",
54
+ "append_assignment",
55
+ "subscript_assignment",
56
+ "compound_assignment",
57
+ "here_string",
58
+ "process_substitution",
59
+ "redirect_both",
60
+ "brace_expansion",
61
+ ];
62
+ export const ANSI_C_ESCAPES = {
63
+ a: "\x07",
64
+ b: "\b",
65
+ e: "\x1b",
66
+ E: "\x1b",
67
+ f: "\f",
68
+ n: "\n",
69
+ r: "\r",
70
+ t: "\t",
71
+ v: "\v",
72
+ "\\": "\\",
73
+ "'": "'",
74
+ '"': '"',
75
+ "?": "?",
76
+ };
77
+ export const SUBSTITUTION_PLACEHOLDER = " subst";
78
+ export const SHELL_CONTROL_PREFIXES = new Set(["!", "if", "then", "elif", "else", "while", "until", "do", "time"]);
79
+ const COMPOUND_OPERAND_COMMANDS = new Set(["alias", "declare", "eval", "export", "let", "local", "readonly", "typeset"]);
80
+ const GROUP_WORDS = new Set(["{", "}"]);
81
+ function quoteStep(open, ch) {
82
+ if (open === undefined)
83
+ return ch === '"' || ch === "'" ? ch : undefined;
84
+ return open === ch ? undefined : open;
85
+ }
86
+ export function dollarOpensQuote(text, at) {
87
+ const next = text[at + 1];
88
+ if (next !== "'" && next !== '"')
89
+ return false;
90
+ let run = 0;
91
+ for (let k = at - 1; k >= 0 && text[k] === "\\"; k--)
92
+ run += 1;
93
+ return run % 2 === 0;
94
+ }
95
+ export function ansiCQuoteClose(source, open) {
96
+ for (let i = open + 1; i < source.length; i++) {
97
+ if (source[i] === "\\") {
98
+ i += 1;
99
+ continue;
100
+ }
101
+ if (source[i] === "'")
102
+ return i;
103
+ }
104
+ return undefined;
105
+ }
106
+ export function quoteMask(s) {
107
+ const openedBy = new Array(s.length).fill(undefined);
108
+ const quoted = new Array(s.length).fill(false);
109
+ let open;
110
+ for (let i = 0; i < s.length; i++) {
111
+ if (open === undefined && s[i] === "$" && s[i + 1] === "'") {
112
+ const close = ansiCQuoteClose(s, i + 1);
113
+ for (let k = i; k <= (close ?? s.length - 1); k++) {
114
+ openedBy[k] = "'";
115
+ quoted[k] = true;
116
+ }
117
+ if (close === undefined) {
118
+ open = "'";
119
+ break;
120
+ }
121
+ i = close;
122
+ continue;
123
+ }
124
+ const before = open;
125
+ open = quoteStep(open, s[i]);
126
+ const holds = before ?? open;
127
+ openedBy[i] = holds;
128
+ quoted[i] = holds !== undefined;
129
+ }
130
+ return { openedBy, quoted, balanced: open === undefined };
131
+ }
132
+ export function literalTildeSpelling(word) {
133
+ return word.startsWith("~") ? `./${word}` : word;
134
+ }
135
+ function decodeAnsiC(body) {
136
+ let text = "";
137
+ let unresolved = false;
138
+ for (let i = 0; i < body.length; i++) {
139
+ const c = body[i];
140
+ if (c !== "\\" || i + 1 >= body.length) {
141
+ text += c;
142
+ continue;
143
+ }
144
+ const next = body[i + 1];
145
+ const folded = ANSI_C_ESCAPES[next];
146
+ if (folded === undefined) {
147
+ unresolved = true;
148
+ text += c + next;
149
+ }
150
+ else
151
+ text += folded;
152
+ i++;
153
+ }
154
+ return { text, unresolved };
155
+ }
156
+ export function wordsOf(segment) {
157
+ const folded = [];
158
+ const raw = [];
159
+ for (const t of segment.tokens) {
160
+ if (t.kind !== "word")
161
+ continue;
162
+ folded.push(t.folded);
163
+ raw.push(t.raw);
164
+ }
165
+ return { folded, raw };
166
+ }
167
+ function past(text, j) {
168
+ let k = j;
169
+ while (text[k] === "\\" && text[k + 1] === "\n")
170
+ k += 2;
171
+ return k;
172
+ }
173
+ function parenBodyEnd(text, open) {
174
+ let depth = 0;
175
+ let quoted;
176
+ let wordStart = true;
177
+ let word = "";
178
+ for (let j = open; j < text.length; j++) {
179
+ const c = text[j];
180
+ if (quoted !== undefined) {
181
+ if (c === "\\" && quoted === '"')
182
+ j++;
183
+ else if (c === quoted)
184
+ quoted = undefined;
185
+ wordStart = false;
186
+ continue;
187
+ }
188
+ if (c === "\\") {
189
+ if (text[j + 1] === "\n") {
190
+ j++;
191
+ continue;
192
+ }
193
+ j++;
194
+ wordStart = false;
195
+ word = "";
196
+ continue;
197
+ }
198
+ if (c === "#" && wordStart) {
199
+ const nl = text.indexOf("\n", j);
200
+ if (nl < 0)
201
+ return undefined;
202
+ j = nl;
203
+ wordStart = true;
204
+ word = "";
205
+ continue;
206
+ }
207
+ if (c === "'" || c === '"') {
208
+ quoted = c;
209
+ wordStart = false;
210
+ word = "";
211
+ continue;
212
+ }
213
+ if (c === "$" && text[past(text, j + 1)] === "{") {
214
+ const open2 = past(text, j + 1);
215
+ const close = braceExpansionEnd(text, open2);
216
+ if (close === undefined)
217
+ return undefined;
218
+ if (UNREADABLE_EXPANSION_TEXT.test(text.slice(open2 + 1, close - 1)))
219
+ return "unreadable";
220
+ j = close - 1;
221
+ wordStart = false;
222
+ word = "";
223
+ continue;
224
+ }
225
+ if (c === "<" && text[past(text, j + 1)] === "<" && text[past(text, past(text, j + 1) + 1)] !== "<")
226
+ return "unreadable";
227
+ if (c === "(")
228
+ depth++;
229
+ else if (c === ")") {
230
+ depth--;
231
+ if (depth === 0)
232
+ return j + 1;
233
+ }
234
+ const boundary = /[\s;|&()]/.test(c);
235
+ if (boundary && word === "case")
236
+ return "unreadable";
237
+ word = boundary ? "" : word + c;
238
+ wordStart = boundary;
239
+ }
240
+ return undefined;
241
+ }
242
+ function braceExpansionEnd(text, open) {
243
+ let quoted;
244
+ for (let j = open + 1; j < text.length; j++) {
245
+ const c = text[j];
246
+ if (quoted !== undefined) {
247
+ if (c === "\\" && quoted === '"')
248
+ j++;
249
+ else if (c === quoted)
250
+ quoted = undefined;
251
+ continue;
252
+ }
253
+ if (c === "\\") {
254
+ j++;
255
+ continue;
256
+ }
257
+ if (c === "'" || c === '"') {
258
+ quoted = c;
259
+ continue;
260
+ }
261
+ if (c === "}")
262
+ return j + 1;
263
+ }
264
+ return undefined;
265
+ }
266
+ function matchedParen(text, open) {
267
+ let depth = 0;
268
+ let quoted;
269
+ for (let j = open; j < text.length; j++) {
270
+ const c = text[j];
271
+ if (quoted !== undefined) {
272
+ if (c === "\\" && quoted === '"')
273
+ j++;
274
+ else if (c === quoted)
275
+ quoted = undefined;
276
+ continue;
277
+ }
278
+ if (c === "\\") {
279
+ j++;
280
+ continue;
281
+ }
282
+ if (c === "'" || c === '"') {
283
+ quoted = c;
284
+ continue;
285
+ }
286
+ if (c === "$" && text[j + 1] === "'") {
287
+ const close = ansiCQuoteClose(text, j + 1);
288
+ if (close === undefined)
289
+ return undefined;
290
+ j = close;
291
+ continue;
292
+ }
293
+ if (c === "$" && text[j + 1] === "{") {
294
+ const close = braceExpansionEnd(text, j + 1);
295
+ if (close === undefined)
296
+ return undefined;
297
+ j = close - 1;
298
+ continue;
299
+ }
300
+ if (c === "(")
301
+ depth++;
302
+ else if (c === ")" && --depth === 0)
303
+ return j + 1;
304
+ }
305
+ return undefined;
306
+ }
307
+ const UNREADABLE_EXPANSION_TEXT = /[$`()<>{\\]/;
308
+ const EXPANSION_NAME = /^[#!]?([A-Za-z_][A-Za-z0-9_]*|\d+|[@*#?$!0-])/;
309
+ const BARE_PARAMETER = /^(?:[A-Za-z_][A-Za-z0-9_]*|\d|[@*#?$!0-])/;
310
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
311
+ const NAME_HEAD = /^[A-Za-z_][A-Za-z0-9_]*/;
312
+ function backtickBody(source, open) {
313
+ let body = "";
314
+ for (let j = open + 1; j < source.length; j++) {
315
+ const c = source[j];
316
+ if (c === "\\" && j + 1 < source.length) {
317
+ const next = source[j + 1];
318
+ if (next === "\n") {
319
+ j++;
320
+ continue;
321
+ }
322
+ body += next === "$" || next === "`" || next === "\\" ? next : c + next;
323
+ j++;
324
+ continue;
325
+ }
326
+ if (c === "`")
327
+ return { body, close: j };
328
+ body += c;
329
+ }
330
+ return undefined;
331
+ }
332
+ export function substitutionsInHeredocBody(body) {
333
+ const out = [];
334
+ for (let i = 0; i < body.length; i++) {
335
+ const ch = body[i];
336
+ if (ch === "\\") {
337
+ i++;
338
+ continue;
339
+ }
340
+ if (ch === "`") {
341
+ const tick = backtickBody(body, i);
342
+ if (tick === undefined)
343
+ return undefined;
344
+ out.push(tick.body);
345
+ i = tick.close;
346
+ continue;
347
+ }
348
+ if (ch === "$" && body[i + 1] === "(") {
349
+ const end = parenBodyEnd(body, i + 1);
350
+ if (typeof end !== "number")
351
+ return undefined;
352
+ out.push(body.slice(i + 2, end - 1));
353
+ i = end - 1;
354
+ }
355
+ }
356
+ return out;
357
+ }
358
+ const WHITESPACE = /\s/;
359
+ const BLANK = /[ \t]/;
360
+ const fail = (reason, at) => ({ parseFailure: { reason, at } });
361
+ const publicPiece = (p) => ({
362
+ span: [p.start, p.end],
363
+ quotedBy: p.quotedBy,
364
+ ...(p.literal !== undefined ? { literal: p.literal } : {}),
365
+ ...(p.expansion !== undefined ? { expansion: p.expansion } : {}),
366
+ });
367
+ class WordBuilder {
368
+ raw = "";
369
+ folded = "";
370
+ started = false;
371
+ at = 0;
372
+ pieces = [];
373
+ rawQuoted = [];
374
+ foldedAt = [];
375
+ srcOfRaw = [];
376
+ rawOfFolded = [];
377
+ marks = [];
378
+ subscript;
379
+ compound = false;
380
+ begin(src) { if (!this.started)
381
+ this.at = src; }
382
+ push(w, f, by, src) {
383
+ const rawStart = this.raw.length;
384
+ for (let k = 0; k < w.length; k++) {
385
+ this.rawQuoted.push(by);
386
+ this.foldedAt.push(this.folded.length);
387
+ this.srcOfRaw.push(src + k);
388
+ }
389
+ for (let k = 0; k < f.length; k++)
390
+ this.rawOfFolded.push(rawStart);
391
+ this.raw += w;
392
+ this.folded += f;
393
+ this.started = true;
394
+ }
395
+ emit(w, f, by, src, srcEnd) {
396
+ this.begin(src);
397
+ const last = this.pieces[this.pieces.length - 1];
398
+ if (last !== undefined && last.literal !== undefined && last.quotedBy === by && last.end === src && last.rawEnd === this.raw.length) {
399
+ last.literal += f;
400
+ last.end = srcEnd;
401
+ last.rawEnd += w.length;
402
+ last.foldedEnd += f.length;
403
+ }
404
+ else {
405
+ this.pieces.push({ start: src, end: srcEnd, rawStart: this.raw.length, rawEnd: this.raw.length + w.length, foldedStart: this.folded.length, foldedEnd: this.folded.length + f.length, quotedBy: by, literal: f, expansion: undefined });
406
+ }
407
+ this.push(w, f, by, src);
408
+ }
409
+ opaque(w, f, by, src, srcEnd, exp) {
410
+ this.begin(src);
411
+ this.pieces.push({ start: src, end: srcEnd, rawStart: this.raw.length, rawEnd: this.raw.length + w.length, foldedStart: this.folded.length, foldedEnd: this.folded.length + f.length, quotedBy: by, literal: undefined, expansion: exp });
412
+ this.push(w, f, by, src);
413
+ }
414
+ quoteChar(ch, by, src) {
415
+ this.begin(src);
416
+ this.rawQuoted.push(by);
417
+ this.foldedAt.push(this.folded.length);
418
+ this.srcOfRaw.push(src);
419
+ this.raw += ch;
420
+ this.started = true;
421
+ }
422
+ mark(reason, rawAt) { this.marks.push({ at: rawAt, reason }); }
423
+ inSubscript() { return this.subscript !== undefined && this.subscript.rawEnd === undefined; }
424
+ foldedOf(r) { return r >= this.foldedAt.length ? this.folded.length : this.foldedAt[r]; }
425
+ slice(from, to) {
426
+ const out = [];
427
+ for (const p of this.pieces) {
428
+ if (p.rawEnd <= from || p.rawStart >= to)
429
+ continue;
430
+ if (p.rawStart >= from && p.rawEnd <= to) {
431
+ out.push(p);
432
+ continue;
433
+ }
434
+ const rs = Math.max(p.rawStart, from);
435
+ const re = Math.min(p.rawEnd, to);
436
+ const fs = this.foldedOf(rs);
437
+ const fe = this.foldedOf(re);
438
+ out.push({
439
+ ...p,
440
+ start: this.srcOfRaw[rs],
441
+ end: this.srcOfRaw[re - 1] + 1,
442
+ rawStart: rs,
443
+ rawEnd: re,
444
+ foldedStart: fs,
445
+ foldedEnd: fe,
446
+ literal: p.literal === undefined ? undefined : p.literal.slice(fs - p.foldedStart, fe - p.foldedStart),
447
+ });
448
+ }
449
+ return out;
450
+ }
451
+ expansionPiece(from, to, exp) {
452
+ return { start: this.srcOfRaw[from], end: this.srcOfRaw[to - 1] + 1, rawStart: from, rawEnd: to, foldedStart: this.foldedOf(from), foldedEnd: this.foldedOf(to), quotedBy: undefined, literal: undefined, expansion: exp };
453
+ }
454
+ carve(from, to, exp, inExp) {
455
+ this.pieces = [...this.slice(0, from), this.expansionPiece(from, to, exp), ...this.slice(to, this.raw.length)];
456
+ if (inExp !== undefined)
457
+ for (let k = from; k < to; k++)
458
+ inExp[k] = true;
459
+ }
460
+ carveAll(ranges) {
461
+ if (ranges.length === 0)
462
+ return;
463
+ const cut = (p, rs, re) => {
464
+ if (rs === p.rawStart && re === p.rawEnd)
465
+ return p;
466
+ const fs = this.foldedOf(rs);
467
+ const fe = this.foldedOf(re);
468
+ return { ...p, start: this.srcOfRaw[rs], end: this.srcOfRaw[re - 1] + 1, rawStart: rs, rawEnd: re, foldedStart: fs, foldedEnd: fe, literal: p.literal === undefined ? undefined : p.literal.slice(fs - p.foldedStart, fe - p.foldedStart) };
469
+ };
470
+ const out = [];
471
+ let r = 0;
472
+ for (const p of this.pieces) {
473
+ let cur = p.rawStart;
474
+ while (r < ranges.length && ranges[r].from < p.rawEnd) {
475
+ const rg = ranges[r];
476
+ if (rg.from >= cur) {
477
+ if (rg.from > cur)
478
+ out.push(cut(p, cur, rg.from));
479
+ out.push(this.expansionPiece(rg.from, rg.to, rg.exp));
480
+ }
481
+ if (rg.to >= p.rawEnd) {
482
+ cur = p.rawEnd;
483
+ break;
484
+ }
485
+ cur = rg.to;
486
+ r++;
487
+ }
488
+ if (cur < p.rawEnd)
489
+ out.push(cut(p, cur, p.rawEnd));
490
+ if (r < ranges.length && ranges[r].to === p.rawEnd)
491
+ r++;
492
+ }
493
+ this.pieces = out;
494
+ }
495
+ inExpansion() {
496
+ const out = new Array(this.raw.length).fill(false);
497
+ for (const p of this.pieces)
498
+ if (p.expansion !== undefined)
499
+ for (let k = p.rawStart; k < p.rawEnd; k++)
500
+ out[k] = true;
501
+ return out;
502
+ }
503
+ tildePrefixEnd(at, inValue) {
504
+ if (this.raw[at] !== "~" || this.rawQuoted[at] !== undefined)
505
+ return undefined;
506
+ let k = at + 1;
507
+ for (; k < this.raw.length; k++) {
508
+ if (this.rawQuoted[k] !== undefined)
509
+ return undefined;
510
+ const c = this.raw[k];
511
+ if (c === "/" || (inValue && c === ":"))
512
+ break;
513
+ }
514
+ return k;
515
+ }
516
+ carveTilde(from, to) {
517
+ const covering = this.pieces.find((p) => p.literal !== undefined && p.rawStart <= from && p.rawEnd >= to);
518
+ if (covering === undefined)
519
+ return;
520
+ this.carve(from, to, { kind: "tilde", yields: "one", body: this.raw.slice(from, to) });
521
+ }
522
+ carveBraceAndGlob() {
523
+ const inExp = this.inExpansion();
524
+ const n = this.raw.length;
525
+ const readable = (j) => this.rawQuoted[j] === undefined && !inExp[j];
526
+ const braceClose = new Map();
527
+ const stack = [];
528
+ for (let j = 0; j < n; j++) {
529
+ if (!readable(j))
530
+ continue;
531
+ const d = this.raw[j];
532
+ if (d === "{")
533
+ stack.push({ open: j, listed: false });
534
+ else if (d === "}") {
535
+ const top = stack.pop();
536
+ if (top !== undefined)
537
+ braceClose.set(top.open, { close: j, listed: top.listed });
538
+ }
539
+ else if (stack.length > 0 && (d === "," || (d === "." && this.raw[j + 1] === ".")))
540
+ stack[stack.length - 1].listed = true;
541
+ }
542
+ const nextBracketClose = new Array(n + 1).fill(-1);
543
+ for (let j = n - 1; j >= 0; j--)
544
+ nextBracketClose[j] = readable(j) && this.raw[j] === "]" ? j : nextBracketClose[j + 1];
545
+ const ranges = [];
546
+ for (let k = 0; k < n; k++) {
547
+ if (!readable(k))
548
+ continue;
549
+ const c = this.raw[k];
550
+ if (c === "{") {
551
+ const m = braceClose.get(k);
552
+ if (m === undefined || !m.listed)
553
+ continue;
554
+ ranges.push({ from: k, to: m.close + 1, exp: { kind: "brace", yields: "many", body: this.raw.slice(k + 1, m.close) } });
555
+ k = m.close;
556
+ continue;
557
+ }
558
+ if (c === "*" || c === "?") {
559
+ ranges.push({ from: k, to: k + 1, exp: { kind: "glob", yields: "many" } });
560
+ continue;
561
+ }
562
+ if (c === "[") {
563
+ let j = k + 1;
564
+ if (this.raw[j] === "!" || this.raw[j] === "^")
565
+ j++;
566
+ if (this.raw[j] === "]")
567
+ j++;
568
+ const close = j < n ? nextBracketClose[j] : -1;
569
+ if (close < 0)
570
+ continue;
571
+ ranges.push({ from: k, to: close + 1, exp: { kind: "glob", yields: "many", body: this.raw.slice(k + 1, close) } });
572
+ k = close;
573
+ }
574
+ }
575
+ this.carveAll(ranges);
576
+ }
577
+ readAssignment() {
578
+ const m = NAME_HEAD.exec(this.folded);
579
+ if (m === null)
580
+ return undefined;
581
+ const name = m[0];
582
+ const inExp = this.inExpansion();
583
+ const quotedF = (f) => { const r = this.rawOfFolded[f]; return r === undefined || this.rawQuoted[r] !== undefined; };
584
+ let at = name.length;
585
+ let sub;
586
+ if (this.folded[at] === "[") {
587
+ let depth = 0;
588
+ let close = -1;
589
+ for (let k = at; k < this.folded.length; k++) {
590
+ const r = this.rawOfFolded[k];
591
+ if (this.rawQuoted[r] !== undefined || inExp[r])
592
+ continue;
593
+ if (this.folded[k] === "[")
594
+ depth++;
595
+ else if (this.folded[k] === "]" && --depth === 0) {
596
+ close = k;
597
+ break;
598
+ }
599
+ }
600
+ if (close < 0)
601
+ return undefined;
602
+ sub = { from: at, to: close + 1 };
603
+ at = close + 1;
604
+ }
605
+ const appends = this.folded.startsWith("+=", at);
606
+ if (!appends && this.folded[at] !== "=")
607
+ return undefined;
608
+ const opLen = appends ? 2 : 1;
609
+ let bare = true;
610
+ for (let f = 0; f < name.length; f++)
611
+ if (quotedF(f))
612
+ bare = false;
613
+ if (sub !== undefined && (quotedF(sub.from) || quotedF(sub.to - 1)))
614
+ bare = false;
615
+ for (let f = at; f < at + opLen; f++)
616
+ if (quotedF(f))
617
+ bare = false;
618
+ const opRaw = this.rawOfFolded[at];
619
+ const valueRawFrom = opRaw + opLen;
620
+ if (!this.compound) {
621
+ for (let k = valueRawFrom; k < this.raw.length; k++) {
622
+ if (k !== valueRawFrom && !(this.raw[k - 1] === ":" && this.rawQuoted[k - 1] === undefined && !inExp[k - 1]))
623
+ continue;
624
+ const end = this.tildePrefixEnd(k, true);
625
+ if (end !== undefined)
626
+ this.carveTilde(k, end);
627
+ }
628
+ }
629
+ const valuePieces = this.slice(valueRawFrom, this.raw.length).map(publicPiece);
630
+ const value = {
631
+ raw: this.raw.slice(valueRawFrom),
632
+ folded: this.folded.slice(at + opLen),
633
+ pieces: valuePieces,
634
+ expansions: valuePieces.filter((p) => p.expansion !== undefined),
635
+ ...(this.compound ? { unresolved: "compound_array_value" } : {}),
636
+ };
637
+ let subscript;
638
+ if (sub !== undefined) {
639
+ const rawFrom = this.rawOfFolded[sub.from] + 1;
640
+ const rawTo = this.rawOfFolded[sub.to - 1];
641
+ const inner = this.slice(rawFrom, rawTo).map(publicPiece);
642
+ subscript = { raw: this.raw.slice(rawFrom, rawTo), folded: this.folded.slice(sub.from + 1, sub.to - 1), expansions: inner.filter((p) => p.expansion !== undefined) };
643
+ }
644
+ return {
645
+ name,
646
+ op: appends ? "+=" : "=",
647
+ ...(subscript !== undefined ? { subscript } : {}),
648
+ value,
649
+ bare,
650
+ form: this.compound ? "compound" : "scalar",
651
+ settlesWholeName: !appends && sub === undefined && !this.compound,
652
+ };
653
+ }
654
+ isAssignmentHead() {
655
+ if (this.compound || this.inSubscript())
656
+ return false;
657
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\[[\s\S]*\])?\+?=$/.test(this.raw))
658
+ return false;
659
+ const sub = this.subscript;
660
+ for (let k = 0; k < this.raw.length; k++) {
661
+ if (sub !== undefined && k > sub.rawStart && k < sub.rawEnd - 1)
662
+ continue;
663
+ if (this.rawQuoted[k] !== undefined)
664
+ return false;
665
+ }
666
+ return true;
667
+ }
668
+ finish(atCommandPosition, tildeSpelling = true) {
669
+ const rawText = this.raw;
670
+ let foldedText = this.folded;
671
+ let assignment;
672
+ const tildeEnd = this.tildePrefixEnd(0, false);
673
+ if (tildeEnd !== undefined)
674
+ this.carveTilde(0, tildeEnd);
675
+ else {
676
+ if (tildeSpelling)
677
+ foldedText = literalTildeSpelling(foldedText);
678
+ assignment = this.readAssignment();
679
+ }
680
+ if (!(assignment !== undefined && assignment.bare && atCommandPosition))
681
+ this.carveBraceAndGlob();
682
+ if (assignment !== undefined && this.subscript?.text)
683
+ this.mark("subscript_text", this.subscript.rawStart);
684
+ const marks = this.marks.filter((m) => m.reason !== "subscript_text" || assignment?.subscript !== undefined)
685
+ .sort((a, b) => (UNRESOLVED_VIEW[a.reason] === UNRESOLVED_VIEW[b.reason] ? a.at - b.at : UNRESOLVED_VIEW[a.reason] === "refuse" ? -1 : 1));
686
+ const unresolved = marks[0]?.reason ?? (assignment?.value.unresolved);
687
+ const spans = [];
688
+ for (let i = 0; i < this.rawQuoted.length; i++) {
689
+ const by = this.rawQuoted[i];
690
+ if (by === undefined)
691
+ continue;
692
+ const last = spans[spans.length - 1];
693
+ if (last !== undefined && last.by === by && last.end === i)
694
+ spans[spans.length - 1] = { ...last, end: i + 1 };
695
+ else
696
+ spans.push({ start: i, end: i + 1, by });
697
+ }
698
+ const pieces = this.pieces.map(publicPiece);
699
+ return {
700
+ kind: "word",
701
+ raw: rawText,
702
+ folded: foldedText,
703
+ pieces,
704
+ quotedSpans: spans,
705
+ expansions: pieces.filter((p) => p.expansion !== undefined),
706
+ ...(assignment !== undefined ? { assignment } : {}),
707
+ ...(unresolved !== undefined ? { unresolved } : {}),
708
+ atCommandPosition,
709
+ };
710
+ }
711
+ }
712
+ function scan(source, opts) {
713
+ const lenient = opts.mode === "lenient";
714
+ const split = opts.connectors !== "text";
715
+ const segments = [{ start: 0, end: source.length, connector: "start", tokens: [], substitutions: [] }];
716
+ const current = () => segments[segments.length - 1];
717
+ const pendingHeredocs = [];
718
+ let pendingRedirect;
719
+ let pendingHeredoc;
720
+ let inSingle = false;
721
+ let inDouble = false;
722
+ let regionBy;
723
+ let regionAt = 0;
724
+ let pendingLocale = false;
725
+ let acceptable = true;
726
+ let afterLeadingRedirect = false;
727
+ let wordsSoFar = 0;
728
+ let commandWord;
729
+ let w = new WordBuilder();
730
+ const next = (j) => (lenient ? past(source, j + 1) : j + 1);
731
+ const quotedBy = () => (inSingle || inDouble ? regionBy : undefined);
732
+ const take = (ch, src) => w.emit(ch, ch, quotedBy(), src, src + 1);
733
+ const substitution = (from, to, body, kind) => {
734
+ const by = quotedBy();
735
+ w.opaque(source.slice(from, to), SUBSTITUTION_PLACEHOLDER, by, from, to, { kind, yields: kind === "command" && by === undefined ? "many" : "one", body });
736
+ current().substitutions.push(body);
737
+ };
738
+ const endWord = () => {
739
+ if (!w.started)
740
+ return undefined;
741
+ if (w.inSubscript())
742
+ return fail("unterminated_subscript", w.srcOfRaw[w.subscript.rawStart]);
743
+ const word = w.finish(acceptable, pendingHeredoc === undefined);
744
+ const at = w.at;
745
+ w = new WordBuilder();
746
+ if (pendingHeredoc !== undefined) {
747
+ const header = pendingHeredoc;
748
+ pendingHeredoc = undefined;
749
+ if (word.folded === "" || /[$`]/.test(word.raw))
750
+ return fail("heredoc_delimiter_expands", at);
751
+ const token = { kind: "heredoc", delimiter: word.folded, quoted: /["'\\]/.test(word.raw), body: "" };
752
+ current().tokens.push(token);
753
+ pendingHeredocs.push({ delimiter: word.folded, quoted: token.quoted, dash: header.dash, token });
754
+ if (wordsSoFar > 0)
755
+ acceptable = false;
756
+ else
757
+ afterLeadingRedirect = true;
758
+ return undefined;
759
+ }
760
+ if (pendingRedirect !== undefined) {
761
+ const op = pendingRedirect;
762
+ pendingRedirect = undefined;
763
+ const target = { ...word, atCommandPosition: false };
764
+ if ((op === ">&" || op === "<&") && word.raw === word.folded && /^(?:\d+-?|-)$/.test(word.folded))
765
+ current().tokens.push({ kind: "redirect", op });
766
+ else
767
+ current().tokens.push({ kind: "redirect", op: op === ">&" ? "&>" : op === "<&" ? "<" : op, target });
768
+ if (wordsSoFar > 0)
769
+ acceptable = false;
770
+ else
771
+ afterLeadingRedirect = true;
772
+ return undefined;
773
+ }
774
+ current().tokens.push(word);
775
+ if (acceptable && word.raw === word.folded && (SHELL_CONTROL_PREFIXES.has(word.folded) || GROUP_WORDS.has(word.folded)))
776
+ return undefined;
777
+ wordsSoFar++;
778
+ if (word.assignment !== undefined && word.assignment.bare && acceptable)
779
+ return undefined;
780
+ if (acceptable)
781
+ commandWord = word.raw === word.folded ? word.folded : undefined;
782
+ acceptable = false;
783
+ return undefined;
784
+ };
785
+ const operandOwed = (at) => pendingRedirect !== undefined ? fail("redirect_without_target", at) : pendingHeredoc !== undefined ? fail("heredoc_without_delimiter", at) : undefined;
786
+ const closeSegment = (connector, at, from) => {
787
+ const refusal = endWord() ?? operandOwed(at);
788
+ if (refusal !== undefined)
789
+ return refusal;
790
+ current().end = at;
791
+ segments.push({ start: from, end: source.length, connector, tokens: [], substitutions: [] });
792
+ acceptable = true;
793
+ wordsSoFar = 0;
794
+ commandWord = undefined;
795
+ afterLeadingRedirect = false;
796
+ return undefined;
797
+ };
798
+ const readHeredocBodies = (from) => {
799
+ let p = from;
800
+ for (const h of pendingHeredocs) {
801
+ const lines = [];
802
+ for (;;) {
803
+ if (p > source.length)
804
+ return fail("unterminated_heredoc", from);
805
+ let nl = source.indexOf("\n", p);
806
+ let line = nl < 0 ? source.slice(p) : source.slice(p, nl);
807
+ if (!h.quoted) {
808
+ while (nl >= 0 && /(?<!\\)(?:\\\\)*\\$/.test(line)) {
809
+ const after = source.indexOf("\n", nl + 1);
810
+ line = line.slice(0, -1) + (after < 0 ? source.slice(nl + 1) : source.slice(nl + 1, after));
811
+ nl = after;
812
+ }
813
+ }
814
+ if ((h.dash ? line.replace(/^\t+/, "") : line) === h.delimiter) {
815
+ p = nl < 0 ? source.length + 1 : nl + 1;
816
+ break;
817
+ }
818
+ if (nl < 0)
819
+ return fail("unterminated_heredoc", from);
820
+ lines.push(line);
821
+ p = nl + 1;
822
+ }
823
+ h.token.body = lines.join("\n");
824
+ }
825
+ pendingHeredocs.length = 0;
826
+ return Math.min(p, source.length);
827
+ };
828
+ for (let i = 0; i < source.length; i++) {
829
+ if (lenient && !inSingle) {
830
+ const j = past(source, i);
831
+ if (j >= source.length)
832
+ break;
833
+ i = j;
834
+ }
835
+ const ch = source[i];
836
+ if (ch === "$" && !inSingle && !inDouble && dollarOpensQuote(source, i)) {
837
+ const q = next(i);
838
+ if (source[q] === '"') {
839
+ w.quoteChar("$", '$"', i);
840
+ pendingLocale = true;
841
+ i = q - 1;
842
+ continue;
843
+ }
844
+ if (source[q] === "'") {
845
+ const close = ansiCQuoteClose(source, q);
846
+ if (close === undefined)
847
+ return fail("unterminated_ansi_c", i);
848
+ const decoded = decodeAnsiC(source.slice(q + 1, close));
849
+ const written = source.slice(i, close + 1);
850
+ if (decoded.unresolved) {
851
+ w.mark("ansi_c_escape", w.raw.length);
852
+ w.opaque(written, decoded.text, "$'", i, close + 1, undefined);
853
+ }
854
+ else
855
+ w.emit(written, decoded.text, "$'", i, close + 1);
856
+ if (w.inSubscript() && decoded.text.includes("]"))
857
+ w.subscript.text = true;
858
+ i = close;
859
+ continue;
860
+ }
861
+ }
862
+ if (ch === "'" && !inDouble) {
863
+ if (inSingle) {
864
+ w.quoteChar(ch, "'", i);
865
+ inSingle = false;
866
+ regionBy = undefined;
867
+ }
868
+ else {
869
+ w.quoteChar(ch, "'", i);
870
+ inSingle = true;
871
+ regionBy = "'";
872
+ regionAt = i;
873
+ }
874
+ continue;
875
+ }
876
+ if (ch === '"' && !inSingle) {
877
+ if (inDouble) {
878
+ w.quoteChar(ch, regionBy ?? '"', i);
879
+ inDouble = false;
880
+ regionBy = undefined;
881
+ }
882
+ else {
883
+ const by = pendingLocale ? '$"' : '"';
884
+ pendingLocale = false;
885
+ if (by === '$"')
886
+ w.mark("locale_string", w.raw.length - 1);
887
+ w.quoteChar(ch, by, i);
888
+ inDouble = true;
889
+ regionBy = by;
890
+ regionAt = i;
891
+ }
892
+ continue;
893
+ }
894
+ if (inSingle) {
895
+ if (w.inSubscript() && ch === "]")
896
+ w.subscript.text = true;
897
+ take(ch, i);
898
+ continue;
899
+ }
900
+ if (!inDouble && ch === "[" && w.started && w.subscript === undefined && !w.compound && acceptable && IDENTIFIER.test(w.raw)) {
901
+ w.subscript = { rawStart: w.raw.length, depth: 1, text: false };
902
+ take(ch, i);
903
+ continue;
904
+ }
905
+ if (w.inSubscript() && !inDouble) {
906
+ const sub = w.subscript;
907
+ if (ch === "[") {
908
+ sub.depth++;
909
+ take(ch, i);
910
+ continue;
911
+ }
912
+ if (ch === "]") {
913
+ take(ch, i);
914
+ if (--sub.depth === 0)
915
+ sub.rawEnd = w.raw.length;
916
+ continue;
917
+ }
918
+ if (/[\n;|&<>]/.test(ch)) {
919
+ sub.text = true;
920
+ take(ch, i);
921
+ continue;
922
+ }
923
+ if (BLANK.test(ch)) {
924
+ if (afterLeadingRedirect)
925
+ sub.text = true;
926
+ take(ch, i);
927
+ continue;
928
+ }
929
+ if (!lenient) {
930
+ take(ch, i);
931
+ continue;
932
+ }
933
+ if (ch === "\\" && source[i + 1] === "]")
934
+ sub.text = true;
935
+ }
936
+ if (w.inSubscript() && inDouble && ch === "]")
937
+ w.subscript.text = true;
938
+ if (lenient) {
939
+ const n1 = next(i);
940
+ if (ch === "\\") {
941
+ const escaped = source[i + 1];
942
+ if (escaped === undefined) {
943
+ take(ch, i);
944
+ continue;
945
+ }
946
+ if (inDouble && !"$`\"\\".includes(escaped))
947
+ w.emit(ch + escaped, ch + escaped, quotedBy(), i, i + 2);
948
+ else
949
+ w.emit(ch + escaped, escaped, inDouble ? quotedBy() : "\\", i, i + 2);
950
+ i++;
951
+ continue;
952
+ }
953
+ if (ch === "`") {
954
+ const tick = backtickBody(source, i);
955
+ if (tick === undefined)
956
+ return fail("unterminated_command_substitution", i);
957
+ substitution(i, tick.close + 1, tick.body, "command");
958
+ i = tick.close;
959
+ continue;
960
+ }
961
+ if (ch === "$" && source[n1] === "(") {
962
+ const arithmetic = source[next(n1)] === "(";
963
+ const end = parenBodyEnd(source, n1);
964
+ if (end === undefined)
965
+ return fail("unterminated_command_substitution", i);
966
+ if (end === "unreadable")
967
+ return fail("undelimited_substitution", i);
968
+ substitution(i, end, source.slice(n1 + 1, end - 1), arithmetic ? "arithmetic" : "command");
969
+ i = end - 1;
970
+ continue;
971
+ }
972
+ if (ch === "$" && source[n1] === "{") {
973
+ const end = braceExpansionEnd(source, n1);
974
+ if (end === undefined)
975
+ return fail("unterminated_parameter_expansion", i);
976
+ const body = source.slice(n1 + 1, end - 1);
977
+ const text = source.slice(i, end);
978
+ const name = EXPANSION_NAME.exec(body)?.[1];
979
+ if (UNREADABLE_EXPANSION_TEXT.test(body))
980
+ w.mark("parameter_text", w.raw.length);
981
+ w.opaque(text, text, quotedBy(), i, end, { kind: "parameter", yields: inDouble ? "one" : "many", body, ...(name !== undefined ? { name } : {}) });
982
+ i = end - 1;
983
+ continue;
984
+ }
985
+ if (ch === "$") {
986
+ const head = BARE_PARAMETER.exec(source.slice(n1, n1 + 64))?.[0];
987
+ if (head !== undefined) {
988
+ const text = source.slice(i, n1 + head.length);
989
+ w.opaque(text, text, quotedBy(), i, n1 + head.length, { kind: "parameter", yields: inDouble ? "one" : "many", name: head });
990
+ i = n1 + head.length - 1;
991
+ continue;
992
+ }
993
+ }
994
+ if (inDouble) {
995
+ take(ch, i);
996
+ continue;
997
+ }
998
+ if ((ch === "<" || ch === ">") && source[n1] === "(") {
999
+ const end = parenBodyEnd(source, n1);
1000
+ if (end === undefined)
1001
+ return fail("unterminated_process_substitution", i);
1002
+ if (end === "unreadable")
1003
+ return fail("undelimited_substitution", i);
1004
+ substitution(i, end, source.slice(n1 + 1, end - 1), "process");
1005
+ i = end - 1;
1006
+ continue;
1007
+ }
1008
+ if (ch === "(" && w.started && w.isAssignmentHead() && (acceptable || (commandWord !== undefined && COMPOUND_OPERAND_COMMANDS.has(commandWord)))) {
1009
+ const end = matchedParen(source, i);
1010
+ if (end === undefined)
1011
+ return fail("unterminated_compound_value", i);
1012
+ const inner = scan(source.slice(i + 1, end - 1), { mode: "lenient", connectors: "text" });
1013
+ if ("parseFailure" in inner)
1014
+ return fail(inner.parseFailure.reason, i + 1 + inner.parseFailure.at);
1015
+ const text = source.slice(i, end);
1016
+ w.mark("compound_array_value", w.raw.length);
1017
+ w.opaque(text, text, undefined, i, end, undefined);
1018
+ w.compound = true;
1019
+ current().substitutions.push(...inner.command.substitutions);
1020
+ i = end - 1;
1021
+ continue;
1022
+ }
1023
+ if (w.inSubscript()) {
1024
+ take(ch, i);
1025
+ continue;
1026
+ }
1027
+ if (ch === "#" && !w.started) {
1028
+ const nl = source.indexOf("\n", i);
1029
+ i = nl < 0 ? source.length : nl - 1;
1030
+ continue;
1031
+ }
1032
+ if (ch === "\n") {
1033
+ if (!split) {
1034
+ const r = endWord();
1035
+ if (r !== undefined)
1036
+ return r;
1037
+ continue;
1038
+ }
1039
+ const refusal = closeSegment("\n", i, i + 1);
1040
+ if (refusal !== undefined)
1041
+ return refusal;
1042
+ if (pendingHeredocs.length > 0) {
1043
+ const resume = readHeredocBodies(i + 1);
1044
+ if (typeof resume !== "number")
1045
+ return resume;
1046
+ current().start = resume;
1047
+ i = resume - 1;
1048
+ }
1049
+ continue;
1050
+ }
1051
+ if (ch === "(" || ch === ")") {
1052
+ const r = endWord() ?? operandOwed(i);
1053
+ if (r !== undefined)
1054
+ return r;
1055
+ current().tokens.push({ kind: "group", op: ch });
1056
+ acceptable = true;
1057
+ wordsSoFar = 0;
1058
+ commandWord = undefined;
1059
+ afterLeadingRedirect = false;
1060
+ continue;
1061
+ }
1062
+ if (ch === "<" || ch === ">" || (ch === "&" && source[n1] === ">")) {
1063
+ if (ch !== "&" && w.started && w.raw === w.folded && /^\d+$/.test(w.raw))
1064
+ w = new WordBuilder();
1065
+ else {
1066
+ const r = endWord() ?? operandOwed(i);
1067
+ if (r !== undefined)
1068
+ return r;
1069
+ }
1070
+ const n2 = next(n1);
1071
+ if (ch === "&") {
1072
+ if (source[n2] === ">") {
1073
+ pendingRedirect = "&>>";
1074
+ i = n2;
1075
+ }
1076
+ else {
1077
+ pendingRedirect = "&>";
1078
+ i = n1;
1079
+ }
1080
+ continue;
1081
+ }
1082
+ if (ch === "<" && source[n1] === "<") {
1083
+ if (source[n2] === "<") {
1084
+ pendingRedirect = "<<<";
1085
+ i = n2;
1086
+ continue;
1087
+ }
1088
+ const dash = source[n2] === "-";
1089
+ pendingHeredoc = { dash };
1090
+ i = dash ? n2 : n1;
1091
+ continue;
1092
+ }
1093
+ if (source[n1] === "&") {
1094
+ pendingRedirect = ch === ">" ? ">&" : "<&";
1095
+ i = n1;
1096
+ continue;
1097
+ }
1098
+ if (ch === ">" && (source[n1] === ">" || source[n1] === "|")) {
1099
+ pendingRedirect = source[n1] === ">" ? ">>" : ">";
1100
+ i = n1;
1101
+ continue;
1102
+ }
1103
+ pendingRedirect = ch;
1104
+ continue;
1105
+ }
1106
+ if (split) {
1107
+ let refusal;
1108
+ if (ch === ";")
1109
+ refusal = closeSegment(";", i, i + 1);
1110
+ else if (ch === "&") {
1111
+ if (source[n1] === "&") {
1112
+ refusal = closeSegment("&&", i, n1 + 1);
1113
+ i = n1;
1114
+ }
1115
+ else
1116
+ refusal = closeSegment("&", i, i + 1);
1117
+ }
1118
+ else if (ch === "|") {
1119
+ if (source[n1] === "|" || source[n1] === "&") {
1120
+ refusal = closeSegment(source[n1] === "|" ? "||" : "|", i, n1 + 1);
1121
+ i = n1;
1122
+ }
1123
+ else
1124
+ refusal = closeSegment("|", i, i + 1);
1125
+ }
1126
+ else if (BLANK.test(ch)) {
1127
+ refusal = endWord();
1128
+ }
1129
+ else {
1130
+ take(ch, i);
1131
+ continue;
1132
+ }
1133
+ if (refusal !== undefined)
1134
+ return refusal;
1135
+ continue;
1136
+ }
1137
+ if (BLANK.test(ch)) {
1138
+ const r = endWord();
1139
+ if (r !== undefined)
1140
+ return r;
1141
+ continue;
1142
+ }
1143
+ take(ch, i);
1144
+ continue;
1145
+ }
1146
+ if (!inDouble && split) {
1147
+ if (ch === "&") {
1148
+ if (source[i + 1] !== "&")
1149
+ return fail("background_operator", i);
1150
+ const r = closeSegment("&&", i, i + 2);
1151
+ if (r !== undefined)
1152
+ return r;
1153
+ i++;
1154
+ continue;
1155
+ }
1156
+ if (ch === "|") {
1157
+ const double = source[i + 1] === "|";
1158
+ const r = closeSegment(double ? "||" : "|", i, double ? i + 2 : i + 1);
1159
+ if (r !== undefined)
1160
+ return r;
1161
+ if (double)
1162
+ i++;
1163
+ continue;
1164
+ }
1165
+ if (ch === ";") {
1166
+ const r = closeSegment(";", i, i + 1);
1167
+ if (r !== undefined)
1168
+ return r;
1169
+ continue;
1170
+ }
1171
+ }
1172
+ if (!inDouble && WHITESPACE.test(ch)) {
1173
+ const r = endWord();
1174
+ if (r !== undefined)
1175
+ return r;
1176
+ continue;
1177
+ }
1178
+ take(ch, i);
1179
+ }
1180
+ if (inSingle)
1181
+ return fail("unterminated_single_quote", regionAt);
1182
+ if (inDouble)
1183
+ return fail("unterminated_double_quote", regionAt);
1184
+ const tail = endWord() ?? operandOwed(source.length);
1185
+ if (tail !== undefined)
1186
+ return tail;
1187
+ if (pendingHeredocs.length > 0)
1188
+ return fail("unterminated_heredoc", source.length);
1189
+ const out = segments.map((s) => ({
1190
+ text: source.slice(s.start, s.end),
1191
+ connector: s.connector,
1192
+ tokens: s.tokens,
1193
+ substitutions: s.substitutions,
1194
+ }));
1195
+ return { command: { segments: out, substitutions: out.flatMap((s) => s.substitutions) } };
1196
+ }
1197
+ export function lexShell(source, opts) {
1198
+ const pass = scan(source, opts);
1199
+ return "parseFailure" in pass ? pass : pass.command;
1200
+ }
1201
+ export function lexShellText(source, opts) {
1202
+ const lexed = lexShell(source, opts);
1203
+ if ("parseFailure" in lexed)
1204
+ return { reject: PARSE_FAILURE_SENTENCE[lexed.parseFailure.reason] };
1205
+ for (const segment of lexed.segments) {
1206
+ for (const token of segment.tokens) {
1207
+ const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined;
1208
+ if (word?.unresolved !== undefined && UNRESOLVED_VIEW[word.unresolved] === "refuse")
1209
+ return { reject: UNRESOLVED_SENTENCE[word.unresolved] };
1210
+ }
1211
+ }
1212
+ return lexed;
1213
+ }
1214
+ export function bashOnlyConstructsOf(command) {
1215
+ const found = new Set();
1216
+ const word = (w) => {
1217
+ for (const s of w.quotedSpans) {
1218
+ if (s.by === "$'")
1219
+ found.add("ansi_c_quoting");
1220
+ if (s.by === '$"')
1221
+ found.add("locale_string");
1222
+ }
1223
+ for (const p of w.expansions) {
1224
+ if (p.expansion?.kind === "process")
1225
+ found.add("process_substitution");
1226
+ if (p.expansion?.kind === "brace")
1227
+ found.add("brace_expansion");
1228
+ }
1229
+ if (w.assignment !== undefined) {
1230
+ if (w.assignment.op === "+=")
1231
+ found.add("append_assignment");
1232
+ if (w.assignment.subscript !== undefined)
1233
+ found.add("subscript_assignment");
1234
+ if (w.assignment.form === "compound")
1235
+ found.add("compound_assignment");
1236
+ }
1237
+ };
1238
+ for (const segment of command.segments) {
1239
+ for (const t of segment.tokens) {
1240
+ if (t.kind === "word")
1241
+ word(t);
1242
+ else if (t.kind === "redirect") {
1243
+ if (t.op === "<<<")
1244
+ found.add("here_string");
1245
+ if (t.op === "&>" || t.op === "&>>")
1246
+ found.add("redirect_both");
1247
+ if (t.target !== undefined)
1248
+ word(t.target);
1249
+ }
1250
+ }
1251
+ }
1252
+ return BASH_ONLY_CONSTRUCTS.filter((c) => found.has(c));
1253
+ }