@poe-platform/safe-bash 0.1.527 → 0.1.529
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 +511 -55
- package/dist/safe-bash/core.browser.js.map +4 -4
- package/dist/safe-bash/shell/arrays/bindings.d.ts +15 -2
- package/dist/safe-bash/shell/arrays/bindings.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/bindings.js +118 -5
- package/dist/safe-bash/shell/arrays/bindings.js.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.d.ts +5 -2
- package/dist/safe-bash/shell/arrays/syntax.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.js +19 -6
- package/dist/safe-bash/shell/arrays/syntax.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 +2 -0
- package/dist/safe-bash/shell/parser.d.ts.map +1 -1
- package/dist/safe-bash/shell/parser.js +14 -5
- 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 +255 -42
- package/dist/safe-bash/shell/runtime.js.map +1 -1
- package/package.json +2 -2
|
@@ -5,7 +5,7 @@ import { ACCESS_MODES, FsError, composeMiddleware, createBytePipe, pipeBytes, re
|
|
|
5
5
|
import { concatShellValues, shellValueByteLength, shellValueBytes, shellValueFromBytes, shellValueText } from "../contracts/value.js";
|
|
6
6
|
import { createCommandArguments, getCommandArguments } from "../contracts/command.js";
|
|
7
7
|
import { ValueArena } from "./value-state.js";
|
|
8
|
-
import { compoundEntryWords, HereDocumentSyntaxError, hereDocumentWords, parseShellInputUnit, parseShellUnit } from "./parser.js";
|
|
8
|
+
import { parseArraySubscript, compoundEntryWords, HereDocumentSyntaxError, hereDocumentWords, parseShellInputUnit, parseShellUnit } from "./parser.js";
|
|
9
9
|
import { SourceLineIndex } from "./source-line-index.js";
|
|
10
10
|
import { ShellLimitError, ShellSyntaxError } from "./types.js";
|
|
11
11
|
import { scopeFileSystem } from "@poe-platform/safe-fs/core";
|
|
@@ -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", "declare", "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;
|
|
@@ -1435,7 +1437,7 @@ export class Runtime {
|
|
|
1435
1437
|
}
|
|
1436
1438
|
variable(state, name) {
|
|
1437
1439
|
const binding = arrayStore(state)?.get(name);
|
|
1438
|
-
return binding ? binding.get(0) : state.variables[name];
|
|
1440
|
+
return binding ? binding.get(binding.associative ? binding.keys.get("30")?.index ?? -1 : 0) : state.variables[name];
|
|
1439
1441
|
}
|
|
1440
1442
|
requireParameter(value, name, state, io, line) {
|
|
1441
1443
|
if (state.nounset && value === undefined)
|
|
@@ -1446,7 +1448,7 @@ export class Runtime {
|
|
|
1446
1448
|
this.writeVariable(state, name, value, origin);
|
|
1447
1449
|
return;
|
|
1448
1450
|
}
|
|
1449
|
-
await this.arrayZero(state, name, async () =>
|
|
1451
|
+
await this.arrayZero(state, name, async () => value);
|
|
1450
1452
|
}
|
|
1451
1453
|
async prepareVariable(state, name, saved, scalarLegacy = false) {
|
|
1452
1454
|
const store = requireArrays(state);
|
|
@@ -1591,7 +1593,7 @@ export class Runtime {
|
|
|
1591
1593
|
await operation.ledger.checkpoint(this.signal, 2);
|
|
1592
1594
|
}
|
|
1593
1595
|
if (typeof index === "number")
|
|
1594
|
-
staged.
|
|
1596
|
+
staged.remove(index);
|
|
1595
1597
|
staged.maximum = maximum;
|
|
1596
1598
|
}
|
|
1597
1599
|
this.signal.throwIfAborted();
|
|
@@ -1629,6 +1631,7 @@ export class Runtime {
|
|
|
1629
1631
|
const store = requireArrays(state);
|
|
1630
1632
|
const operation = ArrayOwner.create(store.owner.ledger, store.owner);
|
|
1631
1633
|
const holding = store.owner.hold();
|
|
1634
|
+
const valueAllocation = this.budget.values.scope();
|
|
1632
1635
|
let staged;
|
|
1633
1636
|
try {
|
|
1634
1637
|
const watch = await store.watch(name, operation, this.signal);
|
|
@@ -1649,10 +1652,13 @@ export class Runtime {
|
|
|
1649
1652
|
if (!current)
|
|
1650
1653
|
throw new ArrayFailure("stale binding");
|
|
1651
1654
|
staged = await current.copy(this.signal);
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1655
|
+
const index = staged.associative ? (await staged.keyIndex("0", operation, this.signal, true)) : 0;
|
|
1656
|
+
const previous = current.values.get(index)?.text.rawValue ?? current.get(index) ?? "";
|
|
1657
|
+
const value = !append ? expanded : typeof previous === "string" && typeof expanded === "string"
|
|
1658
|
+
? await this.arrayJoin(operation, [previous, expanded], "") : concatShellValues([previous, expanded], valueAllocation);
|
|
1659
|
+
const token = await valueToken(staged.owner, value, this.signal);
|
|
1654
1660
|
try {
|
|
1655
|
-
staged.insert(
|
|
1661
|
+
staged.insert(index, token);
|
|
1656
1662
|
}
|
|
1657
1663
|
catch (error) {
|
|
1658
1664
|
token.release();
|
|
@@ -1683,6 +1689,7 @@ export class Runtime {
|
|
|
1683
1689
|
await operation.close();
|
|
1684
1690
|
}
|
|
1685
1691
|
finally {
|
|
1692
|
+
valueAllocation.close();
|
|
1686
1693
|
holding.release();
|
|
1687
1694
|
}
|
|
1688
1695
|
}
|
|
@@ -1701,11 +1708,35 @@ export class Runtime {
|
|
|
1701
1708
|
await owner.ledger.checkpoint(this.signal);
|
|
1702
1709
|
return result;
|
|
1703
1710
|
}
|
|
1711
|
+
async arrayIndex(binding, index, state, io, owner, create = false) {
|
|
1712
|
+
if (!binding?.associative) {
|
|
1713
|
+
const selected = index.source === undefined ? index : literalIndex(index.source, 0, this.budget.parsing);
|
|
1714
|
+
const number = numericIndex(selected);
|
|
1715
|
+
if (number === undefined)
|
|
1716
|
+
throw new ArrayFailure("index outside 0..2147483647");
|
|
1717
|
+
return number;
|
|
1718
|
+
}
|
|
1719
|
+
const word = index.word ?? parseArraySubscript(index.source ?? index.decimal, this.budget.parsing, byteLocale(state.variables), state.depth);
|
|
1720
|
+
const fields = await this.valueWord(word, state, io, false);
|
|
1721
|
+
const value = fields.length === 1 ? fields[0] : concatShellValues(fields, io[valueScope]);
|
|
1722
|
+
if (shellValueByteLength(value) === 0) {
|
|
1723
|
+
await this.diagnostic(io, "associative array: bad array subscript");
|
|
1724
|
+
if (create)
|
|
1725
|
+
throw completedExit(1);
|
|
1726
|
+
return undefined;
|
|
1727
|
+
}
|
|
1728
|
+
return binding.keyIndex(value, owner, this.signal, create);
|
|
1729
|
+
}
|
|
1704
1730
|
async arrayAssignment(assignment, state, io) {
|
|
1705
1731
|
const name = assignment.name;
|
|
1706
1732
|
this.signal.throwIfAborted();
|
|
1707
|
-
if (state.readonlyVariables?.has(name))
|
|
1733
|
+
if (state.readonlyVariables?.has(name)) {
|
|
1734
|
+
if (arrayStore(state)?.get(name)?.associative) {
|
|
1735
|
+
await this.diagnostic(io, `${name}: readonly variable`);
|
|
1736
|
+
throw completedExit(1);
|
|
1737
|
+
}
|
|
1708
1738
|
throw new ArrayFailure("readonly binding");
|
|
1739
|
+
}
|
|
1709
1740
|
if (controlNames.has(name))
|
|
1710
1741
|
throw new ArrayFailure("control binding cannot be indexed");
|
|
1711
1742
|
if (state.exported.has(name))
|
|
@@ -1717,11 +1748,13 @@ export class Runtime {
|
|
|
1717
1748
|
try {
|
|
1718
1749
|
const watch = await store.watch(name, operation, this.signal);
|
|
1719
1750
|
const current = store.get(name);
|
|
1751
|
+
if (current?.associative && assignment.kind === "compound" && assignment.entries.length)
|
|
1752
|
+
throw new ArrayFailure("nonempty associative compound assignment is unsupported");
|
|
1720
1753
|
const initialMaximum = current?.maximum ?? (state.variables[name] === undefined ? -1 : 0);
|
|
1721
1754
|
let planned = assignment.append && assignment.kind === "compound" ? initialMaximum + 1 : 0;
|
|
1722
1755
|
if (assignment.kind === "element") {
|
|
1723
1756
|
operation.reserve({ work: assignment.index.decimal.length + 1 }).release();
|
|
1724
|
-
if (numericIndex(assignment.index) === undefined)
|
|
1757
|
+
if (!current?.associative && numericIndex(literalIndex(assignment.index.source ?? assignment.index.decimal, 0, this.budget.parsing)) === undefined)
|
|
1725
1758
|
throw new ArrayFailure("index outside 0..2147483647");
|
|
1726
1759
|
}
|
|
1727
1760
|
else
|
|
@@ -1755,9 +1788,9 @@ export class Runtime {
|
|
|
1755
1788
|
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
1756
1789
|
const prepared = await store.prepareName(name, operation, this.signal);
|
|
1757
1790
|
const preserve = assignment.kind === "element" || assignment.append;
|
|
1758
|
-
staged = preserve && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner);
|
|
1791
|
+
staged = preserve && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner, current?.associative);
|
|
1759
1792
|
if (preserve && !current && state.variables[name] !== undefined) {
|
|
1760
|
-
const token = await
|
|
1793
|
+
const token = await valueToken(staged.owner, stateMonitor(state)?.values.get(name, state.variables[name]) ?? state.variables[name], this.signal);
|
|
1761
1794
|
try {
|
|
1762
1795
|
staged.insert(0, token);
|
|
1763
1796
|
}
|
|
@@ -1771,7 +1804,7 @@ export class Runtime {
|
|
|
1771
1804
|
const insert = async (index, value) => {
|
|
1772
1805
|
if (index > 2147483647)
|
|
1773
1806
|
throw new ArrayFailure("index outside 0..2147483647");
|
|
1774
|
-
const token = await
|
|
1807
|
+
const token = await valueToken(staged.owner, value, this.signal);
|
|
1775
1808
|
try {
|
|
1776
1809
|
staged.insert(index, token);
|
|
1777
1810
|
}
|
|
@@ -1781,12 +1814,14 @@ export class Runtime {
|
|
|
1781
1814
|
}
|
|
1782
1815
|
writes++;
|
|
1783
1816
|
};
|
|
1817
|
+
const join = async (values) => values.every(value => typeof value === "string")
|
|
1818
|
+
? this.arrayJoin(operation, values, "") : concatShellValues(values, io[valueScope]);
|
|
1784
1819
|
if (assignment.kind === "element") {
|
|
1785
|
-
const index =
|
|
1786
|
-
const fields = await this.
|
|
1787
|
-
let value = await
|
|
1820
|
+
const index = (await this.arrayIndex(staged, assignment.index, state, io, operation, true));
|
|
1821
|
+
const fields = await this.valueWord(assignment.value, state, io, false);
|
|
1822
|
+
let value = await join(fields);
|
|
1788
1823
|
if (assignment.append)
|
|
1789
|
-
value = await
|
|
1824
|
+
value = await join([staged.values.get(index)?.text.rawValue ?? staged.get(index) ?? "", value]);
|
|
1790
1825
|
await insert(index, value);
|
|
1791
1826
|
}
|
|
1792
1827
|
else
|
|
@@ -1800,17 +1835,17 @@ export class Runtime {
|
|
|
1800
1835
|
expandedEntry = true;
|
|
1801
1836
|
const values = await this.valueWord(expanded, state, io, true, false, false, false, undefined, false);
|
|
1802
1837
|
for (const value of values) {
|
|
1803
|
-
await insert(cursor,
|
|
1838
|
+
await insert(cursor, value);
|
|
1804
1839
|
cursor++;
|
|
1805
1840
|
}
|
|
1806
1841
|
}
|
|
1807
1842
|
if (expandedEntry)
|
|
1808
1843
|
continue;
|
|
1809
1844
|
}
|
|
1810
|
-
const fields = await this.
|
|
1845
|
+
const fields = await this.valueWord(entry.value, state, io, entry.index === undefined);
|
|
1811
1846
|
if (entry.index) {
|
|
1812
1847
|
const index = numericIndex(entry.index);
|
|
1813
|
-
await insert(index, await
|
|
1848
|
+
await insert(index, await join(fields));
|
|
1814
1849
|
cursor = index + 1;
|
|
1815
1850
|
}
|
|
1816
1851
|
else
|
|
@@ -2818,8 +2853,8 @@ export class Runtime {
|
|
|
2818
2853
|
throw new ArrayFailure("indexed binding cannot be a command prefix");
|
|
2819
2854
|
}
|
|
2820
2855
|
await this.arrayZero(state, assignment.name, async () => {
|
|
2821
|
-
const fields = await this.
|
|
2822
|
-
return this.arrayJoin(requireArrays(state).owner, fields, "");
|
|
2856
|
+
const fields = await this.valueWord(assignment.value, state, io, false);
|
|
2857
|
+
return fields.every(value => typeof value === "string") ? this.arrayJoin(requireArrays(state).owner, fields, "") : concatShellValues(fields, io[valueScope]);
|
|
2823
2858
|
}, assignment.append);
|
|
2824
2859
|
continue;
|
|
2825
2860
|
}
|
|
@@ -3161,6 +3196,9 @@ export class Runtime {
|
|
|
3161
3196
|
functionRestoration?.close();
|
|
3162
3197
|
throw error;
|
|
3163
3198
|
}
|
|
3199
|
+
const callerLoopDepth = state.loopDepth;
|
|
3200
|
+
if (mapfileCallbackStates.has(state))
|
|
3201
|
+
state.loopDepth = 0;
|
|
3164
3202
|
try {
|
|
3165
3203
|
return { exitCode: await this.command(body, state, { ...io, ...context, scriptName: body.sourceName ?? io.scriptName ?? "shell" }) };
|
|
3166
3204
|
}
|
|
@@ -3175,6 +3213,7 @@ export class Runtime {
|
|
|
3175
3213
|
savedPositionals.close();
|
|
3176
3214
|
state.positionalSetVersion = positionalSetVersion;
|
|
3177
3215
|
state.functionDepth--;
|
|
3216
|
+
state.loopDepth = callerLoopDepth;
|
|
3178
3217
|
state.depth--;
|
|
3179
3218
|
state.locals.pop();
|
|
3180
3219
|
};
|
|
@@ -4793,6 +4832,137 @@ export class Runtime {
|
|
|
4793
4832
|
allocation.close();
|
|
4794
4833
|
}
|
|
4795
4834
|
}
|
|
4835
|
+
async mapfileBuiltin(context, state) {
|
|
4836
|
+
const allocation = this.budget.values.scope();
|
|
4837
|
+
const work = { remaining: this.budget.limits.maxExpansionBytes * 8, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
|
|
4838
|
+
let pinned;
|
|
4839
|
+
let releaseHolding;
|
|
4840
|
+
try {
|
|
4841
|
+
const options = await mapfileOptions(context, work, allocation);
|
|
4842
|
+
if (arrayStore(state)?.get(options.name)?.associative)
|
|
4843
|
+
throw new MapfileUsageError(`${options.name}: not an indexed array`, 1);
|
|
4844
|
+
if (state.readonlyVariables?.has(options.name))
|
|
4845
|
+
throw new MapfileUsageError(`${options.name}: readonly variable`, 1);
|
|
4846
|
+
if (controlNames.has(options.name) || state.exported.has(options.name))
|
|
4847
|
+
throw new ArrayFailure("control or exported binding cannot be indexed");
|
|
4848
|
+
const store = requireArrays(state);
|
|
4849
|
+
const holding = store.owner.hold();
|
|
4850
|
+
releaseHolding = () => holding.release();
|
|
4851
|
+
if (!options.preserve || !store.get(options.name))
|
|
4852
|
+
await this.arrayAssignment({ kind: "compound", name: options.name, append: options.preserve, entries: [] }, state, context);
|
|
4853
|
+
const entry = store.bindings.get(options.name);
|
|
4854
|
+
allocation.reserve(128 + Buffer.byteLength(options.name) * 2, 0);
|
|
4855
|
+
pinned = entry.binding.retain();
|
|
4856
|
+
const input = context.stdin instanceof ShellInput ? context.stdin : new ShellInput(context.stdin, this.budget, this.signal);
|
|
4857
|
+
let pendingFlow;
|
|
4858
|
+
await collectMapfile(options, input, {
|
|
4859
|
+
allocation: () => this.budget.values.scope(),
|
|
4860
|
+
loop: () => this.budget.loop(),
|
|
4861
|
+
write: async (index, value) => {
|
|
4862
|
+
if (pinned !== entry.binding) {
|
|
4863
|
+
const previous = pinned;
|
|
4864
|
+
pinned = entry.binding.retain();
|
|
4865
|
+
await previous.release();
|
|
4866
|
+
}
|
|
4867
|
+
const operation = ArrayOwner.create(store.owner.ledger, store.owner);
|
|
4868
|
+
let staged;
|
|
4869
|
+
try {
|
|
4870
|
+
const current = pinned;
|
|
4871
|
+
const attached = store.bindings.get(options.name) === entry;
|
|
4872
|
+
const watch = attached ? await store.watch(options.name, operation, this.signal) : undefined;
|
|
4873
|
+
const supersede = attached ? await stateMonitor(state).prepareTypedPublication(options.name, operation, this.signal) : undefined;
|
|
4874
|
+
if (attached)
|
|
4875
|
+
await this.prepareArrayObservers(state, operation);
|
|
4876
|
+
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
4877
|
+
// Exclude the mapfile pin and the live name's reference from COW.
|
|
4878
|
+
staged = current.references > (attached ? 2 : 1) ? await current.copy(this.signal) : current.retain();
|
|
4879
|
+
const token = await valueToken(staged.owner, value, this.signal);
|
|
4880
|
+
try {
|
|
4881
|
+
this.signal.throwIfAborted();
|
|
4882
|
+
if (watch && !watch.valid())
|
|
4883
|
+
throw new ArrayFailure("stale binding");
|
|
4884
|
+
staged.insert(index, token);
|
|
4885
|
+
}
|
|
4886
|
+
catch (error) {
|
|
4887
|
+
token.release();
|
|
4888
|
+
throw error;
|
|
4889
|
+
}
|
|
4890
|
+
if (attached) {
|
|
4891
|
+
let released;
|
|
4892
|
+
stateMonitor(state).publish(tickets, options.name, () => {
|
|
4893
|
+
supersede();
|
|
4894
|
+
released = store.publish(options.name, staged, tickets);
|
|
4895
|
+
});
|
|
4896
|
+
staged = undefined;
|
|
4897
|
+
await released;
|
|
4898
|
+
}
|
|
4899
|
+
else {
|
|
4900
|
+
const previous = pinned;
|
|
4901
|
+
pinned = staged;
|
|
4902
|
+
entry.binding = staged;
|
|
4903
|
+
staged = undefined;
|
|
4904
|
+
await previous.release();
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
finally {
|
|
4908
|
+
try {
|
|
4909
|
+
await staged?.release();
|
|
4910
|
+
}
|
|
4911
|
+
finally {
|
|
4912
|
+
await operation.close();
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
},
|
|
4916
|
+
callback: async (source, index, value) => {
|
|
4917
|
+
if (pendingFlow)
|
|
4918
|
+
return;
|
|
4919
|
+
const callbackAllocation = this.budget.values.scope();
|
|
4920
|
+
const nested = mapfileCallbackStates.has(state);
|
|
4921
|
+
try {
|
|
4922
|
+
const quoted = shellValueText(await transformParameter(value, "Q", { maximumBytes: this.budget.limits.maxExpansionBytes, byteLocale: byteLocale(state.variables), work, allocation: callbackAllocation }));
|
|
4923
|
+
if (Buffer.byteLength(source) + Buffer.byteLength(quoted) + String(index).length + 2 > this.budget.limits.maxExpansionBytes)
|
|
4924
|
+
this.budget.fail("maxExpansionBytes");
|
|
4925
|
+
mapfileCallbackStates.add(state);
|
|
4926
|
+
await this.evalBuiltin({ ...context, args: [source, String(index), quoted] }, state, context, false);
|
|
4927
|
+
}
|
|
4928
|
+
catch (error) {
|
|
4929
|
+
if (error instanceof Flow && (error.kind === "break" || error.kind === "continue"))
|
|
4930
|
+
pendingFlow = error;
|
|
4931
|
+
else
|
|
4932
|
+
throw error;
|
|
4933
|
+
}
|
|
4934
|
+
finally {
|
|
4935
|
+
if (!nested)
|
|
4936
|
+
mapfileCallbackStates.delete(state);
|
|
4937
|
+
callbackAllocation.close();
|
|
4938
|
+
}
|
|
4939
|
+
},
|
|
4940
|
+
});
|
|
4941
|
+
if (pendingFlow)
|
|
4942
|
+
throw pendingFlow;
|
|
4943
|
+
return 0;
|
|
4944
|
+
}
|
|
4945
|
+
catch (error) {
|
|
4946
|
+
this.signal.throwIfAborted();
|
|
4947
|
+
if (!(error instanceof MapfileUsageError))
|
|
4948
|
+
throw error;
|
|
4949
|
+
await this.diagnostic(context, `${context.command}: ${error.message}`);
|
|
4950
|
+
return error.status;
|
|
4951
|
+
}
|
|
4952
|
+
finally {
|
|
4953
|
+
try {
|
|
4954
|
+
await pinned?.release();
|
|
4955
|
+
}
|
|
4956
|
+
finally {
|
|
4957
|
+
try {
|
|
4958
|
+
releaseHolding?.();
|
|
4959
|
+
}
|
|
4960
|
+
finally {
|
|
4961
|
+
allocation.close();
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4796
4966
|
async builtin(context, state, assignments, diagnose, suppressSpecial = false) {
|
|
4797
4967
|
const { command, args, stdout, stderr } = context;
|
|
4798
4968
|
if (command === ":" || command === "true")
|
|
@@ -4803,6 +4973,8 @@ export class Runtime {
|
|
|
4803
4973
|
return this.shoptBuiltin(context, state);
|
|
4804
4974
|
if (command === "let")
|
|
4805
4975
|
return this.letBuiltin(context, state);
|
|
4976
|
+
if (command === "mapfile" || command === "readarray")
|
|
4977
|
+
return this.mapfileBuiltin(context, state);
|
|
4806
4978
|
if (command === "getopts")
|
|
4807
4979
|
return this.getoptsBuiltin(context, state);
|
|
4808
4980
|
if (command === "pushd" || command === "dirs" || command === "popd")
|
|
@@ -4876,9 +5048,16 @@ export class Runtime {
|
|
|
4876
5048
|
this.replacePositionals(state, this.positionalValues(state).slice(count));
|
|
4877
5049
|
return 0;
|
|
4878
5050
|
}
|
|
4879
|
-
if (command === "export" || command === "local" || command === "readonly") {
|
|
5051
|
+
if (command === "export" || command === "local" || command === "readonly" || command === "declare") {
|
|
5052
|
+
const associativeDeclaration = command === "declare";
|
|
4880
5053
|
const declarationArgs = [...args];
|
|
4881
|
-
let indexedLocal =
|
|
5054
|
+
let indexedLocal = associativeDeclaration;
|
|
5055
|
+
if (associativeDeclaration) {
|
|
5056
|
+
if (declarationArgs.shift() !== "-A" || !declarationArgs.length) {
|
|
5057
|
+
await this.diagnostic(context, "declare: only -A NAME is supported");
|
|
5058
|
+
return 2;
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
4882
5061
|
if (command === "local") {
|
|
4883
5062
|
const options = localDeclarationOptions(declarationArgs, this.signal);
|
|
4884
5063
|
if (options.error !== undefined) {
|
|
@@ -4941,13 +5120,13 @@ export class Runtime {
|
|
|
4941
5120
|
status = 1;
|
|
4942
5121
|
continue;
|
|
4943
5122
|
}
|
|
4944
|
-
if (command === "local" && indexedLocal) {
|
|
5123
|
+
if ((command === "local" || associativeDeclaration) && indexedLocal) {
|
|
4945
5124
|
if (controlNames.has(name))
|
|
4946
5125
|
throw new ArrayFailure("control binding cannot be indexed");
|
|
4947
5126
|
if (state.exported.has(name))
|
|
4948
5127
|
throw new ArrayFailure("exported binding cannot be indexed");
|
|
4949
|
-
const existingLocal = locals
|
|
4950
|
-
const saved = existingLocal ? undefined : assignments.get(name) ?? saveVariable(state, name);
|
|
5128
|
+
const existingLocal = locals?.get(name);
|
|
5129
|
+
const saved = !locals || existingLocal ? undefined : assignments.get(name) ?? saveVariable(state, name);
|
|
4951
5130
|
let operation;
|
|
4952
5131
|
let holding;
|
|
4953
5132
|
let shadow;
|
|
@@ -4972,12 +5151,15 @@ export class Runtime {
|
|
|
4972
5151
|
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
4973
5152
|
const prepared = await store.prepareName(name, operation, this.signal);
|
|
4974
5153
|
const current = store.get(name);
|
|
4975
|
-
|
|
5154
|
+
if (!saved && current && current.associative !== associativeDeclaration)
|
|
5155
|
+
throw new ArrayFailure("cannot convert array kind");
|
|
5156
|
+
shadow = !saved && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner, associativeDeclaration);
|
|
4976
5157
|
const value = match[2] ?? (!saved && !current && Object.hasOwn(state.variables, name) ? state.variables[name] : undefined);
|
|
4977
5158
|
if (value !== undefined) {
|
|
4978
|
-
const
|
|
5159
|
+
const retained = associativeDeclaration && match[2] === undefined ? stateMonitor(state).values.get(name, value) : value;
|
|
5160
|
+
const token = await valueToken(shadow.owner, retained, this.signal);
|
|
4979
5161
|
try {
|
|
4980
|
-
shadow.insert(0, token);
|
|
5162
|
+
shadow.insert(associativeDeclaration ? (await shadow.keyIndex("0", operation, this.signal, true)) : 0, token);
|
|
4981
5163
|
}
|
|
4982
5164
|
catch (error) {
|
|
4983
5165
|
token.release();
|
|
@@ -5152,7 +5334,8 @@ export class Runtime {
|
|
|
5152
5334
|
}
|
|
5153
5335
|
if (command === "unset") {
|
|
5154
5336
|
let status = 0;
|
|
5155
|
-
for (
|
|
5337
|
+
for (let argument = 0; argument < args.length; argument++) {
|
|
5338
|
+
const name = args[argument];
|
|
5156
5339
|
const selected = /^([a-zA-Z_][a-zA-Z_0-9]*)\[(.*)\]$/su.exec(name);
|
|
5157
5340
|
if (selected) {
|
|
5158
5341
|
const base = selected[1];
|
|
@@ -5162,7 +5345,16 @@ export class Runtime {
|
|
|
5162
5345
|
status = 1;
|
|
5163
5346
|
continue;
|
|
5164
5347
|
}
|
|
5165
|
-
|
|
5348
|
+
const binding = arrayStore(state)?.get(base);
|
|
5349
|
+
if (binding?.associative) {
|
|
5350
|
+
const original = getCommandArguments(context).values[argument];
|
|
5351
|
+
const index = typeof original === "string"
|
|
5352
|
+
? await this.arrayIndex(binding, { decimal: selector, source: selector }, state, context, binding.owner)
|
|
5353
|
+
: await binding.keyIndex(shellValueFromBytes(shellValueBytes(original, context[valueScope]).subarray(base.length + 1, shellValueByteLength(original) - 1), context[valueScope]), binding.owner, this.signal);
|
|
5354
|
+
if (index !== undefined)
|
|
5355
|
+
await this.unsetIndexed(state, base, index);
|
|
5356
|
+
}
|
|
5357
|
+
else if (selector === "@" || selector === "*")
|
|
5166
5358
|
await this.unsetIndexed(state, base, "members");
|
|
5167
5359
|
else {
|
|
5168
5360
|
let index;
|
|
@@ -5423,7 +5615,7 @@ export class Runtime {
|
|
|
5423
5615
|
if (part.kind === "variable" && part.transform) {
|
|
5424
5616
|
const selector = getArraySelector(part);
|
|
5425
5617
|
if (selector?.kind === "members" || part.name === "@" || part.name === "*") {
|
|
5426
|
-
const members = selector ? await this.arrayMembers(part.name, state) : this.positionalValues(state);
|
|
5618
|
+
const members = selector ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
|
|
5427
5619
|
const ifs = state.variables.IFS ?? " ";
|
|
5428
5620
|
const separator = ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
|
|
5429
5621
|
const fragments = [];
|
|
@@ -5469,10 +5661,25 @@ export class Runtime {
|
|
|
5469
5661
|
const binding = part.kind === "variable" ? arrayStore(state)?.get(part.name) : undefined;
|
|
5470
5662
|
const holding = binding ? requireArrays(state).owner.hold() : undefined;
|
|
5471
5663
|
const selector = getArraySelector(part);
|
|
5472
|
-
|
|
5664
|
+
if (selector?.kind === "element" && binding?.associative) {
|
|
5665
|
+
try {
|
|
5666
|
+
const index = await this.arrayIndex(binding, selector.index, state, io, binding.owner);
|
|
5667
|
+
const token = index === undefined ? undefined : binding.values.get(index)?.text;
|
|
5668
|
+
this.requireParameter(token?.value, part.kind === "variable" ? part.name : "", state, io);
|
|
5669
|
+
if (part.kind === "variable" && part.length)
|
|
5670
|
+
return this.parameterLength(token?.value ?? "");
|
|
5671
|
+
return token?.rawValue ? shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]) : token?.value ?? "";
|
|
5672
|
+
}
|
|
5673
|
+
finally {
|
|
5674
|
+
holding?.release();
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
const index = selector?.kind === "element" ? numericIndex(selector.index) : binding?.associative ? binding.keys.get("30")?.index : 0;
|
|
5473
5678
|
const token = index === undefined || selector?.kind === "members" ? undefined : binding?.values.get(index)?.text;
|
|
5474
5679
|
token?.retain();
|
|
5475
5680
|
try {
|
|
5681
|
+
if (token?.rawValue && part.kind === "variable" && !part.length && !part.operator && !part.substring)
|
|
5682
|
+
return shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]);
|
|
5476
5683
|
const value = await this.partValue(part, state, io, hereString);
|
|
5477
5684
|
if (binding)
|
|
5478
5685
|
await textToken(requireArrays(state).owner, shellValueText(value), this.signal);
|
|
@@ -5602,8 +5809,12 @@ export class Runtime {
|
|
|
5602
5809
|
}
|
|
5603
5810
|
if (part.length)
|
|
5604
5811
|
return String(binding?.values.size ?? (state.variables[part.name] === undefined ? 0 : 1));
|
|
5605
|
-
const values = await this.arrayMembers(part.name, state);
|
|
5606
|
-
|
|
5812
|
+
const values = await this.arrayMembers(part.name, state, io, part.keys);
|
|
5813
|
+
const separator = Array.from(state.variables.IFS ?? " ")[0] ?? "";
|
|
5814
|
+
if (values.every(value => typeof value === "string"))
|
|
5815
|
+
return this.arrayJoin(store.owner, values, separator);
|
|
5816
|
+
io[valueScope]?.reserve(values.length * 32 + 64, 0);
|
|
5817
|
+
return concatShellValues(values.flatMap((value, index) => index ? [separator, value] : [value]), io[valueScope]);
|
|
5607
5818
|
}
|
|
5608
5819
|
let value = part.name === "?" ? String(state.status)
|
|
5609
5820
|
: part.name === "-" ? `${state.errexit ? "e" : ""}${state.nounset ? "u" : ""}${state.braceexpand !== false ? "B" : ""}`
|
|
@@ -6080,7 +6291,7 @@ export class Runtime {
|
|
|
6080
6291
|
}
|
|
6081
6292
|
}
|
|
6082
6293
|
else if (part.kind === "variable" && split && (selector?.kind === "members" && !part.length && (!part.quoted || selector.separator === "@") || part.transform && part.name === "@")) {
|
|
6083
|
-
const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state) : this.positionalValues(state);
|
|
6294
|
+
const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
|
|
6084
6295
|
for (let position = 0; position < members.length; position++) {
|
|
6085
6296
|
if (position > 0)
|
|
6086
6297
|
addField();
|
|
@@ -6255,7 +6466,7 @@ export class Runtime {
|
|
|
6255
6466
|
allocation.hold(value);
|
|
6256
6467
|
return createCommandArguments(values, allocation);
|
|
6257
6468
|
}
|
|
6258
|
-
async arrayMembers(name, state) {
|
|
6469
|
+
async arrayMembers(name, state, io, keys = false) {
|
|
6259
6470
|
const store = requireArrays(state);
|
|
6260
6471
|
const holding = store.owner.hold();
|
|
6261
6472
|
try {
|
|
@@ -6267,7 +6478,7 @@ export class Runtime {
|
|
|
6267
6478
|
return [];
|
|
6268
6479
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
6269
6480
|
await textToken(store.owner, value, this.signal);
|
|
6270
|
-
return [value];
|
|
6481
|
+
return [keys ? "0" : value];
|
|
6271
6482
|
}
|
|
6272
6483
|
binding.retain();
|
|
6273
6484
|
try {
|
|
@@ -6275,8 +6486,10 @@ export class Runtime {
|
|
|
6275
6486
|
const values = [];
|
|
6276
6487
|
for (const index of indices) {
|
|
6277
6488
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 4 });
|
|
6278
|
-
const
|
|
6279
|
-
|
|
6489
|
+
const token = keys ? binding.associative ? binding.keys.get(binding.keyByIndex.get(index)).text : await textToken(store.owner, String(index), this.signal) : binding.values.get(index).text;
|
|
6490
|
+
const value = token.rawValue === undefined ? token.value : shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]);
|
|
6491
|
+
if (typeof value === "string")
|
|
6492
|
+
await textToken(store.owner, value, this.signal);
|
|
6280
6493
|
values.push(value);
|
|
6281
6494
|
await store.owner.ledger.checkpoint(this.signal);
|
|
6282
6495
|
}
|