@poe-platform/safe-bash 0.1.737 → 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 (24) 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 +443 -180
  10. package/dist/safe-bash/core.browser.js.map +3 -3
  11. package/dist/safe-bash/shell/runtime.d.ts +3 -0
  12. package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
  13. package/dist/safe-bash/shell/runtime.js +540 -205
  14. package/dist/safe-bash/shell/runtime.js.map +1 -1
  15. package/dist/safe-bash-command-git/index.js +1 -1
  16. package/dist/safe-bash-regex-engine/index.js +2 -2
  17. package/dist/safe-bash-regex-engine/text/regex.js +2 -2
  18. package/dist/safe-bash-regex-engine/text/replacement-buffer.js +1 -1
  19. package/package.json +2 -2
  20. package/dist/safe-bash/command-assets/git_rust-CB24IEB6.wasm +0 -0
  21. package/dist/safe-bash/command-chunks/chunk-L6GGCZOK.js.map +0 -7
  22. /package/dist/safe-bash/chunks/{chunk-DFNSXDHP.js.map → chunk-JNHKE26E.js.map} +0 -0
  23. /package/dist/safe-bash/chunks/{chunk-N3RTTKMA.js.map → chunk-JTKTBT3B.js.map} +0 -0
  24. /package/dist/safe-bash/command-chunks/{chunk-YXAPLUY4.js.map → chunk-WFMUMKBQ.js.map} +0 -0
@@ -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)) {
@@ -4402,15 +4433,16 @@ export class Runtime {
4402
4433
  }
4403
4434
  }
4404
4435
  canSyncArithmeticOperands(tree, state, line, depth = 0) {
4405
- if (!tree || depth > 128 || hasActiveVariableAttributes(state) || state.readonlyVariables?.size)
4436
+ if (!tree || depth > 128 || hasNonNamerefAttributes(state) || state.readonlyVariables?.size)
4406
4437
  return false;
4407
4438
  switch (tree.kind) {
4408
4439
  case "literal": return true;
4409
4440
  case "name": {
4410
- if (tree.name === "OPTIND" || tree.name === "PIPESTATUS")
4441
+ const effName = resolveSyncNameref(state, tree.name);
4442
+ if (effName === "OPTIND" || effName === "PIPESTATUS")
4411
4443
  return false;
4412
4444
  const monitor = stateMonitor(state);
4413
- if (monitor?.hasOverlay(tree.name) || monitor?.store?.watches.has(tree.name))
4445
+ if (monitor?.hasOverlay(effName) || monitor?.store?.watches.has(effName))
4414
4446
  return false;
4415
4447
  if (tree.subscript !== undefined) {
4416
4448
  if (state.nounset || hasUnpreparedLocal(state, tree.name))
@@ -4728,6 +4760,8 @@ export class Runtime {
4728
4760
  const binding = arrayStore(state)?.get(name);
4729
4761
  if (binding)
4730
4762
  return binding.get(binding.associative ? binding.keys.get("30")?.index ?? -1 : 0);
4763
+ if (name === "DIRSTACK")
4764
+ return state.cwd;
4731
4765
  if (name === "FUNCNAME" && state.variables.FUNCNAME === undefined)
4732
4766
  return state.functionNames?.[0];
4733
4767
  if (name === "_" && state.variables._ === undefined)
@@ -4771,14 +4805,14 @@ export class Runtime {
4771
4805
  if (bracket < 0)
4772
4806
  return entries ? entries[0] !== undefined : this.variable(state, base) !== undefined;
4773
4807
  const selector = name.slice(bracket + 1, -1);
4774
- if (selector === "@" || selector === "*")
4775
- return entries ? entries.length > 0 : binding ? binding.values.size > 0 : this.variable(state, base) !== undefined;
4776
4808
  if (binding?.associative) {
4777
4809
  if (selector.length === 0 || selector.length > 4096 || [...selector].some(character => "$`\\\"'".includes(character)))
4778
4810
  return undefined;
4779
4811
  const key = binding.keys.get(fastStringHexIdentity(selector));
4780
4812
  return key !== undefined && binding.getValue(key.index) !== undefined;
4781
4813
  }
4814
+ if (selector === "@" || selector === "*")
4815
+ return entries ? entries.length > 0 : binding ? binding.values.size > 0 : this.variable(state, base) !== undefined;
4782
4816
  if (selector.length === 0 || selector.length > 9 || [...selector].some(character => !"0123456789".includes(character)) || selector.length > 1 && selector[0] === "0")
4783
4817
  return undefined;
4784
4818
  const index = Number(selector);
@@ -5241,24 +5275,162 @@ export class Runtime {
5241
5275
  }
5242
5276
  }
5243
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
+ }
5244
5411
  tryFastArrayAssignmentSync(assignment, state, io, diagnosticLine, declaration, associative, ignoreYield = false) {
5245
5412
  if (!ignoreYield && hasYieldCheckpoint(this.signal))
5246
5413
  return false;
5247
5414
  if (declaration)
5248
5415
  return false;
5416
+ const monitor = stateMonitor(state);
5417
+ const rawState = monitor ? monitor.raw : state;
5249
5418
  // Array writes must prepare scope observers before creating or mutating bindings.
5250
- const name = assignment.name;
5419
+ const name = resolveSyncNameref(rawState, assignment.name);
5251
5420
  if (hasUnpreparedLocal(state, name))
5252
5421
  return false;
5253
5422
  this._syncArithTouched?.delete(name);
5254
- const monitor = stateMonitor(state);
5255
- const rawState = monitor ? monitor.raw : state;
5256
5423
  if (rawState.readonlyVariables?.has(name) || rawState.variableAttributes?.get(name) || rawState.exported.has(name) || controlNames.has(name)) {
5257
5424
  return false;
5258
5425
  }
5259
5426
  // Cold activation belongs to normal command handling, which maps array refusals.
5260
5427
  const store = monitor?.store ?? (monitor && (this.budget.limits.maxExpansionBytes === Infinity && this.budget.limits.maxExpansionFields === Infinity) ? requireArrays(rawState) : undefined);
5261
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;
5262
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)) {
5263
5435
  return false;
5264
5436
  }
@@ -5355,6 +5527,8 @@ export class Runtime {
5355
5527
  const entry = assignment.entries[i];
5356
5528
  if (entry.append)
5357
5529
  return false;
5530
+ if (rawState.braceexpand !== false && entry.value.parts.some(p => !p.quoted && p.kind === "text" && p.value.includes("{")))
5531
+ return false;
5358
5532
  let explicitIdx;
5359
5533
  if (entry.index !== undefined) {
5360
5534
  const kSrc = (entry.index.source ?? entry.index.decimal).trim();
@@ -5362,8 +5536,21 @@ export class Runtime {
5362
5536
  return false;
5363
5537
  explicitIdx = Number(kSrc);
5364
5538
  }
5365
- 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)))) {
5366
- 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
+ }
5367
5554
  }
5368
5555
  let val = "";
5369
5556
  if (entry.value.parts.length > 0) {
@@ -6294,6 +6481,9 @@ export class Runtime {
6294
6481
  if (!watch.valid())
6295
6482
  throw new ArrayFailure("binding changed during writer admission");
6296
6483
  prepared.validate();
6484
+ // Clearing an indexed destination is an assignment even without elements.
6485
+ if (clear)
6486
+ prepared.binding.assigned = true;
6297
6487
  let retirement;
6298
6488
  monitor.publish(prepared.tickets, name, () => {
6299
6489
  delete state.variables[name];
@@ -8268,16 +8458,8 @@ export class Runtime {
8268
8458
  return true;
8269
8459
  }
8270
8460
  }
8271
- if ((w0Plain === "mapfile" || w0Plain === "readarray") &&
8272
- (command.words.length === 2 || command.words.length === 3) &&
8273
- command.words[1]?.plain === "-t" &&
8274
- !hasShellFunction(rawState, w0Plain) &&
8275
- !rawState.extensions?.builtins.has(w0Plain)) {
8276
- const arrVp = command.words[2]?.plain ?? "MAPFILE";
8277
- const st = stateMonitor(rawState)?.store;
8278
- if (isShellIdentifier(arrVp) && arrVp !== "OPTIND" && arrVp !== "PIPESTATUS" && !rawState.readonlyVariables?.has(arrVp) && !st?.get(arrVp)?.associative)
8279
- return true;
8280
- }
8461
+ // mapfile can require the asynchronous path as its input or binding
8462
+ // changes. Never speculate across a loop that has already made writes.
8281
8463
  }
8282
8464
  return false;
8283
8465
  }
@@ -8311,7 +8493,7 @@ export class Runtime {
8311
8493
  return false;
8312
8494
  }
8313
8495
  if (arrayAssign.kind === "compound") {
8314
- const isAssoc = Boolean(st?.get(arrayAssign.name)?.associative);
8496
+ const isAssoc = Boolean(st?.get(resolveSyncNameref(rawState, arrayAssign.name))?.associative);
8315
8497
  return (arrayAssign.entries.length <= 32 && arrayAssign.entries.every(e => {
8316
8498
  if (e.append)
8317
8499
  return false;
@@ -8323,6 +8505,8 @@ export class Runtime {
8323
8505
  const kSrc = (e.index.source ?? e.index.decimal).trim();
8324
8506
  return /^(?:0|[1-9][0-9]{0,8})$/.test(kSrc) && isNoBraceSyncAssignWord(e.value);
8325
8507
  }
8508
+ if (this.canSyncArrayMembersWord(e.value, rawState))
8509
+ return true;
8326
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)));
8327
8511
  }));
8328
8512
  }
@@ -8356,7 +8540,7 @@ export class Runtime {
8356
8540
  }
8357
8541
  if (command.words.length >= 2 && command.words.length <= 16) {
8358
8542
  if (w0Plain === "unset" &&
8359
- rawState.locals.length === 0 &&
8543
+ depth === 0 && rawState.locals.length === 0 &&
8360
8544
  !hasShellFunction(rawState, "unset") &&
8361
8545
  !rawState.extensions?.builtins.has("unset")) {
8362
8546
  const uStart = command.words[1]?.plain === "-v" ? 2 : 1;
@@ -8380,6 +8564,7 @@ export class Runtime {
8380
8564
  }
8381
8565
  if (w0Plain === "export" &&
8382
8566
  rawState.locals.length === 0 &&
8567
+ depth === 0 &&
8383
8568
  !hasShellFunction(rawState, "export") &&
8384
8569
  !rawState.extensions?.builtins.has("export")) {
8385
8570
  let allOk = true;
@@ -8446,12 +8631,14 @@ export class Runtime {
8446
8631
  // Array locals require the typed declaration and restoration lifecycle.
8447
8632
  if (command.words[1]?.plain === "-a" || command.words[1]?.plain === "-A")
8448
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;
8449
8636
  let allValidLocal = true;
8450
- for (let idx = 1; idx < command.words.length; idx++) {
8637
+ for (let idx = (isNamerefDecl || isGlobalDecl) ? 2 : 1; idx < command.words.length; idx++) {
8451
8638
  const wArg = command.words[idx];
8452
8639
  const assignment = !getArrayAssignment(wArg) ? this.assignment(wArg) : undefined;
8453
8640
  if (assignment) {
8454
- if ((assignment.name === "OPTIND" && (assignment.append || rawState.variableAttributes?.get("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)) {
8455
8642
  allValidLocal = false;
8456
8643
  break;
8457
8644
  }
@@ -8467,12 +8654,10 @@ export class Runtime {
8467
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)) {
8468
8655
  return true;
8469
8656
  }
8470
- if (command.words.length >= 3 && command.words.length <= 16 && w0Plain === "getopts" && !hasShellFunction(rawState, "getopts") && !rawState.extensions?.builtins.has("getopts")) {
8471
- const optVar = command.words[2]?.plain;
8472
- if (optVar && isShellIdentifier(optVar) && optVar !== "OPTIND" && optVar !== "PIPESTATUS" && optVar !== "_" && !rawState.readonlyVariables?.has(optVar) && !rawState.readonlyVariables?.has("OPTIND") && !rawState.readonlyVariables?.has("OPTARG") && isNoBraceSyncWord(command.words[1]) && (command.words.length === 3 || (command.words.length === 4 && (() => { const ne = command.words[3].parts.filter(p => !(p.kind === "text" && p.value === "")); return ne.length === 1 && ne[0].kind === "variable" && ne[0].name === "@" && ne[0].quoted; })()) || command.words.slice(3).every(isNoBraceSyncWord))) {
8473
- return true;
8474
- }
8475
- }
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;
8476
8661
  if (w0Plain === "echo" && !hasShellFunction(rawState, "echo") && !rawState.extensions?.builtins.has("echo")) {
8477
8662
  const w1Plain = command.words[1]?.plain;
8478
8663
  if (w1Plain === undefined || !w1Plain.startsWith("-") || w1Plain === "-n" || w1Plain === "-e" || w1Plain === "-ne" || w1Plain === "-en")
@@ -8503,7 +8688,17 @@ export class Runtime {
8503
8688
  }
8504
8689
  if (w0Plain !== undefined && rawState.functions.has(w0Plain) && command.words.slice(1).every(isNoBraceSyncWord)) {
8505
8690
  const fnBody = rawState.functions.get(w0Plain);
8506
- 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
+ }
8507
8702
  }
8508
8703
  return false;
8509
8704
  }
@@ -8597,13 +8792,13 @@ export class Runtime {
8597
8792
  const sel0 = p0?.kind === "variable" ? getArraySelector(p0) : undefined;
8598
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 === "@");
8599
8794
  const isBraceRange = Boolean(p0?.kind === "text" && !p0.quoted && /^\{-?\d+\.\.-?\d+(?:\.\.-?\d+)?\}$/.test(p0.value));
8600
- 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)))))
8601
8796
  return false;
8602
8797
  }
8603
8798
  else {
8604
8799
  for (let i = 0; i < command.words.length; i++) {
8605
8800
  const w = command.words[i];
8606
- 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)))))
8607
8802
  return false;
8608
8803
  }
8609
8804
  }
@@ -8710,7 +8905,9 @@ export class Runtime {
8710
8905
  }
8711
8906
  else if (wp0 === "local" || wp0 === "declare" || wp0 === "typeset") {
8712
8907
  const isArrDecl = c.words[1]?.plain === "-a" || c.words[1]?.plain === "-A";
8713
- 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++) {
8714
8911
  const wk = c.words[k];
8715
8912
  const aa = getArrayAssignment(wk);
8716
8913
  if (aa) {
@@ -8727,6 +8924,9 @@ export class Runtime {
8727
8924
  if (wp0 === "local" || d > 0)
8728
8925
  localArrayVars.add(nm);
8729
8926
  }
8927
+ else if (isNameref) {
8928
+ localArrayVars.add(nm);
8929
+ }
8730
8930
  else {
8731
8931
  scalarVars.add(nm);
8732
8932
  const pv = asg?.value.plain;
@@ -8737,8 +8937,8 @@ export class Runtime {
8737
8937
  }
8738
8938
  }
8739
8939
  }
8740
- else if (wp0 === "printf" && c.words[1]?.plain === "-v" && c.words[2]?.plain) {
8741
- 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;
8742
8942
  const br = target.indexOf("[");
8743
8943
  if (br > 0)
8744
8944
  arrayVars.add(target.slice(0, br));
@@ -9101,7 +9301,24 @@ export class Runtime {
9101
9301
  }
9102
9302
  return undefined;
9103
9303
  }
9104
- 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) {
9105
9322
  if (!existing) {
9106
9323
  if (store) {
9107
9324
  if (publishPipelineStatus(rawState, finalStatus === 0 ? singleStatusZero : singleStatusOne, this.signal, scope))
@@ -9113,7 +9330,7 @@ export class Runtime {
9113
9330
  }
9114
9331
  }
9115
9332
  else {
9116
- (elem0 ?? existing.values.get(0)).text.shellValue = finalStatus === 0 ? "0" : "1";
9333
+ this.setSyncPipeStatusCell(existing, finalStatus === 0 ? "0" : "1");
9117
9334
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
9118
9335
  }
9119
9336
  rawState.status = finalStatus;
@@ -9135,7 +9352,7 @@ export class Runtime {
9135
9352
  return undefined;
9136
9353
  const rawState = monitor.raw;
9137
9354
  const gArrays = guestArrays(rawState);
9138
- 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))
9139
9356
  return undefined;
9140
9357
  if (((this.budget.commands + 1) & 127) === 0) {
9141
9358
  if (hasYieldCheckpoint(this.signal) || (this._syncReturnDepth === 0 && ((this.budget.commands + 1) & 8191) === 0))
@@ -9178,10 +9395,10 @@ export class Runtime {
9178
9395
  return undefined;
9179
9396
  }
9180
9397
  const elem0 = existing?.values.get(0);
9181
- if (existing && (!elem0 || elem0.text.bytes !== 1))
9398
+ if (existing && !elem0)
9182
9399
  return undefined;
9183
9400
  const canMutatePipeStatus = !existing || (existing.references === 1 && elem0.text.references === 1);
9184
- 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);
9185
9402
  if (command.kind === "function") {
9186
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))) {
9187
9404
  return undefined;
@@ -9244,7 +9461,7 @@ export class Runtime {
9244
9461
  }
9245
9462
  }
9246
9463
  else {
9247
- activeExisting.values.get(0).text.shellValue = finalStatus === 0 ? "0" : "1";
9464
+ this.setSyncPipeStatusCell(activeExisting, finalStatus === 0 ? "0" : "1");
9248
9465
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
9249
9466
  }
9250
9467
  rawState.status = finalStatus;
@@ -9350,13 +9567,13 @@ export class Runtime {
9350
9567
  }
9351
9568
  const scalarMutMatch = fastSrc.length <= 64 ? SIMPLE_SCALAR_MUT_RE.exec(fastSrc) : null;
9352
9569
  if (scalarMutMatch) {
9353
- const varName = scalarMutMatch[1];
9570
+ const varName = resolveSyncNameref(rawState, scalarMutMatch[1]);
9354
9571
  const binOp = scalarMutMatch[2];
9355
9572
  const rhs1 = Number(scalarMutMatch[3]);
9356
9573
  const rhsOp = scalarMutMatch[4];
9357
9574
  const rhs2 = scalarMutMatch[5] !== undefined ? Number(scalarMutMatch[5]) : 0;
9358
9575
  const rhsNum = rhsOp === "+" ? rhs1 + rhs2 : rhsOp === "-" ? rhs1 - rhs2 : rhsOp === "*" ? rhs1 * rhs2 : rhs1;
9359
- if (varName !== "OPTIND" && varName !== "PIPESTATUS" && !store?.get(varName) && !hasActiveVariableAttributes(rawState)) {
9576
+ if (varName !== "OPTIND" && varName !== "PIPESTATUS" && !store?.get(varName) && !hasNonNamerefAttributes(rawState)) {
9360
9577
  const curVal = (this._syncArithRawWriteOnly && this._syncArithTouched?.has(varName))
9361
9578
  ? (rawState.variables[varName] ?? "0")
9362
9579
  : (monitor.values.get(varName, rawState.variables[varName] ?? "0") ?? rawState.variables[varName] ?? "0");
@@ -9526,9 +9743,12 @@ export class Runtime {
9526
9743
  }
9527
9744
  return res;
9528
9745
  }
9529
- const fastLoopRes = this.trySyncLoop(command, pipeline, rawState, monitor, store, existing, elem0, canMutatePipeStatus, io, diagnosticLine);
9530
- if (fastLoopRes !== undefined)
9531
- 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
+ }
9532
9752
  }
9533
9753
  if (command.kind === "group" || command.kind === "if" || command.kind === "case" || command.kind === "while" || command.kind === "until" || command.kind === "for" || command.kind === "arithmetic-for") {
9534
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)))) {
@@ -9750,10 +9970,10 @@ export class Runtime {
9750
9970
  }
9751
9971
  else if (command.words.length === 1) {
9752
9972
  const w0 = command.words[0];
9753
- const nonEmpty0 = w0.parts.filter(p => !(p.kind === "text" && p.value === ""));
9754
- 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));
9755
9975
  const sel0 = p0?.kind === "variable" ? getArraySelector(p0) : undefined;
9756
- 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 === "@") {
9757
9977
  const b = monitor.store?.get(p0.name);
9758
9978
  items = [];
9759
9979
  if (b) {
@@ -9792,6 +10012,12 @@ export class Runtime {
9792
10012
  if (!items && command.words) {
9793
10013
  items = [];
9794
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
+ }
9795
10021
  let fv;
9796
10022
  try {
9797
10023
  fv = this.fastValueWord(command.words[i], rawState, io, false, false, false, false, undefined, diagnosticLine);
@@ -9994,7 +10220,7 @@ export class Runtime {
9994
10220
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
9995
10221
  }
9996
10222
  else {
9997
- elem0.text.shellValue = "0";
10223
+ this.setSyncPipeStatusCell(existing, "0");
9998
10224
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
9999
10225
  }
10000
10226
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10091,7 +10317,7 @@ export class Runtime {
10091
10317
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10092
10318
  }
10093
10319
  else {
10094
- elem0.text.shellValue = "0";
10320
+ this.setSyncPipeStatusCell(existing, "0");
10095
10321
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10096
10322
  }
10097
10323
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10107,8 +10333,6 @@ export class Runtime {
10107
10333
  if (w0Plain === "return" && (rawState.functionDepth > 0 || rawState.sourceDepth) && !hasActiveExtensions(rawState) && !hasShellFunction(rawState, "return") && !rawState.extensions?.builtins.has("return") && !pipeline.negate && canMutatePipeStatus) {
10108
10334
  const retStatus = rawState.status;
10109
10335
  const statusStr = retStatus === 0 ? "0" : retStatus === 1 ? "1" : String(retStatus);
10110
- if (existing && elem0.text.bytes !== statusStr.length)
10111
- return undefined;
10112
10336
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10113
10337
  this.budget.tick();
10114
10338
  rawState.substitutionStatus = 0;
@@ -10122,7 +10346,7 @@ export class Runtime {
10122
10346
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10123
10347
  }
10124
10348
  else {
10125
- elem0.text.shellValue = statusStr;
10349
+ this.setSyncPipeStatusCell(existing, statusStr);
10126
10350
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10127
10351
  }
10128
10352
  rawState.status = retStatus;
@@ -10154,7 +10378,7 @@ export class Runtime {
10154
10378
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10155
10379
  }
10156
10380
  else {
10157
- elem0.text.shellValue = "0";
10381
+ this.setSyncPipeStatusCell(existing, "0");
10158
10382
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10159
10383
  }
10160
10384
  rawState.status = 0;
@@ -10186,7 +10410,7 @@ export class Runtime {
10186
10410
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10187
10411
  }
10188
10412
  else {
10189
- elem0.text.shellValue = statusChar;
10413
+ this.setSyncPipeStatusCell(existing, statusChar);
10190
10414
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10191
10415
  }
10192
10416
  rawState.status = finalStatus;
@@ -10227,7 +10451,7 @@ export class Runtime {
10227
10451
  }
10228
10452
  }
10229
10453
  else {
10230
- activeExisting.values.get(0).text.shellValue = "0";
10454
+ this.setSyncPipeStatusCell(activeExisting, "0");
10231
10455
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10232
10456
  }
10233
10457
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10239,14 +10463,15 @@ export class Runtime {
10239
10463
  }
10240
10464
  const assignment = this.assignment(w0);
10241
10465
  if (assignment) {
10466
+ const targetAssignName = resolveSyncNameref(rawState, assignment.name);
10242
10467
  const hasSubPart = assignment.value.parts.some(part => part.kind === "substitution");
10243
- const existingArrAssign = store?.get(assignment.name);
10244
- if ((assignment.name === "OPTIND" && (assignment.append || rawState.variableAttributes?.get("OPTIND"))) ||
10245
- assignment.name === "PIPESTATUS" ||
10246
- 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("[") ||
10247
10472
  (hasSubPart && !this.isPureSyncValueWord(assignment.value, rawState)) ||
10248
- rawState.readonlyVariables?.has(assignment.name) ||
10249
- (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)))) {
10250
10475
  return undefined;
10251
10476
  }
10252
10477
  if (rawState.extensions && !rawState.extensions.eventDepth)
@@ -10260,14 +10485,14 @@ export class Runtime {
10260
10485
  }
10261
10486
  if (typeof fastAssigned !== "string")
10262
10487
  return undefined;
10263
- if (assignment.name === "OPTIND") {
10488
+ if (targetAssignName === "OPTIND") {
10264
10489
  const trimmedOptind = fastAssigned.trim();
10265
10490
  if (!/^[0-9]{1,6}$/.test(trimmedOptind))
10266
10491
  return undefined;
10267
10492
  fastAssigned = String(Number(trimmedOptind));
10268
10493
  }
10269
10494
  if (existingArrAssign) {
10270
- if (!this.tryFastArraySubscriptWriteSync(rawState, assignment.name, "0", fastAssigned))
10495
+ if (!this.tryFastArraySubscriptWriteSync(rawState, targetAssignName, "0", fastAssigned))
10271
10496
  return undefined;
10272
10497
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10273
10498
  this.budget.tick();
@@ -10291,7 +10516,7 @@ export class Runtime {
10291
10516
  }
10292
10517
  }
10293
10518
  else {
10294
- activeExisting.values.get(0).text.shellValue = "0";
10519
+ this.setSyncPipeStatusCell(activeExisting, "0");
10295
10520
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10296
10521
  }
10297
10522
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10303,8 +10528,8 @@ export class Runtime {
10303
10528
  }
10304
10529
  let finalAssigned = fastAssigned;
10305
10530
  if (assignment.append) {
10306
- const curRaw = rawState.variables[assignment.name];
10307
- 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);
10308
10533
  if (typeof curStr !== "string")
10309
10534
  return undefined;
10310
10535
  finalAssigned = curStr + finalAssigned;
@@ -10315,21 +10540,21 @@ export class Runtime {
10315
10540
  this.budget.tick();
10316
10541
  if (!hasSubPart)
10317
10542
  rawState.substitutionStatus = 0;
10318
- if (assignment.name !== "_" && rawState.variables._ !== undefined)
10543
+ if (targetAssignName !== "_" && rawState.variables._ !== undefined)
10319
10544
  delete rawState.variables._;
10320
10545
  rawState.lastArgument = "";
10321
10546
  if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10322
- rawState.variables[assignment.name] = finalAssigned;
10323
- this._syncArithTouched.add(assignment.name);
10547
+ rawState.variables[targetAssignName] = finalAssigned;
10548
+ this._syncArithTouched.add(targetAssignName);
10324
10549
  }
10325
10550
  else
10326
- monitor.publishStringVariable(assignment.name, finalAssigned);
10327
- if (assignment.name === "OPTIND") {
10551
+ monitor.publishStringVariable(targetAssignName, finalAssigned);
10552
+ if (targetAssignName === "OPTIND") {
10328
10553
  rawState.variables.OPTIND = finalAssigned;
10329
10554
  this.syncGetopts(rawState);
10330
10555
  }
10331
10556
  if (rawState.allexport)
10332
- monitor.proxy.exported.add(assignment.name);
10557
+ monitor.proxy.exported.add(targetAssignName);
10333
10558
  if (io.assignmentDiagnosticContext)
10334
10559
  io.assignmentDiagnosticContext.name = undefined;
10335
10560
  if (!existing) {
@@ -10337,7 +10562,7 @@ export class Runtime {
10337
10562
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10338
10563
  }
10339
10564
  else {
10340
- elem0.text.shellValue = "0";
10565
+ this.setSyncPipeStatusCell(existing, "0");
10341
10566
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10342
10567
  }
10343
10568
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10353,60 +10578,98 @@ export class Runtime {
10353
10578
  if (command.words.length >= 1) {
10354
10579
  const w0Plain = command.words[0].plain;
10355
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")) {
10356
- const targetVar = command.words[2]?.plain;
10357
10581
  const def = this.commands.get("printf");
10358
- 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)) {
10359
- fastSubScratchArgs.length = 0;
10360
- let allStrings = true;
10361
- try {
10362
- for (let i = 3; i < command.words.length; i++) {
10363
- const v = this.fastValueWord(command.words[i], rawState, io, true, false, false, true, undefined, diagnosticLine);
10364
- if (typeof v !== "string") {
10365
- allStrings = false;
10366
- break;
10367
- }
10368
- fastSubScratchArgs.push(v);
10369
- }
10370
- }
10371
- catch {
10372
- allStrings = false;
10373
- }
10374
- const lastArg = fastSubScratchArgs.length > 0 ? fastSubScratchArgs[fastSubScratchArgs.length - 1] : targetVar;
10375
- const formatted = allStrings ? tryFastPrintf(fastSubScratchArgs) : undefined;
10376
- fastSubScratchArgs.length = 0;
10377
- if (formatted !== undefined && formatted.length <= this.budget.limits.maxExpansionBytes && (formatted.length * 3 <= this.budget.limits.maxExpansionBytes || shellValueByteLength(formatted) <= this.budget.limits.maxExpansionBytes)) {
10378
- if (rawState.extensions && !rawState.extensions.eventDepth)
10379
- publishCommandSpelling(rawState, commandSpelling(command));
10380
- const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
10381
- this.budget.tick();
10382
- rawState.substitutionStatus = 0;
10383
- if (rawState.variables._ !== undefined)
10384
- delete rawState.variables._;
10385
- rawState.lastArgument = lastArg;
10386
- if (this._syncArithRawWriteOnly && this._syncArithTouched) {
10387
- rawState.variables[targetVar] = formatted;
10388
- this._syncArithTouched.add(targetVar);
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);
10389
10587
  }
10390
- else
10391
- monitor.publishStringVariable(targetVar, formatted);
10392
- if (rawState.allexport)
10393
- monitor.proxy.exported.add(targetVar);
10394
- if (io.assignmentDiagnosticContext)
10395
- io.assignmentDiagnosticContext.name = undefined;
10396
- if (!existing) {
10397
- monitor.lazyPipeStatus = singleStatusZero;
10398
- monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10588
+ catch {
10589
+ targetSpec = undefined;
10399
10590
  }
10400
- else {
10401
- elem0.text.shellValue = "0";
10402
- store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
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 {
10611
+ allStrings = false;
10612
+ }
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
+ }
10403
10672
  }
10404
- const finalStatus = pipeline.negate ? 1 : 0;
10405
- rawState.status = finalStatus;
10406
- monitor.epoch = restEpoch;
10407
- if (store)
10408
- store.epoch = restEpoch;
10409
- return finalStatus;
10410
10673
  }
10411
10674
  }
10412
10675
  }
@@ -10459,7 +10722,7 @@ export class Runtime {
10459
10722
  if (command.words.length === 3) {
10460
10723
  scanArgs = this._fastSubPositional ?? rawState.positional;
10461
10724
  }
10462
- else if (command.words.length === 4 && (() => { const ne = command.words[3].parts.filter(p => !(p.kind === "text" && p.value === "")); return ne.length === 1 && ne[0].kind === "variable" && ne[0].name === "@" && ne[0].quoted; })()) {
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; })()) {
10463
10726
  scanArgs = this._fastSubPositional ?? rawState.positional;
10464
10727
  if (scanArgs.length > 0)
10465
10728
  lastArg = scanArgs[scanArgs.length - 1];
@@ -10484,7 +10747,7 @@ export class Runtime {
10484
10747
  catch {
10485
10748
  scanArgs = undefined;
10486
10749
  }
10487
- if (typeof optstring === "string" && scanArgs !== undefined) {
10750
+ if (typeof optstring === "string" && optstring.startsWith(":") && scanArgs !== undefined) {
10488
10751
  rawState.getopts ??= cloneGetoptsBinding(rawState);
10489
10752
  const res = scanGetoptsSync(rawState.getopts.cursor, optstring, scanArgs);
10490
10753
  if (res !== undefined) {
@@ -10580,7 +10843,7 @@ export class Runtime {
10580
10843
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10581
10844
  }
10582
10845
  else {
10583
- elem0.text.shellValue = "0";
10846
+ this.setSyncPipeStatusCell(existing, "0");
10584
10847
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10585
10848
  }
10586
10849
  rawState.status = 0;
@@ -10686,7 +10949,7 @@ export class Runtime {
10686
10949
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
10687
10950
  }
10688
10951
  else {
10689
- elem0.text.shellValue = "0";
10952
+ this.setSyncPipeStatusCell(existing, "0");
10690
10953
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
10691
10954
  }
10692
10955
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -10926,33 +11189,7 @@ export class Runtime {
10926
11189
  else {
10927
11190
  const arrStore = monitor.store ?? requireArrays(rawState);
10928
11191
  existingArrayBinding = arrStore.get(arrayTarget);
10929
- if (!existingArrayBinding && rawState.variables[arrayTarget] === undefined && !arrStore.watches.has(arrayTarget) && !monitor.hasOverlay(arrayTarget)) {
10930
- const recTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
10931
- const recBinding = arrStore.takeRecycled(arrayTarget, false, true, recTickets);
10932
- if (recBinding) {
10933
- monitor.epoch = recTickets.epoch;
10934
- existingArrayBinding = recBinding;
10935
- }
10936
- else
10937
- try {
10938
- const created = IndexedBinding.create(arrStore.owner, false);
10939
- const nameByteLen = shellValueByteLength(arrayTarget);
10940
- const prepared = arrStore.prepareExistingName(arrayTarget, nameByteLen, arrStore.owner, this.signal);
10941
- if (prepared) {
10942
- const initTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
10943
- arrStore.publish(arrayTarget, created, initTickets, prepared, false, arrStore.owner);
10944
- monitor.epoch = initTickets.epoch;
10945
- existingArrayBinding = created;
10946
- }
10947
- else {
10948
- void created.release();
10949
- }
10950
- }
10951
- catch { }
10952
- }
10953
- if (!existingArrayBinding ||
10954
- existingArrayBinding.associative ||
10955
- existingArrayBinding.references !== 1 ||
11192
+ if ((existingArrayBinding ? existingArrayBinding.associative || existingArrayBinding.references !== 1 : rawState.variables[arrayTarget] !== undefined) ||
10956
11193
  arrStore.watches.has(arrayTarget) ||
10957
11194
  monitor.hasOverlay(arrayTarget)) {
10958
11195
  simpleRead = false;
@@ -11000,6 +11237,36 @@ export class Runtime {
11000
11237
  else
11001
11238
  lineStr = io.stdin.tryReadSimpleRawAsciiLineSync(readDelim, raw);
11002
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
+ }
11003
11270
  if (rawState.extensions && !rawState.extensions.eventDepth)
11004
11271
  publishCommandSpelling(rawState, commandSpelling(command));
11005
11272
  if (arrayTarget !== undefined && existingArrayBinding) {
@@ -11059,6 +11326,7 @@ export class Runtime {
11059
11326
  readArrayScratchFields.length = 0;
11060
11327
  delete rawState.variables[arrayTarget];
11061
11328
  monitor.epoch = tickets.epoch;
11329
+ existingArrayBinding.assigned = true;
11062
11330
  activeArrStore.revise(arrayTarget, existingArrayBinding, tickets);
11063
11331
  }
11064
11332
  catch (error) {
@@ -11138,7 +11406,7 @@ export class Runtime {
11138
11406
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11139
11407
  }
11140
11408
  else {
11141
- elem0.text.shellValue = "0";
11409
+ this.setSyncPipeStatusCell(existing, "0");
11142
11410
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11143
11411
  }
11144
11412
  rawState.status = 0;
@@ -11336,7 +11604,7 @@ export class Runtime {
11336
11604
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11337
11605
  }
11338
11606
  else {
11339
- elem0.text.shellValue = "0";
11607
+ this.setSyncPipeStatusCell(existing, "0");
11340
11608
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11341
11609
  }
11342
11610
  rawState.status = 0;
@@ -11410,7 +11678,7 @@ export class Runtime {
11410
11678
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11411
11679
  }
11412
11680
  else {
11413
- elem0.text.shellValue = "0";
11681
+ this.setSyncPipeStatusCell(existing, "0");
11414
11682
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11415
11683
  }
11416
11684
  rawState.status = 0;
@@ -11490,6 +11758,8 @@ export class Runtime {
11490
11758
  if (isLocalScope) {
11491
11759
  let existingLocal = currentFrame.get(an);
11492
11760
  if (!existingLocal) {
11761
+ if (this._syncArithRawWriteOnly && this._syncArithTouched?.has(an))
11762
+ monitor.values.invalidate(an);
11493
11763
  existingLocal = saveVariable(state, an);
11494
11764
  syncLocalArrayVariables.add(existingLocal);
11495
11765
  currentFrame.set(an, existingLocal);
@@ -11554,13 +11824,15 @@ export class Runtime {
11554
11824
  }
11555
11825
  return undefined;
11556
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;
11557
11829
  const parsedLocals = [];
11558
11830
  let allValidLocal = true;
11559
- for (let idx = 1; idx < command.words.length; idx++) {
11831
+ for (let idx = (isNamerefDecl || isGlobalDecl) ? 2 : 1; idx < command.words.length; idx++) {
11560
11832
  const wArg = command.words[idx];
11561
11833
  const assignment = !getArrayAssignment(wArg) ? this.assignment(wArg) : undefined;
11562
11834
  if (assignment) {
11563
- if ((assignment.name === "OPTIND" && (assignment.append || rawState.variableAttributes?.get("OPTIND"))) || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || rawState.readonlyVariables?.has(assignment.name) || store?.get(assignment.name)) {
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)) {
11564
11836
  allValidLocal = false;
11565
11837
  break;
11566
11838
  }
@@ -11593,10 +11865,28 @@ export class Runtime {
11593
11865
  break;
11594
11866
  }
11595
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
+ }
11596
11886
  if (allValidLocal && parsedLocals.length > 0) {
11597
- if (rawState.locals.length === 0 && parsedLocals.some(item => item.val === undefined && !rawState.variableAttributes?.has(item.name)))
11887
+ if ((rawState.locals.length === 0 || isGlobalDecl) && parsedLocals.some(item => item.val === undefined && !rawState.variableAttributes?.has(item.name)))
11598
11888
  return undefined;
11599
- const locals = rawState.locals.length > 0 ? rawState.locals[rawState.locals.length - 1] : undefined;
11889
+ const locals = (!isGlobalDecl && rawState.locals.length > 0) ? rawState.locals[rawState.locals.length - 1] : undefined;
11600
11890
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
11601
11891
  this.budget.tick();
11602
11892
  rawState.substitutionStatus = 0;
@@ -11610,7 +11900,7 @@ export class Runtime {
11610
11900
  const curVal = this._syncArithTouched.has(item.name)
11611
11901
  ? rawState.variables[item.name]
11612
11902
  : (rawState.variables[item.name] === undefined ? undefined : (monitor.values.get(item.name, rawState.variables[item.name]) ?? rawState.variables[item.name]));
11613
- locals.set(item.name, { attributes: undefined, value: typeof curVal === "string" ? curVal : undefined, exported: false, readOnly: false, ...(item.name === "OPTIND" ? { getopts: cloneGetoptsBinding(rawState) } : {}) });
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) } : {}) });
11614
11904
  }
11615
11905
  else
11616
11906
  locals.set(item.name, saveVariable(state, item.name));
@@ -11640,7 +11930,11 @@ export class Runtime {
11640
11930
  if (rawState.allexport)
11641
11931
  monitor.proxy.exported.add(item.name);
11642
11932
  rawState.lastArgument = `${item.name}=${finalVal}`;
11643
- if (isNewLocal && rawState.variableAttributes?.has(item.name))
11933
+ if (isNamerefDecl) {
11934
+ rawState.variableAttributes ??= new Map();
11935
+ rawState.variableAttributes.set(item.name, "n");
11936
+ }
11937
+ else if (isNewLocal && rawState.variableAttributes?.has(item.name))
11644
11938
  rawState.variableAttributes.delete(item.name);
11645
11939
  }
11646
11940
  else {
@@ -11671,7 +11965,7 @@ export class Runtime {
11671
11965
  }
11672
11966
  }
11673
11967
  else {
11674
- elem0.text.shellValue = "0";
11968
+ this.setSyncPipeStatusCell(existing, "0");
11675
11969
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11676
11970
  }
11677
11971
  rawState.status = 0;
@@ -11697,8 +11991,6 @@ export class Runtime {
11697
11991
  if (fastArg.length > 0 && fastArg.length <= 15 && /^-?[0-9]+$/.test(fastArg)) {
11698
11992
  const retStatus = ((Number(fastArg) % 256) + 256) % 256;
11699
11993
  const statusStr = retStatus === 0 ? "0" : retStatus === 1 ? "1" : String(retStatus);
11700
- if (existing && elem0.text.bytes !== statusStr.length)
11701
- return undefined;
11702
11994
  const prevStatus = rawState.status;
11703
11995
  const restEpoch = monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets).epoch;
11704
11996
  this.budget.tick();
@@ -11713,7 +12005,7 @@ export class Runtime {
11713
12005
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11714
12006
  }
11715
12007
  else {
11716
- elem0.text.shellValue = statusStr;
12008
+ this.setSyncPipeStatusCell(existing, statusStr);
11717
12009
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11718
12010
  }
11719
12011
  rawState.status = retStatus;
@@ -11767,10 +12059,14 @@ export class Runtime {
11767
12059
  else {
11768
12060
  const savedPos = rawState.positional;
11769
12061
  rawState.positional = fnArgs;
12062
+ rawState.locals.push(new Map());
12063
+ rawState.functionDepth++;
11770
12064
  try {
11771
12065
  canSyncFn = this.canSyncScriptCompound(fnBody.body, rawState, 1);
11772
12066
  }
11773
12067
  finally {
12068
+ rawState.functionDepth--;
12069
+ rawState.locals.pop();
11774
12070
  rawState.positional = savedPos;
11775
12071
  }
11776
12072
  }
@@ -11891,8 +12187,8 @@ export class Runtime {
11891
12187
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
11892
12188
  }
11893
12189
  }
11894
- else if (activeExisting.values.get(0).text.bytes === statusStr.length) {
11895
- activeExisting.values.get(0).text.shellValue = statusStr;
12190
+ else {
12191
+ this.setSyncPipeStatusCell(activeExisting, statusStr);
11896
12192
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11897
12193
  }
11898
12194
  rawState.status = exitStatus;
@@ -11963,7 +12259,7 @@ export class Runtime {
11963
12259
  }
11964
12260
  }
11965
12261
  else {
11966
- activeExisting.values.get(0).text.shellValue = "0";
12262
+ this.setSyncPipeStatusCell(activeExisting, "0");
11967
12263
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
11968
12264
  }
11969
12265
  rawState.status = 0;
@@ -11997,7 +12293,7 @@ export class Runtime {
11997
12293
  rawState.depth++;
11998
12294
  let evalStatus;
11999
12295
  try {
12000
- 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));
12001
12297
  }
12002
12298
  finally {
12003
12299
  rawState.depth--;
@@ -12017,7 +12313,7 @@ export class Runtime {
12017
12313
  }
12018
12314
  }
12019
12315
  else {
12020
- activeExisting.values.get(0).text.shellValue = "0";
12316
+ this.setSyncPipeStatusCell(activeExisting, "0");
12021
12317
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
12022
12318
  }
12023
12319
  }
@@ -12101,7 +12397,7 @@ export class Runtime {
12101
12397
  }
12102
12398
  }
12103
12399
  else {
12104
- elem0.text.shellValue = statusChar;
12400
+ this.setSyncPipeStatusCell(existing, statusChar);
12105
12401
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
12106
12402
  }
12107
12403
  const finalStatus = pipeline.negate ? Number(fastPred === 0) : fastPred;
@@ -12166,7 +12462,7 @@ export class Runtime {
12166
12462
  else {
12167
12463
  const idxNum = /^(?:0|[1-9][0-9]{0,8})$/.test(subExpr) ? Number(subExpr) : resolveSimpleArithOperand(subExpr, rawState, monitor, arrStore, this._syncArithRawWriteOnly ? this._syncArithTouched : undefined);
12168
12464
  if (idxNum !== undefined && idxNum >= 0 && idxNum <= 2147483647) {
12169
- 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)) {
12170
12466
  const created = IndexedBinding.create(arrStore.owner, false);
12171
12467
  const prepared = arrStore.prepareExistingName(arrName, shellValueByteLength(arrName), arrStore.owner, this.signal);
12172
12468
  if (prepared) {
@@ -12212,7 +12508,7 @@ export class Runtime {
12212
12508
  }
12213
12509
  }
12214
12510
  else {
12215
- activeExisting.values.get(0).text.shellValue = "0";
12511
+ this.setSyncPipeStatusCell(activeExisting, "0");
12216
12512
  activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
12217
12513
  }
12218
12514
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -12298,7 +12594,7 @@ export class Runtime {
12298
12594
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
12299
12595
  }
12300
12596
  else {
12301
- elem0.text.shellValue = "0";
12597
+ this.setSyncPipeStatusCell(existing, "0");
12302
12598
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
12303
12599
  }
12304
12600
  const finalStatus = pipeline.negate ? 1 : 0;
@@ -12353,7 +12649,7 @@ export class Runtime {
12353
12649
  return false;
12354
12650
  continue;
12355
12651
  }
12356
- 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)) {
12357
12653
  return false;
12358
12654
  }
12359
12655
  if (part.indirect) {
@@ -12850,7 +13146,7 @@ export class Runtime {
12850
13146
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
12851
13147
  }
12852
13148
  else {
12853
- elem0.text.shellValue = "0";
13149
+ this.setSyncPipeStatusCell(existing, "0");
12854
13150
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
12855
13151
  }
12856
13152
  }
@@ -13213,7 +13509,7 @@ export class Runtime {
13213
13509
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
13214
13510
  }
13215
13511
  else {
13216
- elem0.text.shellValue = "0";
13512
+ this.setSyncPipeStatusCell(existing, "0");
13217
13513
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
13218
13514
  }
13219
13515
  }
@@ -13438,7 +13734,7 @@ export class Runtime {
13438
13734
  monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets);
13439
13735
  }
13440
13736
  else {
13441
- elem0.text.shellValue = "0";
13737
+ this.setSyncPipeStatusCell(existing, "0");
13442
13738
  store.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
13443
13739
  }
13444
13740
  }
@@ -16070,7 +16366,7 @@ export class Runtime {
16070
16366
  previous.set(assignment.name, saved);
16071
16367
  const existingArray = arrayStore(state)?.get(assignment.name);
16072
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")));
16073
- if (words.length && !canSkipTypedScalarOverlay && (guestArrays(state) || (assignment.name === "PIPESTATUS" && existingArray))) {
16369
+ if (words.length && !canSkipTypedScalarOverlay && (guestArrays(state) || existingArray)) {
16074
16370
  const store = requireArrays(state);
16075
16371
  await this.prepareVariable(state, assignment.name, saved, !existingArray);
16076
16372
  if (existingArray) {
@@ -16095,7 +16391,7 @@ export class Runtime {
16095
16391
  }
16096
16392
  if (inlineInput || (state.profile === "sh" || !words.length) && assignments.some(assignment => state.readonlyVariables?.has(assignment.name)))
16097
16393
  await assign();
16098
- if (inlineInput && (functionCommand || words[0] === "read" && !state.extensions?.builtins.has("read")) && previous?.size) {
16394
+ if (inlineInput && previous?.size) {
16099
16395
  if (stateMonitor(state)?.lazyPipeStatus !== undefined)
16100
16396
  stateMonitor(state).activate(true);
16101
16397
  const redirectState = tryCloneStateSync(state) ?? await cloneState(state, this.signal);
@@ -16103,10 +16399,26 @@ export class Runtime {
16103
16399
  const redirectAssignments = new Map();
16104
16400
  for (const [name, saved] of previous) {
16105
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);
16106
16406
  if (saved.value === undefined)
16107
16407
  delete variables[name];
16108
16408
  else
16109
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
+ }
16110
16422
  }
16111
16423
  redirectState.redirectAssignments = redirectAssignments;
16112
16424
  const savedIndex = previous.get("OPTIND");
@@ -16124,7 +16436,7 @@ export class Runtime {
16124
16436
  }
16125
16437
  const epoch = stateMonitor(state)?.epoch;
16126
16438
  try {
16127
- 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);
16128
16440
  if (parentStore && stateMonitor(state).epoch !== epoch)
16129
16441
  throw new ArrayFailure("stale state snapshot");
16130
16442
  }
@@ -16147,6 +16459,12 @@ export class Runtime {
16147
16459
  publishVariable(state, name, stateMonitor(redirectState).values.get(name, value));
16148
16460
  }
16149
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
+ }
16150
16468
  saved.value = variables[name];
16151
16469
  const value = stateMonitor(redirectState).values.get(name, saved.value ?? "");
16152
16470
  const held = saved.value === undefined ? undefined : stateMonitor(state).values.scope.hold(value);
@@ -20341,6 +20659,9 @@ export class Runtime {
20341
20659
  seen.add(name);
20342
20660
  names.push(name);
20343
20661
  };
20662
+ await consider("DIRSTACK", true);
20663
+ if (state.functionNames?.length)
20664
+ await consider("FUNCNAME", true);
20344
20665
  for (const name in state.variables)
20345
20666
  if (Object.hasOwn(state.variables, name))
20346
20667
  await consider(name, state.variables[name] !== undefined);
@@ -20421,7 +20742,7 @@ export class Runtime {
20421
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 }) }] } } });
20422
20743
  return resolved;
20423
20744
  }
20424
- 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);
20425
20746
  const index = await this.arrayIndex(binding, selector.index, state, io, store.owner, false, maximum);
20426
20747
  const resolved = { ...part };
20427
20748
  copyArraySelector(part, resolved);
@@ -20493,8 +20814,7 @@ export class Runtime {
20493
20814
  const selector = getArraySelector(part);
20494
20815
  if (selector?.kind === "members" || part.name === "@" || part.name === "*") {
20495
20816
  const members = selector ? await this.arrayMembers(part.name, state, io, part.keys) : this.positionalValues(state);
20496
- const ifs = state.variables.IFS ?? " ";
20497
- const separator = !split && (selector?.kind === "members" ? selector.separator === "@" || !part.quoted : part.name === "@") ? " " : ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
20817
+ const separator = !split && (selector?.kind === "members" ? selector.separator === "@" : part.name === "@") ? " " : this.ifsSeparator(state, io);
20498
20818
  const fragments = [];
20499
20819
  let bytes = 0;
20500
20820
  for (const member of members) {
@@ -21350,7 +21670,7 @@ export class Runtime {
21350
21670
  fastValueWord(word, state, io, split, pattern, hereDocument, braces, assignmentStart, overrideDiagnosticLine) {
21351
21671
  const monitor = stateMonitor(state);
21352
21672
  const rawState = monitor ? monitor.raw : state;
21353
- 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))
21354
21674
  return undefined;
21355
21675
  if (guestArrays(state) && word.parts.some(part => part.kind === "arithmetic" && !part.expression.error && !isSafeSmiProgram(part.expression)))
21356
21676
  return undefined;
@@ -21359,7 +21679,7 @@ export class Runtime {
21359
21679
  this.signal.throwIfAborted();
21360
21680
  let out = "";
21361
21681
  for (let i = 0; i < word.parts.length; i++) {
21362
- const part = word.parts[i];
21682
+ let part = word.parts[i];
21363
21683
  if (part.kind === "text") {
21364
21684
  if (part.byteValue || invokedValues.has(part))
21365
21685
  return undefined;
@@ -21377,8 +21697,17 @@ export class Runtime {
21377
21697
  out += part.value;
21378
21698
  }
21379
21699
  else if (part.kind === "variable") {
21380
- if (rawState.variableAttributes?.get(part.name)?.includes("n"))
21381
- 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
+ }
21382
21711
  if (split && !part.quoted && (rawVars.IFS !== undefined && rawVars.IFS !== " \t\n"))
21383
21712
  return undefined;
21384
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"))) {
@@ -21408,7 +21737,7 @@ export class Runtime {
21408
21737
  const refName = refRaw === undefined ? undefined : (this._syncArithRawWriteOnly && this._syncArithTouched?.has(part.name) ? refRaw : (monitor?.values.get(part.name, refRaw) ?? refRaw));
21409
21738
  if (typeof refName !== "string" || !isShellIdentifier(refName))
21410
21739
  return undefined;
21411
- 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") {
21412
21741
  return undefined;
21413
21742
  }
21414
21743
  const targetRaw = rawVars[refName];
@@ -21424,7 +21753,7 @@ export class Runtime {
21424
21753
  }
21425
21754
  if (part.indirect || part.prefixNames || part.specialParameter || part.transform)
21426
21755
  return undefined;
21427
- 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")
21428
21757
  return undefined;
21429
21758
  if (!isShellIdentifier(part.name))
21430
21759
  return undefined;
@@ -21506,10 +21835,11 @@ export class Runtime {
21506
21835
  const ifsVal = monitor?.values.get("IFS", rawVars.IFS ?? " ") ?? rawVars.IFS ?? " ";
21507
21836
  if (validElemOp && typeof ifsVal === "string" && (ifsVal.length === 0 || ifsVal.charCodeAt(0) < 128)) {
21508
21837
  const sep = selector.separator === "@" && (!isKeys || ifsVal.length === 0) ? " " : ifsVal.length > 0 ? ifsVal[0] : "";
21838
+ const parameterLine = part.line ?? overrideDiagnosticLine;
21509
21839
  const evalSliceInt = (w) => {
21510
21840
  if (w.parts.length === 0)
21511
21841
  return 0;
21512
- 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);
21513
21843
  if (typeof expanded !== "string")
21514
21844
  return undefined;
21515
21845
  const trimmed = expanded.trim();
@@ -21760,10 +22090,11 @@ export class Runtime {
21760
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)) {
21761
22091
  return undefined;
21762
22092
  }
22093
+ const parameterLine = part.line ?? overrideDiagnosticLine;
21763
22094
  const evalSubInt = (w) => {
21764
22095
  if (w.parts.length === 0)
21765
22096
  return 0;
21766
- 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);
21767
22098
  if (typeof expanded !== "string")
21768
22099
  return undefined;
21769
22100
  const trimmed = expanded.trim();
@@ -21837,7 +22168,8 @@ export class Runtime {
21837
22168
  if (part.operator !== undefined) {
21838
22169
  if (this.budget.limits.maxExpansionBytes !== Infinity || rawState.depth + (io.parameterDepth ?? 0) >= 60 || ValueScope.prototype.reserve !== defaultValueScopeReserve || String.prototype.codePointAt !== defaultStringCodePointAt || globalThis.Float64Array !== defaultFloat64Array)
21839
22170
  return undefined;
21840
- 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))))) {
21841
22173
  const patQuoted = part.alternate.parts[0].quoted;
21842
22174
  const pat = (part.alternate.parts.length === 1 && part.alternate.parts[0].kind === "text")
21843
22175
  ? part.alternate.parts[0].value
@@ -21911,7 +22243,7 @@ export class Runtime {
21911
22243
  out += replaced;
21912
22244
  continue;
21913
22245
  }
21914
- 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))))) {
21915
22247
  const patQuoted = part.alternate.parts[0].quoted;
21916
22248
  const pat = (part.alternate.parts.length === 1 && part.alternate.parts[0].kind === "text")
21917
22249
  ? part.alternate.parts[0].value
@@ -22182,7 +22514,7 @@ export class Runtime {
22182
22514
  return false;
22183
22515
  continue;
22184
22516
  }
22185
- 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)) {
22186
22518
  return false;
22187
22519
  }
22188
22520
  if (p.operator !== undefined && (p.operator === "=" || p.operator === ":=" || p.operator === "?" || p.operator === ":?"))
@@ -23288,7 +23620,9 @@ export class Runtime {
23288
23620
  : numericIndex(selector.index, 4294967295);
23289
23621
  if (index === undefined && !binding?.associative)
23290
23622
  throw new ArrayFailure("index outside 0..4294967295");
23291
- 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;
23292
23626
  }
23293
23627
  const missing = value === undefined || (part.operator.startsWith(":") && shellValueByteLength(value) === 0);
23294
23628
  if (part.operator.endsWith("+") ? !missing : missing) {
@@ -23639,8 +23973,9 @@ export class Runtime {
23639
23973
  return this.arithmeticValue(prepareArithmetic(source || "0", this.budget.parsing), state, io);
23640
23974
  };
23641
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);
23642
23977
  if (offset < 0n)
23643
- offset += BigInt((binding?.maximum ?? (state.variables[name] === undefined ? -1 : 0)) + 1);
23978
+ offset += BigInt(maximum + 1);
23644
23979
  if (substring.length) {
23645
23980
  count = await evaluate(substring.length);
23646
23981
  if (count < 0n)
@@ -23653,7 +23988,7 @@ export class Runtime {
23653
23988
  if (!binding) {
23654
23989
  if (name === "FUNCNAME" && state.variables.FUNCNAME === undefined) {
23655
23990
  const stack = state.functionNames ?? [];
23656
- const start = offset < 0n ? Math.max(0, stack.length + Number(offset)) : Number(offset);
23991
+ const start = Number(offset);
23657
23992
  const end = count === undefined ? stack.length : start + Number(count);
23658
23993
  const sliced = stack.slice(start, end);
23659
23994
  return keys ? sliced.map((_v, idx) => String(start + idx)) : sliced;