@poe-platform/safe-bash 0.1.73 → 0.1.75

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 (48) 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.d.ts.map +1 -1
  12. package/dist/safe-bash/commands/text.js +194 -52
  13. package/dist/safe-bash/commands/text.js.map +1 -1
  14. package/dist/safe-bash/shell/arithmetic-parameters.d.ts +2 -0
  15. package/dist/safe-bash/shell/arithmetic-parameters.d.ts.map +1 -1
  16. package/dist/safe-bash/shell/arithmetic-parameters.js +1 -1
  17. package/dist/safe-bash/shell/arithmetic-parameters.js.map +1 -1
  18. package/dist/safe-bash/shell/arithmetic.d.ts +4 -3
  19. package/dist/safe-bash/shell/arithmetic.d.ts.map +1 -1
  20. package/dist/safe-bash/shell/arithmetic.js +19 -7
  21. package/dist/safe-bash/shell/arithmetic.js.map +1 -1
  22. package/dist/safe-bash/shell/arrays/syntax.d.ts +6 -5
  23. package/dist/safe-bash/shell/arrays/syntax.d.ts.map +1 -1
  24. package/dist/safe-bash/shell/arrays/syntax.js +22 -12
  25. package/dist/safe-bash/shell/arrays/syntax.js.map +1 -1
  26. package/dist/safe-bash/shell/index.d.ts +1 -1
  27. package/dist/safe-bash/shell/index.d.ts.map +1 -1
  28. package/dist/safe-bash/shell/parse-budget.d.ts +10 -0
  29. package/dist/safe-bash/shell/parse-budget.d.ts.map +1 -0
  30. package/dist/safe-bash/shell/parse-budget.js +29 -0
  31. package/dist/safe-bash/shell/parse-budget.js.map +1 -0
  32. package/dist/safe-bash/shell/parser.d.ts +6 -4
  33. package/dist/safe-bash/shell/parser.d.ts.map +1 -1
  34. package/dist/safe-bash/shell/parser.js +80 -29
  35. package/dist/safe-bash/shell/parser.js.map +1 -1
  36. package/dist/safe-bash/shell/runtime.d.ts +3 -0
  37. package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
  38. package/dist/safe-bash/shell/runtime.js +136 -78
  39. package/dist/safe-bash/shell/runtime.js.map +1 -1
  40. package/dist/safe-bash/shell/shell.js +2 -2
  41. package/dist/safe-bash/shell/shell.js.map +1 -1
  42. package/dist/safe-bash/shell/types.d.ts +4 -0
  43. package/dist/safe-bash/shell/types.d.ts.map +1 -1
  44. package/dist/safe-bash/shell/types.js.map +1 -1
  45. package/dist/safe-bash/shell/worker-limits.d.ts.map +1 -1
  46. package/dist/safe-bash/shell/worker-limits.js +1 -0
  47. package/dist/safe-bash/shell/worker-limits.js.map +1 -1
  48. package/package.json +2 -2
@@ -7,6 +7,7 @@ import { HereDocumentSyntaxError, hereDocumentWords, parseShellInputUnit, parseS
7
7
  import { ShellLimitError, ShellSyntaxError } from "./types.js";
8
8
  import { fileInput, ShellInput } from "./input.js";
9
9
  import { evaluateArithmetic, prepareArithmetic } from "./arithmetic.js";
10
+ import { defaultMaxParseUnits, ParseBudget } from "./parse-budget.js";
10
11
  import { evaluatePositionalArithmetic } from "./arithmetic-parameters.js";
11
12
  import { compilePattern, matchesPattern } from "./pattern.js";
12
13
  import { byteLocale } from "./locale.js";
@@ -28,6 +29,7 @@ import { EreLedger } from "../commands/regex-execution/ere/limits.js";
28
29
  import { compileEre } from "../commands/regex-execution/ere/syntax.js";
29
30
  import { matchEre } from "../commands/regex-execution/ere/matcher.js";
30
31
  export const defaultLimits = {
32
+ maxParseUnits: defaultMaxParseUnits,
31
33
  maxInputBytes: 32 * 1024 * 1024,
32
34
  maxOutputBytes: 16 * 1024 * 1024,
33
35
  maxCommands: 10_000,
@@ -65,6 +67,7 @@ export function resolveLimits(...limits) {
65
67
  const budgetedSinks = new WeakMap();
66
68
  export class Budget {
67
69
  limits;
70
+ parsing;
68
71
  values;
69
72
  commands = 0;
70
73
  iterations = 0;
@@ -79,6 +82,7 @@ export class Budget {
79
82
  constructor(limits, signal) {
80
83
  this.limits = limits;
81
84
  this.signal = signal ? AbortSignal.any([signal, this.controller.signal]) : this.controller.signal;
85
+ this.parsing = new ParseBudget(limits.maxParseUnits, this.signal, error => this.controller.abort(error));
82
86
  this.values = new ValueArena(limits.maxExpansionBytes, limits.maxExpansionFields, () => this.signal.throwIfAborted(), limit => this.fail(limit));
83
87
  this.#wallClockDeadline = Date.now() + limits.maxWallClockMs;
84
88
  this.#armWallClock();
@@ -1278,7 +1282,7 @@ export class Runtime {
1278
1282
  this.budget.fail("maxExpansionBytes");
1279
1283
  if (name === "OPTIND" && state.getopts?.integer && origin !== "arithmetic") {
1280
1284
  try {
1281
- value = String(evaluateArithmetic(prepareArithmetic(shellValueText(value) || "0"), this.arithmeticVariables(state)));
1285
+ value = String(evaluateArithmetic(prepareArithmetic(shellValueText(value) || "0", this.budget.parsing), this.arithmeticVariables(state), this.budget.parsing));
1282
1286
  }
1283
1287
  catch (error) {
1284
1288
  this.rethrowArithmeticControl(error);
@@ -2074,12 +2078,13 @@ export class Runtime {
2074
2078
  if (command.kind === "arithmetic") {
2075
2079
  try {
2076
2080
  return Number(evaluatePositionalArithmetic(command.expression, {
2081
+ parseBudget: this.budget.parsing,
2077
2082
  positional: state.positional, arg0: state.arg0 ?? "virtual-bash", owner: arrayStore(state)?.owner,
2078
2083
  maximumBytes: this.budget.limits.maxExpansionBytes,
2079
2084
  checkpoint: () => this.signal.throwIfAborted(),
2080
2085
  requireParameter: (name, value) => this.requireParameter(value, name, state, io),
2081
2086
  limit: () => this.budget.fail("maxExpansionBytes"),
2082
- }, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine))) === 0n);
2087
+ }, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine), this.budget.parsing)) === 0n);
2083
2088
  }
2084
2089
  catch (error) {
2085
2090
  this.rethrowArithmeticControl(error);
@@ -2264,7 +2269,7 @@ export class Runtime {
2264
2269
  let words = 0;
2265
2270
  const warnings = [];
2266
2271
  try {
2267
- for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings)) {
2272
+ for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings, this.budget.parsing)) {
2268
2273
  this.signal.throwIfAborted();
2269
2274
  for (const warning of warnings.splice(0))
2270
2275
  await writeText(io.stderr, `shell: warning: ${warning}\n`);
@@ -3274,7 +3279,7 @@ export class Runtime {
3274
3279
  try {
3275
3280
  do {
3276
3281
  this.signal.throwIfAborted();
3277
- const unit = parseShellUnit(source, position, byteLocale(state.variables));
3282
+ const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing);
3278
3283
  for (const warning of unit.script.warnings ?? [])
3279
3284
  await writeText(io.stderr, `${io.scriptName}: warning: ${warning}\n`);
3280
3285
  if (unit.script.lists.length) {
@@ -3318,7 +3323,7 @@ export class Runtime {
3318
3323
  source += this.sourceText(bytes, io.scriptName ?? "shell");
3319
3324
  const unitIO = { ...io, diagnosticOffset: offset };
3320
3325
  try {
3321
- const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables)) : parseShellInputUnit(source, byteLocale(state.variables));
3326
+ const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables), this.budget.parsing) : parseShellInputUnit(source, byteLocale(state.variables), this.budget.parsing);
3322
3327
  if (unit) {
3323
3328
  for (const warning of unit.script.warnings ?? [])
3324
3329
  await writeText(io.stderr, `${io.scriptName}: warning: ${warning}\n`);
@@ -3585,7 +3590,7 @@ export class Runtime {
3585
3590
  let position = 0;
3586
3591
  do {
3587
3592
  this.signal.throwIfAborted();
3588
- const unit = parseShellUnit(source, position, byteLocale(context.env));
3593
+ const unit = parseShellUnit(source, position, byteLocale(context.env), this.budget.parsing);
3589
3594
  units.push(unit.script);
3590
3595
  position = unit.next;
3591
3596
  } while (position < source.length);
@@ -3622,7 +3627,7 @@ export class Runtime {
3622
3627
  try {
3623
3628
  do {
3624
3629
  this.signal.throwIfAborted();
3625
- const unit = parseShellUnit(source, position, byteLocale(state.variables));
3630
+ const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing);
3626
3631
  for (const warning of unit.script.warnings ?? [])
3627
3632
  await writeText(io.stderr, `${io.scriptName ?? "shell"}: warning: ${warning}\n`);
3628
3633
  if (unit.script.lists.length) {
@@ -3992,7 +3997,7 @@ export class Runtime {
3992
3997
  for (let index = offset; index < args.length; index++) {
3993
3998
  this.signal.throwIfAborted();
3994
3999
  try {
3995
- value = evaluateArithmetic(prepareArithmetic(args[index]), variables);
4000
+ value = evaluateArithmetic(prepareArithmetic(args[index], this.budget.parsing), variables, this.budget.parsing);
3996
4001
  }
3997
4002
  catch (error) {
3998
4003
  this.rethrowArithmeticControl(error);
@@ -4725,9 +4730,12 @@ export class Runtime {
4725
4730
  else {
4726
4731
  let index;
4727
4732
  try {
4728
- index = numericIndex(literalIndex(selector, 0));
4733
+ index = numericIndex(literalIndex(selector, 0, this.budget.parsing));
4729
4734
  }
4730
- catch {
4735
+ catch (error) {
4736
+ this.signal.throwIfAborted();
4737
+ if (error instanceof ShellLimitError)
4738
+ throw error;
4731
4739
  await this.diagnostic(context, "indexed array: unsupported subscript");
4732
4740
  status = 2;
4733
4741
  continue;
@@ -4942,12 +4950,13 @@ export class Runtime {
4942
4950
  if (part.kind === "arithmetic") {
4943
4951
  try {
4944
4952
  return String(evaluatePositionalArithmetic(part.expression, {
4953
+ parseBudget: this.budget.parsing,
4945
4954
  positional: state.positional, arg0: state.arg0 ?? "virtual-bash", owner: arrayStore(state)?.owner,
4946
4955
  maximumBytes: this.budget.limits.maxExpansionBytes,
4947
4956
  checkpoint: () => this.signal.throwIfAborted(),
4948
4957
  requireParameter: (name, value) => this.requireParameter(value, name, state, io, part.line),
4949
4958
  limit: () => this.budget.fail("maxExpansionBytes"),
4950
- }, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine ?? part.line))));
4959
+ }, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine ?? part.line), this.budget.parsing)));
4951
4960
  }
4952
4961
  catch (error) {
4953
4962
  this.rethrowArithmeticControl(error);
@@ -5113,7 +5122,7 @@ export class Runtime {
5113
5122
  }
5114
5123
  this.signal.throwIfAborted();
5115
5124
  try {
5116
- return { value: evaluateArithmetic(prepareArithmetic(source), variables), source };
5125
+ return { value: evaluateArithmetic(prepareArithmetic(source, this.budget.parsing), variables, this.budget.parsing), source };
5117
5126
  }
5118
5127
  catch (error) {
5119
5128
  this.rethrowArithmeticControl(error);
@@ -5264,11 +5273,22 @@ export class Runtime {
5264
5273
  const arrayOwned = word.parts.some(part => part.kind === "variable" && (getArraySelector(part) !== undefined || arrayStore(state)?.get(part.name) !== undefined));
5265
5274
  const owner = arrayOwned ? requireArrays(state).owner : undefined;
5266
5275
  const holding = owner?.hold();
5276
+ const scratch = !owner && split && state.variables.IFS !== "" && word.parts.some(part => !part.quoted && part.kind !== "text")
5277
+ ? this.budget.values.scope() : undefined;
5267
5278
  try {
5268
5279
  if (owner)
5269
5280
  await this.prepareArrayObservers(state, owner);
5270
5281
  owner?.reserve({ metadata: 128 + word.parts.length * 32, allocatedSlots: word.parts.length + 1, work: word.parts.length + 5 });
5271
- const fields = [{ value: "", fragments: [], bytes: false, pattern: "", present: false }];
5282
+ scratch?.reserve(word.parts.length * 32, 0);
5283
+ const fields = [];
5284
+ const addField = () => {
5285
+ if (fields.length >= this.budget.limits.maxExpansionFields)
5286
+ this.budget.fail("maxExpansionFields");
5287
+ scratch?.reserve(32, 0);
5288
+ owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
5289
+ fields.push({ fragments: [], bytes: false, patterns: undefined, present: false });
5290
+ };
5291
+ addField();
5272
5292
  let expansionBytes = 0;
5273
5293
  const append = (value, glob, present) => {
5274
5294
  const text = shellValueText(value);
@@ -5276,21 +5296,59 @@ export class Runtime {
5276
5296
  if (size > this.budget.limits.maxExpansionBytes - expansionBytes)
5277
5297
  this.budget.fail("maxExpansionBytes");
5278
5298
  expansionBytes += size;
5279
- regexAppend?.(text, !glob);
5280
5299
  const field = fields.at(-1);
5300
+ let escapes = 0;
5301
+ if (!glob) {
5302
+ const special = conditionalPattern ? "\\*?[]-^()|+!@" : "\\*?[]-^";
5303
+ for (const character of text)
5304
+ if (special.includes(character))
5305
+ escapes++;
5306
+ }
5307
+ scratch?.reserve(32, 0);
5281
5308
  if (owner)
5282
- owner.reserve({ payload: exactSum(Buffer.byteLength(field.value) + size, Buffer.byteLength(field.pattern) + (glob ? size : size * 2)), metadata: 64, work: text.length + 8 });
5309
+ owner.reserve({ payload: size + (escapes ? size + escapes : 0), metadata: 64, work: text.length + 8 });
5310
+ if (escapes) {
5311
+ scratch?.reserve((field.patterns ? 32 : 32 * (field.fragments.length + 1)) + (text.length + escapes) * 2, 0);
5312
+ field.patterns ??= field.fragments.map(shellValueText);
5313
+ field.patterns.push(text.replace(conditionalPattern ? /[\\*?[\]\-^()|+!@]/gu : /[\\*?[\]\-^]/gu, "\\$&"));
5314
+ }
5315
+ else if (field.patterns) {
5316
+ scratch?.reserve(32, 0);
5317
+ field.patterns.push(text);
5318
+ }
5283
5319
  if (typeof value !== "string" || field.bytes) {
5284
- io[valueScope]?.reserve(32 * (field.bytes ? 1 : field.fragments.length + 1), field.bytes ? 1 : field.fragments.length + 1);
5320
+ if (!scratch)
5321
+ io[valueScope]?.reserve(32 * (field.bytes ? 1 : field.fragments.length + 1), field.bytes ? 1 : field.fragments.length + 1);
5285
5322
  if (typeof value !== "string")
5286
5323
  io[valueScope]?.hold(value);
5287
5324
  field.bytes = true;
5288
5325
  }
5326
+ regexAppend?.(text, !glob);
5289
5327
  field.fragments.push(value);
5290
- field.value += text;
5291
- field.pattern += glob ? text : text.replace(conditionalPattern ? /[\\*?[\]\-^()|+!@]/gu : /[\\*?[\]\-^]/gu, "\\$&");
5292
5328
  field.present ||= present;
5293
5329
  };
5330
+ const appendSplit = async (value) => {
5331
+ const separators = state.variables.IFS ?? " \t\n";
5332
+ let boundary = false;
5333
+ for await (const piece of this.splitValue(value, separators, io, scratch)) {
5334
+ if (typeof piece === "string" && separators.includes(piece)) {
5335
+ if (!" \t\n".includes(piece)) {
5336
+ fields.at(-1).present = true;
5337
+ addField();
5338
+ }
5339
+ else if (fields.at(-1).present)
5340
+ boundary = true;
5341
+ }
5342
+ else {
5343
+ if (boundary)
5344
+ addField();
5345
+ boundary = false;
5346
+ append(piece, true, true);
5347
+ }
5348
+ }
5349
+ if (boundary)
5350
+ addField();
5351
+ };
5294
5352
  const parts = word.parts.map((part) => ({ part, splitText: false }));
5295
5353
  for (let index = 0; index < parts.length; index++) {
5296
5354
  const { part, splitText } = parts[index];
@@ -5299,6 +5357,7 @@ export class Runtime {
5299
5357
  const value = this.variable(state, part.name);
5300
5358
  const missing = value === undefined || (part.operator.startsWith(":") && value === "");
5301
5359
  if (part.operator.endsWith("+") ? !missing : missing) {
5360
+ scratch?.reserve(part.alternate.parts.length * 32, 0);
5302
5361
  const alternate = part.alternate.parts.map((entry) => ({ part: copyArraySelector(entry, { ...entry, quoted: entry.quoted || part.quoted }), splitText: true }));
5303
5362
  if (!alternate.length && part.quoted)
5304
5363
  append("", false, true);
@@ -5310,41 +5369,13 @@ export class Runtime {
5310
5369
  if (part.kind === "variable" && selector?.kind === "members" && !part.length && split && (!part.quoted || selector.separator === "@")) {
5311
5370
  const members = await this.arrayMembers(part.name, state);
5312
5371
  for (let position = 0; position < members.length; position++) {
5313
- if (position > 0) {
5314
- owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
5315
- fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
5316
- }
5372
+ if (position > 0)
5373
+ addField();
5317
5374
  const value = members[position];
5318
5375
  if (part.quoted || state.variables.IFS === "")
5319
5376
  append(value, !part.quoted, part.quoted || value.length > 0);
5320
- else {
5321
- const separators = state.variables.IFS ?? " \t\n";
5322
- let boundary = false;
5323
- for (const character of value) {
5324
- if (separators.includes(character)) {
5325
- if (!/[ \t\n]/u.test(character)) {
5326
- fields.at(-1).present = true;
5327
- owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
5328
- fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
5329
- }
5330
- else if (fields.at(-1).present)
5331
- boundary = true;
5332
- }
5333
- else {
5334
- if (boundary) {
5335
- owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
5336
- fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
5337
- }
5338
- boundary = false;
5339
- append(character, true, true);
5340
- }
5341
- await owner.ledger.checkpoint(this.signal);
5342
- }
5343
- if (boundary) {
5344
- owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
5345
- fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
5346
- }
5347
- }
5377
+ else
5378
+ await appendSplit(value);
5348
5379
  }
5349
5380
  }
5350
5381
  else if (part.kind === "text" && !splitText) {
@@ -5356,7 +5387,7 @@ export class Runtime {
5356
5387
  else if (part.kind === "variable" && part.name === "@" && part.quoted && !part.operator && split) {
5357
5388
  for (let position = 0; position < state.positional.length; position++) {
5358
5389
  if (position > 0)
5359
- fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
5390
+ addField();
5360
5391
  append(stateMonitor(state)?.positionals.get(String(position), state.positional[position]) ?? state.positional[position], false, true);
5361
5392
  }
5362
5393
  if (state.positional.length === 0 && word.parts.every((entry) => (entry.kind === "text" && entry.value === "") || entry === part))
@@ -5366,30 +5397,8 @@ export class Runtime {
5366
5397
  const value = part.kind === "text" ? part.byteValue ?? part.value : await this.valuePart(part, state, io, hereString);
5367
5398
  if (part.quoted || !split || state.variables.IFS === "")
5368
5399
  append(value, !part.quoted, quotedPresence || !split || shellValueByteLength(value) > 0);
5369
- else {
5370
- const separators = state.variables.IFS ?? " \t\n";
5371
- let boundary = false;
5372
- const pieces = this.splitValue(value, separators, io);
5373
- for (const character of pieces) {
5374
- const text = shellValueText(character);
5375
- if (typeof character === "string" && separators.includes(character)) {
5376
- if (!/[ \t\n]/u.test(text)) {
5377
- fields.at(-1).present = true;
5378
- fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
5379
- }
5380
- else if (fields.at(-1).present)
5381
- boundary = true;
5382
- }
5383
- else {
5384
- if (boundary)
5385
- fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
5386
- boundary = false;
5387
- append(character, true, true);
5388
- }
5389
- }
5390
- if (boundary)
5391
- fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
5392
- }
5400
+ else
5401
+ await appendSplit(value);
5393
5402
  }
5394
5403
  if (fields.length > this.budget.limits.maxExpansionFields)
5395
5404
  this.budget.fail("maxExpansionFields");
@@ -5401,15 +5410,27 @@ export class Runtime {
5401
5410
  for (const field of fields) {
5402
5411
  if (!field.present && split)
5403
5412
  continue;
5413
+ if (scratch && field.fragments.length > 1 && !field.bytes)
5414
+ scratch.reserve(field.fragments.reduce((bytes, value) => bytes + shellValueText(value).length * 2, 0), 0);
5404
5415
  const assembled = concatShellValues(field.fragments, io[valueScope]);
5405
5416
  const projection = shellValueText(assembled);
5406
- const expanded = split ? await this.glob(projection, field.pattern, state) : [pattern ? field.pattern : projection];
5417
+ if (field.bytes && field.fragments.length > 1 && !field.patterns) {
5418
+ scratch?.reserve(field.fragments.length * 32, 0);
5419
+ field.patterns = field.fragments.map(shellValueText);
5420
+ }
5421
+ if (scratch && field.patterns && field.patterns.length > 1)
5422
+ scratch.reserve(field.patterns.reduce((bytes, text) => bytes + text.length * 2, 0), 0);
5423
+ const fieldPattern = field.patterns ? field.patterns.join("") : projection;
5424
+ const expanded = split ? await this.glob(projection, fieldPattern, state) : [pattern ? fieldPattern : projection];
5407
5425
  for (const text of expanded) {
5426
+ if (result.length >= this.budget.limits.maxExpansionFields)
5427
+ this.budget.fail("maxExpansionFields");
5408
5428
  const value = !pattern && expanded.length === 1 && text === projection ? assembled : text;
5409
5429
  const size = shellValueByteLength(value);
5410
5430
  if (size > this.budget.limits.maxExpansionBytes - resultBytes)
5411
5431
  this.budget.fail("maxExpansionBytes");
5412
5432
  resultBytes += size;
5433
+ scratch?.reserve(32, 0);
5413
5434
  owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
5414
5435
  result.push(value);
5415
5436
  }
@@ -5419,17 +5440,54 @@ export class Runtime {
5419
5440
  return result;
5420
5441
  }
5421
5442
  finally {
5443
+ scratch?.close();
5422
5444
  holding?.release();
5423
5445
  }
5424
5446
  }
5425
- *splitValue(value, separators, io) {
5447
+ splitWork;
5448
+ async *splitValue(value, separators, io, scratch) {
5449
+ this.budget.cpuCheckpoint();
5450
+ const work = this.splitWork ??= { scanned: 0 };
5426
5451
  if (typeof value === "string" || Array.from(separators).some(character => character.charCodeAt(0) > 127)) {
5427
- yield* shellValueText(value);
5452
+ const text = shellValueText(value);
5453
+ const slice = (start, end) => {
5454
+ if (start === 0 && end === text.length)
5455
+ return text;
5456
+ scratch?.reserve((end - start) * 2, 0);
5457
+ return text.slice(start, end);
5458
+ };
5459
+ let start = 0;
5460
+ for (let index = 0; index < text.length;) {
5461
+ const character = String.fromCodePoint(text.codePointAt(index));
5462
+ const end = index + character.length;
5463
+ if (separators.includes(character)) {
5464
+ if (start < index)
5465
+ yield slice(start, index);
5466
+ yield character;
5467
+ start = end;
5468
+ }
5469
+ else if (end - start >= 4096) {
5470
+ yield slice(start, end);
5471
+ start = end;
5472
+ }
5473
+ work.scanned += character.length;
5474
+ index = end;
5475
+ if (work.scanned >= 4096) {
5476
+ work.scanned = 0;
5477
+ await yieldTurn(this.signal);
5478
+ }
5479
+ }
5480
+ if (start < text.length)
5481
+ yield slice(start, text.length);
5428
5482
  return;
5429
5483
  }
5430
5484
  const bytes = shellValueBytes(value, io[valueScope]);
5431
5485
  let start = 0;
5432
5486
  for (let index = 0; index < bytes.length; index++) {
5487
+ if (++work.scanned >= 4096) {
5488
+ work.scanned = 0;
5489
+ await yieldTurn(this.signal);
5490
+ }
5433
5491
  const byte = bytes[index];
5434
5492
  if (byte > 127 || !separators.includes(String.fromCharCode(byte)))
5435
5493
  continue;