@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.
- package/dist/safe-bash/browser.js +435 -176
- package/dist/safe-bash/browser.js.map +4 -4
- package/dist/safe-bash/commands/structured/limits.js +3 -1
- package/dist/safe-bash/commands/structured/limits.js.map +1 -1
- package/dist/safe-bash/commands/structured/split.d.ts.map +1 -1
- package/dist/safe-bash/commands/structured/split.js +7 -2
- package/dist/safe-bash/commands/structured/split.js.map +1 -1
- package/dist/safe-bash/commands/structured/values.d.ts.map +1 -1
- package/dist/safe-bash/commands/structured/values.js +4 -2
- package/dist/safe-bash/commands/structured/values.js.map +1 -1
- package/dist/safe-bash/commands/text.d.ts.map +1 -1
- package/dist/safe-bash/commands/text.js +194 -52
- package/dist/safe-bash/commands/text.js.map +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.d.ts +2 -0
- package/dist/safe-bash/shell/arithmetic-parameters.d.ts.map +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.js +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.js.map +1 -1
- package/dist/safe-bash/shell/arithmetic.d.ts +4 -3
- package/dist/safe-bash/shell/arithmetic.d.ts.map +1 -1
- package/dist/safe-bash/shell/arithmetic.js +19 -7
- package/dist/safe-bash/shell/arithmetic.js.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.d.ts +6 -5
- package/dist/safe-bash/shell/arrays/syntax.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.js +22 -12
- package/dist/safe-bash/shell/arrays/syntax.js.map +1 -1
- package/dist/safe-bash/shell/index.d.ts +1 -1
- package/dist/safe-bash/shell/index.d.ts.map +1 -1
- package/dist/safe-bash/shell/parse-budget.d.ts +10 -0
- package/dist/safe-bash/shell/parse-budget.d.ts.map +1 -0
- package/dist/safe-bash/shell/parse-budget.js +29 -0
- package/dist/safe-bash/shell/parse-budget.js.map +1 -0
- package/dist/safe-bash/shell/parser.d.ts +6 -4
- package/dist/safe-bash/shell/parser.d.ts.map +1 -1
- package/dist/safe-bash/shell/parser.js +80 -29
- package/dist/safe-bash/shell/parser.js.map +1 -1
- package/dist/safe-bash/shell/runtime.d.ts +3 -0
- package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
- package/dist/safe-bash/shell/runtime.js +136 -78
- package/dist/safe-bash/shell/runtime.js.map +1 -1
- package/dist/safe-bash/shell/shell.js +2 -2
- package/dist/safe-bash/shell/shell.js.map +1 -1
- package/dist/safe-bash/shell/types.d.ts +4 -0
- package/dist/safe-bash/shell/types.d.ts.map +1 -1
- package/dist/safe-bash/shell/types.js.map +1 -1
- package/dist/safe-bash/shell/worker-limits.d.ts.map +1 -1
- package/dist/safe-bash/shell/worker-limits.js +1 -0
- package/dist/safe-bash/shell/worker-limits.js.map +1 -1
- 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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|