@poe-platform/safe-bash 0.1.528 → 0.1.530

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.
@@ -37498,12 +37498,23 @@ function literalIndex(source2, offset, budget = new ParseBudget()) {
37498
37498
  return { decimal: decimal4 };
37499
37499
  }
37500
37500
  function numericIndex(index) {
37501
+ if (index.source !== void 0) {
37502
+ try {
37503
+ return numericIndex(literalIndex(index.source, 0));
37504
+ } catch {
37505
+ return void 0;
37506
+ }
37507
+ }
37501
37508
  if (index.decimal.length > 10 || index.decimal.length === 10 && index.decimal > "2147483647") return void 0;
37502
37509
  return Number(index.decimal);
37503
37510
  }
37504
- function arraySelector(source2, offset, budget = new ParseBudget()) {
37511
+ function stringIndex(source2, budget, word) {
37512
+ budget.admit(2);
37513
+ return { decimal: source2, source: source2, ...word ? { word } : {} };
37514
+ }
37515
+ function arraySelector(source2, offset, budget = new ParseBudget(), word) {
37505
37516
  budget.admit();
37506
- return source2 === "@" || source2 === "*" ? { kind: "members", separator: source2 } : { kind: "element", index: literalIndex(source2, offset, budget) };
37517
+ return source2 === "@" || source2 === "*" ? { kind: "members", separator: source2 } : { kind: "element", index: word ? stringIndex(source2, budget, word) : literalIndex(source2, offset, budget) };
37507
37518
  }
37508
37519
  function setArraySelector(part, selector) {
37509
37520
  selectors.set(part, selector);
@@ -37543,21 +37554,22 @@ function removePrefix(word, length, budget) {
37543
37554
  if (length !== 0) throw new ShellSyntaxError("Invalid indexed-array assignment", word.offset);
37544
37555
  return { offset: word.offset, parts };
37545
37556
  }
37546
- function elementAssignment(word, budget = new ParseBudget()) {
37557
+ function elementAssignment(word, budget = new ParseBudget(), parseWord, subscriptEnd) {
37547
37558
  const source2 = word.spelling;
37548
37559
  const first = word.parts[0];
37549
37560
  if (source2 === void 0 || first?.kind !== "text" || first.quoted) return void 0;
37550
37561
  const name2 = /^([a-zA-Z_][a-zA-Z_0-9]*)\[/u.exec(source2)?.[1];
37551
37562
  if (!name2) return void 0;
37552
- const end = source2.indexOf("]", name2.length + 1);
37563
+ const end = subscriptEnd ? subscriptEnd(source2, name2.length + 1) : source2.indexOf("]", name2.length + 1);
37553
37564
  if (end < 0 || !/^(?:\+?=)/u.test(source2.slice(end + 1))) {
37554
37565
  if (source2.includes("=")) throw new ShellSyntaxError("Invalid indexed-array assignment", word.offset);
37555
37566
  return void 0;
37556
37567
  }
37557
37568
  budget.admit();
37558
- const index = literalIndex(source2.slice(name2.length + 1, end), word.offset + name2.length + 1, budget);
37569
+ const keySource = source2.slice(name2.length + 1, end);
37570
+ const index = parseWord ? stringIndex(keySource, budget, parseWord(keySource)) : literalIndex(keySource, word.offset + name2.length + 1, budget);
37559
37571
  const append = source2[end + 1] === "+";
37560
- const value2 = removePrefix(word, name2.length + index.decimal.length + (append ? 4 : 3), budget);
37572
+ const value2 = parseWord ? parseWord(source2.slice(end + (append ? 3 : 2))) : removePrefix(word, name2.length + index.decimal.length + (append ? 4 : 3), budget);
37561
37573
  return { kind: "element", name: name2, index, append, value: value2 };
37562
37574
  }
37563
37575
  function compoundHead(word, budget = new ParseBudget()) {
@@ -38316,7 +38328,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
38316
38328
  if (!name2) this.error("Unsupported parameter expansion");
38317
38329
  this.position += name2.length;
38318
38330
  let prefixNames;
38319
- if (listing) {
38331
+ if (listing && this.source[this.position] !== "[") {
38320
38332
  const separator = this.source[this.position];
38321
38333
  if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name2) || separator !== "*" && separator !== "@" || this.source[this.position + 1] !== "}") this.error("Unsupported indirect parameter expansion");
38322
38334
  prefixNames = separator;
@@ -38326,12 +38338,14 @@ shell: command substitution: line ${line}: \`${sourceLine}'
38326
38338
  if (this.source[this.position] === "[") {
38327
38339
  if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name2)) this.error("Unsupported indexed-array parameter");
38328
38340
  const start = ++this.position;
38329
- const end = this.source.indexOf("]", start);
38330
- if (end < 0) this.error("Unterminated indexed-array subscript");
38331
- selector = arraySelector(this.source.slice(start, end), start, this.budget);
38341
+ const keyWord = this.word("]");
38342
+ const end = this.position;
38343
+ if (this.source[end] !== "]") this.error("Unterminated indexed-array subscript");
38344
+ selector = arraySelector(this.source.slice(start, end), start, this.budget, keyWord);
38332
38345
  this.position = end + 1;
38333
38346
  if (this.source[this.position] !== "}" && this.source[this.position] !== "@") this.error("Unsupported indexed-array operator");
38334
38347
  }
38348
+ if (listing && selector?.kind === "element") this.error("Unsupported indirect parameter expansion");
38335
38349
  let transform2;
38336
38350
  if (this.source[this.position] === "@") {
38337
38351
  const operation2 = this.source[this.position + 1];
@@ -38382,6 +38396,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
38382
38396
  } else {
38383
38397
  this.position++;
38384
38398
  const part = { kind: "variable", name: name2, quoted: quoted2, line, ...prefixNames ? { prefixNames } : {}, ...transform2 ? { transform: transform2 } : {}, ...length ? { length } : {}, ...operator ? { operator, alternate } : {}, ...replacement ? { replacement } : {}, ...substring ? { substring } : {} };
38399
+ if (listing && selector) part.keys = true;
38385
38400
  if (selector) setArraySelector(part, selector);
38386
38401
  parts.push(part);
38387
38402
  }
@@ -38756,7 +38771,12 @@ var Parser4 = class {
38756
38771
  word = { offset: word.offset, parts: word.parts, spelling: this.lexer.source.slice(word.offset, end) };
38757
38772
  setArrayAssignment(word, { kind: "compound", ...head, entries: entries2 });
38758
38773
  } else {
38759
- const assignment = words.every((previous) => getArrayAssignment(previous) || scalarAssignmentName(previous)) ? elementAssignment(word, this.budget) : void 0;
38774
+ const assignment = words.every((previous) => getArrayAssignment(previous) || scalarAssignmentName(previous)) ? elementAssignment(word, this.budget, (source2) => parseArraySubscript(source2, this.budget, this.lexer.byteLocale, this.lexer.depth), (source2, start) => {
38775
+ const lexer = new Lexer2(this.budget, source2, this.lexer.depth, [], 0, this.lexer.byteLocale);
38776
+ lexer.position = start;
38777
+ lexer.word("]");
38778
+ return lexer.source[lexer.position] === "]" ? lexer.position : -1;
38779
+ }) : void 0;
38760
38780
  if (assignment) setArrayAssignment(word, assignment);
38761
38781
  }
38762
38782
  words.push(word);
@@ -38798,6 +38818,10 @@ function parseShell(source2, depth = 0, options3 = {}) {
38798
38818
  budget.admit();
38799
38819
  return { ...script, ...warnings.length ? { warnings } : {} };
38800
38820
  }
38821
+ function parseArraySubscript(source2, budget, byteLocale2 = false, depth = 0) {
38822
+ const lexer = new Lexer2(budget, source2, depth, [], 0, byteLocale2);
38823
+ return lexer.word("\0");
38824
+ }
38801
38825
  function parseBraceWord(source2, opaque, budget) {
38802
38826
  budget.admit(source2.length + 1);
38803
38827
  const lexer = new Lexer2(budget, source2, 0);
@@ -40947,26 +40971,77 @@ async function valueToken(owner, value2, signal) {
40947
40971
  }
40948
40972
  }
40949
40973
  var IndexedBinding = class _IndexedBinding {
40950
- constructor(owner) {
40974
+ constructor(owner, associative = false) {
40951
40975
  this.owner = owner;
40976
+ this.associative = associative;
40952
40977
  }
40953
40978
  owner;
40979
+ associative;
40954
40980
  values = /* @__PURE__ */ new Map();
40981
+ keys = /* @__PURE__ */ new Map();
40982
+ keyByIndex = /* @__PURE__ */ new Map();
40955
40983
  maximum = -1;
40956
40984
  generation = 0;
40957
40985
  version = 0;
40958
40986
  references = 1;
40959
40987
  assigned = false;
40960
- static create(parent) {
40988
+ static create(parent, associative = false) {
40961
40989
  const owner = ArrayOwner.create(parent.ledger, parent);
40962
40990
  try {
40963
40991
  owner.reserve({ wrappers: 1, metadata: 128, work: 7 });
40964
- return new _IndexedBinding(owner);
40992
+ return new _IndexedBinding(owner, associative);
40965
40993
  } catch (error) {
40966
40994
  void owner.close();
40967
40995
  throw error;
40968
40996
  }
40969
40997
  }
40998
+ async keyIdentity(value2, owner, signal) {
40999
+ const length = shellValueByteLength(value2);
41000
+ if (!length) throw new ArrayFailure("bad array subscript");
41001
+ const admission = owner.reserve({ payload: length * 5, metadata: 64 + length * 16, work: length + 2 });
41002
+ try {
41003
+ const bytes3 = shellValueBytes(value2);
41004
+ const parts = [];
41005
+ for (const byte of bytes3) {
41006
+ parts.push(byte.toString(16).padStart(2, "0"));
41007
+ await owner.ledger.checkpoint(signal);
41008
+ }
41009
+ return parts.join("");
41010
+ } finally {
41011
+ admission.release();
41012
+ }
41013
+ }
41014
+ async keyIndex(value2, owner, signal, create = false) {
41015
+ const identity2 = await this.keyIdentity(value2, owner, signal);
41016
+ const existing2 = this.keys.get(identity2);
41017
+ if (existing2 || !create) return existing2?.index;
41018
+ const index = this.maximum + 1;
41019
+ const admission = this.owner.reserve({ metadata: 128 + identity2.length * 2, work: 8 });
41020
+ let text3;
41021
+ try {
41022
+ text3 = await valueToken(this.owner, value2, signal);
41023
+ } catch (error) {
41024
+ admission.release();
41025
+ throw error;
41026
+ }
41027
+ const entry = { index, text: text3, admission };
41028
+ admission.cleanup = () => {
41029
+ if (this.keys.get(identity2) === entry) {
41030
+ this.keys.delete(identity2);
41031
+ this.keyByIndex.delete(index);
41032
+ }
41033
+ text3.release();
41034
+ };
41035
+ this.keys.set(identity2, entry);
41036
+ this.keyByIndex.set(index, identity2);
41037
+ this.maximum = index;
41038
+ return index;
41039
+ }
41040
+ remove(index) {
41041
+ this.values.get(index)?.slot.release();
41042
+ const identity2 = this.keyByIndex.get(index);
41043
+ if (identity2 !== void 0) this.keys.get(identity2)?.admission.release();
41044
+ }
40970
41045
  get(index) {
40971
41046
  return this.values.get(index)?.text.value;
40972
41047
  }
@@ -40993,13 +41068,30 @@ var IndexedBinding = class _IndexedBinding {
40993
41068
  previous?.slot.release();
40994
41069
  }
40995
41070
  async copy(signal) {
40996
- const copy2 = _IndexedBinding.create(this.owner.parent);
41071
+ const copy2 = _IndexedBinding.create(this.owner.parent, this.associative);
40997
41072
  copy2.assigned = this.assigned;
40998
41073
  this.retain();
40999
41074
  try {
41075
+ for (const [identity2, entry] of this.keys) {
41076
+ const admission = copy2.owner.reserve({ metadata: 128 + identity2.length * 2, work: identity2.length + 8 });
41077
+ copy2.owner.parent.adopt(entry.text.admission);
41078
+ const text3 = entry.text.retain();
41079
+ const cloned = { index: entry.index, text: text3, admission };
41080
+ admission.cleanup = () => {
41081
+ if (copy2.keys.get(identity2) === cloned) {
41082
+ copy2.keys.delete(identity2);
41083
+ copy2.keyByIndex.delete(entry.index);
41084
+ }
41085
+ text3.release();
41086
+ };
41087
+ copy2.keys.set(identity2, cloned);
41088
+ copy2.keyByIndex.set(entry.index, identity2);
41089
+ await copy2.owner.ledger.checkpoint(signal, identity2.length + 8);
41090
+ }
41000
41091
  for (const [index, element] of this.values) {
41001
41092
  copy2.owner.reserve({ work: 2 }).release();
41002
41093
  const slot = copy2.owner.reserve({ slots: 1, metadata: 32, work: 5 });
41094
+ copy2.owner.parent.adopt(element.text.admission);
41003
41095
  const text3 = element.text.retain();
41004
41096
  const cloned = { text: text3, slot };
41005
41097
  slot.cleanup = () => {
@@ -42053,6 +42145,7 @@ var shellBuiltinNames = /* @__PURE__ */ new Set([
42053
42145
  "local",
42054
42146
  "unset",
42055
42147
  "read",
42148
+ "declare",
42056
42149
  "mapfile",
42057
42150
  "readarray",
42058
42151
  "exit",
@@ -43394,7 +43487,7 @@ var Runtime = class _Runtime {
43394
43487
  }
43395
43488
  variable(state, name2) {
43396
43489
  const binding = arrayStore(state)?.get(name2);
43397
- return binding ? binding.get(0) : state.variables[name2];
43490
+ return binding ? binding.get(binding.associative ? binding.keys.get("30")?.index ?? -1 : 0) : state.variables[name2];
43398
43491
  }
43399
43492
  requireParameter(value2, name2, state, io, line) {
43400
43493
  if (state.nounset && value2 === void 0) throw new NounsetFailure(`${name2}: unbound variable`, io.diagnosticLine ?? line);
@@ -43530,7 +43623,7 @@ var Runtime = class _Runtime {
43530
43623
  if (key !== index && key > maximum) maximum = key;
43531
43624
  await operation2.ledger.checkpoint(this.signal, 2);
43532
43625
  }
43533
- if (typeof index === "number") staged.values.get(index)?.slot.release();
43626
+ if (typeof index === "number") staged.remove(index);
43534
43627
  staged.maximum = maximum;
43535
43628
  }
43536
43629
  this.signal.throwIfAborted();
@@ -43578,11 +43671,12 @@ var Runtime = class _Runtime {
43578
43671
  const current = store.get(name2);
43579
43672
  if (!current) throw new ArrayFailure("stale binding");
43580
43673
  staged = await current.copy(this.signal);
43581
- const previous = current.values.get(0)?.text.rawValue ?? current.get(0) ?? "";
43674
+ const index = staged.associative ? await staged.keyIndex("0", operation2, this.signal, true) : 0;
43675
+ const previous = current.values.get(index)?.text.rawValue ?? current.get(index) ?? "";
43582
43676
  const value2 = !append ? expanded : typeof previous === "string" && typeof expanded === "string" ? await this.arrayJoin(operation2, [previous, expanded], "") : concatShellValues([previous, expanded], valueAllocation);
43583
43677
  const token = await valueToken(staged.owner, value2, this.signal);
43584
43678
  try {
43585
- staged.insert(0, token);
43679
+ staged.insert(index, token);
43586
43680
  } catch (error) {
43587
43681
  token.release();
43588
43682
  throw error;
@@ -43627,10 +43721,33 @@ var Runtime = class _Runtime {
43627
43721
  await owner.ledger.checkpoint(this.signal);
43628
43722
  return result2;
43629
43723
  }
43724
+ async arrayIndex(binding, index, state, io, owner, create = false) {
43725
+ if (!binding?.associative) {
43726
+ const selected = index.source === void 0 ? index : literalIndex(index.source, 0, this.budget.parsing);
43727
+ const number5 = numericIndex(selected);
43728
+ if (number5 === void 0) throw new ArrayFailure("index outside 0..2147483647");
43729
+ return number5;
43730
+ }
43731
+ const word = index.word ?? parseArraySubscript(index.source ?? index.decimal, this.budget.parsing, byteLocale(state.variables), state.depth);
43732
+ const fields3 = await this.valueWord(word, state, io, false);
43733
+ const value2 = fields3.length === 1 ? fields3[0] : concatShellValues(fields3, io[valueScope]);
43734
+ if (shellValueByteLength(value2) === 0) {
43735
+ await this.diagnostic(io, "associative array: bad array subscript");
43736
+ if (create) throw completedExit(1);
43737
+ return void 0;
43738
+ }
43739
+ return binding.keyIndex(value2, owner, this.signal, create);
43740
+ }
43630
43741
  async arrayAssignment(assignment, state, io) {
43631
43742
  const name2 = assignment.name;
43632
43743
  this.signal.throwIfAborted();
43633
- if (state.readonlyVariables?.has(name2)) throw new ArrayFailure("readonly binding");
43744
+ if (state.readonlyVariables?.has(name2)) {
43745
+ if (arrayStore(state)?.get(name2)?.associative) {
43746
+ await this.diagnostic(io, `${name2}: readonly variable`);
43747
+ throw completedExit(1);
43748
+ }
43749
+ throw new ArrayFailure("readonly binding");
43750
+ }
43634
43751
  if (controlNames.has(name2)) throw new ArrayFailure("control binding cannot be indexed");
43635
43752
  if (state.exported.has(name2)) throw new ArrayFailure("exported binding cannot be indexed");
43636
43753
  const store = requireArrays(state);
@@ -43640,11 +43757,12 @@ var Runtime = class _Runtime {
43640
43757
  try {
43641
43758
  const watch = await store.watch(name2, operation2, this.signal);
43642
43759
  const current = store.get(name2);
43760
+ if (current?.associative && assignment.kind === "compound" && assignment.entries.length) throw new ArrayFailure("nonempty associative compound assignment is unsupported");
43643
43761
  const initialMaximum = current?.maximum ?? (state.variables[name2] === void 0 ? -1 : 0);
43644
43762
  let planned = assignment.append && assignment.kind === "compound" ? initialMaximum + 1 : 0;
43645
43763
  if (assignment.kind === "element") {
43646
43764
  operation2.reserve({ work: assignment.index.decimal.length + 1 }).release();
43647
- if (numericIndex(assignment.index) === void 0) throw new ArrayFailure("index outside 0..2147483647");
43765
+ if (!current?.associative && numericIndex(literalIndex(assignment.index.source ?? assignment.index.decimal, 0, this.budget.parsing)) === void 0) throw new ArrayFailure("index outside 0..2147483647");
43648
43766
  } else for (const entry of assignment.entries) {
43649
43767
  operation2.reserve({ work: entry.value.parts.length + (entry.index?.decimal.length ?? 0) + 2 }).release();
43650
43768
  if (entry.index) {
@@ -43670,7 +43788,7 @@ var Runtime = class _Runtime {
43670
43788
  const tickets = operation2.reserve({ generation: true, version: true, epoch: true, work: 8 });
43671
43789
  const prepared = await store.prepareName(name2, operation2, this.signal);
43672
43790
  const preserve = assignment.kind === "element" || assignment.append;
43673
- staged = preserve && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner);
43791
+ staged = preserve && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner, current?.associative);
43674
43792
  if (preserve && !current && state.variables[name2] !== void 0) {
43675
43793
  const token = await valueToken(staged.owner, stateMonitor(state)?.values.get(name2, state.variables[name2]) ?? state.variables[name2], this.signal);
43676
43794
  try {
@@ -43695,7 +43813,7 @@ var Runtime = class _Runtime {
43695
43813
  };
43696
43814
  const join = async (values) => values.every((value2) => typeof value2 === "string") ? this.arrayJoin(operation2, values, "") : concatShellValues(values, io[valueScope]);
43697
43815
  if (assignment.kind === "element") {
43698
- const index = numericIndex(assignment.index);
43816
+ const index = await this.arrayIndex(staged, assignment.index, state, io, operation2, true);
43699
43817
  const fields3 = await this.valueWord(assignment.value, state, io, false);
43700
43818
  let value2 = await join(fields3);
43701
43819
  if (assignment.append) value2 = await join([staged.values.get(index)?.text.rawValue ?? staged.get(index) ?? "", value2]);
@@ -46423,6 +46541,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46423
46541
  let releaseHolding;
46424
46542
  try {
46425
46543
  const options3 = await mapfileOptions(context2, work, allocation);
46544
+ if (arrayStore(state)?.get(options3.name)?.associative) throw new MapfileUsageError(`${options3.name}: not an indexed array`, 1);
46426
46545
  if (state.readonlyVariables?.has(options3.name)) throw new MapfileUsageError(`${options3.name}: readonly variable`, 1);
46427
46546
  if (controlNames.has(options3.name) || state.exported.has(options3.name)) throw new ArrayFailure("control or exported binding cannot be indexed");
46428
46547
  const store = requireArrays(state);
@@ -46593,9 +46712,16 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46593
46712
  this.replacePositionals(state, this.positionalValues(state).slice(count2));
46594
46713
  return 0;
46595
46714
  }
46596
- if (command7 === "export" || command7 === "local" || command7 === "readonly") {
46715
+ if (command7 === "export" || command7 === "local" || command7 === "readonly" || command7 === "declare") {
46716
+ const associativeDeclaration = command7 === "declare";
46597
46717
  const declarationArgs = [...args];
46598
- let indexedLocal = false;
46718
+ let indexedLocal = associativeDeclaration;
46719
+ if (associativeDeclaration) {
46720
+ if (declarationArgs.shift() !== "-A" || !declarationArgs.length) {
46721
+ await this.diagnostic(context2, "declare: only -A NAME is supported");
46722
+ return 2;
46723
+ }
46724
+ }
46599
46725
  if (command7 === "local") {
46600
46726
  const options3 = localDeclarationOptions(declarationArgs, this.signal);
46601
46727
  if (options3.error !== void 0) {
@@ -46658,11 +46784,11 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46658
46784
  status2 = 1;
46659
46785
  continue;
46660
46786
  }
46661
- if (command7 === "local" && indexedLocal) {
46787
+ if ((command7 === "local" || associativeDeclaration) && indexedLocal) {
46662
46788
  if (controlNames.has(name2)) throw new ArrayFailure("control binding cannot be indexed");
46663
46789
  if (state.exported.has(name2)) throw new ArrayFailure("exported binding cannot be indexed");
46664
- const existingLocal = locals.get(name2);
46665
- const saved = existingLocal ? void 0 : assignments2.get(name2) ?? saveVariable(state, name2);
46790
+ const existingLocal = locals?.get(name2);
46791
+ const saved = !locals || existingLocal ? void 0 : assignments2.get(name2) ?? saveVariable(state, name2);
46666
46792
  let operation2;
46667
46793
  let holding;
46668
46794
  let shadow;
@@ -46687,12 +46813,14 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46687
46813
  const tickets = operation2.reserve({ generation: true, version: true, epoch: true, work: 8 });
46688
46814
  const prepared = await store.prepareName(name2, operation2, this.signal);
46689
46815
  const current = store.get(name2);
46690
- shadow = !saved && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner);
46816
+ if (!saved && current && current.associative !== associativeDeclaration) throw new ArrayFailure("cannot convert array kind");
46817
+ shadow = !saved && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner, associativeDeclaration);
46691
46818
  const value2 = match[2] ?? (!saved && !current && Object.hasOwn(state.variables, name2) ? state.variables[name2] : void 0);
46692
46819
  if (value2 !== void 0) {
46693
- const token = await textToken(shadow.owner, value2, this.signal);
46820
+ const retained = associativeDeclaration && match[2] === void 0 ? stateMonitor(state).values.get(name2, value2) : value2;
46821
+ const token = await valueToken(shadow.owner, retained, this.signal);
46694
46822
  try {
46695
- shadow.insert(0, token);
46823
+ shadow.insert(associativeDeclaration ? await shadow.keyIndex("0", operation2, this.signal, true) : 0, token);
46696
46824
  } catch (error) {
46697
46825
  token.release();
46698
46826
  throw error;
@@ -46840,7 +46968,8 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46840
46968
  }
46841
46969
  if (command7 === "unset") {
46842
46970
  let status2 = 0;
46843
- for (const name2 of args) {
46971
+ for (let argument2 = 0; argument2 < args.length; argument2++) {
46972
+ const name2 = args[argument2];
46844
46973
  const selected = /^([a-zA-Z_][a-zA-Z_0-9]*)\[(.*)\]$/su.exec(name2);
46845
46974
  if (selected) {
46846
46975
  const base = selected[1];
@@ -46850,7 +46979,12 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46850
46979
  status2 = 1;
46851
46980
  continue;
46852
46981
  }
46853
- if (selector === "@" || selector === "*") await this.unsetIndexed(state, base, "members");
46982
+ const binding = arrayStore(state)?.get(base);
46983
+ if (binding?.associative) {
46984
+ const original = getCommandArguments(context2).values[argument2];
46985
+ const index = typeof original === "string" ? await this.arrayIndex(binding, { decimal: selector, source: selector }, state, context2, binding.owner) : await binding.keyIndex(shellValueFromBytes(shellValueBytes(original, context2[valueScope]).subarray(base.length + 1, shellValueByteLength(original) - 1), context2[valueScope]), binding.owner, this.signal);
46986
+ if (index !== void 0) await this.unsetIndexed(state, base, index);
46987
+ } else if (selector === "@" || selector === "*") await this.unsetIndexed(state, base, "members");
46854
46988
  else {
46855
46989
  let index;
46856
46990
  try {
@@ -47084,7 +47218,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47084
47218
  if (part.kind === "variable" && part.transform) {
47085
47219
  const selector2 = getArraySelector(part);
47086
47220
  if (selector2?.kind === "members" || part.name === "@" || part.name === "*") {
47087
- const members = selector2 ? await this.arrayMembers(part.name, state, io) : this.positionalValues(state);
47221
+ const members = selector2 ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
47088
47222
  const ifs = state.variables.IFS ?? " ";
47089
47223
  const separator = ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
47090
47224
  const fragments = [];
@@ -47124,7 +47258,18 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47124
47258
  const binding = part.kind === "variable" ? arrayStore(state)?.get(part.name) : void 0;
47125
47259
  const holding = binding ? requireArrays(state).owner.hold() : void 0;
47126
47260
  const selector = getArraySelector(part);
47127
- const index = selector?.kind === "element" ? numericIndex(selector.index) : 0;
47261
+ if (selector?.kind === "element" && binding?.associative) {
47262
+ try {
47263
+ const index2 = await this.arrayIndex(binding, selector.index, state, io, binding.owner);
47264
+ const token2 = index2 === void 0 ? void 0 : binding.values.get(index2)?.text;
47265
+ this.requireParameter(token2?.value, part.kind === "variable" ? part.name : "", state, io);
47266
+ if (part.kind === "variable" && part.length) return this.parameterLength(token2?.value ?? "");
47267
+ return token2?.rawValue ? shellValueFromBytes(shellValueBytes(token2.rawValue, io[valueScope]), io[valueScope]) : token2?.value ?? "";
47268
+ } finally {
47269
+ holding?.release();
47270
+ }
47271
+ }
47272
+ const index = selector?.kind === "element" ? numericIndex(selector.index) : binding?.associative ? binding.keys.get("30")?.index : 0;
47128
47273
  const token = index === void 0 || selector?.kind === "members" ? void 0 : binding?.values.get(index)?.text;
47129
47274
  token?.retain();
47130
47275
  try {
@@ -47246,7 +47391,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47246
47391
  return part.length ? this.parameterLength(value3 ?? "") : value3 ?? "";
47247
47392
  }
47248
47393
  if (part.length) return String(binding?.values.size ?? (state.variables[part.name] === void 0 ? 0 : 1));
47249
- const values = await this.arrayMembers(part.name, state, io);
47394
+ const values = await this.arrayMembers(part.name, state, io, part.keys);
47250
47395
  const separator = Array.from(state.variables.IFS ?? " ")[0] ?? "";
47251
47396
  if (values.every((value3) => typeof value3 === "string")) return this.arrayJoin(store.owner, values, separator);
47252
47397
  io[valueScope]?.reserve(values.length * 32 + 64, 0);
@@ -47659,7 +47804,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47659
47804
  emptyNameGroups.add(quoteGroup);
47660
47805
  }
47661
47806
  } else if (part.kind === "variable" && split4 && (selector?.kind === "members" && !part.length && (!part.quoted || selector.separator === "@") || part.transform && part.name === "@")) {
47662
- const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state, io) : this.positionalValues(state);
47807
+ const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
47663
47808
  for (let position = 0; position < members.length; position++) {
47664
47809
  if (position > 0) addField();
47665
47810
  const original = members[position];
@@ -47801,7 +47946,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47801
47946
  for (const value2 of values) if (typeof value2 !== "string") allocation.hold(value2);
47802
47947
  return createCommandArguments(values, allocation);
47803
47948
  }
47804
- async arrayMembers(name2, state, io) {
47949
+ async arrayMembers(name2, state, io, keys = false) {
47805
47950
  const store = requireArrays(state);
47806
47951
  const holding = store.owner.hold();
47807
47952
  try {
@@ -47812,7 +47957,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47812
47957
  if (value2 === void 0) return [];
47813
47958
  store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
47814
47959
  await textToken(store.owner, value2, this.signal);
47815
- return [value2];
47960
+ return [keys ? "0" : value2];
47816
47961
  }
47817
47962
  binding.retain();
47818
47963
  try {
@@ -47820,7 +47965,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47820
47965
  const values = [];
47821
47966
  for (const index of indices) {
47822
47967
  store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 4 });
47823
- const token = binding.values.get(index).text;
47968
+ 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;
47824
47969
  const value2 = token.rawValue === void 0 ? token.value : shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]);
47825
47970
  if (typeof value2 === "string") await textToken(store.owner, value2, this.signal);
47826
47971
  values.push(value2);