@poe-platform/safe-bash 0.1.736 → 0.1.737

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.
@@ -63,7 +63,7 @@ export const customRegisteredCommands = new WeakSet();
63
63
  export const customRegisteredRegistries = new WeakSet();
64
64
  import { defaultPredicateExecutors, tryFastPredicate } from "../commands/predicates.js";
65
65
  import { builtInDirectContextExecutors, pathOf, RESOLVED_EXIT_ONE, RESOLVED_EXIT_ZERO, UsageError } from "../commands/internal.js";
66
- import { cloneGetoptsState, createGetoptsInput, createGetoptsState, GetoptsError, getoptsInputAllocationSize, scanGetopts, withGetoptsIndex } from "./getopts.js";
66
+ import { cloneGetoptsState, createGetoptsInput, createGetoptsState, GetoptsError, getoptsInputAllocationSize, scanGetopts, scanGetoptsSync, withGetoptsIndex } from "./getopts.js";
67
67
  import { activateChildCancellation, admitCancellationSubscriptionCapacity, prepareChildCancellation, selectRuntimeCancellationOutcome, subscribeCancellationOwner, unsubscribeCancellationOwner, } from "./cancellation.js";
68
68
  import { variablePresence } from "../commands/variable-presence.js";
69
69
  import { getArrayAssignment, getArraySelector, copyArraySelector, numericIndex, literalIndex, stringIndex, isQuoteMarker, prefixNameQuoteGroups, setArraySelector } from "./arrays/syntax.js";
@@ -4242,6 +4242,15 @@ export class Runtime {
4242
4242
  resolvedSub = rawSub === undefined || rawSub === "" ? "0" : shellValueText(stateMonitor(st)?.values.get(resolvedSub, rawSub) ?? rawSub);
4243
4243
  }
4244
4244
  }
4245
+ else {
4246
+ const st = this._syncArithState;
4247
+ const binding = arrayStore(st)?.get(variable);
4248
+ if (!binding || !binding.associative) {
4249
+ const subStr = tryEvalSimpleExpandedArith(resolvedSub, st, stateMonitor(st), arrayStore(st), this._syncArithRawWriteOnly ? this._syncArithTouched : undefined);
4250
+ if (subStr !== undefined && !subStr.startsWith("-"))
4251
+ resolvedSub = subStr;
4252
+ }
4253
+ }
4245
4254
  if (resolvedSub.length === 0 || resolvedSub.length > 128 || !/^[a-zA-Z0-9_.\-/:@]+$/.test(resolvedSub))
4246
4255
  throw new ArrayFailure("complex subscript in sync arithmetic");
4247
4256
  return `[idx]${variable}:${resolvedSub}`;
@@ -4398,11 +4407,28 @@ export class Runtime {
4398
4407
  switch (tree.kind) {
4399
4408
  case "literal": return true;
4400
4409
  case "name": {
4401
- if (tree.subscript !== undefined || tree.name === "OPTIND" || tree.name === "PIPESTATUS" || arrayStore(state)?.get(tree.name))
4410
+ if (tree.name === "OPTIND" || tree.name === "PIPESTATUS")
4402
4411
  return false;
4403
4412
  const monitor = stateMonitor(state);
4404
4413
  if (monitor?.hasOverlay(tree.name) || monitor?.store?.watches.has(tree.name))
4405
4414
  return false;
4415
+ if (tree.subscript !== undefined) {
4416
+ if (state.nounset || hasUnpreparedLocal(state, tree.name))
4417
+ return false;
4418
+ const binding = arrayStore(state)?.get(tree.name);
4419
+ if (!binding)
4420
+ return false;
4421
+ if (binding.associative) {
4422
+ if (/^\$[a-zA-Z_][a-zA-Z_0-9]*$/.test(tree.subscript)) {
4423
+ const sv = tree.subscript.slice(1);
4424
+ return !arrayStore(state)?.get(sv) && !monitor?.hasOverlay(sv);
4425
+ }
4426
+ return /^[a-zA-Z0-9_.\-/:@]+$/.test(tree.subscript);
4427
+ }
4428
+ return /^(?:0|[1-9][0-9]{0,6})$/.test(tree.subscript) || /^\$?[a-zA-Z_][a-zA-Z_0-9]*$/.test(tree.subscript) || /^\s*[a-zA-Z_][a-zA-Z_0-9]*\s*(?:[-+*]\s*(?:0|[1-9][0-9]{0,6})|[/%]\s*[1-9][0-9]{0,6})\s*$/.test(tree.subscript);
4429
+ }
4430
+ if (arrayStore(state)?.get(tree.name))
4431
+ return false;
4406
4432
  const value = state.variables[tree.name]
4407
4433
  ?? (tree.name === "LINENO" ? String(line ?? 1)
4408
4434
  : tree.name === "_" ? state.lastArgument ?? ""
@@ -4709,27 +4735,55 @@ export class Runtime {
4709
4735
  return state.variables[name];
4710
4736
  }
4711
4737
  async variablePresent(state, name, io) {
4738
+ const simple = this.tryVariablePresentSync(state, name);
4739
+ if (simple !== undefined)
4740
+ return simple;
4712
4741
  name = this.referenceName(state, name);
4713
4742
  const bracket = name.indexOf("[");
4714
4743
  const base = bracket < 0 ? name : name.slice(0, bracket);
4715
- const validName = base.length > 0 && [...base].every((character, index) => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".includes(character)
4716
- || index > 0 && "0123456789".includes(character));
4717
- if (!validName || bracket >= 0 && !name.endsWith("]"))
4718
- return false;
4719
4744
  const resolved = this.referenceName(state, base);
4720
4745
  const binding = arrayStore(state)?.get(resolved);
4721
- if (bracket < 0)
4722
- return this.variable(state, base) !== undefined;
4723
4746
  const selector = name.slice(bracket + 1, -1);
4724
- if (!binding?.associative && (selector === "@" || selector === "*"))
4725
- return binding ? binding.values.size > 0 : this.variable(state, base) !== undefined;
4726
- const store = arrayStore(state);
4727
- if (!store)
4728
- return selector === "0" && this.variable(state, base) !== undefined;
4729
- const index = await this.arrayIndex(binding, { decimal: selector, source: selector }, state, io, store.owner);
4747
+ const entries = this.presenceArrayEntries(state, resolved);
4748
+ const store = requireArrays(state);
4749
+ const index = await this.arrayIndex(binding, { decimal: selector, source: selector }, state, io, store.owner, false, entries ? entries.length - 1 : binding?.maximum ?? (this.variable(state, base) === undefined ? -1 : 0));
4750
+ if (entries)
4751
+ return index !== undefined && entries[index] !== undefined;
4730
4752
  return index !== undefined && (binding ? binding.getValue(index) !== undefined
4731
4753
  : index === 0 && this.variable(state, base) !== undefined);
4732
4754
  }
4755
+ presenceArrayEntries(state, name) {
4756
+ if (name === "DIRSTACK")
4757
+ return [state.cwd, ...state.directoryStack?.entries ?? []];
4758
+ if (name === "FUNCNAME" && state.variables.FUNCNAME === undefined && !arrayStore(state)?.get(name))
4759
+ return state.functionNames ?? [];
4760
+ return undefined;
4761
+ }
4762
+ tryVariablePresentSync(state, name) {
4763
+ name = this.referenceName(state, name);
4764
+ const bracket = name.indexOf("[");
4765
+ const base = bracket < 0 ? name : name.slice(0, bracket);
4766
+ if (!isShellIdentifier(base) || bracket >= 0 && !name.endsWith("]"))
4767
+ return false;
4768
+ const resolved = this.referenceName(state, base);
4769
+ const binding = arrayStore(state)?.get(resolved);
4770
+ const entries = this.presenceArrayEntries(state, resolved);
4771
+ if (bracket < 0)
4772
+ return entries ? entries[0] !== undefined : this.variable(state, base) !== undefined;
4773
+ const selector = name.slice(bracket + 1, -1);
4774
+ if (selector === "@" || selector === "*")
4775
+ return entries ? entries.length > 0 : binding ? binding.values.size > 0 : this.variable(state, base) !== undefined;
4776
+ if (binding?.associative) {
4777
+ if (selector.length === 0 || selector.length > 4096 || [...selector].some(character => "$`\\\"'".includes(character)))
4778
+ return undefined;
4779
+ const key = binding.keys.get(fastStringHexIdentity(selector));
4780
+ return key !== undefined && binding.getValue(key.index) !== undefined;
4781
+ }
4782
+ if (selector.length === 0 || selector.length > 9 || [...selector].some(character => !"0123456789".includes(character)) || selector.length > 1 && selector[0] === "0")
4783
+ return undefined;
4784
+ const index = Number(selector);
4785
+ return entries ? entries[index] !== undefined : binding ? binding.getValue(index) !== undefined : index === 0 && this.variable(state, base) !== undefined;
4786
+ }
4733
4787
  requireParameter(value, name, state, io, line) {
4734
4788
  if (value === undefined && state.nounset)
4735
4789
  throw new NounsetFailure(`${name}: unbound variable`, io.diagnosticLine ?? line);
@@ -5203,7 +5257,7 @@ export class Runtime {
5203
5257
  return false;
5204
5258
  }
5205
5259
  // Cold activation belongs to normal command handling, which maps array refusals.
5206
- const store = monitor?.store ?? (monitor && !this.budget._arraySession?.hasActiveLedger ? requireArrays(rawState) : undefined);
5260
+ const store = monitor?.store ?? (monitor && (this.budget.limits.maxExpansionBytes === Infinity && this.budget.limits.maxExpansionFields === Infinity) ? requireArrays(rawState) : undefined);
5207
5261
  let current = store?.get(name);
5208
5262
  if (!monitor || !store || store.watches.has(name) || monitor.hasOverlay(name) || (current && (current.references !== 1 || (associative !== undefined && current.associative !== associative))) || (!current && rawState.variables[name] !== undefined)) {
5209
5263
  return false;
@@ -5253,7 +5307,7 @@ export class Runtime {
5253
5307
  let val = "";
5254
5308
  if (entry.value.parts.length > 0) {
5255
5309
  try {
5256
- val = this.fastValueWord(entry.value, rawState, io, false, false, false, false, undefined, diagnosticLine);
5310
+ val = this.fastValueWord(entry.value, rawState, io, false, false, false, false, entry.index === undefined ? undefined : 0, diagnosticLine);
5257
5311
  }
5258
5312
  catch {
5259
5313
  return false;
@@ -5314,7 +5368,7 @@ export class Runtime {
5314
5368
  let val = "";
5315
5369
  if (entry.value.parts.length > 0) {
5316
5370
  try {
5317
- val = this.fastValueWord(entry.value, rawState, io, false, false, false, false, undefined, diagnosticLine);
5371
+ val = this.fastValueWord(entry.value, rawState, io, false, false, false, false, entry.index === undefined ? undefined : 0, diagnosticLine);
5318
5372
  }
5319
5373
  catch {
5320
5374
  return false;
@@ -5723,7 +5777,7 @@ export class Runtime {
5723
5777
  continue;
5724
5778
  }
5725
5779
  const index = entry.index ? (await this.arrayIndex(targetBinding, entry.index, state, io, operation, true)) : undefined;
5726
- const fields = await this.valueWord(entry.value, state, io, entry.index === undefined);
5780
+ const fields = await this.valueWord(entry.value, state, io, entry.index === undefined, false, false, false, undefined, false, entry.index === undefined, entry.index === undefined ? undefined : 0);
5727
5781
  if (index !== undefined) {
5728
5782
  const value = await join(fields);
5729
5783
  await insert(index, value, entry.append);
@@ -8145,14 +8199,14 @@ export class Runtime {
8145
8199
  const r0 = command.redirects[0];
8146
8200
  const rOp = r0.operator;
8147
8201
  const isDevNull = (r0.descriptor === undefined || r0.descriptor === 1) && !r0.move && !r0.document && (rOp === ">" || rOp === ">>" || rOp === ">|") && r0.target.plain === "/dev/null";
8148
- const isReadCmd = w0Plain === "read" || (command.words[1]?.plain === "read" && w0.parts[0]?.kind === "text" && w0.parts[0].value.startsWith("IFS="));
8202
+ const isReadCmd = w0Plain === "read" || w0Plain === "mapfile" || w0Plain === "readarray" || (command.words[1]?.plain === "read" && w0.parts[0]?.kind === "text" && w0.parts[0].value.startsWith("IFS="));
8149
8203
  if (!isDevNull && w0Plain !== "echo" && w0Plain !== "printf" && !(isReadCmd && rOp === "<<<")) {
8150
8204
  pipeline._skipTrySync = true;
8151
8205
  return undefined;
8152
8206
  }
8153
8207
  }
8154
8208
  else if (wordsLen >= 2) {
8155
- if (w0Plain !== "echo" && w0Plain !== "printf" && w0Plain !== "[" && w0Plain !== "test" && w0Plain !== "mkdir" && w0Plain !== "rm" && w0Plain !== "local" && w0Plain !== "declare" && w0Plain !== "typeset" && w0Plain !== "return" && w0Plain !== "break" && w0Plain !== "continue" && w0Plain !== "shift" && w0Plain !== "read" && w0Plain !== "eval" && w0Plain !== "unset" && w0Plain !== "export" && w0Plain !== "let" && !(command.words[1]?.plain === "read" && w0.parts[0]?.kind === "text" && w0.parts[0].value.startsWith("IFS=")) && !(w0Plain !== undefined && state.functions.has(w0Plain))) {
8209
+ if (w0Plain !== ":" && w0Plain !== "true" && w0Plain !== "false" && w0Plain !== "getopts" && w0Plain !== "echo" && w0Plain !== "printf" && w0Plain !== "[" && w0Plain !== "test" && w0Plain !== "mkdir" && w0Plain !== "rm" && w0Plain !== "local" && w0Plain !== "declare" && w0Plain !== "typeset" && w0Plain !== "return" && w0Plain !== "break" && w0Plain !== "continue" && w0Plain !== "shift" && w0Plain !== "read" && w0Plain !== "eval" && w0Plain !== "unset" && w0Plain !== "export" && w0Plain !== "let" && !(command.words[1]?.plain === "read" && w0.parts[0]?.kind === "text" && w0.parts[0].value.startsWith("IFS=")) && !(w0Plain !== undefined && state.functions.has(w0Plain))) {
8156
8210
  return undefined;
8157
8211
  }
8158
8212
  }
@@ -8214,6 +8268,16 @@ export class Runtime {
8214
8268
  return true;
8215
8269
  }
8216
8270
  }
8271
+ if ((w0Plain === "mapfile" || w0Plain === "readarray") &&
8272
+ (command.words.length === 2 || command.words.length === 3) &&
8273
+ command.words[1]?.plain === "-t" &&
8274
+ !hasShellFunction(rawState, w0Plain) &&
8275
+ !rawState.extensions?.builtins.has(w0Plain)) {
8276
+ const arrVp = command.words[2]?.plain ?? "MAPFILE";
8277
+ const st = stateMonitor(rawState)?.store;
8278
+ if (isShellIdentifier(arrVp) && arrVp !== "OPTIND" && arrVp !== "PIPESTATUS" && !rawState.readonlyVariables?.has(arrVp) && !st?.get(arrVp)?.associative)
8279
+ return true;
8280
+ }
8217
8281
  }
8218
8282
  return false;
8219
8283
  }
@@ -8238,7 +8302,7 @@ export class Runtime {
8238
8302
  const fnBody = rawState.functions.get(w0Plain);
8239
8303
  return fnBody.kind === "group" && fnBody.redirects.length === 0 && this.canSyncScriptCompound(fnBody.body, rawState, depth + 1);
8240
8304
  }
8241
- const st = stateMonitor(rawState)?.store ?? (stateMonitor(rawState) && !this.budget._arraySession?.hasActiveLedger ? requireArrays(rawState) : undefined);
8305
+ const st = stateMonitor(rawState)?.store ?? (stateMonitor(rawState) && (this.budget.limits.maxExpansionBytes === Infinity && this.budget.limits.maxExpansionFields === Infinity) ? requireArrays(rawState) : undefined);
8242
8306
  const arrayAssign = getArrayAssignment(w0);
8243
8307
  if (arrayAssign) {
8244
8308
  if (!st)
@@ -8265,7 +8329,7 @@ export class Runtime {
8265
8329
  return isNoBraceSyncAssignWord(arrayAssign.value);
8266
8330
  }
8267
8331
  const assignment = this.assignment(w0);
8268
- return Boolean(assignment && assignment.name !== "OPTIND" && assignment.name !== "PIPESTATUS" && !assignment.name.includes("[") && !rawState.readonlyVariables?.has(assignment.name) && (!st?.get(assignment.name) || (!assignment.append && !hasUnpreparedLocal(rawState, assignment.name) && !rawState.exported.has(assignment.name))) && isNoBraceSyncAssignWord(assignment.value));
8332
+ return Boolean(assignment && (assignment.name !== "OPTIND" || (!assignment.append && !rawState.variableAttributes?.get("OPTIND"))) && assignment.name !== "PIPESTATUS" && !assignment.name.includes("[") && !rawState.readonlyVariables?.has(assignment.name) && (!st?.get(assignment.name) || (!assignment.append && !hasUnpreparedLocal(rawState, assignment.name) && !rawState.exported.has(assignment.name))) && isNoBraceSyncAssignWord(assignment.value));
8269
8333
  }
8270
8334
  if (w0Plain !== undefined && command.words.length <= 64 && rawState.functions.has(w0Plain) && this.firstInternalDiscovery(w0Plain, rawState, false) === "function" && !hasActiveExtensions(rawState)) {
8271
8335
  const fnBody = rawState.functions.get(w0Plain);
@@ -8387,12 +8451,12 @@ export class Runtime {
8387
8451
  const wArg = command.words[idx];
8388
8452
  const assignment = !getArrayAssignment(wArg) ? this.assignment(wArg) : undefined;
8389
8453
  if (assignment) {
8390
- if (assignment.name === "OPTIND" || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || !isNoBraceSyncAssignWord(assignment.value)) {
8454
+ if ((assignment.name === "OPTIND" && (assignment.append || rawState.variableAttributes?.get("OPTIND"))) || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || !isNoBraceSyncAssignWord(assignment.value)) {
8391
8455
  allValidLocal = false;
8392
8456
  break;
8393
8457
  }
8394
8458
  }
8395
- else if (!wArg.plain || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(wArg.plain) || wArg.plain === "OPTIND" || wArg.plain === "PIPESTATUS") {
8459
+ else if (!wArg.plain || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(wArg.plain) || (wArg.plain === "OPTIND" && rawState.variableAttributes?.get("OPTIND")) || wArg.plain === "PIPESTATUS") {
8396
8460
  allValidLocal = false;
8397
8461
  break;
8398
8462
  }
@@ -8400,6 +8464,15 @@ export class Runtime {
8400
8464
  if (allValidLocal)
8401
8465
  return true;
8402
8466
  }
8467
+ if ((w0Plain === ":" || w0Plain === "true" || w0Plain === "false") && command.words.length <= 16 && !hasShellFunction(rawState, w0Plain) && !rawState.extensions?.builtins.has(w0Plain) && command.words.slice(1).every(isNoBraceSyncWord)) {
8468
+ return true;
8469
+ }
8470
+ if (command.words.length >= 3 && command.words.length <= 16 && w0Plain === "getopts" && !hasShellFunction(rawState, "getopts") && !rawState.extensions?.builtins.has("getopts")) {
8471
+ const optVar = command.words[2]?.plain;
8472
+ if (optVar && isShellIdentifier(optVar) && optVar !== "OPTIND" && optVar !== "PIPESTATUS" && optVar !== "_" && !rawState.readonlyVariables?.has(optVar) && !rawState.readonlyVariables?.has("OPTIND") && !rawState.readonlyVariables?.has("OPTARG") && isNoBraceSyncWord(command.words[1]) && (command.words.length === 3 || (command.words.length === 4 && (() => { const ne = command.words[3].parts.filter(p => !(p.kind === "text" && p.value === "")); return ne.length === 1 && ne[0].kind === "variable" && ne[0].name === "@" && ne[0].quoted; })()) || command.words.slice(3).every(isNoBraceSyncWord))) {
8473
+ return true;
8474
+ }
8475
+ }
8403
8476
  if (w0Plain === "echo" && !hasShellFunction(rawState, "echo") && !rawState.extensions?.builtins.has("echo")) {
8404
8477
  const w1Plain = command.words[1]?.plain;
8405
8478
  if (w1Plain === undefined || !w1Plain.startsWith("-") || w1Plain === "-n" || w1Plain === "-e" || w1Plain === "-ne" || w1Plain === "-en")
@@ -8458,8 +8531,35 @@ export class Runtime {
8458
8531
  return this.isPureSyncValueWord(e.operand, rawState);
8459
8532
  if (e.kind === "unary")
8460
8533
  return (e.operator === "-n" || e.operator === "-z" || e.operator === "-v") && this.isPureSyncValueWord(e.operand, rawState);
8461
- if (e.kind === "binary")
8534
+ if (e.kind === "binary") {
8535
+ if (e.operator === "=~") {
8536
+ if (!this.isPureSyncValueWord(e.left, rawState) || !this.extractSimpleErePattern(e.right) || rawState.readonlyVariables?.has("BASH_REMATCH") || rawState.exported.has("BASH_REMATCH") || hasUnpreparedLocal(rawState, "BASH_REMATCH"))
8537
+ return false;
8538
+ const mon = stateMonitor(rawState);
8539
+ if (!mon || (this.budget.limits.maxExpansionBytes !== Infinity || this.budget.limits.maxExpansionFields !== Infinity) || mon.hasOverlay("BASH_REMATCH"))
8540
+ return false;
8541
+ const st = mon.store ?? requireArrays(rawState);
8542
+ if (st.watches.has("BASH_REMATCH"))
8543
+ return false;
8544
+ let rem = st.get("BASH_REMATCH");
8545
+ if (!rem) {
8546
+ if (rawState.variables.BASH_REMATCH !== undefined)
8547
+ return false;
8548
+ const created = IndexedBinding.create(st.owner, false);
8549
+ const prepared = st.prepareExistingName("BASH_REMATCH", 12, st.owner, this.signal);
8550
+ if (!prepared) {
8551
+ void created.release();
8552
+ return false;
8553
+ }
8554
+ const initTickets = st.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
8555
+ st.publish("BASH_REMATCH", created, initTickets, prepared, false, st.owner);
8556
+ mon.epoch = initTickets.epoch;
8557
+ rem = created;
8558
+ }
8559
+ return !rem.associative && rem.references === 1;
8560
+ }
8462
8561
  return this.isPureSyncValueWord(e.left, rawState) && (e.operator === "==" || e.operator === "=" || e.operator === "!=" ? this.canSyncPatternWordParts(e.right, rawState) : this.isPureSyncValueWord(e.right, rawState));
8562
+ }
8463
8563
  if (e.kind === "not")
8464
8564
  return checkCondNoFile(e.operand);
8465
8565
  if (e.kind === "and" || e.kind === "or")
@@ -8647,6 +8747,19 @@ export class Runtime {
8647
8747
  nonIntScalarVars.add(target);
8648
8748
  }
8649
8749
  }
8750
+ else if (wp0 === "mapfile" || wp0 === "readarray") {
8751
+ arrayVars.add(c.words[2]?.plain ?? "MAPFILE");
8752
+ }
8753
+ else if (wp0 === "getopts") {
8754
+ const ov = c.words[2]?.plain;
8755
+ if (ov && isShellIdentifier(ov)) {
8756
+ scalarVars.add(ov);
8757
+ nonIntScalarVars.add(ov);
8758
+ }
8759
+ scalarVars.add("OPTIND");
8760
+ scalarVars.add("OPTARG");
8761
+ nonIntScalarVars.add("OPTARG");
8762
+ }
8650
8763
  else if (wp0 === "read" || (c.words[1]?.plain === "read")) {
8651
8764
  for (let k = 1; k < c.words.length; k++) {
8652
8765
  const vp = c.words[k]?.plain;
@@ -8828,36 +8941,8 @@ export class Runtime {
8828
8941
  if (expr.operator === "-z")
8829
8942
  return val.length === 0 ? 0 : 1;
8830
8943
  if (expr.operator === "-v") {
8831
- const bracket = val.indexOf("[");
8832
- if (bracket < 0) {
8833
- if (!isShellIdentifier(val))
8834
- return 1;
8835
- const b = store?.get(val);
8836
- if (b)
8837
- return b.get(b.associative ? b.keys.get("30")?.index ?? -1 : 0) !== undefined ? 0 : 1;
8838
- return this.variable(rawState, val) !== undefined ? 0 : 1;
8839
- }
8840
- if (!val.endsWith("]"))
8841
- return 1;
8842
- const base = val.slice(0, bracket);
8843
- if (!isShellIdentifier(base))
8844
- return 1;
8845
- const sub = val.slice(bracket + 1, -1);
8846
- const b = store?.get(base);
8847
- if (!b?.associative && (sub === "@" || sub === "*")) {
8848
- return (b ? b.values.size > 0 : this.variable(rawState, base) !== undefined) ? 0 : 1;
8849
- }
8850
- if (b?.associative) {
8851
- if (sub.length === 0 || sub.length > 4096 || /[$\x60\\"']/.test(sub))
8852
- return undefined;
8853
- const ek = b.keys.get(fastStringHexIdentity(sub));
8854
- return (ek !== undefined && b.getValue(ek.index) !== undefined) ? 0 : 1;
8855
- }
8856
- if (/^(?:0|[1-9][0-9]{0,8})$/.test(sub)) {
8857
- const idx = Number(sub);
8858
- return (b ? b.getValue(idx) !== undefined : idx === 0 && this.variable(rawState, base) !== undefined) ? 0 : 1;
8859
- }
8860
- return undefined;
8944
+ const present = this.tryVariablePresentSync(rawState, val);
8945
+ return present === undefined ? undefined : present ? 0 : 1;
8861
8946
  }
8862
8947
  if (syncConditionalFileUnaryOps.has(expr.operator)) {
8863
8948
  const fileRes = this.tryEvalFastMemoryFileUnary(expr.operator, val, rawState);
@@ -9067,7 +9152,7 @@ export class Runtime {
9067
9152
  let existing = store?.get("PIPESTATUS");
9068
9153
  if (store && !existing) {
9069
9154
  const initTarget = pipelineStatusTarget(rawState);
9070
- if (!this.budget._arraySession?.hasActiveLedger &&
9155
+ if ((this.budget.limits.maxExpansionBytes === Infinity && this.budget.limits.maxExpansionFields === Infinity) &&
9071
9156
  (initTarget === "indexed" || initTarget === "absent") &&
9072
9157
  !store.watches.has("PIPESTATUS") &&
9073
9158
  !monitor.hasOverlay("PIPESTATUS") &&
@@ -9353,7 +9438,15 @@ export class Runtime {
9353
9438
  }
9354
9439
  const dynProg = prepareArithmetic(fastSrc, this.budget.parsing);
9355
9440
  if (!dynProg.error && isSafeSmiProgram(dynProg) && this.canSyncArithmeticWithoutFault(dynProg.tree, rawState, diagnosticLine)) {
9356
- const nonZero = this.syncShellArithmeticNonZero(dynProg, rawState, diagnosticLine);
9441
+ let nonZero;
9442
+ try {
9443
+ nonZero = this.syncShellArithmeticNonZero(dynProg, rawState, diagnosticLine);
9444
+ }
9445
+ catch (err) {
9446
+ if (err instanceof ArrayFailure)
9447
+ return undefined;
9448
+ throw err;
9449
+ }
9357
9450
  if (rawState.extensions && !rawState.extensions.eventDepth)
9358
9451
  publishCommandSpelling(rawState, commandSpelling(command));
9359
9452
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
@@ -9366,7 +9459,15 @@ export class Runtime {
9366
9459
  }
9367
9460
  }
9368
9461
  if (!expr.error && isSafeSmiProgram(expr) && this.canSyncArithmeticWithoutFault(expr.tree, rawState, diagnosticLine)) {
9369
- const nonZero = this.syncShellArithmeticNonZero(expr, rawState, diagnosticLine);
9462
+ let nonZero;
9463
+ try {
9464
+ nonZero = this.syncShellArithmeticNonZero(expr, rawState, diagnosticLine);
9465
+ }
9466
+ catch (err) {
9467
+ if (err instanceof ArrayFailure)
9468
+ return undefined;
9469
+ throw err;
9470
+ }
9370
9471
  if (rawState.extensions && !rawState.extensions.eventDepth)
9371
9472
  publishCommandSpelling(rawState, commandSpelling(command));
9372
9473
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
@@ -9488,6 +9589,12 @@ export class Runtime {
9488
9589
  if (command.kind === "while" || command.kind === "until") {
9489
9590
  this.budget.tick();
9490
9591
  let lastStatus = 0;
9592
+ const enableBatchRawWrite = !this._syncArithRawWriteOnly && !hasActiveExtensions(rawState) && !hasActiveVariableAttributes(rawState) && !rawState.readonlyVariables?.size && !rawState.allexport;
9593
+ const batchTouched = enableBatchRawWrite ? new Set() : undefined;
9594
+ if (enableBatchRawWrite) {
9595
+ this._syncArithRawWriteOnly = true;
9596
+ this._syncArithTouched = batchTouched;
9597
+ }
9491
9598
  rawState.loopDepth++;
9492
9599
  this._syncReturnDepth++;
9493
9600
  try {
@@ -9544,6 +9651,17 @@ export class Runtime {
9544
9651
  finally {
9545
9652
  this._syncReturnDepth--;
9546
9653
  rawState.loopDepth--;
9654
+ if (enableBatchRawWrite && batchTouched) {
9655
+ this._syncArithRawWriteOnly = false;
9656
+ this._syncArithTouched = undefined;
9657
+ for (const vName of batchTouched) {
9658
+ const vVal = rawState.variables[vName];
9659
+ if (typeof vVal === "string")
9660
+ monitor.publishStringVariable(vName, vVal);
9661
+ else if (vVal === undefined)
9662
+ delete monitor.proxy.variables[vName];
9663
+ }
9664
+ }
9547
9665
  }
9548
9666
  rawState.status = lastStatus;
9549
9667
  return lastStatus;
@@ -9554,12 +9672,18 @@ export class Runtime {
9554
9672
  return undefined;
9555
9673
  }
9556
9674
  this.budget.tick();
9557
- if (e0)
9558
- this.syncShellArithmeticNonZero(e0, rawState, diagnosticLine);
9675
+ const enableBatchRawWrite = !this._syncArithRawWriteOnly && !hasActiveExtensions(rawState) && !hasActiveVariableAttributes(rawState) && !rawState.readonlyVariables?.size && !rawState.allexport;
9676
+ const batchTouched = enableBatchRawWrite ? new Set() : undefined;
9677
+ if (enableBatchRawWrite) {
9678
+ this._syncArithRawWriteOnly = true;
9679
+ this._syncArithTouched = batchTouched;
9680
+ }
9559
9681
  let lastStatus = 0;
9560
9682
  rawState.loopDepth++;
9561
9683
  this._syncReturnDepth++;
9562
9684
  try {
9685
+ if (e0)
9686
+ this.syncShellArithmeticNonZero(e0, rawState, diagnosticLine);
9563
9687
  let iter = 0;
9564
9688
  while (true) {
9565
9689
  if (++iter > 4096)
@@ -9603,6 +9727,17 @@ export class Runtime {
9603
9727
  finally {
9604
9728
  this._syncReturnDepth--;
9605
9729
  rawState.loopDepth--;
9730
+ if (enableBatchRawWrite && batchTouched) {
9731
+ this._syncArithRawWriteOnly = false;
9732
+ this._syncArithTouched = undefined;
9733
+ for (const vName of batchTouched) {
9734
+ const vVal = rawState.variables[vName];
9735
+ if (typeof vVal === "string")
9736
+ monitor.publishStringVariable(vName, vVal);
9737
+ else if (vVal === undefined)
9738
+ delete monitor.proxy.variables[vName];
9739
+ }
9740
+ }
9606
9741
  }
9607
9742
  rawState.status = lastStatus;
9608
9743
  return lastStatus;
@@ -9673,6 +9808,12 @@ export class Runtime {
9673
9808
  return undefined;
9674
9809
  this.budget.tick();
9675
9810
  let lastStatus = 0;
9811
+ const enableBatchRawWrite = !this._syncArithRawWriteOnly && !hasActiveExtensions(rawState) && !hasActiveVariableAttributes(rawState) && !rawState.readonlyVariables?.size && !rawState.allexport;
9812
+ const batchTouched = enableBatchRawWrite ? new Set() : undefined;
9813
+ if (enableBatchRawWrite) {
9814
+ this._syncArithRawWriteOnly = true;
9815
+ this._syncArithTouched = batchTouched;
9816
+ }
9676
9817
  rawState.loopDepth++;
9677
9818
  this._syncReturnDepth++;
9678
9819
  try {
@@ -9683,7 +9824,12 @@ export class Runtime {
9683
9824
  runYieldCheckpoint(this.signal);
9684
9825
  }
9685
9826
  this.budget.loop();
9686
- monitor.publishStringVariable(command.name, items[idx]);
9827
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
9828
+ rawState.variables[command.name] = items[idx];
9829
+ this._syncArithTouched.add(command.name);
9830
+ }
9831
+ else
9832
+ monitor.publishStringVariable(command.name, items[idx]);
9687
9833
  if (rawState.allexport)
9688
9834
  rawState.exported.add(command.name);
9689
9835
  const bodyRes = this.trySyncScript(command.body, state, io, ignored);
@@ -9709,6 +9855,17 @@ export class Runtime {
9709
9855
  finally {
9710
9856
  this._syncReturnDepth--;
9711
9857
  rawState.loopDepth--;
9858
+ if (enableBatchRawWrite && batchTouched) {
9859
+ this._syncArithRawWriteOnly = false;
9860
+ this._syncArithTouched = undefined;
9861
+ for (const vName of batchTouched) {
9862
+ const vVal = rawState.variables[vName];
9863
+ if (typeof vVal === "string")
9864
+ monitor.publishStringVariable(vName, vVal);
9865
+ else if (vVal === undefined)
9866
+ delete monitor.proxy.variables[vName];
9867
+ }
9868
+ }
9712
9869
  }
9713
9870
  rawState.status = lastStatus;
9714
9871
  return lastStatus;
@@ -10084,7 +10241,7 @@ export class Runtime {
10084
10241
  if (assignment) {
10085
10242
  const hasSubPart = assignment.value.parts.some(part => part.kind === "substitution");
10086
10243
  const existingArrAssign = store?.get(assignment.name);
10087
- if (assignment.name === "OPTIND" ||
10244
+ if ((assignment.name === "OPTIND" && (assignment.append || rawState.variableAttributes?.get("OPTIND"))) ||
10088
10245
  assignment.name === "PIPESTATUS" ||
10089
10246
  assignment.name.includes("[") ||
10090
10247
  (hasSubPart && !this.isPureSyncValueWord(assignment.value, rawState)) ||
@@ -10103,6 +10260,12 @@ export class Runtime {
10103
10260
  }
10104
10261
  if (typeof fastAssigned !== "string")
10105
10262
  return undefined;
10263
+ if (assignment.name === "OPTIND") {
10264
+ const trimmedOptind = fastAssigned.trim();
10265
+ if (!/^[0-9]{1,6}$/.test(trimmedOptind))
10266
+ return undefined;
10267
+ fastAssigned = String(Number(trimmedOptind));
10268
+ }
10106
10269
  if (existingArrAssign) {
10107
10270
  if (!this.tryFastArraySubscriptWriteSync(rawState, assignment.name, "0", fastAssigned))
10108
10271
  return undefined;
@@ -10161,6 +10324,10 @@ export class Runtime {
10161
10324
  }
10162
10325
  else
10163
10326
  monitor.publishStringVariable(assignment.name, finalAssigned);
10327
+ if (assignment.name === "OPTIND") {
10328
+ rawState.variables.OPTIND = finalAssigned;
10329
+ this.syncGetopts(rawState);
10330
+ }
10164
10331
  if (rawState.allexport)
10165
10332
  monitor.proxy.exported.add(assignment.name);
10166
10333
  if (io.assignmentDiagnosticContext)
@@ -10243,6 +10410,142 @@ export class Runtime {
10243
10410
  }
10244
10411
  }
10245
10412
  }
10413
+ if (command.words.length >= 2 && command.words.length <= 16 && (w0Plain === ":" || w0Plain === "true" || w0Plain === "false") && command.redirects.length === 0 && !hasShellFunction(rawState, w0Plain) && !rawState.extensions?.builtins.has(w0Plain)) {
10414
+ const rawStatus = w0Plain === "false" ? 1 : 0;
10415
+ const statusChar = rawStatus === 0 ? "0" : "1";
10416
+ if (!canMutatePipeStatus && elem0.text.shellValue !== statusChar)
10417
+ return undefined;
10418
+ const finalStatus = pipeline.negate ? Number(rawStatus === 0) : rawStatus;
10419
+ if (finalStatus !== 0 && !ignored && rawState.errexit)
10420
+ return undefined;
10421
+ let lastArg = w0Plain;
10422
+ let argsOk = true;
10423
+ try {
10424
+ for (let i = 1; i < command.words.length; i++) {
10425
+ const v = this.fastValueWord(command.words[i], rawState, io, true, false, false, true, undefined, diagnosticLine);
10426
+ if (typeof v !== "string") {
10427
+ argsOk = false;
10428
+ break;
10429
+ }
10430
+ lastArg = v;
10431
+ }
10432
+ }
10433
+ catch {
10434
+ argsOk = false;
10435
+ }
10436
+ if (argsOk) {
10437
+ if (rawState.extensions && !rawState.extensions.eventDepth)
10438
+ publishCommandSpelling(rawState, commandSpelling(command));
10439
+ const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10440
+ this.budget.tick();
10441
+ rawState.substitutionStatus = 0;
10442
+ if (rawState.variables._ !== undefined)
10443
+ delete rawState.variables._;
10444
+ rawState.lastArgument = lastArg;
10445
+ if (io.assignmentDiagnosticContext)
10446
+ io.assignmentDiagnosticContext.name = undefined;
10447
+ return this.finishSyncPipeStatus(rawState, monitor, monitor.store, monitor.store?.get("PIPESTATUS"), elem0, scope, finalStatus, restEpoch);
10448
+ }
10449
+ }
10450
+ if (command.words.length >= 3 && command.words.length <= 16 && w0Plain === "getopts" && command.redirects.length === 0 && !hasShellFunction(rawState, "getopts") && !rawState.extensions?.builtins.has("getopts")) {
10451
+ const optVar = command.words[2]?.plain;
10452
+ if (optVar && isShellIdentifier(optVar) && optVar !== "OPTIND" && optVar !== "PIPESTATUS" && optVar !== "_" && !rawState.readonlyVariables?.has(optVar) && !rawState.readonlyVariables?.has("OPTIND") && !rawState.readonlyVariables?.has("OPTARG") && !rawState.variableAttributes?.get(optVar) && !rawState.variableAttributes?.get("OPTIND") && !rawState.variableAttributes?.get("OPTARG") && !store?.get(optVar) && !store?.get("OPTIND") && !store?.get("OPTARG") && !monitor.hasOverlay(optVar)) {
10453
+ let optstring;
10454
+ let scanArgs;
10455
+ let lastArg = optVar;
10456
+ try {
10457
+ optstring = command.words[1].plain ?? this.fastValueWord(command.words[1], rawState, io, true, false, false, true, undefined, diagnosticLine);
10458
+ if (typeof optstring === "string") {
10459
+ if (command.words.length === 3) {
10460
+ scanArgs = this._fastSubPositional ?? rawState.positional;
10461
+ }
10462
+ else if (command.words.length === 4 && (() => { const ne = command.words[3].parts.filter(p => !(p.kind === "text" && p.value === "")); return ne.length === 1 && ne[0].kind === "variable" && ne[0].name === "@" && ne[0].quoted; })()) {
10463
+ scanArgs = this._fastSubPositional ?? rawState.positional;
10464
+ if (scanArgs.length > 0)
10465
+ lastArg = scanArgs[scanArgs.length - 1];
10466
+ }
10467
+ else {
10468
+ const customArgs = [];
10469
+ let customOk = true;
10470
+ for (let i = 3; i < command.words.length; i++) {
10471
+ const v = this.fastValueWord(command.words[i], rawState, io, true, false, false, true, undefined, diagnosticLine);
10472
+ if (typeof v !== "string") {
10473
+ customOk = false;
10474
+ break;
10475
+ }
10476
+ customArgs.push(v);
10477
+ lastArg = v;
10478
+ }
10479
+ if (customOk)
10480
+ scanArgs = customArgs;
10481
+ }
10482
+ }
10483
+ }
10484
+ catch {
10485
+ scanArgs = undefined;
10486
+ }
10487
+ if (typeof optstring === "string" && scanArgs !== undefined) {
10488
+ rawState.getopts ??= cloneGetoptsBinding(rawState);
10489
+ const res = scanGetoptsSync(rawState.getopts.cursor, optstring, scanArgs);
10490
+ if (res !== undefined) {
10491
+ const rawStatus = res.status;
10492
+ const statusChar = rawStatus === 0 ? "0" : "1";
10493
+ if (canMutatePipeStatus || elem0.text.shellValue === statusChar) {
10494
+ const finalStatus = pipeline.negate ? Number(rawStatus === 0) : rawStatus;
10495
+ if (finalStatus === 0 || ignored || !rawState.errexit) {
10496
+ if (rawState.extensions && !rawState.extensions.eventDepth)
10497
+ publishCommandSpelling(rawState, commandSpelling(command));
10498
+ const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10499
+ this.budget.tick();
10500
+ rawState.substitutionStatus = 0;
10501
+ if (rawState.variables._ !== undefined)
10502
+ delete rawState.variables._;
10503
+ rawState.lastArgument = lastArg;
10504
+ rawState.getopts.cursor = res.state;
10505
+ const optindStr = String(res.optind);
10506
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10507
+ rawState.variables.OPTIND = optindStr;
10508
+ this._syncArithTouched.add("OPTIND");
10509
+ }
10510
+ else
10511
+ monitor.publishStringVariable("OPTIND", optindStr);
10512
+ if (rawState.allexport)
10513
+ monitor.proxy.exported.add("OPTIND");
10514
+ if (res.argument.kind === "set") {
10515
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10516
+ rawState.variables.OPTARG = res.argument.value;
10517
+ this._syncArithTouched.add("OPTARG");
10518
+ }
10519
+ else
10520
+ monitor.publishStringVariable("OPTARG", res.argument.value);
10521
+ if (rawState.allexport)
10522
+ monitor.proxy.exported.add("OPTARG");
10523
+ }
10524
+ else {
10525
+ delete rawState.variables.OPTARG;
10526
+ rawState.exported.delete("OPTARG");
10527
+ if (this._syncArithRawWriteOnly && this._syncArithTouched)
10528
+ this._syncArithTouched.add("OPTARG");
10529
+ else
10530
+ monitor.values.invalidate("OPTARG");
10531
+ }
10532
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10533
+ rawState.variables[optVar] = res.option;
10534
+ this._syncArithTouched.add(optVar);
10535
+ }
10536
+ else
10537
+ monitor.publishStringVariable(optVar, res.option);
10538
+ if (rawState.allexport)
10539
+ monitor.proxy.exported.add(optVar);
10540
+ if (io.assignmentDiagnosticContext)
10541
+ io.assignmentDiagnosticContext.name = undefined;
10542
+ return this.finishSyncPipeStatus(rawState, monitor, monitor.store, monitor.store?.get("PIPESTATUS"), elem0, scope, finalStatus, restEpoch);
10543
+ }
10544
+ }
10545
+ }
10546
+ }
10547
+ }
10548
+ }
10246
10549
  if (command.words.length === 2 && w0Plain === "shift" && command.redirects.length === 0 && canMutatePipeStatus && !pipeline.negate && !hasShellFunction(rawState, "shift") && !rawState.extensions?.builtins.has("shift")) {
10247
10550
  let argVal;
10248
10551
  try {
@@ -10406,6 +10709,105 @@ export class Runtime {
10406
10709
  !command.redirects[0].move &&
10407
10710
  !command.redirects[0].document &&
10408
10711
  (command.redirects[0].descriptor === undefined || command.redirects[0].descriptor === 0);
10712
+ if ((w0Plain === "mapfile" || w0Plain === "readarray") &&
10713
+ isHereStringRead &&
10714
+ (command.words.length === 2 || command.words.length === 3) &&
10715
+ command.words[1]?.plain === "-t" &&
10716
+ canMutatePipeStatus &&
10717
+ !pipeline.negate &&
10718
+ !rawState.allexport &&
10719
+ !hasShellFunction(rawState, w0Plain) &&
10720
+ !rawState.extensions?.builtins.has(w0Plain)) {
10721
+ const arrayTarget = command.words[2]?.plain ?? "MAPFILE";
10722
+ if (!hasUnpreparedLocal(rawState, arrayTarget) &&
10723
+ isShellIdentifier(arrayTarget) &&
10724
+ !rawState.readonlyVariables?.has(arrayTarget) &&
10725
+ !rawState.exported.has(arrayTarget) &&
10726
+ !rawState.variableAttributes?.get(arrayTarget) &&
10727
+ !controlNames.has(arrayTarget)) {
10728
+ let hv;
10729
+ try {
10730
+ hv = this.fastValueWord(command.redirects[0].target, rawState, io, false, false, false, false, undefined, diagnosticLine);
10731
+ }
10732
+ catch {
10733
+ hv = undefined;
10734
+ }
10735
+ if (typeof hv === "string" &&
10736
+ !hv.includes("\0") &&
10737
+ hv.length + 1 < this.budget.limits.maxExpansionBytes &&
10738
+ hv.length + 1 <= this.budget.limits.maxInputBytes) {
10739
+ const arrStore = monitor.store ?? requireArrays(rawState);
10740
+ let existingArrayBinding = arrStore.get(arrayTarget);
10741
+ if (!existingArrayBinding && rawState.variables[arrayTarget] === undefined && !arrStore.watches.has(arrayTarget) && !monitor.hasOverlay(arrayTarget)) {
10742
+ const recTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
10743
+ const recBinding = arrStore.takeRecycled(arrayTarget, false, true, recTickets);
10744
+ if (recBinding) {
10745
+ monitor.epoch = recTickets.epoch;
10746
+ existingArrayBinding = recBinding;
10747
+ }
10748
+ else {
10749
+ try {
10750
+ const created = IndexedBinding.create(arrStore.owner, false);
10751
+ const prepared = arrStore.prepareExistingName(arrayTarget, shellValueByteLength(arrayTarget), arrStore.owner, this.signal);
10752
+ if (prepared) {
10753
+ const initTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
10754
+ arrStore.publish(arrayTarget, created, initTickets, prepared, false, arrStore.owner);
10755
+ monitor.epoch = initTickets.epoch;
10756
+ existingArrayBinding = created;
10757
+ }
10758
+ else {
10759
+ void created.release();
10760
+ }
10761
+ }
10762
+ catch { }
10763
+ }
10764
+ }
10765
+ if (existingArrayBinding && !existingArrayBinding.associative && existingArrayBinding.references === 1 && !arrStore.watches.has(arrayTarget) && !monitor.hasOverlay(arrayTarget)) {
10766
+ const lines = hv.split("\n");
10767
+ const fieldLen = lines.length;
10768
+ if (fieldLen <= 256) {
10769
+ if (rawState.extensions && !rawState.extensions.eventDepth)
10770
+ publishCommandSpelling(rawState, commandSpelling(command));
10771
+ const tickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 + fieldLen * 4 });
10772
+ if (existingArrayBinding.values.size !== fieldLen || existingArrayBinding.maximum !== fieldLen - 1) {
10773
+ for (const k of [...existingArrayBinding.values.keys()]) {
10774
+ if (k >= fieldLen)
10775
+ existingArrayBinding.remove(k);
10776
+ }
10777
+ existingArrayBinding.maximum = fieldLen - 1;
10778
+ }
10779
+ for (let idx = 0; idx < fieldLen; idx++) {
10780
+ const val = lines[idx];
10781
+ const byteLen = shellValueByteLength(val);
10782
+ const slot = existingArrayBinding.values.get(idx);
10783
+ if (slot && slot.text.references === 1 && slot.text.bytes === byteLen) {
10784
+ existingArrayBinding.owner.chargeWork(byteLen);
10785
+ slot.text.shellValue = val;
10786
+ }
10787
+ else {
10788
+ existingArrayBinding.owner.chargeWork(byteLen);
10789
+ const token = new OwnedText(val, byteLen, existingArrayBinding.owner.reserve({ payload: byteLen, metadata: 32, work: 4 }));
10790
+ existingArrayBinding.insert(idx, token);
10791
+ }
10792
+ }
10793
+ delete rawState.variables[arrayTarget];
10794
+ monitor.values.invalidate(arrayTarget);
10795
+ monitor.epoch = tickets.epoch;
10796
+ arrStore.revise(arrayTarget, existingArrayBinding, tickets);
10797
+ const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10798
+ this.budget.tick();
10799
+ rawState.substitutionStatus = 0;
10800
+ if (rawState.variables._ !== undefined)
10801
+ delete rawState.variables._;
10802
+ rawState.lastArgument = command.words[command.words.length - 1].plain ?? arrayTarget;
10803
+ if (io.assignmentDiagnosticContext)
10804
+ io.assignmentDiagnosticContext.name = undefined;
10805
+ return this.finishSyncPipeStatus(rawState, monitor, monitor.store, monitor.store?.get("PIPESTATUS"), elem0, scope, 0, restEpoch);
10806
+ }
10807
+ }
10808
+ }
10809
+ }
10810
+ }
10409
10811
  if ((w0Plain === "read" || (command.words[1]?.plain === "read" && w0.parts[0]?.kind === "text" && w0.parts[0].value.startsWith("IFS="))) &&
10410
10812
  canMutatePipeStatus &&
10411
10813
  !pipeline.negate &&
@@ -10470,7 +10872,7 @@ export class Runtime {
10470
10872
  delimStr = nextW.plain;
10471
10873
  else {
10472
10874
  try {
10473
- const fv = this.fastValueWord(nextW, rawState, io, false, false, false, false, 0, diagnosticLine);
10875
+ const fv = this.fastValueWord(nextW, rawState, io, true, false, false, false, undefined, diagnosticLine);
10474
10876
  if (typeof fv === "string")
10475
10877
  delimStr = fv;
10476
10878
  }
@@ -10516,7 +10918,7 @@ export class Runtime {
10516
10918
  const varCount = command.words.length - readWordIdx;
10517
10919
  let existingArrayBinding;
10518
10920
  if (arrayTarget !== undefined) {
10519
- if (hasUnpreparedLocals(rawState))
10921
+ if (hasUnpreparedLocal(rawState, arrayTarget))
10520
10922
  return undefined;
10521
10923
  if (varCount !== 0 || !isShellIdentifier(arrayTarget) || rawState.readonlyVariables?.has(arrayTarget) || rawState.exported.has(arrayTarget) || rawState.variableAttributes?.get(arrayTarget) || controlNames.has(arrayTarget)) {
10522
10924
  simpleRead = false;
@@ -10525,21 +10927,28 @@ export class Runtime {
10525
10927
  const arrStore = monitor.store ?? requireArrays(rawState);
10526
10928
  existingArrayBinding = arrStore.get(arrayTarget);
10527
10929
  if (!existingArrayBinding && rawState.variables[arrayTarget] === undefined && !arrStore.watches.has(arrayTarget) && !monitor.hasOverlay(arrayTarget)) {
10528
- try {
10529
- const created = IndexedBinding.create(arrStore.owner, false);
10530
- const nameByteLen = shellValueByteLength(arrayTarget);
10531
- const prepared = arrStore.prepareExistingName(arrayTarget, nameByteLen, arrStore.owner, this.signal);
10532
- if (prepared) {
10533
- const initTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
10534
- arrStore.publish(arrayTarget, created, initTickets, prepared, false, arrStore.owner);
10535
- monitor.epoch = initTickets.epoch;
10536
- existingArrayBinding = created;
10537
- }
10538
- else {
10539
- void created.release();
10540
- }
10930
+ const recTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
10931
+ const recBinding = arrStore.takeRecycled(arrayTarget, false, true, recTickets);
10932
+ if (recBinding) {
10933
+ monitor.epoch = recTickets.epoch;
10934
+ existingArrayBinding = recBinding;
10541
10935
  }
10542
- catch { }
10936
+ else
10937
+ try {
10938
+ const created = IndexedBinding.create(arrStore.owner, false);
10939
+ const nameByteLen = shellValueByteLength(arrayTarget);
10940
+ const prepared = arrStore.prepareExistingName(arrayTarget, nameByteLen, arrStore.owner, this.signal);
10941
+ if (prepared) {
10942
+ const initTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
10943
+ arrStore.publish(arrayTarget, created, initTickets, prepared, false, arrStore.owner);
10944
+ monitor.epoch = initTickets.epoch;
10945
+ existingArrayBinding = created;
10946
+ }
10947
+ else {
10948
+ void created.release();
10949
+ }
10950
+ }
10951
+ catch { }
10543
10952
  }
10544
10953
  if (!existingArrayBinding ||
10545
10954
  existingArrayBinding.associative ||
@@ -11151,7 +11560,7 @@ export class Runtime {
11151
11560
  const wArg = command.words[idx];
11152
11561
  const assignment = !getArrayAssignment(wArg) ? this.assignment(wArg) : undefined;
11153
11562
  if (assignment) {
11154
- if (assignment.name === "OPTIND" || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || rawState.readonlyVariables?.has(assignment.name) || store?.get(assignment.name)) {
11563
+ if ((assignment.name === "OPTIND" && (assignment.append || rawState.variableAttributes?.get("OPTIND"))) || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || rawState.readonlyVariables?.has(assignment.name) || store?.get(assignment.name)) {
11155
11564
  allValidLocal = false;
11156
11565
  break;
11157
11566
  }
@@ -11166,9 +11575,17 @@ export class Runtime {
11166
11575
  allValidLocal = false;
11167
11576
  break;
11168
11577
  }
11578
+ if (assignment.name === "OPTIND") {
11579
+ const trimmedOptind = fastAssigned.trim();
11580
+ if (!/^[0-9]{1,6}$/.test(trimmedOptind)) {
11581
+ allValidLocal = false;
11582
+ break;
11583
+ }
11584
+ fastAssigned = String(Number(trimmedOptind));
11585
+ }
11169
11586
  parsedLocals.push({ name: assignment.name, val: fastAssigned, append: assignment.append, lastArg: `${assignment.name}=${fastAssigned}` });
11170
11587
  }
11171
- else if (wArg.plain && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(wArg.plain) && wArg.plain !== "OPTIND" && wArg.plain !== "PIPESTATUS" && !rawState.readonlyVariables?.has(wArg.plain) && !store?.get(wArg.plain)) {
11588
+ else if (wArg.plain && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(wArg.plain) && (wArg.plain !== "OPTIND" || !rawState.variableAttributes?.get("OPTIND")) && wArg.plain !== "PIPESTATUS" && !rawState.readonlyVariables?.has(wArg.plain) && !store?.get(wArg.plain)) {
11172
11589
  parsedLocals.push({ name: wArg.plain, val: undefined, append: false, lastArg: wArg.plain });
11173
11590
  }
11174
11591
  else {
@@ -11177,6 +11594,8 @@ export class Runtime {
11177
11594
  }
11178
11595
  }
11179
11596
  if (allValidLocal && parsedLocals.length > 0) {
11597
+ if (rawState.locals.length === 0 && parsedLocals.some(item => item.val === undefined && !rawState.variableAttributes?.has(item.name)))
11598
+ return undefined;
11180
11599
  const locals = rawState.locals.length > 0 ? rawState.locals[rawState.locals.length - 1] : undefined;
11181
11600
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
11182
11601
  this.budget.tick();
@@ -11191,7 +11610,7 @@ export class Runtime {
11191
11610
  const curVal = this._syncArithTouched.has(item.name)
11192
11611
  ? rawState.variables[item.name]
11193
11612
  : (rawState.variables[item.name] === undefined ? undefined : (monitor.values.get(item.name, rawState.variables[item.name]) ?? rawState.variables[item.name]));
11194
- locals.set(item.name, { attributes: undefined, value: typeof curVal === "string" ? curVal : undefined, exported: false, readOnly: false });
11613
+ locals.set(item.name, { attributes: undefined, value: typeof curVal === "string" ? curVal : undefined, exported: false, readOnly: false, ...(item.name === "OPTIND" ? { getopts: cloneGetoptsBinding(rawState) } : {}) });
11195
11614
  }
11196
11615
  else
11197
11616
  locals.set(item.name, saveVariable(state, item.name));
@@ -11214,18 +11633,27 @@ export class Runtime {
11214
11633
  }
11215
11634
  else
11216
11635
  monitor.publishStringVariable(item.name, finalVal);
11636
+ if (item.name === "OPTIND") {
11637
+ rawState.variables.OPTIND = finalVal;
11638
+ this.syncGetopts(rawState);
11639
+ }
11217
11640
  if (rawState.allexport)
11218
11641
  monitor.proxy.exported.add(item.name);
11219
11642
  rawState.lastArgument = `${item.name}=${finalVal}`;
11220
- if (!rawState.variableAttributes?.has(item.name)) {
11221
- rawState.variableAttributes ??= new Map();
11222
- rawState.variableAttributes.set(item.name, "");
11223
- }
11643
+ if (isNewLocal && rawState.variableAttributes?.has(item.name))
11644
+ rawState.variableAttributes.delete(item.name);
11224
11645
  }
11225
11646
  else {
11226
- if (!rawState.variableAttributes?.has(item.name)) {
11227
- rawState.variableAttributes ??= new Map();
11228
- rawState.variableAttributes.set(item.name, "");
11647
+ if (item.name === "OPTIND")
11648
+ this.syncGetopts(rawState);
11649
+ if (!locals) {
11650
+ if (!rawState.variableAttributes?.has(item.name)) {
11651
+ rawState.variableAttributes ??= new Map();
11652
+ rawState.variableAttributes.set(item.name, "");
11653
+ }
11654
+ }
11655
+ else if (isNewLocal && rawState.variableAttributes?.has(item.name)) {
11656
+ rawState.variableAttributes.delete(item.name);
11229
11657
  }
11230
11658
  rawState.lastArgument = item.lastArg;
11231
11659
  }
@@ -11434,11 +11862,15 @@ export class Runtime {
11434
11862
  rawState.locals.pop();
11435
11863
  rawState.functionNames?.shift();
11436
11864
  for (const [k, previous] of locals) {
11437
- if (this._syncArithRawWriteOnly && this._syncArithTouched && !syncLocalArrayVariables.has(previous) && !previous.heldValue && !previous.attributes && !previous.exported && !previous.readOnly && k !== "OPTIND") {
11865
+ if (this._syncArithRawWriteOnly && this._syncArithTouched && !syncLocalArrayVariables.has(previous) && !previous.heldValue && !previous.attributes && !previous.exported && !previous.readOnly) {
11438
11866
  if (previous.value === undefined)
11439
11867
  delete rawState.variables[k];
11440
11868
  else
11441
11869
  rawState.variables[k] = shellValueText(previous.value);
11870
+ if (k === "OPTIND" && previous.getopts) {
11871
+ rawState.getopts = { integer: previous.getopts.integer, cursor: cloneGetoptsState(previous.getopts.cursor) };
11872
+ }
11873
+ rawState.variableAttributes?.delete(k);
11442
11874
  this._syncArithTouched.add(k);
11443
11875
  }
11444
11876
  else
@@ -11998,9 +12430,11 @@ export class Runtime {
11998
12430
  if (part.alternate && part.alternate.parts.length > 0)
11999
12431
  return false;
12000
12432
  }
12001
- else if (part.operator === "-" || part.operator === ":-" || part.operator === "+" || part.operator === ":+") {
12433
+ else if (part.operator === "-" || part.operator === ":-" || part.operator === "+" || part.operator === ":+" || part.operator === "=" || part.operator === ":=") {
12002
12434
  if (!part.alternate || !part.alternate.parts.every(p => p.kind === "text" || p.kind === "arithmetic" || (p.kind === "variable" && !p.operator && !p.substring)))
12003
12435
  return false;
12436
+ if ((part.operator === "=" || part.operator === ":=") && (!isShellIdentifier(part.name) || part.name === "OPTIND" || part.name === "PIPESTATUS" || part.name === "_" || rawState.readonlyVariables?.has(part.name) || rawState.variableAttributes?.get(part.name) || stateMonitor(rawState)?.store?.get(part.name)))
12437
+ return false;
12004
12438
  }
12005
12439
  else if (part.operator === "#" || part.operator === "##" || part.operator === "%" || part.operator === "%%") {
12006
12440
  if (!part.alternate || part.alternate.parts.length === 0)
@@ -12036,8 +12470,18 @@ export class Runtime {
12036
12470
  if (rawState.nounset)
12037
12471
  return false;
12038
12472
  const em = SIMPLE_EXPANDED_ARITH_RE.exec(part.source);
12039
- if (!em || ((em[2] === "/" || em[2] === "%") && !(em[3] && /^[1-9][0-9]*$/.test(em[3]))))
12040
- return false;
12473
+ if (!em || ((em[2] === "/" || em[2] === "%") && !(em[3] && /^[1-9][0-9]*$/.test(em[3])))) {
12474
+ if (part.source.includes("<<") || part.source.includes(">>") || part.source.includes("**") || /[/%](?!\s*[1-9][0-9]*\b)/.test(part.source))
12475
+ return false;
12476
+ try {
12477
+ const expW = parseArithmeticExpansion(part.source, this.budget.parsing, false, 0, undefined, rawState.extensions?.syntax);
12478
+ if (!this.isPureSyncValueWord(expW, rawState))
12479
+ return false;
12480
+ }
12481
+ catch {
12482
+ return false;
12483
+ }
12484
+ }
12041
12485
  }
12042
12486
  continue;
12043
12487
  }
@@ -15651,7 +16095,7 @@ export class Runtime {
15651
16095
  }
15652
16096
  if (inlineInput || (state.profile === "sh" || !words.length) && assignments.some(assignment => state.readonlyVariables?.has(assignment.name)))
15653
16097
  await assign();
15654
- if (inlineInput && functionCommand && previous?.size) {
16098
+ if (inlineInput && (functionCommand || words[0] === "read" && !state.extensions?.builtins.has("read")) && previous?.size) {
15655
16099
  if (stateMonitor(state)?.lazyPipeStatus !== undefined)
15656
16100
  stateMonitor(state).activate(true);
15657
16101
  const redirectState = tryCloneStateSync(state) ?? await cloneState(state, this.signal);
@@ -20050,7 +20494,7 @@ export class Runtime {
20050
20494
  if (selector?.kind === "members" || part.name === "@" || part.name === "*") {
20051
20495
  const members = selector ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
20052
20496
  const ifs = state.variables.IFS ?? " ";
20053
- const separator = selector?.kind === "members" && !split && (selector.separator === "@" || !part.quoted) ? " " : ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
20497
+ const separator = !split && (selector?.kind === "members" ? selector.separator === "@" || !part.quoted : part.name === "@") ? " " : ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
20054
20498
  const fragments = [];
20055
20499
  let bytes = 0;
20056
20500
  for (const member of members) {
@@ -20490,7 +20934,7 @@ export class Runtime {
20490
20934
  if (part.length)
20491
20935
  return String(binding?.values.size ?? (part.name === "FUNCNAME" && state.variables.FUNCNAME === undefined ? state.functionNames?.length ?? 0 : state.variables[part.name] === undefined ? 0 : 1));
20492
20936
  const values = await this.arrayMembers(part.name, state, io, selector.kind === "keys" || part.keys === true, part.substring);
20493
- const space = (selector.kind === "keys" || part.keys === true) && state.extensions?.syntax?.arrayKeys ? Boolean(hereDocument || (selector.separator === "@" ? !part.quoted && !split || state.variables.IFS === "" : !part.quoted && split && state.variables.IFS === "")) : (!split && (selector.separator === "@" || !part.quoted));
20937
+ const space = (selector.kind === "keys" || part.keys === true) ? Boolean(hereDocument || (selector.separator === "@" ? !part.quoted && !split || state.variables.IFS === "" : !part.quoted && split && state.variables.IFS === "")) : (!split && selector.separator === "@");
20494
20938
  return this.arrayJoin(store.owner, values, space ? " " : this.ifsSeparator(state, io));
20495
20939
  }
20496
20940
  if (part.substring && (part.name === "@" || part.name === "*")) {
@@ -20504,7 +20948,7 @@ export class Runtime {
20504
20948
  : part.name === "?" ? String(state.status)
20505
20949
  : part.name === "-" ? `${state.allexport ? "a" : ""}${state.errexit ? "e" : ""}${state.noglob ? "f" : ""}${state.noexec ? "n" : ""}${state.nounset ? "u" : ""}${state.braceexpand !== false ? "B" : ""}${state.noclobber ? "C" : ""}`
20506
20950
  : part.name === "#" ? String(state.positional.length)
20507
- : part.name === "@" || part.name === "*" ? state.positional.join(hereString && (part.name === "@" || !part.quoted) ? " " : Array.from(state.variables.IFS ?? " ")[0] ?? "")
20951
+ : part.name === "@" || part.name === "*" ? state.positional.join(part.name === "@" && !split || hereString && (part.name === "@" || !part.quoted) ? " " : Array.from(state.variables.IFS ?? " ")[0] ?? "")
20508
20952
  : /^0+$/u.test(part.name) ? state.arg0 ?? "virtual-bash"
20509
20953
  : /^\d+$/u.test(part.name) ? state.positional[Number(part.name) - 1]
20510
20954
  : part.name === "LINENO" ? (state.extensions ? String(io.diagnosticLine ?? part.line ?? 1) : state.variables.LINENO ?? String(io.diagnosticLine ?? part.line ?? 1)) : this.variable(state, part.name);
@@ -20521,7 +20965,7 @@ export class Runtime {
20521
20965
  else if (/^[0-9]+$/u.test(part.name))
20522
20966
  retained = stateMonitor(state)?.positionals.get(String(Number(part.name) - 1), value) ?? value;
20523
20967
  else if (part.name === "@" || part.name === "*") {
20524
- const separator = hereString && (part.name === "@" || !part.quoted) ? " " : this.ifsSeparator(state, io);
20968
+ const separator = part.name === "@" && !split || hereString && (part.name === "@" || !part.quoted) ? " " : this.ifsSeparator(state, io);
20525
20969
  const values = this.positionalValues(state);
20526
20970
  retained = concatShellValues(values.flatMap((entry, index) => index ? [separator, entry] : [entry]), io[valueScope]);
20527
20971
  }
@@ -21061,7 +21505,7 @@ export class Runtime {
21061
21505
  }
21062
21506
  const ifsVal = monitor?.values.get("IFS", rawVars.IFS ?? " ") ?? rawVars.IFS ?? " ";
21063
21507
  if (validElemOp && typeof ifsVal === "string" && (ifsVal.length === 0 || ifsVal.charCodeAt(0) < 128)) {
21064
- const sep = selector.separator === "@" ? " " : ifsVal.length > 0 ? ifsVal[0] : "";
21508
+ const sep = selector.separator === "@" && (!isKeys || ifsVal.length === 0) ? " " : ifsVal.length > 0 ? ifsVal[0] : "";
21065
21509
  const evalSliceInt = (w) => {
21066
21510
  if (w.parts.length === 0)
21067
21511
  return 0;
@@ -21574,7 +22018,10 @@ export class Runtime {
21574
22018
  out += converted;
21575
22019
  continue;
21576
22020
  }
21577
- if ((part.operator === "-" || part.operator === ":-" || part.operator === "+" || part.operator === ":+") && part.alternate && !rawState.nounset && part.alternate.parts.every(p => p.kind === "text" || p.kind === "arithmetic" || (p.kind === "variable" && !p.operator && !p.substring))) {
22021
+ if ((part.operator === "-" || part.operator === ":-" || part.operator === "+" || part.operator === ":+" || part.operator === "=" || part.operator === ":=") && part.alternate && !rawState.nounset && part.alternate.parts.every(p => p.kind === "text" || p.kind === "arithmetic" || (p.kind === "variable" && !p.operator && !p.substring))) {
22022
+ const isAssignOp = part.operator === "=" || part.operator === ":=";
22023
+ if (isAssignOp && (!isShellIdentifier(part.name) || part.name === "OPTIND" || part.name === "PIPESTATUS" || part.name === "_" || rawState.readonlyVariables?.has(part.name) || rawState.variableAttributes?.get(part.name) || activeArrayStore?.get(part.name) || monitor?.hasOverlay(part.name) || (i < word.parts.length - 1 && !this.isPureSyncValueWord({ offset: 0, parts: word.parts.slice(i + 1) }, rawState, false))))
22024
+ return undefined;
21578
22025
  const raw = rawVars[part.name];
21579
22026
  const cur = raw === undefined ? undefined : (this._syncArithRawWriteOnly && this._syncArithTouched?.has(part.name) ? raw : (monitor?.values.get(part.name, raw) ?? raw));
21580
22027
  if (cur !== undefined && typeof cur !== "string")
@@ -21592,11 +22039,25 @@ export class Runtime {
21592
22039
  resolvedScalar = "";
21593
22040
  }
21594
22041
  else {
21595
- const isMissing = cur === undefined || (part.operator === ":-" && cur === "");
22042
+ const isMissing = cur === undefined || ((part.operator === ":-" || part.operator === ":=") && cur === "");
21596
22043
  if (isMissing) {
21597
22044
  const altVal = this.fastValueWord(part.alternate, rawState, io, false, false, false, false, undefined, part.line ?? overrideDiagnosticLine);
21598
22045
  if (typeof altVal !== "string")
21599
22046
  return undefined;
22047
+ if (isAssignOp) {
22048
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
22049
+ rawState.variables[part.name] = altVal;
22050
+ this._syncArithTouched.add(part.name);
22051
+ }
22052
+ else if (monitor) {
22053
+ monitor.publishStringVariable(part.name, altVal);
22054
+ }
22055
+ else {
22056
+ rawState.variables[part.name] = altVal;
22057
+ }
22058
+ if (rawState.allexport)
22059
+ (monitor ? monitor.proxy.exported : rawState.exported).add(part.name);
22060
+ }
21600
22061
  resolvedScalar = altVal;
21601
22062
  }
21602
22063
  else