@poe-platform/safe-bash 0.1.527 → 0.1.529

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);
@@ -40879,14 +40903,16 @@ var controlNames = /* @__PURE__ */ new Set([
40879
40903
  "LC_CTYPE"
40880
40904
  ]);
40881
40905
  var OwnedText = class {
40882
- constructor(value2, bytes3, admission) {
40906
+ constructor(value2, bytes3, admission, rawValue) {
40883
40907
  this.value = value2;
40884
40908
  this.bytes = bytes3;
40885
40909
  this.admission = admission;
40910
+ this.rawValue = rawValue;
40886
40911
  }
40887
40912
  value;
40888
40913
  bytes;
40889
40914
  admission;
40915
+ rawValue;
40890
40916
  references = 1;
40891
40917
  retain() {
40892
40918
  if (this.references === Number.MAX_SAFE_INTEGER) throw new ArrayFailure("reference capacity is not representable");
@@ -40912,27 +40938,110 @@ async function textToken(owner, value2, signal) {
40912
40938
  const admission = owner.reserve({ payload: bytes3, metadata: 32, work: 4 });
40913
40939
  return new OwnedText(value2, bytes3, admission);
40914
40940
  }
40941
+ async function valueToken(owner, value2, signal) {
40942
+ if (typeof value2 === "string") return textToken(owner, value2, signal);
40943
+ const bytes3 = shellValueByteLength(value2);
40944
+ const temporary = owner.reserve({ payload: bytes3 * 3, metadata: 64, work: 4 });
40945
+ try {
40946
+ const input3 = shellValueBytes(value2);
40947
+ const decoder2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
40948
+ let text3 = "";
40949
+ for (let offset = 0; offset < bytes3; offset += 1024) {
40950
+ const end = Math.min(bytes3, offset + 1024);
40951
+ owner.reserve({ work: end - offset }).release();
40952
+ await owner.ledger.checkpoint(signal, end - offset);
40953
+ if (text3 !== void 0) try {
40954
+ text3 += decoder2.decode(input3.subarray(offset, end), { stream: end < bytes3 });
40955
+ } catch (error) {
40956
+ if (!(error instanceof TypeError) || "code" in error && error.code !== "ERR_ENCODING_INVALID_ENCODED_DATA") throw error;
40957
+ text3 = void 0;
40958
+ }
40959
+ }
40960
+ if (text3 !== void 0) return await textToken(owner, text3, signal);
40961
+ const admission = owner.reserve({ payload: bytes3 * 4, metadata: 128, work: 4 });
40962
+ try {
40963
+ const raw = shellValueFromBytes(input3);
40964
+ return new OwnedText(shellValueText(raw), bytes3, admission, raw);
40965
+ } catch (error) {
40966
+ admission.release();
40967
+ throw error;
40968
+ }
40969
+ } finally {
40970
+ temporary.release();
40971
+ }
40972
+ }
40915
40973
  var IndexedBinding = class _IndexedBinding {
40916
- constructor(owner) {
40974
+ constructor(owner, associative = false) {
40917
40975
  this.owner = owner;
40976
+ this.associative = associative;
40918
40977
  }
40919
40978
  owner;
40979
+ associative;
40920
40980
  values = /* @__PURE__ */ new Map();
40981
+ keys = /* @__PURE__ */ new Map();
40982
+ keyByIndex = /* @__PURE__ */ new Map();
40921
40983
  maximum = -1;
40922
40984
  generation = 0;
40923
40985
  version = 0;
40924
40986
  references = 1;
40925
40987
  assigned = false;
40926
- static create(parent) {
40988
+ static create(parent, associative = false) {
40927
40989
  const owner = ArrayOwner.create(parent.ledger, parent);
40928
40990
  try {
40929
40991
  owner.reserve({ wrappers: 1, metadata: 128, work: 7 });
40930
- return new _IndexedBinding(owner);
40992
+ return new _IndexedBinding(owner, associative);
40931
40993
  } catch (error) {
40932
40994
  void owner.close();
40933
40995
  throw error;
40934
40996
  }
40935
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
+ }
40936
41045
  get(index) {
40937
41046
  return this.values.get(index)?.text.value;
40938
41047
  }
@@ -40959,13 +41068,30 @@ var IndexedBinding = class _IndexedBinding {
40959
41068
  previous?.slot.release();
40960
41069
  }
40961
41070
  async copy(signal) {
40962
- const copy2 = _IndexedBinding.create(this.owner.parent);
41071
+ const copy2 = _IndexedBinding.create(this.owner.parent, this.associative);
40963
41072
  copy2.assigned = this.assigned;
40964
41073
  this.retain();
40965
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
+ }
40966
41091
  for (const [index, element] of this.values) {
40967
41092
  copy2.owner.reserve({ work: 2 }).release();
40968
41093
  const slot = copy2.owner.reserve({ slots: 1, metadata: 32, work: 5 });
41094
+ copy2.owner.parent.adopt(element.text.admission);
40969
41095
  const text3 = element.text.retain();
40970
41096
  const cloned = { text: text3, slot };
40971
41097
  slot.cleanup = () => {
@@ -41148,6 +41274,116 @@ var BindingStore = class _BindingStore {
41148
41274
  }
41149
41275
  };
41150
41276
 
41277
+ // packages/safe-bash/src/shell/mapfile.ts
41278
+ init_platform();
41279
+ var MapfileUsageError = class extends Error {
41280
+ constructor(message3, status2) {
41281
+ super(message3);
41282
+ this.status = status2;
41283
+ }
41284
+ status;
41285
+ };
41286
+ async function mapfileOptions(context2, work, allocation) {
41287
+ const options3 = { name: "MAPFILE", strip: false, delimiter: 10, count: 0, skip: 0, origin: 0, preserve: false, callback: void 0, quantum: 5e3 };
41288
+ const args = getCommandArguments(context2);
41289
+ let cursor = 0;
41290
+ const numeric4 = async (text3, maximum) => {
41291
+ let value2 = 0;
41292
+ let index = 0;
41293
+ const whitespace2 = (code) => code === 32 || code >= 9 && code <= 13;
41294
+ while (index < text3.length && whitespace2(text3.charCodeAt(index))) {
41295
+ const pending = stringCheckpoint(work);
41296
+ if (pending) await pending;
41297
+ index++;
41298
+ }
41299
+ const negative = text3[index] === "-";
41300
+ if (negative || text3[index] === "+") index++;
41301
+ let digits = 0;
41302
+ for (; index < text3.length; index++) {
41303
+ const pending = stringCheckpoint(work);
41304
+ if (pending) await pending;
41305
+ const digit3 = text3.charCodeAt(index) - 48;
41306
+ if (digit3 < 0 || digit3 > 9) break;
41307
+ if (value2 > Math.floor((maximum - digit3) / 10)) throw new MapfileUsageError(`${text3}: invalid number`, 1);
41308
+ value2 = value2 * 10 + digit3;
41309
+ digits++;
41310
+ }
41311
+ for (; index < text3.length; index++) {
41312
+ const pending = stringCheckpoint(work);
41313
+ if (pending) await pending;
41314
+ if (!whitespace2(text3.charCodeAt(index))) throw new MapfileUsageError(`${text3}: invalid number`, 1);
41315
+ }
41316
+ if (!digits || negative && value2 !== 0) throw new MapfileUsageError(`${text3}: invalid number`, 1);
41317
+ return value2;
41318
+ };
41319
+ while (cursor < context2.args.length) {
41320
+ const option = context2.args[cursor];
41321
+ if (option === "--") {
41322
+ cursor++;
41323
+ break;
41324
+ }
41325
+ if (!option.startsWith("-") || option === "-") break;
41326
+ cursor++;
41327
+ for (let index = 1; index < option.length; index++) {
41328
+ const pending = stringCheckpoint(work);
41329
+ if (pending) await pending;
41330
+ const flag = option[index];
41331
+ if (flag === "t") {
41332
+ options3.strip = true;
41333
+ continue;
41334
+ }
41335
+ if (!"dnsOCc".includes(flag)) throw new MapfileUsageError(`-${flag}: invalid option`, 2);
41336
+ const attached = index + 1 < option.length;
41337
+ const operandIndex = attached ? cursor - 1 : cursor++;
41338
+ const operand = attached ? option.slice(index + 1) : context2.args[operandIndex];
41339
+ if (operand === void 0) throw new MapfileUsageError(`-${flag}: option requires an argument`, 2);
41340
+ if (flag === "d") {
41341
+ const bytes3 = shellValueBytes(args.values[operandIndex], allocation);
41342
+ options3.delimiter = bytes3[attached ? index + 1 : 0] ?? 0;
41343
+ } else if (flag === "C") options3.callback = operand;
41344
+ else {
41345
+ const value2 = await numeric4(operand, flag === "O" ? 2147483647 : Number.MAX_SAFE_INTEGER);
41346
+ if (flag === "c") {
41347
+ if (!value2) throw new MapfileUsageError("0: invalid callback quantum", 1);
41348
+ options3.quantum = value2;
41349
+ } else if (flag === "n") options3.count = value2;
41350
+ else if (flag === "s") options3.skip = value2;
41351
+ else {
41352
+ options3.origin = value2;
41353
+ options3.preserve = true;
41354
+ }
41355
+ }
41356
+ break;
41357
+ }
41358
+ }
41359
+ options3.name = context2.args[cursor] ?? "MAPFILE";
41360
+ if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(options3.name)) throw new MapfileUsageError(`${options3.name}: invalid identifier`, 1);
41361
+ return options3;
41362
+ }
41363
+ async function collectMapfile(options3, input3, hooks) {
41364
+ let skipped = 0;
41365
+ let written = 0;
41366
+ while (!options3.count || written < options3.count) {
41367
+ hooks.loop();
41368
+ const allocation = hooks.allocation();
41369
+ try {
41370
+ const record4 = await input3.mapfileRecord(options3.delimiter, options3.strip, allocation);
41371
+ if (!record4.present) break;
41372
+ if (skipped < options3.skip) {
41373
+ skipped++;
41374
+ continue;
41375
+ }
41376
+ const index = options3.origin + written;
41377
+ if (index > 2147483647) throw new MapfileUsageError("array index exceeds 2147483647", 1);
41378
+ if (options3.callback !== void 0 && (written + 1) % options3.quantum === 0) await hooks.callback(options3.callback, index, record4.value);
41379
+ await hooks.write(index, record4.value);
41380
+ written++;
41381
+ } finally {
41382
+ allocation.close();
41383
+ }
41384
+ }
41385
+ }
41386
+
41151
41387
  // packages/safe-bash/src/shell/arrays/state.ts
41152
41388
  init_platform();
41153
41389
  var sessions = /* @__PURE__ */ new WeakMap();
@@ -41909,6 +42145,9 @@ var shellBuiltinNames = /* @__PURE__ */ new Set([
41909
42145
  "local",
41910
42146
  "unset",
41911
42147
  "read",
42148
+ "declare",
42149
+ "mapfile",
42150
+ "readarray",
41912
42151
  "exit",
41913
42152
  "return",
41914
42153
  "break",
@@ -42907,6 +43146,7 @@ var InvocationCancellationOwner = class {
42907
43146
  this.#failures.push(...result2.failures);
42908
43147
  }
42909
43148
  };
43149
+ var mapfileCallbackStates = /* @__PURE__ */ new WeakSet();
42910
43150
  var Runtime = class _Runtime {
42911
43151
  constructor(fs, commands, middleware, budget, signal = budget.signal, fileWrites = /* @__PURE__ */ new Map(), outputFiles = /* @__PURE__ */ new Map(), commandSignal = signal, cancellation, cancellationState, cancellationOwner, cancellationDepth, cancellationMaxDepth, outcomeFrame = void 0, inputProfile = fs) {
42912
43152
  this.fs = fs;
@@ -43247,7 +43487,7 @@ var Runtime = class _Runtime {
43247
43487
  }
43248
43488
  variable(state, name2) {
43249
43489
  const binding = arrayStore(state)?.get(name2);
43250
- 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];
43251
43491
  }
43252
43492
  requireParameter(value2, name2, state, io, line) {
43253
43493
  if (state.nounset && value2 === void 0) throw new NounsetFailure(`${name2}: unbound variable`, io.diagnosticLine ?? line);
@@ -43257,7 +43497,7 @@ var Runtime = class _Runtime {
43257
43497
  this.writeVariable(state, name2, value2, origin);
43258
43498
  return;
43259
43499
  }
43260
- await this.arrayZero(state, name2, async () => shellValueText(value2));
43500
+ await this.arrayZero(state, name2, async () => value2);
43261
43501
  }
43262
43502
  async prepareVariable(state, name2, saved, scalarLegacy = false) {
43263
43503
  const store = requireArrays(state);
@@ -43383,7 +43623,7 @@ var Runtime = class _Runtime {
43383
43623
  if (key !== index && key > maximum) maximum = key;
43384
43624
  await operation2.ledger.checkpoint(this.signal, 2);
43385
43625
  }
43386
- if (typeof index === "number") staged.values.get(index)?.slot.release();
43626
+ if (typeof index === "number") staged.remove(index);
43387
43627
  staged.maximum = maximum;
43388
43628
  }
43389
43629
  this.signal.throwIfAborted();
@@ -43414,6 +43654,7 @@ var Runtime = class _Runtime {
43414
43654
  const store = requireArrays(state);
43415
43655
  const operation2 = ArrayOwner.create(store.owner.ledger, store.owner);
43416
43656
  const holding = store.owner.hold();
43657
+ const valueAllocation = this.budget.values.scope();
43417
43658
  let staged;
43418
43659
  try {
43419
43660
  const watch = await store.watch(name2, operation2, this.signal);
@@ -43430,10 +43671,12 @@ var Runtime = class _Runtime {
43430
43671
  const current = store.get(name2);
43431
43672
  if (!current) throw new ArrayFailure("stale binding");
43432
43673
  staged = await current.copy(this.signal);
43433
- const value2 = append ? await this.arrayJoin(operation2, [current.get(0) ?? "", expanded], "") : expanded;
43434
- const token = await textToken(staged.owner, value2, this.signal);
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) ?? "";
43676
+ const value2 = !append ? expanded : typeof previous === "string" && typeof expanded === "string" ? await this.arrayJoin(operation2, [previous, expanded], "") : concatShellValues([previous, expanded], valueAllocation);
43677
+ const token = await valueToken(staged.owner, value2, this.signal);
43435
43678
  try {
43436
- staged.insert(0, token);
43679
+ staged.insert(index, token);
43437
43680
  } catch (error) {
43438
43681
  token.release();
43439
43682
  throw error;
@@ -43459,6 +43702,7 @@ var Runtime = class _Runtime {
43459
43702
  await staged?.release();
43460
43703
  await operation2.close();
43461
43704
  } finally {
43705
+ valueAllocation.close();
43462
43706
  holding.release();
43463
43707
  }
43464
43708
  }
@@ -43477,10 +43721,33 @@ var Runtime = class _Runtime {
43477
43721
  await owner.ledger.checkpoint(this.signal);
43478
43722
  return result2;
43479
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
+ }
43480
43741
  async arrayAssignment(assignment, state, io) {
43481
43742
  const name2 = assignment.name;
43482
43743
  this.signal.throwIfAborted();
43483
- 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
+ }
43484
43751
  if (controlNames.has(name2)) throw new ArrayFailure("control binding cannot be indexed");
43485
43752
  if (state.exported.has(name2)) throw new ArrayFailure("exported binding cannot be indexed");
43486
43753
  const store = requireArrays(state);
@@ -43490,11 +43757,12 @@ var Runtime = class _Runtime {
43490
43757
  try {
43491
43758
  const watch = await store.watch(name2, operation2, this.signal);
43492
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");
43493
43761
  const initialMaximum = current?.maximum ?? (state.variables[name2] === void 0 ? -1 : 0);
43494
43762
  let planned = assignment.append && assignment.kind === "compound" ? initialMaximum + 1 : 0;
43495
43763
  if (assignment.kind === "element") {
43496
43764
  operation2.reserve({ work: assignment.index.decimal.length + 1 }).release();
43497
- 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");
43498
43766
  } else for (const entry of assignment.entries) {
43499
43767
  operation2.reserve({ work: entry.value.parts.length + (entry.index?.decimal.length ?? 0) + 2 }).release();
43500
43768
  if (entry.index) {
@@ -43520,9 +43788,9 @@ var Runtime = class _Runtime {
43520
43788
  const tickets = operation2.reserve({ generation: true, version: true, epoch: true, work: 8 });
43521
43789
  const prepared = await store.prepareName(name2, operation2, this.signal);
43522
43790
  const preserve = assignment.kind === "element" || assignment.append;
43523
- 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);
43524
43792
  if (preserve && !current && state.variables[name2] !== void 0) {
43525
- const token = await textToken(staged.owner, state.variables[name2], this.signal);
43793
+ const token = await valueToken(staged.owner, stateMonitor(state)?.values.get(name2, state.variables[name2]) ?? state.variables[name2], this.signal);
43526
43794
  try {
43527
43795
  staged.insert(0, token);
43528
43796
  } catch (error) {
@@ -43534,7 +43802,7 @@ var Runtime = class _Runtime {
43534
43802
  let cursor = assignment.append && assignment.kind === "compound" ? initialMaximum + 1 : 0;
43535
43803
  const insert = async (index, value2) => {
43536
43804
  if (index > 2147483647) throw new ArrayFailure("index outside 0..2147483647");
43537
- const token = await textToken(staged.owner, value2, this.signal);
43805
+ const token = await valueToken(staged.owner, value2, this.signal);
43538
43806
  try {
43539
43807
  staged.insert(index, token);
43540
43808
  } catch (error) {
@@ -43543,11 +43811,12 @@ var Runtime = class _Runtime {
43543
43811
  }
43544
43812
  writes++;
43545
43813
  };
43814
+ const join = async (values) => values.every((value2) => typeof value2 === "string") ? this.arrayJoin(operation2, values, "") : concatShellValues(values, io[valueScope]);
43546
43815
  if (assignment.kind === "element") {
43547
- const index = numericIndex(assignment.index);
43548
- const fields3 = await this.word(assignment.value, state, io, false);
43549
- let value2 = await this.arrayJoin(operation2, fields3, "");
43550
- if (assignment.append) value2 = await this.arrayJoin(operation2, [staged.get(index) ?? "", value2], "");
43816
+ const index = await this.arrayIndex(staged, assignment.index, state, io, operation2, true);
43817
+ const fields3 = await this.valueWord(assignment.value, state, io, false);
43818
+ let value2 = await join(fields3);
43819
+ if (assignment.append) value2 = await join([staged.values.get(index)?.text.rawValue ?? staged.get(index) ?? "", value2]);
43551
43820
  await insert(index, value2);
43552
43821
  } else for (const entry of assignment.entries) {
43553
43822
  const original = compoundEntryWords.get(entry);
@@ -43558,16 +43827,16 @@ var Runtime = class _Runtime {
43558
43827
  expandedEntry = true;
43559
43828
  const values = await this.valueWord(expanded, state, io, true, false, false, false, void 0, false);
43560
43829
  for (const value2 of values) {
43561
- await insert(cursor, shellValueText(value2));
43830
+ await insert(cursor, value2);
43562
43831
  cursor++;
43563
43832
  }
43564
43833
  }
43565
43834
  if (expandedEntry) continue;
43566
43835
  }
43567
- const fields3 = await this.word(entry.value, state, io, entry.index === void 0);
43836
+ const fields3 = await this.valueWord(entry.value, state, io, entry.index === void 0);
43568
43837
  if (entry.index) {
43569
43838
  const index = numericIndex(entry.index);
43570
- await insert(index, await this.arrayJoin(operation2, fields3, ""));
43839
+ await insert(index, await join(fields3));
43571
43840
  cursor = index + 1;
43572
43841
  } else for (const value2 of fields3) {
43573
43842
  await insert(cursor, value2);
@@ -44458,8 +44727,8 @@ var Runtime = class _Runtime {
44458
44727
  throw new ArrayFailure("indexed binding cannot be a command prefix");
44459
44728
  }
44460
44729
  await this.arrayZero(state, assignment.name, async () => {
44461
- const fields3 = await this.word(assignment.value, state, io, false);
44462
- return this.arrayJoin(requireArrays(state).owner, fields3, "");
44730
+ const fields3 = await this.valueWord(assignment.value, state, io, false);
44731
+ return fields3.every((value3) => typeof value3 === "string") ? this.arrayJoin(requireArrays(state).owner, fields3, "") : concatShellValues(fields3, io[valueScope]);
44463
44732
  }, assignment.append);
44464
44733
  continue;
44465
44734
  }
@@ -44778,6 +45047,8 @@ var Runtime = class _Runtime {
44778
45047
  functionRestoration?.close();
44779
45048
  throw error;
44780
45049
  }
45050
+ const callerLoopDepth = state.loopDepth;
45051
+ if (mapfileCallbackStates.has(state)) state.loopDepth = 0;
44781
45052
  try {
44782
45053
  return { exitCode: await this.command(body, state, { ...io, ...context3, scriptName: body.sourceName ?? io.scriptName ?? "shell" }) };
44783
45054
  } catch (error) {
@@ -44791,6 +45062,7 @@ var Runtime = class _Runtime {
44791
45062
  savedPositionals.close();
44792
45063
  state.positionalSetVersion = positionalSetVersion;
44793
45064
  state.functionDepth--;
45065
+ state.loopDepth = callerLoopDepth;
44794
45066
  state.depth--;
44795
45067
  state.locals.pop();
44796
45068
  };
@@ -46262,12 +46534,120 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46262
46534
  allocation.close();
46263
46535
  }
46264
46536
  }
46537
+ async mapfileBuiltin(context2, state) {
46538
+ const allocation = this.budget.values.scope();
46539
+ const work = { remaining: this.budget.limits.maxExpansionBytes * 8, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
46540
+ let pinned;
46541
+ let releaseHolding;
46542
+ try {
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);
46545
+ if (state.readonlyVariables?.has(options3.name)) throw new MapfileUsageError(`${options3.name}: readonly variable`, 1);
46546
+ if (controlNames.has(options3.name) || state.exported.has(options3.name)) throw new ArrayFailure("control or exported binding cannot be indexed");
46547
+ const store = requireArrays(state);
46548
+ const holding = store.owner.hold();
46549
+ releaseHolding = () => holding.release();
46550
+ if (!options3.preserve || !store.get(options3.name)) await this.arrayAssignment({ kind: "compound", name: options3.name, append: options3.preserve, entries: [] }, state, context2);
46551
+ const entry = store.bindings.get(options3.name);
46552
+ allocation.reserve(128 + import_buffer.Buffer.byteLength(options3.name) * 2, 0);
46553
+ pinned = entry.binding.retain();
46554
+ const input3 = context2.stdin instanceof ShellInput ? context2.stdin : new ShellInput(context2.stdin, this.budget, this.signal);
46555
+ let pendingFlow;
46556
+ await collectMapfile(options3, input3, {
46557
+ allocation: () => this.budget.values.scope(),
46558
+ loop: () => this.budget.loop(),
46559
+ write: async (index, value2) => {
46560
+ if (pinned !== entry.binding) {
46561
+ const previous = pinned;
46562
+ pinned = entry.binding.retain();
46563
+ await previous.release();
46564
+ }
46565
+ const operation2 = ArrayOwner.create(store.owner.ledger, store.owner);
46566
+ let staged;
46567
+ try {
46568
+ const current = pinned;
46569
+ const attached = store.bindings.get(options3.name) === entry;
46570
+ const watch = attached ? await store.watch(options3.name, operation2, this.signal) : void 0;
46571
+ const supersede = attached ? await stateMonitor(state).prepareTypedPublication(options3.name, operation2, this.signal) : void 0;
46572
+ if (attached) await this.prepareArrayObservers(state, operation2);
46573
+ const tickets = operation2.reserve({ generation: true, version: true, epoch: true, work: 8 });
46574
+ staged = current.references > (attached ? 2 : 1) ? await current.copy(this.signal) : current.retain();
46575
+ const token = await valueToken(staged.owner, value2, this.signal);
46576
+ try {
46577
+ this.signal.throwIfAborted();
46578
+ if (watch && !watch.valid()) throw new ArrayFailure("stale binding");
46579
+ staged.insert(index, token);
46580
+ } catch (error) {
46581
+ token.release();
46582
+ throw error;
46583
+ }
46584
+ if (attached) {
46585
+ let released;
46586
+ stateMonitor(state).publish(tickets, options3.name, () => {
46587
+ supersede();
46588
+ released = store.publish(options3.name, staged, tickets);
46589
+ });
46590
+ staged = void 0;
46591
+ await released;
46592
+ } else {
46593
+ const previous = pinned;
46594
+ pinned = staged;
46595
+ entry.binding = staged;
46596
+ staged = void 0;
46597
+ await previous.release();
46598
+ }
46599
+ } finally {
46600
+ try {
46601
+ await staged?.release();
46602
+ } finally {
46603
+ await operation2.close();
46604
+ }
46605
+ }
46606
+ },
46607
+ callback: async (source2, index, value2) => {
46608
+ if (pendingFlow) return;
46609
+ const callbackAllocation = this.budget.values.scope();
46610
+ const nested = mapfileCallbackStates.has(state);
46611
+ try {
46612
+ const quoted2 = shellValueText(await transformParameter(value2, "Q", { maximumBytes: this.budget.limits.maxExpansionBytes, byteLocale: byteLocale(state.variables), work, allocation: callbackAllocation }));
46613
+ if (import_buffer.Buffer.byteLength(source2) + import_buffer.Buffer.byteLength(quoted2) + String(index).length + 2 > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
46614
+ mapfileCallbackStates.add(state);
46615
+ await this.evalBuiltin({ ...context2, args: [source2, String(index), quoted2] }, state, context2, false);
46616
+ } catch (error) {
46617
+ if (error instanceof Flow2 && (error.kind === "break" || error.kind === "continue")) pendingFlow = error;
46618
+ else throw error;
46619
+ } finally {
46620
+ if (!nested) mapfileCallbackStates.delete(state);
46621
+ callbackAllocation.close();
46622
+ }
46623
+ }
46624
+ });
46625
+ if (pendingFlow) throw pendingFlow;
46626
+ return 0;
46627
+ } catch (error) {
46628
+ this.signal.throwIfAborted();
46629
+ if (!(error instanceof MapfileUsageError)) throw error;
46630
+ await this.diagnostic(context2, `${context2.command}: ${error.message}`);
46631
+ return error.status;
46632
+ } finally {
46633
+ try {
46634
+ await pinned?.release();
46635
+ } finally {
46636
+ try {
46637
+ releaseHolding?.();
46638
+ } finally {
46639
+ allocation.close();
46640
+ }
46641
+ }
46642
+ }
46643
+ }
46265
46644
  async builtin(context2, state, assignments2, diagnose, suppressSpecial = false) {
46266
46645
  const { command: command7, args, stdout, stderr } = context2;
46267
46646
  if (command7 === ":" || command7 === "true") return 0;
46268
46647
  if (command7 === "false") return 1;
46269
46648
  if (command7 === "shopt") return this.shoptBuiltin(context2, state);
46270
46649
  if (command7 === "let") return this.letBuiltin(context2, state);
46650
+ if (command7 === "mapfile" || command7 === "readarray") return this.mapfileBuiltin(context2, state);
46271
46651
  if (command7 === "getopts") return this.getoptsBuiltin(context2, state);
46272
46652
  if (command7 === "pushd" || command7 === "dirs" || command7 === "popd") return this.directoryStackBuiltin(context2, state, diagnose);
46273
46653
  if (command7 === "pwd") {
@@ -46332,9 +46712,16 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46332
46712
  this.replacePositionals(state, this.positionalValues(state).slice(count2));
46333
46713
  return 0;
46334
46714
  }
46335
- if (command7 === "export" || command7 === "local" || command7 === "readonly") {
46715
+ if (command7 === "export" || command7 === "local" || command7 === "readonly" || command7 === "declare") {
46716
+ const associativeDeclaration = command7 === "declare";
46336
46717
  const declarationArgs = [...args];
46337
- 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
+ }
46338
46725
  if (command7 === "local") {
46339
46726
  const options3 = localDeclarationOptions(declarationArgs, this.signal);
46340
46727
  if (options3.error !== void 0) {
@@ -46397,11 +46784,11 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46397
46784
  status2 = 1;
46398
46785
  continue;
46399
46786
  }
46400
- if (command7 === "local" && indexedLocal) {
46787
+ if ((command7 === "local" || associativeDeclaration) && indexedLocal) {
46401
46788
  if (controlNames.has(name2)) throw new ArrayFailure("control binding cannot be indexed");
46402
46789
  if (state.exported.has(name2)) throw new ArrayFailure("exported binding cannot be indexed");
46403
- const existingLocal = locals.get(name2);
46404
- 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);
46405
46792
  let operation2;
46406
46793
  let holding;
46407
46794
  let shadow;
@@ -46426,12 +46813,14 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46426
46813
  const tickets = operation2.reserve({ generation: true, version: true, epoch: true, work: 8 });
46427
46814
  const prepared = await store.prepareName(name2, operation2, this.signal);
46428
46815
  const current = store.get(name2);
46429
- 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);
46430
46818
  const value2 = match[2] ?? (!saved && !current && Object.hasOwn(state.variables, name2) ? state.variables[name2] : void 0);
46431
46819
  if (value2 !== void 0) {
46432
- 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);
46433
46822
  try {
46434
- shadow.insert(0, token);
46823
+ shadow.insert(associativeDeclaration ? await shadow.keyIndex("0", operation2, this.signal, true) : 0, token);
46435
46824
  } catch (error) {
46436
46825
  token.release();
46437
46826
  throw error;
@@ -46579,7 +46968,8 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46579
46968
  }
46580
46969
  if (command7 === "unset") {
46581
46970
  let status2 = 0;
46582
- for (const name2 of args) {
46971
+ for (let argument2 = 0; argument2 < args.length; argument2++) {
46972
+ const name2 = args[argument2];
46583
46973
  const selected = /^([a-zA-Z_][a-zA-Z_0-9]*)\[(.*)\]$/su.exec(name2);
46584
46974
  if (selected) {
46585
46975
  const base = selected[1];
@@ -46589,7 +46979,12 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46589
46979
  status2 = 1;
46590
46980
  continue;
46591
46981
  }
46592
- 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");
46593
46988
  else {
46594
46989
  let index;
46595
46990
  try {
@@ -46823,7 +47218,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46823
47218
  if (part.kind === "variable" && part.transform) {
46824
47219
  const selector2 = getArraySelector(part);
46825
47220
  if (selector2?.kind === "members" || part.name === "@" || part.name === "*") {
46826
- const members = selector2 ? await this.arrayMembers(part.name, state) : this.positionalValues(state);
47221
+ const members = selector2 ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
46827
47222
  const ifs = state.variables.IFS ?? " ";
46828
47223
  const separator = ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
46829
47224
  const fragments = [];
@@ -46863,10 +47258,22 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46863
47258
  const binding = part.kind === "variable" ? arrayStore(state)?.get(part.name) : void 0;
46864
47259
  const holding = binding ? requireArrays(state).owner.hold() : void 0;
46865
47260
  const selector = getArraySelector(part);
46866
- 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;
46867
47273
  const token = index === void 0 || selector?.kind === "members" ? void 0 : binding?.values.get(index)?.text;
46868
47274
  token?.retain();
46869
47275
  try {
47276
+ if (token?.rawValue && part.kind === "variable" && !part.length && !part.operator && !part.substring) return shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]);
46870
47277
  const value2 = await this.partValue(part, state, io, hereString);
46871
47278
  if (binding) await textToken(requireArrays(state).owner, shellValueText(value2), this.signal);
46872
47279
  return value2;
@@ -46984,8 +47391,11 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46984
47391
  return part.length ? this.parameterLength(value3 ?? "") : value3 ?? "";
46985
47392
  }
46986
47393
  if (part.length) return String(binding?.values.size ?? (state.variables[part.name] === void 0 ? 0 : 1));
46987
- const values = await this.arrayMembers(part.name, state);
46988
- return this.arrayJoin(store.owner, values, Array.from(state.variables.IFS ?? " ")[0] ?? "");
47394
+ const values = await this.arrayMembers(part.name, state, io, part.keys);
47395
+ const separator = Array.from(state.variables.IFS ?? " ")[0] ?? "";
47396
+ if (values.every((value3) => typeof value3 === "string")) return this.arrayJoin(store.owner, values, separator);
47397
+ io[valueScope]?.reserve(values.length * 32 + 64, 0);
47398
+ return concatShellValues(values.flatMap((value3, index) => index ? [separator, value3] : [value3]), io[valueScope]);
46989
47399
  }
46990
47400
  let value2 = part.name === "?" ? String(state.status) : part.name === "-" ? `${state.errexit ? "e" : ""}${state.nounset ? "u" : ""}${state.braceexpand !== false ? "B" : ""}` : part.name === "#" ? String(state.positional.length) : part.name === "@" || part.name === "*" ? state.positional.join(hereString && (part.name === "@" || !part.quoted) ? " " : Array.from(state.variables.IFS ?? " ")[0] ?? "") : /^0+$/u.test(part.name) ? state.arg0 ?? "virtual-bash" : /^\d+$/u.test(part.name) ? state.positional[Number(part.name) - 1] : this.variable(state, part.name);
46991
47401
  let retained = value2;
@@ -47394,7 +47804,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47394
47804
  emptyNameGroups.add(quoteGroup);
47395
47805
  }
47396
47806
  } else if (part.kind === "variable" && split4 && (selector?.kind === "members" && !part.length && (!part.quoted || selector.separator === "@") || part.transform && part.name === "@")) {
47397
- const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state) : this.positionalValues(state);
47807
+ const members = selector?.kind === "members" ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
47398
47808
  for (let position = 0; position < members.length; position++) {
47399
47809
  if (position > 0) addField();
47400
47810
  const original = members[position];
@@ -47536,7 +47946,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47536
47946
  for (const value2 of values) if (typeof value2 !== "string") allocation.hold(value2);
47537
47947
  return createCommandArguments(values, allocation);
47538
47948
  }
47539
- async arrayMembers(name2, state) {
47949
+ async arrayMembers(name2, state, io, keys = false) {
47540
47950
  const store = requireArrays(state);
47541
47951
  const holding = store.owner.hold();
47542
47952
  try {
@@ -47547,7 +47957,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47547
47957
  if (value2 === void 0) return [];
47548
47958
  store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
47549
47959
  await textToken(store.owner, value2, this.signal);
47550
- return [value2];
47960
+ return [keys ? "0" : value2];
47551
47961
  }
47552
47962
  binding.retain();
47553
47963
  try {
@@ -47555,8 +47965,9 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
47555
47965
  const values = [];
47556
47966
  for (const index of indices) {
47557
47967
  store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 4 });
47558
- const value2 = binding.get(index);
47559
- await textToken(store.owner, value2, this.signal);
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;
47969
+ const value2 = token.rawValue === void 0 ? token.value : shellValueFromBytes(shellValueBytes(token.rawValue, io[valueScope]), io[valueScope]);
47970
+ if (typeof value2 === "string") await textToken(store.owner, value2, this.signal);
47560
47971
  values.push(value2);
47561
47972
  await store.owner.ledger.checkpoint(this.signal);
47562
47973
  }
@@ -48231,6 +48642,51 @@ var ShellInput = class _ShellInput {
48231
48642
  }
48232
48643
  });
48233
48644
  }
48645
+ mapfileRecord(delimiter, strip, allocation) {
48646
+ return this.#cursor.consume(this.signal, async () => {
48647
+ this.#cursor.admitBoundedRead();
48648
+ allocation.reserve(64, 0);
48649
+ const parts = [];
48650
+ let length = 0;
48651
+ let truncated = false;
48652
+ let pulls = 0;
48653
+ while (true) {
48654
+ if (++pulls % 128 === 0) await yieldTurn(this.signal);
48655
+ const result2 = await this.#cursor.take(this.signal);
48656
+ if (result2.done) return { value: concatShellValues(parts, allocation), present: length > 0 };
48657
+ const chunk = result2.value;
48658
+ for (let offset = 0; offset < chunk.length; ) {
48659
+ await yieldTurn(this.signal);
48660
+ if (length >= this.budget.limits.maxOutputBytes) this.budget.fail("maxOutputBytes");
48661
+ const end = Math.min(offset + 1024, chunk.length, offset + this.budget.limits.maxOutputBytes - length);
48662
+ allocation.reserve(end - offset + 64, 0);
48663
+ const output2 = new Uint8Array(end - offset);
48664
+ let used = 0;
48665
+ let terminated = false;
48666
+ while (offset < end) {
48667
+ const byte = chunk[offset++];
48668
+ this.#cursor.position++;
48669
+ if (++length > this.budget.limits.maxOutputBytes) this.budget.fail("maxOutputBytes");
48670
+ if (byte === delimiter) {
48671
+ if (!strip && byte !== 0 && !truncated) output2[used++] = byte;
48672
+ terminated = true;
48673
+ break;
48674
+ }
48675
+ if (byte === 0) truncated = true;
48676
+ if (!truncated) output2[used++] = byte;
48677
+ }
48678
+ if (used) {
48679
+ allocation.reserve(32, 0);
48680
+ parts.push(shellValueFromBytes(output2.subarray(0, used), allocation));
48681
+ }
48682
+ if (terminated) {
48683
+ if (offset < chunk.length) this.#cursor.remainder = chunk.subarray(offset);
48684
+ return { value: concatShellValues(parts, allocation), present: true };
48685
+ }
48686
+ }
48687
+ }
48688
+ });
48689
+ }
48234
48690
  async readBounded(raw, options3) {
48235
48691
  const text3 = new ReadText();
48236
48692
  let characters = 0;