@poe-platform/safe-bash 0.1.79 → 0.1.80
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 +287 -167
- package/dist/safe-bash/browser.js.map +4 -4
- package/dist/safe-bash/shell/parser.d.ts.map +1 -1
- package/dist/safe-bash/shell/parser.js +33 -22
- package/dist/safe-bash/shell/parser.js.map +1 -1
- package/dist/safe-bash/shell/pattern.d.ts +3 -8
- package/dist/safe-bash/shell/pattern.d.ts.map +1 -1
- package/dist/safe-bash/shell/pattern.js +20 -16
- package/dist/safe-bash/shell/pattern.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 +229 -151
- package/dist/safe-bash/shell/runtime.js.map +1 -1
- package/dist/safe-bash/shell/string-operations.d.ts +17 -0
- package/dist/safe-bash/shell/string-operations.d.ts.map +1 -0
- package/dist/safe-bash/shell/string-operations.js +42 -0
- package/dist/safe-bash/shell/string-operations.js.map +1 -0
- package/package.json +2 -2
|
@@ -5734,6 +5734,46 @@ init_platform();
|
|
|
5734
5734
|
|
|
5735
5735
|
// packages/safe-bash/src/shell/pattern.ts
|
|
5736
5736
|
init_platform();
|
|
5737
|
+
|
|
5738
|
+
// packages/safe-bash/src/shell/string-operations.ts
|
|
5739
|
+
init_platform();
|
|
5740
|
+
function stringCheckpoint(work, units = 1) {
|
|
5741
|
+
work.signal.throwIfAborted();
|
|
5742
|
+
work.remaining -= units;
|
|
5743
|
+
if (work.remaining < 0) work.exhausted();
|
|
5744
|
+
work.steps = (work.steps ?? 0) + units;
|
|
5745
|
+
if (work.steps < 128) return void 0;
|
|
5746
|
+
work.steps %= 128;
|
|
5747
|
+
return yieldTurn(work.signal);
|
|
5748
|
+
}
|
|
5749
|
+
function nextCodePointOffset(value2, offset) {
|
|
5750
|
+
if (offset >= value2.length) return value2.length;
|
|
5751
|
+
return offset + (value2.codePointAt(offset) > 65535 ? 2 : 1);
|
|
5752
|
+
}
|
|
5753
|
+
function previousCodePointOffset(value2, offset) {
|
|
5754
|
+
if (offset <= 0) return 0;
|
|
5755
|
+
const last = value2.charCodeAt(offset - 1);
|
|
5756
|
+
const previous = value2.charCodeAt(offset - 2);
|
|
5757
|
+
return offset - (last >= 56320 && last <= 57343 && previous >= 55296 && previous <= 56319 ? 2 : 1);
|
|
5758
|
+
}
|
|
5759
|
+
async function scanString(value2, work, start = 0, end = value2.length, maximum = Infinity) {
|
|
5760
|
+
let position = start;
|
|
5761
|
+
let count2 = 0;
|
|
5762
|
+
let bytes2 = 0;
|
|
5763
|
+
while (position < end && count2 < maximum) {
|
|
5764
|
+
const pending = stringCheckpoint(work);
|
|
5765
|
+
if (pending) await pending;
|
|
5766
|
+
work.signal.throwIfAborted();
|
|
5767
|
+
const point = position + 1 < end ? value2.codePointAt(position) : value2.charCodeAt(position);
|
|
5768
|
+
position += point > 65535 ? 2 : 1;
|
|
5769
|
+
bytes2 += point <= 127 ? 1 : point <= 2047 ? 2 : point <= 65535 ? 3 : 4;
|
|
5770
|
+
count2++;
|
|
5771
|
+
}
|
|
5772
|
+
work.signal.throwIfAborted();
|
|
5773
|
+
return { end: position, count: count2, bytes: bytes2 };
|
|
5774
|
+
}
|
|
5775
|
+
|
|
5776
|
+
// packages/safe-bash/src/shell/pattern.ts
|
|
5737
5777
|
var characterClasses = {
|
|
5738
5778
|
alnum: "a-zA-Z0-9",
|
|
5739
5779
|
alpha: "a-zA-Z",
|
|
@@ -5751,6 +5791,9 @@ var characterClasses = {
|
|
|
5751
5791
|
xdigit: "a-fA-F0-9"
|
|
5752
5792
|
};
|
|
5753
5793
|
async function tokens(pattern, work) {
|
|
5794
|
+
const admission = stringCheckpoint(work, pattern.length);
|
|
5795
|
+
if (admission) await admission;
|
|
5796
|
+
work.allocation?.reserve(128 + pattern.length * 64, 0);
|
|
5754
5797
|
const result = [];
|
|
5755
5798
|
const characters = Array.from(pattern);
|
|
5756
5799
|
const lastClosingBracket = characters.lastIndexOf("]");
|
|
@@ -5818,37 +5861,36 @@ async function tokens(pattern, work) {
|
|
|
5818
5861
|
async function compilePattern(pattern, work) {
|
|
5819
5862
|
work.signal.throwIfAborted();
|
|
5820
5863
|
const patternTokens = await tokens(pattern, work);
|
|
5821
|
-
return (value2) => matchTokens(patternTokens, value2, work);
|
|
5864
|
+
return (value2, start = 0, end = value2.length) => matchTokens(patternTokens, value2, work, start, end);
|
|
5822
5865
|
}
|
|
5823
5866
|
async function matchesPattern(pattern, value2, work) {
|
|
5824
5867
|
return (await compilePattern(pattern, work))(value2);
|
|
5825
5868
|
}
|
|
5826
|
-
async function matchTokens(patternTokens, value2, work) {
|
|
5869
|
+
async function matchTokens(patternTokens, value2, work, start, end) {
|
|
5827
5870
|
work.signal.throwIfAborted();
|
|
5828
|
-
|
|
5829
|
-
let position = 0;
|
|
5871
|
+
let position = start;
|
|
5830
5872
|
let tokenIndex = 0;
|
|
5831
5873
|
let star = -1;
|
|
5832
|
-
let retry =
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
if (
|
|
5836
|
-
|
|
5837
|
-
await yieldTurn(work.signal);
|
|
5838
|
-
work.signal.throwIfAborted();
|
|
5839
|
-
}
|
|
5874
|
+
let retry = start;
|
|
5875
|
+
while (position < end) {
|
|
5876
|
+
const pending = stringCheckpoint(work);
|
|
5877
|
+
if (pending) await pending;
|
|
5878
|
+
work.signal.throwIfAborted();
|
|
5840
5879
|
const token = patternTokens[tokenIndex];
|
|
5880
|
+
const point = value2.codePointAt(position);
|
|
5841
5881
|
if (token?.kind === "star") {
|
|
5842
5882
|
star = tokenIndex++;
|
|
5843
5883
|
retry = position;
|
|
5844
|
-
} else if (token && (token.kind === "any" || (token.kind === "literal" ? token.value ===
|
|
5845
|
-
position
|
|
5884
|
+
} else if (token && (token.kind === "any" || (token.kind === "literal" ? token.value.codePointAt(0) === point : token.expression.test(String.fromCodePoint(point))))) {
|
|
5885
|
+
position += point > 65535 ? 2 : 1;
|
|
5846
5886
|
tokenIndex++;
|
|
5847
5887
|
} else if (star !== -1) {
|
|
5848
5888
|
tokenIndex = star + 1;
|
|
5849
|
-
|
|
5889
|
+
retry = nextCodePointOffset(value2, retry);
|
|
5890
|
+
position = retry;
|
|
5850
5891
|
} else return false;
|
|
5851
5892
|
}
|
|
5893
|
+
work.signal.throwIfAborted();
|
|
5852
5894
|
while (patternTokens[tokenIndex]?.kind === "star") tokenIndex++;
|
|
5853
5895
|
return tokenIndex === patternTokens.length;
|
|
5854
5896
|
}
|
|
@@ -6082,6 +6124,7 @@ var Lexer = class {
|
|
|
6082
6124
|
documentLine;
|
|
6083
6125
|
partial;
|
|
6084
6126
|
position = 0;
|
|
6127
|
+
operandDepth = 0;
|
|
6085
6128
|
printedNewlineReduction = 0;
|
|
6086
6129
|
unprintedWords = 0;
|
|
6087
6130
|
delimiterOperator;
|
|
@@ -6464,7 +6507,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6464
6507
|
if (this.source[this.position] !== "`") this.error("Unterminated command substitution");
|
|
6465
6508
|
this.position++;
|
|
6466
6509
|
try {
|
|
6467
|
-
parts.push({ kind: "substitution", script: parseSource(source, this.depth + 1, this.warnings, line - 1, this.byteLocale, this.budget), line, quoted });
|
|
6510
|
+
parts.push({ kind: "substitution", script: parseSource(source, this.depth + this.operandDepth + 1, this.warnings, line - 1, this.byteLocale, this.budget), line, quoted });
|
|
6468
6511
|
} catch (error) {
|
|
6469
6512
|
if (this.documentLine === void 0 || !(error instanceof ShellSyntaxError) || /nesting|exceeds/u.test(error.reason)) throw error;
|
|
6470
6513
|
this.budget.admit();
|
|
@@ -6486,7 +6529,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6486
6529
|
let script;
|
|
6487
6530
|
try {
|
|
6488
6531
|
this.budget.admit();
|
|
6489
|
-
nested = new Parser(this.budget, this.source.slice(start), this.depth + 1, this.warnings, this.lineAt(start) - 1, void 0, this.byteLocale);
|
|
6532
|
+
nested = new Parser(this.budget, this.source.slice(start), this.depth + this.operandDepth + 1, this.warnings, this.lineAt(start) - 1, void 0, this.byteLocale);
|
|
6490
6533
|
script = nested.script(/* @__PURE__ */ new Set([")"]));
|
|
6491
6534
|
} catch (error) {
|
|
6492
6535
|
if (this.documentLine !== void 0 && error instanceof ShellSyntaxError && !/nesting|exceeds/u.test(error.reason)) throw this.documentSubstitutionError(this.source.slice(start), error);
|
|
@@ -6524,25 +6567,33 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6524
6567
|
let alternate;
|
|
6525
6568
|
let replacement;
|
|
6526
6569
|
let substring;
|
|
6527
|
-
if (operator) {
|
|
6528
|
-
if (
|
|
6529
|
-
this.
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6570
|
+
if (operator || this.source[this.position] === ":") {
|
|
6571
|
+
if (this.depth + this.operandDepth + 1 > 64) this.error("Syntax nesting exceeds 64");
|
|
6572
|
+
this.operandDepth++;
|
|
6573
|
+
try {
|
|
6574
|
+
if (operator) {
|
|
6575
|
+
if (length) this.error("Invalid length expansion");
|
|
6576
|
+
this.position += operator.length;
|
|
6577
|
+
alternate = this.word(operator.startsWith("/") ? "/}" : "}", quoted && !["#", "##", "%", "%%"].includes(operator) && !operator.startsWith("/"));
|
|
6578
|
+
if (operator.startsWith("/") && this.source[this.position] === "/") {
|
|
6579
|
+
this.position++;
|
|
6580
|
+
replacement = this.word("}");
|
|
6581
|
+
}
|
|
6582
|
+
} else {
|
|
6583
|
+
if (length || !/^(?:[a-zA-Z_][a-zA-Z_0-9]*|[0-9]+)$/u.test(name2)) this.error("Unsupported non-scalar substring expansion");
|
|
6584
|
+
this.position++;
|
|
6585
|
+
const offset = this.word(":}", true, false, true);
|
|
6586
|
+
let substringLength;
|
|
6587
|
+
if (this.source[this.position] === ":") {
|
|
6588
|
+
this.position++;
|
|
6589
|
+
substringLength = this.word("}", true, false, true);
|
|
6590
|
+
}
|
|
6591
|
+
this.budget.admit();
|
|
6592
|
+
substring = { offset, ...substringLength ? { length: substringLength } : {}, source: this.source.slice(parameterStart, this.position + 1) };
|
|
6593
|
+
}
|
|
6594
|
+
} finally {
|
|
6595
|
+
this.operandDepth--;
|
|
6543
6596
|
}
|
|
6544
|
-
this.budget.admit();
|
|
6545
|
-
substring = { offset, ...substringLength ? { length: substringLength } : {}, source: this.source.slice(parameterStart, this.position + 1) };
|
|
6546
6597
|
}
|
|
6547
6598
|
if (this.source[this.position] !== "}") this.error("Unterminated or unsupported parameter expansion");
|
|
6548
6599
|
this.position++;
|
|
@@ -14822,7 +14873,14 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14822
14873
|
holding?.release();
|
|
14823
14874
|
}
|
|
14824
14875
|
}
|
|
14876
|
+
parameterOperandIO(word, state, io) {
|
|
14877
|
+
this.signal.throwIfAborted();
|
|
14878
|
+
const parameterDepth = (io.parameterDepth ?? 0) + 1;
|
|
14879
|
+
if (state.depth + parameterDepth > 64) throw new ShellSyntaxError("Syntax nesting exceeds 64", word.offset);
|
|
14880
|
+
return { ...io, parameterDepth };
|
|
14881
|
+
}
|
|
14825
14882
|
async partValue(part, state, io, hereString) {
|
|
14883
|
+
this.signal.throwIfAborted();
|
|
14826
14884
|
if (part.kind === "failed-substitution") {
|
|
14827
14885
|
if (state.depth >= this.budget.limits.maxSubstitutionDepth) this.budget.fail("maxSubstitutionDepth");
|
|
14828
14886
|
await writeText(io.stderr, part.diagnostic);
|
|
@@ -14848,6 +14906,8 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14848
14906
|
}
|
|
14849
14907
|
if (part.kind === "substitution") {
|
|
14850
14908
|
if (state.depth >= this.budget.limits.maxSubstitutionDepth) this.budget.fail("maxSubstitutionDepth");
|
|
14909
|
+
const parameterDepth = io.parameterDepth ?? 0;
|
|
14910
|
+
if (parameterDepth > 0 && state.depth + parameterDepth + 1 > 64) throw new ShellSyntaxError("Syntax nesting exceeds 64", 0);
|
|
14851
14911
|
const capture = new Capture();
|
|
14852
14912
|
const child = await cloneState(state, this.signal);
|
|
14853
14913
|
child.isolated = true;
|
|
@@ -14898,7 +14958,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14898
14958
|
if (index === void 0) throw new ArrayFailure("index outside 0..2147483647");
|
|
14899
14959
|
const value3 = binding ? binding.get(index) : index === 0 ? state.variables[part.name] : void 0;
|
|
14900
14960
|
this.requireParameter(value3, `${part.name}[${selector.index}]`, state, io, part.line);
|
|
14901
|
-
return part.length ?
|
|
14961
|
+
return part.length ? this.parameterLength(value3 ?? "") : value3 ?? "";
|
|
14902
14962
|
}
|
|
14903
14963
|
if (part.length) return String(binding?.values.size ?? (state.variables[part.name] === void 0 ? 0 : 1));
|
|
14904
14964
|
const values = await this.arrayMembers(part.name, state);
|
|
@@ -14927,17 +14987,18 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14927
14987
|
const missing = value2 === void 0 || part.operator.startsWith(":") && value2 === "";
|
|
14928
14988
|
const operator = part.operator.at(-1);
|
|
14929
14989
|
if (operator === "+" && !missing || operator !== "+" && missing) {
|
|
14990
|
+
const operandIO = this.parameterOperandIO(part.alternate, state, io);
|
|
14930
14991
|
let alternate;
|
|
14931
14992
|
if (operator === "=" && arrayStore(state)?.get(part.name)) {
|
|
14932
14993
|
alternate = "";
|
|
14933
14994
|
await this.arrayZero(state, part.name, async () => {
|
|
14934
|
-
alternate = await this.arrayJoin(requireArrays(state).owner, await this.word(part.alternate, state,
|
|
14995
|
+
alternate = await this.arrayJoin(requireArrays(state).owner, await this.word(part.alternate, state, operandIO, false, false, hereString), "");
|
|
14935
14996
|
return alternate;
|
|
14936
14997
|
});
|
|
14937
14998
|
value2 = alternate;
|
|
14938
|
-
return part.length ?
|
|
14999
|
+
return part.length ? this.parameterLength(value2) : value2;
|
|
14939
15000
|
}
|
|
14940
|
-
retained = concatShellValues(await this.valueWord(part.alternate, state,
|
|
15001
|
+
retained = concatShellValues(await this.valueWord(part.alternate, state, operandIO, false, false, hereString), io[valueScope]);
|
|
14941
15002
|
alternate = shellValueText(retained);
|
|
14942
15003
|
if (operator === "?") throw new ParameterExpansionFailure(`${part.name}: ${alternate || (part.operator.startsWith(":") ? "parameter null or not set" : "parameter not set")}`, io.diagnosticLine ?? part.line);
|
|
14943
15004
|
if (operator === "=") {
|
|
@@ -14950,7 +15011,14 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14950
15011
|
retained = "";
|
|
14951
15012
|
}
|
|
14952
15013
|
} else this.requireParameter(value2, part.name, state, io, part.line);
|
|
14953
|
-
return part.length ?
|
|
15014
|
+
return part.length ? this.parameterLength(value2 ?? "") : retained ?? "";
|
|
15015
|
+
}
|
|
15016
|
+
async parameterLength(value2) {
|
|
15017
|
+
const limit = this.budget.limits.maxExpansionBytes;
|
|
15018
|
+
const work = { remaining: Math.min(Number.MAX_SAFE_INTEGER, limit * 4 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
|
|
15019
|
+
const scanned = await scanString(value2, work);
|
|
15020
|
+
if (scanned.bytes > limit) this.budget.fail("maxExpansionBytes");
|
|
15021
|
+
return String(scanned.count);
|
|
14954
15022
|
}
|
|
14955
15023
|
async substring(part, value2, state, io) {
|
|
14956
15024
|
const owner = arrayStore(state)?.get(part.name) ? requireArrays(state).owner : void 0;
|
|
@@ -14960,147 +15028,198 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14960
15028
|
if (value2 === void 0) return "";
|
|
14961
15029
|
const limit = this.budget.limits.maxExpansionBytes;
|
|
14962
15030
|
if (import_buffer.Buffer.byteLength(value2) > limit) this.budget.fail("maxExpansionBytes");
|
|
14963
|
-
const
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
15031
|
+
const scratch = this.budget.values.scope();
|
|
15032
|
+
const work = { remaining: Math.min(Number.MAX_SAFE_INTEGER, limit * 4 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
|
|
15033
|
+
try {
|
|
15034
|
+
const variables = new Proxy(this.arithmeticVariables(state, line), { get: (target, key) => {
|
|
15035
|
+
this.signal.throwIfAborted();
|
|
15036
|
+
const value3 = Reflect.get(target, key);
|
|
15037
|
+
if (typeof value3 === "string" && import_buffer.Buffer.byteLength(value3) > limit) this.budget.fail("maxExpansionBytes");
|
|
15038
|
+
return value3;
|
|
15039
|
+
} });
|
|
15040
|
+
const arithmetic = async (word) => {
|
|
15041
|
+
const operandIO = this.parameterOperandIO(word, state, io);
|
|
15042
|
+
let source = "";
|
|
15043
|
+
let bytes3 = 0;
|
|
15044
|
+
let retained;
|
|
15045
|
+
for (const entry of word.parts) {
|
|
15046
|
+
this.signal.throwIfAborted();
|
|
15047
|
+
const text = entry.kind === "text" ? entry.value : await this.part(entry, state, operandIO);
|
|
15048
|
+
bytes3 += import_buffer.Buffer.byteLength(text);
|
|
15049
|
+
if (bytes3 > limit) this.budget.fail("maxExpansionBytes");
|
|
15050
|
+
owner?.reserve({ metadata: 32, payload: bytes3, work: text.length + 4 });
|
|
15051
|
+
const pending = stringCheckpoint(work, text.length + 1);
|
|
15052
|
+
if (pending) await pending;
|
|
15053
|
+
const next = scratch.reserve((source.length + text.length) * 2, 0);
|
|
15054
|
+
source += text;
|
|
15055
|
+
retained?.release();
|
|
15056
|
+
retained = next;
|
|
15057
|
+
}
|
|
14973
15058
|
this.signal.throwIfAborted();
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
|
|
15059
|
+
try {
|
|
15060
|
+
return { value: evaluateArithmetic(prepareArithmetic(source, this.budget.parsing), variables, this.budget.parsing), source };
|
|
15061
|
+
} catch (error) {
|
|
15062
|
+
this.rethrowArithmeticControl(error);
|
|
15063
|
+
throw new ExpansionFailure(`${part.name}: ${message(error)}`, line);
|
|
15064
|
+
} finally {
|
|
15065
|
+
retained?.release();
|
|
15066
|
+
}
|
|
15067
|
+
};
|
|
15068
|
+
const offsetExpression = await arithmetic(expression.offset);
|
|
15069
|
+
let bytes2;
|
|
15070
|
+
if (byteLocale(state.variables)) {
|
|
15071
|
+
scratch.reserve(import_buffer.Buffer.byteLength(value2), 0);
|
|
15072
|
+
bytes2 = import_buffer.Buffer.from(value2);
|
|
15073
|
+
}
|
|
15074
|
+
const size = BigInt(bytes2?.byteLength ?? (await scanString(value2, work)).count);
|
|
15075
|
+
const offset = offsetExpression.value < 0n ? size + offsetExpression.value : offsetExpression.value;
|
|
15076
|
+
if (offset < 0n || offset > size) return "";
|
|
15077
|
+
let end = size;
|
|
15078
|
+
if (expression.length) {
|
|
15079
|
+
const length = await arithmetic(expression.length);
|
|
15080
|
+
end = length.value < 0n ? size + length.value : offset + length.value;
|
|
15081
|
+
if (end < offset) throw new ExpansionFailure(`${length.source}: substring expression < 0`, line);
|
|
15082
|
+
if (end > size) end = size;
|
|
14979
15083
|
}
|
|
14980
15084
|
this.signal.throwIfAborted();
|
|
15085
|
+
if (!bytes2) {
|
|
15086
|
+
const start = (await scanString(value2, work, 0, value2.length, Number(offset))).end;
|
|
15087
|
+
const finish = (await scanString(value2, work, start, value2.length, Number(end - offset))).end;
|
|
15088
|
+
if (finish > start) scratch.reserve((finish - start) * 2, 0);
|
|
15089
|
+
return value2.slice(start, finish);
|
|
15090
|
+
}
|
|
15091
|
+
scratch.reserve(Number(end - offset) * 2, 0);
|
|
14981
15092
|
try {
|
|
14982
|
-
return {
|
|
14983
|
-
} catch
|
|
14984
|
-
|
|
14985
|
-
throw new ExpansionFailure(`${part.name}: ${message(error)}`, line);
|
|
15093
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2.subarray(Number(offset), Number(end)));
|
|
15094
|
+
} catch {
|
|
15095
|
+
throw new ExpansionFailure("substring expansion splits a UTF-8 character in a byte locale", line);
|
|
14986
15096
|
}
|
|
14987
|
-
}
|
|
14988
|
-
|
|
14989
|
-
owner?.reserve({ metadata: 128 + value2.length * 64, payload: import_buffer.Buffer.byteLength(value2), allocatedSlots: value2.length, work: value2.length + 8 });
|
|
14990
|
-
const characters = byteLocale(state.variables) ? void 0 : Array.from(value2);
|
|
14991
|
-
const bytes2 = characters ? void 0 : import_buffer.Buffer.from(value2);
|
|
14992
|
-
const size = BigInt(characters?.length ?? bytes2.byteLength);
|
|
14993
|
-
const offset = offsetExpression.value < 0n ? size + offsetExpression.value : offsetExpression.value;
|
|
14994
|
-
if (offset < 0n || offset > size) return "";
|
|
14995
|
-
let end = size;
|
|
14996
|
-
if (expression.length) {
|
|
14997
|
-
const length = await arithmetic(expression.length);
|
|
14998
|
-
end = length.value < 0n ? size + length.value : offset + length.value;
|
|
14999
|
-
if (end < offset) throw new ExpansionFailure(`${length.source}: substring expression < 0`, line);
|
|
15000
|
-
if (end > size) end = size;
|
|
15001
|
-
}
|
|
15002
|
-
this.signal.throwIfAborted();
|
|
15003
|
-
owner?.reserve({ metadata: 96 + value2.length * 32, payload: import_buffer.Buffer.byteLength(value2) * 3, allocatedSlots: value2.length, work: value2.length + 7 });
|
|
15004
|
-
if (characters) return characters.slice(Number(offset), Number(end)).join("");
|
|
15005
|
-
try {
|
|
15006
|
-
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2.subarray(Number(offset), Number(end)));
|
|
15007
|
-
} catch {
|
|
15008
|
-
throw new ExpansionFailure("substring expansion splits a UTF-8 character in a byte locale", line);
|
|
15097
|
+
} finally {
|
|
15098
|
+
scratch.close();
|
|
15009
15099
|
}
|
|
15010
15100
|
}
|
|
15011
15101
|
async parameterPattern(part, text, state, io, hereString) {
|
|
15012
|
-
const owner = arrayStore(state)?.get(part.name) ? requireArrays(state).owner : void 0;
|
|
15013
15102
|
const limit = this.budget.limits.maxExpansionBytes;
|
|
15014
15103
|
if (import_buffer.Buffer.byteLength(text) > limit) this.budget.fail("maxExpansionBytes");
|
|
15015
|
-
const
|
|
15016
|
-
const
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
for (let length = longest ? characters.length : 0; longest ? length >= 0 : length <= characters.length; length += longest ? -1 : 1) {
|
|
15104
|
+
const scratch = this.budget.values.scope();
|
|
15105
|
+
const work = { remaining: Math.min(Number.MAX_SAFE_INTEGER, limit * 4 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes"), allocation: scratch };
|
|
15106
|
+
try {
|
|
15107
|
+
const patternFields = await this.word(part.alternate, state, this.parameterOperandIO(part.alternate, state, io), false, true, hereString);
|
|
15108
|
+
let patternUnits = 0;
|
|
15109
|
+
for (const field of patternFields) {
|
|
15110
|
+
const pending = stringCheckpoint(work, field.length + 1);
|
|
15111
|
+
if (pending) await pending;
|
|
15112
|
+
patternUnits += field.length;
|
|
15113
|
+
}
|
|
15114
|
+
scratch.reserve(patternUnits * 2, 0);
|
|
15115
|
+
const pattern = patternFields.join("");
|
|
15116
|
+
const size = (await scanString(text, work)).count;
|
|
15117
|
+
const matches = await compilePattern(pattern, work);
|
|
15118
|
+
const match = async (start, end, length) => {
|
|
15119
|
+
const pending = stringCheckpoint(work, length + 1);
|
|
15120
|
+
if (pending) await pending;
|
|
15121
|
+
return matches(text, start, end);
|
|
15122
|
+
};
|
|
15123
|
+
const operator = part.operator;
|
|
15124
|
+
if (!operator.startsWith("/")) {
|
|
15125
|
+
const longest = operator.length === 2;
|
|
15038
15126
|
const prefix2 = operator.startsWith("#");
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
replacements
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15127
|
+
let boundary = longest === prefix2 ? text.length : 0;
|
|
15128
|
+
for (let length = longest ? size : 0; longest ? length >= 0 : length <= size; length += longest ? -1 : 1) {
|
|
15129
|
+
if (await match(prefix2 ? 0 : boundary, prefix2 ? boundary : text.length, length)) {
|
|
15130
|
+
const start = prefix2 ? boundary : 0;
|
|
15131
|
+
const end = prefix2 ? text.length : boundary;
|
|
15132
|
+
scratch.reserve((end - start) * 2, 0);
|
|
15133
|
+
return text.slice(start, end);
|
|
15134
|
+
}
|
|
15135
|
+
boundary = longest === prefix2 ? previousCodePointOffset(text, boundary) : nextCodePointOffset(text, boundary);
|
|
15136
|
+
}
|
|
15137
|
+
return text;
|
|
15138
|
+
}
|
|
15139
|
+
scratch.reserve(64, 0);
|
|
15140
|
+
const replacements = [];
|
|
15141
|
+
let replacementBytes = 0;
|
|
15142
|
+
const replacementIO = part.replacement ? this.parameterOperandIO(part.replacement, state, io) : io;
|
|
15143
|
+
for (const [index, entry] of (part.replacement?.parts ?? []).entries()) {
|
|
15144
|
+
let value2 = entry.kind === "text" ? entry.value : await this.part(entry, state, replacementIO, hereString);
|
|
15145
|
+
if (index === 0 && !entry.quoted && /^~(?:\/|$)/u.test(value2)) {
|
|
15146
|
+
const home = state.variables.HOME ?? "~";
|
|
15147
|
+
scratch.reserve((home.length + value2.length - 1) * 2, 0);
|
|
15148
|
+
value2 = home + value2.slice(1);
|
|
15149
|
+
}
|
|
15150
|
+
replacementBytes += import_buffer.Buffer.byteLength(value2);
|
|
15151
|
+
if (replacementBytes > limit) this.budget.fail("maxExpansionBytes");
|
|
15152
|
+
const pending = stringCheckpoint(work, value2.length + 1);
|
|
15153
|
+
if (pending) await pending;
|
|
15154
|
+
scratch.reserve(64 + value2.length * 2, 0);
|
|
15155
|
+
replacements.push({ value: value2, quoted: entry.quoted });
|
|
15156
|
+
}
|
|
15157
|
+
if (!pattern && operator !== "/#" && operator !== "/%") return text;
|
|
15158
|
+
let result = "";
|
|
15159
|
+
let resultBytes = 0;
|
|
15160
|
+
let retained;
|
|
15161
|
+
const append = async (value2, start = 0, end = value2.length) => {
|
|
15162
|
+
resultBytes += (await scanString(value2, work, start, end)).bytes;
|
|
15163
|
+
if (resultBytes > limit) this.budget.fail("maxExpansionBytes");
|
|
15164
|
+
if (start === end) return;
|
|
15165
|
+
const fragment = scratch.reserve((end - start) * 2, 0);
|
|
15166
|
+
const next = scratch.reserve((result.length + end - start) * 2, 0);
|
|
15167
|
+
result += value2.slice(start, end);
|
|
15168
|
+
fragment.release();
|
|
15169
|
+
retained?.release();
|
|
15170
|
+
retained = next;
|
|
15171
|
+
};
|
|
15172
|
+
let position = 0;
|
|
15173
|
+
let positionIndex = 0;
|
|
15174
|
+
while (positionIndex <= size) {
|
|
15175
|
+
let found = false;
|
|
15176
|
+
for (let start = position, startIndex = positionIndex; startIndex <= size; startIndex++, start = nextCodePointOffset(text, start)) {
|
|
15177
|
+
if (operator === "/#" && start !== 0) break;
|
|
15178
|
+
for (let end = text.length, endIndex = size; endIndex >= startIndex; endIndex--, end = previousCodePointOffset(text, end)) {
|
|
15179
|
+
if (operator === "/%" && end !== text.length) break;
|
|
15180
|
+
if (!await match(start, end, endIndex - startIndex)) continue;
|
|
15181
|
+
await append(text, position, start);
|
|
15182
|
+
for (const replacement of replacements) {
|
|
15183
|
+
if (replacement.quoted) await append(replacement.value);
|
|
15184
|
+
else {
|
|
15185
|
+
let fragment = 0;
|
|
15186
|
+
for (let cursor = 0; cursor < replacement.value.length; cursor++) {
|
|
15187
|
+
const pending = stringCheckpoint(work);
|
|
15188
|
+
if (pending) await pending;
|
|
15189
|
+
if (replacement.value[cursor] !== "&") continue;
|
|
15190
|
+
await append(replacement.value, fragment, cursor);
|
|
15191
|
+
await append(text, start, end);
|
|
15192
|
+
fragment = cursor + 1;
|
|
15193
|
+
}
|
|
15194
|
+
await append(replacement.value, fragment);
|
|
15081
15195
|
}
|
|
15082
15196
|
}
|
|
15197
|
+
position = end;
|
|
15198
|
+
positionIndex = endIndex;
|
|
15199
|
+
found = true;
|
|
15200
|
+
if (operator !== "//" || end === text.length) {
|
|
15201
|
+
await append(text, end);
|
|
15202
|
+
return result;
|
|
15203
|
+
}
|
|
15204
|
+
if (end === start) {
|
|
15205
|
+
position = nextCodePointOffset(text, end);
|
|
15206
|
+
positionIndex++;
|
|
15207
|
+
await append(text, end, position);
|
|
15208
|
+
}
|
|
15209
|
+
break;
|
|
15083
15210
|
}
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
}
|
|
15090
|
-
if (end === start) {
|
|
15091
|
-
append(characters[position]);
|
|
15092
|
-
position++;
|
|
15093
|
-
}
|
|
15211
|
+
if (found) break;
|
|
15212
|
+
}
|
|
15213
|
+
if (!found) {
|
|
15214
|
+
if (positionIndex === 0) return text;
|
|
15215
|
+
await append(text, position);
|
|
15094
15216
|
break;
|
|
15095
15217
|
}
|
|
15096
|
-
if (found) break;
|
|
15097
|
-
}
|
|
15098
|
-
if (!found) {
|
|
15099
|
-
append(slice(position));
|
|
15100
|
-
break;
|
|
15101
15218
|
}
|
|
15219
|
+
return result;
|
|
15220
|
+
} finally {
|
|
15221
|
+
scratch.close();
|
|
15102
15222
|
}
|
|
15103
|
-
return result;
|
|
15104
15223
|
}
|
|
15105
15224
|
async word(word, state, io, split = true, pattern = false, hereString = false, conditionalPattern = false, regexAppend) {
|
|
15106
15225
|
return (await this.valueWord(word, state, io, split, pattern, hereString, conditionalPattern, regexAppend)).map(shellValueText);
|
|
@@ -15170,16 +15289,17 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
15170
15289
|
}
|
|
15171
15290
|
if (boundary) addField();
|
|
15172
15291
|
};
|
|
15173
|
-
const parts = word.parts.map((part) => ({ part, splitText: false }));
|
|
15292
|
+
const parts = word.parts.map((part) => ({ part, splitText: false, io }));
|
|
15174
15293
|
for (let index = 0; index < parts.length; index++) {
|
|
15175
|
-
const { part, splitText } = parts[index];
|
|
15294
|
+
const { part, splitText, io: partIO } = parts[index];
|
|
15176
15295
|
const quotedPresence = part.quoted && !(arrayOwned && isQuoteMarker(part));
|
|
15177
15296
|
if (part.kind === "variable" && ["-", "+", ":-", ":+"].includes(part.operator ?? "") && /^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name)) {
|
|
15178
15297
|
const value2 = this.variable(state, part.name);
|
|
15179
15298
|
const missing = value2 === void 0 || part.operator.startsWith(":") && value2 === "";
|
|
15180
15299
|
if (part.operator.endsWith("+") ? !missing : missing) {
|
|
15300
|
+
const operandIO = this.parameterOperandIO(part.alternate, state, partIO);
|
|
15181
15301
|
scratch?.reserve(part.alternate.parts.length * 32, 0);
|
|
15182
|
-
const alternate = part.alternate.parts.map((entry) => ({ part: copyArraySelector(entry, { ...entry, quoted: entry.quoted || part.quoted }), splitText: true }));
|
|
15302
|
+
const alternate = part.alternate.parts.map((entry) => ({ part: copyArraySelector(entry, { ...entry, quoted: entry.quoted || part.quoted }), splitText: true, io: operandIO }));
|
|
15183
15303
|
if (!alternate.length && part.quoted) append("", false, true);
|
|
15184
15304
|
parts.splice(index + 1, 0, ...alternate);
|
|
15185
15305
|
continue;
|
|
@@ -15205,7 +15325,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
15205
15325
|
}
|
|
15206
15326
|
if (state.positional.length === 0 && word.parts.every((entry) => entry.kind === "text" && entry.value === "" || entry === part)) fields[0].present = false;
|
|
15207
15327
|
} else {
|
|
15208
|
-
const value2 = part.kind === "text" ? part.byteValue ?? part.value : await this.valuePart(part, state,
|
|
15328
|
+
const value2 = part.kind === "text" ? part.byteValue ?? part.value : await this.valuePart(part, state, partIO, hereString);
|
|
15209
15329
|
if (part.quoted || !split || state.variables.IFS === "") append(value2, !part.quoted, quotedPresence || !split || shellValueByteLength(value2) > 0);
|
|
15210
15330
|
else await appendSplit(value2);
|
|
15211
15331
|
}
|