@poe-platform/safe-bash 0.1.736 → 0.1.738

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.
Files changed (31) hide show
  1. package/dist/safe-bash/chunks/{chunk-ZFV5FGGH.js → chunk-E2FTAYLG.js} +15 -16
  2. package/dist/safe-bash/chunks/{chunk-ZFV5FGGH.js.map → chunk-E2FTAYLG.js.map} +2 -2
  3. package/dist/safe-bash/chunks/{chunk-DFNSXDHP.js → chunk-JNHKE26E.js} +2 -2
  4. package/dist/safe-bash/chunks/{chunk-N3RTTKMA.js → chunk-JTKTBT3B.js} +2 -2
  5. package/dist/safe-bash/command-assets/git_rust-NOIGQE52.wasm +0 -0
  6. package/dist/safe-bash/command-chunks/{chunk-L6GGCZOK.js → chunk-5EOMWWKN.js} +16 -15
  7. package/dist/safe-bash/command-chunks/chunk-5EOMWWKN.js.map +7 -0
  8. package/dist/safe-bash/command-chunks/{chunk-YXAPLUY4.js → chunk-WFMUMKBQ.js} +2 -2
  9. package/dist/safe-bash/core.browser.js +928 -218
  10. package/dist/safe-bash/core.browser.js.map +3 -3
  11. package/dist/safe-bash/shell/arithmetic.d.ts.map +1 -1
  12. package/dist/safe-bash/shell/arithmetic.js +10 -1
  13. package/dist/safe-bash/shell/arithmetic.js.map +1 -1
  14. package/dist/safe-bash/shell/getopts.d.ts +1 -0
  15. package/dist/safe-bash/shell/getopts.d.ts.map +1 -1
  16. package/dist/safe-bash/shell/getopts.js +71 -0
  17. package/dist/safe-bash/shell/getopts.js.map +1 -1
  18. package/dist/safe-bash/shell/runtime.d.ts +5 -0
  19. package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
  20. package/dist/safe-bash/shell/runtime.js +1043 -247
  21. package/dist/safe-bash/shell/runtime.js.map +1 -1
  22. package/dist/safe-bash-command-git/index.js +1 -1
  23. package/dist/safe-bash-regex-engine/index.js +2 -2
  24. package/dist/safe-bash-regex-engine/text/regex.js +2 -2
  25. package/dist/safe-bash-regex-engine/text/replacement-buffer.js +1 -1
  26. package/package.json +2 -2
  27. package/dist/safe-bash/command-assets/git_rust-VC5UCZXR.wasm +0 -0
  28. package/dist/safe-bash/command-chunks/chunk-L6GGCZOK.js.map +0 -7
  29. /package/dist/safe-bash/chunks/{chunk-DFNSXDHP.js.map → chunk-JNHKE26E.js.map} +0 -0
  30. /package/dist/safe-bash/chunks/{chunk-N3RTTKMA.js.map → chunk-JTKTBT3B.js.map} +0 -0
  31. /package/dist/safe-bash/command-chunks/{chunk-YXAPLUY4.js.map → chunk-WFMUMKBQ.js.map} +0 -0
@@ -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";
@@ -2098,6 +2098,32 @@ function hasActiveVariableAttributes(state) {
2098
2098
  }
2099
2099
  return false;
2100
2100
  }
2101
+ function hasNonNamerefAttributes(state) {
2102
+ const attrs = state.variableAttributes;
2103
+ if (!attrs || attrs.size === 0)
2104
+ return false;
2105
+ for (const [k, val] of attrs.entries()) {
2106
+ if (val.length === 0)
2107
+ continue;
2108
+ if (val !== "n")
2109
+ return true;
2110
+ const ref = state.variables[k];
2111
+ if (!ref || !isShellIdentifier(ref) || ref === k || attrs.get(ref))
2112
+ return true;
2113
+ }
2114
+ return false;
2115
+ }
2116
+ function resolveSyncNameref(state, name) {
2117
+ const attrs = state.variableAttributes;
2118
+ if (!attrs || attrs.size === 0)
2119
+ return name;
2120
+ if (attrs.get(name) === "n") {
2121
+ const ref = state.variables[name];
2122
+ if (ref && isShellIdentifier(ref) && ref !== name && !attrs.get(ref))
2123
+ return ref;
2124
+ }
2125
+ return name;
2126
+ }
2101
2127
  function publishVariable(state, name, value) {
2102
2128
  const monitor = stateMonitor(state);
2103
2129
  if (monitor) {
@@ -3408,6 +3434,7 @@ function resolveSimpleArithOperand(tok, rawState, monitor, activeArrayStore, syn
3408
3434
  }
3409
3435
  tok = c1 === 123 ? tok.slice(2, -1) : tok.slice(1);
3410
3436
  }
3437
+ tok = resolveSyncNameref(rawState, tok);
3411
3438
  if (rawState.nounset || activeArrayStore?.bindings.has(tok) || monitor?.hasOverlay(tok) || activeArrayStore?.watches.has(tok))
3412
3439
  return undefined;
3413
3440
  const raw = rawState.variables[tok];
@@ -4218,8 +4245,12 @@ export class Runtime {
4218
4245
  return {
4219
4246
  isSync: true, resolve: (variable, subscript) => {
4220
4247
  this.signal.throwIfAborted();
4221
- if (this._syncArithState.variableAttributes?.get(variable)?.includes("n"))
4222
- throw new ArrayFailure("nameref in sync arithmetic");
4248
+ if (this._syncArithState.variableAttributes?.get(variable)?.includes("n")) {
4249
+ const resolved = resolveSyncNameref(this._syncArithState, variable);
4250
+ if (resolved === variable)
4251
+ throw new ArrayFailure("nameref in sync arithmetic");
4252
+ variable = resolved;
4253
+ }
4223
4254
  if (subscript !== undefined) {
4224
4255
  let resolvedSub = subscript;
4225
4256
  if (resolvedSub.charCodeAt(0) === 36 && /^\$[a-zA-Z_][a-zA-Z_0-9]*$/.test(resolvedSub)) {
@@ -4242,6 +4273,15 @@ export class Runtime {
4242
4273
  resolvedSub = rawSub === undefined || rawSub === "" ? "0" : shellValueText(stateMonitor(st)?.values.get(resolvedSub, rawSub) ?? rawSub);
4243
4274
  }
4244
4275
  }
4276
+ else {
4277
+ const st = this._syncArithState;
4278
+ const binding = arrayStore(st)?.get(variable);
4279
+ if (!binding || !binding.associative) {
4280
+ const subStr = tryEvalSimpleExpandedArith(resolvedSub, st, stateMonitor(st), arrayStore(st), this._syncArithRawWriteOnly ? this._syncArithTouched : undefined);
4281
+ if (subStr !== undefined && !subStr.startsWith("-"))
4282
+ resolvedSub = subStr;
4283
+ }
4284
+ }
4245
4285
  if (resolvedSub.length === 0 || resolvedSub.length > 128 || !/^[a-zA-Z0-9_.\-/:@]+$/.test(resolvedSub))
4246
4286
  throw new ArrayFailure("complex subscript in sync arithmetic");
4247
4287
  return `[idx]${variable}:${resolvedSub}`;
@@ -4393,15 +4433,33 @@ export class Runtime {
4393
4433
  }
4394
4434
  }
4395
4435
  canSyncArithmeticOperands(tree, state, line, depth = 0) {
4396
- if (!tree || depth > 128 || hasActiveVariableAttributes(state) || state.readonlyVariables?.size)
4436
+ if (!tree || depth > 128 || hasNonNamerefAttributes(state) || state.readonlyVariables?.size)
4397
4437
  return false;
4398
4438
  switch (tree.kind) {
4399
4439
  case "literal": return true;
4400
4440
  case "name": {
4401
- if (tree.subscript !== undefined || tree.name === "OPTIND" || tree.name === "PIPESTATUS" || arrayStore(state)?.get(tree.name))
4441
+ const effName = resolveSyncNameref(state, tree.name);
4442
+ if (effName === "OPTIND" || effName === "PIPESTATUS")
4402
4443
  return false;
4403
4444
  const monitor = stateMonitor(state);
4404
- if (monitor?.hasOverlay(tree.name) || monitor?.store?.watches.has(tree.name))
4445
+ if (monitor?.hasOverlay(effName) || monitor?.store?.watches.has(effName))
4446
+ return false;
4447
+ if (tree.subscript !== undefined) {
4448
+ if (state.nounset || hasUnpreparedLocal(state, tree.name))
4449
+ return false;
4450
+ const binding = arrayStore(state)?.get(tree.name);
4451
+ if (!binding)
4452
+ return false;
4453
+ if (binding.associative) {
4454
+ if (/^\$[a-zA-Z_][a-zA-Z_0-9]*$/.test(tree.subscript)) {
4455
+ const sv = tree.subscript.slice(1);
4456
+ return !arrayStore(state)?.get(sv) && !monitor?.hasOverlay(sv);
4457
+ }
4458
+ return /^[a-zA-Z0-9_.\-/:@]+$/.test(tree.subscript);
4459
+ }
4460
+ 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);
4461
+ }
4462
+ if (arrayStore(state)?.get(tree.name))
4405
4463
  return false;
4406
4464
  const value = state.variables[tree.name]
4407
4465
  ?? (tree.name === "LINENO" ? String(line ?? 1)
@@ -4702,6 +4760,8 @@ export class Runtime {
4702
4760
  const binding = arrayStore(state)?.get(name);
4703
4761
  if (binding)
4704
4762
  return binding.get(binding.associative ? binding.keys.get("30")?.index ?? -1 : 0);
4763
+ if (name === "DIRSTACK")
4764
+ return state.cwd;
4705
4765
  if (name === "FUNCNAME" && state.variables.FUNCNAME === undefined)
4706
4766
  return state.functionNames?.[0];
4707
4767
  if (name === "_" && state.variables._ === undefined)
@@ -4709,27 +4769,55 @@ export class Runtime {
4709
4769
  return state.variables[name];
4710
4770
  }
4711
4771
  async variablePresent(state, name, io) {
4772
+ const simple = this.tryVariablePresentSync(state, name);
4773
+ if (simple !== undefined)
4774
+ return simple;
4712
4775
  name = this.referenceName(state, name);
4713
4776
  const bracket = name.indexOf("[");
4714
4777
  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
4778
  const resolved = this.referenceName(state, base);
4720
4779
  const binding = arrayStore(state)?.get(resolved);
4721
- if (bracket < 0)
4722
- return this.variable(state, base) !== undefined;
4723
4780
  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);
4781
+ const entries = this.presenceArrayEntries(state, resolved);
4782
+ const store = requireArrays(state);
4783
+ 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));
4784
+ if (entries)
4785
+ return index !== undefined && entries[index] !== undefined;
4730
4786
  return index !== undefined && (binding ? binding.getValue(index) !== undefined
4731
4787
  : index === 0 && this.variable(state, base) !== undefined);
4732
4788
  }
4789
+ presenceArrayEntries(state, name) {
4790
+ if (name === "DIRSTACK")
4791
+ return [state.cwd, ...state.directoryStack?.entries ?? []];
4792
+ if (name === "FUNCNAME" && state.variables.FUNCNAME === undefined && !arrayStore(state)?.get(name))
4793
+ return state.functionNames ?? [];
4794
+ return undefined;
4795
+ }
4796
+ tryVariablePresentSync(state, name) {
4797
+ name = this.referenceName(state, name);
4798
+ const bracket = name.indexOf("[");
4799
+ const base = bracket < 0 ? name : name.slice(0, bracket);
4800
+ if (!isShellIdentifier(base) || bracket >= 0 && !name.endsWith("]"))
4801
+ return false;
4802
+ const resolved = this.referenceName(state, base);
4803
+ const binding = arrayStore(state)?.get(resolved);
4804
+ const entries = this.presenceArrayEntries(state, resolved);
4805
+ if (bracket < 0)
4806
+ return entries ? entries[0] !== undefined : this.variable(state, base) !== undefined;
4807
+ const selector = name.slice(bracket + 1, -1);
4808
+ if (binding?.associative) {
4809
+ if (selector.length === 0 || selector.length > 4096 || [...selector].some(character => "$`\\\"'".includes(character)))
4810
+ return undefined;
4811
+ const key = binding.keys.get(fastStringHexIdentity(selector));
4812
+ return key !== undefined && binding.getValue(key.index) !== undefined;
4813
+ }
4814
+ if (selector === "@" || selector === "*")
4815
+ return entries ? entries.length > 0 : binding ? binding.values.size > 0 : this.variable(state, base) !== undefined;
4816
+ if (selector.length === 0 || selector.length > 9 || [...selector].some(character => !"0123456789".includes(character)) || selector.length > 1 && selector[0] === "0")
4817
+ return undefined;
4818
+ const index = Number(selector);
4819
+ return entries ? entries[index] !== undefined : binding ? binding.getValue(index) !== undefined : index === 0 && this.variable(state, base) !== undefined;
4820
+ }
4733
4821
  requireParameter(value, name, state, io, line) {
4734
4822
  if (value === undefined && state.nounset)
4735
4823
  throw new NounsetFailure(`${name}: unbound variable`, io.diagnosticLine ?? line);
@@ -5187,24 +5275,162 @@ export class Runtime {
5187
5275
  }
5188
5276
  }
5189
5277
  }
5278
+ canSyncArrayMembersWord(w, rawState) {
5279
+ if (rawState.nounset)
5280
+ return false;
5281
+ const p0 = w.parts.length === 1 ? w.parts[0] : (w.parts.length === 2 && w.parts[0].kind === "text" && w.parts[0].value === "" ? w.parts[1] : undefined);
5282
+ if (!p0)
5283
+ return false;
5284
+ if (p0.kind !== "variable" || !p0.quoted || p0.indirect || p0.prefixNames || p0.length || p0.transform || p0.operator !== undefined)
5285
+ return false;
5286
+ const sel0 = getArraySelector(p0);
5287
+ if (!sel0 || (sel0.kind !== "members" && sel0.kind !== "keys") || sel0.separator !== "@")
5288
+ return false;
5289
+ if (p0.substring) {
5290
+ if (sel0.kind !== "members" || p0.keys)
5291
+ return false;
5292
+ const b = stateMonitor(rawState)?.store?.get(resolveSyncNameref(rawState, p0.name));
5293
+ if (b && b.associative)
5294
+ return false;
5295
+ if (!this.isPureSyncValueWord(p0.substring.offset, rawState))
5296
+ return false;
5297
+ if (p0.substring.length && !this.isPureSyncValueWord(p0.substring.length, rawState))
5298
+ return false;
5299
+ }
5300
+ return true;
5301
+ }
5302
+ tryExpandSyncArrayMembersWord(w, rawState, io, diagnosticLine) {
5303
+ if (rawState.nounset)
5304
+ return undefined;
5305
+ const p0 = w.parts.length === 1 ? w.parts[0] : (w.parts.length === 2 && w.parts[0].kind === "text" && w.parts[0].value === "" ? w.parts[1] : undefined);
5306
+ if (!p0)
5307
+ return undefined;
5308
+ if (p0.kind !== "variable" || !p0.quoted || p0.indirect || p0.prefixNames || p0.length || p0.transform || p0.operator !== undefined)
5309
+ return undefined;
5310
+ const sel0 = getArraySelector(p0);
5311
+ if (!sel0 || (sel0.kind !== "members" && sel0.kind !== "keys") || sel0.separator !== "@")
5312
+ return undefined;
5313
+ const monitor = stateMonitor(rawState);
5314
+ const activeStore = monitor?.store ?? arrayStore(rawState);
5315
+ const resolvedName = resolveSyncNameref(rawState, p0.name);
5316
+ const b = activeStore?.get(resolvedName);
5317
+ if (p0.substring) {
5318
+ if (sel0.kind !== "members" || p0.keys || (b && b.associative))
5319
+ return undefined;
5320
+ if (b && b.maximum >= 4096)
5321
+ return undefined;
5322
+ const evalSliceInt = (sw) => {
5323
+ if (sw.parts.length === 0)
5324
+ return 0;
5325
+ let expanded;
5326
+ try {
5327
+ expanded = this.fastValueWord(sw, rawState, io, false, false, false, false, undefined, diagnosticLine);
5328
+ }
5329
+ catch {
5330
+ return undefined;
5331
+ }
5332
+ if (typeof expanded !== "string")
5333
+ return undefined;
5334
+ const trimmed = expanded.trim();
5335
+ if (trimmed.length === 0)
5336
+ return 0;
5337
+ const syncTouched = this._syncArithRawWriteOnly ? this._syncArithTouched : undefined;
5338
+ const directOp = resolveSimpleArithOperand(trimmed, rawState, monitor, activeStore, syncTouched);
5339
+ if (directOp !== undefined)
5340
+ return directOp;
5341
+ const simpleExpr = tryEvalSimpleExpandedArith(trimmed, rawState, monitor, activeStore, syncTouched);
5342
+ if (simpleExpr !== undefined)
5343
+ return Number(simpleExpr);
5344
+ return undefined;
5345
+ };
5346
+ const offNum = evalSliceInt(p0.substring.offset);
5347
+ if (offNum === undefined)
5348
+ return undefined;
5349
+ let maxCount = Infinity;
5350
+ if (p0.substring.length) {
5351
+ const lenNum = evalSliceInt(p0.substring.length);
5352
+ if (lenNum === undefined || lenNum < 0)
5353
+ return undefined;
5354
+ maxCount = lenNum;
5355
+ }
5356
+ if (!b) {
5357
+ const sv = rawState.variables[resolvedName];
5358
+ if (sv === undefined)
5359
+ return [];
5360
+ const mv = (this._syncArithRawWriteOnly && this._syncArithTouched?.has(resolvedName)) ? sv : (monitor?.values.get(resolvedName, sv) ?? sv);
5361
+ if (typeof mv !== "string")
5362
+ return undefined;
5363
+ const startIdx = offNum < 0 ? 1 + offNum : offNum;
5364
+ if (startIdx < 0 || startIdx > 0 || maxCount === 0)
5365
+ return [];
5366
+ return [mv];
5367
+ }
5368
+ if (b.values.size === 0 || maxCount === 0)
5369
+ return [];
5370
+ const startIdx = offNum < 0 ? b.maximum + 1 + offNum : offNum;
5371
+ if (startIdx < 0)
5372
+ return [];
5373
+ const out = [];
5374
+ for (let k = startIdx; k <= b.maximum && out.length < maxCount; k++) {
5375
+ const slot = b.values.get(k);
5376
+ if (!slot)
5377
+ continue;
5378
+ const v = slot.text.shellValue;
5379
+ if (typeof v !== "string")
5380
+ return undefined;
5381
+ out.push(v);
5382
+ }
5383
+ return out;
5384
+ }
5385
+ const isKeys = sel0.kind === "keys" || p0.keys === true;
5386
+ if (b) {
5387
+ if (b.maximum >= 4096)
5388
+ return undefined;
5389
+ const out = [];
5390
+ for (let k = 0; k <= b.maximum; k++) {
5391
+ const slot = b.values.get(k);
5392
+ if (!slot)
5393
+ continue;
5394
+ const v = isKeys ? (b.associative ? b.keys.get(b.keyByIndex.get(k))?.text.shellValue : String(k)) : slot.text.shellValue;
5395
+ if (typeof v !== "string")
5396
+ return undefined;
5397
+ out.push(v);
5398
+ }
5399
+ return out;
5400
+ }
5401
+ const sv = rawState.variables[resolvedName];
5402
+ if (sv === undefined)
5403
+ return [];
5404
+ if (isKeys)
5405
+ return ["0"];
5406
+ const mv = (this._syncArithRawWriteOnly && this._syncArithTouched?.has(resolvedName)) ? sv : (monitor?.values.get(resolvedName, sv) ?? sv);
5407
+ if (typeof mv !== "string")
5408
+ return undefined;
5409
+ return [mv];
5410
+ }
5190
5411
  tryFastArrayAssignmentSync(assignment, state, io, diagnosticLine, declaration, associative, ignoreYield = false) {
5191
5412
  if (!ignoreYield && hasYieldCheckpoint(this.signal))
5192
5413
  return false;
5193
5414
  if (declaration)
5194
5415
  return false;
5416
+ const monitor = stateMonitor(state);
5417
+ const rawState = monitor ? monitor.raw : state;
5195
5418
  // Array writes must prepare scope observers before creating or mutating bindings.
5196
- const name = assignment.name;
5419
+ const name = resolveSyncNameref(rawState, assignment.name);
5197
5420
  if (hasUnpreparedLocal(state, name))
5198
5421
  return false;
5199
5422
  this._syncArithTouched?.delete(name);
5200
- const monitor = stateMonitor(state);
5201
- const rawState = monitor ? monitor.raw : state;
5202
5423
  if (rawState.readonlyVariables?.has(name) || rawState.variableAttributes?.get(name) || rawState.exported.has(name) || controlNames.has(name)) {
5203
5424
  return false;
5204
5425
  }
5205
5426
  // Cold activation belongs to normal command handling, which maps array refusals.
5206
- const store = monitor?.store ?? (monitor && !this.budget._arraySession?.hasActiveLedger ? requireArrays(rawState) : undefined);
5427
+ const store = monitor?.store ?? (monitor && (this.budget.limits.maxExpansionBytes === Infinity && this.budget.limits.maxExpansionFields === Infinity) ? requireArrays(rawState) : undefined);
5207
5428
  let current = store?.get(name);
5429
+ if (assignment.kind === "compound" && !(associative ?? current?.associative) && state.braceexpand !== false && assignment.entries.some(entry => {
5430
+ const original = compoundEntryWords.get(entry);
5431
+ return entry.index !== undefined && original?.parts.some(part => part.kind === "text" && !part.quoted && part.value.includes("{"));
5432
+ }))
5433
+ return false;
5208
5434
  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
5435
  return false;
5210
5436
  }
@@ -5253,7 +5479,7 @@ export class Runtime {
5253
5479
  let val = "";
5254
5480
  if (entry.value.parts.length > 0) {
5255
5481
  try {
5256
- val = this.fastValueWord(entry.value, rawState, io, false, false, false, false, undefined, diagnosticLine);
5482
+ val = this.fastValueWord(entry.value, rawState, io, false, false, false, false, entry.index === undefined ? undefined : 0, diagnosticLine);
5257
5483
  }
5258
5484
  catch {
5259
5485
  return false;
@@ -5301,6 +5527,8 @@ export class Runtime {
5301
5527
  const entry = assignment.entries[i];
5302
5528
  if (entry.append)
5303
5529
  return false;
5530
+ if (rawState.braceexpand !== false && entry.value.parts.some(p => !p.quoted && p.kind === "text" && p.value.includes("{")))
5531
+ return false;
5304
5532
  let explicitIdx;
5305
5533
  if (entry.index !== undefined) {
5306
5534
  const kSrc = (entry.index.source ?? entry.index.decimal).trim();
@@ -5308,13 +5536,26 @@ export class Runtime {
5308
5536
  return false;
5309
5537
  explicitIdx = Number(kSrc);
5310
5538
  }
5311
- else if (entry.value.parts.length === 0 || !entry.value.parts.every(p => p.quoted || (p.kind === "text" && p.value.length > 0 && !p.value.includes(" ") && !p.value.includes("\t") && !p.value.includes("\n") && !p.value.includes("{") && !hasGlobOrEscape(p.value, true)))) {
5312
- return false;
5539
+ else {
5540
+ const expandedArr = this.tryExpandSyncArrayMembersWord(entry.value, rawState, io, diagnosticLine);
5541
+ if (expandedArr !== undefined) {
5542
+ for (let mIdx = 0; mIdx < expandedArr.length; mIdx++) {
5543
+ const mVal = expandedArr[mIdx];
5544
+ const bLen = shellValueByteLength(mVal);
5545
+ if (bLen > this.budget.limits.maxExpansionBytes)
5546
+ return false;
5547
+ items.push({ explicitIdx: undefined, val: mVal, bLen });
5548
+ }
5549
+ continue;
5550
+ }
5551
+ if (entry.value.parts.length === 0 || !entry.value.parts.every(p => p.quoted || (p.kind === "text" && p.value.length > 0 && !p.value.includes(" ") && !p.value.includes("\t") && !p.value.includes("\n") && !p.value.includes("{") && !hasGlobOrEscape(p.value, true)))) {
5552
+ return false;
5553
+ }
5313
5554
  }
5314
5555
  let val = "";
5315
5556
  if (entry.value.parts.length > 0) {
5316
5557
  try {
5317
- val = this.fastValueWord(entry.value, rawState, io, false, false, false, false, undefined, diagnosticLine);
5558
+ val = this.fastValueWord(entry.value, rawState, io, false, false, false, false, entry.index === undefined ? undefined : 0, diagnosticLine);
5318
5559
  }
5319
5560
  catch {
5320
5561
  return false;
@@ -5723,7 +5964,7 @@ export class Runtime {
5723
5964
  continue;
5724
5965
  }
5725
5966
  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);
5967
+ 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
5968
  if (index !== undefined) {
5728
5969
  const value = await join(fields);
5729
5970
  await insert(index, value, entry.append);
@@ -6240,6 +6481,9 @@ export class Runtime {
6240
6481
  if (!watch.valid())
6241
6482
  throw new ArrayFailure("binding changed during writer admission");
6242
6483
  prepared.validate();
6484
+ // Clearing an indexed destination is an assignment even without elements.
6485
+ if (clear)
6486
+ prepared.binding.assigned = true;
6243
6487
  let retirement;
6244
6488
  monitor.publish(prepared.tickets, name, () => {
6245
6489
  delete state.variables[name];
@@ -8145,14 +8389,14 @@ export class Runtime {
8145
8389
  const r0 = command.redirects[0];
8146
8390
  const rOp = r0.operator;
8147
8391
  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="));
8392
+ 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
8393
  if (!isDevNull && w0Plain !== "echo" && w0Plain !== "printf" && !(isReadCmd && rOp === "<<<")) {
8150
8394
  pipeline._skipTrySync = true;
8151
8395
  return undefined;
8152
8396
  }
8153
8397
  }
8154
8398
  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))) {
8399
+ 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
8400
  return undefined;
8157
8401
  }
8158
8402
  }
@@ -8214,6 +8458,8 @@ export class Runtime {
8214
8458
  return true;
8215
8459
  }
8216
8460
  }
8461
+ // mapfile can require the asynchronous path as its input or binding
8462
+ // changes. Never speculate across a loop that has already made writes.
8217
8463
  }
8218
8464
  return false;
8219
8465
  }
@@ -8238,7 +8484,7 @@ export class Runtime {
8238
8484
  const fnBody = rawState.functions.get(w0Plain);
8239
8485
  return fnBody.kind === "group" && fnBody.redirects.length === 0 && this.canSyncScriptCompound(fnBody.body, rawState, depth + 1);
8240
8486
  }
8241
- const st = stateMonitor(rawState)?.store ?? (stateMonitor(rawState) && !this.budget._arraySession?.hasActiveLedger ? requireArrays(rawState) : undefined);
8487
+ const st = stateMonitor(rawState)?.store ?? (stateMonitor(rawState) && (this.budget.limits.maxExpansionBytes === Infinity && this.budget.limits.maxExpansionFields === Infinity) ? requireArrays(rawState) : undefined);
8242
8488
  const arrayAssign = getArrayAssignment(w0);
8243
8489
  if (arrayAssign) {
8244
8490
  if (!st)
@@ -8247,7 +8493,7 @@ export class Runtime {
8247
8493
  return false;
8248
8494
  }
8249
8495
  if (arrayAssign.kind === "compound") {
8250
- const isAssoc = Boolean(st?.get(arrayAssign.name)?.associative);
8496
+ const isAssoc = Boolean(st?.get(resolveSyncNameref(rawState, arrayAssign.name))?.associative);
8251
8497
  return (arrayAssign.entries.length <= 32 && arrayAssign.entries.every(e => {
8252
8498
  if (e.append)
8253
8499
  return false;
@@ -8259,13 +8505,15 @@ export class Runtime {
8259
8505
  const kSrc = (e.index.source ?? e.index.decimal).trim();
8260
8506
  return /^(?:0|[1-9][0-9]{0,8})$/.test(kSrc) && isNoBraceSyncAssignWord(e.value);
8261
8507
  }
8508
+ if (this.canSyncArrayMembersWord(e.value, rawState))
8509
+ return true;
8262
8510
  return isNoBraceSyncWord(e.value) && e.value.parts.length > 0 && e.value.parts.every(p => p.quoted || (p.kind === "text" && p.value.length > 0 && !p.value.includes(" ") && !p.value.includes("\t") && !p.value.includes("\n") && !p.value.includes("{") && !hasGlobOrEscape(p.value, true)));
8263
8511
  }));
8264
8512
  }
8265
8513
  return isNoBraceSyncAssignWord(arrayAssign.value);
8266
8514
  }
8267
8515
  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));
8516
+ 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
8517
  }
8270
8518
  if (w0Plain !== undefined && command.words.length <= 64 && rawState.functions.has(w0Plain) && this.firstInternalDiscovery(w0Plain, rawState, false) === "function" && !hasActiveExtensions(rawState)) {
8271
8519
  const fnBody = rawState.functions.get(w0Plain);
@@ -8292,7 +8540,7 @@ export class Runtime {
8292
8540
  }
8293
8541
  if (command.words.length >= 2 && command.words.length <= 16) {
8294
8542
  if (w0Plain === "unset" &&
8295
- rawState.locals.length === 0 &&
8543
+ depth === 0 && rawState.locals.length === 0 &&
8296
8544
  !hasShellFunction(rawState, "unset") &&
8297
8545
  !rawState.extensions?.builtins.has("unset")) {
8298
8546
  const uStart = command.words[1]?.plain === "-v" ? 2 : 1;
@@ -8316,6 +8564,7 @@ export class Runtime {
8316
8564
  }
8317
8565
  if (w0Plain === "export" &&
8318
8566
  rawState.locals.length === 0 &&
8567
+ depth === 0 &&
8319
8568
  !hasShellFunction(rawState, "export") &&
8320
8569
  !rawState.extensions?.builtins.has("export")) {
8321
8570
  let allOk = true;
@@ -8382,17 +8631,19 @@ export class Runtime {
8382
8631
  // Array locals require the typed declaration and restoration lifecycle.
8383
8632
  if (command.words[1]?.plain === "-a" || command.words[1]?.plain === "-A")
8384
8633
  return false;
8634
+ const isNamerefDecl = command.words[1]?.plain === "-n" && (rawState.locals.length > 0 || depth > 0) && command.words.length >= 3;
8635
+ const isGlobalDecl = command.words[1]?.plain === "-g" && (w0Plain === "declare" || w0Plain === "typeset") && command.words.length >= 3;
8385
8636
  let allValidLocal = true;
8386
- for (let idx = 1; idx < command.words.length; idx++) {
8637
+ for (let idx = (isNamerefDecl || isGlobalDecl) ? 2 : 1; idx < command.words.length; idx++) {
8387
8638
  const wArg = command.words[idx];
8388
8639
  const assignment = !getArrayAssignment(wArg) ? this.assignment(wArg) : undefined;
8389
8640
  if (assignment) {
8390
- if (assignment.name === "OPTIND" || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || !isNoBraceSyncAssignWord(assignment.value)) {
8641
+ if ((!isNamerefDecl && rawState.variableAttributes?.get(assignment.name)) || (assignment.name === "OPTIND" && (assignment.append || rawState.variableAttributes?.get("OPTIND"))) || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || !isNoBraceSyncAssignWord(assignment.value)) {
8391
8642
  allValidLocal = false;
8392
8643
  break;
8393
8644
  }
8394
8645
  }
8395
- else if (!wArg.plain || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(wArg.plain) || wArg.plain === "OPTIND" || wArg.plain === "PIPESTATUS") {
8646
+ 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
8647
  allValidLocal = false;
8397
8648
  break;
8398
8649
  }
@@ -8400,6 +8651,13 @@ export class Runtime {
8400
8651
  if (allValidLocal)
8401
8652
  return true;
8402
8653
  }
8654
+ if ((w0Plain === ":" || w0Plain === "true" || w0Plain === "false") && command.words.length <= 16 && !hasShellFunction(rawState, w0Plain) && !rawState.extensions?.builtins.has(w0Plain) && command.words.slice(1).every(isNoBraceSyncWord)) {
8655
+ return true;
8656
+ }
8657
+ // The scanner may require diagnostics or exceed its synchronous bounds.
8658
+ // Falling back after an earlier loop iteration would replay its effects.
8659
+ if (w0Plain === "getopts")
8660
+ return false;
8403
8661
  if (w0Plain === "echo" && !hasShellFunction(rawState, "echo") && !rawState.extensions?.builtins.has("echo")) {
8404
8662
  const w1Plain = command.words[1]?.plain;
8405
8663
  if (w1Plain === undefined || !w1Plain.startsWith("-") || w1Plain === "-n" || w1Plain === "-e" || w1Plain === "-ne" || w1Plain === "-en")
@@ -8430,7 +8688,17 @@ export class Runtime {
8430
8688
  }
8431
8689
  if (w0Plain !== undefined && rawState.functions.has(w0Plain) && command.words.slice(1).every(isNoBraceSyncWord)) {
8432
8690
  const fnBody = rawState.functions.get(w0Plain);
8433
- return fnBody.kind === "group" && fnBody.redirects.length === 0 && this.canSyncScriptCompound(fnBody.body, rawState, depth + 1);
8691
+ if (fnBody.kind !== "group" || fnBody.redirects.length !== 0)
8692
+ return false;
8693
+ rawState.locals.push(new Map());
8694
+ rawState.functionDepth++;
8695
+ try {
8696
+ return this.canSyncScriptCompound(fnBody.body, rawState, depth + 1);
8697
+ }
8698
+ finally {
8699
+ rawState.functionDepth--;
8700
+ rawState.locals.pop();
8701
+ }
8434
8702
  }
8435
8703
  return false;
8436
8704
  }
@@ -8458,8 +8726,35 @@ export class Runtime {
8458
8726
  return this.isPureSyncValueWord(e.operand, rawState);
8459
8727
  if (e.kind === "unary")
8460
8728
  return (e.operator === "-n" || e.operator === "-z" || e.operator === "-v") && this.isPureSyncValueWord(e.operand, rawState);
8461
- if (e.kind === "binary")
8729
+ if (e.kind === "binary") {
8730
+ if (e.operator === "=~") {
8731
+ if (!this.isPureSyncValueWord(e.left, rawState) || !this.extractSimpleErePattern(e.right) || rawState.readonlyVariables?.has("BASH_REMATCH") || rawState.exported.has("BASH_REMATCH") || hasUnpreparedLocal(rawState, "BASH_REMATCH"))
8732
+ return false;
8733
+ const mon = stateMonitor(rawState);
8734
+ if (!mon || (this.budget.limits.maxExpansionBytes !== Infinity || this.budget.limits.maxExpansionFields !== Infinity) || mon.hasOverlay("BASH_REMATCH"))
8735
+ return false;
8736
+ const st = mon.store ?? requireArrays(rawState);
8737
+ if (st.watches.has("BASH_REMATCH"))
8738
+ return false;
8739
+ let rem = st.get("BASH_REMATCH");
8740
+ if (!rem) {
8741
+ if (rawState.variables.BASH_REMATCH !== undefined)
8742
+ return false;
8743
+ const created = IndexedBinding.create(st.owner, false);
8744
+ const prepared = st.prepareExistingName("BASH_REMATCH", 12, st.owner, this.signal);
8745
+ if (!prepared) {
8746
+ void created.release();
8747
+ return false;
8748
+ }
8749
+ const initTickets = st.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
8750
+ st.publish("BASH_REMATCH", created, initTickets, prepared, false, st.owner);
8751
+ mon.epoch = initTickets.epoch;
8752
+ rem = created;
8753
+ }
8754
+ return !rem.associative && rem.references === 1;
8755
+ }
8462
8756
  return this.isPureSyncValueWord(e.left, rawState) && (e.operator === "==" || e.operator === "=" || e.operator === "!=" ? this.canSyncPatternWordParts(e.right, rawState) : this.isPureSyncValueWord(e.right, rawState));
8757
+ }
8463
8758
  if (e.kind === "not")
8464
8759
  return checkCondNoFile(e.operand);
8465
8760
  if (e.kind === "and" || e.kind === "or")
@@ -8497,13 +8792,13 @@ export class Runtime {
8497
8792
  const sel0 = p0?.kind === "variable" ? getArraySelector(p0) : undefined;
8498
8793
  const isArrAt = Boolean(p0?.kind === "variable" && p0.quoted && !p0.indirect && !p0.prefixNames && !p0.length && !p0.substring && !p0.transform && p0.operator === undefined && (sel0?.kind === "members" || sel0?.kind === "keys") && sel0.separator === "@");
8499
8794
  const isBraceRange = Boolean(p0?.kind === "text" && !p0.quoted && /^\{-?\d+\.\.-?\d+(?:\.\.-?\d+)?\}$/.test(p0.value));
8500
- if (!isArrAt && !isBraceRange && !(this.isPureSyncValueWord(w0, rawState) && w0.parts.every(p => p.quoted || (p.kind === "text" && !p.value.includes(" ") && !p.value.includes("\t") && !p.value.includes("\n") && !p.value.includes("{") && !hasGlobOrEscape(p.value, true)))))
8795
+ if (!isArrAt && !isBraceRange && !this.canSyncArrayMembersWord(w0, rawState) && !(this.isPureSyncValueWord(w0, rawState) && w0.parts.every(p => p.quoted || (p.kind === "text" && !p.value.includes(" ") && !p.value.includes("\t") && !p.value.includes("\n") && !p.value.includes("{") && !hasGlobOrEscape(p.value, true)))))
8501
8796
  return false;
8502
8797
  }
8503
8798
  else {
8504
8799
  for (let i = 0; i < command.words.length; i++) {
8505
8800
  const w = command.words[i];
8506
- if (!this.isPureSyncValueWord(w, rawState) || !w.parts.every(p => p.quoted || (p.kind === "text" && !p.value.includes(" ") && !p.value.includes("\t") && !p.value.includes("\n") && !p.value.includes("{") && !hasGlobOrEscape(p.value, true))))
8801
+ if (!this.canSyncArrayMembersWord(w, rawState) && (!this.isPureSyncValueWord(w, rawState) || !w.parts.every(p => p.quoted || (p.kind === "text" && !p.value.includes(" ") && !p.value.includes("\t") && !p.value.includes("\n") && !p.value.includes("{") && !hasGlobOrEscape(p.value, true)))))
8507
8802
  return false;
8508
8803
  }
8509
8804
  }
@@ -8610,7 +8905,9 @@ export class Runtime {
8610
8905
  }
8611
8906
  else if (wp0 === "local" || wp0 === "declare" || wp0 === "typeset") {
8612
8907
  const isArrDecl = c.words[1]?.plain === "-a" || c.words[1]?.plain === "-A";
8613
- for (let k = isArrDecl ? 2 : 1; k < c.words.length; k++) {
8908
+ const isNameref = c.words[1]?.plain === "-n";
8909
+ const isGlobal = c.words[1]?.plain === "-g";
8910
+ for (let k = (isArrDecl || isNameref || isGlobal) ? 2 : 1; k < c.words.length; k++) {
8614
8911
  const wk = c.words[k];
8615
8912
  const aa = getArrayAssignment(wk);
8616
8913
  if (aa) {
@@ -8627,6 +8924,9 @@ export class Runtime {
8627
8924
  if (wp0 === "local" || d > 0)
8628
8925
  localArrayVars.add(nm);
8629
8926
  }
8927
+ else if (isNameref) {
8928
+ localArrayVars.add(nm);
8929
+ }
8630
8930
  else {
8631
8931
  scalarVars.add(nm);
8632
8932
  const pv = asg?.value.plain;
@@ -8637,8 +8937,8 @@ export class Runtime {
8637
8937
  }
8638
8938
  }
8639
8939
  }
8640
- else if (wp0 === "printf" && c.words[1]?.plain === "-v" && c.words[2]?.plain) {
8641
- const target = c.words[2].plain;
8940
+ else if (wp0 === "printf" && c.words[1]?.plain === "-v" && (c.words[2]?.plain || c.words[2]?.parts[0]?.kind === "text")) {
8941
+ const target = c.words[2].plain ?? c.words[2].parts[0].value;
8642
8942
  const br = target.indexOf("[");
8643
8943
  if (br > 0)
8644
8944
  arrayVars.add(target.slice(0, br));
@@ -8647,6 +8947,19 @@ export class Runtime {
8647
8947
  nonIntScalarVars.add(target);
8648
8948
  }
8649
8949
  }
8950
+ else if (wp0 === "mapfile" || wp0 === "readarray") {
8951
+ arrayVars.add(c.words[2]?.plain ?? "MAPFILE");
8952
+ }
8953
+ else if (wp0 === "getopts") {
8954
+ const ov = c.words[2]?.plain;
8955
+ if (ov && isShellIdentifier(ov)) {
8956
+ scalarVars.add(ov);
8957
+ nonIntScalarVars.add(ov);
8958
+ }
8959
+ scalarVars.add("OPTIND");
8960
+ scalarVars.add("OPTARG");
8961
+ nonIntScalarVars.add("OPTARG");
8962
+ }
8650
8963
  else if (wp0 === "read" || (c.words[1]?.plain === "read")) {
8651
8964
  for (let k = 1; k < c.words.length; k++) {
8652
8965
  const vp = c.words[k]?.plain;
@@ -8828,36 +9141,8 @@ export class Runtime {
8828
9141
  if (expr.operator === "-z")
8829
9142
  return val.length === 0 ? 0 : 1;
8830
9143
  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;
9144
+ const present = this.tryVariablePresentSync(rawState, val);
9145
+ return present === undefined ? undefined : present ? 0 : 1;
8861
9146
  }
8862
9147
  if (syncConditionalFileUnaryOps.has(expr.operator)) {
8863
9148
  const fileRes = this.tryEvalFastMemoryFileUnary(expr.operator, val, rawState);
@@ -9016,7 +9301,24 @@ export class Runtime {
9016
9301
  }
9017
9302
  return undefined;
9018
9303
  }
9019
- finishSyncPipeStatus(rawState, monitor, store, existing, elem0, scope, finalStatus, restEpoch) {
9304
+ setSyncPipeStatusCell(existing, status) {
9305
+ const cell = existing.values.get(0);
9306
+ if (cell && cell.text.references === 1 && cell.text.bytes === status.length) {
9307
+ cell.text.shellValue = status;
9308
+ }
9309
+ else {
9310
+ existing.owner.chargeWork(status.length);
9311
+ const token = new OwnedText(status, status.length, existing.owner.reserve({ payload: status.length, metadata: 32, work: 4 }));
9312
+ try {
9313
+ existing.insert(0, token);
9314
+ }
9315
+ catch (error) {
9316
+ token.release();
9317
+ throw error;
9318
+ }
9319
+ }
9320
+ }
9321
+ finishSyncPipeStatus(rawState, monitor, store, existing, _elem0, scope, finalStatus, restEpoch) {
9020
9322
  if (!existing) {
9021
9323
  if (store) {
9022
9324
  if (publishPipelineStatus(rawState, finalStatus === 0 ? singleStatusZero : singleStatusOne, this.signal, scope))
@@ -9028,7 +9330,7 @@ export class Runtime {
9028
9330
  }
9029
9331
  }
9030
9332
  else {
9031
- (elem0 ?? existing.values.get(0)).text.shellValue = finalStatus === 0 ? "0" : "1";
9333
+ this.setSyncPipeStatusCell(existing, finalStatus === 0 ? "0" : "1");
9032
9334
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
9033
9335
  }
9034
9336
  rawState.status = finalStatus;
@@ -9050,7 +9352,7 @@ export class Runtime {
9050
9352
  return undefined;
9051
9353
  const rawState = monitor.raw;
9052
9354
  const gArrays = guestArrays(rawState);
9053
- if (io.terminal || io.asyncDefaultInput || hasActiveExtensions(rawState) || hasActiveVariableAttributes(rawState) || (gArrays && gArrays.watches.size > 0))
9355
+ if (io.terminal || io.asyncDefaultInput || hasActiveExtensions(rawState) || hasNonNamerefAttributes(rawState) || (gArrays && gArrays.watches.size > 0))
9054
9356
  return undefined;
9055
9357
  if (((this.budget.commands + 1) & 127) === 0) {
9056
9358
  if (hasYieldCheckpoint(this.signal) || (this._syncReturnDepth === 0 && ((this.budget.commands + 1) & 8191) === 0))
@@ -9067,7 +9369,7 @@ export class Runtime {
9067
9369
  let existing = store?.get("PIPESTATUS");
9068
9370
  if (store && !existing) {
9069
9371
  const initTarget = pipelineStatusTarget(rawState);
9070
- if (!this.budget._arraySession?.hasActiveLedger &&
9372
+ if ((this.budget.limits.maxExpansionBytes === Infinity && this.budget.limits.maxExpansionFields === Infinity) &&
9071
9373
  (initTarget === "indexed" || initTarget === "absent") &&
9072
9374
  !store.watches.has("PIPESTATUS") &&
9073
9375
  !monitor.hasOverlay("PIPESTATUS") &&
@@ -9093,10 +9395,10 @@ export class Runtime {
9093
9395
  return undefined;
9094
9396
  }
9095
9397
  const elem0 = existing?.values.get(0);
9096
- if (existing && (!elem0 || elem0.text.bytes !== 1))
9398
+ if (existing && !elem0)
9097
9399
  return undefined;
9098
9400
  const canMutatePipeStatus = !existing || (existing.references === 1 && elem0.text.references === 1);
9099
- const diagnosticLine = io.diagnosticCommandLines?.get(command) ?? (command.line ?? 1) + (io.diagnosticOffset ?? 0);
9401
+ const diagnosticLine = io.diagnosticCommandLines?.get(command) ?? io.substitutionDiagnosticLines?.get(command) ?? (command.line ?? 1) + (io.diagnosticOffset ?? 0);
9100
9402
  if (command.kind === "function") {
9101
9403
  if (pipeline.negate || !canMutatePipeStatus || command.redirects.length > 0 || rawState.readonlyFunctions?.has(command.name) || (rawState.profile === "sh" && (specialBuiltinNames.has(command.name) || rawState.extensions?.builtins.get(command.name)?.special))) {
9102
9404
  return undefined;
@@ -9159,7 +9461,7 @@ export class Runtime {
9159
9461
  }
9160
9462
  }
9161
9463
  else {
9162
- activeExisting.values.get(0).text.shellValue = finalStatus === 0 ? "0" : "1";
9464
+ this.setSyncPipeStatusCell(activeExisting, finalStatus === 0 ? "0" : "1");
9163
9465
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
9164
9466
  }
9165
9467
  rawState.status = finalStatus;
@@ -9265,13 +9567,13 @@ export class Runtime {
9265
9567
  }
9266
9568
  const scalarMutMatch = fastSrc.length <= 64 ? SIMPLE_SCALAR_MUT_RE.exec(fastSrc) : null;
9267
9569
  if (scalarMutMatch) {
9268
- const varName = scalarMutMatch[1];
9570
+ const varName = resolveSyncNameref(rawState, scalarMutMatch[1]);
9269
9571
  const binOp = scalarMutMatch[2];
9270
9572
  const rhs1 = Number(scalarMutMatch[3]);
9271
9573
  const rhsOp = scalarMutMatch[4];
9272
9574
  const rhs2 = scalarMutMatch[5] !== undefined ? Number(scalarMutMatch[5]) : 0;
9273
9575
  const rhsNum = rhsOp === "+" ? rhs1 + rhs2 : rhsOp === "-" ? rhs1 - rhs2 : rhsOp === "*" ? rhs1 * rhs2 : rhs1;
9274
- if (varName !== "OPTIND" && varName !== "PIPESTATUS" && !store?.get(varName) && !hasActiveVariableAttributes(rawState)) {
9576
+ if (varName !== "OPTIND" && varName !== "PIPESTATUS" && !store?.get(varName) && !hasNonNamerefAttributes(rawState)) {
9275
9577
  const curVal = (this._syncArithRawWriteOnly && this._syncArithTouched?.has(varName))
9276
9578
  ? (rawState.variables[varName] ?? "0")
9277
9579
  : (monitor.values.get(varName, rawState.variables[varName] ?? "0") ?? rawState.variables[varName] ?? "0");
@@ -9353,7 +9655,15 @@ export class Runtime {
9353
9655
  }
9354
9656
  const dynProg = prepareArithmetic(fastSrc, this.budget.parsing);
9355
9657
  if (!dynProg.error && isSafeSmiProgram(dynProg) && this.canSyncArithmeticWithoutFault(dynProg.tree, rawState, diagnosticLine)) {
9356
- const nonZero = this.syncShellArithmeticNonZero(dynProg, rawState, diagnosticLine);
9658
+ let nonZero;
9659
+ try {
9660
+ nonZero = this.syncShellArithmeticNonZero(dynProg, rawState, diagnosticLine);
9661
+ }
9662
+ catch (err) {
9663
+ if (err instanceof ArrayFailure)
9664
+ return undefined;
9665
+ throw err;
9666
+ }
9357
9667
  if (rawState.extensions && !rawState.extensions.eventDepth)
9358
9668
  publishCommandSpelling(rawState, commandSpelling(command));
9359
9669
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
@@ -9366,7 +9676,15 @@ export class Runtime {
9366
9676
  }
9367
9677
  }
9368
9678
  if (!expr.error && isSafeSmiProgram(expr) && this.canSyncArithmeticWithoutFault(expr.tree, rawState, diagnosticLine)) {
9369
- const nonZero = this.syncShellArithmeticNonZero(expr, rawState, diagnosticLine);
9679
+ let nonZero;
9680
+ try {
9681
+ nonZero = this.syncShellArithmeticNonZero(expr, rawState, diagnosticLine);
9682
+ }
9683
+ catch (err) {
9684
+ if (err instanceof ArrayFailure)
9685
+ return undefined;
9686
+ throw err;
9687
+ }
9370
9688
  if (rawState.extensions && !rawState.extensions.eventDepth)
9371
9689
  publishCommandSpelling(rawState, commandSpelling(command));
9372
9690
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
@@ -9425,9 +9743,12 @@ export class Runtime {
9425
9743
  }
9426
9744
  return res;
9427
9745
  }
9428
- const fastLoopRes = this.trySyncLoop(command, pipeline, rawState, monitor, store, existing, elem0, canMutatePipeStatus, io, diagnosticLine);
9429
- if (fastLoopRes !== undefined)
9430
- return fastLoopRes;
9746
+ if (!command._skipTrySyncLoop) {
9747
+ const fastLoopRes = this.trySyncLoop(command, pipeline, rawState, monitor, store, existing, elem0, canMutatePipeStatus, io, diagnosticLine);
9748
+ if (fastLoopRes !== undefined)
9749
+ return fastLoopRes;
9750
+ command._skipTrySyncLoop = true;
9751
+ }
9431
9752
  }
9432
9753
  if (command.kind === "group" || command.kind === "if" || command.kind === "case" || command.kind === "while" || command.kind === "until" || command.kind === "for" || command.kind === "arithmetic-for") {
9433
9754
  if (command.redirects.length !== 0 || pipeline.negate || !canMutatePipeStatus || (!ignored && rawState.errexit) || rawState.nounset || rawState.readonlyVariables?.size || hasYieldCheckpoint(this.signal) || (this._syncReturnDepth === 0 && (((this.budget.commands + 32) & 8191) < 32 || !this.canSyncCommandCompound(command, rawState, 0, rawState.loopDepth)))) {
@@ -9488,6 +9809,12 @@ export class Runtime {
9488
9809
  if (command.kind === "while" || command.kind === "until") {
9489
9810
  this.budget.tick();
9490
9811
  let lastStatus = 0;
9812
+ const enableBatchRawWrite = !this._syncArithRawWriteOnly && !hasActiveExtensions(rawState) && !hasActiveVariableAttributes(rawState) && !rawState.readonlyVariables?.size && !rawState.allexport;
9813
+ const batchTouched = enableBatchRawWrite ? new Set() : undefined;
9814
+ if (enableBatchRawWrite) {
9815
+ this._syncArithRawWriteOnly = true;
9816
+ this._syncArithTouched = batchTouched;
9817
+ }
9491
9818
  rawState.loopDepth++;
9492
9819
  this._syncReturnDepth++;
9493
9820
  try {
@@ -9544,6 +9871,17 @@ export class Runtime {
9544
9871
  finally {
9545
9872
  this._syncReturnDepth--;
9546
9873
  rawState.loopDepth--;
9874
+ if (enableBatchRawWrite && batchTouched) {
9875
+ this._syncArithRawWriteOnly = false;
9876
+ this._syncArithTouched = undefined;
9877
+ for (const vName of batchTouched) {
9878
+ const vVal = rawState.variables[vName];
9879
+ if (typeof vVal === "string")
9880
+ monitor.publishStringVariable(vName, vVal);
9881
+ else if (vVal === undefined)
9882
+ delete monitor.proxy.variables[vName];
9883
+ }
9884
+ }
9547
9885
  }
9548
9886
  rawState.status = lastStatus;
9549
9887
  return lastStatus;
@@ -9554,12 +9892,18 @@ export class Runtime {
9554
9892
  return undefined;
9555
9893
  }
9556
9894
  this.budget.tick();
9557
- if (e0)
9558
- this.syncShellArithmeticNonZero(e0, rawState, diagnosticLine);
9895
+ const enableBatchRawWrite = !this._syncArithRawWriteOnly && !hasActiveExtensions(rawState) && !hasActiveVariableAttributes(rawState) && !rawState.readonlyVariables?.size && !rawState.allexport;
9896
+ const batchTouched = enableBatchRawWrite ? new Set() : undefined;
9897
+ if (enableBatchRawWrite) {
9898
+ this._syncArithRawWriteOnly = true;
9899
+ this._syncArithTouched = batchTouched;
9900
+ }
9559
9901
  let lastStatus = 0;
9560
9902
  rawState.loopDepth++;
9561
9903
  this._syncReturnDepth++;
9562
9904
  try {
9905
+ if (e0)
9906
+ this.syncShellArithmeticNonZero(e0, rawState, diagnosticLine);
9563
9907
  let iter = 0;
9564
9908
  while (true) {
9565
9909
  if (++iter > 4096)
@@ -9603,6 +9947,17 @@ export class Runtime {
9603
9947
  finally {
9604
9948
  this._syncReturnDepth--;
9605
9949
  rawState.loopDepth--;
9950
+ if (enableBatchRawWrite && batchTouched) {
9951
+ this._syncArithRawWriteOnly = false;
9952
+ this._syncArithTouched = undefined;
9953
+ for (const vName of batchTouched) {
9954
+ const vVal = rawState.variables[vName];
9955
+ if (typeof vVal === "string")
9956
+ monitor.publishStringVariable(vName, vVal);
9957
+ else if (vVal === undefined)
9958
+ delete monitor.proxy.variables[vName];
9959
+ }
9960
+ }
9606
9961
  }
9607
9962
  rawState.status = lastStatus;
9608
9963
  return lastStatus;
@@ -9615,10 +9970,10 @@ export class Runtime {
9615
9970
  }
9616
9971
  else if (command.words.length === 1) {
9617
9972
  const w0 = command.words[0];
9618
- const nonEmpty0 = w0.parts.filter(p => !(p.kind === "text" && p.value === ""));
9619
- const p0 = nonEmpty0.length === 1 ? nonEmpty0[0] : undefined;
9973
+ items = this.tryExpandSyncArrayMembersWord(w0, rawState, io, diagnosticLine);
9974
+ const p0 = items ? undefined : (w0.parts.length === 1 ? w0.parts[0] : (w0.parts.length === 2 && w0.parts[0].kind === "text" && w0.parts[0].value === "" ? w0.parts[1] : undefined));
9620
9975
  const sel0 = p0?.kind === "variable" ? getArraySelector(p0) : undefined;
9621
- if (p0?.kind === "variable" && p0.quoted && (sel0?.kind === "members" || sel0?.kind === "keys") && sel0.separator === "@") {
9976
+ if (p0?.kind === "variable" && p0.quoted && !p0.substring && (sel0?.kind === "members" || sel0?.kind === "keys") && sel0.separator === "@") {
9622
9977
  const b = monitor.store?.get(p0.name);
9623
9978
  items = [];
9624
9979
  if (b) {
@@ -9657,6 +10012,12 @@ export class Runtime {
9657
10012
  if (!items && command.words) {
9658
10013
  items = [];
9659
10014
  for (let i = 0; i < command.words.length; i++) {
10015
+ const expandedArr = this.tryExpandSyncArrayMembersWord(command.words[i], rawState, io, diagnosticLine);
10016
+ if (expandedArr !== undefined) {
10017
+ for (let mIdx = 0; mIdx < expandedArr.length; mIdx++)
10018
+ items.push(expandedArr[mIdx]);
10019
+ continue;
10020
+ }
9660
10021
  let fv;
9661
10022
  try {
9662
10023
  fv = this.fastValueWord(command.words[i], rawState, io, false, false, false, false, undefined, diagnosticLine);
@@ -9673,6 +10034,12 @@ export class Runtime {
9673
10034
  return undefined;
9674
10035
  this.budget.tick();
9675
10036
  let lastStatus = 0;
10037
+ const enableBatchRawWrite = !this._syncArithRawWriteOnly && !hasActiveExtensions(rawState) && !hasActiveVariableAttributes(rawState) && !rawState.readonlyVariables?.size && !rawState.allexport;
10038
+ const batchTouched = enableBatchRawWrite ? new Set() : undefined;
10039
+ if (enableBatchRawWrite) {
10040
+ this._syncArithRawWriteOnly = true;
10041
+ this._syncArithTouched = batchTouched;
10042
+ }
9676
10043
  rawState.loopDepth++;
9677
10044
  this._syncReturnDepth++;
9678
10045
  try {
@@ -9683,7 +10050,12 @@ export class Runtime {
9683
10050
  runYieldCheckpoint(this.signal);
9684
10051
  }
9685
10052
  this.budget.loop();
9686
- monitor.publishStringVariable(command.name, items[idx]);
10053
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10054
+ rawState.variables[command.name] = items[idx];
10055
+ this._syncArithTouched.add(command.name);
10056
+ }
10057
+ else
10058
+ monitor.publishStringVariable(command.name, items[idx]);
9687
10059
  if (rawState.allexport)
9688
10060
  rawState.exported.add(command.name);
9689
10061
  const bodyRes = this.trySyncScript(command.body, state, io, ignored);
@@ -9709,6 +10081,17 @@ export class Runtime {
9709
10081
  finally {
9710
10082
  this._syncReturnDepth--;
9711
10083
  rawState.loopDepth--;
10084
+ if (enableBatchRawWrite && batchTouched) {
10085
+ this._syncArithRawWriteOnly = false;
10086
+ this._syncArithTouched = undefined;
10087
+ for (const vName of batchTouched) {
10088
+ const vVal = rawState.variables[vName];
10089
+ if (typeof vVal === "string")
10090
+ monitor.publishStringVariable(vName, vVal);
10091
+ else if (vVal === undefined)
10092
+ delete monitor.proxy.variables[vName];
10093
+ }
10094
+ }
9712
10095
  }
9713
10096
  rawState.status = lastStatus;
9714
10097
  return lastStatus;
@@ -9837,7 +10220,7 @@ export class Runtime {
9837
10220
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
9838
10221
  }
9839
10222
  else {
9840
- elem0.text.shellValue = "0";
10223
+ this.setSyncPipeStatusCell(existing, "0");
9841
10224
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
9842
10225
  }
9843
10226
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -9934,7 +10317,7 @@ export class Runtime {
9934
10317
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
9935
10318
  }
9936
10319
  else {
9937
- elem0.text.shellValue = "0";
10320
+ this.setSyncPipeStatusCell(existing, "0");
9938
10321
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
9939
10322
  }
9940
10323
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -9950,8 +10333,6 @@ export class Runtime {
9950
10333
  if (w0Plain === "return" && (rawState.functionDepth > 0 || rawState.sourceDepth) && !hasActiveExtensions(rawState) && !hasShellFunction(rawState, "return") && !rawState.extensions?.builtins.has("return") && !pipeline.negate && canMutatePipeStatus) {
9951
10334
  const retStatus = rawState.status;
9952
10335
  const statusStr = retStatus === 0 ? "0" : retStatus === 1 ? "1" : String(retStatus);
9953
- if (existing && elem0.text.bytes !== statusStr.length)
9954
- return undefined;
9955
10336
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
9956
10337
  this.budget.tick();
9957
10338
  rawState.substitutionStatus = 0;
@@ -9965,7 +10346,7 @@ export class Runtime {
9965
10346
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
9966
10347
  }
9967
10348
  else {
9968
- elem0.text.shellValue = statusStr;
10349
+ this.setSyncPipeStatusCell(existing, statusStr);
9969
10350
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
9970
10351
  }
9971
10352
  rawState.status = retStatus;
@@ -9997,7 +10378,7 @@ export class Runtime {
9997
10378
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
9998
10379
  }
9999
10380
  else {
10000
- elem0.text.shellValue = "0";
10381
+ this.setSyncPipeStatusCell(existing, "0");
10001
10382
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10002
10383
  }
10003
10384
  rawState.status = 0;
@@ -10029,7 +10410,7 @@ export class Runtime {
10029
10410
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10030
10411
  }
10031
10412
  else {
10032
- elem0.text.shellValue = statusChar;
10413
+ this.setSyncPipeStatusCell(existing, statusChar);
10033
10414
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10034
10415
  }
10035
10416
  rawState.status = finalStatus;
@@ -10070,7 +10451,7 @@ export class Runtime {
10070
10451
  }
10071
10452
  }
10072
10453
  else {
10073
- activeExisting.values.get(0).text.shellValue = "0";
10454
+ this.setSyncPipeStatusCell(activeExisting, "0");
10074
10455
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10075
10456
  }
10076
10457
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10082,14 +10463,15 @@ export class Runtime {
10082
10463
  }
10083
10464
  const assignment = this.assignment(w0);
10084
10465
  if (assignment) {
10466
+ const targetAssignName = resolveSyncNameref(rawState, assignment.name);
10085
10467
  const hasSubPart = assignment.value.parts.some(part => part.kind === "substitution");
10086
- const existingArrAssign = store?.get(assignment.name);
10087
- if (assignment.name === "OPTIND" ||
10088
- assignment.name === "PIPESTATUS" ||
10089
- assignment.name.includes("[") ||
10468
+ const existingArrAssign = store?.get(targetAssignName);
10469
+ if ((targetAssignName === "OPTIND" && (assignment.append || rawState.variableAttributes?.get("OPTIND"))) ||
10470
+ targetAssignName === "PIPESTATUS" ||
10471
+ targetAssignName.includes("[") ||
10090
10472
  (hasSubPart && !this.isPureSyncValueWord(assignment.value, rawState)) ||
10091
- rawState.readonlyVariables?.has(assignment.name) ||
10092
- (existingArrAssign && (assignment.append || hasUnpreparedLocal(rawState, assignment.name) || rawState.exported.has(assignment.name)))) {
10473
+ rawState.readonlyVariables?.has(targetAssignName) ||
10474
+ (existingArrAssign && (assignment.append || hasUnpreparedLocal(rawState, targetAssignName) || rawState.exported.has(targetAssignName)))) {
10093
10475
  return undefined;
10094
10476
  }
10095
10477
  if (rawState.extensions && !rawState.extensions.eventDepth)
@@ -10103,8 +10485,14 @@ export class Runtime {
10103
10485
  }
10104
10486
  if (typeof fastAssigned !== "string")
10105
10487
  return undefined;
10488
+ if (targetAssignName === "OPTIND") {
10489
+ const trimmedOptind = fastAssigned.trim();
10490
+ if (!/^[0-9]{1,6}$/.test(trimmedOptind))
10491
+ return undefined;
10492
+ fastAssigned = String(Number(trimmedOptind));
10493
+ }
10106
10494
  if (existingArrAssign) {
10107
- if (!this.tryFastArraySubscriptWriteSync(rawState, assignment.name, "0", fastAssigned))
10495
+ if (!this.tryFastArraySubscriptWriteSync(rawState, targetAssignName, "0", fastAssigned))
10108
10496
  return undefined;
10109
10497
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10110
10498
  this.budget.tick();
@@ -10128,7 +10516,7 @@ export class Runtime {
10128
10516
  }
10129
10517
  }
10130
10518
  else {
10131
- activeExisting.values.get(0).text.shellValue = "0";
10519
+ this.setSyncPipeStatusCell(activeExisting, "0");
10132
10520
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10133
10521
  }
10134
10522
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10140,8 +10528,8 @@ export class Runtime {
10140
10528
  }
10141
10529
  let finalAssigned = fastAssigned;
10142
10530
  if (assignment.append) {
10143
- const curRaw = rawState.variables[assignment.name];
10144
- const curStr = curRaw === undefined ? "" : (monitor.values.get(assignment.name, curRaw) ?? curRaw);
10531
+ const curRaw = rawState.variables[targetAssignName];
10532
+ const curStr = curRaw === undefined ? "" : (monitor.values.get(targetAssignName, curRaw) ?? curRaw);
10145
10533
  if (typeof curStr !== "string")
10146
10534
  return undefined;
10147
10535
  finalAssigned = curStr + finalAssigned;
@@ -10152,17 +10540,21 @@ export class Runtime {
10152
10540
  this.budget.tick();
10153
10541
  if (!hasSubPart)
10154
10542
  rawState.substitutionStatus = 0;
10155
- if (assignment.name !== "_" && rawState.variables._ !== undefined)
10543
+ if (targetAssignName !== "_" && rawState.variables._ !== undefined)
10156
10544
  delete rawState.variables._;
10157
10545
  rawState.lastArgument = "";
10158
10546
  if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10159
- rawState.variables[assignment.name] = finalAssigned;
10160
- this._syncArithTouched.add(assignment.name);
10547
+ rawState.variables[targetAssignName] = finalAssigned;
10548
+ this._syncArithTouched.add(targetAssignName);
10161
10549
  }
10162
10550
  else
10163
- monitor.publishStringVariable(assignment.name, finalAssigned);
10551
+ monitor.publishStringVariable(targetAssignName, finalAssigned);
10552
+ if (targetAssignName === "OPTIND") {
10553
+ rawState.variables.OPTIND = finalAssigned;
10554
+ this.syncGetopts(rawState);
10555
+ }
10164
10556
  if (rawState.allexport)
10165
- monitor.proxy.exported.add(assignment.name);
10557
+ monitor.proxy.exported.add(targetAssignName);
10166
10558
  if (io.assignmentDiagnosticContext)
10167
10559
  io.assignmentDiagnosticContext.name = undefined;
10168
10560
  if (!existing) {
@@ -10170,7 +10562,7 @@ export class Runtime {
10170
10562
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10171
10563
  }
10172
10564
  else {
10173
- elem0.text.shellValue = "0";
10565
+ this.setSyncPipeStatusCell(existing, "0");
10174
10566
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10175
10567
  }
10176
10568
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10186,60 +10578,234 @@ export class Runtime {
10186
10578
  if (command.words.length >= 1) {
10187
10579
  const w0Plain = command.words[0].plain;
10188
10580
  if (this.budget.limits.maxExpansionFields === Infinity && command.words.length >= 4 && w0Plain === "printf" && command.words[1]?.plain === "-v" && command.redirects.length === 0 && canMutatePipeStatus && (!pipeline.negate || ignored || !rawState.errexit) && !hasShellFunction(rawState, "printf") && !rawState.extensions?.builtins.has("printf")) {
10189
- const targetVar = command.words[2]?.plain;
10190
10581
  const def = this.commands.get("printf");
10191
- if (targetVar && isShellIdentifier(targetVar) && targetVar !== "OPTIND" && targetVar !== "PIPESTATUS" && targetVar !== "_" && !rawState.readonlyVariables?.has(targetVar) && !store?.get(targetVar) && !monitor.hasOverlay(targetVar) && def && def.execute === printfCommand.execute && command.words.length <= this.budget.maxExpansionFieldsSmi && this.arePureArgWords(command.words, rawState)) {
10192
- fastSubScratchArgs.length = 0;
10193
- let allStrings = true;
10194
- try {
10195
- for (let i = 3; i < command.words.length; i++) {
10196
- const v = this.fastValueWord(command.words[i], rawState, io, true, false, false, true, undefined, diagnosticLine);
10197
- if (typeof v !== "string") {
10582
+ if (def && def.execute === printfCommand.execute && command.words.length <= this.budget.maxExpansionFieldsSmi && this.arePureArgWords(command.words, rawState)) {
10583
+ let targetSpec = command.words[2]?.plain;
10584
+ if (targetSpec === undefined) {
10585
+ try {
10586
+ targetSpec = this.fastValueWord(command.words[2], rawState, io, true, false, false, true, undefined, diagnosticLine);
10587
+ }
10588
+ catch {
10589
+ targetSpec = undefined;
10590
+ }
10591
+ }
10592
+ if (typeof targetSpec === "string") {
10593
+ const openBr = targetSpec.indexOf("[");
10594
+ const isSubTarget = openBr > 0 && targetSpec.endsWith("]") && targetSpec.length > openBr + 2;
10595
+ const rawTargetVar = isSubTarget ? targetSpec.slice(0, openBr) : targetSpec;
10596
+ const targetVar = isShellIdentifier(rawTargetVar) ? resolveSyncNameref(rawState, rawTargetVar) : "";
10597
+ if (targetVar && isShellIdentifier(targetVar) && targetVar !== "OPTIND" && targetVar !== "PIPESTATUS" && targetVar !== "_" && !rawState.readonlyVariables?.has(targetVar) && !monitor.hasOverlay(targetVar) && (isSubTarget || !store?.get(targetVar))) {
10598
+ fastSubScratchArgs.length = 0;
10599
+ let allStrings = true;
10600
+ try {
10601
+ for (let i = 3; i < command.words.length; i++) {
10602
+ const v = this.fastValueWord(command.words[i], rawState, io, true, false, false, true, undefined, diagnosticLine);
10603
+ if (typeof v !== "string") {
10604
+ allStrings = false;
10605
+ break;
10606
+ }
10607
+ fastSubScratchArgs.push(v);
10608
+ }
10609
+ }
10610
+ catch {
10198
10611
  allStrings = false;
10199
- break;
10200
10612
  }
10201
- fastSubScratchArgs.push(v);
10613
+ const lastArg = fastSubScratchArgs.length > 0 ? fastSubScratchArgs[fastSubScratchArgs.length - 1] : targetSpec;
10614
+ const formatted = allStrings ? tryFastPrintf(fastSubScratchArgs) : undefined;
10615
+ fastSubScratchArgs.length = 0;
10616
+ if (formatted !== undefined && formatted.length <= this.budget.limits.maxExpansionBytes && (formatted.length * 3 <= this.budget.limits.maxExpansionBytes || shellValueByteLength(formatted) <= this.budget.limits.maxExpansionBytes)) {
10617
+ let wroteTarget = true;
10618
+ if (isSubTarget) {
10619
+ let subKey = targetSpec.slice(openBr + 1, -1);
10620
+ const existingArr = store?.get(targetVar);
10621
+ if (!existingArr?.associative && !/^(?:0|[1-9][0-9]{0,8})$/.test(subKey)) {
10622
+ const syncTouched = this._syncArithRawWriteOnly ? this._syncArithTouched : undefined;
10623
+ const directIdx = resolveSimpleArithOperand(subKey.trim(), rawState, monitor, store, syncTouched);
10624
+ if (directIdx !== undefined && directIdx >= 0)
10625
+ subKey = String(directIdx);
10626
+ else {
10627
+ const exprIdx = tryEvalSimpleExpandedArith(subKey.trim(), rawState, monitor, store, syncTouched);
10628
+ if (exprIdx !== undefined && Number(exprIdx) >= 0)
10629
+ subKey = exprIdx;
10630
+ else
10631
+ wroteTarget = false;
10632
+ }
10633
+ }
10634
+ if (wroteTarget && !this.tryFastArraySubscriptWriteSync(rawState, targetVar, subKey, formatted))
10635
+ wroteTarget = false;
10636
+ }
10637
+ else if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10638
+ rawState.variables[targetVar] = formatted;
10639
+ this._syncArithTouched.add(targetVar);
10640
+ }
10641
+ else
10642
+ monitor.publishStringVariable(targetVar, formatted);
10643
+ if (wroteTarget) {
10644
+ if (rawState.extensions && !rawState.extensions.eventDepth)
10645
+ publishCommandSpelling(rawState, commandSpelling(command));
10646
+ const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10647
+ this.budget.tick();
10648
+ rawState.substitutionStatus = 0;
10649
+ if (rawState.variables._ !== undefined)
10650
+ delete rawState.variables._;
10651
+ rawState.lastArgument = lastArg;
10652
+ if (!isSubTarget && rawState.allexport)
10653
+ monitor.proxy.exported.add(targetVar);
10654
+ if (io.assignmentDiagnosticContext)
10655
+ io.assignmentDiagnosticContext.name = undefined;
10656
+ if (!existing) {
10657
+ monitor.lazyPipeStatus = singleStatusZero;
10658
+ monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10659
+ }
10660
+ else {
10661
+ this.setSyncPipeStatusCell(existing, "0");
10662
+ store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10663
+ }
10664
+ const finalStatus = pipeline.negate ? 1 : 0;
10665
+ rawState.status = finalStatus;
10666
+ monitor.epoch = restEpoch;
10667
+ if (store)
10668
+ store.epoch = restEpoch;
10669
+ return finalStatus;
10670
+ }
10671
+ }
10202
10672
  }
10203
10673
  }
10204
- catch {
10205
- allStrings = false;
10206
- }
10207
- const lastArg = fastSubScratchArgs.length > 0 ? fastSubScratchArgs[fastSubScratchArgs.length - 1] : targetVar;
10208
- const formatted = allStrings ? tryFastPrintf(fastSubScratchArgs) : undefined;
10209
- fastSubScratchArgs.length = 0;
10210
- if (formatted !== undefined && formatted.length <= this.budget.limits.maxExpansionBytes && (formatted.length * 3 <= this.budget.limits.maxExpansionBytes || shellValueByteLength(formatted) <= this.budget.limits.maxExpansionBytes)) {
10211
- if (rawState.extensions && !rawState.extensions.eventDepth)
10212
- publishCommandSpelling(rawState, commandSpelling(command));
10213
- const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10214
- this.budget.tick();
10215
- rawState.substitutionStatus = 0;
10216
- if (rawState.variables._ !== undefined)
10217
- delete rawState.variables._;
10218
- rawState.lastArgument = lastArg;
10219
- if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10220
- rawState.variables[targetVar] = formatted;
10221
- this._syncArithTouched.add(targetVar);
10674
+ }
10675
+ }
10676
+ 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)) {
10677
+ const rawStatus = w0Plain === "false" ? 1 : 0;
10678
+ const statusChar = rawStatus === 0 ? "0" : "1";
10679
+ if (!canMutatePipeStatus && elem0.text.shellValue !== statusChar)
10680
+ return undefined;
10681
+ const finalStatus = pipeline.negate ? Number(rawStatus === 0) : rawStatus;
10682
+ if (finalStatus !== 0 && !ignored && rawState.errexit)
10683
+ return undefined;
10684
+ let lastArg = w0Plain;
10685
+ let argsOk = true;
10686
+ try {
10687
+ for (let i = 1; i < command.words.length; i++) {
10688
+ const v = this.fastValueWord(command.words[i], rawState, io, true, false, false, true, undefined, diagnosticLine);
10689
+ if (typeof v !== "string") {
10690
+ argsOk = false;
10691
+ break;
10222
10692
  }
10223
- else
10224
- monitor.publishStringVariable(targetVar, formatted);
10225
- if (rawState.allexport)
10226
- monitor.proxy.exported.add(targetVar);
10227
- if (io.assignmentDiagnosticContext)
10228
- io.assignmentDiagnosticContext.name = undefined;
10229
- if (!existing) {
10230
- monitor.lazyPipeStatus = singleStatusZero;
10231
- monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10693
+ lastArg = v;
10694
+ }
10695
+ }
10696
+ catch {
10697
+ argsOk = false;
10698
+ }
10699
+ if (argsOk) {
10700
+ if (rawState.extensions && !rawState.extensions.eventDepth)
10701
+ publishCommandSpelling(rawState, commandSpelling(command));
10702
+ const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10703
+ this.budget.tick();
10704
+ rawState.substitutionStatus = 0;
10705
+ if (rawState.variables._ !== undefined)
10706
+ delete rawState.variables._;
10707
+ rawState.lastArgument = lastArg;
10708
+ if (io.assignmentDiagnosticContext)
10709
+ io.assignmentDiagnosticContext.name = undefined;
10710
+ return this.finishSyncPipeStatus(rawState, monitor, monitor.store, monitor.store?.get("PIPESTATUS"), elem0, scope, finalStatus, restEpoch);
10711
+ }
10712
+ }
10713
+ if (command.words.length >= 3 && command.words.length <= 16 && w0Plain === "getopts" && command.redirects.length === 0 && !hasShellFunction(rawState, "getopts") && !rawState.extensions?.builtins.has("getopts")) {
10714
+ const optVar = command.words[2]?.plain;
10715
+ 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)) {
10716
+ let optstring;
10717
+ let scanArgs;
10718
+ let lastArg = optVar;
10719
+ try {
10720
+ optstring = command.words[1].plain ?? this.fastValueWord(command.words[1], rawState, io, true, false, false, true, undefined, diagnosticLine);
10721
+ if (typeof optstring === "string") {
10722
+ if (command.words.length === 3) {
10723
+ scanArgs = this._fastSubPositional ?? rawState.positional;
10724
+ }
10725
+ 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 && !ne[0].length && !ne[0].substring && !ne[0].operator && !ne[0].transform && !ne[0].indirect && !ne[0].prefixNames && getArraySelector(ne[0]) === undefined; })()) {
10726
+ scanArgs = this._fastSubPositional ?? rawState.positional;
10727
+ if (scanArgs.length > 0)
10728
+ lastArg = scanArgs[scanArgs.length - 1];
10729
+ }
10730
+ else {
10731
+ const customArgs = [];
10732
+ let customOk = true;
10733
+ for (let i = 3; i < command.words.length; i++) {
10734
+ const v = this.fastValueWord(command.words[i], rawState, io, true, false, false, true, undefined, diagnosticLine);
10735
+ if (typeof v !== "string") {
10736
+ customOk = false;
10737
+ break;
10738
+ }
10739
+ customArgs.push(v);
10740
+ lastArg = v;
10741
+ }
10742
+ if (customOk)
10743
+ scanArgs = customArgs;
10744
+ }
10232
10745
  }
10233
- else {
10234
- elem0.text.shellValue = "0";
10235
- store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10746
+ }
10747
+ catch {
10748
+ scanArgs = undefined;
10749
+ }
10750
+ if (typeof optstring === "string" && optstring.startsWith(":") && scanArgs !== undefined) {
10751
+ rawState.getopts ??= cloneGetoptsBinding(rawState);
10752
+ const res = scanGetoptsSync(rawState.getopts.cursor, optstring, scanArgs);
10753
+ if (res !== undefined) {
10754
+ const rawStatus = res.status;
10755
+ const statusChar = rawStatus === 0 ? "0" : "1";
10756
+ if (canMutatePipeStatus || elem0.text.shellValue === statusChar) {
10757
+ const finalStatus = pipeline.negate ? Number(rawStatus === 0) : rawStatus;
10758
+ if (finalStatus === 0 || ignored || !rawState.errexit) {
10759
+ if (rawState.extensions && !rawState.extensions.eventDepth)
10760
+ publishCommandSpelling(rawState, commandSpelling(command));
10761
+ const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10762
+ this.budget.tick();
10763
+ rawState.substitutionStatus = 0;
10764
+ if (rawState.variables._ !== undefined)
10765
+ delete rawState.variables._;
10766
+ rawState.lastArgument = lastArg;
10767
+ rawState.getopts.cursor = res.state;
10768
+ const optindStr = String(res.optind);
10769
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10770
+ rawState.variables.OPTIND = optindStr;
10771
+ this._syncArithTouched.add("OPTIND");
10772
+ }
10773
+ else
10774
+ monitor.publishStringVariable("OPTIND", optindStr);
10775
+ if (rawState.allexport)
10776
+ monitor.proxy.exported.add("OPTIND");
10777
+ if (res.argument.kind === "set") {
10778
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10779
+ rawState.variables.OPTARG = res.argument.value;
10780
+ this._syncArithTouched.add("OPTARG");
10781
+ }
10782
+ else
10783
+ monitor.publishStringVariable("OPTARG", res.argument.value);
10784
+ if (rawState.allexport)
10785
+ monitor.proxy.exported.add("OPTARG");
10786
+ }
10787
+ else {
10788
+ delete rawState.variables.OPTARG;
10789
+ rawState.exported.delete("OPTARG");
10790
+ if (this._syncArithRawWriteOnly && this._syncArithTouched)
10791
+ this._syncArithTouched.add("OPTARG");
10792
+ else
10793
+ monitor.values.invalidate("OPTARG");
10794
+ }
10795
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10796
+ rawState.variables[optVar] = res.option;
10797
+ this._syncArithTouched.add(optVar);
10798
+ }
10799
+ else
10800
+ monitor.publishStringVariable(optVar, res.option);
10801
+ if (rawState.allexport)
10802
+ monitor.proxy.exported.add(optVar);
10803
+ if (io.assignmentDiagnosticContext)
10804
+ io.assignmentDiagnosticContext.name = undefined;
10805
+ return this.finishSyncPipeStatus(rawState, monitor, monitor.store, monitor.store?.get("PIPESTATUS"), elem0, scope, finalStatus, restEpoch);
10806
+ }
10807
+ }
10236
10808
  }
10237
- const finalStatus = pipeline.negate ? 1 : 0;
10238
- rawState.status = finalStatus;
10239
- monitor.epoch = restEpoch;
10240
- if (store)
10241
- store.epoch = restEpoch;
10242
- return finalStatus;
10243
10809
  }
10244
10810
  }
10245
10811
  }
@@ -10277,7 +10843,7 @@ export class Runtime {
10277
10843
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10278
10844
  }
10279
10845
  else {
10280
- elem0.text.shellValue = "0";
10846
+ this.setSyncPipeStatusCell(existing, "0");
10281
10847
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10282
10848
  }
10283
10849
  rawState.status = 0;
@@ -10383,7 +10949,7 @@ export class Runtime {
10383
10949
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10384
10950
  }
10385
10951
  else {
10386
- elem0.text.shellValue = "0";
10952
+ this.setSyncPipeStatusCell(existing, "0");
10387
10953
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10388
10954
  }
10389
10955
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10406,6 +10972,105 @@ export class Runtime {
10406
10972
  !command.redirects[0].move &&
10407
10973
  !command.redirects[0].document &&
10408
10974
  (command.redirects[0].descriptor === undefined || command.redirects[0].descriptor === 0);
10975
+ if ((w0Plain === "mapfile" || w0Plain === "readarray") &&
10976
+ isHereStringRead &&
10977
+ (command.words.length === 2 || command.words.length === 3) &&
10978
+ command.words[1]?.plain === "-t" &&
10979
+ canMutatePipeStatus &&
10980
+ !pipeline.negate &&
10981
+ !rawState.allexport &&
10982
+ !hasShellFunction(rawState, w0Plain) &&
10983
+ !rawState.extensions?.builtins.has(w0Plain)) {
10984
+ const arrayTarget = command.words[2]?.plain ?? "MAPFILE";
10985
+ if (!hasUnpreparedLocal(rawState, arrayTarget) &&
10986
+ isShellIdentifier(arrayTarget) &&
10987
+ !rawState.readonlyVariables?.has(arrayTarget) &&
10988
+ !rawState.exported.has(arrayTarget) &&
10989
+ !rawState.variableAttributes?.get(arrayTarget) &&
10990
+ !controlNames.has(arrayTarget)) {
10991
+ let hv;
10992
+ try {
10993
+ hv = this.fastValueWord(command.redirects[0].target, rawState, io, false, false, false, false, undefined, diagnosticLine);
10994
+ }
10995
+ catch {
10996
+ hv = undefined;
10997
+ }
10998
+ if (typeof hv === "string" &&
10999
+ !hv.includes("\0") &&
11000
+ hv.length + 1 < this.budget.limits.maxExpansionBytes &&
11001
+ hv.length + 1 <= this.budget.limits.maxInputBytes) {
11002
+ const arrStore = monitor.store ?? requireArrays(rawState);
11003
+ let existingArrayBinding = arrStore.get(arrayTarget);
11004
+ if (!existingArrayBinding && rawState.variables[arrayTarget] === undefined && !arrStore.watches.has(arrayTarget) && !monitor.hasOverlay(arrayTarget)) {
11005
+ const recTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
11006
+ const recBinding = arrStore.takeRecycled(arrayTarget, false, true, recTickets);
11007
+ if (recBinding) {
11008
+ monitor.epoch = recTickets.epoch;
11009
+ existingArrayBinding = recBinding;
11010
+ }
11011
+ else {
11012
+ try {
11013
+ const created = IndexedBinding.create(arrStore.owner, false);
11014
+ const prepared = arrStore.prepareExistingName(arrayTarget, shellValueByteLength(arrayTarget), arrStore.owner, this.signal);
11015
+ if (prepared) {
11016
+ const initTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
11017
+ arrStore.publish(arrayTarget, created, initTickets, prepared, false, arrStore.owner);
11018
+ monitor.epoch = initTickets.epoch;
11019
+ existingArrayBinding = created;
11020
+ }
11021
+ else {
11022
+ void created.release();
11023
+ }
11024
+ }
11025
+ catch { }
11026
+ }
11027
+ }
11028
+ if (existingArrayBinding && !existingArrayBinding.associative && existingArrayBinding.references === 1 && !arrStore.watches.has(arrayTarget) && !monitor.hasOverlay(arrayTarget)) {
11029
+ const lines = hv.split("\n");
11030
+ const fieldLen = lines.length;
11031
+ if (fieldLen <= 256) {
11032
+ if (rawState.extensions && !rawState.extensions.eventDepth)
11033
+ publishCommandSpelling(rawState, commandSpelling(command));
11034
+ const tickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 + fieldLen * 4 });
11035
+ if (existingArrayBinding.values.size !== fieldLen || existingArrayBinding.maximum !== fieldLen - 1) {
11036
+ for (const k of [...existingArrayBinding.values.keys()]) {
11037
+ if (k >= fieldLen)
11038
+ existingArrayBinding.remove(k);
11039
+ }
11040
+ existingArrayBinding.maximum = fieldLen - 1;
11041
+ }
11042
+ for (let idx = 0; idx < fieldLen; idx++) {
11043
+ const val = lines[idx];
11044
+ const byteLen = shellValueByteLength(val);
11045
+ const slot = existingArrayBinding.values.get(idx);
11046
+ if (slot && slot.text.references === 1 && slot.text.bytes === byteLen) {
11047
+ existingArrayBinding.owner.chargeWork(byteLen);
11048
+ slot.text.shellValue = val;
11049
+ }
11050
+ else {
11051
+ existingArrayBinding.owner.chargeWork(byteLen);
11052
+ const token = new OwnedText(val, byteLen, existingArrayBinding.owner.reserve({ payload: byteLen, metadata: 32, work: 4 }));
11053
+ existingArrayBinding.insert(idx, token);
11054
+ }
11055
+ }
11056
+ delete rawState.variables[arrayTarget];
11057
+ monitor.values.invalidate(arrayTarget);
11058
+ monitor.epoch = tickets.epoch;
11059
+ arrStore.revise(arrayTarget, existingArrayBinding, tickets);
11060
+ const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
11061
+ this.budget.tick();
11062
+ rawState.substitutionStatus = 0;
11063
+ if (rawState.variables._ !== undefined)
11064
+ delete rawState.variables._;
11065
+ rawState.lastArgument = command.words[command.words.length - 1].plain ?? arrayTarget;
11066
+ if (io.assignmentDiagnosticContext)
11067
+ io.assignmentDiagnosticContext.name = undefined;
11068
+ return this.finishSyncPipeStatus(rawState, monitor, monitor.store, monitor.store?.get("PIPESTATUS"), elem0, scope, 0, restEpoch);
11069
+ }
11070
+ }
11071
+ }
11072
+ }
11073
+ }
10409
11074
  if ((w0Plain === "read" || (command.words[1]?.plain === "read" && w0.parts[0]?.kind === "text" && w0.parts[0].value.startsWith("IFS="))) &&
10410
11075
  canMutatePipeStatus &&
10411
11076
  !pipeline.negate &&
@@ -10470,7 +11135,7 @@ export class Runtime {
10470
11135
  delimStr = nextW.plain;
10471
11136
  else {
10472
11137
  try {
10473
- const fv = this.fastValueWord(nextW, rawState, io, false, false, false, false, 0, diagnosticLine);
11138
+ const fv = this.fastValueWord(nextW, rawState, io, true, false, false, false, undefined, diagnosticLine);
10474
11139
  if (typeof fv === "string")
10475
11140
  delimStr = fv;
10476
11141
  }
@@ -10516,7 +11181,7 @@ export class Runtime {
10516
11181
  const varCount = command.words.length - readWordIdx;
10517
11182
  let existingArrayBinding;
10518
11183
  if (arrayTarget !== undefined) {
10519
- if (hasUnpreparedLocals(rawState))
11184
+ if (hasUnpreparedLocal(rawState, arrayTarget))
10520
11185
  return undefined;
10521
11186
  if (varCount !== 0 || !isShellIdentifier(arrayTarget) || rawState.readonlyVariables?.has(arrayTarget) || rawState.exported.has(arrayTarget) || rawState.variableAttributes?.get(arrayTarget) || controlNames.has(arrayTarget)) {
10522
11187
  simpleRead = false;
@@ -10524,26 +11189,7 @@ export class Runtime {
10524
11189
  else {
10525
11190
  const arrStore = monitor.store ?? requireArrays(rawState);
10526
11191
  existingArrayBinding = arrStore.get(arrayTarget);
10527
- 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
- }
10541
- }
10542
- catch { }
10543
- }
10544
- if (!existingArrayBinding ||
10545
- existingArrayBinding.associative ||
10546
- existingArrayBinding.references !== 1 ||
11192
+ if ((existingArrayBinding ? existingArrayBinding.associative || existingArrayBinding.references !== 1 : rawState.variables[arrayTarget] !== undefined) ||
10547
11193
  arrStore.watches.has(arrayTarget) ||
10548
11194
  monitor.hasOverlay(arrayTarget)) {
10549
11195
  simpleRead = false;
@@ -10591,6 +11237,36 @@ export class Runtime {
10591
11237
  else
10592
11238
  lineStr = io.stdin.tryReadSimpleRawAsciiLineSync(readDelim, raw);
10593
11239
  if (lineStr !== undefined) {
11240
+ // Expand/read the input before publishing the destination array.
11241
+ if (arrayTarget !== undefined && !existingArrayBinding) {
11242
+ const arrStore = monitor.store ?? requireArrays(rawState);
11243
+ if (!existingArrayBinding && rawState.variables[arrayTarget] === undefined && !arrStore.watches.has(arrayTarget) && !monitor.hasOverlay(arrayTarget)) {
11244
+ const recTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
11245
+ const recBinding = arrStore.takeRecycled(arrayTarget, false, true, recTickets);
11246
+ if (recBinding) {
11247
+ monitor.epoch = recTickets.epoch;
11248
+ existingArrayBinding = recBinding;
11249
+ }
11250
+ else
11251
+ try {
11252
+ const created = IndexedBinding.create(arrStore.owner, false);
11253
+ const nameByteLen = shellValueByteLength(arrayTarget);
11254
+ const prepared = arrStore.prepareExistingName(arrayTarget, nameByteLen, arrStore.owner, this.signal);
11255
+ if (prepared) {
11256
+ const initTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
11257
+ arrStore.publish(arrayTarget, created, initTickets, prepared, false, arrStore.owner);
11258
+ monitor.epoch = initTickets.epoch;
11259
+ existingArrayBinding = created;
11260
+ }
11261
+ else {
11262
+ void created.release();
11263
+ }
11264
+ }
11265
+ catch { }
11266
+ }
11267
+ if (!existingArrayBinding)
11268
+ return undefined;
11269
+ }
10594
11270
  if (rawState.extensions && !rawState.extensions.eventDepth)
10595
11271
  publishCommandSpelling(rawState, commandSpelling(command));
10596
11272
  if (arrayTarget !== undefined && existingArrayBinding) {
@@ -10650,6 +11326,7 @@ export class Runtime {
10650
11326
  readArrayScratchFields.length = 0;
10651
11327
  delete rawState.variables[arrayTarget];
10652
11328
  monitor.epoch = tickets.epoch;
11329
+ existingArrayBinding.assigned = true;
10653
11330
  activeArrStore.revise(arrayTarget, existingArrayBinding, tickets);
10654
11331
  }
10655
11332
  catch (error) {
@@ -10729,7 +11406,7 @@ export class Runtime {
10729
11406
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10730
11407
  }
10731
11408
  else {
10732
- elem0.text.shellValue = "0";
11409
+ this.setSyncPipeStatusCell(existing, "0");
10733
11410
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10734
11411
  }
10735
11412
  rawState.status = 0;
@@ -10927,7 +11604,7 @@ export class Runtime {
10927
11604
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10928
11605
  }
10929
11606
  else {
10930
- elem0.text.shellValue = "0";
11607
+ this.setSyncPipeStatusCell(existing, "0");
10931
11608
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10932
11609
  }
10933
11610
  rawState.status = 0;
@@ -11001,7 +11678,7 @@ export class Runtime {
11001
11678
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11002
11679
  }
11003
11680
  else {
11004
- elem0.text.shellValue = "0";
11681
+ this.setSyncPipeStatusCell(existing, "0");
11005
11682
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11006
11683
  }
11007
11684
  rawState.status = 0;
@@ -11081,6 +11758,8 @@ export class Runtime {
11081
11758
  if (isLocalScope) {
11082
11759
  let existingLocal = currentFrame.get(an);
11083
11760
  if (!existingLocal) {
11761
+ if (this._syncArithRawWriteOnly && this._syncArithTouched?.has(an))
11762
+ monitor.values.invalidate(an);
11084
11763
  existingLocal = saveVariable(state, an);
11085
11764
  syncLocalArrayVariables.add(existingLocal);
11086
11765
  currentFrame.set(an, existingLocal);
@@ -11145,13 +11824,15 @@ export class Runtime {
11145
11824
  }
11146
11825
  return undefined;
11147
11826
  }
11827
+ const isNamerefDecl = command.words[1]?.plain === "-n" && rawState.locals.length > 0 && command.words.length >= 3;
11828
+ const isGlobalDecl = command.words[1]?.plain === "-g" && (w0Plain === "declare" || w0Plain === "typeset") && command.words.length >= 3;
11148
11829
  const parsedLocals = [];
11149
11830
  let allValidLocal = true;
11150
- for (let idx = 1; idx < command.words.length; idx++) {
11831
+ for (let idx = (isNamerefDecl || isGlobalDecl) ? 2 : 1; idx < command.words.length; idx++) {
11151
11832
  const wArg = command.words[idx];
11152
11833
  const assignment = !getArrayAssignment(wArg) ? this.assignment(wArg) : undefined;
11153
11834
  if (assignment) {
11154
- if (assignment.name === "OPTIND" || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || rawState.readonlyVariables?.has(assignment.name) || store?.get(assignment.name)) {
11835
+ if ((!isNamerefDecl && rawState.variableAttributes?.get(assignment.name)) || (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
11836
  allValidLocal = false;
11156
11837
  break;
11157
11838
  }
@@ -11166,9 +11847,17 @@ export class Runtime {
11166
11847
  allValidLocal = false;
11167
11848
  break;
11168
11849
  }
11850
+ if (assignment.name === "OPTIND") {
11851
+ const trimmedOptind = fastAssigned.trim();
11852
+ if (!/^[0-9]{1,6}$/.test(trimmedOptind)) {
11853
+ allValidLocal = false;
11854
+ break;
11855
+ }
11856
+ fastAssigned = String(Number(trimmedOptind));
11857
+ }
11169
11858
  parsedLocals.push({ name: assignment.name, val: fastAssigned, append: assignment.append, lastArg: `${assignment.name}=${fastAssigned}` });
11170
11859
  }
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)) {
11860
+ 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
11861
  parsedLocals.push({ name: wArg.plain, val: undefined, append: false, lastArg: wArg.plain });
11173
11862
  }
11174
11863
  else {
@@ -11176,8 +11865,28 @@ export class Runtime {
11176
11865
  break;
11177
11866
  }
11178
11867
  }
11868
+ if (isNamerefDecl && allValidLocal) {
11869
+ for (let i = 0; i < parsedLocals.length; i++) {
11870
+ const it = parsedLocals[i];
11871
+ if (!it.val || it.append || !isShellIdentifier(it.val) || it.val === it.name || rawState.variableAttributes?.get(it.val)) {
11872
+ allValidLocal = false;
11873
+ break;
11874
+ }
11875
+ }
11876
+ }
11877
+ if (isGlobalDecl && allValidLocal && rawState.locals.length > 0) {
11878
+ for (let i = 0; i < parsedLocals.length; i++) {
11879
+ const it = parsedLocals[i];
11880
+ if (rawState.locals.some(f => f.has(it.name))) {
11881
+ allValidLocal = false;
11882
+ break;
11883
+ }
11884
+ }
11885
+ }
11179
11886
  if (allValidLocal && parsedLocals.length > 0) {
11180
- const locals = rawState.locals.length > 0 ? rawState.locals[rawState.locals.length - 1] : undefined;
11887
+ if ((rawState.locals.length === 0 || isGlobalDecl) && parsedLocals.some(item => item.val === undefined && !rawState.variableAttributes?.has(item.name)))
11888
+ return undefined;
11889
+ const locals = (!isGlobalDecl && rawState.locals.length > 0) ? rawState.locals[rawState.locals.length - 1] : undefined;
11181
11890
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
11182
11891
  this.budget.tick();
11183
11892
  rawState.substitutionStatus = 0;
@@ -11191,7 +11900,7 @@ export class Runtime {
11191
11900
  const curVal = this._syncArithTouched.has(item.name)
11192
11901
  ? rawState.variables[item.name]
11193
11902
  : (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 });
11903
+ locals.set(item.name, { attributes: undefined, value: typeof curVal === "string" ? curVal : undefined, exported: rawState.exported.has(item.name), readOnly: false, ...(item.name === "OPTIND" ? { getopts: cloneGetoptsBinding(rawState) } : {}) });
11195
11904
  }
11196
11905
  else
11197
11906
  locals.set(item.name, saveVariable(state, item.name));
@@ -11214,18 +11923,31 @@ export class Runtime {
11214
11923
  }
11215
11924
  else
11216
11925
  monitor.publishStringVariable(item.name, finalVal);
11926
+ if (item.name === "OPTIND") {
11927
+ rawState.variables.OPTIND = finalVal;
11928
+ this.syncGetopts(rawState);
11929
+ }
11217
11930
  if (rawState.allexport)
11218
11931
  monitor.proxy.exported.add(item.name);
11219
11932
  rawState.lastArgument = `${item.name}=${finalVal}`;
11220
- if (!rawState.variableAttributes?.has(item.name)) {
11933
+ if (isNamerefDecl) {
11221
11934
  rawState.variableAttributes ??= new Map();
11222
- rawState.variableAttributes.set(item.name, "");
11935
+ rawState.variableAttributes.set(item.name, "n");
11223
11936
  }
11937
+ else if (isNewLocal && rawState.variableAttributes?.has(item.name))
11938
+ rawState.variableAttributes.delete(item.name);
11224
11939
  }
11225
11940
  else {
11226
- if (!rawState.variableAttributes?.has(item.name)) {
11227
- rawState.variableAttributes ??= new Map();
11228
- rawState.variableAttributes.set(item.name, "");
11941
+ if (item.name === "OPTIND")
11942
+ this.syncGetopts(rawState);
11943
+ if (!locals) {
11944
+ if (!rawState.variableAttributes?.has(item.name)) {
11945
+ rawState.variableAttributes ??= new Map();
11946
+ rawState.variableAttributes.set(item.name, "");
11947
+ }
11948
+ }
11949
+ else if (isNewLocal && rawState.variableAttributes?.has(item.name)) {
11950
+ rawState.variableAttributes.delete(item.name);
11229
11951
  }
11230
11952
  rawState.lastArgument = item.lastArg;
11231
11953
  }
@@ -11243,7 +11965,7 @@ export class Runtime {
11243
11965
  }
11244
11966
  }
11245
11967
  else {
11246
- elem0.text.shellValue = "0";
11968
+ this.setSyncPipeStatusCell(existing, "0");
11247
11969
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11248
11970
  }
11249
11971
  rawState.status = 0;
@@ -11269,8 +11991,6 @@ export class Runtime {
11269
11991
  if (fastArg.length > 0 && fastArg.length <= 15 && /^-?[0-9]+$/.test(fastArg)) {
11270
11992
  const retStatus = ((Number(fastArg) % 256) + 256) % 256;
11271
11993
  const statusStr = retStatus === 0 ? "0" : retStatus === 1 ? "1" : String(retStatus);
11272
- if (existing && elem0.text.bytes !== statusStr.length)
11273
- return undefined;
11274
11994
  const prevStatus = rawState.status;
11275
11995
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
11276
11996
  this.budget.tick();
@@ -11285,7 +12005,7 @@ export class Runtime {
11285
12005
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11286
12006
  }
11287
12007
  else {
11288
- elem0.text.shellValue = statusStr;
12008
+ this.setSyncPipeStatusCell(existing, statusStr);
11289
12009
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11290
12010
  }
11291
12011
  rawState.status = retStatus;
@@ -11339,10 +12059,14 @@ export class Runtime {
11339
12059
  else {
11340
12060
  const savedPos = rawState.positional;
11341
12061
  rawState.positional = fnArgs;
12062
+ rawState.locals.push(new Map());
12063
+ rawState.functionDepth++;
11342
12064
  try {
11343
12065
  canSyncFn = this.canSyncScriptCompound(fnBody.body, rawState, 1);
11344
12066
  }
11345
12067
  finally {
12068
+ rawState.functionDepth--;
12069
+ rawState.locals.pop();
11346
12070
  rawState.positional = savedPos;
11347
12071
  }
11348
12072
  }
@@ -11434,11 +12158,15 @@ export class Runtime {
11434
12158
  rawState.locals.pop();
11435
12159
  rawState.functionNames?.shift();
11436
12160
  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") {
12161
+ if (this._syncArithRawWriteOnly && this._syncArithTouched && !syncLocalArrayVariables.has(previous) && !previous.heldValue && !previous.attributes && !previous.exported && !previous.readOnly) {
11438
12162
  if (previous.value === undefined)
11439
12163
  delete rawState.variables[k];
11440
12164
  else
11441
12165
  rawState.variables[k] = shellValueText(previous.value);
12166
+ if (k === "OPTIND" && previous.getopts) {
12167
+ rawState.getopts = { integer: previous.getopts.integer, cursor: cloneGetoptsState(previous.getopts.cursor) };
12168
+ }
12169
+ rawState.variableAttributes?.delete(k);
11442
12170
  this._syncArithTouched.add(k);
11443
12171
  }
11444
12172
  else
@@ -11459,8 +12187,8 @@ export class Runtime {
11459
12187
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11460
12188
  }
11461
12189
  }
11462
- else if (activeExisting.values.get(0).text.bytes === statusStr.length) {
11463
- activeExisting.values.get(0).text.shellValue = statusStr;
12190
+ else {
12191
+ this.setSyncPipeStatusCell(activeExisting, statusStr);
11464
12192
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11465
12193
  }
11466
12194
  rawState.status = exitStatus;
@@ -11531,7 +12259,7 @@ export class Runtime {
11531
12259
  }
11532
12260
  }
11533
12261
  else {
11534
- activeExisting.values.get(0).text.shellValue = "0";
12262
+ this.setSyncPipeStatusCell(activeExisting, "0");
11535
12263
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11536
12264
  }
11537
12265
  rawState.status = 0;
@@ -11565,7 +12293,7 @@ export class Runtime {
11565
12293
  rawState.depth++;
11566
12294
  let evalStatus;
11567
12295
  try {
11568
- evalStatus = this.trySyncScript(cachedUnit.script, state, io, Boolean(ignored));
12296
+ evalStatus = this.trySyncScript(cachedUnit.script, state, { ...io, diagnosticOffset: (diagnosticLine ?? 1) - 1, functionCommandLines: undefined, assignmentDiagnosticContext: { name: "eval" } }, Boolean(ignored));
11569
12297
  }
11570
12298
  finally {
11571
12299
  rawState.depth--;
@@ -11585,7 +12313,7 @@ export class Runtime {
11585
12313
  }
11586
12314
  }
11587
12315
  else {
11588
- activeExisting.values.get(0).text.shellValue = "0";
12316
+ this.setSyncPipeStatusCell(activeExisting, "0");
11589
12317
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11590
12318
  }
11591
12319
  }
@@ -11669,7 +12397,7 @@ export class Runtime {
11669
12397
  }
11670
12398
  }
11671
12399
  else {
11672
- elem0.text.shellValue = statusChar;
12400
+ this.setSyncPipeStatusCell(existing, statusChar);
11673
12401
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11674
12402
  }
11675
12403
  const finalStatus = pipeline.negate ? Number(fastPred === 0) : fastPred;
@@ -11734,7 +12462,7 @@ export class Runtime {
11734
12462
  else {
11735
12463
  const idxNum = /^(?:0|[1-9][0-9]{0,8})$/.test(subExpr) ? Number(subExpr) : resolveSimpleArithOperand(subExpr, rawState, monitor, arrStore, this._syncArithRawWriteOnly ? this._syncArithTouched : undefined);
11736
12464
  if (idxNum !== undefined && idxNum >= 0 && idxNum <= 2147483647) {
11737
- if (!curB && rawState.variables[arrName] === undefined && !arrStore.watches.has(arrName) && !monitor.hasOverlay(arrName)) {
12465
+ if (!curB && !hasUnpreparedLocal(rawState, arrName) && rawState.variables[arrName] === undefined && !arrStore.watches.has(arrName) && !monitor.hasOverlay(arrName)) {
11738
12466
  const created = IndexedBinding.create(arrStore.owner, false);
11739
12467
  const prepared = arrStore.prepareExistingName(arrName, shellValueByteLength(arrName), arrStore.owner, this.signal);
11740
12468
  if (prepared) {
@@ -11780,7 +12508,7 @@ export class Runtime {
11780
12508
  }
11781
12509
  }
11782
12510
  else {
11783
- activeExisting.values.get(0).text.shellValue = "0";
12511
+ this.setSyncPipeStatusCell(activeExisting, "0");
11784
12512
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11785
12513
  }
11786
12514
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -11866,7 +12594,7 @@ export class Runtime {
11866
12594
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11867
12595
  }
11868
12596
  else {
11869
- elem0.text.shellValue = "0";
12597
+ this.setSyncPipeStatusCell(existing, "0");
11870
12598
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11871
12599
  }
11872
12600
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -11921,7 +12649,7 @@ export class Runtime {
11921
12649
  return false;
11922
12650
  continue;
11923
12651
  }
11924
- if (part.prefixNames || part.specialParameter || part.transform || part.name === "@" || part.name === "*" || part.name === "PIPESTATUS" || part.name === "LINENO" || part.name === "_" || part.name === "FUNCNAME" || !isShellIdentifier(part.name)) {
12652
+ if (part.prefixNames || part.specialParameter || part.transform || part.name === "@" || part.name === "*" || part.name === "PIPESTATUS" || part.name === "LINENO" || part.name === "_" || part.name === "FUNCNAME" || part.name === "DIRSTACK" || !isShellIdentifier(part.name)) {
11925
12653
  return false;
11926
12654
  }
11927
12655
  if (part.indirect) {
@@ -11998,9 +12726,11 @@ export class Runtime {
11998
12726
  if (part.alternate && part.alternate.parts.length > 0)
11999
12727
  return false;
12000
12728
  }
12001
- else if (part.operator === "-" || part.operator === ":-" || part.operator === "+" || part.operator === ":+") {
12729
+ else if (part.operator === "-" || part.operator === ":-" || part.operator === "+" || part.operator === ":+" || part.operator === "=" || part.operator === ":=") {
12002
12730
  if (!part.alternate || !part.alternate.parts.every(p => p.kind === "text" || p.kind === "arithmetic" || (p.kind === "variable" && !p.operator && !p.substring)))
12003
12731
  return false;
12732
+ 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)))
12733
+ return false;
12004
12734
  }
12005
12735
  else if (part.operator === "#" || part.operator === "##" || part.operator === "%" || part.operator === "%%") {
12006
12736
  if (!part.alternate || part.alternate.parts.length === 0)
@@ -12036,8 +12766,18 @@ export class Runtime {
12036
12766
  if (rawState.nounset)
12037
12767
  return false;
12038
12768
  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;
12769
+ if (!em || ((em[2] === "/" || em[2] === "%") && !(em[3] && /^[1-9][0-9]*$/.test(em[3])))) {
12770
+ if (part.source.includes("<<") || part.source.includes(">>") || part.source.includes("**") || /[/%](?!\s*[1-9][0-9]*\b)/.test(part.source))
12771
+ return false;
12772
+ try {
12773
+ const expW = parseArithmeticExpansion(part.source, this.budget.parsing, false, 0, undefined, rawState.extensions?.syntax);
12774
+ if (!this.isPureSyncValueWord(expW, rawState))
12775
+ return false;
12776
+ }
12777
+ catch {
12778
+ return false;
12779
+ }
12780
+ }
12041
12781
  }
12042
12782
  continue;
12043
12783
  }
@@ -12406,7 +13146,7 @@ export class Runtime {
12406
13146
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
12407
13147
  }
12408
13148
  else {
12409
- elem0.text.shellValue = "0";
13149
+ this.setSyncPipeStatusCell(existing, "0");
12410
13150
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
12411
13151
  }
12412
13152
  }
@@ -12769,7 +13509,7 @@ export class Runtime {
12769
13509
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
12770
13510
  }
12771
13511
  else {
12772
- elem0.text.shellValue = "0";
13512
+ this.setSyncPipeStatusCell(existing, "0");
12773
13513
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
12774
13514
  }
12775
13515
  }
@@ -12994,7 +13734,7 @@ export class Runtime {
12994
13734
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
12995
13735
  }
12996
13736
  else {
12997
- elem0.text.shellValue = "0";
13737
+ this.setSyncPipeStatusCell(existing, "0");
12998
13738
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
12999
13739
  }
13000
13740
  }
@@ -15626,7 +16366,7 @@ export class Runtime {
15626
16366
  previous.set(assignment.name, saved);
15627
16367
  const existingArray = arrayStore(state)?.get(assignment.name);
15628
16368
  const canSkipTypedScalarOverlay = !existingArray && words[0] === "read" && !state.functions.has("read") && !state.extensions?.builtins.has("read") && !words.some(w => w.startsWith("-a") || (w.startsWith("-") && !w.startsWith("--") && w.includes("a")));
15629
- if (words.length && !canSkipTypedScalarOverlay && (guestArrays(state) || (assignment.name === "PIPESTATUS" && existingArray))) {
16369
+ if (words.length && !canSkipTypedScalarOverlay && (guestArrays(state) || existingArray)) {
15630
16370
  const store = requireArrays(state);
15631
16371
  await this.prepareVariable(state, assignment.name, saved, !existingArray);
15632
16372
  if (existingArray) {
@@ -15651,7 +16391,7 @@ export class Runtime {
15651
16391
  }
15652
16392
  if (inlineInput || (state.profile === "sh" || !words.length) && assignments.some(assignment => state.readonlyVariables?.has(assignment.name)))
15653
16393
  await assign();
15654
- if (inlineInput && functionCommand && previous?.size) {
16394
+ if (inlineInput && previous?.size) {
15655
16395
  if (stateMonitor(state)?.lazyPipeStatus !== undefined)
15656
16396
  stateMonitor(state).activate(true);
15657
16397
  const redirectState = tryCloneStateSync(state) ?? await cloneState(state, this.signal);
@@ -15659,10 +16399,26 @@ export class Runtime {
15659
16399
  const redirectAssignments = new Map();
15660
16400
  for (const [name, saved] of previous) {
15661
16401
  redirectAssignments.set(name, stateMonitor(state).values.get(name, state.variables[name]));
16402
+ if (saved.exported)
16403
+ redirectState.exported.add(name);
16404
+ else
16405
+ redirectState.exported.delete(name);
15662
16406
  if (saved.value === undefined)
15663
16407
  delete variables[name];
15664
16408
  else
15665
16409
  publishVariable(redirectState, name, saved.heldValue?.value ?? saved.value);
16410
+ const binding = typedSavedVariables.get(saved)?.binding;
16411
+ if (binding) {
16412
+ const store = requireArrays(redirectState);
16413
+ const prepared = await store.prepareName(name, store.owner, this.signal);
16414
+ const tickets = store.tickets(name);
16415
+ try {
16416
+ await store.publish(name, binding.retain(), tickets, prepared);
16417
+ }
16418
+ finally {
16419
+ tickets.release();
16420
+ }
16421
+ }
15666
16422
  }
15667
16423
  redirectState.redirectAssignments = redirectAssignments;
15668
16424
  const savedIndex = previous.get("OPTIND");
@@ -15680,7 +16436,7 @@ export class Runtime {
15680
16436
  }
15681
16437
  const epoch = stateMonitor(state)?.epoch;
15682
16438
  try {
15683
- io = await this.redirect(command.redirects, redirectState, io, inputs, outputs, false, true, false, command.line ?? 1);
16439
+ io = await this.redirect(command.redirects, redirectState, io, inputs, outputs, isolatedInlineInput, !isolatedInlineInput, false, command.line ?? 1);
15684
16440
  if (parentStore && stateMonitor(state).epoch !== epoch)
15685
16441
  throw new ArrayFailure("stale state snapshot");
15686
16442
  }
@@ -15703,6 +16459,12 @@ export class Runtime {
15703
16459
  publishVariable(state, name, stateMonitor(redirectState).values.get(name, value));
15704
16460
  }
15705
16461
  for (const [name, saved] of previous) {
16462
+ const typed = typedSavedVariables.get(saved);
16463
+ const binding = arrayStore(redirectState)?.get(name);
16464
+ if (typed && binding !== typed.binding) {
16465
+ typedSavedVariables.set(saved, { ...typed, binding: binding?.retain() });
16466
+ await typed.binding?.release();
16467
+ }
15706
16468
  saved.value = variables[name];
15707
16469
  const value = stateMonitor(redirectState).values.get(name, saved.value ?? "");
15708
16470
  const held = saved.value === undefined ? undefined : stateMonitor(state).values.scope.hold(value);
@@ -19897,6 +20659,9 @@ export class Runtime {
19897
20659
  seen.add(name);
19898
20660
  names.push(name);
19899
20661
  };
20662
+ await consider("DIRSTACK", true);
20663
+ if (state.functionNames?.length)
20664
+ await consider("FUNCNAME", true);
19900
20665
  for (const name in state.variables)
19901
20666
  if (Object.hasOwn(state.variables, name))
19902
20667
  await consider(name, state.variables[name] !== undefined);
@@ -19977,7 +20742,7 @@ export class Runtime {
19977
20742
  setArraySelector(resolved, { kind: "element", index: { decimal: shellValueText(value), word: { offset: word.offset, parts: [{ kind: "text", value: shellValueText(value), quoted: true, ...(typeof value === "string" ? {} : { byteValue: value }) }] } } });
19978
20743
  return resolved;
19979
20744
  }
19980
- const maximum = binding?.maximum ?? (state.variables[part.name] === undefined ? -1 : 0);
20745
+ const maximum = binding?.maximum ?? (part.name === "FUNCNAME" && state.variables.FUNCNAME === undefined ? (state.functionNames?.length ?? 0) - 1 : state.variables[part.name] === undefined ? -1 : 0);
19981
20746
  const index = await this.arrayIndex(binding, selector.index, state, io, store.owner, false, maximum);
19982
20747
  const resolved = { ...part };
19983
20748
  copyArraySelector(part, resolved);
@@ -20049,8 +20814,7 @@ export class Runtime {
20049
20814
  const selector = getArraySelector(part);
20050
20815
  if (selector?.kind === "members" || part.name === "@" || part.name === "*") {
20051
20816
  const members = selector ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
20052
- const ifs = state.variables.IFS ?? " ";
20053
- const separator = selector?.kind === "members" && !split && (selector.separator === "@" || !part.quoted) ? " " : ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
20817
+ const separator = !split && (selector?.kind === "members" ? selector.separator === "@" : part.name === "@") ? " " : this.ifsSeparator(state, io);
20054
20818
  const fragments = [];
20055
20819
  let bytes = 0;
20056
20820
  for (const member of members) {
@@ -20490,7 +21254,7 @@ export class Runtime {
20490
21254
  if (part.length)
20491
21255
  return String(binding?.values.size ?? (part.name === "FUNCNAME" && state.variables.FUNCNAME === undefined ? state.functionNames?.length ?? 0 : state.variables[part.name] === undefined ? 0 : 1));
20492
21256
  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));
21257
+ 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
21258
  return this.arrayJoin(store.owner, values, space ? " " : this.ifsSeparator(state, io));
20495
21259
  }
20496
21260
  if (part.substring && (part.name === "@" || part.name === "*")) {
@@ -20504,7 +21268,7 @@ export class Runtime {
20504
21268
  : part.name === "?" ? String(state.status)
20505
21269
  : 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
21270
  : 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] ?? "")
21271
+ : part.name === "@" || part.name === "*" ? state.positional.join(part.name === "@" && !split || hereString && (part.name === "@" || !part.quoted) ? " " : Array.from(state.variables.IFS ?? " ")[0] ?? "")
20508
21272
  : /^0+$/u.test(part.name) ? state.arg0 ?? "virtual-bash"
20509
21273
  : /^\d+$/u.test(part.name) ? state.positional[Number(part.name) - 1]
20510
21274
  : 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 +21285,7 @@ export class Runtime {
20521
21285
  else if (/^[0-9]+$/u.test(part.name))
20522
21286
  retained = stateMonitor(state)?.positionals.get(String(Number(part.name) - 1), value) ?? value;
20523
21287
  else if (part.name === "@" || part.name === "*") {
20524
- const separator = hereString && (part.name === "@" || !part.quoted) ? " " : this.ifsSeparator(state, io);
21288
+ const separator = part.name === "@" && !split || hereString && (part.name === "@" || !part.quoted) ? " " : this.ifsSeparator(state, io);
20525
21289
  const values = this.positionalValues(state);
20526
21290
  retained = concatShellValues(values.flatMap((entry, index) => index ? [separator, entry] : [entry]), io[valueScope]);
20527
21291
  }
@@ -20906,7 +21670,7 @@ export class Runtime {
20906
21670
  fastValueWord(word, state, io, split, pattern, hereDocument, braces, assignmentStart, overrideDiagnosticLine) {
20907
21671
  const monitor = stateMonitor(state);
20908
21672
  const rawState = monitor ? monitor.raw : state;
20909
- if (pattern || word.parts.length === 0 || (rawState.depth + (io.parameterDepth ?? 0)) >= 32 || hasActiveVariableAttributes(rawState))
21673
+ if (pattern || word.parts.length === 0 || (rawState.depth + (io.parameterDepth ?? 0)) >= 32 || hasNonNamerefAttributes(rawState))
20910
21674
  return undefined;
20911
21675
  if (guestArrays(state) && word.parts.some(part => part.kind === "arithmetic" && !part.expression.error && !isSafeSmiProgram(part.expression)))
20912
21676
  return undefined;
@@ -20915,7 +21679,7 @@ export class Runtime {
20915
21679
  this.signal.throwIfAborted();
20916
21680
  let out = "";
20917
21681
  for (let i = 0; i < word.parts.length; i++) {
20918
- const part = word.parts[i];
21682
+ let part = word.parts[i];
20919
21683
  if (part.kind === "text") {
20920
21684
  if (part.byteValue || invokedValues.has(part))
20921
21685
  return undefined;
@@ -20933,8 +21697,17 @@ export class Runtime {
20933
21697
  out += part.value;
20934
21698
  }
20935
21699
  else if (part.kind === "variable") {
20936
- if (rawState.variableAttributes?.get(part.name)?.includes("n"))
20937
- return undefined;
21700
+ if (rawState.variableAttributes?.get(part.name)?.includes("n")) {
21701
+ if (part.indirect)
21702
+ return undefined;
21703
+ const resolvedName = resolveSyncNameref(rawState, part.name);
21704
+ if (resolvedName === part.name)
21705
+ return undefined;
21706
+ const origSel = getArraySelector(part);
21707
+ part = { ...part, name: resolvedName };
21708
+ if (origSel)
21709
+ setArraySelector(part, origSel);
21710
+ }
20938
21711
  if (split && !part.quoted && (rawVars.IFS !== undefined && rawVars.IFS !== " \t\n"))
20939
21712
  return undefined;
20940
21713
  if (!part.indirect && !part.prefixNames && !part.length && !part.substring && !part.transform && part.operator === undefined && getArraySelector(part) === undefined && (part.name === "?" || part.name === "#" || (part.name.length === 1 && part.name >= "1" && part.name <= "9"))) {
@@ -20964,7 +21737,7 @@ export class Runtime {
20964
21737
  const refName = refRaw === undefined ? undefined : (this._syncArithRawWriteOnly && this._syncArithTouched?.has(part.name) ? refRaw : (monitor?.values.get(part.name, refRaw) ?? refRaw));
20965
21738
  if (typeof refName !== "string" || !isShellIdentifier(refName))
20966
21739
  return undefined;
20967
- if (activeArrayStore?.get(refName) || rawState.variableAttributes?.get(refName) || refName === "PIPESTATUS" || refName === "LINENO" || refName === "_" || refName === "FUNCNAME") {
21740
+ if (activeArrayStore?.get(refName) || rawState.variableAttributes?.get(refName) || refName === "PIPESTATUS" || refName === "LINENO" || refName === "_" || refName === "FUNCNAME" || refName === "DIRSTACK") {
20968
21741
  return undefined;
20969
21742
  }
20970
21743
  const targetRaw = rawVars[refName];
@@ -20980,7 +21753,7 @@ export class Runtime {
20980
21753
  }
20981
21754
  if (part.indirect || part.prefixNames || part.specialParameter || part.transform)
20982
21755
  return undefined;
20983
- if (part.name === "@" || part.name === "*" || part.name === "PIPESTATUS" || part.name === "LINENO" || part.name === "_" || part.name === "FUNCNAME")
21756
+ if (part.name === "@" || part.name === "*" || part.name === "PIPESTATUS" || part.name === "LINENO" || part.name === "_" || part.name === "FUNCNAME" || part.name === "DIRSTACK")
20984
21757
  return undefined;
20985
21758
  if (!isShellIdentifier(part.name))
20986
21759
  return undefined;
@@ -21061,11 +21834,12 @@ export class Runtime {
21061
21834
  }
21062
21835
  const ifsVal = monitor?.values.get("IFS", rawVars.IFS ?? " ") ?? rawVars.IFS ?? " ";
21063
21836
  if (validElemOp && typeof ifsVal === "string" && (ifsVal.length === 0 || ifsVal.charCodeAt(0) < 128)) {
21064
- const sep = selector.separator === "@" ? " " : ifsVal.length > 0 ? ifsVal[0] : "";
21837
+ const sep = selector.separator === "@" && (!isKeys || ifsVal.length === 0) ? " " : ifsVal.length > 0 ? ifsVal[0] : "";
21838
+ const parameterLine = part.line ?? overrideDiagnosticLine;
21065
21839
  const evalSliceInt = (w) => {
21066
21840
  if (w.parts.length === 0)
21067
21841
  return 0;
21068
- const expanded = this.fastValueWord(w, rawState, io, false, false, false, false, undefined, part.line ?? overrideDiagnosticLine);
21842
+ const expanded = this.fastValueWord(w, rawState, io, false, false, false, false, undefined, parameterLine);
21069
21843
  if (typeof expanded !== "string")
21070
21844
  return undefined;
21071
21845
  const trimmed = expanded.trim();
@@ -21316,10 +22090,11 @@ export class Runtime {
21316
22090
  if (part.length || part.operator !== undefined || this.budget.limits.maxExpansionBytes !== Infinity || rawState.depth + (io.parameterDepth ?? 0) >= 60 || !part.substring.offset.parts.every(p => p.kind === "text" || p.kind === "arithmetic" || (p.kind === "variable" && !p.operator && !p.substring)) || (part.substring.length && !part.substring.length.parts.every(p => p.kind === "text" || p.kind === "arithmetic" || (p.kind === "variable" && !p.operator && !p.substring))) || ValueScope.prototype.reserve !== defaultValueScopeReserve || String.prototype.codePointAt !== defaultStringCodePointAt || globalThis.Float64Array !== defaultFloat64Array || (!part.substring.offset.parts.length && !part.substring.length)) {
21317
22091
  return undefined;
21318
22092
  }
22093
+ const parameterLine = part.line ?? overrideDiagnosticLine;
21319
22094
  const evalSubInt = (w) => {
21320
22095
  if (w.parts.length === 0)
21321
22096
  return 0;
21322
- const expanded = this.fastValueWord(w, rawState, io, false, false, false, false, undefined, part.line ?? overrideDiagnosticLine);
22097
+ const expanded = this.fastValueWord(w, rawState, io, false, false, false, false, undefined, parameterLine);
21323
22098
  if (typeof expanded !== "string")
21324
22099
  return undefined;
21325
22100
  const trimmed = expanded.trim();
@@ -21393,7 +22168,8 @@ export class Runtime {
21393
22168
  if (part.operator !== undefined) {
21394
22169
  if (this.budget.limits.maxExpansionBytes !== Infinity || rawState.depth + (io.parameterDepth ?? 0) >= 60 || ValueScope.prototype.reserve !== defaultValueScopeReserve || String.prototype.codePointAt !== defaultStringCodePointAt || globalThis.Float64Array !== defaultFloat64Array)
21395
22170
  return undefined;
21396
- if (!rawState.nocasematch && (part.operator === "/" || part.operator === "//" || part.operator === "/#" || part.operator === "/%") && part.alternate && part.alternate.parts.length >= 1 && part.alternate.parts.every(p => p.quoted === part.alternate.parts[0].quoted && ((p.kind === "text" && !p.byteValue) || (p.kind === "variable" && !p.operator && !p.substring && !p.length && !p.indirect && !p.prefixNames && !p.specialParameter && !p.transform && getArraySelector(p) === undefined && isShellIdentifier(p.name)))) && (!part.replacement || part.replacement.parts.length === 0 || part.replacement.parts.every(p => (p.kind === "text" && !p.byteValue) || (p.kind === "variable" && !p.operator && !p.substring && !p.length && !p.indirect && !p.prefixNames && !p.specialParameter && !p.transform && getArraySelector(p) === undefined && isShellIdentifier(p.name))))) {
22171
+ const patternQuoted = part.alternate?.parts[0]?.quoted;
22172
+ if (!rawState.nocasematch && (part.operator === "/" || part.operator === "//" || part.operator === "/#" || part.operator === "/%") && part.alternate && part.alternate.parts.length >= 1 && part.alternate.parts.every(p => p.quoted === patternQuoted && ((p.kind === "text" && !p.byteValue) || (p.kind === "variable" && !p.operator && !p.substring && !p.length && !p.indirect && !p.prefixNames && !p.specialParameter && !p.transform && getArraySelector(p) === undefined && isShellIdentifier(p.name)))) && (!part.replacement || part.replacement.parts.length === 0 || part.replacement.parts.every(p => (p.kind === "text" && !p.byteValue) || (p.kind === "variable" && !p.operator && !p.substring && !p.length && !p.indirect && !p.prefixNames && !p.specialParameter && !p.transform && getArraySelector(p) === undefined && isShellIdentifier(p.name))))) {
21397
22173
  const patQuoted = part.alternate.parts[0].quoted;
21398
22174
  const pat = (part.alternate.parts.length === 1 && part.alternate.parts[0].kind === "text")
21399
22175
  ? part.alternate.parts[0].value
@@ -21467,7 +22243,7 @@ export class Runtime {
21467
22243
  out += replaced;
21468
22244
  continue;
21469
22245
  }
21470
- if ((part.operator === "#" || part.operator === "##" || part.operator === "%" || part.operator === "%%") && part.alternate && part.alternate.parts.length >= 1 && part.alternate.parts.every(p => p.quoted === part.alternate.parts[0].quoted && ((p.kind === "text" && !p.byteValue) || (p.kind === "variable" && !p.operator && !p.substring && !p.length && !p.indirect && !p.prefixNames && !p.specialParameter && !p.transform && getArraySelector(p) === undefined && isShellIdentifier(p.name))))) {
22246
+ if ((part.operator === "#" || part.operator === "##" || part.operator === "%" || part.operator === "%%") && part.alternate && part.alternate.parts.length >= 1 && part.alternate.parts.every(p => p.quoted === patternQuoted && ((p.kind === "text" && !p.byteValue) || (p.kind === "variable" && !p.operator && !p.substring && !p.length && !p.indirect && !p.prefixNames && !p.specialParameter && !p.transform && getArraySelector(p) === undefined && isShellIdentifier(p.name))))) {
21471
22247
  const patQuoted = part.alternate.parts[0].quoted;
21472
22248
  const pat = (part.alternate.parts.length === 1 && part.alternate.parts[0].kind === "text")
21473
22249
  ? part.alternate.parts[0].value
@@ -21574,7 +22350,10 @@ export class Runtime {
21574
22350
  out += converted;
21575
22351
  continue;
21576
22352
  }
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))) {
22353
+ 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))) {
22354
+ const isAssignOp = part.operator === "=" || part.operator === ":=";
22355
+ 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))))
22356
+ return undefined;
21578
22357
  const raw = rawVars[part.name];
21579
22358
  const cur = raw === undefined ? undefined : (this._syncArithRawWriteOnly && this._syncArithTouched?.has(part.name) ? raw : (monitor?.values.get(part.name, raw) ?? raw));
21580
22359
  if (cur !== undefined && typeof cur !== "string")
@@ -21592,11 +22371,25 @@ export class Runtime {
21592
22371
  resolvedScalar = "";
21593
22372
  }
21594
22373
  else {
21595
- const isMissing = cur === undefined || (part.operator === ":-" && cur === "");
22374
+ const isMissing = cur === undefined || ((part.operator === ":-" || part.operator === ":=") && cur === "");
21596
22375
  if (isMissing) {
21597
22376
  const altVal = this.fastValueWord(part.alternate, rawState, io, false, false, false, false, undefined, part.line ?? overrideDiagnosticLine);
21598
22377
  if (typeof altVal !== "string")
21599
22378
  return undefined;
22379
+ if (isAssignOp) {
22380
+ if (this._syncArithRawWriteOnly && this._syncArithTouched) {
22381
+ rawState.variables[part.name] = altVal;
22382
+ this._syncArithTouched.add(part.name);
22383
+ }
22384
+ else if (monitor) {
22385
+ monitor.publishStringVariable(part.name, altVal);
22386
+ }
22387
+ else {
22388
+ rawState.variables[part.name] = altVal;
22389
+ }
22390
+ if (rawState.allexport)
22391
+ (monitor ? monitor.proxy.exported : rawState.exported).add(part.name);
22392
+ }
21600
22393
  resolvedScalar = altVal;
21601
22394
  }
21602
22395
  else
@@ -21721,7 +22514,7 @@ export class Runtime {
21721
22514
  return false;
21722
22515
  continue;
21723
22516
  }
21724
- if (p.prefixNames || p.specialParameter || p.transform || p.name === "@" || p.name === "*" || p.name === "PIPESTATUS" || p.name === "LINENO" || p.name === "_" || p.name === "FUNCNAME" || !isShellIdentifier(p.name)) {
22517
+ if (p.prefixNames || p.specialParameter || p.transform || p.name === "@" || p.name === "*" || p.name === "PIPESTATUS" || p.name === "LINENO" || p.name === "_" || p.name === "FUNCNAME" || p.name === "DIRSTACK" || !isShellIdentifier(p.name)) {
21725
22518
  return false;
21726
22519
  }
21727
22520
  if (p.operator !== undefined && (p.operator === "=" || p.operator === ":=" || p.operator === "?" || p.operator === ":?"))
@@ -22827,7 +23620,9 @@ export class Runtime {
22827
23620
  : numericIndex(selector.index, 4294967295);
22828
23621
  if (index === undefined && !binding?.associative)
22829
23622
  throw new ArrayFailure("index outside 0..4294967295");
22830
- value = binding ? index === undefined ? undefined : binding.getValue(index) : index === 0 ? value : undefined;
23623
+ value = binding ? index === undefined ? undefined : binding.getValue(index)
23624
+ : part.name === "FUNCNAME" && state.variables.FUNCNAME === undefined ? (index === undefined ? undefined : state.functionNames?.[index])
23625
+ : index === 0 ? value : undefined;
22831
23626
  }
22832
23627
  const missing = value === undefined || (part.operator.startsWith(":") && shellValueByteLength(value) === 0);
22833
23628
  if (part.operator.endsWith("+") ? !missing : missing) {
@@ -23178,8 +23973,9 @@ export class Runtime {
23178
23973
  return this.arithmeticValue(prepareArithmetic(source || "0", this.budget.parsing), state, io);
23179
23974
  };
23180
23975
  offset = await evaluate(substring.offset);
23976
+ const maximum = binding?.maximum ?? (name === "FUNCNAME" && state.variables.FUNCNAME === undefined ? (state.functionNames?.length ?? 0) - 1 : state.variables[name] === undefined ? -1 : 0);
23181
23977
  if (offset < 0n)
23182
- offset += BigInt((binding?.maximum ?? (state.variables[name] === undefined ? -1 : 0)) + 1);
23978
+ offset += BigInt(maximum + 1);
23183
23979
  if (substring.length) {
23184
23980
  count = await evaluate(substring.length);
23185
23981
  if (count < 0n)
@@ -23192,7 +23988,7 @@ export class Runtime {
23192
23988
  if (!binding) {
23193
23989
  if (name === "FUNCNAME" && state.variables.FUNCNAME === undefined) {
23194
23990
  const stack = state.functionNames ?? [];
23195
- const start = offset < 0n ? Math.max(0, stack.length + Number(offset)) : Number(offset);
23991
+ const start = Number(offset);
23196
23992
  const end = count === undefined ? stack.length : start + Number(count);
23197
23993
  const sliced = stack.slice(start, end);
23198
23994
  return keys ? sliced.map((_v, idx) => String(start + idx)) : sliced;