poe-code 15.0.4 → 15.0.5
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/metafile.json +1 -1
- package/package.json +8 -2
- package/packages/safe-bash/dist/chunks/chunk-2GV4WO7O.js +136 -0
- package/packages/safe-bash/dist/chunks/chunk-2GV4WO7O.js.map +7 -0
- package/packages/safe-bash/dist/chunks/chunk-54Z5EUCW.js +340 -0
- package/packages/safe-bash/dist/chunks/chunk-54Z5EUCW.js.map +7 -0
- package/packages/safe-bash/dist/chunks/{chunk-NYHAKXQ7.js → chunk-6MHYEBAJ.js} +16 -140
- package/packages/safe-bash/dist/chunks/chunk-6MHYEBAJ.js.map +7 -0
- package/packages/safe-bash/dist/chunks/{chunk-5MSOBYRG.js → chunk-IKATLB4I.js} +5 -3
- package/packages/safe-bash/dist/chunks/{chunk-5MSOBYRG.js.map → chunk-IKATLB4I.js.map} +1 -1
- package/packages/safe-bash/dist/chunks/{chunk-5OCQTFXT.js → chunk-LUJYX5H5.js} +6 -332
- package/packages/safe-bash/dist/chunks/{chunk-5OCQTFXT.js.map → chunk-LUJYX5H5.js.map} +4 -4
- package/packages/safe-bash/dist/chunks/chunk-UGMUVHIY.js +1603 -0
- package/packages/safe-bash/dist/chunks/chunk-UGMUVHIY.js.map +7 -0
- package/packages/safe-bash/dist/commands/network/args.d.ts +3 -0
- package/packages/safe-bash/dist/commands/network/args.d.ts.map +1 -1
- package/packages/safe-bash/dist/commands/network/args.js +1 -1
- package/packages/safe-bash/dist/commands/network/args.js.map +1 -1
- package/packages/safe-bash/dist/commands/network/body.js +2 -2
- package/packages/safe-bash/dist/commands/network/body.js.map +1 -1
- package/packages/safe-bash/dist/commands/network/curl.d.ts +12 -2
- package/packages/safe-bash/dist/commands/network/curl.d.ts.map +1 -1
- package/packages/safe-bash/dist/commands/network/curl.js +46 -27
- package/packages/safe-bash/dist/commands/network/curl.js.map +1 -1
- package/packages/safe-bash/dist/commands/network/index.browser.js +29 -0
- package/packages/safe-bash/dist/commands/network/index.browser.js.map +7 -0
- package/packages/safe-bash/dist/commands/network/index.d.ts +1 -10
- package/packages/safe-bash/dist/commands/network/index.d.ts.map +1 -1
- package/packages/safe-bash/dist/commands/network/index.js +1 -22
- package/packages/safe-bash/dist/commands/network/index.js.map +1 -1
- package/packages/safe-bash/dist/commands/network/platform.d.ts +4 -0
- package/packages/safe-bash/dist/commands/network/platform.d.ts.map +1 -0
- package/packages/safe-bash/dist/commands/network/platform.js +4 -0
- package/packages/safe-bash/dist/commands/network/platform.js.map +1 -0
- package/packages/safe-bash/dist/commands/network/public.d.ts +12 -0
- package/packages/safe-bash/dist/commands/network/public.d.ts.map +1 -0
- package/packages/safe-bash/dist/commands/network/public.js +27 -0
- package/packages/safe-bash/dist/commands/network/public.js.map +1 -0
- package/packages/safe-bash/dist/commands/network/shared.js +1 -1
- package/packages/safe-bash/dist/commands/network/shared.js.map +1 -1
- package/packages/safe-bash/dist/commands/network/wget.d.ts +4 -0
- package/packages/safe-bash/dist/commands/network/wget.d.ts.map +1 -0
- package/packages/safe-bash/dist/commands/network/wget.js +155 -0
- package/packages/safe-bash/dist/commands/network/wget.js.map +1 -0
- package/packages/safe-bash/dist/commands/xml/index.browser.js +3 -2
- package/packages/safe-bash/dist/commands/yq/index.browser.js +4 -2
- package/packages/safe-bash/dist/core.browser.js +1054 -459
- package/packages/safe-bash/dist/core.browser.js.map +4 -4
- package/packages/safe-bash/dist/core.d.ts +1 -5
- package/packages/safe-bash/dist/core.d.ts.map +1 -1
- package/packages/safe-bash/dist/core.js +1 -3
- package/packages/safe-bash/dist/core.js.map +1 -1
- package/packages/safe-bash/dist/shell/arrays/bindings.d.ts +15 -2
- package/packages/safe-bash/dist/shell/arrays/bindings.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/arrays/bindings.js +118 -5
- package/packages/safe-bash/dist/shell/arrays/bindings.js.map +1 -1
- package/packages/safe-bash/dist/shell/arrays/syntax.d.ts +5 -2
- package/packages/safe-bash/dist/shell/arrays/syntax.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/arrays/syntax.js +19 -6
- package/packages/safe-bash/dist/shell/arrays/syntax.js.map +1 -1
- package/packages/safe-bash/dist/shell/display.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/display.js +5 -1
- package/packages/safe-bash/dist/shell/display.js.map +1 -1
- package/packages/safe-bash/dist/shell/input.d.ts +10 -0
- package/packages/safe-bash/dist/shell/input.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/input.js +110 -0
- package/packages/safe-bash/dist/shell/input.js.map +1 -1
- package/packages/safe-bash/dist/shell/mapfile.d.ts +29 -0
- package/packages/safe-bash/dist/shell/mapfile.d.ts.map +1 -0
- package/packages/safe-bash/dist/shell/mapfile.js +134 -0
- package/packages/safe-bash/dist/shell/mapfile.js.map +1 -0
- package/packages/safe-bash/dist/shell/parameter-transforms.d.ts +12 -0
- package/packages/safe-bash/dist/shell/parameter-transforms.d.ts.map +1 -0
- package/packages/safe-bash/dist/shell/parameter-transforms.js +166 -0
- package/packages/safe-bash/dist/shell/parameter-transforms.js.map +1 -0
- package/packages/safe-bash/dist/shell/parser.d.ts +12 -0
- package/packages/safe-bash/dist/shell/parser.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/parser.js +75 -14
- package/packages/safe-bash/dist/shell/parser.js.map +1 -1
- package/packages/safe-bash/dist/shell/runtime.d.ts +7 -3
- package/packages/safe-bash/dist/shell/runtime.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/runtime.js +440 -63
- package/packages/safe-bash/dist/shell/runtime.js.map +1 -1
- package/packages/safe-bash/dist/shell/select-menu.d.ts +6 -0
- package/packages/safe-bash/dist/shell/select-menu.d.ts.map +1 -0
- package/packages/safe-bash/dist/shell/select-menu.js +103 -0
- package/packages/safe-bash/dist/shell/select-menu.js.map +1 -0
- package/packages/safe-bash/dist/chunks/chunk-NYHAKXQ7.js.map +0 -7
|
@@ -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-code/safe-fs/core";
|
|
@@ -16,6 +16,7 @@ import { BraceExpansionFailure, expandBraces } from "./brace-expansion.js";
|
|
|
16
16
|
import { evaluatePositionalArithmetic } from "./arithmetic-parameters.js";
|
|
17
17
|
import { compilePattern, compilePatternBoundaries, matchesPattern } from "./pattern.js";
|
|
18
18
|
import { nextCodePointOffset, previousCodePointOffset, scanString, stringCheckpoint } from "./string-operations.js";
|
|
19
|
+
import { selectMenu } from "./select-menu.js";
|
|
19
20
|
import { byteLocale } from "./locale.js";
|
|
20
21
|
import { functionDisplay } from "./display.js";
|
|
21
22
|
import { ConditionalUnsupported, evaluateConditional } from "./conditional.js";
|
|
@@ -29,7 +30,8 @@ import { cloneGetoptsState, createGetoptsInput, createGetoptsState, GetoptsError
|
|
|
29
30
|
import { activateChildCancellation, prepareChildCancellation, selectRuntimeCancellationOutcome, subscribeCancellation, } from "./cancellation.js";
|
|
30
31
|
import { getArrayAssignment, getArraySelector, copyArraySelector, numericIndex, literalIndex, isQuoteMarker, prefixNameQuoteGroups } from "./arrays/syntax.js";
|
|
31
32
|
import { ArrayFailure, ArrayOwner, exactSum } from "./arrays/ledger.js";
|
|
32
|
-
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";
|
|
33
35
|
import { arrayStore, guestArrays, requireArrays, snapshotState, stateMonitor, trackState } from "./arrays/state.js";
|
|
34
36
|
import { publishPipelineStatus } from "./pipestatus.js";
|
|
35
37
|
import { EreProfileLimitError, EreSyntaxError, EreUnsupportedError } from "../commands/regex-execution/ere/errors.js";
|
|
@@ -37,6 +39,7 @@ import { EreLedger } from "../commands/regex-execution/ere/limits.js";
|
|
|
37
39
|
import { compileEre } from "../commands/regex-execution/ere/syntax.js";
|
|
38
40
|
import { matchEre } from "../commands/regex-execution/ere/matcher.js";
|
|
39
41
|
import { PathLookup, pathTargets } from "./path-lookup.js";
|
|
42
|
+
import { transformParameter } from "./parameter-transforms.js";
|
|
40
43
|
export const defaultLimits = {
|
|
41
44
|
maxParseUnits: defaultMaxParseUnits,
|
|
42
45
|
maxInputBytes: 32 * 1024 * 1024,
|
|
@@ -56,7 +59,7 @@ export const defaultLimits = {
|
|
|
56
59
|
pipeHighWaterMark: 64 * 1024,
|
|
57
60
|
};
|
|
58
61
|
const shellBuiltinNames = new Set([
|
|
59
|
-
":", "true", "false", "pwd", "cd", "set", "shift", "export", "local", "unset", "read",
|
|
62
|
+
":", "true", "false", "pwd", "cd", "set", "shift", "export", "local", "unset", "read", "declare", "mapfile", "readarray",
|
|
60
63
|
"exit", "return", "break", "continue", "command", "builtin", "type", "readonly", "echo", "printf", "test", "[", ".", "source", "eval", "getopts", "let", "pushd", "dirs", "popd", "shopt",
|
|
61
64
|
]);
|
|
62
65
|
const implementedBuiltins = new Set([...shellBuiltinNames].filter(name => !["echo", "printf", "test", "["].includes(name)));
|
|
@@ -1079,6 +1082,7 @@ class InvocationCancellationOwner {
|
|
|
1079
1082
|
this.#failures.push(...result.failures);
|
|
1080
1083
|
}
|
|
1081
1084
|
}
|
|
1085
|
+
const mapfileCallbackStates = new WeakSet();
|
|
1082
1086
|
export class Runtime {
|
|
1083
1087
|
fs;
|
|
1084
1088
|
commands;
|
|
@@ -1407,6 +1411,16 @@ export class Runtime {
|
|
|
1407
1411
|
if (error instanceof NounsetFailure || error instanceof Flow || error instanceof ShellLimitError || error instanceof ShellSyntaxError)
|
|
1408
1412
|
throw error;
|
|
1409
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
|
+
}
|
|
1410
1424
|
arithmeticVariables(state, line) {
|
|
1411
1425
|
return new Proxy(state.variables, {
|
|
1412
1426
|
get: (target, key) => {
|
|
@@ -1423,7 +1437,7 @@ export class Runtime {
|
|
|
1423
1437
|
}
|
|
1424
1438
|
variable(state, name) {
|
|
1425
1439
|
const binding = arrayStore(state)?.get(name);
|
|
1426
|
-
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];
|
|
1427
1441
|
}
|
|
1428
1442
|
requireParameter(value, name, state, io, line) {
|
|
1429
1443
|
if (state.nounset && value === undefined)
|
|
@@ -1434,7 +1448,7 @@ export class Runtime {
|
|
|
1434
1448
|
this.writeVariable(state, name, value, origin);
|
|
1435
1449
|
return;
|
|
1436
1450
|
}
|
|
1437
|
-
await this.arrayZero(state, name, async () =>
|
|
1451
|
+
await this.arrayZero(state, name, async () => value);
|
|
1438
1452
|
}
|
|
1439
1453
|
async prepareVariable(state, name, saved, scalarLegacy = false) {
|
|
1440
1454
|
const store = requireArrays(state);
|
|
@@ -1579,7 +1593,7 @@ export class Runtime {
|
|
|
1579
1593
|
await operation.ledger.checkpoint(this.signal, 2);
|
|
1580
1594
|
}
|
|
1581
1595
|
if (typeof index === "number")
|
|
1582
|
-
staged.
|
|
1596
|
+
staged.remove(index);
|
|
1583
1597
|
staged.maximum = maximum;
|
|
1584
1598
|
}
|
|
1585
1599
|
this.signal.throwIfAborted();
|
|
@@ -1617,6 +1631,7 @@ export class Runtime {
|
|
|
1617
1631
|
const store = requireArrays(state);
|
|
1618
1632
|
const operation = ArrayOwner.create(store.owner.ledger, store.owner);
|
|
1619
1633
|
const holding = store.owner.hold();
|
|
1634
|
+
const valueAllocation = this.budget.values.scope();
|
|
1620
1635
|
let staged;
|
|
1621
1636
|
try {
|
|
1622
1637
|
const watch = await store.watch(name, operation, this.signal);
|
|
@@ -1637,10 +1652,13 @@ export class Runtime {
|
|
|
1637
1652
|
if (!current)
|
|
1638
1653
|
throw new ArrayFailure("stale binding");
|
|
1639
1654
|
staged = await current.copy(this.signal);
|
|
1640
|
-
const
|
|
1641
|
-
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);
|
|
1642
1660
|
try {
|
|
1643
|
-
staged.insert(
|
|
1661
|
+
staged.insert(index, token);
|
|
1644
1662
|
}
|
|
1645
1663
|
catch (error) {
|
|
1646
1664
|
token.release();
|
|
@@ -1671,6 +1689,7 @@ export class Runtime {
|
|
|
1671
1689
|
await operation.close();
|
|
1672
1690
|
}
|
|
1673
1691
|
finally {
|
|
1692
|
+
valueAllocation.close();
|
|
1674
1693
|
holding.release();
|
|
1675
1694
|
}
|
|
1676
1695
|
}
|
|
@@ -1689,11 +1708,35 @@ export class Runtime {
|
|
|
1689
1708
|
await owner.ledger.checkpoint(this.signal);
|
|
1690
1709
|
return result;
|
|
1691
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
|
+
}
|
|
1692
1730
|
async arrayAssignment(assignment, state, io) {
|
|
1693
1731
|
const name = assignment.name;
|
|
1694
1732
|
this.signal.throwIfAborted();
|
|
1695
|
-
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
|
+
}
|
|
1696
1738
|
throw new ArrayFailure("readonly binding");
|
|
1739
|
+
}
|
|
1697
1740
|
if (controlNames.has(name))
|
|
1698
1741
|
throw new ArrayFailure("control binding cannot be indexed");
|
|
1699
1742
|
if (state.exported.has(name))
|
|
@@ -1705,11 +1748,13 @@ export class Runtime {
|
|
|
1705
1748
|
try {
|
|
1706
1749
|
const watch = await store.watch(name, operation, this.signal);
|
|
1707
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");
|
|
1708
1753
|
const initialMaximum = current?.maximum ?? (state.variables[name] === undefined ? -1 : 0);
|
|
1709
1754
|
let planned = assignment.append && assignment.kind === "compound" ? initialMaximum + 1 : 0;
|
|
1710
1755
|
if (assignment.kind === "element") {
|
|
1711
1756
|
operation.reserve({ work: assignment.index.decimal.length + 1 }).release();
|
|
1712
|
-
if (numericIndex(assignment.index) === undefined)
|
|
1757
|
+
if (!current?.associative && numericIndex(literalIndex(assignment.index.source ?? assignment.index.decimal, 0, this.budget.parsing)) === undefined)
|
|
1713
1758
|
throw new ArrayFailure("index outside 0..2147483647");
|
|
1714
1759
|
}
|
|
1715
1760
|
else
|
|
@@ -1743,9 +1788,9 @@ export class Runtime {
|
|
|
1743
1788
|
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
1744
1789
|
const prepared = await store.prepareName(name, operation, this.signal);
|
|
1745
1790
|
const preserve = assignment.kind === "element" || assignment.append;
|
|
1746
|
-
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);
|
|
1747
1792
|
if (preserve && !current && state.variables[name] !== undefined) {
|
|
1748
|
-
const token = await
|
|
1793
|
+
const token = await valueToken(staged.owner, stateMonitor(state)?.values.get(name, state.variables[name]) ?? state.variables[name], this.signal);
|
|
1749
1794
|
try {
|
|
1750
1795
|
staged.insert(0, token);
|
|
1751
1796
|
}
|
|
@@ -1759,7 +1804,7 @@ export class Runtime {
|
|
|
1759
1804
|
const insert = async (index, value) => {
|
|
1760
1805
|
if (index > 2147483647)
|
|
1761
1806
|
throw new ArrayFailure("index outside 0..2147483647");
|
|
1762
|
-
const token = await
|
|
1807
|
+
const token = await valueToken(staged.owner, value, this.signal);
|
|
1763
1808
|
try {
|
|
1764
1809
|
staged.insert(index, token);
|
|
1765
1810
|
}
|
|
@@ -1769,12 +1814,14 @@ export class Runtime {
|
|
|
1769
1814
|
}
|
|
1770
1815
|
writes++;
|
|
1771
1816
|
};
|
|
1817
|
+
const join = async (values) => values.every(value => typeof value === "string")
|
|
1818
|
+
? this.arrayJoin(operation, values, "") : concatShellValues(values, io[valueScope]);
|
|
1772
1819
|
if (assignment.kind === "element") {
|
|
1773
|
-
const index =
|
|
1774
|
-
const fields = await this.
|
|
1775
|
-
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);
|
|
1776
1823
|
if (assignment.append)
|
|
1777
|
-
value = await
|
|
1824
|
+
value = await join([staged.values.get(index)?.text.rawValue ?? staged.get(index) ?? "", value]);
|
|
1778
1825
|
await insert(index, value);
|
|
1779
1826
|
}
|
|
1780
1827
|
else
|
|
@@ -1788,17 +1835,17 @@ export class Runtime {
|
|
|
1788
1835
|
expandedEntry = true;
|
|
1789
1836
|
const values = await this.valueWord(expanded, state, io, true, false, false, false, undefined, false);
|
|
1790
1837
|
for (const value of values) {
|
|
1791
|
-
await insert(cursor,
|
|
1838
|
+
await insert(cursor, value);
|
|
1792
1839
|
cursor++;
|
|
1793
1840
|
}
|
|
1794
1841
|
}
|
|
1795
1842
|
if (expandedEntry)
|
|
1796
1843
|
continue;
|
|
1797
1844
|
}
|
|
1798
|
-
const fields = await this.
|
|
1845
|
+
const fields = await this.valueWord(entry.value, state, io, entry.index === undefined);
|
|
1799
1846
|
if (entry.index) {
|
|
1800
1847
|
const index = numericIndex(entry.index);
|
|
1801
|
-
await insert(index, await
|
|
1848
|
+
await insert(index, await join(fields));
|
|
1802
1849
|
cursor = index + 1;
|
|
1803
1850
|
}
|
|
1804
1851
|
else
|
|
@@ -2188,14 +2235,7 @@ export class Runtime {
|
|
|
2188
2235
|
}
|
|
2189
2236
|
if (command.kind === "arithmetic") {
|
|
2190
2237
|
try {
|
|
2191
|
-
return Number(
|
|
2192
|
-
parseBudget: this.budget.parsing,
|
|
2193
|
-
positional: state.positional, arg0: state.arg0 ?? "virtual-bash", owner: arrayStore(state)?.owner,
|
|
2194
|
-
maximumBytes: this.budget.limits.maxExpansionBytes,
|
|
2195
|
-
checkpoint: () => this.signal.throwIfAborted(),
|
|
2196
|
-
requireParameter: (name, value) => this.requireParameter(value, name, state, io),
|
|
2197
|
-
limit: () => this.budget.fail("maxExpansionBytes"),
|
|
2198
|
-
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine), this.budget.parsing)) === 0n);
|
|
2238
|
+
return Number(this.arithmeticValue(command.expression, state, io) === 0n);
|
|
2199
2239
|
}
|
|
2200
2240
|
catch (error) {
|
|
2201
2241
|
this.rethrowArithmeticControl(error);
|
|
@@ -2265,6 +2305,101 @@ export class Runtime {
|
|
|
2265
2305
|
break;
|
|
2266
2306
|
}
|
|
2267
2307
|
}
|
|
2308
|
+
else if (command.kind === "arithmetic-for") {
|
|
2309
|
+
const evaluate = async (program) => {
|
|
2310
|
+
if (!program)
|
|
2311
|
+
return 1n;
|
|
2312
|
+
try {
|
|
2313
|
+
return this.arithmeticValue(program, state, io);
|
|
2314
|
+
}
|
|
2315
|
+
catch (error) {
|
|
2316
|
+
this.rethrowArithmeticControl(error);
|
|
2317
|
+
await this.diagnostic(io, `((: ${message(error, this.budget.onInternalError)}`);
|
|
2318
|
+
return undefined;
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
if (await evaluate(command.expressions[0]) === undefined)
|
|
2322
|
+
return 1;
|
|
2323
|
+
while (true) {
|
|
2324
|
+
this.budget.loop();
|
|
2325
|
+
await yieldTurn(this.signal);
|
|
2326
|
+
const condition = await evaluate(command.expressions[1]);
|
|
2327
|
+
if (condition === undefined)
|
|
2328
|
+
return 1;
|
|
2329
|
+
if (condition === 0n)
|
|
2330
|
+
break;
|
|
2331
|
+
const result = await this.loopBody(command.body, state, io);
|
|
2332
|
+
status = result.status;
|
|
2333
|
+
if (result.stop)
|
|
2334
|
+
break;
|
|
2335
|
+
if (await evaluate(command.expressions[2]) === undefined)
|
|
2336
|
+
return 1;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
else if (command.kind === "select") {
|
|
2340
|
+
const values = command.words ? await this.valueWords(command.words, state, io) : this.positionalValues(state);
|
|
2341
|
+
if (!values.length)
|
|
2342
|
+
return 0;
|
|
2343
|
+
const input = io.stdin instanceof ShellInput ? io.stdin : new ShellInput(io.stdin, this.budget, this.signal);
|
|
2344
|
+
const work = { remaining: this.budget.limits.maxExpansionBytes * 8, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
|
|
2345
|
+
let showMenu = true;
|
|
2346
|
+
while (true) {
|
|
2347
|
+
this.budget.loop();
|
|
2348
|
+
if (showMenu)
|
|
2349
|
+
await selectMenu(values, state.variables.COLUMNS, byteLocale(state.variables), io.stderr, work, allocation);
|
|
2350
|
+
const prompt = stateMonitor(state)?.values.get("PS3", state.variables.PS3 ?? "#? ") ?? state.variables.PS3 ?? "#? ";
|
|
2351
|
+
await io.stderr.write(shellValueBytes(prompt, allocation));
|
|
2352
|
+
const line = await input.selectLine(allocation);
|
|
2353
|
+
if (state.readonlyVariables?.has("REPLY")) {
|
|
2354
|
+
await this.diagnostic(io, "REPLY: readonly variable");
|
|
2355
|
+
await writeText(io.stdout, "\n");
|
|
2356
|
+
return 1;
|
|
2357
|
+
}
|
|
2358
|
+
this.writeVariable(state, "REPLY", line.value);
|
|
2359
|
+
if (!line.terminated) {
|
|
2360
|
+
await writeText(io.stdout, "\n");
|
|
2361
|
+
return 1;
|
|
2362
|
+
}
|
|
2363
|
+
const bytes = shellValueBytes(line.value, allocation);
|
|
2364
|
+
if (!bytes.length) {
|
|
2365
|
+
showMenu = true;
|
|
2366
|
+
continue;
|
|
2367
|
+
}
|
|
2368
|
+
let choice = 0;
|
|
2369
|
+
let phase = "start";
|
|
2370
|
+
let valid = true;
|
|
2371
|
+
let negative = false;
|
|
2372
|
+
let digits = false;
|
|
2373
|
+
for (const byte of bytes) {
|
|
2374
|
+
const pending = stringCheckpoint(work);
|
|
2375
|
+
if (pending)
|
|
2376
|
+
await pending;
|
|
2377
|
+
if (byte === 32 || byte >= 9 && byte <= 13) {
|
|
2378
|
+
if (phase === "digits")
|
|
2379
|
+
phase = "tail";
|
|
2380
|
+
continue;
|
|
2381
|
+
}
|
|
2382
|
+
if (phase === "start" && (byte === 43 || byte === 45)) {
|
|
2383
|
+
negative = byte === 45;
|
|
2384
|
+
phase = "digits";
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
if (phase === "tail" || byte < 48 || byte > 57) {
|
|
2388
|
+
valid = false;
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
phase = "digits";
|
|
2392
|
+
digits = true;
|
|
2393
|
+
choice = Math.min(values.length + 1, choice * 10 + byte - 48);
|
|
2394
|
+
}
|
|
2395
|
+
await this.assignVariable(state, command.name, valid && digits && !negative && choice >= 1 && choice <= values.length ? values[choice - 1] : "");
|
|
2396
|
+
const result = await this.loopBody(command.body, state, io);
|
|
2397
|
+
status = result.status;
|
|
2398
|
+
if (result.stop)
|
|
2399
|
+
break;
|
|
2400
|
+
showMenu = shellValueByteLength(stateMonitor(state)?.values.get("REPLY", state.variables.REPLY ?? "") ?? state.variables.REPLY ?? "") === 0;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2268
2403
|
else {
|
|
2269
2404
|
while (true) {
|
|
2270
2405
|
this.budget.loop();
|
|
@@ -2384,6 +2519,7 @@ export class Runtime {
|
|
|
2384
2519
|
async document(document, state, io, line = document.endLine) {
|
|
2385
2520
|
this.signal.throwIfAborted();
|
|
2386
2521
|
let value = "";
|
|
2522
|
+
let fragments;
|
|
2387
2523
|
let size = 0;
|
|
2388
2524
|
let words = 0;
|
|
2389
2525
|
const warnings = [];
|
|
@@ -2394,18 +2530,28 @@ export class Runtime {
|
|
|
2394
2530
|
await writeDiagnostic(io.stderr, `shell: warning: ${warning}\n`);
|
|
2395
2531
|
if (++words % 128 === 0)
|
|
2396
2532
|
await yieldTurn(this.signal);
|
|
2397
|
-
const part = (await this.
|
|
2398
|
-
size +=
|
|
2533
|
+
const part = concatShellValues(await this.valueWord(word, state, { ...io, nameExpansionContext: "document" }, false), io[valueScope]);
|
|
2534
|
+
size += shellValueByteLength(part);
|
|
2399
2535
|
if (size > this.budget.limits.maxExpansionBytes)
|
|
2400
2536
|
this.budget.fail("maxExpansionBytes");
|
|
2401
|
-
|
|
2537
|
+
if (!fragments && typeof part === "string")
|
|
2538
|
+
value += part;
|
|
2539
|
+
else {
|
|
2540
|
+
if (!fragments) {
|
|
2541
|
+
io[valueScope]?.reserve(64 + value.length * 2, 0);
|
|
2542
|
+
fragments = [value];
|
|
2543
|
+
value = "";
|
|
2544
|
+
}
|
|
2545
|
+
io[valueScope]?.reserve(32 + (typeof part === "string" ? part.length * 2 : 0), 0);
|
|
2546
|
+
fragments.push(part);
|
|
2547
|
+
}
|
|
2402
2548
|
}
|
|
2403
2549
|
}
|
|
2404
2550
|
finally {
|
|
2405
2551
|
for (const warning of warnings.splice(0))
|
|
2406
2552
|
await writeDiagnostic(io.stderr, `shell: warning: ${warning}\n`);
|
|
2407
2553
|
}
|
|
2408
|
-
return value;
|
|
2554
|
+
return fragments ? concatShellValues(fragments, io[valueScope]) : value;
|
|
2409
2555
|
}
|
|
2410
2556
|
async redirect(redirects, state, io, inputs, outputs, isolatedInlineInput = false, persistMoves = false, fileShortcut = false, line) {
|
|
2411
2557
|
this.signal.throwIfAborted();
|
|
@@ -2437,6 +2583,7 @@ export class Runtime {
|
|
|
2437
2583
|
const stdinIsDefault = descriptor?.input ? descriptor.stdinIsDefault : false;
|
|
2438
2584
|
return {
|
|
2439
2585
|
[invocationScope]: io[invocationScope],
|
|
2586
|
+
...(io[valueScope] === undefined ? {} : { [valueScope]: io[valueScope] }),
|
|
2440
2587
|
...(io.execution === undefined ? {} : { execution: io.execution }),
|
|
2441
2588
|
...(io.diagnosticLine === undefined ? {} : { diagnosticLine: io.diagnosticLine }),
|
|
2442
2589
|
...(io.diagnosticOffset === undefined ? {} : { diagnosticOffset: io.diagnosticOffset }),
|
|
@@ -2457,7 +2604,7 @@ export class Runtime {
|
|
|
2457
2604
|
const hereString = redirect.operator === "<<<";
|
|
2458
2605
|
let value;
|
|
2459
2606
|
try {
|
|
2460
|
-
value = redirect.document ? await this.document(redirect.document, state, currentIO(), line) : (await this.
|
|
2607
|
+
value = redirect.document ? await this.document(redirect.document, state, currentIO(), line) : concatShellValues(await this.valueWord(redirect.target, state, currentIO(), false, false, hereString), io[valueScope]);
|
|
2461
2608
|
}
|
|
2462
2609
|
catch (error) {
|
|
2463
2610
|
if (error instanceof NounsetFailure)
|
|
@@ -2471,11 +2618,11 @@ export class Runtime {
|
|
|
2471
2618
|
throw error;
|
|
2472
2619
|
}
|
|
2473
2620
|
if (hereString) {
|
|
2474
|
-
if (
|
|
2621
|
+
if (shellValueByteLength(value) >= this.budget.limits.maxExpansionBytes)
|
|
2475
2622
|
this.budget.fail("maxExpansionBytes");
|
|
2476
|
-
value
|
|
2623
|
+
value = concatShellValues([value, "\n"], io[valueScope]);
|
|
2477
2624
|
}
|
|
2478
|
-
const input = new ShellInput(toByteSource(value), this.budget, this.signal);
|
|
2625
|
+
const input = new ShellInput(toByteSource(typeof value === "string" ? value : shellValueBytes(value, io[valueScope])), this.budget, this.signal);
|
|
2479
2626
|
inputs.add(input);
|
|
2480
2627
|
descriptors.set(redirect.descriptor, { input, stdinIsDefault: false });
|
|
2481
2628
|
continue;
|
|
@@ -2706,8 +2853,8 @@ export class Runtime {
|
|
|
2706
2853
|
throw new ArrayFailure("indexed binding cannot be a command prefix");
|
|
2707
2854
|
}
|
|
2708
2855
|
await this.arrayZero(state, assignment.name, async () => {
|
|
2709
|
-
const fields = await this.
|
|
2710
|
-
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]);
|
|
2711
2858
|
}, assignment.append);
|
|
2712
2859
|
continue;
|
|
2713
2860
|
}
|
|
@@ -2870,6 +3017,7 @@ export class Runtime {
|
|
|
2870
3017
|
}
|
|
2871
3018
|
async dispatchScoped(name, values, state, io, assignments, bypassFunctions) {
|
|
2872
3019
|
const { [invocationScope]: scope, ...publicIO } = io;
|
|
3020
|
+
Reflect.deleteProperty(publicIO, valueScope);
|
|
2873
3021
|
const allocation = this.budget.values.scope();
|
|
2874
3022
|
scope.register(() => allocation.close());
|
|
2875
3023
|
const argumentValues = this.admitArguments(values, allocation);
|
|
@@ -2911,7 +3059,8 @@ export class Runtime {
|
|
|
2911
3059
|
scope.assertOpen();
|
|
2912
3060
|
const forwardedValues = getCommandArguments(forwarded);
|
|
2913
3061
|
const admitted = forwardedValues === argumentValues ? argumentValues : this.admitArguments(forwardedValues.values, allocation);
|
|
2914
|
-
const context = { ...forwarded, args: admitted.args, argumentValues: admitted, [invocationScope]: scope
|
|
3062
|
+
const context = { ...forwarded, args: admitted.args, argumentValues: admitted, [invocationScope]: scope,
|
|
3063
|
+
...(io[valueScope] === undefined ? {} : { [valueScope]: io[valueScope] }) };
|
|
2915
3064
|
const previous = new Map();
|
|
2916
3065
|
const cwd = state.cwd;
|
|
2917
3066
|
const directoryStackCwdPublication = state.directoryStackCwdPublication;
|
|
@@ -3047,6 +3196,9 @@ export class Runtime {
|
|
|
3047
3196
|
functionRestoration?.close();
|
|
3048
3197
|
throw error;
|
|
3049
3198
|
}
|
|
3199
|
+
const callerLoopDepth = state.loopDepth;
|
|
3200
|
+
if (mapfileCallbackStates.has(state))
|
|
3201
|
+
state.loopDepth = 0;
|
|
3050
3202
|
try {
|
|
3051
3203
|
return { exitCode: await this.command(body, state, { ...io, ...context, scriptName: body.sourceName ?? io.scriptName ?? "shell" }) };
|
|
3052
3204
|
}
|
|
@@ -3061,6 +3213,7 @@ export class Runtime {
|
|
|
3061
3213
|
savedPositionals.close();
|
|
3062
3214
|
state.positionalSetVersion = positionalSetVersion;
|
|
3063
3215
|
state.functionDepth--;
|
|
3216
|
+
state.loopDepth = callerLoopDepth;
|
|
3064
3217
|
state.depth--;
|
|
3065
3218
|
state.locals.pop();
|
|
3066
3219
|
};
|
|
@@ -3558,6 +3711,8 @@ export class Runtime {
|
|
|
3558
3711
|
return invocation;
|
|
3559
3712
|
},
|
|
3560
3713
|
};
|
|
3714
|
+
Reflect.deleteProperty(context, invocationScope);
|
|
3715
|
+
Reflect.deleteProperty(context, valueScope);
|
|
3561
3716
|
bindCommandIO(context);
|
|
3562
3717
|
bindFileOutputBudget(context, sink => this.budget.sink(sink, runtime.signal));
|
|
3563
3718
|
if (argumentValues.values.every(value => typeof value === "string"))
|
|
@@ -4677,6 +4832,137 @@ export class Runtime {
|
|
|
4677
4832
|
allocation.close();
|
|
4678
4833
|
}
|
|
4679
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
|
+
}
|
|
4680
4966
|
async builtin(context, state, assignments, diagnose, suppressSpecial = false) {
|
|
4681
4967
|
const { command, args, stdout, stderr } = context;
|
|
4682
4968
|
if (command === ":" || command === "true")
|
|
@@ -4687,6 +4973,8 @@ export class Runtime {
|
|
|
4687
4973
|
return this.shoptBuiltin(context, state);
|
|
4688
4974
|
if (command === "let")
|
|
4689
4975
|
return this.letBuiltin(context, state);
|
|
4976
|
+
if (command === "mapfile" || command === "readarray")
|
|
4977
|
+
return this.mapfileBuiltin(context, state);
|
|
4690
4978
|
if (command === "getopts")
|
|
4691
4979
|
return this.getoptsBuiltin(context, state);
|
|
4692
4980
|
if (command === "pushd" || command === "dirs" || command === "popd")
|
|
@@ -4760,9 +5048,16 @@ export class Runtime {
|
|
|
4760
5048
|
this.replacePositionals(state, this.positionalValues(state).slice(count));
|
|
4761
5049
|
return 0;
|
|
4762
5050
|
}
|
|
4763
|
-
if (command === "export" || command === "local" || command === "readonly") {
|
|
5051
|
+
if (command === "export" || command === "local" || command === "readonly" || command === "declare") {
|
|
5052
|
+
const associativeDeclaration = command === "declare";
|
|
4764
5053
|
const declarationArgs = [...args];
|
|
4765
|
-
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
|
+
}
|
|
4766
5061
|
if (command === "local") {
|
|
4767
5062
|
const options = localDeclarationOptions(declarationArgs, this.signal);
|
|
4768
5063
|
if (options.error !== undefined) {
|
|
@@ -4825,13 +5120,13 @@ export class Runtime {
|
|
|
4825
5120
|
status = 1;
|
|
4826
5121
|
continue;
|
|
4827
5122
|
}
|
|
4828
|
-
if (command === "local" && indexedLocal) {
|
|
5123
|
+
if ((command === "local" || associativeDeclaration) && indexedLocal) {
|
|
4829
5124
|
if (controlNames.has(name))
|
|
4830
5125
|
throw new ArrayFailure("control binding cannot be indexed");
|
|
4831
5126
|
if (state.exported.has(name))
|
|
4832
5127
|
throw new ArrayFailure("exported binding cannot be indexed");
|
|
4833
|
-
const existingLocal = locals
|
|
4834
|
-
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);
|
|
4835
5130
|
let operation;
|
|
4836
5131
|
let holding;
|
|
4837
5132
|
let shadow;
|
|
@@ -4856,12 +5151,15 @@ export class Runtime {
|
|
|
4856
5151
|
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
4857
5152
|
const prepared = await store.prepareName(name, operation, this.signal);
|
|
4858
5153
|
const current = store.get(name);
|
|
4859
|
-
|
|
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);
|
|
4860
5157
|
const value = match[2] ?? (!saved && !current && Object.hasOwn(state.variables, name) ? state.variables[name] : undefined);
|
|
4861
5158
|
if (value !== undefined) {
|
|
4862
|
-
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);
|
|
4863
5161
|
try {
|
|
4864
|
-
shadow.insert(0, token);
|
|
5162
|
+
shadow.insert(associativeDeclaration ? (await shadow.keyIndex("0", operation, this.signal, true)) : 0, token);
|
|
4865
5163
|
}
|
|
4866
5164
|
catch (error) {
|
|
4867
5165
|
token.release();
|
|
@@ -5036,7 +5334,8 @@ export class Runtime {
|
|
|
5036
5334
|
}
|
|
5037
5335
|
if (command === "unset") {
|
|
5038
5336
|
let status = 0;
|
|
5039
|
-
for (
|
|
5337
|
+
for (let argument = 0; argument < args.length; argument++) {
|
|
5338
|
+
const name = args[argument];
|
|
5040
5339
|
const selected = /^([a-zA-Z_][a-zA-Z_0-9]*)\[(.*)\]$/su.exec(name);
|
|
5041
5340
|
if (selected) {
|
|
5042
5341
|
const base = selected[1];
|
|
@@ -5046,7 +5345,16 @@ export class Runtime {
|
|
|
5046
5345
|
status = 1;
|
|
5047
5346
|
continue;
|
|
5048
5347
|
}
|
|
5049
|
-
|
|
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 === "*")
|
|
5050
5358
|
await this.unsetIndexed(state, base, "members");
|
|
5051
5359
|
else {
|
|
5052
5360
|
let index;
|
|
@@ -5304,6 +5612,35 @@ export class Runtime {
|
|
|
5304
5612
|
}
|
|
5305
5613
|
}
|
|
5306
5614
|
async valuePart(part, state, io, hereString = false) {
|
|
5615
|
+
if (part.kind === "variable" && part.transform) {
|
|
5616
|
+
const selector = getArraySelector(part);
|
|
5617
|
+
if (selector?.kind === "members" || part.name === "@" || part.name === "*") {
|
|
5618
|
+
const members = selector ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
|
|
5619
|
+
const ifs = state.variables.IFS ?? " ";
|
|
5620
|
+
const separator = ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
|
|
5621
|
+
const fragments = [];
|
|
5622
|
+
let bytes = 0;
|
|
5623
|
+
for (const member of members) {
|
|
5624
|
+
const value = await this.transformValue(member, part.transform, state, io);
|
|
5625
|
+
bytes += shellValueByteLength(value) + (fragments.length ? Buffer.byteLength(separator) : 0);
|
|
5626
|
+
if (bytes > this.budget.limits.maxExpansionBytes)
|
|
5627
|
+
this.budget.fail("maxExpansionBytes");
|
|
5628
|
+
io[valueScope]?.reserve(32, 0);
|
|
5629
|
+
if (fragments.length)
|
|
5630
|
+
fragments.push(separator);
|
|
5631
|
+
fragments.push(value);
|
|
5632
|
+
}
|
|
5633
|
+
return concatShellValues(fragments, io[valueScope]);
|
|
5634
|
+
}
|
|
5635
|
+
const base = { ...part };
|
|
5636
|
+
delete base.transform;
|
|
5637
|
+
copyArraySelector(part, base);
|
|
5638
|
+
const existing = selector?.kind === "element" ? arrayStore(state)?.get(part.name)?.get(numericIndex(selector.index) ?? -1) ?? (numericIndex(selector.index) === 0 ? this.variable(state, part.name) : undefined)
|
|
5639
|
+
: /^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name) ? this.variable(state, part.name)
|
|
5640
|
+
: /^[1-9][0-9]*$/u.test(part.name) ? state.positional[Number(part.name) - 1] : "";
|
|
5641
|
+
const value = await this.valuePart(base, state, io, hereString);
|
|
5642
|
+
return existing === undefined ? "" : this.transformValue(value, part.transform, state, io);
|
|
5643
|
+
}
|
|
5307
5644
|
if (part.kind === "variable" && part.prefixNames) {
|
|
5308
5645
|
const ifs = state.variables.IFS ?? " ";
|
|
5309
5646
|
const separator = io.nameExpansionContext === "document" || io.nameExpansionContext === "conditional" && part.prefixNames === "@"
|
|
@@ -5324,10 +5661,25 @@ export class Runtime {
|
|
|
5324
5661
|
const binding = part.kind === "variable" ? arrayStore(state)?.get(part.name) : undefined;
|
|
5325
5662
|
const holding = binding ? requireArrays(state).owner.hold() : undefined;
|
|
5326
5663
|
const selector = getArraySelector(part);
|
|
5327
|
-
|
|
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;
|
|
5328
5678
|
const token = index === undefined || selector?.kind === "members" ? undefined : binding?.values.get(index)?.text;
|
|
5329
5679
|
token?.retain();
|
|
5330
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]);
|
|
5331
5683
|
const value = await this.partValue(part, state, io, hereString);
|
|
5332
5684
|
if (binding)
|
|
5333
5685
|
await textToken(requireArrays(state).owner, shellValueText(value), this.signal);
|
|
@@ -5345,6 +5697,19 @@ export class Runtime {
|
|
|
5345
5697
|
throw new ShellSyntaxError("Syntax nesting exceeds 64", word.offset);
|
|
5346
5698
|
return { ...io, parameterDepth };
|
|
5347
5699
|
}
|
|
5700
|
+
async transformValue(value, operator, state, io) {
|
|
5701
|
+
const allocation = io[valueScope] ?? this.budget.values.scope();
|
|
5702
|
+
try {
|
|
5703
|
+
return await transformParameter(value, operator, {
|
|
5704
|
+
maximumBytes: this.budget.limits.maxExpansionBytes, byteLocale: byteLocale(state.variables), allocation,
|
|
5705
|
+
work: { remaining: Math.min(Number.MAX_SAFE_INTEGER, this.budget.limits.maxExpansionBytes * 8 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") },
|
|
5706
|
+
});
|
|
5707
|
+
}
|
|
5708
|
+
finally {
|
|
5709
|
+
if (!io[valueScope])
|
|
5710
|
+
allocation.close();
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5348
5713
|
async partValue(part, state, io, hereString) {
|
|
5349
5714
|
this.signal.throwIfAborted();
|
|
5350
5715
|
if (part.kind === "compound-substitution-eof") {
|
|
@@ -5444,8 +5809,12 @@ export class Runtime {
|
|
|
5444
5809
|
}
|
|
5445
5810
|
if (part.length)
|
|
5446
5811
|
return String(binding?.values.size ?? (state.variables[part.name] === undefined ? 0 : 1));
|
|
5447
|
-
const values = await this.arrayMembers(part.name, state);
|
|
5448
|
-
|
|
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]);
|
|
5449
5818
|
}
|
|
5450
5819
|
let value = part.name === "?" ? String(state.status)
|
|
5451
5820
|
: part.name === "-" ? `${state.errexit ? "e" : ""}${state.nounset ? "u" : ""}${state.braceexpand !== false ? "B" : ""}`
|
|
@@ -5769,7 +6138,7 @@ export class Runtime {
|
|
|
5769
6138
|
return fields;
|
|
5770
6139
|
}
|
|
5771
6140
|
const arrayOwned = word.parts.some(part => part.kind === "variable" && !part.prefixNames && (getArraySelector(part) !== undefined || arrayStore(state)?.get(part.name) !== undefined));
|
|
5772
|
-
const prefixOwned = word.parts.some(part => part.kind === "variable" && part.prefixNames === "@");
|
|
6141
|
+
const prefixOwned = word.parts.some(part => part.kind === "variable" && (part.prefixNames === "@" || part.transform && part.name === "@"));
|
|
5773
6142
|
const owner = arrayOwned ? requireArrays(state).owner : undefined;
|
|
5774
6143
|
const holding = owner?.hold();
|
|
5775
6144
|
const scratch = !owner && split && state.variables.IFS !== "" && word.parts.some(part => !part.quoted && part.kind !== "text")
|
|
@@ -5921,17 +6290,23 @@ export class Runtime {
|
|
|
5921
6290
|
emptyNameGroups.add(quoteGroup);
|
|
5922
6291
|
}
|
|
5923
6292
|
}
|
|
5924
|
-
else if (part.kind === "variable" && selector?.kind === "members" && !part.length &&
|
|
5925
|
-
const members = await this.arrayMembers(part.name, state);
|
|
6293
|
+
else if (part.kind === "variable" && split && (selector?.kind === "members" && !part.length && (!part.quoted || selector.separator === "@") || part.transform && part.name === "@")) {
|
|
6294
|
+
const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
|
|
5926
6295
|
for (let position = 0; position < members.length; position++) {
|
|
5927
6296
|
if (position > 0)
|
|
5928
6297
|
addField();
|
|
5929
|
-
const
|
|
6298
|
+
const original = members[position];
|
|
6299
|
+
const value = part.transform ? await this.transformValue(original, part.transform, state, partIO) : original;
|
|
5930
6300
|
if (part.quoted || state.variables.IFS === "")
|
|
5931
|
-
append(value, !part.quoted, part.quoted || value
|
|
6301
|
+
append(value, !part.quoted, part.quoted || shellValueByteLength(value) > 0);
|
|
5932
6302
|
else
|
|
5933
6303
|
await appendSplit(value);
|
|
5934
6304
|
}
|
|
6305
|
+
if (part.transform && members.length === 0 && quoteGroup) {
|
|
6306
|
+
io[valueScope]?.reserve(32, 0);
|
|
6307
|
+
emptyNameGroups ??= new Set();
|
|
6308
|
+
emptyNameGroups.add(quoteGroup);
|
|
6309
|
+
}
|
|
5935
6310
|
}
|
|
5936
6311
|
else if (part.kind === "text" && !splitText) {
|
|
5937
6312
|
let value = invokedValues.get(part) ?? part.byteValue ?? part.value;
|
|
@@ -5939,7 +6314,7 @@ export class Runtime {
|
|
|
5939
6314
|
value = (state.variables.HOME ?? "~") + value.slice(1);
|
|
5940
6315
|
append(value, !part.quoted, quotedPresence || shellValueByteLength(value) > 0);
|
|
5941
6316
|
}
|
|
5942
|
-
else if (part.kind === "variable" && part.name === "@" && part.quoted && !part.operator && split) {
|
|
6317
|
+
else if (part.kind === "variable" && part.name === "@" && part.quoted && !part.operator && !part.transform && split) {
|
|
5943
6318
|
for (let position = 0; position < state.positional.length; position++) {
|
|
5944
6319
|
if (position > 0)
|
|
5945
6320
|
addField();
|
|
@@ -6091,7 +6466,7 @@ export class Runtime {
|
|
|
6091
6466
|
allocation.hold(value);
|
|
6092
6467
|
return createCommandArguments(values, allocation);
|
|
6093
6468
|
}
|
|
6094
|
-
async arrayMembers(name, state) {
|
|
6469
|
+
async arrayMembers(name, state, io, keys = false) {
|
|
6095
6470
|
const store = requireArrays(state);
|
|
6096
6471
|
const holding = store.owner.hold();
|
|
6097
6472
|
try {
|
|
@@ -6103,7 +6478,7 @@ export class Runtime {
|
|
|
6103
6478
|
return [];
|
|
6104
6479
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
6105
6480
|
await textToken(store.owner, value, this.signal);
|
|
6106
|
-
return [value];
|
|
6481
|
+
return [keys ? "0" : value];
|
|
6107
6482
|
}
|
|
6108
6483
|
binding.retain();
|
|
6109
6484
|
try {
|
|
@@ -6111,8 +6486,10 @@ export class Runtime {
|
|
|
6111
6486
|
const values = [];
|
|
6112
6487
|
for (const index of indices) {
|
|
6113
6488
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 4 });
|
|
6114
|
-
const
|
|
6115
|
-
|
|
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);
|
|
6116
6493
|
values.push(value);
|
|
6117
6494
|
await store.owner.ledger.checkpoint(this.signal);
|
|
6118
6495
|
}
|