@poe-platform/safe-bash 0.1.526 → 0.1.528
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/core.browser.js +408 -31
- package/dist/safe-bash/core.browser.js.map +4 -4
- package/dist/safe-bash/shell/arrays/bindings.d.ts +4 -1
- package/dist/safe-bash/shell/arrays/bindings.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/bindings.js +44 -1
- package/dist/safe-bash/shell/arrays/bindings.js.map +1 -1
- package/dist/safe-bash/shell/display.d.ts.map +1 -1
- package/dist/safe-bash/shell/display.js +3 -0
- package/dist/safe-bash/shell/display.js.map +1 -1
- package/dist/safe-bash/shell/input.d.ts +4 -0
- package/dist/safe-bash/shell/input.d.ts.map +1 -1
- package/dist/safe-bash/shell/input.js +53 -0
- package/dist/safe-bash/shell/input.js.map +1 -1
- package/dist/safe-bash/shell/mapfile.d.ts +29 -0
- package/dist/safe-bash/shell/mapfile.d.ts.map +1 -0
- package/dist/safe-bash/shell/mapfile.js +134 -0
- package/dist/safe-bash/shell/mapfile.js.map +1 -0
- package/dist/safe-bash/shell/parser.d.ts +4 -0
- package/dist/safe-bash/shell/parser.d.ts.map +1 -1
- package/dist/safe-bash/shell/parser.js +43 -0
- package/dist/safe-bash/shell/parser.js.map +1 -1
- package/dist/safe-bash/shell/runtime.d.ts +4 -2
- package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
- package/dist/safe-bash/shell/runtime.js +215 -30
- package/dist/safe-bash/shell/runtime.js.map +1 -1
- package/package.json +2 -2
|
@@ -30,7 +30,8 @@ import { cloneGetoptsState, createGetoptsInput, createGetoptsState, GetoptsError
|
|
|
30
30
|
import { activateChildCancellation, prepareChildCancellation, selectRuntimeCancellationOutcome, subscribeCancellation, } from "./cancellation.js";
|
|
31
31
|
import { getArrayAssignment, getArraySelector, copyArraySelector, numericIndex, literalIndex, isQuoteMarker, prefixNameQuoteGroups } from "./arrays/syntax.js";
|
|
32
32
|
import { ArrayFailure, ArrayOwner, exactSum } from "./arrays/ledger.js";
|
|
33
|
-
import { controlNames, IndexedBinding, textToken } from "./arrays/bindings.js";
|
|
33
|
+
import { controlNames, IndexedBinding, textToken, valueToken } from "./arrays/bindings.js";
|
|
34
|
+
import { collectMapfile, mapfileOptions, MapfileUsageError } from "./mapfile.js";
|
|
34
35
|
import { arrayStore, guestArrays, requireArrays, snapshotState, stateMonitor, trackState } from "./arrays/state.js";
|
|
35
36
|
import { publishPipelineStatus } from "./pipestatus.js";
|
|
36
37
|
import { EreProfileLimitError, EreSyntaxError, EreUnsupportedError } from "../commands/regex-execution/ere/errors.js";
|
|
@@ -58,7 +59,7 @@ export const defaultLimits = {
|
|
|
58
59
|
pipeHighWaterMark: 64 * 1024,
|
|
59
60
|
};
|
|
60
61
|
const shellBuiltinNames = new Set([
|
|
61
|
-
":", "true", "false", "pwd", "cd", "set", "shift", "export", "local", "unset", "read",
|
|
62
|
+
":", "true", "false", "pwd", "cd", "set", "shift", "export", "local", "unset", "read", "mapfile", "readarray",
|
|
62
63
|
"exit", "return", "break", "continue", "command", "builtin", "type", "readonly", "echo", "printf", "test", "[", ".", "source", "eval", "getopts", "let", "pushd", "dirs", "popd", "shopt",
|
|
63
64
|
]);
|
|
64
65
|
const implementedBuiltins = new Set([...shellBuiltinNames].filter(name => !["echo", "printf", "test", "["].includes(name)));
|
|
@@ -1081,6 +1082,7 @@ class InvocationCancellationOwner {
|
|
|
1081
1082
|
this.#failures.push(...result.failures);
|
|
1082
1083
|
}
|
|
1083
1084
|
}
|
|
1085
|
+
const mapfileCallbackStates = new WeakSet();
|
|
1084
1086
|
export class Runtime {
|
|
1085
1087
|
fs;
|
|
1086
1088
|
commands;
|
|
@@ -1409,6 +1411,16 @@ export class Runtime {
|
|
|
1409
1411
|
if (error instanceof NounsetFailure || error instanceof Flow || error instanceof ShellLimitError || error instanceof ShellSyntaxError)
|
|
1410
1412
|
throw error;
|
|
1411
1413
|
}
|
|
1414
|
+
arithmeticValue(program, state, io) {
|
|
1415
|
+
return evaluatePositionalArithmetic(program, {
|
|
1416
|
+
parseBudget: this.budget.parsing,
|
|
1417
|
+
positional: state.positional, arg0: state.arg0 ?? "virtual-bash", owner: arrayStore(state)?.owner,
|
|
1418
|
+
maximumBytes: this.budget.limits.maxExpansionBytes,
|
|
1419
|
+
checkpoint: () => this.signal.throwIfAborted(),
|
|
1420
|
+
requireParameter: (name, value) => this.requireParameter(value, name, state, io),
|
|
1421
|
+
limit: () => this.budget.fail("maxExpansionBytes"),
|
|
1422
|
+
}, prepared => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine), this.budget.parsing));
|
|
1423
|
+
}
|
|
1412
1424
|
arithmeticVariables(state, line) {
|
|
1413
1425
|
return new Proxy(state.variables, {
|
|
1414
1426
|
get: (target, key) => {
|
|
@@ -1436,7 +1448,7 @@ export class Runtime {
|
|
|
1436
1448
|
this.writeVariable(state, name, value, origin);
|
|
1437
1449
|
return;
|
|
1438
1450
|
}
|
|
1439
|
-
await this.arrayZero(state, name, async () =>
|
|
1451
|
+
await this.arrayZero(state, name, async () => value);
|
|
1440
1452
|
}
|
|
1441
1453
|
async prepareVariable(state, name, saved, scalarLegacy = false) {
|
|
1442
1454
|
const store = requireArrays(state);
|
|
@@ -1619,6 +1631,7 @@ export class Runtime {
|
|
|
1619
1631
|
const store = requireArrays(state);
|
|
1620
1632
|
const operation = ArrayOwner.create(store.owner.ledger, store.owner);
|
|
1621
1633
|
const holding = store.owner.hold();
|
|
1634
|
+
const valueAllocation = this.budget.values.scope();
|
|
1622
1635
|
let staged;
|
|
1623
1636
|
try {
|
|
1624
1637
|
const watch = await store.watch(name, operation, this.signal);
|
|
@@ -1639,8 +1652,10 @@ export class Runtime {
|
|
|
1639
1652
|
if (!current)
|
|
1640
1653
|
throw new ArrayFailure("stale binding");
|
|
1641
1654
|
staged = await current.copy(this.signal);
|
|
1642
|
-
const
|
|
1643
|
-
const
|
|
1655
|
+
const previous = current.values.get(0)?.text.rawValue ?? current.get(0) ?? "";
|
|
1656
|
+
const value = !append ? expanded : typeof previous === "string" && typeof expanded === "string"
|
|
1657
|
+
? await this.arrayJoin(operation, [previous, expanded], "") : concatShellValues([previous, expanded], valueAllocation);
|
|
1658
|
+
const token = await valueToken(staged.owner, value, this.signal);
|
|
1644
1659
|
try {
|
|
1645
1660
|
staged.insert(0, token);
|
|
1646
1661
|
}
|
|
@@ -1673,6 +1688,7 @@ export class Runtime {
|
|
|
1673
1688
|
await operation.close();
|
|
1674
1689
|
}
|
|
1675
1690
|
finally {
|
|
1691
|
+
valueAllocation.close();
|
|
1676
1692
|
holding.release();
|
|
1677
1693
|
}
|
|
1678
1694
|
}
|
|
@@ -1747,7 +1763,7 @@ export class Runtime {
|
|
|
1747
1763
|
const preserve = assignment.kind === "element" || assignment.append;
|
|
1748
1764
|
staged = preserve && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner);
|
|
1749
1765
|
if (preserve && !current && state.variables[name] !== undefined) {
|
|
1750
|
-
const token = await
|
|
1766
|
+
const token = await valueToken(staged.owner, stateMonitor(state)?.values.get(name, state.variables[name]) ?? state.variables[name], this.signal);
|
|
1751
1767
|
try {
|
|
1752
1768
|
staged.insert(0, token);
|
|
1753
1769
|
}
|
|
@@ -1761,7 +1777,7 @@ export class Runtime {
|
|
|
1761
1777
|
const insert = async (index, value) => {
|
|
1762
1778
|
if (index > 2147483647)
|
|
1763
1779
|
throw new ArrayFailure("index outside 0..2147483647");
|
|
1764
|
-
const token = await
|
|
1780
|
+
const token = await valueToken(staged.owner, value, this.signal);
|
|
1765
1781
|
try {
|
|
1766
1782
|
staged.insert(index, token);
|
|
1767
1783
|
}
|
|
@@ -1771,12 +1787,14 @@ export class Runtime {
|
|
|
1771
1787
|
}
|
|
1772
1788
|
writes++;
|
|
1773
1789
|
};
|
|
1790
|
+
const join = async (values) => values.every(value => typeof value === "string")
|
|
1791
|
+
? this.arrayJoin(operation, values, "") : concatShellValues(values, io[valueScope]);
|
|
1774
1792
|
if (assignment.kind === "element") {
|
|
1775
1793
|
const index = numericIndex(assignment.index);
|
|
1776
|
-
const fields = await this.
|
|
1777
|
-
let value = await
|
|
1794
|
+
const fields = await this.valueWord(assignment.value, state, io, false);
|
|
1795
|
+
let value = await join(fields);
|
|
1778
1796
|
if (assignment.append)
|
|
1779
|
-
value = await
|
|
1797
|
+
value = await join([staged.values.get(index)?.text.rawValue ?? staged.get(index) ?? "", value]);
|
|
1780
1798
|
await insert(index, value);
|
|
1781
1799
|
}
|
|
1782
1800
|
else
|
|
@@ -1790,17 +1808,17 @@ export class Runtime {
|
|
|
1790
1808
|
expandedEntry = true;
|
|
1791
1809
|
const values = await this.valueWord(expanded, state, io, true, false, false, false, undefined, false);
|
|
1792
1810
|
for (const value of values) {
|
|
1793
|
-
await insert(cursor,
|
|
1811
|
+
await insert(cursor, value);
|
|
1794
1812
|
cursor++;
|
|
1795
1813
|
}
|
|
1796
1814
|
}
|
|
1797
1815
|
if (expandedEntry)
|
|
1798
1816
|
continue;
|
|
1799
1817
|
}
|
|
1800
|
-
const fields = await this.
|
|
1818
|
+
const fields = await this.valueWord(entry.value, state, io, entry.index === undefined);
|
|
1801
1819
|
if (entry.index) {
|
|
1802
1820
|
const index = numericIndex(entry.index);
|
|
1803
|
-
await insert(index, await
|
|
1821
|
+
await insert(index, await join(fields));
|
|
1804
1822
|
cursor = index + 1;
|
|
1805
1823
|
}
|
|
1806
1824
|
else
|
|
@@ -2190,14 +2208,7 @@ export class Runtime {
|
|
|
2190
2208
|
}
|
|
2191
2209
|
if (command.kind === "arithmetic") {
|
|
2192
2210
|
try {
|
|
2193
|
-
return Number(
|
|
2194
|
-
parseBudget: this.budget.parsing,
|
|
2195
|
-
positional: state.positional, arg0: state.arg0 ?? "virtual-bash", owner: arrayStore(state)?.owner,
|
|
2196
|
-
maximumBytes: this.budget.limits.maxExpansionBytes,
|
|
2197
|
-
checkpoint: () => this.signal.throwIfAborted(),
|
|
2198
|
-
requireParameter: (name, value) => this.requireParameter(value, name, state, io),
|
|
2199
|
-
limit: () => this.budget.fail("maxExpansionBytes"),
|
|
2200
|
-
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine), this.budget.parsing)) === 0n);
|
|
2211
|
+
return Number(this.arithmeticValue(command.expression, state, io) === 0n);
|
|
2201
2212
|
}
|
|
2202
2213
|
catch (error) {
|
|
2203
2214
|
this.rethrowArithmeticControl(error);
|
|
@@ -2267,6 +2278,37 @@ export class Runtime {
|
|
|
2267
2278
|
break;
|
|
2268
2279
|
}
|
|
2269
2280
|
}
|
|
2281
|
+
else if (command.kind === "arithmetic-for") {
|
|
2282
|
+
const evaluate = async (program) => {
|
|
2283
|
+
if (!program)
|
|
2284
|
+
return 1n;
|
|
2285
|
+
try {
|
|
2286
|
+
return this.arithmeticValue(program, state, io);
|
|
2287
|
+
}
|
|
2288
|
+
catch (error) {
|
|
2289
|
+
this.rethrowArithmeticControl(error);
|
|
2290
|
+
await this.diagnostic(io, `((: ${message(error, this.budget.onInternalError)}`);
|
|
2291
|
+
return undefined;
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2294
|
+
if (await evaluate(command.expressions[0]) === undefined)
|
|
2295
|
+
return 1;
|
|
2296
|
+
while (true) {
|
|
2297
|
+
this.budget.loop();
|
|
2298
|
+
await yieldTurn(this.signal);
|
|
2299
|
+
const condition = await evaluate(command.expressions[1]);
|
|
2300
|
+
if (condition === undefined)
|
|
2301
|
+
return 1;
|
|
2302
|
+
if (condition === 0n)
|
|
2303
|
+
break;
|
|
2304
|
+
const result = await this.loopBody(command.body, state, io);
|
|
2305
|
+
status = result.status;
|
|
2306
|
+
if (result.stop)
|
|
2307
|
+
break;
|
|
2308
|
+
if (await evaluate(command.expressions[2]) === undefined)
|
|
2309
|
+
return 1;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2270
2312
|
else if (command.kind === "select") {
|
|
2271
2313
|
const values = command.words ? await this.valueWords(command.words, state, io) : this.positionalValues(state);
|
|
2272
2314
|
if (!values.length)
|
|
@@ -2784,8 +2826,8 @@ export class Runtime {
|
|
|
2784
2826
|
throw new ArrayFailure("indexed binding cannot be a command prefix");
|
|
2785
2827
|
}
|
|
2786
2828
|
await this.arrayZero(state, assignment.name, async () => {
|
|
2787
|
-
const fields = await this.
|
|
2788
|
-
return this.arrayJoin(requireArrays(state).owner, fields, "");
|
|
2829
|
+
const fields = await this.valueWord(assignment.value, state, io, false);
|
|
2830
|
+
return fields.every(value => typeof value === "string") ? this.arrayJoin(requireArrays(state).owner, fields, "") : concatShellValues(fields, io[valueScope]);
|
|
2789
2831
|
}, assignment.append);
|
|
2790
2832
|
continue;
|
|
2791
2833
|
}
|
|
@@ -3127,6 +3169,9 @@ export class Runtime {
|
|
|
3127
3169
|
functionRestoration?.close();
|
|
3128
3170
|
throw error;
|
|
3129
3171
|
}
|
|
3172
|
+
const callerLoopDepth = state.loopDepth;
|
|
3173
|
+
if (mapfileCallbackStates.has(state))
|
|
3174
|
+
state.loopDepth = 0;
|
|
3130
3175
|
try {
|
|
3131
3176
|
return { exitCode: await this.command(body, state, { ...io, ...context, scriptName: body.sourceName ?? io.scriptName ?? "shell" }) };
|
|
3132
3177
|
}
|
|
@@ -3141,6 +3186,7 @@ export class Runtime {
|
|
|
3141
3186
|
savedPositionals.close();
|
|
3142
3187
|
state.positionalSetVersion = positionalSetVersion;
|
|
3143
3188
|
state.functionDepth--;
|
|
3189
|
+
state.loopDepth = callerLoopDepth;
|
|
3144
3190
|
state.depth--;
|
|
3145
3191
|
state.locals.pop();
|
|
3146
3192
|
};
|
|
@@ -4759,6 +4805,135 @@ export class Runtime {
|
|
|
4759
4805
|
allocation.close();
|
|
4760
4806
|
}
|
|
4761
4807
|
}
|
|
4808
|
+
async mapfileBuiltin(context, state) {
|
|
4809
|
+
const allocation = this.budget.values.scope();
|
|
4810
|
+
const work = { remaining: this.budget.limits.maxExpansionBytes * 8, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
|
|
4811
|
+
let pinned;
|
|
4812
|
+
let releaseHolding;
|
|
4813
|
+
try {
|
|
4814
|
+
const options = await mapfileOptions(context, work, allocation);
|
|
4815
|
+
if (state.readonlyVariables?.has(options.name))
|
|
4816
|
+
throw new MapfileUsageError(`${options.name}: readonly variable`, 1);
|
|
4817
|
+
if (controlNames.has(options.name) || state.exported.has(options.name))
|
|
4818
|
+
throw new ArrayFailure("control or exported binding cannot be indexed");
|
|
4819
|
+
const store = requireArrays(state);
|
|
4820
|
+
const holding = store.owner.hold();
|
|
4821
|
+
releaseHolding = () => holding.release();
|
|
4822
|
+
if (!options.preserve || !store.get(options.name))
|
|
4823
|
+
await this.arrayAssignment({ kind: "compound", name: options.name, append: options.preserve, entries: [] }, state, context);
|
|
4824
|
+
const entry = store.bindings.get(options.name);
|
|
4825
|
+
allocation.reserve(128 + Buffer.byteLength(options.name) * 2, 0);
|
|
4826
|
+
pinned = entry.binding.retain();
|
|
4827
|
+
const input = context.stdin instanceof ShellInput ? context.stdin : new ShellInput(context.stdin, this.budget, this.signal);
|
|
4828
|
+
let pendingFlow;
|
|
4829
|
+
await collectMapfile(options, input, {
|
|
4830
|
+
allocation: () => this.budget.values.scope(),
|
|
4831
|
+
loop: () => this.budget.loop(),
|
|
4832
|
+
write: async (index, value) => {
|
|
4833
|
+
if (pinned !== entry.binding) {
|
|
4834
|
+
const previous = pinned;
|
|
4835
|
+
pinned = entry.binding.retain();
|
|
4836
|
+
await previous.release();
|
|
4837
|
+
}
|
|
4838
|
+
const operation = ArrayOwner.create(store.owner.ledger, store.owner);
|
|
4839
|
+
let staged;
|
|
4840
|
+
try {
|
|
4841
|
+
const current = pinned;
|
|
4842
|
+
const attached = store.bindings.get(options.name) === entry;
|
|
4843
|
+
const watch = attached ? await store.watch(options.name, operation, this.signal) : undefined;
|
|
4844
|
+
const supersede = attached ? await stateMonitor(state).prepareTypedPublication(options.name, operation, this.signal) : undefined;
|
|
4845
|
+
if (attached)
|
|
4846
|
+
await this.prepareArrayObservers(state, operation);
|
|
4847
|
+
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
4848
|
+
// Exclude the mapfile pin and the live name's reference from COW.
|
|
4849
|
+
staged = current.references > (attached ? 2 : 1) ? await current.copy(this.signal) : current.retain();
|
|
4850
|
+
const token = await valueToken(staged.owner, value, this.signal);
|
|
4851
|
+
try {
|
|
4852
|
+
this.signal.throwIfAborted();
|
|
4853
|
+
if (watch && !watch.valid())
|
|
4854
|
+
throw new ArrayFailure("stale binding");
|
|
4855
|
+
staged.insert(index, token);
|
|
4856
|
+
}
|
|
4857
|
+
catch (error) {
|
|
4858
|
+
token.release();
|
|
4859
|
+
throw error;
|
|
4860
|
+
}
|
|
4861
|
+
if (attached) {
|
|
4862
|
+
let released;
|
|
4863
|
+
stateMonitor(state).publish(tickets, options.name, () => {
|
|
4864
|
+
supersede();
|
|
4865
|
+
released = store.publish(options.name, staged, tickets);
|
|
4866
|
+
});
|
|
4867
|
+
staged = undefined;
|
|
4868
|
+
await released;
|
|
4869
|
+
}
|
|
4870
|
+
else {
|
|
4871
|
+
const previous = pinned;
|
|
4872
|
+
pinned = staged;
|
|
4873
|
+
entry.binding = staged;
|
|
4874
|
+
staged = undefined;
|
|
4875
|
+
await previous.release();
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
finally {
|
|
4879
|
+
try {
|
|
4880
|
+
await staged?.release();
|
|
4881
|
+
}
|
|
4882
|
+
finally {
|
|
4883
|
+
await operation.close();
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
},
|
|
4887
|
+
callback: async (source, index, value) => {
|
|
4888
|
+
if (pendingFlow)
|
|
4889
|
+
return;
|
|
4890
|
+
const callbackAllocation = this.budget.values.scope();
|
|
4891
|
+
const nested = mapfileCallbackStates.has(state);
|
|
4892
|
+
try {
|
|
4893
|
+
const quoted = shellValueText(await transformParameter(value, "Q", { maximumBytes: this.budget.limits.maxExpansionBytes, byteLocale: byteLocale(state.variables), work, allocation: callbackAllocation }));
|
|
4894
|
+
if (Buffer.byteLength(source) + Buffer.byteLength(quoted) + String(index).length + 2 > this.budget.limits.maxExpansionBytes)
|
|
4895
|
+
this.budget.fail("maxExpansionBytes");
|
|
4896
|
+
mapfileCallbackStates.add(state);
|
|
4897
|
+
await this.evalBuiltin({ ...context, args: [source, String(index), quoted] }, state, context, false);
|
|
4898
|
+
}
|
|
4899
|
+
catch (error) {
|
|
4900
|
+
if (error instanceof Flow && (error.kind === "break" || error.kind === "continue"))
|
|
4901
|
+
pendingFlow = error;
|
|
4902
|
+
else
|
|
4903
|
+
throw error;
|
|
4904
|
+
}
|
|
4905
|
+
finally {
|
|
4906
|
+
if (!nested)
|
|
4907
|
+
mapfileCallbackStates.delete(state);
|
|
4908
|
+
callbackAllocation.close();
|
|
4909
|
+
}
|
|
4910
|
+
},
|
|
4911
|
+
});
|
|
4912
|
+
if (pendingFlow)
|
|
4913
|
+
throw pendingFlow;
|
|
4914
|
+
return 0;
|
|
4915
|
+
}
|
|
4916
|
+
catch (error) {
|
|
4917
|
+
this.signal.throwIfAborted();
|
|
4918
|
+
if (!(error instanceof MapfileUsageError))
|
|
4919
|
+
throw error;
|
|
4920
|
+
await this.diagnostic(context, `${context.command}: ${error.message}`);
|
|
4921
|
+
return error.status;
|
|
4922
|
+
}
|
|
4923
|
+
finally {
|
|
4924
|
+
try {
|
|
4925
|
+
await pinned?.release();
|
|
4926
|
+
}
|
|
4927
|
+
finally {
|
|
4928
|
+
try {
|
|
4929
|
+
releaseHolding?.();
|
|
4930
|
+
}
|
|
4931
|
+
finally {
|
|
4932
|
+
allocation.close();
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4762
4937
|
async builtin(context, state, assignments, diagnose, suppressSpecial = false) {
|
|
4763
4938
|
const { command, args, stdout, stderr } = context;
|
|
4764
4939
|
if (command === ":" || command === "true")
|
|
@@ -4769,6 +4944,8 @@ export class Runtime {
|
|
|
4769
4944
|
return this.shoptBuiltin(context, state);
|
|
4770
4945
|
if (command === "let")
|
|
4771
4946
|
return this.letBuiltin(context, state);
|
|
4947
|
+
if (command === "mapfile" || command === "readarray")
|
|
4948
|
+
return this.mapfileBuiltin(context, state);
|
|
4772
4949
|
if (command === "getopts")
|
|
4773
4950
|
return this.getoptsBuiltin(context, state);
|
|
4774
4951
|
if (command === "pushd" || command === "dirs" || command === "popd")
|
|
@@ -5389,7 +5566,7 @@ export class Runtime {
|
|
|
5389
5566
|
if (part.kind === "variable" && part.transform) {
|
|
5390
5567
|
const selector = getArraySelector(part);
|
|
5391
5568
|
if (selector?.kind === "members" || part.name === "@" || part.name === "*") {
|
|
5392
|
-
const members = selector ? await this.arrayMembers(part.name, state) : this.positionalValues(state);
|
|
5569
|
+
const members = selector ? await this.arrayMembers(part.name, state, io) : this.positionalValues(state);
|
|
5393
5570
|
const ifs = state.variables.IFS ?? " ";
|
|
5394
5571
|
const separator = ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
|
|
5395
5572
|
const fragments = [];
|
|
@@ -5439,6 +5616,8 @@ export class Runtime {
|
|
|
5439
5616
|
const token = index === undefined || selector?.kind === "members" ? undefined : binding?.values.get(index)?.text;
|
|
5440
5617
|
token?.retain();
|
|
5441
5618
|
try {
|
|
5619
|
+
if (token?.rawValue && part.kind === "variable" && !part.length && !part.operator && !part.substring)
|
|
5620
|
+
return shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]);
|
|
5442
5621
|
const value = await this.partValue(part, state, io, hereString);
|
|
5443
5622
|
if (binding)
|
|
5444
5623
|
await textToken(requireArrays(state).owner, shellValueText(value), this.signal);
|
|
@@ -5568,8 +5747,12 @@ export class Runtime {
|
|
|
5568
5747
|
}
|
|
5569
5748
|
if (part.length)
|
|
5570
5749
|
return String(binding?.values.size ?? (state.variables[part.name] === undefined ? 0 : 1));
|
|
5571
|
-
const values = await this.arrayMembers(part.name, state);
|
|
5572
|
-
|
|
5750
|
+
const values = await this.arrayMembers(part.name, state, io);
|
|
5751
|
+
const separator = Array.from(state.variables.IFS ?? " ")[0] ?? "";
|
|
5752
|
+
if (values.every(value => typeof value === "string"))
|
|
5753
|
+
return this.arrayJoin(store.owner, values, separator);
|
|
5754
|
+
io[valueScope]?.reserve(values.length * 32 + 64, 0);
|
|
5755
|
+
return concatShellValues(values.flatMap((value, index) => index ? [separator, value] : [value]), io[valueScope]);
|
|
5573
5756
|
}
|
|
5574
5757
|
let value = part.name === "?" ? String(state.status)
|
|
5575
5758
|
: part.name === "-" ? `${state.errexit ? "e" : ""}${state.nounset ? "u" : ""}${state.braceexpand !== false ? "B" : ""}`
|
|
@@ -6046,7 +6229,7 @@ export class Runtime {
|
|
|
6046
6229
|
}
|
|
6047
6230
|
}
|
|
6048
6231
|
else if (part.kind === "variable" && split && (selector?.kind === "members" && !part.length && (!part.quoted || selector.separator === "@") || part.transform && part.name === "@")) {
|
|
6049
|
-
const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state) : this.positionalValues(state);
|
|
6232
|
+
const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state, io) : this.positionalValues(state);
|
|
6050
6233
|
for (let position = 0; position < members.length; position++) {
|
|
6051
6234
|
if (position > 0)
|
|
6052
6235
|
addField();
|
|
@@ -6221,7 +6404,7 @@ export class Runtime {
|
|
|
6221
6404
|
allocation.hold(value);
|
|
6222
6405
|
return createCommandArguments(values, allocation);
|
|
6223
6406
|
}
|
|
6224
|
-
async arrayMembers(name, state) {
|
|
6407
|
+
async arrayMembers(name, state, io) {
|
|
6225
6408
|
const store = requireArrays(state);
|
|
6226
6409
|
const holding = store.owner.hold();
|
|
6227
6410
|
try {
|
|
@@ -6241,8 +6424,10 @@ export class Runtime {
|
|
|
6241
6424
|
const values = [];
|
|
6242
6425
|
for (const index of indices) {
|
|
6243
6426
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 4 });
|
|
6244
|
-
const
|
|
6245
|
-
|
|
6427
|
+
const token = binding.values.get(index).text;
|
|
6428
|
+
const value = token.rawValue === undefined ? token.value : shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]);
|
|
6429
|
+
if (typeof value === "string")
|
|
6430
|
+
await textToken(store.owner, value, this.signal);
|
|
6246
6431
|
values.push(value);
|
|
6247
6432
|
await store.owner.ledger.checkpoint(this.signal);
|
|
6248
6433
|
}
|