@poe-platform/safe-bash 0.1.74 → 0.1.76

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 (63) hide show
  1. package/dist/safe-bash/browser.js +435 -176
  2. package/dist/safe-bash/browser.js.map +4 -4
  3. package/dist/safe-bash/commands/structured/limits.js +3 -1
  4. package/dist/safe-bash/commands/structured/limits.js.map +1 -1
  5. package/dist/safe-bash/commands/structured/split.d.ts.map +1 -1
  6. package/dist/safe-bash/commands/structured/split.js +7 -2
  7. package/dist/safe-bash/commands/structured/split.js.map +1 -1
  8. package/dist/safe-bash/commands/structured/values.d.ts.map +1 -1
  9. package/dist/safe-bash/commands/structured/values.js +4 -2
  10. package/dist/safe-bash/commands/structured/values.js.map +1 -1
  11. package/dist/safe-bash/commands/text-programs/awk-reader.d.ts +25 -0
  12. package/dist/safe-bash/commands/text-programs/awk-reader.d.ts.map +1 -0
  13. package/dist/safe-bash/commands/text-programs/awk-reader.js +180 -0
  14. package/dist/safe-bash/commands/text-programs/awk-reader.js.map +1 -0
  15. package/dist/safe-bash/commands/text-programs/awk-retention.d.ts +11 -0
  16. package/dist/safe-bash/commands/text-programs/awk-retention.d.ts.map +1 -0
  17. package/dist/safe-bash/commands/text-programs/awk-retention.js +31 -0
  18. package/dist/safe-bash/commands/text-programs/awk-retention.js.map +1 -0
  19. package/dist/safe-bash/commands/text-programs/awk-runtime.d.ts +11 -1
  20. package/dist/safe-bash/commands/text-programs/awk-runtime.d.ts.map +1 -1
  21. package/dist/safe-bash/commands/text-programs/awk-runtime.js +291 -191
  22. package/dist/safe-bash/commands/text-programs/awk-runtime.js.map +1 -1
  23. package/dist/safe-bash/commands/text-programs/awk.d.ts.map +1 -1
  24. package/dist/safe-bash/commands/text-programs/awk.js +2 -1
  25. package/dist/safe-bash/commands/text-programs/awk.js.map +1 -1
  26. package/dist/safe-bash/commands/text.d.ts.map +1 -1
  27. package/dist/safe-bash/commands/text.js +194 -52
  28. package/dist/safe-bash/commands/text.js.map +1 -1
  29. package/dist/safe-bash/shell/arithmetic-parameters.d.ts +2 -0
  30. package/dist/safe-bash/shell/arithmetic-parameters.d.ts.map +1 -1
  31. package/dist/safe-bash/shell/arithmetic-parameters.js +1 -1
  32. package/dist/safe-bash/shell/arithmetic-parameters.js.map +1 -1
  33. package/dist/safe-bash/shell/arithmetic.d.ts +4 -3
  34. package/dist/safe-bash/shell/arithmetic.d.ts.map +1 -1
  35. package/dist/safe-bash/shell/arithmetic.js +19 -7
  36. package/dist/safe-bash/shell/arithmetic.js.map +1 -1
  37. package/dist/safe-bash/shell/arrays/syntax.d.ts +6 -5
  38. package/dist/safe-bash/shell/arrays/syntax.d.ts.map +1 -1
  39. package/dist/safe-bash/shell/arrays/syntax.js +22 -12
  40. package/dist/safe-bash/shell/arrays/syntax.js.map +1 -1
  41. package/dist/safe-bash/shell/index.d.ts +1 -1
  42. package/dist/safe-bash/shell/index.d.ts.map +1 -1
  43. package/dist/safe-bash/shell/parse-budget.d.ts +10 -0
  44. package/dist/safe-bash/shell/parse-budget.d.ts.map +1 -0
  45. package/dist/safe-bash/shell/parse-budget.js +29 -0
  46. package/dist/safe-bash/shell/parse-budget.js.map +1 -0
  47. package/dist/safe-bash/shell/parser.d.ts +6 -4
  48. package/dist/safe-bash/shell/parser.d.ts.map +1 -1
  49. package/dist/safe-bash/shell/parser.js +80 -29
  50. package/dist/safe-bash/shell/parser.js.map +1 -1
  51. package/dist/safe-bash/shell/runtime.d.ts +3 -0
  52. package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
  53. package/dist/safe-bash/shell/runtime.js +136 -78
  54. package/dist/safe-bash/shell/runtime.js.map +1 -1
  55. package/dist/safe-bash/shell/shell.js +2 -2
  56. package/dist/safe-bash/shell/shell.js.map +1 -1
  57. package/dist/safe-bash/shell/types.d.ts +4 -0
  58. package/dist/safe-bash/shell/types.d.ts.map +1 -1
  59. package/dist/safe-bash/shell/types.js.map +1 -1
  60. package/dist/safe-bash/shell/worker-limits.d.ts.map +1 -1
  61. package/dist/safe-bash/shell/worker-limits.js +1 -0
  62. package/dist/safe-bash/shell/worker-limits.js.map +1 -1
  63. package/package.json +2 -2
@@ -0,0 +1,10 @@
1
+ import { ShellLimitError } from "./types.js";
2
+ export declare const defaultMaxParseUnits = 262144;
3
+ export declare class ParseBudget {
4
+ #private;
5
+ private readonly signal?;
6
+ private readonly onLimit?;
7
+ constructor(maximum?: number, signal?: AbortSignal | undefined, onLimit?: ((error: ShellLimitError) => void) | undefined);
8
+ admit(units?: number): void;
9
+ }
10
+ //# sourceMappingURL=parse-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-budget.d.ts","sourceRoot":"","sources":["../../src/shell/parse-budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,oBAAoB,SAAU,CAAC;AAE5C,qBAAa,WAAW;;IAIsB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAAe,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAhG,OAAO,SAAuB,EAAmB,MAAM,CAAC,EAAE,WAAW,YAAA,EAAmB,OAAO,CAAC,GAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,aAAA;IAK9I,KAAK,CAAC,KAAK,SAAI,GAAG,IAAI;CAWvB"}
@@ -0,0 +1,29 @@
1
+ import { ShellLimitError } from "./types.js";
2
+ export const defaultMaxParseUnits = 262_144;
3
+ export class ParseBudget {
4
+ signal;
5
+ onLimit;
6
+ #remaining;
7
+ #failure;
8
+ constructor(maximum = defaultMaxParseUnits, signal, onLimit) {
9
+ this.signal = signal;
10
+ this.onLimit = onLimit;
11
+ if (!Number.isSafeInteger(maximum) || maximum < 0)
12
+ throw new RangeError("maxParseUnits must be a nonnegative safe integer");
13
+ this.#remaining = maximum;
14
+ }
15
+ admit(units = 1) {
16
+ this.signal?.throwIfAborted();
17
+ if (this.#failure)
18
+ throw this.#failure;
19
+ if (!Number.isSafeInteger(units) || units < 0)
20
+ throw new RangeError("Parse admission must be a nonnegative safe integer");
21
+ if (units > this.#remaining) {
22
+ const error = this.#failure = new ShellLimitError("maxParseUnits");
23
+ this.onLimit?.(error);
24
+ throw error;
25
+ }
26
+ this.#remaining -= units;
27
+ }
28
+ }
29
+ //# sourceMappingURL=parse-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-budget.js","sourceRoot":"","sources":["../../src/shell/parse-budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAE5C,MAAM,OAAO,WAAW;IAIuC;IAAuC;IAHpG,UAAU,CAAS;IACnB,QAAQ,CAA8B;IAEtC,YAAY,OAAO,GAAG,oBAAoB,EAAmB,MAAoB,EAAmB,OAA0C;QAAjF,WAAM,GAAN,MAAM,CAAc;QAAmB,YAAO,GAAP,OAAO,CAAmC;QAC5I,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,kDAAkD,CAAC,CAAC;QAC5H,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,KAAK,GAAG,CAAC;QACb,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,oDAAoD,CAAC,CAAC;QAC1H,IAAI,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,eAAe,CAAC,CAAC;YACnE,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YACtB,MAAM,KAAK,CAAC;QACd,CAAC;QACD,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC;IAC3B,CAAC;CACF"}
@@ -1,3 +1,5 @@
1
+ import type { ShellParseOptions } from "./types.js";
2
+ import { ParseBudget } from "./parse-budget.js";
1
3
  import type { ArithmeticProgram } from "./arithmetic.js";
2
4
  import type { ConditionalExpression } from "./conditional.js";
3
5
  import type { ByteShellValue } from "../contracts/value.js";
@@ -136,13 +138,13 @@ export interface Script {
136
138
  readonly printedLines?: ReadonlyMap<Command, number>;
137
139
  readonly printedNewlines?: number;
138
140
  }
139
- export declare function parseShell(source: string, depth?: number): Script;
140
- export declare function hereDocumentWords(document: HereDocument, line: number, byteLocale: boolean, warnings: string[]): Generator<Word>;
141
- export declare function parseShellUnit(source: string, position?: number, byteLocale?: boolean): {
141
+ export declare function parseShell(source: string, depth?: number, options?: ShellParseOptions): Script;
142
+ export declare function hereDocumentWords(document: HereDocument, line: number, byteLocale: boolean, warnings: string[], budget?: ParseBudget): Generator<Word>;
143
+ export declare function parseShellUnit(source: string, position?: number, byteLocale?: boolean, budget?: ParseBudget): {
142
144
  script: Script;
143
145
  next: number;
144
146
  };
145
- export declare function parseShellInputUnit(source: string, byteLocale?: boolean): {
147
+ export declare function parseShellInputUnit(source: string, byteLocale?: boolean, budget?: ParseBudget): {
146
148
  script: Script;
147
149
  next: number;
148
150
  } | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/shell/parser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/shell/parser.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { ShellSyntaxError } from "./types.js";
2
+ import { ParseBudget } from "./parse-budget.js";
2
3
  import { arithmeticEnd, prepareArithmetic } from "./arithmetic.js";
3
4
  import { arraySelector, compoundEntry, compoundHead, elementAssignment, getArrayAssignment, scalarAssignmentName, setArrayAssignment, setArraySelector, setQuoteMarker } from "./arrays/syntax.js";
4
5
  import { conditionalBinary, conditionalUnary } from "./conditional.js";
@@ -10,7 +11,8 @@ export class HereDocumentSyntaxError extends Error {
10
11
  this.diagnostic = diagnostic;
11
12
  }
12
13
  }
13
- function printedSimpleLines(lists, separators) {
14
+ function printedSimpleLines(lists, separators, budget) {
15
+ budget.admit();
14
16
  const printedLines = new Map();
15
17
  let line = 1;
16
18
  for (let index = 0; index < lists.length; index++) {
@@ -33,6 +35,7 @@ function printedSimpleLines(lists, separators) {
33
35
  class IncompleteShellInput extends Error {
34
36
  }
35
37
  class Lexer {
38
+ budget;
36
39
  source;
37
40
  depth;
38
41
  warnings;
@@ -48,7 +51,8 @@ class Lexer {
48
51
  conditionalPattern;
49
52
  documents = [];
50
53
  newlineOffsets = [];
51
- constructor(source, depth, warnings = [], lineOffset = 0, byteLocale = false, documentLine, partial = false) {
54
+ constructor(budget, source, depth, warnings = [], lineOffset = 0, byteLocale = false, documentLine, partial = false) {
55
+ this.budget = budget;
52
56
  this.source = source;
53
57
  this.depth = depth;
54
58
  this.warnings = warnings;
@@ -77,6 +81,7 @@ class Lexer {
77
81
  throw new ShellSyntaxError(message, this.position, 2, undefined, unclosedQuote);
78
82
  }
79
83
  next() {
84
+ this.budget.admit();
80
85
  while (this.position < this.source.length) {
81
86
  const current = this.source[this.position];
82
87
  if (current === " " || current === "\t" || this.conditional && current === "\n") {
@@ -128,6 +133,7 @@ class Lexer {
128
133
  this.error("Tokenizer made no progress");
129
134
  let document;
130
135
  if (delimiterOperator) {
136
+ this.budget.admit();
131
137
  document = {
132
138
  delimiter: word.parts.map((part) => part.kind === "text" ? part.value : "").join(""),
133
139
  quoted: word.parts.some((part) => part.quoted), stripTabs: delimiterOperator === "<<-", offset,
@@ -135,6 +141,7 @@ class Lexer {
135
141
  };
136
142
  this.documents.push(document);
137
143
  }
144
+ this.budget.admit();
138
145
  return { kind: "word", value: word.plain ?? "", offset, end: this.position, word: { ...word, spelling: this.source.slice(offset, this.position) }, ...(document ? { document } : {}) };
139
146
  }
140
147
  readDocuments() {
@@ -175,10 +182,12 @@ class Lexer {
175
182
  const current = this.source[this.position];
176
183
  if (current === "$" || current === "`") {
177
184
  if (text) {
185
+ this.budget.admit(2);
178
186
  yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
179
187
  text = "";
180
188
  }
181
189
  const offset = this.position;
190
+ this.budget.admit();
182
191
  const parts = [];
183
192
  try {
184
193
  this.expansion(parts, true);
@@ -205,12 +214,15 @@ class Lexer {
205
214
  this.position++;
206
215
  }
207
216
  if (text.length >= 1024) {
217
+ this.budget.admit(2);
208
218
  yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
209
219
  text = "";
210
220
  }
211
221
  }
212
- if (text)
222
+ if (text) {
223
+ this.budget.admit(2);
213
224
  yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
225
+ }
214
226
  }
215
227
  documentSubstitutionError(source, error, backtick = false) {
216
228
  const line = this.documentLine + source.slice(0, error.offset).split("\n").length;
@@ -267,6 +279,7 @@ class Lexer {
267
279
  this.error("Unterminated delimiter expansion syntax");
268
280
  }
269
281
  word(terminator, enclosingQuoted = false, literal = false, arithmetic = false) {
282
+ this.budget.admit();
270
283
  const offset = this.position;
271
284
  const reduction = this.printedNewlineReduction;
272
285
  const unprinted = this.unprintedWords;
@@ -293,6 +306,7 @@ class Lexer {
293
306
  setQuoteMarker(previous, false);
294
307
  }
295
308
  else {
309
+ this.budget.admit();
296
310
  const part = { kind: "text", value: projection, quoted, ...(typeof value === "string" ? {} : { byteValue: value }) };
297
311
  setQuoteMarker(part, synthetic);
298
312
  parts.push(part);
@@ -440,6 +454,7 @@ class Lexer {
440
454
  catch (error) {
441
455
  if (!(error instanceof TypeError) || !("code" in error) || error.code !== "ERR_ENCODING_INVALID_ENCODED_DATA")
442
456
  throw error;
457
+ this.budget.admit(2);
443
458
  return shellValueFromBytes(raw);
444
459
  }
445
460
  }
@@ -491,6 +506,7 @@ class Lexer {
491
506
  this.error("Unterminated ANSI-C quote", { quote: "'", line: quoteLine });
492
507
  }
493
508
  expansion(parts, quoted) {
509
+ this.budget.admit();
494
510
  const line = this.documentLine ?? this.lineAt(this.position);
495
511
  if (this.source[this.position] === "`") {
496
512
  this.position++;
@@ -504,11 +520,12 @@ class Lexer {
504
520
  this.error("Unterminated command substitution");
505
521
  this.position++;
506
522
  try {
507
- parts.push({ kind: "substitution", script: parseSource(source, this.depth + 1, this.warnings, line - 1, this.byteLocale), line, quoted });
523
+ parts.push({ kind: "substitution", script: parseSource(source, this.depth + 1, this.warnings, line - 1, this.byteLocale, this.budget), line, quoted });
508
524
  }
509
525
  catch (error) {
510
526
  if (this.documentLine === undefined || !(error instanceof ShellSyntaxError) || /nesting|exceeds/u.test(error.reason))
511
527
  throw error;
528
+ this.budget.admit();
512
529
  parts.push({ kind: "failed-substitution", diagnostic: this.documentSubstitutionError(source, error, true).diagnostic, quoted });
513
530
  }
514
531
  return;
@@ -520,7 +537,7 @@ class Lexer {
520
537
  const start = this.position + 2;
521
538
  const end = arithmeticEnd(this.source, start);
522
539
  const source = this.source.slice(start, end);
523
- parts.push({ kind: "arithmetic", expression: prepareArithmetic(source), source, line, quoted });
540
+ parts.push({ kind: "arithmetic", expression: prepareArithmetic(source, this.budget), source, line, quoted });
524
541
  this.position = end + 2;
525
542
  }
526
543
  else if (this.source[this.position] === "(") {
@@ -528,7 +545,8 @@ class Lexer {
528
545
  let nested;
529
546
  let script;
530
547
  try {
531
- nested = new Parser(this.source.slice(start), this.depth + 1, this.warnings, this.lineAt(start) - 1, undefined, this.byteLocale);
548
+ this.budget.admit();
549
+ nested = new Parser(this.budget, this.source.slice(start), this.depth + 1, this.warnings, this.lineAt(start) - 1, undefined, this.byteLocale);
532
550
  script = nested.script(new Set([")"]));
533
551
  }
534
552
  catch (error) {
@@ -570,7 +588,7 @@ class Lexer {
570
588
  const end = this.source.indexOf("]", start);
571
589
  if (end < 0)
572
590
  this.error("Unterminated indexed-array subscript");
573
- selector = arraySelector(this.source.slice(start, end), start);
591
+ selector = arraySelector(this.source.slice(start, end), start, this.budget);
574
592
  this.position = end + 1;
575
593
  if (this.source[this.position] !== "}")
576
594
  this.error("Unsupported indexed-array operator");
@@ -599,6 +617,7 @@ class Lexer {
599
617
  this.position++;
600
618
  substringLength = this.word("}", true, false, true);
601
619
  }
620
+ this.budget.admit();
602
621
  substring = { offset, ...(substringLength ? { length: substringLength } : {}), source: this.source.slice(parameterStart, this.position + 1) };
603
622
  }
604
623
  if (this.source[this.position] !== "}")
@@ -621,15 +640,18 @@ class Lexer {
621
640
  }
622
641
  }
623
642
  class Parser {
643
+ budget;
624
644
  lexer;
625
645
  current;
626
646
  lookahead;
627
647
  nesting = 0;
628
648
  openCommands = [];
629
- constructor(source, depth, warnings = [], lineOffset = 0, position, byteLocale = false, partial = false) {
649
+ constructor(budget, source, depth, warnings = [], lineOffset = 0, position, byteLocale = false, partial = false) {
650
+ this.budget = budget;
630
651
  if (position === undefined && depth === 0 && source.includes("\0"))
631
652
  throw new ShellSyntaxError("NUL bytes are not valid shell source", source.indexOf("\0"));
632
- this.lexer = new Lexer(source, depth, warnings, lineOffset, byteLocale, undefined, partial);
653
+ budget.admit();
654
+ this.lexer = new Lexer(budget, source, depth, warnings, lineOffset, byteLocale, undefined, partial);
633
655
  this.lexer.position = position ?? 0;
634
656
  this.current = this.lexer.next();
635
657
  }
@@ -656,11 +678,13 @@ class Parser {
656
678
  newlines() { while (this.is("\n"))
657
679
  this.advance(); }
658
680
  script(stops = new Set(), inputUnit = false) {
681
+ this.budget.admit();
659
682
  const lists = [];
660
683
  const separators = [];
661
684
  this.newlines();
662
685
  const line = this.lexer.lineAt(this.current.offset);
663
686
  while (this.current.kind !== "end" && !stops.has(this.current.value)) {
687
+ this.budget.admit();
664
688
  const pipelines = [this.pipeline()];
665
689
  const operators = [];
666
690
  while (this.is("&&") || this.is("||")) {
@@ -681,21 +705,24 @@ class Parser {
681
705
  else if (!this.isEnd() && !this.is(")") && !(stops.has(this.current.value) && [";;", ";&", ";;&"].includes(this.current.value)))
682
706
  this.error("Expected command separator");
683
707
  }
684
- const printed = printedSimpleLines(lists, separators);
708
+ const printed = printedSimpleLines(lists, separators, this.budget);
685
709
  return { lists, line, ...printed };
686
710
  }
687
711
  pipeline() {
712
+ this.budget.admit();
688
713
  const negate = this.is("!");
689
714
  if (negate)
690
715
  this.advance();
691
716
  const commands = [this.command()];
692
717
  while (this.is("|") || this.is("|&")) {
693
718
  const operator = this.advance();
694
- if (operator.value === "|&")
719
+ if (operator.value === "|&") {
720
+ this.budget.admit(3);
695
721
  commands.at(-1).redirects.push({
696
722
  descriptor: 2, operator: ">&", implicitPipeline: true,
697
723
  target: { offset: operator.offset, plain: "1", spelling: "1", parts: [{ kind: "text", value: "1", quoted: false }] },
698
724
  });
725
+ }
699
726
  this.newlines();
700
727
  commands.push(this.command());
701
728
  }
@@ -704,10 +731,12 @@ class Parser {
704
731
  command() {
705
732
  if (++this.nesting + this.lexer.depth > 64)
706
733
  this.error("Syntax nesting exceeds 64");
734
+ this.budget.admit();
707
735
  const line = this.lexer.lineAt(this.current.offset);
708
736
  this.openCommands.push({ name: this.current.value, line });
709
737
  try {
710
738
  const command = this.commandInner();
739
+ this.budget.admit();
711
740
  return { ...command, line: command.line ?? line };
712
741
  }
713
742
  finally {
@@ -716,6 +745,7 @@ class Parser {
716
745
  }
717
746
  }
718
747
  commandInner() {
748
+ this.budget.admit();
719
749
  let command;
720
750
  if (this.is("[[")) {
721
751
  const start = this.current.end;
@@ -737,7 +767,10 @@ class Parser {
737
767
  admit();
738
768
  this.advance();
739
769
  const expression = primary(depth + 1);
740
- return expression.kind === "not" && !grouped.has(expression) ? expression.operand : { kind: "not", operand: expression };
770
+ if (expression.kind === "not" && !grouped.has(expression))
771
+ return expression.operand;
772
+ this.budget.admit();
773
+ return { kind: "not", operand: expression };
741
774
  }
742
775
  if (this.is("(")) {
743
776
  this.advance();
@@ -747,6 +780,7 @@ class Parser {
747
780
  return expression;
748
781
  }
749
782
  admit();
783
+ this.budget.admit();
750
784
  const first = operand();
751
785
  if (conditionalBinary.has(this.current.value)) {
752
786
  const operator = this.current.value;
@@ -763,6 +797,7 @@ class Parser {
763
797
  let expression = primary(depth);
764
798
  while (this.is("&&")) {
765
799
  admit();
800
+ this.budget.admit();
766
801
  this.advance();
767
802
  expression = { kind: "and", left: expression, right: primary(depth) };
768
803
  }
@@ -772,6 +807,7 @@ class Parser {
772
807
  let expression = conjunction(depth);
773
808
  while (this.is("||")) {
774
809
  admit();
810
+ this.budget.admit();
775
811
  this.advance();
776
812
  expression = { kind: "or", left: expression, right: conjunction(depth) };
777
813
  }
@@ -789,7 +825,7 @@ class Parser {
789
825
  const start = this.current.offset + 2;
790
826
  const end = arithmeticEnd(this.lexer.source, start);
791
827
  const source = this.lexer.source.slice(start, end);
792
- command = { kind: "arithmetic", expression: prepareArithmetic(source), source, redirects: [] };
828
+ command = { kind: "arithmetic", expression: prepareArithmetic(source, this.budget), source, redirects: [] };
793
829
  this.lexer.position = end + 2;
794
830
  this.lookahead = undefined;
795
831
  this.current = this.lexer.next();
@@ -810,6 +846,7 @@ class Parser {
810
846
  const condition = this.nonemptyScript(new Set(["then"]));
811
847
  this.expect("then");
812
848
  const body = this.nonemptyScript(new Set(["elif", "else", "fi"]));
849
+ this.budget.admit();
813
850
  branches.push({ condition, body });
814
851
  if (!this.is("elif"))
815
852
  break;
@@ -849,6 +886,7 @@ class Parser {
849
886
  if (![";;", ";&", ";;&", "esac"].includes(this.current.value))
850
887
  this.error("Expected case terminator");
851
888
  const terminator = this.current.value;
889
+ this.budget.admit();
852
890
  clauses.push({ patterns, body, terminator });
853
891
  if (terminator === "esac")
854
892
  break;
@@ -903,7 +941,7 @@ class Parser {
903
941
  this.error("Expected brace function body");
904
942
  command = { kind: "function", name, body: this.command(), redirects: [] };
905
943
  }
906
- else if (this.current.kind === "word" && !compoundHead(this.current.word) && this.peek().value === "(") {
944
+ else if (this.current.kind === "word" && !compoundHead(this.current.word, this.budget) && this.peek().value === "(") {
907
945
  const name = this.advance().value;
908
946
  if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name))
909
947
  this.error("Invalid function name");
@@ -930,23 +968,24 @@ class Parser {
930
968
  else if (this.current.kind === "word") {
931
969
  line ??= wordLine;
932
970
  let word = this.advance().word;
933
- const head = compoundHead(word);
971
+ const head = compoundHead(word, this.budget);
934
972
  if (head && this.is("(")) {
935
973
  this.advance();
936
974
  const entries = [];
937
975
  this.newlines();
938
976
  while (this.current.kind === "word") {
939
- entries.push(compoundEntry(this.advance().word));
977
+ entries.push(compoundEntry(this.advance().word, this.budget));
940
978
  this.newlines();
941
979
  }
942
980
  if (!this.is(")"))
943
981
  this.error("Unsupported indexed-array compound assignment");
944
982
  const end = this.advance().end;
983
+ this.budget.admit(2);
945
984
  word = { offset: word.offset, parts: word.parts, spelling: this.lexer.source.slice(word.offset, end) };
946
985
  setArrayAssignment(word, { kind: "compound", ...head, entries });
947
986
  }
948
987
  else {
949
- const assignment = words.every(previous => getArrayAssignment(previous) || scalarAssignmentName(previous)) ? elementAssignment(word) : undefined;
988
+ const assignment = words.every(previous => getArrayAssignment(previous) || scalarAssignmentName(previous)) ? elementAssignment(word, this.budget) : undefined;
950
989
  if (assignment)
951
990
  setArrayAssignment(word, assignment);
952
991
  }
@@ -981,6 +1020,7 @@ class Parser {
981
1020
  }
982
1021
  if (!/^(?:>|>>|<|<<|<<-|<<<|>&|<&|>\||&>)$/u.test(this.current.value) || this.current.kind !== "operator")
983
1022
  return undefined;
1023
+ this.budget.admit();
984
1024
  const operator = this.advance().value;
985
1025
  descriptor ??= operator.startsWith("<") ? 0 : 1;
986
1026
  if (!Number.isSafeInteger(descriptor) || descriptor > 255)
@@ -991,32 +1031,42 @@ class Parser {
991
1031
  return { descriptor, operator, target: target.word, line: this.lexer.lineAt(Math.max(target.offset, target.end - 1)), ...((operator === "<&" || operator === ">&") && this.lexer.source[target.end - 1] === "-" ? { move: true } : {}), ...(target.document ? { document: target.document } : {}) };
992
1032
  }
993
1033
  }
994
- export function parseShell(source, depth = 0) {
1034
+ export function parseShell(source, depth = 0, options = {}) {
1035
+ const budget = new ParseBudget(options.maxParseUnits);
995
1036
  const warnings = [];
996
- const script = parseSource(source, depth, warnings);
1037
+ const script = parseSource(source, depth, warnings, 0, false, budget);
1038
+ budget.admit();
997
1039
  return { ...script, ...(warnings.length ? { warnings } : {}) };
998
1040
  }
999
- export function* hereDocumentWords(document, line, byteLocale, warnings) {
1000
- if (document.quoted)
1041
+ export function* hereDocumentWords(document, line, byteLocale, warnings, budget = new ParseBudget()) {
1042
+ if (document.quoted) {
1043
+ budget.admit(2);
1001
1044
  yield { offset: document.offset, parts: [{ kind: "text", value: document.body, quoted: true }] };
1002
- else
1003
- yield* new Lexer(document.body, document.depth, warnings, line - 1, byteLocale, line).documentWords();
1045
+ }
1046
+ else {
1047
+ budget.admit();
1048
+ yield* new Lexer(budget, document.body, document.depth, warnings, line - 1, byteLocale, line).documentWords();
1049
+ }
1004
1050
  }
1005
- export function parseShellUnit(source, position = 0, byteLocale = false) {
1051
+ export function parseShellUnit(source, position = 0, byteLocale = false, budget = new ParseBudget()) {
1006
1052
  const warnings = [];
1007
- const parser = new Parser(source, 0, warnings, 0, position, byteLocale);
1053
+ budget.admit();
1054
+ const parser = new Parser(budget, source, 0, warnings, 0, position, byteLocale);
1008
1055
  const script = parser.script(new Set(), true);
1009
1056
  const next = parser.current.end;
1010
1057
  const nul = source.indexOf("\0", position);
1011
1058
  if (nul >= 0 && nul < next)
1012
1059
  throw new ShellSyntaxError("NUL bytes are not valid shell source", nul);
1060
+ budget.admit(2);
1013
1061
  return { script: { ...script, ...(warnings.length ? { warnings } : {}) }, next };
1014
1062
  }
1015
- export function parseShellInputUnit(source, byteLocale = false) {
1063
+ export function parseShellInputUnit(source, byteLocale = false, budget = new ParseBudget()) {
1016
1064
  const warnings = [];
1017
1065
  try {
1018
- const parser = new Parser(source, 0, warnings, 0, 0, byteLocale, true);
1066
+ budget.admit();
1067
+ const parser = new Parser(budget, source, 0, warnings, 0, 0, byteLocale, true);
1019
1068
  const script = parser.script(new Set(), true);
1069
+ budget.admit(2);
1020
1070
  return { script: { ...script, ...(warnings.length ? { warnings } : {}) }, next: parser.current.end };
1021
1071
  }
1022
1072
  catch (error) {
@@ -1027,8 +1077,9 @@ export function parseShellInputUnit(source, byteLocale = false) {
1027
1077
  throw error;
1028
1078
  }
1029
1079
  }
1030
- function parseSource(source, depth, warnings, lineOffset = 0, byteLocale = false) {
1031
- const parser = new Parser(source, depth, warnings, lineOffset, undefined, byteLocale);
1080
+ function parseSource(source, depth, warnings, lineOffset, byteLocale, budget) {
1081
+ budget.admit();
1082
+ const parser = new Parser(budget, source, depth, warnings, lineOffset, undefined, byteLocale);
1032
1083
  const script = parser.script();
1033
1084
  if (parser.current.kind !== "end")
1034
1085
  parser.error("Unexpected token");