@poe-platform/safe-bash 0.1.528 → 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 +185 -40
- package/dist/safe-bash/core.browser.js.map +3 -3
- package/dist/safe-bash/shell/arrays/bindings.d.ts +11 -1
- package/dist/safe-bash/shell/arrays/bindings.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/bindings.js +74 -4
- 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/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 +2 -1
- package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
- package/dist/safe-bash/shell/runtime.js +89 -27
- 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";
|
|
@@ -59,7 +59,7 @@ export const defaultLimits = {
|
|
|
59
59
|
pipeHighWaterMark: 64 * 1024,
|
|
60
60
|
};
|
|
61
61
|
const shellBuiltinNames = new Set([
|
|
62
|
-
":", "true", "false", "pwd", "cd", "set", "shift", "export", "local", "unset", "read", "mapfile", "readarray",
|
|
62
|
+
":", "true", "false", "pwd", "cd", "set", "shift", "export", "local", "unset", "read", "declare", "mapfile", "readarray",
|
|
63
63
|
"exit", "return", "break", "continue", "command", "builtin", "type", "readonly", "echo", "printf", "test", "[", ".", "source", "eval", "getopts", "let", "pushd", "dirs", "popd", "shopt",
|
|
64
64
|
]);
|
|
65
65
|
const implementedBuiltins = new Set([...shellBuiltinNames].filter(name => !["echo", "printf", "test", "["].includes(name)));
|
|
@@ -1437,7 +1437,7 @@ export class Runtime {
|
|
|
1437
1437
|
}
|
|
1438
1438
|
variable(state, name) {
|
|
1439
1439
|
const binding = arrayStore(state)?.get(name);
|
|
1440
|
-
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];
|
|
1441
1441
|
}
|
|
1442
1442
|
requireParameter(value, name, state, io, line) {
|
|
1443
1443
|
if (state.nounset && value === undefined)
|
|
@@ -1593,7 +1593,7 @@ export class Runtime {
|
|
|
1593
1593
|
await operation.ledger.checkpoint(this.signal, 2);
|
|
1594
1594
|
}
|
|
1595
1595
|
if (typeof index === "number")
|
|
1596
|
-
staged.
|
|
1596
|
+
staged.remove(index);
|
|
1597
1597
|
staged.maximum = maximum;
|
|
1598
1598
|
}
|
|
1599
1599
|
this.signal.throwIfAborted();
|
|
@@ -1652,12 +1652,13 @@ export class Runtime {
|
|
|
1652
1652
|
if (!current)
|
|
1653
1653
|
throw new ArrayFailure("stale binding");
|
|
1654
1654
|
staged = await current.copy(this.signal);
|
|
1655
|
-
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) ?? "";
|
|
1656
1657
|
const value = !append ? expanded : typeof previous === "string" && typeof expanded === "string"
|
|
1657
1658
|
? await this.arrayJoin(operation, [previous, expanded], "") : concatShellValues([previous, expanded], valueAllocation);
|
|
1658
1659
|
const token = await valueToken(staged.owner, value, this.signal);
|
|
1659
1660
|
try {
|
|
1660
|
-
staged.insert(
|
|
1661
|
+
staged.insert(index, token);
|
|
1661
1662
|
}
|
|
1662
1663
|
catch (error) {
|
|
1663
1664
|
token.release();
|
|
@@ -1707,11 +1708,35 @@ export class Runtime {
|
|
|
1707
1708
|
await owner.ledger.checkpoint(this.signal);
|
|
1708
1709
|
return result;
|
|
1709
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
|
+
}
|
|
1710
1730
|
async arrayAssignment(assignment, state, io) {
|
|
1711
1731
|
const name = assignment.name;
|
|
1712
1732
|
this.signal.throwIfAborted();
|
|
1713
|
-
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
|
+
}
|
|
1714
1738
|
throw new ArrayFailure("readonly binding");
|
|
1739
|
+
}
|
|
1715
1740
|
if (controlNames.has(name))
|
|
1716
1741
|
throw new ArrayFailure("control binding cannot be indexed");
|
|
1717
1742
|
if (state.exported.has(name))
|
|
@@ -1723,11 +1748,13 @@ export class Runtime {
|
|
|
1723
1748
|
try {
|
|
1724
1749
|
const watch = await store.watch(name, operation, this.signal);
|
|
1725
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");
|
|
1726
1753
|
const initialMaximum = current?.maximum ?? (state.variables[name] === undefined ? -1 : 0);
|
|
1727
1754
|
let planned = assignment.append && assignment.kind === "compound" ? initialMaximum + 1 : 0;
|
|
1728
1755
|
if (assignment.kind === "element") {
|
|
1729
1756
|
operation.reserve({ work: assignment.index.decimal.length + 1 }).release();
|
|
1730
|
-
if (numericIndex(assignment.index) === undefined)
|
|
1757
|
+
if (!current?.associative && numericIndex(literalIndex(assignment.index.source ?? assignment.index.decimal, 0, this.budget.parsing)) === undefined)
|
|
1731
1758
|
throw new ArrayFailure("index outside 0..2147483647");
|
|
1732
1759
|
}
|
|
1733
1760
|
else
|
|
@@ -1761,7 +1788,7 @@ export class Runtime {
|
|
|
1761
1788
|
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
1762
1789
|
const prepared = await store.prepareName(name, operation, this.signal);
|
|
1763
1790
|
const preserve = assignment.kind === "element" || assignment.append;
|
|
1764
|
-
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);
|
|
1765
1792
|
if (preserve && !current && state.variables[name] !== undefined) {
|
|
1766
1793
|
const token = await valueToken(staged.owner, stateMonitor(state)?.values.get(name, state.variables[name]) ?? state.variables[name], this.signal);
|
|
1767
1794
|
try {
|
|
@@ -1790,7 +1817,7 @@ export class Runtime {
|
|
|
1790
1817
|
const join = async (values) => values.every(value => typeof value === "string")
|
|
1791
1818
|
? this.arrayJoin(operation, values, "") : concatShellValues(values, io[valueScope]);
|
|
1792
1819
|
if (assignment.kind === "element") {
|
|
1793
|
-
const index =
|
|
1820
|
+
const index = (await this.arrayIndex(staged, assignment.index, state, io, operation, true));
|
|
1794
1821
|
const fields = await this.valueWord(assignment.value, state, io, false);
|
|
1795
1822
|
let value = await join(fields);
|
|
1796
1823
|
if (assignment.append)
|
|
@@ -4812,6 +4839,8 @@ export class Runtime {
|
|
|
4812
4839
|
let releaseHolding;
|
|
4813
4840
|
try {
|
|
4814
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);
|
|
4815
4844
|
if (state.readonlyVariables?.has(options.name))
|
|
4816
4845
|
throw new MapfileUsageError(`${options.name}: readonly variable`, 1);
|
|
4817
4846
|
if (controlNames.has(options.name) || state.exported.has(options.name))
|
|
@@ -5019,9 +5048,16 @@ export class Runtime {
|
|
|
5019
5048
|
this.replacePositionals(state, this.positionalValues(state).slice(count));
|
|
5020
5049
|
return 0;
|
|
5021
5050
|
}
|
|
5022
|
-
if (command === "export" || command === "local" || command === "readonly") {
|
|
5051
|
+
if (command === "export" || command === "local" || command === "readonly" || command === "declare") {
|
|
5052
|
+
const associativeDeclaration = command === "declare";
|
|
5023
5053
|
const declarationArgs = [...args];
|
|
5024
|
-
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
|
+
}
|
|
5025
5061
|
if (command === "local") {
|
|
5026
5062
|
const options = localDeclarationOptions(declarationArgs, this.signal);
|
|
5027
5063
|
if (options.error !== undefined) {
|
|
@@ -5084,13 +5120,13 @@ export class Runtime {
|
|
|
5084
5120
|
status = 1;
|
|
5085
5121
|
continue;
|
|
5086
5122
|
}
|
|
5087
|
-
if (command === "local" && indexedLocal) {
|
|
5123
|
+
if ((command === "local" || associativeDeclaration) && indexedLocal) {
|
|
5088
5124
|
if (controlNames.has(name))
|
|
5089
5125
|
throw new ArrayFailure("control binding cannot be indexed");
|
|
5090
5126
|
if (state.exported.has(name))
|
|
5091
5127
|
throw new ArrayFailure("exported binding cannot be indexed");
|
|
5092
|
-
const existingLocal = locals
|
|
5093
|
-
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);
|
|
5094
5130
|
let operation;
|
|
5095
5131
|
let holding;
|
|
5096
5132
|
let shadow;
|
|
@@ -5115,12 +5151,15 @@ export class Runtime {
|
|
|
5115
5151
|
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
5116
5152
|
const prepared = await store.prepareName(name, operation, this.signal);
|
|
5117
5153
|
const current = store.get(name);
|
|
5118
|
-
|
|
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);
|
|
5119
5157
|
const value = match[2] ?? (!saved && !current && Object.hasOwn(state.variables, name) ? state.variables[name] : undefined);
|
|
5120
5158
|
if (value !== undefined) {
|
|
5121
|
-
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);
|
|
5122
5161
|
try {
|
|
5123
|
-
shadow.insert(0, token);
|
|
5162
|
+
shadow.insert(associativeDeclaration ? (await shadow.keyIndex("0", operation, this.signal, true)) : 0, token);
|
|
5124
5163
|
}
|
|
5125
5164
|
catch (error) {
|
|
5126
5165
|
token.release();
|
|
@@ -5295,7 +5334,8 @@ export class Runtime {
|
|
|
5295
5334
|
}
|
|
5296
5335
|
if (command === "unset") {
|
|
5297
5336
|
let status = 0;
|
|
5298
|
-
for (
|
|
5337
|
+
for (let argument = 0; argument < args.length; argument++) {
|
|
5338
|
+
const name = args[argument];
|
|
5299
5339
|
const selected = /^([a-zA-Z_][a-zA-Z_0-9]*)\[(.*)\]$/su.exec(name);
|
|
5300
5340
|
if (selected) {
|
|
5301
5341
|
const base = selected[1];
|
|
@@ -5305,7 +5345,16 @@ export class Runtime {
|
|
|
5305
5345
|
status = 1;
|
|
5306
5346
|
continue;
|
|
5307
5347
|
}
|
|
5308
|
-
|
|
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 === "*")
|
|
5309
5358
|
await this.unsetIndexed(state, base, "members");
|
|
5310
5359
|
else {
|
|
5311
5360
|
let index;
|
|
@@ -5566,7 +5615,7 @@ export class Runtime {
|
|
|
5566
5615
|
if (part.kind === "variable" && part.transform) {
|
|
5567
5616
|
const selector = getArraySelector(part);
|
|
5568
5617
|
if (selector?.kind === "members" || part.name === "@" || part.name === "*") {
|
|
5569
|
-
const members = selector ? await this.arrayMembers(part.name, state, io) : this.positionalValues(state);
|
|
5618
|
+
const members = selector ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
|
|
5570
5619
|
const ifs = state.variables.IFS ?? " ";
|
|
5571
5620
|
const separator = ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
|
|
5572
5621
|
const fragments = [];
|
|
@@ -5612,7 +5661,20 @@ export class Runtime {
|
|
|
5612
5661
|
const binding = part.kind === "variable" ? arrayStore(state)?.get(part.name) : undefined;
|
|
5613
5662
|
const holding = binding ? requireArrays(state).owner.hold() : undefined;
|
|
5614
5663
|
const selector = getArraySelector(part);
|
|
5615
|
-
|
|
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;
|
|
5616
5678
|
const token = index === undefined || selector?.kind === "members" ? undefined : binding?.values.get(index)?.text;
|
|
5617
5679
|
token?.retain();
|
|
5618
5680
|
try {
|
|
@@ -5747,7 +5809,7 @@ export class Runtime {
|
|
|
5747
5809
|
}
|
|
5748
5810
|
if (part.length)
|
|
5749
5811
|
return String(binding?.values.size ?? (state.variables[part.name] === undefined ? 0 : 1));
|
|
5750
|
-
const values = await this.arrayMembers(part.name, state, io);
|
|
5812
|
+
const values = await this.arrayMembers(part.name, state, io, part.keys);
|
|
5751
5813
|
const separator = Array.from(state.variables.IFS ?? " ")[0] ?? "";
|
|
5752
5814
|
if (values.every(value => typeof value === "string"))
|
|
5753
5815
|
return this.arrayJoin(store.owner, values, separator);
|
|
@@ -6229,7 +6291,7 @@ export class Runtime {
|
|
|
6229
6291
|
}
|
|
6230
6292
|
}
|
|
6231
6293
|
else if (part.kind === "variable" && split && (selector?.kind === "members" && !part.length && (!part.quoted || selector.separator === "@") || part.transform && part.name === "@")) {
|
|
6232
|
-
const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state, io) : this.positionalValues(state);
|
|
6294
|
+
const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
|
|
6233
6295
|
for (let position = 0; position < members.length; position++) {
|
|
6234
6296
|
if (position > 0)
|
|
6235
6297
|
addField();
|
|
@@ -6404,7 +6466,7 @@ export class Runtime {
|
|
|
6404
6466
|
allocation.hold(value);
|
|
6405
6467
|
return createCommandArguments(values, allocation);
|
|
6406
6468
|
}
|
|
6407
|
-
async arrayMembers(name, state, io) {
|
|
6469
|
+
async arrayMembers(name, state, io, keys = false) {
|
|
6408
6470
|
const store = requireArrays(state);
|
|
6409
6471
|
const holding = store.owner.hold();
|
|
6410
6472
|
try {
|
|
@@ -6416,7 +6478,7 @@ export class Runtime {
|
|
|
6416
6478
|
return [];
|
|
6417
6479
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
6418
6480
|
await textToken(store.owner, value, this.signal);
|
|
6419
|
-
return [value];
|
|
6481
|
+
return [keys ? "0" : value];
|
|
6420
6482
|
}
|
|
6421
6483
|
binding.retain();
|
|
6422
6484
|
try {
|
|
@@ -6424,7 +6486,7 @@ export class Runtime {
|
|
|
6424
6486
|
const values = [];
|
|
6425
6487
|
for (const index of indices) {
|
|
6426
6488
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 4 });
|
|
6427
|
-
const token = binding.values.get(index).text;
|
|
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;
|
|
6428
6490
|
const value = token.rawValue === undefined ? token.value : shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]);
|
|
6429
6491
|
if (typeof value === "string")
|
|
6430
6492
|
await textToken(store.owner, value, this.signal);
|