@poe-platform/safe-bash 0.1.744 → 0.1.745

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.
@@ -3771,7 +3771,7 @@ export class Runtime {
3771
3771
  throw new NounsetDiagnosticFailure(reason);
3772
3772
  }
3773
3773
  }
3774
- extractSimpleErePattern(pattern) {
3774
+ extractSimpleErePattern(pattern, state) {
3775
3775
  const cached = pattern._cachedSimpleEre;
3776
3776
  if (cached !== undefined)
3777
3777
  return cached ?? undefined;
@@ -3781,8 +3781,35 @@ export class Runtime {
3781
3781
  }
3782
3782
  if (pattern.parts.length === 1) {
3783
3783
  const p0 = pattern.parts[0];
3784
+ if (state !== undefined &&
3785
+ p0.kind === "variable" &&
3786
+ !p0.quoted &&
3787
+ !p0.operator &&
3788
+ !p0.substring &&
3789
+ !p0.length &&
3790
+ !p0.indirect &&
3791
+ !p0.prefixNames &&
3792
+ !p0.specialParameter &&
3793
+ !p0.transform &&
3794
+ getArraySelector(p0) === undefined &&
3795
+ isShellIdentifier(p0.name) &&
3796
+ !stateMonitor(state)?.store?.get(p0.name)) {
3797
+ const raw = state.variables[p0.name];
3798
+ const val = this._syncArithRawWriteOnly && this._syncArithTouched?.has(p0.name)
3799
+ ? raw
3800
+ : (raw === undefined ? undefined : (stateMonitor(state)?.values.get(p0.name, raw) ?? raw));
3801
+ if (typeof val !== "string" || val.length === 0 || val.includes("\\") || val.includes("~"))
3802
+ return undefined;
3803
+ const varCache = pattern._cachedVarEre;
3804
+ if (varCache && varCache.text === val)
3805
+ return varCache;
3806
+ const nextVarCache = { text: val, literal: false, isVar: p0.name };
3807
+ pattern._cachedVarEre = nextVarCache;
3808
+ return nextVarCache;
3809
+ }
3784
3810
  if (p0.kind !== "text" || p0.byteValue || p0.value.includes("\\") || p0.value.includes("~")) {
3785
- pattern._cachedSimpleEre = null;
3811
+ if (p0.kind !== "variable")
3812
+ pattern._cachedSimpleEre = null;
3786
3813
  return undefined;
3787
3814
  }
3788
3815
  const res = { text: p0.value, literal: !!p0.quoted };
@@ -3803,7 +3830,7 @@ export class Runtime {
3803
3830
  return res;
3804
3831
  }
3805
3832
  getFastAnchoredEreRegex(pattern, state) {
3806
- const pat = this.extractSimpleErePattern(pattern);
3833
+ const pat = this.extractSimpleErePattern(pattern, state);
3807
3834
  if (!pat || pat.literal || state.nocasematch || this.budget.limits.maxExpansionBytes !== Infinity || this.budget.limits.maxExpansionFields !== Infinity) {
3808
3835
  return undefined;
3809
3836
  }
@@ -3831,6 +3858,8 @@ export class Runtime {
3831
3858
  }
3832
3859
  }
3833
3860
  if (caretOk &&
3861
+ !pat.text.includes("|") &&
3862
+ !/[*+?][*+?]/.test(pat.text) &&
3834
3863
  /^[\^a-zA-Z0-9_.\-/:@()\[\]+?*,;=| $]+$/.test(pat.text) &&
3835
3864
  !pat.text.includes("[:") &&
3836
3865
  !pat.text.includes("[.") &&
@@ -3853,7 +3882,7 @@ export class Runtime {
3853
3882
  return patWithCache._fastAnchoredRe ?? undefined;
3854
3883
  }
3855
3884
  trySyncEre(subject, pattern, state, ignoreYield = false) {
3856
- const pat = this.extractSimpleErePattern(pattern);
3885
+ const pat = this.extractSimpleErePattern(pattern, state);
3857
3886
  if (!pat)
3858
3887
  return undefined;
3859
3888
  const monitor = stateMonitor(state);
@@ -5820,7 +5849,7 @@ export class Runtime {
5820
5849
  if (!entry.index || entry.append)
5821
5850
  return false;
5822
5851
  const kSrc = entry.index.source ?? entry.index.decimal;
5823
- if (kSrc.length === 0 || kSrc.length > 4096 || /[$\x60\\"']/.test(kSrc))
5852
+ if (kSrc.length === 0 || kSrc.length > 4096 || kSrc.startsWith("~") || /[$\x60\\"']/.test(kSrc))
5824
5853
  return false;
5825
5854
  let val = "";
5826
5855
  if (entry.value.parts.length > 0) {
@@ -5904,6 +5933,34 @@ export class Runtime {
5904
5933
  }
5905
5934
  continue;
5906
5935
  }
5936
+ if (entry.value.parts.length === 1 &&
5937
+ !entry.value.parts[0].quoted &&
5938
+ (entry.value.parts[0].kind === "variable" || entry.value.parts[0].kind === "substitution") &&
5939
+ (rawState.variables.IFS === undefined || rawState.variables.IFS === " \t\n") &&
5940
+ !rawState.noglob &&
5941
+ this.budget.limits.maxExpansionFields === Infinity &&
5942
+ this.budget.limits.maxExpansionBytes === Infinity) {
5943
+ let unsplit;
5944
+ try {
5945
+ unsplit = this.fastValueWord(entry.value, rawState, io, false, false, false, false, undefined, diagnosticLine);
5946
+ }
5947
+ catch {
5948
+ return false;
5949
+ }
5950
+ if (typeof unsplit !== "string" || hasGlobOrEscape(unsplit, !!rawState.extglob))
5951
+ return false;
5952
+ const cw = entry.value;
5953
+ const splitWords = (cw._cachedSplitSrc === unsplit && cw._cachedSplitWords !== undefined)
5954
+ ? cw._cachedSplitWords
5955
+ : (cw._cachedSplitSrc = unsplit, cw._cachedSplitWords = unsplit.split(/[ \t\n]+/).filter(w => w.length > 0));
5956
+ if (splitWords.length > 1500)
5957
+ return false;
5958
+ for (let swIdx = 0; swIdx < splitWords.length; swIdx++) {
5959
+ const sw = splitWords[swIdx];
5960
+ items.push({ explicitIdx: undefined, val: sw, bLen: shellValueByteLength(sw) });
5961
+ }
5962
+ continue;
5963
+ }
5907
5964
  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)))) {
5908
5965
  return false;
5909
5966
  }
@@ -5941,18 +5998,16 @@ export class Runtime {
5941
5998
  }
5942
5999
  try {
5943
6000
  if (!assignment.append && target.values.size > 0) {
5944
- if (!(target.values.size === keepIndices.size && target.maximum === resolved.length - 1)) {
5945
- for (const k of target.values.keys()) {
5946
- if (!keepIndices.has(k))
5947
- target.remove(k);
5948
- }
5949
- let newMax = -1;
5950
- for (const k of target.values.keys()) {
5951
- if (k > newMax)
5952
- newMax = k;
5953
- }
5954
- target.maximum = newMax;
6001
+ for (const k of target.values.keys()) {
6002
+ if (!keepIndices.has(k))
6003
+ target.remove(k);
5955
6004
  }
6005
+ let newMax = -1;
6006
+ for (const k of target.values.keys()) {
6007
+ if (k > newMax)
6008
+ newMax = k;
6009
+ }
6010
+ target.maximum = newMax;
5956
6011
  }
5957
6012
  for (let i = 0; i < resolved.length; i++) {
5958
6013
  const { idx, val, bLen } = resolved[i];
@@ -8804,7 +8859,7 @@ export class Runtime {
8804
8859
  return false;
8805
8860
  if (isAssoc) {
8806
8861
  const kSrc = e.index ? (e.index.source ?? e.index.decimal) : "";
8807
- return kSrc.length > 0 && !/[$\x60\\"']/.test(kSrc) && isNoBraceSyncAssignWord(e.value);
8862
+ return kSrc.length > 0 && !kSrc.startsWith("~") && !/[$\x60\\"']/.test(kSrc) && isNoBraceSyncAssignWord(e.value);
8808
8863
  }
8809
8864
  if (e.index !== undefined) {
8810
8865
  const kSrc = (e.index.source ?? e.index.decimal).trim();
@@ -9019,7 +9074,7 @@ export class Runtime {
9019
9074
  return false;
9020
9075
  if (wantAssoc) {
9021
9076
  const kSrc = e.index ? (e.index.source ?? e.index.decimal) : "";
9022
- return kSrc.length > 0 && !/[$\x60\\"']/.test(kSrc) && isNoBraceSyncAssignWord(e.value);
9077
+ return kSrc.length > 0 && !kSrc.startsWith("~") && !/[$\x60\\"']/.test(kSrc) && isNoBraceSyncAssignWord(e.value);
9023
9078
  }
9024
9079
  if (e.index !== undefined) {
9025
9080
  const kSrc = (e.index.source ?? e.index.decimal).trim();
@@ -9346,7 +9401,7 @@ export class Runtime {
9346
9401
  return (e.operator === "-n" || e.operator === "-z" || e.operator === "-v" || (syncConditionalFileUnaryOps.has(e.operator) && this._isMemoryBackingFs && this.canFastMemoryRedirect && this.budget.limits.maxFileSystemOperations >= 1000 && (this._fileWrites === undefined || this._fileWrites.size === 0) && (this._outputFiles === undefined || this._outputFiles.size === 0))) && this.isPureSyncValueWord(e.operand, rawState);
9347
9402
  if (e.kind === "binary") {
9348
9403
  if (e.operator === "=~") {
9349
- if (!this.isPureSyncValueWord(e.left, rawState) || !this.extractSimpleErePattern(e.right) || rawState.readonlyVariables?.has("BASH_REMATCH") || rawState.exported.has("BASH_REMATCH") || hasUnpreparedLocal(rawState, "BASH_REMATCH"))
9404
+ if (!this.isPureSyncValueWord(e.left, rawState) || !this.extractSimpleErePattern(e.right, rawState) || rawState.readonlyVariables?.has("BASH_REMATCH") || rawState.exported.has("BASH_REMATCH") || hasUnpreparedLocal(rawState, "BASH_REMATCH"))
9350
9405
  return false;
9351
9406
  const mon = stateMonitor(rawState);
9352
9407
  if (!mon || (this.budget.limits.maxExpansionBytes !== Infinity || this.budget.limits.maxExpansionFields !== Infinity) || mon.hasOverlay("BASH_REMATCH"))
@@ -9721,7 +9776,7 @@ export class Runtime {
9721
9776
  // Matching publishes BASH_REMATCH. A sibling may still require fallback,
9722
9777
  // so only a standalone match can be attempted without replaying writes.
9723
9778
  if (expr.operator === "=~")
9724
- return depth === 0 && this.extractSimpleErePattern(expr.right) !== undefined;
9779
+ return depth === 0 && this.extractSimpleErePattern(expr.right, rawState) !== undefined;
9725
9780
  if (expr.operator === "==" || expr.operator === "=" || expr.operator === "!=") {
9726
9781
  if (expr.right.parts.length > 0 && expr.right.parts.every(p => p.quoted))
9727
9782
  return true;
@@ -9845,7 +9900,7 @@ export class Runtime {
9845
9900
  if (expr.operator === "=~") {
9846
9901
  if (depth !== 0)
9847
9902
  return undefined;
9848
- if (!this.extractSimpleErePattern(expr.right))
9903
+ if (!this.extractSimpleErePattern(expr.right, rawState))
9849
9904
  return undefined;
9850
9905
  let subject;
9851
9906
  try {
@@ -11202,7 +11257,7 @@ export class Runtime {
11202
11257
  catch {
11203
11258
  scanArgs = undefined;
11204
11259
  }
11205
- if (typeof optstring === "string" && scanArgs !== undefined) {
11260
+ if (typeof optstring === "string" && (!optstring.startsWith("-") || optstring === "-") && scanArgs !== undefined) {
11206
11261
  rawState.getopts ??= cloneGetoptsBinding(rawState);
11207
11262
  const res = scanGetoptsSync(rawState.getopts.cursor, optstring, scanArgs);
11208
11263
  if (res !== undefined) {
@@ -12279,10 +12334,11 @@ export class Runtime {
12279
12334
  const ops = [];
12280
12335
  for (let ui = uStart; ui < command.words.length; ui++) {
12281
12336
  const wu = command.words[ui];
12337
+ const canSkipGlobUnset = Boolean(wu.parts[0]?.quoted || rawState.noglob || (!rawState.nullglob && this._isMemoryBackingFs && tryGetMemoryDirectoryEntryNamesSync(this.backingFs, rawState.cwd)?.size === 0));
12282
12338
  let tExpanded;
12283
12339
  try {
12284
- const fv = this.fastValueWord(wu, rawState, io, true, false, false, true, undefined, diagnosticLine);
12285
- if (typeof fv === "string")
12340
+ const fv = this.fastValueWord(wu, rawState, io, !canSkipGlobUnset, false, false, true, undefined, diagnosticLine);
12341
+ if (typeof fv === "string" && (!canSkipGlobUnset || (!fv.includes(" ") && !fv.includes("\t") && !fv.includes("\n"))))
12286
12342
  tExpanded = fv;
12287
12343
  }
12288
12344
  catch { }
@@ -13515,8 +13571,42 @@ export class Runtime {
13515
13571
  // as loop state changes. Decide before the loop produces any effects.
13516
13572
  if (this._syncLoopFnCheckDepth > 0 && (part.substring || getArraySelector(part)?.kind === "element"))
13517
13573
  return false;
13518
- if (part.prefixNames !== undefined || part.transform !== undefined)
13519
- return false;
13574
+ if (part.prefixNames !== undefined) {
13575
+ if (part.indirect ||
13576
+ part.specialParameter ||
13577
+ part.length ||
13578
+ part.substring ||
13579
+ part.transform !== undefined ||
13580
+ part.operator !== undefined ||
13581
+ getArraySelector(part) !== undefined ||
13582
+ this.budget.limits.maxExpansionFields !== Infinity ||
13583
+ this.budget.limits.maxExpansionBytes !== Infinity ||
13584
+ !isShellIdentifier(part.name)) {
13585
+ return false;
13586
+ }
13587
+ continue;
13588
+ }
13589
+ if (part.transform !== undefined) {
13590
+ if ((part.transform !== "U" && part.transform !== "L" && part.transform !== "u" && (part.transform !== "Q" || byteLocale(rawState.variables))) ||
13591
+ part.indirect ||
13592
+ part.specialParameter ||
13593
+ part.length ||
13594
+ part.substring ||
13595
+ part.operator !== undefined ||
13596
+ getArraySelector(part) !== undefined ||
13597
+ this.budget.limits.maxExpansionBytes !== Infinity ||
13598
+ !isShellIdentifier(part.name) ||
13599
+ stateMonitor(rawState)?.store?.get(part.name) ||
13600
+ part.name === "PIPESTATUS" ||
13601
+ part.name === "LINENO" ||
13602
+ part.name === "_" ||
13603
+ part.name === "FUNCNAME" ||
13604
+ part.name === "DIRSTACK" ||
13605
+ (rawState.nounset && rawState.variables[part.name] === undefined)) {
13606
+ return false;
13607
+ }
13608
+ continue;
13609
+ }
13520
13610
  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"))) {
13521
13611
  if (rawState.nounset && part.name >= "1" && part.name <= "9" && rawState.positional[part.name.charCodeAt(0) - 49] === undefined)
13522
13612
  return false;
@@ -13691,11 +13781,12 @@ export class Runtime {
13691
13781
  !cmd.redirects[0].move &&
13692
13782
  !cmd.redirects[0].document &&
13693
13783
  this.isPureSyncValueWord(cmd.redirects[0].target, rawState);
13694
- if (p.commands.length === 1 && (cmd.kind === "for" || cmd.kind === "arithmetic-for") && cmd.redirects.length === 0) {
13695
- if (cmd.body.lists.length === 1 && !cmd.body.lists[0].terminator && cmd.body.lists[0].pipelines.every(pl => pl.commands.length === 1 && pl.commands[0].kind === "simple" && pl.commands[0].redirects.length === 0 && (pl.commands[0].words[0]?.plain === "echo" ? this.isSyncEchoCallOk(pl.commands[0], rawState) : (pl.commands[0].words[0]?.plain === "printf" && pl.commands[0].words[1]?.plain !== "-v" && this.isSyncPrintfCallOk(pl.commands[0], 1, rawState, cmd.kind === "for" ? cmd.name : undefined))))) {
13696
- continue;
13697
- }
13784
+ // Loop substitutions require a real subshell: header admission can
13785
+ // decline at runtime, and induction writes also change export attributes.
13786
+ if (cmd.kind === "for" || cmd.kind === "arithmetic-for" || cmd.kind === "while")
13698
13787
  return false;
13788
+ if (p.commands.length === 1 && (cmd.kind === "if" || cmd.kind === "case") && this.isPureSyncSubIfOrCase(cmd, rawState)) {
13789
+ continue;
13699
13790
  }
13700
13791
  if (cmd.kind !== "simple" || (cmd.redirects.length > 0 && !hasSingleStdinRedir && !hasSingleHereStringRedir) || cmd.words.length === 0)
13701
13792
  return false;
@@ -13748,14 +13839,15 @@ export class Runtime {
13748
13839
  w0Plain === "cut" ||
13749
13840
  w0Plain === "wc" ||
13750
13841
  w0Plain === "sort" ||
13842
+ w0Plain === "uniq" ||
13751
13843
  w0Plain === "tr" ||
13752
13844
  (hasSingleHereStringRedir && (w0Plain === "base64" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl"));
13753
- if (!w0Plain || (w0Plain !== "printf" && w0Plain !== "echo" && w0Plain !== "dirname" && w0Plain !== "basename" && w0Plain !== "seq" && !isFileCommand) || rawState.functions.has(w0Plain) || rawState.extensions?.builtins.has(w0Plain))
13845
+ const def = w0Plain ? (this.commands.get(w0Plain) ?? ((w0Plain === "rev" || w0Plain === "tac") ? this.getExternalCommand(w0Plain) : undefined)) : undefined;
13846
+ if (!w0Plain || (w0Plain !== "printf" && w0Plain !== "echo" && w0Plain !== "dirname" && w0Plain !== "basename" && w0Plain !== "seq" && !isFileCommand) || !def || rawState.functions.has(w0Plain) || rawState.extensions?.builtins.has(w0Plain))
13754
13847
  return false;
13755
13848
  if ((hasSingleStdinRedir || hasSingleHereStringRedir) && !isFileCommand)
13756
13849
  return false;
13757
- const def = this.commands.get(w0Plain);
13758
- if (!def || (w0Plain === "printf" ? def.execute !== printfCommand.execute : w0Plain === "echo" ? !defaultEchoExecutors.has(def.execute) : w0Plain === "seq" ? (!builtInDirectContextExecutors.has(def.execute) && (customRegisteredCommands.has(def.execute) || customRegisteredRegistries.has(this.commands))) : !builtInDirectContextExecutors.has(def.execute)))
13850
+ if (w0Plain === "printf" ? def.execute !== printfCommand.execute : w0Plain === "echo" ? !defaultEchoExecutors.has(def.execute) : (w0Plain === "seq" || w0Plain === "rev" || w0Plain === "tac") ? (!builtInDirectContextExecutors.has(def.execute) && (customRegisteredCommands.has(def.execute) || customRegisteredRegistries.has(this.commands))) : !builtInDirectContextExecutors.has(def.execute))
13759
13851
  return false;
13760
13852
  if (!cmd.words.slice(1).every(w => this.isPureSyncValueWord(w, rawState)))
13761
13853
  return false;
@@ -13772,7 +13864,10 @@ export class Runtime {
13772
13864
  if (!vCheck || vCheck.byteLength > 16384 || vCheck.includes(0))
13773
13865
  return false;
13774
13866
  }
13775
- const opCount = (hasSingleStdinRedir || hasSingleHereStringRedir) ? cmd.words.length - 1 : cmd.words.length - 2;
13867
+ const opWords = (hasSingleStdinRedir || hasSingleHereStringRedir) ? cmd.words.slice(1) : cmd.words.slice(1, -1);
13868
+ const opCount = opWords.length;
13869
+ const op0 = opWords[0]?.plain;
13870
+ const op1 = opWords[1]?.plain;
13776
13871
  if (w0Plain === "cat" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl") {
13777
13872
  if (opCount !== 0)
13778
13873
  return false;
@@ -13780,18 +13875,61 @@ export class Runtime {
13780
13875
  else {
13781
13876
  if (p.commands.length !== 1 && !hasSingleHereStringRedir)
13782
13877
  return false;
13783
- if ((w0Plain === "head" || w0Plain === "tail") && opCount > 2)
13784
- return false;
13785
- if ((w0Plain === "jq" || w0Plain === "awk" || w0Plain === "grep" || w0Plain === "sed") && (opCount < 1 || opCount > 3))
13786
- return false;
13787
- if (w0Plain === "cut" && (opCount < 1 || opCount > 4))
13788
- return false;
13789
- if (w0Plain === "wc" && opCount !== 1)
13790
- return false;
13791
- if ((w0Plain === "sort" || w0Plain === "base64") && (opCount > 1 || (w0Plain === "sort" && byteLocale(rawState.variables))))
13792
- return false;
13793
- if (w0Plain === "tr" && opCount !== 2)
13794
- return false;
13878
+ if (w0Plain === "head" || w0Plain === "tail") {
13879
+ const validHT = opCount === 0 ||
13880
+ (opCount === 1 && op0 !== undefined && /^-(?:n|c)?[0-9]{1,5}$/.test(op0)) ||
13881
+ (opCount === 2 && (op0 === "-n" || op0 === "-c") && op1 !== undefined && /^[0-9]{1,5}$/.test(op1));
13882
+ if (!validHT)
13883
+ return false;
13884
+ }
13885
+ const plainOps = [];
13886
+ for (let oi = 0; oi < opCount; oi++) {
13887
+ const ow = opWords[oi];
13888
+ const pw = ow.plain ?? (ow.parts.length > 0 && ow.parts.every(pt => pt.kind === "text") ? ow.parts.map(pt => pt.value).join("") : undefined);
13889
+ if (pw === undefined)
13890
+ break;
13891
+ plainOps.push(pw);
13892
+ }
13893
+ if (w0Plain === "cut") {
13894
+ if (plainOps.length !== opCount || this.evalSyncCut([], plainOps, byteLocale(rawState.variables)) === undefined)
13895
+ return false;
13896
+ }
13897
+ else if (w0Plain === "sed") {
13898
+ if (plainOps.length !== opCount || this.evalSyncSed([], plainOps) === undefined)
13899
+ return false;
13900
+ }
13901
+ else if (w0Plain === "grep") {
13902
+ if (plainOps.length !== opCount || this.evalSyncGrep([], plainOps, Boolean(rawState.errexit)) === undefined)
13903
+ return false;
13904
+ }
13905
+ else if (w0Plain === "awk") {
13906
+ if (plainOps.length !== opCount || this.evalSyncAwk([], plainOps) === undefined)
13907
+ return false;
13908
+ }
13909
+ else if (w0Plain === "jq") {
13910
+ if (plainOps.length !== opCount || this.evalSyncJq("null", plainOps) === undefined)
13911
+ return false;
13912
+ }
13913
+ else if (w0Plain === "wc") {
13914
+ if (opCount !== 1 || (op0 !== "-l" && op0 !== "-c" && op0 !== "-w"))
13915
+ return false;
13916
+ }
13917
+ else if (w0Plain === "sort") {
13918
+ if (byteLocale(rawState.variables) || opCount > 1 || (opCount === 1 && op0 !== "-r" && op0 !== "-u" && op0 !== "-n" && op0 !== "-rn" && op0 !== "-nr" && op0 !== "-ru" && op0 !== "-ur"))
13919
+ return false;
13920
+ }
13921
+ else if (w0Plain === "uniq") {
13922
+ if (opCount > 1 || (opCount === 1 && op0 !== "-d" && op0 !== "-u" && op0 !== "-c"))
13923
+ return false;
13924
+ }
13925
+ else if (w0Plain === "base64") {
13926
+ if (opCount > 1 || (opCount === 1 && op0 !== "-d" && op0 !== "--decode"))
13927
+ return false;
13928
+ }
13929
+ else if (w0Plain === "tr") {
13930
+ if (plainOps.length !== opCount || this.evalSyncTr("", plainOps) === undefined)
13931
+ return false;
13932
+ }
13795
13933
  }
13796
13934
  }
13797
13935
  if (w0Plain === "printf") {
@@ -13833,6 +13971,8 @@ export class Runtime {
13833
13971
  if (p.commands.length >= 2) {
13834
13972
  if ((w0Plain !== "printf" && w0Plain !== "echo" && w0Plain !== "seq" && w0Plain !== "cat" && !hasSingleHereStringRedir) || syncPurePipelineSlotInUse || !this.budget.canSyncPurePipe || rawState.errexit || rawState.nounset)
13835
13973
  return false;
13974
+ if (w0Plain === "printf" && !this.isSyncPrintfCallOk(cmd, 1, rawState))
13975
+ return false;
13836
13976
  for (let sIdx = 1; sIdx < p.commands.length; sIdx++) {
13837
13977
  const sCmd = p.commands[sIdx];
13838
13978
  if (sCmd.kind !== "simple" || sCmd.redirects.length !== 0 || sCmd.words.length === 0)
@@ -13899,9 +14039,9 @@ export class Runtime {
13899
14039
  return false;
13900
14040
  }
13901
14041
  if (sName === "jq") {
13902
- const jqFilter = sPlainArgs.length === 1 ? sPlainArgs[0] : (sPlainArgs.length === 2 && (sPlainArgs[0] === "-r" || sPlainArgs[0] === "--raw-output") ? sPlainArgs[1] : undefined);
13903
- if (!jqFilter || !/^\.[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/.test(jqFilter))
13904
- return false;
14042
+ // JSON types are runtime inputs: the speculative evaluator may
14043
+ // refuse array indexing and must leave diagnostics to jq.
14044
+ return false;
13905
14045
  }
13906
14046
  if (sName === "base64") {
13907
14047
  if (sPlainArgs.length > 1 || (sPlainArgs.length === 1 && sPlainArgs[0] !== "-d" && sPlainArgs[0] !== "--decode"))
@@ -13978,6 +14118,53 @@ export class Runtime {
13978
14118
  }
13979
14119
  return undefined;
13980
14120
  }
14121
+ execSyncUnsetStep(step, rawState, io, monitor, touched) {
14122
+ const uVars = step.unsetVars;
14123
+ const store = monitor.store;
14124
+ let lastExpanded = "";
14125
+ for (let ui = 0; ui < uVars.length; ui++) {
14126
+ const uvn = uVars[ui];
14127
+ if (uvn.charCodeAt(0) === 64) {
14128
+ const wIdx = Number(uvn.slice(1));
14129
+ const wu = step.cmd.words[wIdx];
14130
+ const tExpanded = this.fastValueWord(wu, rawState, io, false, false, false, false, 0, step.line);
14131
+ lastExpanded = tExpanded;
14132
+ const bracket = tExpanded.indexOf("[");
14133
+ if (bracket > 0 && tExpanded.endsWith("]") && store) {
14134
+ const arrName = tExpanded.slice(0, bracket);
14135
+ const subStr = tExpanded.slice(bracket + 1, -1);
14136
+ const binding = store.get(arrName);
14137
+ if (binding) {
14138
+ if (binding.associative) {
14139
+ const id = fastStringHexIdentity(subStr);
14140
+ const existingKey = binding.keys.get(id);
14141
+ if (existingKey !== undefined) {
14142
+ binding.remove(existingKey.index);
14143
+ store.changed(monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets), arrName);
14144
+ }
14145
+ }
14146
+ else {
14147
+ const idxNum = /^(?:0|[1-9][0-9]{0,8})$/.test(subStr)
14148
+ ? Number(subStr)
14149
+ : resolveSimpleArithOperand(subStr, rawState, monitor, store, this._syncArithRawWriteOnly ? this._syncArithTouched : undefined);
14150
+ const resolvedIdx = idxNum !== undefined ? (idxNum >= 0 ? idxNum : binding.maximum + 1 + idxNum) : undefined;
14151
+ if (resolvedIdx !== undefined && resolvedIdx >= 0 && binding.values.has(resolvedIdx)) {
14152
+ binding.remove(resolvedIdx);
14153
+ store.changed(monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets), arrName);
14154
+ }
14155
+ }
14156
+ }
14157
+ }
14158
+ }
14159
+ else {
14160
+ delete rawState.variables[uvn];
14161
+ monitor.proxy.exported.delete(uvn);
14162
+ touched.add(uvn);
14163
+ lastExpanded = uvn;
14164
+ }
14165
+ }
14166
+ return lastExpanded;
14167
+ }
13981
14168
  extractSyncUnsetVars(cmd, rawState) {
13982
14169
  if (this._syncLoopFnCheckDepth > 0 || cmd.redirects.length !== 0 || cmd.words.length < 2 || cmd.words.length > 16 || cmd.words[0].plain !== "unset")
13983
14170
  return undefined;
@@ -13998,6 +14185,26 @@ export class Runtime {
13998
14185
  if (vn && startIdx === 1 && hasShellFunction(rawState, vn))
13999
14186
  return undefined;
14000
14187
  if (!vn || vn.startsWith("-") || !isShellIdentifier(vn) || controlNames.has(vn) || vn === "OPTIND" || rawState.readonlyVariables?.has(vn) || rawState.variableAttributes?.get(vn) || store?.get(vn) || stateMonitor(rawState)?.hasOverlay(vn) || rawState.locals.some(f => f.has(vn))) {
14188
+ const wu = cmd.words[i];
14189
+ const p0 = wu.parts[0];
14190
+ const pLast = wu.parts[wu.parts.length - 1];
14191
+ const m = p0?.kind === "text" ? /^([a-zA-Z_][a-zA-Z0-9_]*)\[/.exec(p0.value) : undefined;
14192
+ const arrName = m?.[1];
14193
+ const b = arrName ? store?.get(arrName) : undefined;
14194
+ if (arrName &&
14195
+ b &&
14196
+ b.references === 1 &&
14197
+ !store.watches.has(arrName) &&
14198
+ !rawState.readonlyVariables?.has(arrName) &&
14199
+ !hasUnpreparedLocal(rawState, arrName) &&
14200
+ !stateMonitor(rawState)?.hasOverlay(arrName) &&
14201
+ pLast?.kind === "text" &&
14202
+ pLast.value.endsWith("]") &&
14203
+ this.isPureSyncValueWord(wu, rawState) &&
14204
+ (p0.quoted || rawState.noglob || (!rawState.nullglob && this._isMemoryBackingFs && tryGetMemoryDirectoryEntryNamesSync(this.backingFs, rawState.cwd)?.size === 0))) {
14205
+ vars.push(`@${i}`);
14206
+ continue;
14207
+ }
14001
14208
  return undefined;
14002
14209
  }
14003
14210
  vars.push(vn);
@@ -14026,14 +14233,11 @@ export class Runtime {
14026
14233
  return undefined;
14027
14234
  if (w0Plain === "local" && rawState.locals.length === 0)
14028
14235
  return undefined;
14029
- let startIdx = 1;
14030
- let attr;
14236
+ const startIdx = 1;
14031
14237
  const w1Plain = cmd.words[1]?.plain;
14032
14238
  if (w0Plain !== "export" && (w1Plain === "-u" || w1Plain === "-l" || w1Plain === "-i")) {
14033
- if (cmd.words.length < 3)
14034
- return undefined;
14035
- attr = w1Plain.slice(1);
14036
- startIdx = 2;
14239
+ // Attribute changes invalidate the assumptions used to plan later loop steps.
14240
+ return undefined;
14037
14241
  }
14038
14242
  const store = stateMonitor(rawState)?.store;
14039
14243
  const items = [];
@@ -14069,7 +14273,7 @@ export class Runtime {
14069
14273
  return undefined;
14070
14274
  }
14071
14275
  }
14072
- return { kind: w0Plain === "export" ? "export" : w0Plain === "local" ? "local" : "declare", attr, items };
14276
+ return { kind: w0Plain === "export" ? "export" : w0Plain === "local" ? "local" : "declare", items };
14073
14277
  }
14074
14278
  isQuotedAtWord(w) {
14075
14279
  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);
@@ -14082,22 +14286,14 @@ export class Runtime {
14082
14286
  return false;
14083
14287
  if (words.length === 1 && this.isQuotedAtWord(words[0]))
14084
14288
  return true;
14085
- const store = stateMonitor(rawState)?.store;
14289
+ // Unquoted expansions need splitting and globbing against the state at
14290
+ // execution time. The loop body can change IFS or the expansion value.
14086
14291
  if (words.length === 1) {
14087
14292
  const w0 = words[0];
14088
14293
  if (rawState.braceexpand !== false && tryFastExpandBraceRange(w0, this.budget, undefined) !== undefined)
14089
14294
  return true;
14090
14295
  if (io !== undefined && this.tryExpandSyncArrayMembersWord(w0, rawState, io, line) !== undefined)
14091
14296
  return true;
14092
- const p0 = w0.parts.length === 1 ? w0.parts[0] : (w0.parts.length === 2 && w0.parts[0].kind === "text" && w0.parts[0].value === "" ? w0.parts[1] : undefined);
14093
- if (p0 && (rawState.variables.IFS === undefined || rawState.variables.IFS === " \t\n") && !rawState.noglob) {
14094
- if (p0.kind === "variable" && !p0.quoted && !p0.indirect && !p0.prefixNames && !p0.length && !p0.substring && !p0.transform && p0.operator === undefined && getArraySelector(p0) === undefined && isShellIdentifier(p0.name) && !store?.get(p0.name) && !stateMonitor(rawState)?.hasOverlay(p0.name)) {
14095
- return true;
14096
- }
14097
- if (p0.kind === "substitution" && !p0.quoted && this.isPureSyncValueWord(w0, rawState)) {
14098
- return true;
14099
- }
14100
- }
14101
14297
  }
14102
14298
  const stdIfs = (rawState.variables.IFS ?? " \t\n") === " \t\n";
14103
14299
  for (let i = 0; i < words.length; i++) {
@@ -14134,9 +14330,10 @@ export class Runtime {
14134
14330
  return arrMembers;
14135
14331
  const p0 = w0.parts.length === 1 ? w0.parts[0] : (w0.parts.length === 2 && w0.parts[0].kind === "text" && w0.parts[0].value === "" ? w0.parts[1] : undefined);
14136
14332
  if (p0 && !p0.quoted && (rawState.variables.IFS === undefined || rawState.variables.IFS === " \t\n") && !rawState.noglob) {
14137
- if (p0.kind === "variable" && !p0.indirect && !p0.prefixNames && !p0.length && !p0.substring && !p0.transform && p0.operator === undefined && getArraySelector(p0) === undefined) {
14138
- const curRaw = rawState.variables[p0.name];
14139
- const rawVal = curRaw === undefined ? "" : (touched.has(p0.name) ? curRaw : (monitor.values.get(p0.name, curRaw) ?? curRaw));
14333
+ if (p0.kind === "variable" && !p0.indirect && !p0.prefixNames && !p0.length && !p0.transform && getArraySelector(p0) === undefined) {
14334
+ const rawVal = (p0.operator === undefined && !p0.substring)
14335
+ ? (rawState.variables[p0.name] === undefined ? "" : (touched.has(p0.name) ? rawState.variables[p0.name] : (monitor.values.get(p0.name, rawState.variables[p0.name]) ?? rawState.variables[p0.name])))
14336
+ : this.fastValueWord(w0, rawState, io, false, false, false, false, 0, line);
14140
14337
  if (typeof rawVal === "string") {
14141
14338
  const cw = w0;
14142
14339
  if (cw._cachedSplitSrc === rawVal && cw._cachedSplitWords !== undefined)
@@ -14250,6 +14447,8 @@ export class Runtime {
14250
14447
  let rawMode = false;
14251
14448
  let arrayTarget;
14252
14449
  let delimChar;
14450
+ let maxChars;
14451
+ let exactChars;
14253
14452
  if (isMapfile) {
14254
14453
  rawMode = true;
14255
14454
  arrayTarget = cmd.words[2]?.plain ?? "MAPFILE";
@@ -14280,6 +14479,25 @@ export class Runtime {
14280
14479
  delimChar = dPlain;
14281
14480
  argIdx += 2;
14282
14481
  }
14482
+ else if (p === "-n" || p === "-rn" || p === "-nr" || p === "-N" || p === "-rN" || p === "-Nr") {
14483
+ if (p.includes("r"))
14484
+ rawMode = true;
14485
+ exactChars = p.includes("N");
14486
+ const nPlain = cmd.words[argIdx + 1]?.plain;
14487
+ if (!nPlain || !/^[1-9][0-9]{0,5}$/.test(nPlain) || byteLocale(rawState.variables))
14488
+ return undefined;
14489
+ maxChars = Number(nPlain);
14490
+ argIdx += 2;
14491
+ }
14492
+ else if ((p.startsWith("-n") || p.startsWith("-rn") || p.startsWith("-N") || p.startsWith("-rN")) && /^[1-9][0-9]{0,5}$/.test(p.slice(p.startsWith("-r") ? 3 : 2))) {
14493
+ if (byteLocale(rawState.variables))
14494
+ return undefined;
14495
+ if (p.startsWith("-r"))
14496
+ rawMode = true;
14497
+ exactChars = p.includes("N");
14498
+ maxChars = Number(p.slice(p.startsWith("-r") ? 3 : 2));
14499
+ argIdx++;
14500
+ }
14283
14501
  else if (p === "-ra" || p === "-ar" || p === "-a") {
14284
14502
  if (p.includes("r"))
14285
14503
  rawMode = true;
@@ -14297,7 +14515,7 @@ export class Runtime {
14297
14515
  return undefined;
14298
14516
  }
14299
14517
  }
14300
- if (!rawMode || (delimChar !== undefined && arrayTarget !== undefined))
14518
+ if (!rawMode || ((delimChar !== undefined || maxChars !== undefined) && arrayTarget !== undefined))
14301
14519
  return undefined;
14302
14520
  }
14303
14521
  const monitor = stateMonitor(rawState);
@@ -14317,36 +14535,9 @@ export class Runtime {
14317
14535
  return undefined;
14318
14536
  }
14319
14537
  const arrStore = store ?? requireArrays(rawState);
14320
- let existingArr = arrStore.get(arrayTarget);
14321
- if (existingArr && (existingArr.associative || existingArr.references !== 1 || arrStore.watches.has(arrayTarget)))
14322
- return undefined;
14323
- if (!existingArr && !arrStore.watches.has(arrayTarget)) {
14324
- const recTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
14325
- const recBinding = arrStore.takeRecycled(arrayTarget, false, recTickets);
14326
- if (recBinding) {
14327
- if (monitor)
14328
- monitor.epoch = recTickets.epoch;
14329
- existingArr = recBinding;
14330
- }
14331
- else {
14332
- try {
14333
- const created = IndexedBinding.create(arrStore.owner, false);
14334
- const prepared = arrStore.prepareExistingName(arrayTarget, shellValueByteLength(arrayTarget), arrStore.owner, this.signal);
14335
- if (prepared) {
14336
- const initTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
14337
- arrStore.publish(arrayTarget, created, initTickets, prepared, false, arrStore.owner);
14338
- if (monitor)
14339
- monitor.epoch = initTickets.epoch;
14340
- existingArr = created;
14341
- }
14342
- else {
14343
- void created.release();
14344
- }
14345
- }
14346
- catch { }
14347
- }
14348
- }
14349
- if (!existingArr)
14538
+ // Planning must not publish bindings: the read may never execute.
14539
+ const existingArr = arrStore.get(arrayTarget);
14540
+ if (!existingArr || existingArr.associative || existingArr.references !== 1 || arrStore.watches.has(arrayTarget))
14350
14541
  return undefined;
14351
14542
  return { ifsWord, varNames: [], arrayTarget, ...(isMapfile ? { isMapfile: true } : {}), isBareReply: false, inputWord: r0.target };
14352
14543
  }
@@ -14364,7 +14555,67 @@ export class Runtime {
14364
14555
  return undefined;
14365
14556
  varNames.push("REPLY");
14366
14557
  }
14367
- return { ifsWord, varNames, isBareReply, inputWord: r0.target, ...(delimChar !== undefined ? { delimChar } : {}) };
14558
+ return { ifsWord, varNames, isBareReply, inputWord: r0.target, ...(delimChar !== undefined ? { delimChar } : {}), ...(maxChars !== undefined ? { maxChars } : {}), ...(exactChars !== undefined ? { exactChars } : {}) };
14559
+ }
14560
+ isPureSyncSubIfOrCase(cmd, rawState) {
14561
+ if (cmd.redirects.length !== 0)
14562
+ return false;
14563
+ const isSimpleEchoOrPrintf = (s) => {
14564
+ if (s.lists.length !== 1 || s.lists[0].terminator || s.lists[0].pipelines.length !== 1)
14565
+ return false;
14566
+ const pl = s.lists[0].pipelines[0];
14567
+ if (pl.negate || pl.commands.length !== 1)
14568
+ return false;
14569
+ const sc = pl.commands[0];
14570
+ if (sc.kind !== "simple" || sc.redirects.length !== 0 || sc.words.length === 0)
14571
+ return false;
14572
+ const w0 = sc.words[0]?.plain;
14573
+ if (w0 === "echo")
14574
+ return this.isSyncEchoCallOk(sc, rawState);
14575
+ if (w0 === "printf" && sc.words[1]?.plain !== "-v")
14576
+ return this.isSyncPrintfCallOk(sc, 1, rawState);
14577
+ return false;
14578
+ };
14579
+ if (cmd.kind === "if") {
14580
+ for (const br of cmd.branches) {
14581
+ if (br.condition.lists.length !== 1 || br.condition.lists[0].terminator || br.condition.lists[0].pipelines.length !== 1)
14582
+ return false;
14583
+ const bp = br.condition.lists[0].pipelines[0];
14584
+ if (bp.negate || bp.commands.length !== 1)
14585
+ return false;
14586
+ const bc = bp.commands[0];
14587
+ if (bc.kind === "arithmetic") {
14588
+ if (bc.redirects.length !== 0 || bc.expression.error || bc.expression.hasMutation || bc.expression.hasSubscript || !isSafeSmiProgram(bc.expression) || !this.canSyncArithmeticWithoutFault(bc.expression.tree, rawState, bc.line ?? 1))
14589
+ return false;
14590
+ }
14591
+ else if (bc.kind === "conditional") {
14592
+ if (bc.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(bc.expression, 0, rawState))
14593
+ return false;
14594
+ if (bc.expression.kind === "binary" && bc.expression.operator === "=~")
14595
+ return false;
14596
+ }
14597
+ else if (!this.extractPosixBracketCondExpr(bc, rawState, true, br.body)) {
14598
+ return false;
14599
+ }
14600
+ if (!isSimpleEchoOrPrintf(br.body))
14601
+ return false;
14602
+ }
14603
+ if (cmd.otherwise && !isSimpleEchoOrPrintf(cmd.otherwise))
14604
+ return false;
14605
+ return true;
14606
+ }
14607
+ if (rawState.nocasematch || rawState.extglob || !this.isPureSyncValueWord(cmd.subject, rawState))
14608
+ return false;
14609
+ const work = { remaining: 4096, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
14610
+ for (const cl of cmd.clauses) {
14611
+ if (cl.terminator !== ";;" && cl.terminator !== "esac")
14612
+ return false;
14613
+ if (!cl.patterns.every(pw => pw.plain !== undefined && tryMatchesPatternSync(pw.plain, "", work, false, false) !== undefined))
14614
+ return false;
14615
+ if (!isSimpleEchoOrPrintf(cl.body))
14616
+ return false;
14617
+ }
14618
+ return true;
14368
14619
  }
14369
14620
  canSyncLoopBody(script, rawState, io, nested = false, printfInductionName) {
14370
14621
  if (script.lists.length === 0)
@@ -14388,8 +14639,13 @@ export class Runtime {
14388
14639
  if (e.kind === "unary")
14389
14640
  return (e.operator === "-n" || e.operator === "-z" || e.operator === "-v" || (syncConditionalFileUnaryOps.has(e.operator) && this._isMemoryBackingFs && this.canFastMemoryRedirect && this.budget.limits.maxFileSystemOperations >= 1000 && (this._fileWrites === undefined || this._fileWrites.size === 0) && (this._outputFiles === undefined || this._outputFiles.size === 0))) && this.isPureSyncValueWord(e.operand, rawState);
14390
14641
  if (e.kind === "binary") {
14391
- if (e.operator === "=~")
14642
+ if (e.operator === "=~") {
14643
+ const patInfo = this.extractSimpleErePattern(e.right, rawState);
14644
+ // A variable pattern can change through expansions or called functions.
14645
+ if (patInfo?.isVar)
14646
+ return false;
14392
14647
  return this.isPureSyncValueWord(e.left, rawState) && this.getFastAnchoredEreRegex(e.right, rawState) !== undefined && !rawState.readonlyVariables?.has("BASH_REMATCH") && !rawState.exported.has("BASH_REMATCH") && !stateMonitor(rawState)?.hasOverlay("BASH_REMATCH") && !(store ?? requireArrays(rawState))?.watches.has("BASH_REMATCH");
14648
+ }
14393
14649
  return this.isPureSyncValueWord(e.left, rawState) && (e.operator === "==" || e.operator === "=" || e.operator === "!=" ? this.canSyncPatternWordParts(e.right, rawState) : this.isPureSyncValueWord(e.right, rawState));
14394
14650
  }
14395
14651
  if (e.kind === "not")
@@ -14642,7 +14898,8 @@ export class Runtime {
14642
14898
  }
14643
14899
  return false;
14644
14900
  }
14645
- if (this.extractSyncUnsetVars(cmd, rawState) !== undefined || this.extractSyncLocalDeclStep(cmd, rawState) !== undefined || this.extractSyncShiftCount(cmd, rawState) !== undefined) {
14901
+ const declaration = this.extractSyncLocalDeclStep(cmd, rawState);
14902
+ if (this.extractSyncUnsetVars(cmd, rawState) !== undefined || declaration !== undefined || this.extractSyncShiftCount(cmd, rawState) !== undefined) {
14646
14903
  continue;
14647
14904
  }
14648
14905
  if (cmd.redirects.length === 0 && cmd.words.length >= 1 && cmd.words.length <= 16 && this.extractSyncLoopFnSpec(cmd, rawState, io) !== undefined) {
@@ -14668,56 +14925,39 @@ export class Runtime {
14668
14925
  return false;
14669
14926
  if (hasUnpreparedLocals(rawState))
14670
14927
  return false;
14671
- let curArr = store?.get(arrAssign.name);
14672
- if (!curArr &&
14673
- (arrAssign.kind === "compound" || arrAssign.kind === "element") &&
14674
- this.budget.limits.maxExpansionBytes === Infinity &&
14675
- this.budget.limits.maxExpansionFields === Infinity &&
14676
- isShellIdentifier(arrAssign.name) &&
14677
- rawState.variables[arrAssign.name] === undefined &&
14678
- !rawState.readonlyVariables?.has(arrAssign.name) &&
14679
- !rawState.exported.has(arrAssign.name) &&
14680
- !rawState.variableAttributes?.get(arrAssign.name) &&
14681
- !controlNames.has(arrAssign.name) &&
14682
- !stateMonitor(rawState)?.hasOverlay(arrAssign.name)) {
14683
- const monitor = stateMonitor(rawState);
14684
- const arrStore = monitor?.store ?? (monitor ? requireArrays(rawState) : undefined);
14685
- if (arrStore && !arrStore.watches.has(arrAssign.name)) {
14686
- const recTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
14687
- const recBinding = arrStore.takeRecycled(arrAssign.name, false, recTickets);
14688
- if (recBinding) {
14689
- if (monitor)
14690
- monitor.epoch = recTickets.epoch;
14691
- curArr = recBinding;
14692
- }
14693
- else {
14694
- try {
14695
- const created = IndexedBinding.create(arrStore.owner, false);
14696
- const prepared = arrStore.prepareExistingName(arrAssign.name, shellValueByteLength(arrAssign.name), arrStore.owner, this.signal);
14697
- if (prepared) {
14698
- const initTickets = arrStore.owner.charge({ generation: true, version: true, epoch: true, work: 8 });
14699
- arrStore.publish(arrAssign.name, created, initTickets, prepared, false, arrStore.owner);
14700
- if (monitor)
14701
- monitor.epoch = initTickets.epoch;
14702
- curArr = created;
14703
- }
14704
- else {
14705
- void created.release();
14706
- }
14707
- }
14708
- catch { }
14709
- }
14710
- }
14711
- }
14928
+ // Eligibility checks must not create bindings: the loop may never run.
14929
+ // Element subscripts need the normal arithmetic and relative-index evaluator.
14930
+ if (arrAssign.kind === "element")
14931
+ return false;
14932
+ const curArr = store?.get(arrAssign.name);
14712
14933
  const activeArrStore = store ?? stateMonitor(rawState)?.store;
14713
14934
  if (!curArr || curArr.references !== 1 || activeArrStore?.watches.has(arrAssign.name) || rawState.readonlyVariables?.has(arrAssign.name) || rawState.exported.has(arrAssign.name) || controlNames.has(arrAssign.name)) {
14714
14935
  return false;
14715
14936
  }
14716
- if (arrAssign.kind === "compound" && !curArr.associative && arrAssign.entries.length <= 16 && arrAssign.entries.every(e => !e.index && !e.append)) {
14937
+ if (arrAssign.kind === "compound" && arrAssign.entries.length <= 16 && arrAssign.entries.every(e => !e.append)) {
14717
14938
  let allEntriesOk = true;
14718
14939
  for (let ei = 0; ei < arrAssign.entries.length; ei++) {
14719
- const ev = arrAssign.entries[ei].value;
14720
- if (!this.isPureSyncValueWord(ev, rawState) || !ev.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)))) {
14940
+ const entry = arrAssign.entries[ei];
14941
+ const ev = entry.value;
14942
+ if (curArr.associative) {
14943
+ const kSrc = entry.index ? (entry.index.source ?? entry.index.decimal) : "";
14944
+ if (kSrc.length === 0 || kSrc.length > 4096 || kSrc.startsWith("~") || /[$\x60\\"']/.test(kSrc) || !this.isPureSyncValueWord(ev, rawState)) {
14945
+ allEntriesOk = false;
14946
+ break;
14947
+ }
14948
+ continue;
14949
+ }
14950
+ if (entry.index !== undefined) {
14951
+ const kSrc = (entry.index.source ?? entry.index.decimal).trim();
14952
+ const original = compoundEntryWords.get(entry);
14953
+ const hasBraceInOriginal = rawState.braceexpand !== false && Boolean(original?.parts.some(p => p.kind === "text" && !p.quoted && p.value.includes("{")));
14954
+ if (!/^(?:0|[1-9][0-9]{0,8})$/.test(kSrc) || hasBraceInOriginal || (rawState.braceexpand !== false && ev.parts.some(p => !p.quoted && p.kind === "text" && p.value.includes("{"))) || !this.isPureSyncValueWord(ev, rawState)) {
14955
+ allEntriesOk = false;
14956
+ break;
14957
+ }
14958
+ continue;
14959
+ }
14960
+ if (!this.isPureSyncValueWord(ev, rawState) || (!ev.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))))) {
14721
14961
  allEntriesOk = false;
14722
14962
  break;
14723
14963
  }
@@ -14726,19 +14966,6 @@ export class Runtime {
14726
14966
  continue;
14727
14967
  return false;
14728
14968
  }
14729
- if (arrAssign.kind === "element" && (!arrAssign.append || this.budget.limits.maxExpansionBytes === Infinity) && curArr.associative) {
14730
- const kSrc = arrAssign.index.source ?? arrAssign.index.decimal;
14731
- if (kSrc.trim().length > 0 && !kSrc.includes("`") && !kSrc.includes("$(") && this.isPureSyncValueWord(arrAssign.value, rawState))
14732
- continue;
14733
- return false;
14734
- }
14735
- if (arrAssign.kind === "element" && !curArr.associative) {
14736
- const subSrc = (arrAssign.index.source ?? arrAssign.index.decimal).trim();
14737
- if (subSrc.length > 0 && subSrc.length <= 64 && /^[$a-zA-Z0-9_ +*-]+$/.test(subSrc) && !subSrc.includes("/") && !subSrc.includes("%") && !subSrc.includes("=") && !subSrc.includes("++") && !subSrc.includes("--") && this.isPureSyncValueWord(arrAssign.value, rawState)) {
14738
- continue;
14739
- }
14740
- return false;
14741
- }
14742
14969
  return false;
14743
14970
  }
14744
14971
  const assignment = this.assignment(w0);
@@ -15194,6 +15421,8 @@ export class Runtime {
15194
15421
  return 0;
15195
15422
  }
15196
15423
  trySyncLoop(command, pipeline, rawState, monitor, store, existing, elem0, canMutatePipeStatus, io, diagnosticLine, syncReadInputText) {
15424
+ if (!syncSinks.get(io.stderr))
15425
+ return undefined;
15197
15426
  // Substitution callers also need to refuse before effects when status
15198
15427
  // publication requires array admission. Cleanup cannot start async fallback.
15199
15428
  if (store && !existing) {
@@ -15225,13 +15454,17 @@ export class Runtime {
15225
15454
  const initializer = command.kind === "arithmetic-for" ? command.expressions[0]?.tree : undefined;
15226
15455
  const condition = command.kind === "arithmetic-for" ? command.expressions[1]?.tree : undefined;
15227
15456
  const increment = command.kind === "arithmetic-for" ? command.expressions[2]?.tree : undefined;
15457
+ const whileCondCmd = command.kind === "while" && command.condition.lists.length === 1 && command.condition.lists[0].pipelines.length === 1 ? command.condition.lists[0].pipelines[0].commands[0] : undefined;
15458
+ const whilePrintfInd = whileCondCmd?.kind === "arithmetic" && !whileCondCmd.expression.error && whileCondCmd.expression.tree?.kind === "binary" && (whileCondCmd.expression.tree.left.kind === "name" ? whileCondCmd.expression.tree.left.name : (whileCondCmd.expression.tree.left.kind === "unary" && whileCondCmd.expression.tree.left.operand.kind === "name" ? whileCondCmd.expression.tree.left.operand.name : undefined));
15228
15459
  const printfInductionName = initializer?.kind === "binary" && initializer.operator === "=" && initializer.left.kind === "name" &&
15229
15460
  initializer.right.kind === "literal" && initializer.right.value >= 0n && initializer.right.value <= 2000n &&
15230
15461
  condition?.kind === "binary" && (condition.operator === "<" || condition.operator === "<=") &&
15231
15462
  condition.left.kind === "name" && condition.left.name === initializer.left.name &&
15232
15463
  condition.right.kind === "literal" && condition.right.value >= 0n && condition.right.value <= 2000n &&
15233
15464
  increment?.kind === "unary" && increment.operator === "++" && increment.operand.kind === "name" && increment.operand.name === initializer.left.name &&
15234
- this.canSyncPrintfInductionBody(command.body, rawState) ? initializer.left.name : undefined;
15465
+ this.canSyncPrintfInductionBody(command.body, rawState)
15466
+ ? initializer.left.name
15467
+ : (whilePrintfInd && this.canSyncPrintfInductionBody(command.body, rawState) ? whilePrintfInd : undefined);
15235
15468
  if ((guestArrays(rawState) && (guestArrays(rawState).watches.size > 0 || this.budget.limits.maxExpansionBytes < 65536)) || !this.canSyncLoopBody(command.body, rawState, io, false, printfInductionName)) {
15236
15469
  command._skipTrySyncLoop = true;
15237
15470
  return undefined;
@@ -15246,7 +15479,7 @@ export class Runtime {
15246
15479
  if (!existing || !elem0)
15247
15480
  return undefined;
15248
15481
  }
15249
- for (const v of Object.values(rawState.variables)) {
15482
+ for (const v of [...Object.values(rawState.variables), ...rawState.positional]) {
15250
15483
  if (typeof v === "string") {
15251
15484
  for (let k = 0; k < v.length; k++) {
15252
15485
  if (v.charCodeAt(k) >= 128)
@@ -15254,10 +15487,22 @@ export class Runtime {
15254
15487
  }
15255
15488
  }
15256
15489
  }
15490
+ // The loop evaluator assumes ASCII, including values reached through array elements.
15491
+ if (store) {
15492
+ for (const { binding } of store.bindings.values()) {
15493
+ for (const slot of binding.values.values()) {
15494
+ const value = slot.text.value;
15495
+ for (let k = 0; k < value.length; k++) {
15496
+ if (value.charCodeAt(k) >= 128)
15497
+ return undefined;
15498
+ }
15499
+ }
15500
+ }
15501
+ }
15257
15502
  const { steps: bodyAssignments, redirectCount } = this.buildSyncLoopBody(command, rawState, io);
15258
15503
  if (bodyAssignments.length > 30)
15259
15504
  return undefined;
15260
- const touched = sharedSyncLoopTouched;
15505
+ const touched = this._syncArithRawWriteOnly ? new Set() : sharedSyncLoopTouched;
15261
15506
  touched.clear();
15262
15507
  let lastCmd;
15263
15508
  let lastArg = "";
@@ -15350,7 +15595,7 @@ export class Runtime {
15350
15595
  const stepMutatesInduction = (st) => {
15351
15596
  // Integer declarations evaluate arithmetic strings, which can mutate
15352
15597
  // any variable. Array reads can also replace the scalar counter.
15353
- if (st.name === inductionName || st.readHereString?.varNames.includes(inductionName) || st.readHereString?.arrayTarget === inductionName || st.unsetVars?.includes(inductionName) || st.localDecl?.attr === "i" || st.localDecl?.items.some(it => it.name === inductionName))
15598
+ if (st.name === inductionName || st.readHereString?.varNames.includes(inductionName) || st.readHereString?.arrayTarget === inductionName || st.unsetVars?.includes(inductionName) || st.localDecl?.items.some(it => it.name === inductionName))
15354
15599
  return true;
15355
15600
  if (st.arithStmt?.hasMutation && arithTreeMutatesVar(st.arithStmt.tree, inductionName))
15356
15601
  return true;
@@ -15401,9 +15646,13 @@ export class Runtime {
15401
15646
  intSteps[b] = intStep;
15402
15647
  continue;
15403
15648
  }
15404
- if (step.value) {
15405
- for (let i = 0; i < step.value.parts.length; i++) {
15406
- const part = step.value.parts[i];
15649
+ const wordsToScan = step.arrayAssign?.kind === "compound"
15650
+ ? step.arrayAssign.entries.map(e => e.value)
15651
+ : step.value ? [step.value] : [];
15652
+ for (let wIdx = 0; wIdx < wordsToScan.length; wIdx++) {
15653
+ const wVal = wordsToScan[wIdx];
15654
+ for (let i = 0; i < wVal.parts.length; i++) {
15655
+ const part = wVal.parts[i];
15407
15656
  if (part.kind === "text" || part.kind === "variable")
15408
15657
  continue;
15409
15658
  if (part.kind === "arithmetic") {
@@ -15525,7 +15774,7 @@ export class Runtime {
15525
15774
  let lastInductionVal;
15526
15775
  const intBudgetSnapshot = this.budget.parsing.snapshot();
15527
15776
  // Empty loops must evaluate the header without publishing unexecuted body registers or command state.
15528
- let canUseIntRegisters = iterations > 0 && allIntStepsReady && (!hasSubIntStep || (this.middleware.length === 0 && rawState.depth < this.budget.maxSubstitutionDepthSmi)) && !this.budget.hasCpuLimit && this.budget.maxExpansionFieldsSmi >= 1 && this.budget.maxExpansionBytesSmi >= 32 && !store?.get(inductionName) && inductionName !== "LINENO" && inductionName !== "_" && inductionName !== "FUNCNAME";
15777
+ let canUseIntRegisters = !prevRawWrite && iterations > 0 && allIntStepsReady && (!hasSubIntStep || (this.middleware.length === 0 && rawState.depth < this.budget.maxSubstitutionDepthSmi)) && !this.budget.hasCpuLimit && this.budget.maxExpansionFieldsSmi >= 1 && this.budget.maxExpansionBytesSmi >= 32 && !store?.get(inductionName) && inductionName !== "LINENO" && inductionName !== "_" && inductionName !== "FUNCNAME";
15529
15778
  if (canUseIntRegisters && arithNamesList.length > 0) {
15530
15779
  for (let a = 0; a < arithNamesList.length; a++) {
15531
15780
  const refName = arithNamesList[a];
@@ -15807,6 +16056,10 @@ export class Runtime {
15807
16056
  return (e.operator === "-n" || e.operator === "-z" || e.operator === "-v") && this.isPureSyncValueWord(e.operand, rawState);
15808
16057
  if (e.kind === "binary") {
15809
16058
  if (e.operator === "=~") {
16059
+ const patInfo = this.extractSimpleErePattern(e.right, rawState);
16060
+ // Nested branches cannot prove a variable pattern is invariant.
16061
+ if (patInfo?.isVar)
16062
+ return false;
15810
16063
  return (depth === 0 &&
15811
16064
  this.isPureSyncValueWord(e.left, rawState) &&
15812
16065
  this.getFastAnchoredEreRegex(e.right, rawState) !== undefined &&
@@ -16257,9 +16510,11 @@ export class Runtime {
16257
16510
  if (asciiOk)
16258
16511
  fastLoopWords = arrMembers;
16259
16512
  }
16260
- else if (p0.kind === "variable" && !p0.quoted && !p0.indirect && !p0.prefixNames && !p0.length && !p0.substring && !p0.transform && p0.operator === undefined && getArraySelector(p0) === undefined && (rawState.variables.IFS === undefined || rawState.variables.IFS === " \t\n") && !rawState.noglob && !store?.get(p0.name) && !monitor.hasOverlay(p0.name)) {
16261
- const rawVal = rawState.variables[p0.name] ?? "";
16262
- if (rawVal.length <= 32768 && !/[*?[\]\\]/.test(rawVal)) {
16513
+ else if (p0.kind === "variable" && !p0.quoted && !p0.indirect && !p0.prefixNames && !p0.length && !p0.transform && getArraySelector(p0) === undefined && (rawState.variables.IFS === undefined || rawState.variables.IFS === " \t\n") && !rawState.noglob && !store?.get(p0.name) && !monitor.hasOverlay(p0.name) && ((p0.operator === undefined && !p0.substring) || this.isPureSyncValueWord(command.words[0], rawState))) {
16514
+ const rawVal = (p0.operator === undefined && !p0.substring)
16515
+ ? (rawState.variables[p0.name] ?? "")
16516
+ : this.fastValueWord(command.words[0], rawState, io, false, false, false, false, 0, diagnosticLine);
16517
+ if (typeof rawVal === "string" && rawVal.length <= 32768 && !/[*?[\]\\]/.test(rawVal)) {
16263
16518
  let asciiOk = true;
16264
16519
  for (let j = 0; j < rawVal.length; j++) {
16265
16520
  if (rawVal.charCodeAt(j) >= 128) {
@@ -16648,6 +16903,11 @@ export class Runtime {
16648
16903
  for (const list of script.lists)
16649
16904
  for (const pipeline of list.pipelines)
16650
16905
  for (const command of pipeline.commands) {
16906
+ if (command.kind === "arithmetic") {
16907
+ if (command.redirects.length === 0 && !command.expression.error && !command.expression.hasSubscript && isSafeSmiProgram(command.expression) && command.expression.tree?.kind === "unary" && (command.expression.tree.operator === "++" || command.expression.tree.operator === "--") && command.expression.tree.operand.kind === "name")
16908
+ continue;
16909
+ return false;
16910
+ }
16651
16911
  if (command.kind !== "simple" || hasShellFunction(rawState, command.words[0]?.plain ?? "") || command.words.some(word => getArrayAssignment(word) !== undefined))
16652
16912
  return false;
16653
16913
  for (const word of [...command.words, ...command.redirects.map(redirect => redirect.target)])
@@ -16656,6 +16916,8 @@ export class Runtime {
16656
16916
  continue;
16657
16917
  if (part.kind === "variable" && !part.indirect && !part.length && !part.substring && !part.transform && !part.prefixNames && part.operator === undefined && getArraySelector(part) === undefined)
16658
16918
  continue;
16919
+ if (part.kind === "arithmetic" && !part.expression.error && !part.expression.hasMutation && !part.expression.hasSubscript && isSafeSmiProgram(part.expression) && /^[a-zA-Z0-9_ +*-]+$/.test(part.expression.source))
16920
+ continue;
16659
16921
  if (part.kind === "substitution" && this.canSyncPrintfInductionBody(part.script, rawState))
16660
16922
  continue;
16661
16923
  return false;
@@ -16683,6 +16945,20 @@ export class Runtime {
16683
16945
  args.push("0");
16684
16946
  continue;
16685
16947
  }
16948
+ if (i > fmtWordIdx &&
16949
+ inductionName !== undefined &&
16950
+ valueParts.length === 1 &&
16951
+ part?.kind === "arithmetic" &&
16952
+ part.quoted &&
16953
+ !part.expression.error &&
16954
+ !part.expression.hasMutation &&
16955
+ !part.expression.hasSubscript &&
16956
+ isSafeSmiProgram(part.expression) &&
16957
+ /^[a-zA-Z0-9_ +*-]+$/.test(part.expression.source) &&
16958
+ this.canSyncArithmeticWithoutFault(part.expression.tree, rawState, cmd.line ?? 1)) {
16959
+ args.push("0");
16960
+ continue;
16961
+ }
16686
16962
  let arg = "";
16687
16963
  for (const part of w.parts) {
16688
16964
  if (part.kind !== "text" || (!part.quoted && (part.value.length === 0 || part.value.includes(" ") || part.value.includes("\t") || part.value.includes("\n") || part.value.includes("{") || hasGlobOrEscape(part.value, true))))
@@ -16699,6 +16975,39 @@ export class Runtime {
16699
16975
  return false;
16700
16976
  return w.parts.every(p => p.kind === "text" || (p.kind === "variable" && !p.indirect && !p.prefixNames && !p.length && !p.substring && p.operator === undefined && getArraySelector(p) === undefined));
16701
16977
  }
16978
+ execSyncReadHereStringStep(rs, step, rawState, io, monitor, touched) {
16979
+ const rawInput = this.fastValueWord(rs.inputWord, rawState, io, false, false, false, false, 0, step.line);
16980
+ const delim = rs.delimChar ?? "\n";
16981
+ let lineStr;
16982
+ let status = 0;
16983
+ if (rs.maxChars !== undefined) {
16984
+ const fullInput = rawInput + "\n";
16985
+ if (rs.exactChars) {
16986
+ lineStr = fullInput.length > rs.maxChars ? fullInput.slice(0, rs.maxChars) : fullInput;
16987
+ status = fullInput.length >= rs.maxChars ? 0 : 1;
16988
+ }
16989
+ else {
16990
+ const nlIdx = fullInput.indexOf(delim);
16991
+ const untilDelim = nlIdx === -1 ? fullInput : fullInput.slice(0, nlIdx);
16992
+ lineStr = untilDelim.length > rs.maxChars ? untilDelim.slice(0, rs.maxChars) : untilDelim;
16993
+ status = (nlIdx !== -1 || fullInput.length >= rs.maxChars) ? 0 : 1;
16994
+ }
16995
+ }
16996
+ else {
16997
+ const fullInput = delim === "\n" ? rawInput : rawInput + "\n";
16998
+ const nlIdx = rs.isMapfile ? -1 : fullInput.indexOf(delim);
16999
+ lineStr = nlIdx === -1 ? fullInput : fullInput.slice(0, nlIdx);
17000
+ status = (!rs.isMapfile && delim !== "\n" && nlIdx === -1) ? 1 : 0;
17001
+ }
17002
+ const ifsStr = rs.exactChars
17003
+ ? ""
17004
+ : rs.ifsWord !== undefined
17005
+ ? this.fastValueWord(rs.ifsWord, rawState, io, false, false, false, false, 0, step.line)
17006
+ : (rawState.variables.IFS ?? " \t\n");
17007
+ this.splitSyncReadLine(lineStr, ifsStr, rs.varNames, rs.isBareReply, rawState, touched, rs.arrayTarget, monitor, rs.isMapfile);
17008
+ rawState.status = status;
17009
+ return step.cmd.words[step.cmd.words.length - 1].plain;
17010
+ }
16702
17011
  splitSyncReadLine(lineStr, ifsStr, varNames, isBareReply, rawState, touched, arrayTarget, monitor, isMapfile = false) {
16703
17012
  if (arrayTarget !== undefined && monitor !== undefined) {
16704
17013
  const arrStore = monitor.store ?? requireArrays(rawState);
@@ -16833,17 +17142,8 @@ export class Runtime {
16833
17142
  touched.add(item.name);
16834
17143
  }
16835
17144
  if (item.value !== undefined) {
16836
- let val = this.fastValueWord(item.value, rawState, io, false, false, false, rawState.braceexpand !== false, 0, line);
16837
- if (decl.attr === "u")
16838
- val = val.toUpperCase();
16839
- else if (decl.attr === "l")
16840
- val = val.toLowerCase();
16841
- else if (decl.attr === "i") {
16842
- const curRaw = item.append ? rawState.variables[item.name] : undefined;
16843
- const curStr = curRaw === undefined ? "" : (touched.has(item.name) ? curRaw : (monitor.values.get(item.name, curRaw) ?? curRaw));
16844
- val = applySyncScalarAttributes("i", val, typeof curStr === "string" ? curStr : "", item.append, rawState, monitor, arrayStore(rawState), touched) ?? "0";
16845
- }
16846
- if (item.append && decl.attr !== "i") {
17145
+ const val = this.fastValueWord(item.value, rawState, io, false, false, false, rawState.braceexpand !== false, 0, line);
17146
+ if (item.append) {
16847
17147
  const curRaw = rawState.variables[item.name];
16848
17148
  const curStr = curRaw === undefined ? "" : (touched.has(item.name) ? curRaw : (monitor.values.get(item.name, curRaw) ?? curRaw));
16849
17149
  rawState.variables[item.name] = (typeof curStr === "string" ? curStr : "") + val;
@@ -16857,9 +17157,6 @@ export class Runtime {
16857
17157
  else {
16858
17158
  lastArg = item.name;
16859
17159
  }
16860
- if (decl.attr !== undefined) {
16861
- (this._syncPendingVarAttrs ??= new Map()).set(item.name, decl.attr);
16862
- }
16863
17160
  if (decl.kind === "export") {
16864
17161
  rawState.exported.add(item.name);
16865
17162
  }
@@ -16867,20 +17164,12 @@ export class Runtime {
16867
17164
  return lastArg;
16868
17165
  }
16869
17166
  _syncTouchedPositionals = false;
16870
- _syncPendingVarAttrs = undefined;
16871
17167
  flushSyncPositionals(rawState) {
16872
17168
  if (this._syncTouchedPositionals) {
16873
17169
  this._syncTouchedPositionals = false;
16874
17170
  this._fastSubPositional = undefined;
16875
17171
  this.replacePositionals(rawState, rawState.positional);
16876
17172
  }
16877
- if (this._syncPendingVarAttrs && this._syncPendingVarAttrs.size > 0) {
16878
- rawState.variableAttributes ??= new Map();
16879
- for (const [k, v] of this._syncPendingVarAttrs) {
16880
- rawState.variableAttributes.set(k, v);
16881
- }
16882
- this._syncPendingVarAttrs.clear();
16883
- }
16884
17173
  }
16885
17174
  execSyncShiftStep(shiftCount, cmd, rawState) {
16886
17175
  const activePos = this._fastSubPositional ?? rawState.positional;
@@ -17006,13 +17295,7 @@ export class Runtime {
17006
17295
  break;
17007
17296
  }
17008
17297
  if (step.unsetVars !== undefined) {
17009
- for (let ui = 0; ui < step.unsetVars.length; ui++) {
17010
- const uvn = step.unsetVars[ui];
17011
- delete rawState.variables[uvn];
17012
- monitor.proxy.exported.delete(uvn);
17013
- touched.add(uvn);
17014
- }
17015
- lastArg = step.cmd.words[step.cmd.words.length - 1].plain;
17298
+ lastArg = this.execSyncUnsetStep(step, rawState, io, monitor, touched);
17016
17299
  rawState.status = 0;
17017
17300
  continue;
17018
17301
  }
@@ -17028,16 +17311,7 @@ export class Runtime {
17028
17311
  continue;
17029
17312
  }
17030
17313
  if (step.readHereString !== undefined) {
17031
- const rs = step.readHereString;
17032
- const rawInput = this.fastValueWord(rs.inputWord, rawState, io, false, false, false, false, 0, step.line);
17033
- const delim = rs.delimChar ?? "\n";
17034
- const fullInput = delim === "\n" ? rawInput : rawInput + "\n";
17035
- const nlIdx = rs.isMapfile ? -1 : fullInput.indexOf(delim);
17036
- const lineStr = nlIdx === -1 ? fullInput : fullInput.slice(0, nlIdx);
17037
- const ifsStr = rs.ifsWord !== undefined ? this.fastValueWord(rs.ifsWord, rawState, io, false, false, false, false, 0, step.line) : (rawState.variables.IFS ?? " \t\n");
17038
- this.splitSyncReadLine(lineStr, ifsStr, rs.varNames, rs.isBareReply, rawState, touched, rs.arrayTarget, monitor, rs.isMapfile);
17039
- lastArg = step.cmd.words[step.cmd.words.length - 1].plain;
17040
- rawState.status = (!rs.isMapfile && delim !== "\n" && nlIdx === -1) ? 1 : 0;
17314
+ lastArg = this.execSyncReadHereStringStep(step.readHereString, step, rawState, io, monitor, touched);
17041
17315
  continue;
17042
17316
  }
17043
17317
  if (deferredMask & (1 << b)) {
@@ -17092,7 +17366,9 @@ export class Runtime {
17092
17366
  lastArg = pLastArg;
17093
17367
  }
17094
17368
  else if (step.arrayAssign !== undefined) {
17095
- this.tryFastArrayAssignmentSync(step.arrayAssign, rawState, io, step.line, undefined, undefined, true);
17369
+ if (!this.tryFastArrayAssignmentSync(step.arrayAssign, rawState, io, step.line, undefined, undefined, true)) {
17370
+ throw new Error("Synchronous loop array assignment was not executable");
17371
+ }
17096
17372
  lastArg = "";
17097
17373
  }
17098
17374
  else if (step.caseClauses !== undefined) {
@@ -17270,44 +17546,56 @@ export class Runtime {
17270
17546
  }
17271
17547
  }
17272
17548
  evalSyncLoopArithStmt(expr, rawState, io, touched, line) {
17273
- if (!expr.error)
17274
- return this.syncShellArithmeticNonZero(expr, rawState, line);
17275
- const cachedExpr = expr;
17276
- const expWord = cachedExpr._cachedArithWord ?? parseArithmeticExpansion(expr.source, this.budget.parsing, false, 0, undefined, rawState.extensions?.syntax);
17277
- cachedExpr._cachedArithWord = expWord;
17278
- const fastSrc = this.fastValueWord(expWord, rawState, io, false, false, true, false, 0, line);
17279
- const scalarMutMatch = fastSrc.length <= 64 ? SIMPLE_SCALAR_MUT_RE.exec(fastSrc) : null;
17280
- if (scalarMutMatch) {
17281
- const varName = resolveSyncNameref(rawState, scalarMutMatch[1]);
17282
- const binOp = scalarMutMatch[2];
17283
- const rhs1 = Number(scalarMutMatch[3]);
17284
- const rhsOp = scalarMutMatch[4];
17285
- const rhs2 = scalarMutMatch[5] !== undefined ? Number(scalarMutMatch[5]) : 0;
17286
- const rhsNum = rhsOp === "+" ? rhs1 + rhs2 : rhsOp === "-" ? rhs1 - rhs2 : rhsOp === "*" ? rhs1 * rhs2 : rhs1;
17287
- const curVal = rawState.variables[varName] ?? "0";
17288
- if (curVal === "" || /^-?(?:0|[1-9][0-9]{0,12})$/.test(curVal)) {
17289
- const curNum = curVal === "" ? 0 : Number(curVal);
17290
- const nextNum = binOp === "=" ? rhsNum : binOp === "+=" ? curNum + rhsNum : curNum - rhsNum;
17291
- if (Number.isSafeInteger(nextNum)) {
17549
+ try {
17550
+ if (!expr.error)
17551
+ return this.syncShellArithmeticNonZero(expr, rawState, line);
17552
+ const cachedExpr = expr;
17553
+ const expWord = cachedExpr._cachedArithWord ?? parseArithmeticExpansion(expr.source, this.budget.parsing, false, 0, undefined, rawState.extensions?.syntax);
17554
+ cachedExpr._cachedArithWord = expWord;
17555
+ const fastSrc = this.fastValueWord(expWord, rawState, io, false, false, true, false, 0, line);
17556
+ const scalarMutMatch = fastSrc.length <= 64 ? SIMPLE_SCALAR_MUT_RE.exec(fastSrc) : null;
17557
+ if (scalarMutMatch) {
17558
+ const varName = resolveSyncNameref(rawState, scalarMutMatch[1]);
17559
+ const binOp = scalarMutMatch[2];
17560
+ const rhs1 = Number(scalarMutMatch[3]);
17561
+ const rhsOp = scalarMutMatch[4];
17562
+ const rhs2 = scalarMutMatch[5] !== undefined ? Number(scalarMutMatch[5]) : 0;
17563
+ const rhsNum = rhsOp === "+" ? rhs1 + rhs2 : rhsOp === "-" ? rhs1 - rhs2 : rhsOp === "*" ? rhs1 * rhs2 : rhs1;
17564
+ const curVal = rawState.variables[varName] ?? "0";
17565
+ if (curVal === "" || /^-?(?:0|[1-9][0-9]{0,12})$/.test(curVal)) {
17566
+ const curNum = curVal === "" ? 0 : Number(curVal);
17567
+ const nextNum = binOp === "=" ? rhsNum : binOp === "+=" ? curNum + rhsNum : curNum - rhsNum;
17568
+ if (Number.isSafeInteger(nextNum)) {
17569
+ this.budget.parsing.admit(4);
17570
+ rawState.variables[varName] = intToStr(nextNum);
17571
+ touched.add(varName);
17572
+ return nextNum !== 0;
17573
+ }
17574
+ }
17575
+ }
17576
+ const binCmpMatch = fastSrc.length <= 64 ? /^\s*(-?(?:0|[1-9][0-9]{0,12}))\s*(==|!=|<|<=|>|>=)\s*(-?(?:0|[1-9][0-9]{0,12}))\s*$/.exec(fastSrc) : null;
17577
+ if (binCmpMatch) {
17578
+ const lhs = Number(binCmpMatch[1]);
17579
+ const op = binCmpMatch[2];
17580
+ const rhs = Number(binCmpMatch[3]);
17581
+ if (Number.isSafeInteger(lhs) && Number.isSafeInteger(rhs)) {
17292
17582
  this.budget.parsing.admit(4);
17293
- rawState.variables[varName] = intToStr(nextNum);
17294
- touched.add(varName);
17295
- return nextNum !== 0;
17583
+ return op === ">" ? lhs > rhs : op === ">=" ? lhs >= rhs : op === "<" ? lhs < rhs : op === "<=" ? lhs <= rhs : op === "==" ? lhs === rhs : lhs !== rhs;
17296
17584
  }
17297
17585
  }
17586
+ const dynProg = prepareArithmetic(fastSrc, this.budget.parsing);
17587
+ return this.syncShellArithmeticNonZero(dynProg, rawState, line);
17298
17588
  }
17299
- const binCmpMatch = fastSrc.length <= 64 ? /^\s*(-?(?:0|[1-9][0-9]{0,12}))\s*(==|!=|<|<=|>|>=)\s*(-?(?:0|[1-9][0-9]{0,12}))\s*$/.exec(fastSrc) : null;
17300
- if (binCmpMatch) {
17301
- const lhs = Number(binCmpMatch[1]);
17302
- const op = binCmpMatch[2];
17303
- const rhs = Number(binCmpMatch[3]);
17304
- if (Number.isSafeInteger(lhs) && Number.isSafeInteger(rhs)) {
17305
- this.budget.parsing.admit(4);
17306
- return op === ">" ? lhs > rhs : op === ">=" ? lhs >= rhs : op === "<" ? lhs < rhs : op === "<=" ? lhs <= rhs : op === "==" ? lhs === rhs : lhs !== rhs;
17307
- }
17589
+ catch (error) {
17590
+ if (!(error instanceof PublicDiagnostic))
17591
+ throw error;
17592
+ const write = syncSinks.get(io.stderr);
17593
+ if (!write)
17594
+ throw error;
17595
+ write(fastSharedTextEncoder.encode(`${io.scriptName ?? "shell"}: line ${line}: ((: ${error.message}\n`));
17596
+ rawState.status = 1;
17597
+ return undefined;
17308
17598
  }
17309
- const dynProg = prepareArithmetic(fastSrc, this.budget.parsing);
17310
- return this.syncShellArithmeticNonZero(dynProg, rawState, line);
17311
17599
  }
17312
17600
  extractSyncLoopFnSpec(cmd, rawState, io) {
17313
17601
  if (this._syncLoopFnCheckDepth > 0 || (rawState.locals && rawState.locals.length > 0))
@@ -17585,13 +17873,7 @@ export class Runtime {
17585
17873
  break;
17586
17874
  }
17587
17875
  if (step.unsetVars !== undefined) {
17588
- for (let ui = 0; ui < step.unsetVars.length; ui++) {
17589
- const uvn = step.unsetVars[ui];
17590
- delete rawState.variables[uvn];
17591
- monitor.proxy.exported.delete(uvn);
17592
- touched.add(uvn);
17593
- }
17594
- lastArg = step.cmd.words[step.cmd.words.length - 1].plain;
17876
+ lastArg = this.execSyncUnsetStep(step, rawState, io, monitor, touched);
17595
17877
  rawState.status = 0;
17596
17878
  onUpdate({ lastCmd, lastArg });
17597
17879
  continue;
@@ -17610,16 +17892,7 @@ export class Runtime {
17610
17892
  continue;
17611
17893
  }
17612
17894
  if (step.readHereString !== undefined) {
17613
- const rs = step.readHereString;
17614
- const rawInput = this.fastValueWord(rs.inputWord, rawState, io, false, false, false, false, 0, step.line);
17615
- const delim = rs.delimChar ?? "\n";
17616
- const fullInput = delim === "\n" ? rawInput : rawInput + "\n";
17617
- const nlIdx = rs.isMapfile ? -1 : fullInput.indexOf(delim);
17618
- const lineStr = nlIdx === -1 ? fullInput : fullInput.slice(0, nlIdx);
17619
- const ifsStr = rs.ifsWord !== undefined ? this.fastValueWord(rs.ifsWord, rawState, io, false, false, false, false, 0, step.line) : (rawState.variables.IFS ?? " \t\n");
17620
- this.splitSyncReadLine(lineStr, ifsStr, rs.varNames, rs.isBareReply, rawState, touched, rs.arrayTarget, monitor, rs.isMapfile);
17621
- lastArg = step.cmd.words[step.cmd.words.length - 1].plain;
17622
- rawState.status = (!rs.isMapfile && delim !== "\n" && nlIdx === -1) ? 1 : 0;
17895
+ lastArg = this.execSyncReadHereStringStep(step.readHereString, step, rawState, io, monitor, touched);
17623
17896
  onUpdate({ lastCmd, lastArg });
17624
17897
  continue;
17625
17898
  }
@@ -17694,7 +17967,9 @@ export class Runtime {
17694
17967
  lastArg = pLastArg;
17695
17968
  }
17696
17969
  else if (step.arrayAssign !== undefined) {
17697
- this.tryFastArrayAssignmentSync(step.arrayAssign, rawState, io, step.line, undefined, undefined, true);
17970
+ if (!this.tryFastArrayAssignmentSync(step.arrayAssign, rawState, io, step.line, undefined, undefined, true)) {
17971
+ throw new Error("Synchronous loop array assignment was not executable");
17972
+ }
17698
17973
  lastArg = "";
17699
17974
  }
17700
17975
  else if (step.printfVArgs !== undefined && step.name !== undefined) {
@@ -17783,13 +18058,7 @@ export class Runtime {
17783
18058
  break;
17784
18059
  }
17785
18060
  if (step.unsetVars !== undefined) {
17786
- for (let ui = 0; ui < step.unsetVars.length; ui++) {
17787
- const uvn = step.unsetVars[ui];
17788
- delete rawState.variables[uvn];
17789
- monitor.proxy.exported.delete(uvn);
17790
- touched.add(uvn);
17791
- }
17792
- lastArg = step.cmd.words[step.cmd.words.length - 1].plain;
18061
+ lastArg = this.execSyncUnsetStep(step, rawState, io, monitor, touched);
17793
18062
  rawState.status = 0;
17794
18063
  continue;
17795
18064
  }
@@ -17805,16 +18074,7 @@ export class Runtime {
17805
18074
  continue;
17806
18075
  }
17807
18076
  if (step.readHereString !== undefined) {
17808
- const rs = step.readHereString;
17809
- const rawInput = this.fastValueWord(rs.inputWord, rawState, io, false, false, false, false, 0, step.line);
17810
- const delim = rs.delimChar ?? "\n";
17811
- const fullInput = delim === "\n" ? rawInput : rawInput + "\n";
17812
- const nlIdx = rs.isMapfile ? -1 : fullInput.indexOf(delim);
17813
- const lineStr = nlIdx === -1 ? fullInput : fullInput.slice(0, nlIdx);
17814
- const ifsStr = rs.ifsWord !== undefined ? this.fastValueWord(rs.ifsWord, rawState, io, false, false, false, false, 0, step.line) : (rawState.variables.IFS ?? " \t\n");
17815
- this.splitSyncReadLine(lineStr, ifsStr, rs.varNames, rs.isBareReply, rawState, touched, rs.arrayTarget, monitor, rs.isMapfile);
17816
- lastArg = step.cmd.words[step.cmd.words.length - 1].plain;
17817
- rawState.status = (!rs.isMapfile && delim !== "\n" && nlIdx === -1) ? 1 : 0;
18077
+ lastArg = this.execSyncReadHereStringStep(step.readHereString, step, rawState, io, monitor, touched);
17818
18078
  continue;
17819
18079
  }
17820
18080
  if (step.coalesceNext) {
@@ -17882,7 +18142,9 @@ export class Runtime {
17882
18142
  continue;
17883
18143
  }
17884
18144
  else if (step.arrayAssign !== undefined) {
17885
- this.tryFastArrayAssignmentSync(step.arrayAssign, rawState, io, step.line, undefined, undefined, true);
18145
+ if (!this.tryFastArrayAssignmentSync(step.arrayAssign, rawState, io, step.line, undefined, undefined, true)) {
18146
+ throw new Error("Synchronous loop array assignment was not executable");
18147
+ }
17886
18148
  lastArg = "";
17887
18149
  }
17888
18150
  else if (step.arithStmt !== undefined) {
@@ -19153,7 +19415,7 @@ export class Runtime {
19153
19415
  try {
19154
19416
  status = await evaluateConditional(command.expression, {
19155
19417
  fs: this.fs, cwd: state.cwd, signal: this.signal, predicateIdentity: io.capabilities?.predicateIdentity, reference: name => state.variableAttributes?.get(name)?.includes("n") ?? false, locale: state.variables.LC_ALL || state.variables.LC_COLLATE || state.variables.LANG || "C", characterLocale: state.variables.LC_ALL || state.variables.LC_CTYPE || state.variables.LANG || "C", ignoreCase: !!state.nocasematch, extglob: true, work: { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes"), allocation }, expand: async (word, pattern = false) => {
19156
- const fast = !pattern ? this.fastValueWord(word, state, io, false, false, false, false) : undefined;
19418
+ const fast = !pattern ? this.fastValueWord(word, state, { ...io, nameExpansionContext: "conditional" }, false, false, false, false) : undefined;
19157
19419
  if (typeof fast === "string")
19158
19420
  return fast;
19159
19421
  return (await this.word(word, state, { ...io, nameExpansionContext: "conditional" }, false, pattern, false, pattern)).join("");
@@ -24338,8 +24600,8 @@ export class Runtime {
24338
24600
  invalid = true;
24339
24601
  break;
24340
24602
  }
24341
- if (flag === "N")
24342
- exact = true;
24603
+ if (flag === "n" || flag === "N")
24604
+ exact = flag === "N";
24343
24605
  const value = option.slice(index + 1) || names.shift();
24344
24606
  if (value === undefined)
24345
24607
  invalid = true;
@@ -25782,25 +26044,27 @@ export class Runtime {
25782
26044
  return undefined;
25783
26045
  let transformed;
25784
26046
  if (part.transform === "Q") {
25785
- for (let k = 0; k < val.length; k++) {
25786
- const c = val.charCodeAt(k);
25787
- if (c < 32 || c >= 127)
25788
- return undefined;
25789
- }
25790
- transformed = "'" + (val.includes("'") ? val.split("'").join("'\\''") : val) + "'";
26047
+ if (byteLocale(rawState.variables))
26048
+ return undefined;
26049
+ transformed = this.formatSyncTransformQ(val);
25791
26050
  }
25792
26051
  else {
25793
- for (let k = 0; k < val.length; k++) {
25794
- if (val.charCodeAt(k) >= 128)
25795
- return undefined;
26052
+ if (part.transform === "U") {
26053
+ transformed = val.toUpperCase();
26054
+ }
26055
+ else if (part.transform === "L") {
26056
+ transformed = val.toLowerCase();
26057
+ }
26058
+ else if (val.length === 0) {
26059
+ transformed = "";
26060
+ }
26061
+ else if (val.charCodeAt(0) < 128) {
26062
+ transformed = val[0].toUpperCase() + val.slice(1);
26063
+ }
26064
+ else {
26065
+ const first = [...val][0];
26066
+ transformed = first.toUpperCase() + val.slice(first.length);
25796
26067
  }
25797
- transformed = part.transform === "U"
25798
- ? val.toUpperCase()
25799
- : part.transform === "L"
25800
- ? val.toLowerCase()
25801
- : val.length === 0
25802
- ? ""
25803
- : val[0].toUpperCase() + val.slice(1);
25804
26068
  }
25805
26069
  if (split && !part.quoted) {
25806
26070
  if (transformed.length === 0 || transformed.includes(" ") || transformed.includes("\t") || transformed.includes("\n") || (!rawState.noglob && hasGlobOrEscape(transformed, !!rawState.extglob)))
@@ -25809,8 +26073,35 @@ export class Runtime {
25809
26073
  out += transformed;
25810
26074
  continue;
25811
26075
  }
25812
- if (part.prefixNames !== undefined)
25813
- return undefined;
26076
+ if (part.prefixNames !== undefined) {
26077
+ if ((split && !(part.quoted && part.prefixNames === "*")) ||
26078
+ part.indirect ||
26079
+ part.specialParameter ||
26080
+ part.length ||
26081
+ part.substring ||
26082
+ part.transform !== undefined ||
26083
+ part.operator !== undefined ||
26084
+ getArraySelector(part) !== undefined ||
26085
+ this.budget.limits.maxExpansionFields !== Infinity ||
26086
+ this.budget.limits.maxExpansionBytes !== Infinity ||
26087
+ !isShellIdentifier(part.name)) {
26088
+ return undefined;
26089
+ }
26090
+ const pNames = this.collectSyncPrefixNames(part.name, rawState);
26091
+ if (pNames === undefined)
26092
+ return undefined;
26093
+ const ifs = rawState.variables.IFS ?? " ";
26094
+ const sep = io.nameExpansionContext === "document" || (io.nameExpansionContext === "conditional" && part.prefixNames === "@")
26095
+ ? " "
26096
+ : ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
26097
+ const joined = pNames.join(sep);
26098
+ if (split && !part.quoted) {
26099
+ if (joined.length === 0 || joined.includes(" ") || joined.includes("\t") || joined.includes("\n"))
26100
+ return undefined;
26101
+ }
26102
+ out += joined;
26103
+ continue;
26104
+ }
25814
26105
  if (part.indirect || part.specialParameter)
25815
26106
  return undefined;
25816
26107
  if (part.name === "@" || part.name === "*" || part.name === "PIPESTATUS" || part.name === "LINENO" || part.name === "_" || part.name === "FUNCNAME" || part.name === "DIRSTACK")
@@ -26587,12 +26878,638 @@ export class Runtime {
26587
26878
  this.budget.fail("maxExpansionBytes");
26588
26879
  return out;
26589
26880
  }
26590
- expandSyncTrCharSet(spec) {
26591
- const out = new Set();
26881
+ expandSyncTrCharArray(spec) {
26882
+ if (spec.length === 0)
26883
+ return undefined;
26884
+ const posixClasses = {
26885
+ "[:lower:]": "abcdefghijklmnopqrstuvwxyz",
26886
+ "[:upper:]": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
26887
+ "[:digit:]": "0123456789",
26888
+ "[:alpha:]": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
26889
+ "[:alnum:]": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
26890
+ "[:space:]": "\t\n\v\f\r ",
26891
+ "[:blank:]": "\t ",
26892
+ "[:xdigit:]": "0123456789ABCDEFabcdef",
26893
+ "[:punct:]": "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
26894
+ };
26895
+ const out = [];
26592
26896
  for (let i = 0; i < spec.length; i++) {
26593
- if (i + 2 < spec.length && spec[i + 1] === "-") {
26594
- const start = spec.charCodeAt(i);
26595
- const end = spec.charCodeAt(i + 2);
26897
+ if (spec[i] === "[" && spec[i + 1] === ":") {
26898
+ const close = spec.indexOf(":]", i + 2);
26899
+ if (close === -1)
26900
+ return undefined;
26901
+ const cls = spec.slice(i, close + 2);
26902
+ const exp = posixClasses[cls];
26903
+ if (!exp)
26904
+ return undefined;
26905
+ for (let k = 0; k < exp.length; k++)
26906
+ out.push(exp[k]);
26907
+ i = close + 1;
26908
+ continue;
26909
+ }
26910
+ if (spec[i] === "[")
26911
+ return undefined;
26912
+ if (spec[i] === "\\") {
26913
+ if (i + 1 >= spec.length)
26914
+ return undefined;
26915
+ const next = spec[++i];
26916
+ if (next === "n")
26917
+ out.push("\n");
26918
+ else if (next === "t")
26919
+ out.push("\t");
26920
+ else if (next === "r")
26921
+ out.push("\r");
26922
+ else if (next === "\\")
26923
+ out.push("\\");
26924
+ else
26925
+ return undefined;
26926
+ continue;
26927
+ }
26928
+ if (i + 2 < spec.length && spec[i + 1] === "-" && spec[i + 2] !== "\\") {
26929
+ const start = spec.charCodeAt(i);
26930
+ const end = spec.charCodeAt(i + 2);
26931
+ if (start <= end && end - start <= 256) {
26932
+ for (let c = start; c <= end; c++)
26933
+ out.push(String.fromCharCode(c));
26934
+ i += 2;
26935
+ continue;
26936
+ }
26937
+ return undefined;
26938
+ }
26939
+ out.push(spec[i]);
26940
+ }
26941
+ return out;
26942
+ }
26943
+ evalSyncTr(input, opArgs) {
26944
+ if (opArgs.length === 2 && !opArgs[0].startsWith("-") && !opArgs[1].startsWith("-")) {
26945
+ return this.translateSyncTr(input, opArgs[0], opArgs[1]);
26946
+ }
26947
+ if (opArgs.length === 2 && opArgs[0] === "-d" && !opArgs[1].startsWith("-")) {
26948
+ const arr = this.expandSyncTrCharArray(opArgs[1]);
26949
+ if (!arr)
26950
+ return undefined;
26951
+ const delSet = new Set(arr);
26952
+ let out = "";
26953
+ for (let i = 0; i < input.length; i++) {
26954
+ const ch = input[i];
26955
+ if (!delSet.has(ch))
26956
+ out += ch;
26957
+ }
26958
+ return out;
26959
+ }
26960
+ if (opArgs.length === 2 && opArgs[0] === "-s" && !opArgs[1].startsWith("-")) {
26961
+ const arr = this.expandSyncTrCharArray(opArgs[1]);
26962
+ if (!arr)
26963
+ return undefined;
26964
+ const sqSet = new Set(arr);
26965
+ let out = "";
26966
+ let prev = "";
26967
+ for (let i = 0; i < input.length; i++) {
26968
+ const ch = input[i];
26969
+ if (sqSet.has(ch) && ch === prev)
26970
+ continue;
26971
+ out += ch;
26972
+ prev = ch;
26973
+ }
26974
+ return out;
26975
+ }
26976
+ if (opArgs.length === 3 && (opArgs[0] === "-s" || opArgs[0] === "-ds" || opArgs[0] === "-sd") && !opArgs[1].startsWith("-") && !opArgs[2].startsWith("-")) {
26977
+ const stage1 = opArgs[0] === "-s"
26978
+ ? this.translateSyncTr(input, opArgs[1], opArgs[2])
26979
+ : this.evalSyncTr(input, ["-d", opArgs[1]]);
26980
+ if (stage1 === undefined)
26981
+ return undefined;
26982
+ return this.evalSyncTr(stage1, ["-s", opArgs[2]]);
26983
+ }
26984
+ return undefined;
26985
+ }
26986
+ evalSyncJq(input, opArgs) {
26987
+ let rawOut = false;
26988
+ let filter;
26989
+ if (opArgs.length === 1 && !opArgs[0].startsWith("-")) {
26990
+ filter = opArgs[0];
26991
+ }
26992
+ else if (opArgs.length === 2 && (opArgs[0] === "-r" || opArgs[0] === "--raw-output" || opArgs[0] === "-c" || opArgs[0] === "--compact-output" || opArgs[0] === "-rc" || opArgs[0] === "-cr")) {
26993
+ rawOut = opArgs[0].includes("r");
26994
+ filter = opArgs[1];
26995
+ }
26996
+ if (!filter)
26997
+ return undefined;
26998
+ const stages = filter.split("|").map(s => s.trim());
26999
+ const compiledStages = [];
27000
+ for (const st of stages) {
27001
+ if (st === ".") {
27002
+ compiledStages.push([]);
27003
+ continue;
27004
+ }
27005
+ if (st === "length") {
27006
+ compiledStages.push([{ kind: "length" }]);
27007
+ continue;
27008
+ }
27009
+ if (st === "keys") {
27010
+ compiledStages.push([{ kind: "keys" }]);
27011
+ continue;
27012
+ }
27013
+ if (st === "keys[]") {
27014
+ compiledStages.push([{ kind: "keys" }, { kind: "iter" }]);
27015
+ continue;
27016
+ }
27017
+ if (!st.startsWith("."))
27018
+ return undefined;
27019
+ let rest = st.slice(1);
27020
+ const ops = [];
27021
+ while (rest.length > 0) {
27022
+ if (rest.startsWith("."))
27023
+ rest = rest.slice(1);
27024
+ const propM = /^([a-zA-Z_][a-zA-Z0-9_]*)/.exec(rest);
27025
+ if (propM) {
27026
+ ops.push({ kind: "prop", key: propM[1] });
27027
+ rest = rest.slice(propM[1].length);
27028
+ continue;
27029
+ }
27030
+ if (rest.startsWith("[]")) {
27031
+ ops.push({ kind: "iter" });
27032
+ rest = rest.slice(2);
27033
+ continue;
27034
+ }
27035
+ const idxM = /^\[(-?[0-9]+)\]/.exec(rest);
27036
+ if (idxM) {
27037
+ ops.push({ kind: "index", idx: Number(idxM[1]) });
27038
+ rest = rest.slice(idxM[0].length);
27039
+ continue;
27040
+ }
27041
+ return undefined;
27042
+ }
27043
+ compiledStages.push(ops);
27044
+ }
27045
+ let parsed;
27046
+ try {
27047
+ parsed = JSON.parse(input);
27048
+ }
27049
+ catch {
27050
+ return undefined;
27051
+ }
27052
+ let current = [parsed];
27053
+ for (const ops of compiledStages) {
27054
+ for (const op of ops) {
27055
+ const next = [];
27056
+ for (const item of current) {
27057
+ if (op.kind === "prop") {
27058
+ if (item === null || item === undefined)
27059
+ next.push(null);
27060
+ else if (typeof item === "object" && !Array.isArray(item))
27061
+ next.push(item[op.key] ?? null);
27062
+ else
27063
+ return undefined;
27064
+ }
27065
+ else if (op.kind === "index") {
27066
+ if (item === null || item === undefined)
27067
+ next.push(null);
27068
+ else if (Array.isArray(item)) {
27069
+ const i = op.idx < 0 ? item.length + op.idx : op.idx;
27070
+ next.push(i >= 0 && i < item.length ? item[i] : null);
27071
+ }
27072
+ else
27073
+ return undefined;
27074
+ }
27075
+ else if (op.kind === "iter") {
27076
+ if (Array.isArray(item)) {
27077
+ for (const el of item)
27078
+ next.push(el);
27079
+ }
27080
+ else if (item && typeof item === "object") {
27081
+ for (const k of Object.keys(item))
27082
+ next.push(item[k]);
27083
+ }
27084
+ else
27085
+ return undefined;
27086
+ }
27087
+ else if (op.kind === "length") {
27088
+ if (item === null || item === undefined)
27089
+ next.push(0);
27090
+ else if (typeof item === "string" || Array.isArray(item))
27091
+ next.push(item.length);
27092
+ else if (typeof item === "object")
27093
+ next.push(Object.keys(item).length);
27094
+ else
27095
+ return undefined;
27096
+ }
27097
+ else if (op.kind === "keys") {
27098
+ if (Array.isArray(item))
27099
+ next.push(item.map((_, idx) => idx));
27100
+ else if (item && typeof item === "object")
27101
+ next.push(Object.keys(item).sort());
27102
+ else
27103
+ return undefined;
27104
+ }
27105
+ }
27106
+ current = next;
27107
+ }
27108
+ }
27109
+ return current.map(val => (rawOut && typeof val === "string" ? val : JSON.stringify(val ?? null)));
27110
+ }
27111
+ parseSyncCutSpec(spec) {
27112
+ if (!/^(?:[1-9][0-9]{0,4}|[1-9][0-9]{0,4}-[1-9][0-9]{0,4}|[1-9][0-9]{0,4}-|-[1-9][0-9]{0,4})(?:,(?:[1-9][0-9]{0,4}|[1-9][0-9]{0,4}-[1-9][0-9]{0,4}|[1-9][0-9]{0,4}-|-[1-9][0-9]{0,4}))*$/.test(spec)) {
27113
+ return undefined;
27114
+ }
27115
+ const ranges = [];
27116
+ for (const part of spec.split(",")) {
27117
+ const dash = part.indexOf("-");
27118
+ if (dash === -1) {
27119
+ const n = Number(part);
27120
+ ranges.push([n, n]);
27121
+ }
27122
+ else if (dash === 0) {
27123
+ const end = Number(part.slice(1));
27124
+ ranges.push([1, end]);
27125
+ }
27126
+ else if (dash === part.length - 1) {
27127
+ const start = Number(part.slice(0, -1));
27128
+ ranges.push([start, Infinity]);
27129
+ }
27130
+ else {
27131
+ const start = Number(part.slice(0, dash));
27132
+ const end = Number(part.slice(dash + 1));
27133
+ if (start > end)
27134
+ return undefined;
27135
+ ranges.push([start, end]);
27136
+ }
27137
+ }
27138
+ return (len) => {
27139
+ const out = [];
27140
+ for (let i = 1; i <= len; i++) {
27141
+ for (let r = 0; r < ranges.length; r++) {
27142
+ const [s, e] = ranges[r];
27143
+ if (i >= s && i <= e) {
27144
+ out.push(i - 1);
27145
+ break;
27146
+ }
27147
+ }
27148
+ }
27149
+ return out;
27150
+ };
27151
+ }
27152
+ evalSyncCut(rawLines, opArgs, byteLocaleMode) {
27153
+ const norm = [];
27154
+ let suppressNoDelim = false;
27155
+ for (let ci = 0; ci < opArgs.length; ci++) {
27156
+ const ca = opArgs[ci];
27157
+ if (ca === "-s" || ca === "--only-delimited") {
27158
+ suppressNoDelim = true;
27159
+ }
27160
+ else if (ca.startsWith("-sd") || ca.startsWith("-ds")) {
27161
+ suppressNoDelim = true;
27162
+ const rest = ca.slice(3);
27163
+ if (rest.length > 0)
27164
+ norm.push("-d" + rest);
27165
+ else if (ci + 1 < opArgs.length)
27166
+ norm.push("-d" + opArgs[++ci]);
27167
+ else
27168
+ return undefined;
27169
+ }
27170
+ else if ((ca === "-d" || ca === "-f" || ca === "-c") && ci + 1 < opArgs.length) {
27171
+ norm.push(ca + opArgs[++ci]);
27172
+ }
27173
+ else {
27174
+ norm.push(ca);
27175
+ }
27176
+ }
27177
+ if (!suppressNoDelim && norm.length === 1 && norm[0].startsWith("-c")) {
27178
+ if (byteLocaleMode)
27179
+ return undefined;
27180
+ const picker = this.parseSyncCutSpec(norm[0].slice(2));
27181
+ if (!picker)
27182
+ return undefined;
27183
+ return rawLines.map(l => {
27184
+ const chars = Array.from(l);
27185
+ const idxs = picker(chars.length);
27186
+ return idxs.map(i => chars[i]).join("");
27187
+ });
27188
+ }
27189
+ let delim = "\t";
27190
+ let fArg;
27191
+ if (norm.length === 1 && norm[0].startsWith("-f")) {
27192
+ fArg = norm[0];
27193
+ }
27194
+ else if (norm.length === 2) {
27195
+ const dCandidate = norm[0].startsWith("-d") ? norm[0] : (norm[1].startsWith("-d") ? norm[1] : undefined);
27196
+ const fCandidate = norm[0].startsWith("-f") ? norm[0] : (norm[1].startsWith("-f") ? norm[1] : undefined);
27197
+ if (dCandidate && dCandidate.length === 3 && fCandidate) {
27198
+ delim = dCandidate[2];
27199
+ fArg = fCandidate;
27200
+ }
27201
+ }
27202
+ if (!fArg || !fArg.startsWith("-f"))
27203
+ return undefined;
27204
+ const picker = this.parseSyncCutSpec(fArg.slice(2));
27205
+ if (!picker)
27206
+ return undefined;
27207
+ const out = [];
27208
+ for (let li = 0; li < rawLines.length; li++) {
27209
+ const l = rawLines[li];
27210
+ if (!l.includes(delim)) {
27211
+ if (!suppressNoDelim)
27212
+ out.push(l);
27213
+ continue;
27214
+ }
27215
+ const parts = l.split(delim);
27216
+ const idxs = picker(parts.length);
27217
+ out.push(idxs.map(i => parts[i]).join(delim));
27218
+ }
27219
+ return out;
27220
+ }
27221
+ evalSyncSed(rawLines, opArgs) {
27222
+ let quiet = false;
27223
+ let expr;
27224
+ if (opArgs.length === 1 && !opArgs[0].startsWith("-")) {
27225
+ expr = opArgs[0];
27226
+ }
27227
+ else if (opArgs.length === 2 && opArgs[0] === "-e") {
27228
+ expr = opArgs[1];
27229
+ }
27230
+ else if (opArgs.length === 2 && opArgs[0] === "-n") {
27231
+ quiet = true;
27232
+ expr = opArgs[1];
27233
+ }
27234
+ else if (opArgs.length === 3 && ((opArgs[0] === "-n" && opArgs[1] === "-e") || (opArgs[0] === "-e" && opArgs[1] === "-n"))) {
27235
+ quiet = true;
27236
+ expr = opArgs[2];
27237
+ }
27238
+ if (!expr)
27239
+ return undefined;
27240
+ const addrCmdM = /^(?:([1-9][0-9]{0,4}|\$)(?:,([1-9][0-9]{0,4}|\$))?|\/(\^?[a-zA-Z0-9_ :;,.-]+\$?)\/)([dp])$/.exec(expr);
27241
+ if (addrCmdM) {
27242
+ const cmdChar = addrCmdM[4];
27243
+ if ((quiet && cmdChar !== "p") || (!quiet && cmdChar !== "d"))
27244
+ return undefined;
27245
+ const startSpec = addrCmdM[1];
27246
+ const endSpec = addrCmdM[2];
27247
+ const patSpec = addrCmdM[3];
27248
+ const matchLine = (l, idx1, total) => {
27249
+ if (patSpec !== undefined) {
27250
+ const aStart = patSpec.startsWith("^");
27251
+ const c1 = aStart ? patSpec.slice(1) : patSpec;
27252
+ const aEnd = c1.endsWith("$");
27253
+ const core = aEnd ? c1.slice(0, -1) : c1;
27254
+ if (aStart && aEnd)
27255
+ return l === core;
27256
+ if (aStart)
27257
+ return l.startsWith(core);
27258
+ if (aEnd)
27259
+ return l.endsWith(core);
27260
+ return l.includes(core);
27261
+ }
27262
+ const sNum = startSpec === "$" ? total : Number(startSpec);
27263
+ if (endSpec === undefined)
27264
+ return idx1 === sNum;
27265
+ const eNum = endSpec === "$" ? total : Number(endSpec);
27266
+ return idx1 >= sNum && idx1 <= eNum;
27267
+ };
27268
+ const out = [];
27269
+ for (let i = 0; i < rawLines.length; i++) {
27270
+ const hit = matchLine(rawLines[i], i + 1, rawLines.length);
27271
+ if (cmdChar === "p" && hit)
27272
+ out.push(rawLines[i]);
27273
+ else if (cmdChar === "d" && !hit)
27274
+ out.push(rawLines[i]);
27275
+ }
27276
+ return out;
27277
+ }
27278
+ if (quiet)
27279
+ return undefined;
27280
+ if (!expr.startsWith("s") || expr.length < 4)
27281
+ return undefined;
27282
+ const delim = expr[1];
27283
+ if (!"/#|:@,;%!".includes(delim))
27284
+ return undefined;
27285
+ const parts = expr.slice(2).split(delim);
27286
+ if (parts.length !== 3)
27287
+ return undefined;
27288
+ const [pat, rep, flags] = parts;
27289
+ if ((flags !== "" && flags !== "g") || /[&\\\n]/.test(rep))
27290
+ return undefined;
27291
+ const anchorStart = pat.startsWith("^");
27292
+ const core1 = anchorStart ? pat.slice(1) : pat;
27293
+ const anchorEnd = core1.endsWith("$");
27294
+ const core = anchorEnd ? core1.slice(0, -1) : core1;
27295
+ if (core.length === 0 || !/^[a-zA-Z0-9_ :;,/-]+$/.test(core))
27296
+ return undefined;
27297
+ const isGlobal = flags === "g";
27298
+ return rawLines.map(l => {
27299
+ if (anchorStart && anchorEnd)
27300
+ return l === core ? rep : l;
27301
+ if (anchorStart)
27302
+ return l.startsWith(core) ? rep + l.slice(core.length) : l;
27303
+ if (anchorEnd)
27304
+ return l.endsWith(core) ? l.slice(0, l.length - core.length) + rep : l;
27305
+ return isGlobal ? l.split(core).join(rep) : l.replace(core, () => rep);
27306
+ });
27307
+ }
27308
+ evalSyncGrep(rawLines, opArgs, errexit) {
27309
+ if (errexit || (opArgs.length !== 1 && opArgs.length !== 2))
27310
+ return undefined;
27311
+ const mode = opArgs.length === 2 ? opArgs[0] : "";
27312
+ const pat = opArgs[opArgs.length - 1];
27313
+ if (pat.startsWith("-"))
27314
+ return undefined;
27315
+ if (opArgs.length === 2 && mode !== "-v" && mode !== "-i" && mode !== "-c" && mode !== "-F" && mode !== "-E" && mode !== "-Ei" && mode !== "-iE" && mode !== "-Ev" && mode !== "-vE") {
27316
+ return undefined;
27317
+ }
27318
+ const isCaseInsensitive = mode.includes("i");
27319
+ const isInvert = mode.includes("v");
27320
+ const isCount = mode === "-c";
27321
+ const isExtended = mode.includes("E");
27322
+ const isFixed = mode.includes("F");
27323
+ const branches = isExtended ? pat.split("|") : [pat];
27324
+ const compiledBranches = [];
27325
+ for (const br of branches) {
27326
+ if (br.length === 0)
27327
+ return undefined;
27328
+ const anchorStart = !isFixed && br.startsWith("^");
27329
+ const c1 = anchorStart ? br.slice(1) : br;
27330
+ const anchorEnd = !isFixed && c1.endsWith("$");
27331
+ const core = anchorEnd ? c1.slice(0, -1) : c1;
27332
+ if (core.length === 0 || !/^[a-zA-Z0-9_ :;,/-]+$/.test(core))
27333
+ return undefined;
27334
+ compiledBranches.push({ anchorStart, anchorEnd, needle: isCaseInsensitive ? core.toLowerCase() : core });
27335
+ }
27336
+ const matched = [];
27337
+ for (let li = 0; li < rawLines.length; li++) {
27338
+ const l = rawLines[li];
27339
+ const hay = isCaseInsensitive ? l.toLowerCase() : l;
27340
+ let hit = false;
27341
+ for (let bi = 0; bi < compiledBranches.length; bi++) {
27342
+ const b = compiledBranches[bi];
27343
+ const ok = b.anchorStart && b.anchorEnd
27344
+ ? hay === b.needle
27345
+ : b.anchorStart
27346
+ ? hay.startsWith(b.needle)
27347
+ : b.anchorEnd
27348
+ ? hay.endsWith(b.needle)
27349
+ : hay.includes(b.needle);
27350
+ if (ok) {
27351
+ hit = true;
27352
+ break;
27353
+ }
27354
+ }
27355
+ if (isInvert ? !hit : hit)
27356
+ matched.push(l);
27357
+ }
27358
+ return {
27359
+ lines: isCount ? [String(matched.length)] : matched,
27360
+ status: matched.length > 0 ? 0 : 1,
27361
+ };
27362
+ }
27363
+ evalSyncAwk(rawLines, opArgs) {
27364
+ if (opArgs.length < 1 || opArgs.length > 3)
27365
+ return undefined;
27366
+ const norm = opArgs.length === 3 && opArgs[0] === "-F" ? ["-F" + opArgs[1], opArgs[2]] : opArgs;
27367
+ let awkSep;
27368
+ let awkProg;
27369
+ if (norm.length === 1)
27370
+ awkProg = norm[0];
27371
+ else if (norm.length === 2 && norm[0].length === 3 && norm[0].startsWith("-F") && norm[0][2] !== "\\") {
27372
+ awkSep = norm[0].slice(2);
27373
+ awkProg = norm[1];
27374
+ }
27375
+ if (!awkProg)
27376
+ return undefined;
27377
+ const awkM = /^\s*\{\s*print(?:\s+((?:\$(?:[0-9]+|NF)|"[^"$\\]*")(?:\s*,?\s*(?:\$(?:[0-9]+|NF)|"[^"$\\]*"))*))?\s*;?\s*\}\s*$/.exec(awkProg);
27378
+ if (!awkM)
27379
+ return undefined;
27380
+ const exprBody = awkM[1]?.trim();
27381
+ const parts = [];
27382
+ if (!exprBody) {
27383
+ parts.push({ kind: "field", token: "0" });
27384
+ }
27385
+ else {
27386
+ const tokenRe = /\$([0-9]+|NF)|"([^"$\\]*)"|(,)/g;
27387
+ let m;
27388
+ while ((m = tokenRe.exec(exprBody)) !== null) {
27389
+ if (m[1] !== undefined)
27390
+ parts.push({ kind: "field", token: m[1] });
27391
+ else if (m[2] !== undefined)
27392
+ parts.push({ kind: "lit", text: m[2] });
27393
+ else if (m[3] !== undefined)
27394
+ parts.push({ kind: "lit", text: " " });
27395
+ }
27396
+ }
27397
+ return rawLines.map(l => {
27398
+ if (parts.length === 1 && parts[0].kind === "field" && parts[0].token === "0")
27399
+ return l;
27400
+ const fields = awkSep !== undefined
27401
+ ? (awkSep === " " ? l.trim().split(/\s+/).filter(Boolean) : (l.length === 0 ? [] : l.split(awkSep)))
27402
+ : l.trim().split(/\s+/).filter(Boolean);
27403
+ let out = "";
27404
+ for (let pi = 0; pi < parts.length; pi++) {
27405
+ const p = parts[pi];
27406
+ if (p.kind === "lit")
27407
+ out += p.text;
27408
+ else if (p.token === "0")
27409
+ out += l;
27410
+ else {
27411
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
27412
+ out += idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
27413
+ }
27414
+ }
27415
+ return out;
27416
+ });
27417
+ }
27418
+ formatSyncTransformQ(val) {
27419
+ let ansi = false;
27420
+ for (let i = 0; i < val.length; i++) {
27421
+ const c = val.charCodeAt(i);
27422
+ if (c < 32 || c === 127) {
27423
+ ansi = true;
27424
+ break;
27425
+ }
27426
+ if (c >= 128) {
27427
+ const cp = val.codePointAt(i);
27428
+ if (/[\p{Cc}\p{Cf}\p{Cn}]/u.test(String.fromCodePoint(cp))) {
27429
+ ansi = true;
27430
+ break;
27431
+ }
27432
+ if (cp > 0xffff)
27433
+ i++;
27434
+ }
27435
+ }
27436
+ if (!ansi) {
27437
+ return "'" + (val.includes("'") ? val.split("'").join("'\\''") : val) + "'";
27438
+ }
27439
+ const escMap = { 7: "\\a", 8: "\\b", 9: "\\t", 10: "\\n", 11: "\\v", 12: "\\f", 13: "\\r", 27: "\\E" };
27440
+ let out = "$'";
27441
+ for (const ch of val) {
27442
+ const cp = ch.codePointAt(0);
27443
+ if (escMap[cp] !== undefined)
27444
+ out += escMap[cp];
27445
+ else if (cp === 39)
27446
+ out += "\\'";
27447
+ else if (cp === 92)
27448
+ out += "\\\\";
27449
+ else if ((cp >= 32 && cp < 127) || (cp >= 128 && !/[\p{Cc}\p{Cf}\p{Cn}]/u.test(ch)))
27450
+ out += ch;
27451
+ else {
27452
+ const bytes = fastSharedTextEncoder.encode(ch);
27453
+ for (let bi = 0; bi < bytes.length; bi++)
27454
+ out += "\\" + bytes[bi].toString(8).padStart(3, "0");
27455
+ }
27456
+ }
27457
+ return out + "'";
27458
+ }
27459
+ sortSyncLines(rawLines, flag) {
27460
+ const rev = flag !== undefined && flag.includes("r");
27461
+ const num = flag !== undefined && flag.includes("n");
27462
+ const uniq = flag !== undefined && flag.includes("u");
27463
+ const parseNum = (s) => {
27464
+ const m = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(s);
27465
+ return m ? Number(m[1]) : 0;
27466
+ };
27467
+ const sorted = [...rawLines].sort((a, b) => {
27468
+ if (num) {
27469
+ const na = parseNum(a);
27470
+ const nb = parseNum(b);
27471
+ if (na !== nb)
27472
+ return rev ? nb - na : na - nb;
27473
+ }
27474
+ return a < b ? (rev ? 1 : -1) : a > b ? (rev ? -1 : 1) : 0;
27475
+ });
27476
+ if (!uniq)
27477
+ return sorted;
27478
+ const dedup = [];
27479
+ for (let i = 0; i < sorted.length; i++) {
27480
+ if (i === 0 || sorted[i] !== sorted[i - 1])
27481
+ dedup.push(sorted[i]);
27482
+ }
27483
+ return dedup;
27484
+ }
27485
+ translateSyncTr(input, set1Spec, set2Spec) {
27486
+ if (set1Spec === "a-z" && set2Spec === "A-Z")
27487
+ return input.toUpperCase();
27488
+ if (set1Spec === "A-Z" && set2Spec === "a-z")
27489
+ return input.toLowerCase();
27490
+ const s1 = this.expandSyncTrCharArray(set1Spec);
27491
+ const s2 = this.expandSyncTrCharArray(set2Spec);
27492
+ if (!s1 || !s2 || s1.length === 0 || s2.length === 0)
27493
+ return undefined;
27494
+ if (s1.length === 1 && s2.length === 1)
27495
+ return input.split(s1[0]).join(s2[0]);
27496
+ const map = new Map();
27497
+ for (let i = 0; i < s1.length; i++) {
27498
+ map.set(s1[i], s2[Math.min(i, s2.length - 1)]);
27499
+ }
27500
+ let out = "";
27501
+ for (let i = 0; i < input.length; i++) {
27502
+ const ch = input[i];
27503
+ out += map.get(ch) ?? ch;
27504
+ }
27505
+ return out;
27506
+ }
27507
+ expandSyncTrCharSet(spec) {
27508
+ const out = new Set();
27509
+ for (let i = 0; i < spec.length; i++) {
27510
+ if (i + 2 < spec.length && spec[i + 1] === "-") {
27511
+ const start = spec.charCodeAt(i);
27512
+ const end = spec.charCodeAt(i + 2);
26596
27513
  if (start <= end && end - start <= 256) {
26597
27514
  for (let c = start; c <= end; c++)
26598
27515
  out.add(String.fromCharCode(c));
@@ -26779,35 +27696,11 @@ export class Runtime {
26779
27696
  sArgs.push(v);
26780
27697
  }
26781
27698
  if (sName === "cut") {
26782
- const normCut = [];
26783
- for (let ci = 0; ci < sArgs.length; ci++) {
26784
- const ca = sArgs[ci];
26785
- if ((ca === "-d" || ca === "-f") && ci + 1 < sArgs.length) {
26786
- normCut.push(ca + sArgs[++ci]);
26787
- }
26788
- else {
26789
- normCut.push(ca);
26790
- }
26791
- }
26792
- sArgs.length = 0;
26793
- for (let ci = 0; ci < normCut.length; ci++)
26794
- sArgs.push(normCut[ci]);
26795
- const validCut2 = sArgs.length === 2 && ((sArgs[0].length === 3 && sArgs[0].startsWith("-d") && sArgs[1].length >= 3 && sArgs[1].startsWith("-f")) || (sArgs[0] === "-c" && /^[1-9][0-9]*-[1-9][0-9]*$/.test(sArgs[1])));
26796
- const validCut1 = sArgs.length === 1 && sArgs[0].length >= 3 && sArgs[0].startsWith("-f");
26797
- if (!validCut2 && !validCut1)
27699
+ if (this.evalSyncCut([], sArgs, byteLocale(rawState.variables)) === undefined)
26798
27700
  return undefined;
26799
- if (sArgs[0] === "-c") {
26800
- if (byteLocale(rawState.variables))
26801
- return undefined;
26802
- const [start, end] = sArgs[1].split("-").map(Number);
26803
- if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start > end)
26804
- return undefined;
26805
- }
26806
27701
  }
26807
27702
  else if (sName === "tr") {
26808
- const validTr2 = sArgs.length === 2 && ((!sArgs[0].startsWith("-") && !sArgs[1].startsWith("-")) || ((sArgs[0] === "-d" || sArgs[0] === "-s") && !sArgs[1].startsWith("-")));
26809
- const validTr3 = sArgs.length === 3 && (sArgs[0] === "-ds" || sArgs[0] === "-sd" || sArgs[0] === "-s") && !sArgs[1].startsWith("-") && !sArgs[2].startsWith("-");
26810
- if (!validTr2 && !validTr3)
27703
+ if (this.evalSyncTr("", sArgs) === undefined)
26811
27704
  return undefined;
26812
27705
  }
26813
27706
  else if (sName === "uniq") {
@@ -26815,16 +27708,20 @@ export class Runtime {
26815
27708
  return undefined;
26816
27709
  }
26817
27710
  else if (sName === "sort") {
26818
- if (sArgs.length > 1 || (sArgs.length === 1 && sArgs[0] !== "-r"))
27711
+ if (sArgs.length > 1 || (sArgs.length === 1 && sArgs[0] !== "-r" && sArgs[0] !== "-u" && sArgs[0] !== "-n" && sArgs[0] !== "-rn" && sArgs[0] !== "-nr" && sArgs[0] !== "-ru" && sArgs[0] !== "-ur"))
26819
27712
  return undefined;
26820
27713
  }
26821
27714
  else if (sName === "head" || sName === "tail") {
26822
- if (sArgs.length === 1 && /^-(?:n)?[0-9]+$/.test(sArgs[0])) {
26823
- const numStr = sArgs[0].startsWith("-n") ? sArgs[0].slice(2) : sArgs[0].slice(1);
27715
+ if (sArgs.length === 0) {
27716
+ sArgs.push("-n", "10");
27717
+ }
27718
+ else if (sArgs.length === 1 && /^-(?:n|c)?[0-9]+$/.test(sArgs[0])) {
27719
+ const flag = sArgs[0].startsWith("-c") ? "-c" : "-n";
27720
+ const numStr = (sArgs[0].startsWith("-n") || sArgs[0].startsWith("-c")) ? sArgs[0].slice(2) : sArgs[0].slice(1);
26824
27721
  sArgs.length = 0;
26825
- sArgs.push("-n", numStr);
27722
+ sArgs.push(flag, numStr);
26826
27723
  }
26827
- if (sArgs.length !== 2 || sArgs[0] !== "-n" || !/^[0-9]+$/.test(sArgs[1]))
27724
+ if (sArgs.length !== 2 || (sArgs[0] !== "-n" && sArgs[0] !== "-c") || !/^[0-9]+$/.test(sArgs[1]))
26828
27725
  return undefined;
26829
27726
  }
26830
27727
  else if (sName === "wc") {
@@ -26832,9 +27729,7 @@ export class Runtime {
26832
27729
  return undefined;
26833
27730
  }
26834
27731
  else if (sName === "sed") {
26835
- const validSed1 = sArgs.length === 1 && sArgs[0].startsWith("s");
26836
- const validSed2 = sArgs.length === 2 && sArgs[0] === "-e" && sArgs[1].startsWith("s");
26837
- if (!validSed1 && !validSed2)
27732
+ if (this.evalSyncSed([], sArgs) === undefined)
26838
27733
  return undefined;
26839
27734
  }
26840
27735
  else if (sName === "rev") {
@@ -26842,23 +27737,15 @@ export class Runtime {
26842
27737
  return undefined;
26843
27738
  }
26844
27739
  else if (sName === "awk") {
26845
- if (sArgs.length === 3 && sArgs[0] === "-F") {
26846
- sArgs.splice(0, 2, "-F" + sArgs[1]);
26847
- }
26848
- const awkSepOk = sArgs.length === 1 || (sArgs.length === 2 && sArgs[0].length === 3 && sArgs[0].startsWith("-F") && sArgs[0][2] !== "\\");
26849
- const awkProg = sArgs[sArgs.length - 1] ?? "";
26850
- if (!awkSepOk || !/^\s*\{\s*print(?:\s+(\$(?:[0-9]+|NF)(?:\s*,\s*\$(?:[0-9]+|NF))*))?\s*;?\s*\}\s*$/.test(awkProg))
27740
+ if (this.evalSyncAwk([], sArgs) === undefined)
26851
27741
  return undefined;
26852
27742
  }
26853
27743
  else if (sName === "grep") {
26854
- const grepOk = (sArgs.length === 1 && !sArgs[0].startsWith("-") && /^[a-zA-Z0-9_ :;,/-]+$/.test(sArgs[0])) ||
26855
- (sArgs.length === 2 && (sArgs[0] === "-v" || sArgs[0] === "-i" || sArgs[0] === "-c" || sArgs[0] === "-F") && /^[a-zA-Z0-9_ :;,/-]+$/.test(sArgs[1]));
26856
- if (!grepOk)
27744
+ if (this.evalSyncGrep([], sArgs, Boolean(rawState.errexit)) === undefined)
26857
27745
  return undefined;
26858
27746
  }
26859
27747
  else if (sName === "jq") {
26860
- const jqFilter = sArgs.length === 1 ? sArgs[0] : (sArgs.length === 2 && (sArgs[0] === "-r" || sArgs[0] === "--raw-output") ? sArgs[1] : undefined);
26861
- if (!jqFilter || !/^\.[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/.test(jqFilter))
27748
+ if (this.evalSyncJq("null", sArgs) === undefined)
26862
27749
  return undefined;
26863
27750
  }
26864
27751
  else if (sName === "base64") {
@@ -26948,23 +27835,17 @@ export class Runtime {
26948
27835
  const extDef = stageDefs[sIdx];
26949
27836
  const stageArgs = stageArgsList[sIdx];
26950
27837
  const nextBuf = (index & 1) === 0 ? sharedSyncPipeBuf0 : sharedSyncPipeBuf1;
26951
- const isInlineCutField = firstName === "cut" && ((stageArgs.length === 1 && /^-[f][1-9][0-9]*(?:,[1-9][0-9]*)*$/.test(stageArgs[0])) ||
26952
- (stageArgs.length === 2 && stageArgs[0].length === 3 && stageArgs[0].startsWith("-d") && /^-[f][1-9][0-9]*(?:,[1-9][0-9]*)*$/.test(stageArgs[1])));
26953
- const isInlineTr = firstName === "tr" && stageArgs.length === 2 && ((stageArgs[0] === "a-z" && stageArgs[1] === "A-Z") ||
26954
- (stageArgs[0] === "A-Z" && stageArgs[1] === "a-z") ||
26955
- (stageArgs[0] === "-d" && /^[a-zA-Z0-9_ :;,./-]+$/.test(stageArgs[1])) ||
26956
- (stageArgs[0] === "-s" && stageArgs[1].length === 1));
27838
+ const isInlineCutField = firstName === "cut";
27839
+ const isInlineTr = firstName === "tr";
26957
27840
  const isInlineAwk = firstName === "awk";
26958
27841
  const isInlineGrep = firstName === "grep";
26959
27842
  const isInlineJq = firstName === "jq";
26960
27843
  const isInlineBase64 = firstName === "base64";
26961
27844
  const isInlineTac = firstName === "tac";
26962
27845
  const isInlineNl = firstName === "nl";
26963
- const isInlineSort = firstName === "sort" && !byteLocale(rawState.variables) && (stageArgs.length === 0 || (stageArgs.length === 1 && stageArgs[0] === "-r"));
26964
- const isInlineUniq = firstName === "uniq" && (stageArgs.length === 0 || (stageArgs.length === 1 && (stageArgs[0] === "-d" || stageArgs[0] === "-u")));
26965
- const inlineSedMatch = firstName === "sed"
26966
- ? /^s\/([a-zA-Z0-9_ :;,-]+)\/([a-zA-Z0-9_ :;,./-]*)\/(g?)$/.exec(stageArgs.length === 1 ? stageArgs[0] : (stageArgs.length === 2 && stageArgs[0] === "-e" ? stageArgs[1] : ""))
26967
- : null;
27846
+ const isInlineSort = firstName === "sort" && !byteLocale(rawState.variables) && (stageArgs.length === 0 || (stageArgs.length === 1 && (stageArgs[0] === "-r" || stageArgs[0] === "-u" || stageArgs[0] === "-n" || stageArgs[0] === "-rn" || stageArgs[0] === "-nr" || stageArgs[0] === "-ru" || stageArgs[0] === "-ur")));
27847
+ const isInlineUniq = firstName === "uniq" && (stageArgs.length === 0 || (stageArgs.length === 1 && (stageArgs[0] === "-d" || stageArgs[0] === "-u" || stageArgs[0] === "-c")));
27848
+ const inlineSedMatch = firstName === "sed";
26968
27849
  if (firstName === "rev" ||
26969
27850
  firstName === "head" ||
26970
27851
  firstName === "tail" ||
@@ -26974,7 +27855,7 @@ export class Runtime {
26974
27855
  isInlineTr ||
26975
27856
  isInlineSort ||
26976
27857
  isInlineUniq ||
26977
- inlineSedMatch !== null ||
27858
+ inlineSedMatch ||
26978
27859
  isInlineAwk ||
26979
27860
  isInlineGrep ||
26980
27861
  isInlineJq ||
@@ -26987,16 +27868,22 @@ export class Runtime {
26987
27868
  if (firstName === "rev") {
26988
27869
  outLines = rawLines.map(l => Array.from(l).reverse().join(""));
26989
27870
  }
26990
- else if (firstName === "head") {
26991
- outLines = rawLines.slice(0, Number(stageArgs[1]));
26992
- }
26993
- else if (firstName === "tail") {
26994
- const nTail = Number(stageArgs[1]);
26995
- outLines = nTail === 0 ? [] : rawLines.slice(-nTail);
26996
- }
26997
- else if (firstName === "cut" && stageArgs[0] === "-c") {
26998
- const [cStart, cEnd] = stageArgs[1].split("-").map(Number);
26999
- outLines = rawLines.map(l => Array.from(l).slice(cStart - 1, cEnd).join(""));
27871
+ else if (firstName === "head" || firstName === "tail") {
27872
+ const count = Number(stageArgs[1]);
27873
+ if (stageArgs[0] === "-c") {
27874
+ const sub = firstName === "head"
27875
+ ? prevBuf.subarray(0, Math.min(prevLen, count))
27876
+ : (count === 0 ? prevBuf.subarray(0, 0) : prevBuf.subarray(Math.max(0, prevLen - count), prevLen));
27877
+ const nextTotalBytes = this.budget.bytes + sub.byteLength;
27878
+ if (nextTotalBytes > this.budget.maxOutputBytesSmi && sub.byteLength > this.budget.limits.maxOutputBytes - this.budget.bytes)
27879
+ this.budget.fail("maxOutputBytes");
27880
+ this.budget.bytes = nextTotalBytes;
27881
+ nextBuf.set(sub, 0);
27882
+ prevBuf = nextBuf;
27883
+ prevLen = sub.byteLength;
27884
+ continue;
27885
+ }
27886
+ outLines = firstName === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
27000
27887
  }
27001
27888
  else if (firstName === "wc") {
27002
27889
  if (stageArgs[0] === "-l") {
@@ -27015,60 +27902,16 @@ export class Runtime {
27015
27902
  }
27016
27903
  }
27017
27904
  else if (isInlineCutField) {
27018
- const delim = stageArgs.length === 2 ? stageArgs[0][2] : "\t";
27019
- const fSpec = (stageArgs.length === 2 ? stageArgs[1] : stageArgs[0]).slice(2);
27020
- const fIndices = Array.from(new Set(fSpec.split(",").map(Number))).sort((a, b) => a - b);
27021
- outLines = rawLines.map(l => {
27022
- if (!l.includes(delim))
27023
- return l;
27024
- const parts = l.split(delim);
27025
- const picked = [];
27026
- for (let fi = 0; fi < fIndices.length; fi++) {
27027
- const idx = fIndices[fi] - 1;
27028
- if (idx >= 0 && idx < parts.length)
27029
- picked.push(parts[idx]);
27030
- }
27031
- return picked.join(delim);
27032
- });
27905
+ const cutRes = this.evalSyncCut(rawLines, stageArgs, byteLocale(rawState.variables));
27906
+ if (cutRes === undefined)
27907
+ return undefined;
27908
+ outLines = cutRes;
27033
27909
  }
27034
27910
  else if (isInlineTr) {
27035
- let transformed = inStr;
27036
- if (stageArgs[0] === "a-z" && stageArgs[1] === "A-Z") {
27037
- transformed = inStr.replace(/[a-z]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 32));
27038
- }
27039
- else if (stageArgs[0] === "A-Z" && stageArgs[1] === "a-z") {
27040
- transformed = inStr.replace(/[A-Z]/g, ch => String.fromCharCode(ch.charCodeAt(0) + 32));
27041
- }
27042
- else if (stageArgs[0] === "-s") {
27043
- const sq = stageArgs[1];
27044
- let out = "";
27045
- let prevSq = false;
27046
- for (let ci = 0; ci < inStr.length; ci++) {
27047
- const ch = inStr[ci];
27048
- if (ch === sq) {
27049
- if (!prevSq) {
27050
- out += ch;
27051
- prevSq = true;
27052
- }
27053
- }
27054
- else {
27055
- out += ch;
27056
- prevSq = false;
27057
- }
27058
- }
27059
- transformed = out;
27060
- }
27061
- else if (stageArgs[0] === "-d") {
27062
- const delSet = this.expandSyncTrCharSet(stageArgs[1]);
27063
- let out = "";
27064
- for (let ci = 0; ci < inStr.length; ci++) {
27065
- const ch = inStr[ci];
27066
- if (!delSet.has(ch))
27067
- out += ch;
27068
- }
27069
- transformed = out;
27070
- }
27071
- const outByteLen = transformed.length * 3 > 127 ? shellValueByteLength(transformed) : transformed.length;
27911
+ const transformed = this.evalSyncTr(inStr, stageArgs);
27912
+ if (transformed === undefined)
27913
+ return undefined;
27914
+ const outByteLen = shellValueByteLength(transformed);
27072
27915
  if (outByteLen > nextBuf.byteLength)
27073
27916
  return undefined;
27074
27917
  const nextTotalBytes = this.budget.bytes + outByteLen;
@@ -27081,8 +27924,7 @@ export class Runtime {
27081
27924
  continue;
27082
27925
  }
27083
27926
  else if (isInlineSort) {
27084
- const revSort = stageArgs[0] === "-r";
27085
- outLines = [...rawLines].sort((a, b) => (a < b ? (revSort ? 1 : -1) : a > b ? (revSort ? -1 : 1) : 0));
27927
+ outLines = this.sortSyncLines(rawLines, stageArgs[0]);
27086
27928
  }
27087
27929
  else if (isInlineUniq) {
27088
27930
  const mode = stageArgs[0];
@@ -27094,69 +27936,31 @@ export class Runtime {
27094
27936
  uEnd++;
27095
27937
  const count = uEnd - uIdx;
27096
27938
  if (mode === "-d" ? count > 1 : mode === "-u" ? count === 1 : true) {
27097
- outLines.push(rawLines[uIdx]);
27939
+ outLines.push(mode === "-c" ? `${String(count).padStart(7, " ")} ${rawLines[uIdx]}` : rawLines[uIdx]);
27098
27940
  }
27099
27941
  uIdx = uEnd;
27100
27942
  }
27101
27943
  }
27102
- else if (inlineSedMatch !== null) {
27103
- const pat = inlineSedMatch[1];
27104
- const rep = inlineSedMatch[2];
27105
- const isGlobal = inlineSedMatch[3] === "g";
27106
- outLines = rawLines.map(l => (isGlobal ? l.split(pat).join(rep) : l.replace(pat, () => rep)));
27944
+ else if (inlineSedMatch) {
27945
+ const sedRes = this.evalSyncSed(rawLines, stageArgs);
27946
+ if (sedRes === undefined)
27947
+ return undefined;
27948
+ outLines = sedRes;
27107
27949
  }
27108
27950
  else if (isInlineAwk) {
27109
- const awkSep = stageArgs.length === 2 ? stageArgs[0].slice(2) : undefined;
27110
- const awkProg = stageArgs[stageArgs.length - 1];
27111
- const awkM = /^\s*\{\s*print(?:\s+(\$(?:[0-9]+|NF)(?:\s*,\s*\$(?:[0-9]+|NF))*))?\s*;?\s*\}\s*$/.exec(awkProg);
27112
- if (!awkM)
27951
+ const awkRes = this.evalSyncAwk(rawLines, stageArgs);
27952
+ if (awkRes === undefined)
27113
27953
  return undefined;
27114
- const toks = awkM[1] ? awkM[1].split(",").map(t => t.trim().slice(1)) : ["0"];
27115
- outLines = [];
27116
- for (let li = 0; li < rawLines.length; li++) {
27117
- const l = rawLines[li];
27118
- if (toks.length === 1 && toks[0] === "0") {
27119
- outLines.push(l);
27120
- continue;
27121
- }
27122
- let fields;
27123
- if (awkSep === undefined || awkSep === " ") {
27124
- const tr = l.replace(/^[ \t]+|[ \t]+$/g, "");
27125
- fields = tr.length === 0 ? [] : tr.split(/[ \t]+/);
27126
- }
27127
- else {
27128
- fields = l.length === 0 ? [] : l.split(awkSep);
27129
- }
27130
- const vals = [];
27131
- for (let ti = 0; ti < toks.length; ti++) {
27132
- const tk = toks[ti];
27133
- if (tk === "0")
27134
- vals.push(l);
27135
- else if (tk === "NF")
27136
- vals.push(fields.length > 0 ? fields[fields.length - 1] : l);
27137
- else {
27138
- const idx = Number(tk);
27139
- vals.push(idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
27140
- }
27141
- }
27142
- outLines.push(vals.join(" "));
27143
- }
27954
+ outLines = awkRes;
27144
27955
  }
27145
27956
  else if (isInlineGrep) {
27146
- const gMode = stageArgs.length === 2 ? stageArgs[0] : "";
27147
- const gPat = stageArgs[stageArgs.length - 1];
27148
- const gPatLower = gMode === "-i" ? gPat.toLowerCase() : gPat;
27149
- const matched = [];
27150
- for (let li = 0; li < rawLines.length; li++) {
27151
- const l = rawLines[li];
27152
- const hit = gMode === "-i" ? l.toLowerCase().includes(gPatLower) : l.includes(gPat);
27153
- if (gMode === "-v" ? !hit : hit)
27154
- matched.push(l);
27155
- }
27156
- stageStatus = matched.length === 0 ? 1 : 0;
27957
+ const grepRes = this.evalSyncGrep(rawLines, stageArgs, Boolean(rawState.errexit));
27958
+ if (grepRes === undefined)
27959
+ return undefined;
27960
+ stageStatus = grepRes.status;
27157
27961
  if (stageStatus !== 0)
27158
27962
  failureStatus = stageStatus;
27159
- outLines = gMode === "-c" ? [String(matched.length)] : matched;
27963
+ outLines = grepRes.lines;
27160
27964
  }
27161
27965
  else if (isInlineTac) {
27162
27966
  if (inStr.length > 0 && !inStr.endsWith("\n"))
@@ -27199,49 +28003,10 @@ export class Runtime {
27199
28003
  }
27200
28004
  }
27201
28005
  else if (isInlineJq) {
27202
- const rawOut = stageArgs.length === 2;
27203
- const filter = stageArgs[stageArgs.length - 1];
27204
- const trimmedJson = inStr.trim();
27205
- if (!trimmedJson.startsWith("{") || trimmedJson.includes("\n"))
27206
- return undefined;
27207
- let parsedObj;
27208
- try {
27209
- parsedObj = JSON.parse(trimmedJson);
27210
- }
27211
- catch {
27212
- return undefined;
27213
- }
27214
- if (!parsedObj || typeof parsedObj !== "object" || Array.isArray(parsedObj))
27215
- return undefined;
27216
- const keys = filter.slice(1).split(".");
27217
- let cur = parsedObj;
27218
- for (let ki = 0; ki < keys.length; ki++) {
27219
- if (cur === null || cur === undefined || typeof cur !== "object") {
27220
- cur = undefined;
27221
- break;
27222
- }
27223
- if (Array.isArray(cur))
27224
- return undefined;
27225
- cur = Object.hasOwn(cur, keys[ki]) ? cur[keys[ki]] : undefined;
27226
- }
27227
- if (cur !== null && cur !== undefined && typeof cur === "object")
28006
+ const jqRes = this.evalSyncJq(inStr.trim(), stageArgs);
28007
+ if (jqRes === undefined)
27228
28008
  return undefined;
27229
- if (rawOut) {
27230
- if (typeof cur === "string") {
27231
- if (cur.includes("\n"))
27232
- return undefined;
27233
- outLines = [cur];
27234
- }
27235
- else if (typeof cur === "number" || typeof cur === "boolean") {
27236
- outLines = [String(cur)];
27237
- }
27238
- else {
27239
- outLines = ["null"];
27240
- }
27241
- }
27242
- else {
27243
- outLines = [JSON.stringify(cur ?? null)];
27244
- }
28009
+ outLines = jqRes;
27245
28010
  }
27246
28011
  // These filters preserve the terminator of the final selected input line.
27247
28012
  const preservesTerminator = firstName === "head" || firstName === "tail" || firstName === "rev" || firstName === "sed";
@@ -27318,62 +28083,66 @@ export class Runtime {
27318
28083
  if (pipeline.commands.length !== 1)
27319
28084
  return undefined;
27320
28085
  let cmd = pipeline.commands[0];
27321
- if ((cmd.kind === "arithmetic-for" || cmd.kind === "for") && cmd.redirects.length === 0 && !rawState.errexit && !rawState.nounset && !rawState.readonlyVariables?.size) {
28086
+ if ((cmd.kind === "if" || cmd.kind === "case") && this.isPureSyncSubIfOrCase(cmd, rawState)) {
27322
28087
  const monitor = stateMonitor(state);
27323
- if (monitor) {
27324
- const store = arrayStore(rawState);
27325
- const existing = store?.get("PIPESTATUS");
27326
- const elem0 = existing ? existing.values.get(0) : undefined;
27327
- const canMutatePipeStatus = !existing || (!existing.associative && existing.references === 1 && existing.values.size === 1 && existing.maximum === 0 && elem0 !== undefined && elem0.text.references === 1 && elem0.text.bytes === 1 && !store.watches.has("PIPESTATUS") && !monitor.hasOverlay("PIPESTATUS") && !rawState.readonlyVariables?.has("PIPESTATUS"));
27328
- if (canMutatePipeStatus) {
27329
- const capture = new Capture();
27330
- capture.budget = this.budget;
27331
- capture.signal = this.signal;
27332
- const { descriptors: ignoredDescriptors, ...ioRest } = io;
27333
- const subIO = { ...ioRest, stdout: capture };
27334
- if (this.canSyncLoopBody(cmd.body, rawState, subIO)) {
27335
- const { steps: bodySteps } = this.buildSyncLoopBody(cmd, rawState, subIO);
27336
- if (bodySteps.length > 0 && bodySteps.every(s => s.isStdoutEcho === true)) {
27337
- const indName = cmd.kind === "for" ? cmd.name : /^([a-zA-Z_][a-zA-Z_0-9]*)\s*=/.exec(cmd.expressions[0].source.trim())?.[1];
27338
- if (indName && !store?.get(indName)) {
27339
- const hadInd = Object.prototype.hasOwnProperty.call(rawState.variables, indName);
27340
- const prevInd = rawState.variables[indName];
27341
- const prevLastArg = rawState.lastArgument;
27342
- let loopRes;
27343
- rawState.depth++;
27344
- try {
27345
- loopRes = this.trySyncLoop(cmd, pipeline, rawState, monitor, store, existing, elem0, true, subIO, part.line);
27346
- }
27347
- finally {
27348
- rawState.depth--;
27349
- if (prevLastArg !== undefined)
27350
- rawState.lastArgument = prevLastArg;
27351
- else
27352
- delete rawState.lastArgument;
27353
- if (hadInd && prevInd !== undefined)
27354
- monitor.publishStringVariable(indName, prevInd);
27355
- else {
27356
- delete rawState.variables[indName];
27357
- monitor.values.invalidate(indName);
27358
- }
27359
- }
27360
- if (loopRes !== undefined) {
27361
- rawState.substitutionStatus = loopRes;
27362
- rawState.status = loopRes;
27363
- const bytes = capture.takeBytes();
27364
- let str = sharedSyncPipeDecoder.decode(bytes);
27365
- let end = str.length;
27366
- while (end > 0 && str.charCodeAt(end - 1) === 10)
27367
- end--;
27368
- if (end < str.length)
27369
- str = str.slice(0, end);
27370
- return str;
27371
- }
27372
- }
28088
+ if (!monitor)
28089
+ return undefined;
28090
+ let selectedBody;
28091
+ if (cmd.kind === "if") {
28092
+ for (const br of cmd.branches) {
28093
+ const bc = br.condition.lists[0].pipelines[0].commands[0];
28094
+ let condOk = false;
28095
+ if (bc.kind === "arithmetic") {
28096
+ condOk = this.evalSyncLoopArithStmt(bc.expression, rawState, io, this._syncArithTouched ?? new Set(), bc.line ?? part.line) === true;
28097
+ }
28098
+ else if (bc.kind === "conditional") {
28099
+ const st = this.tryEvalConditionalSync(bc.expression, rawState, monitor, arrayStore(rawState), io, bc.line ?? part.line);
28100
+ if (st === undefined)
28101
+ return undefined;
28102
+ condOk = st === 0;
28103
+ }
28104
+ else {
28105
+ const bExpr = this.extractPosixBracketCondExpr(bc, rawState, true, br.body);
28106
+ if (!bExpr)
28107
+ return undefined;
28108
+ const st = this.tryEvalConditionalSync(bExpr, rawState, monitor, arrayStore(rawState), io, bc.line ?? part.line);
28109
+ if (st === undefined)
28110
+ return undefined;
28111
+ condOk = st === 0;
28112
+ }
28113
+ if (condOk) {
28114
+ selectedBody = br.body;
28115
+ break;
28116
+ }
28117
+ }
28118
+ if (!selectedBody)
28119
+ selectedBody = cmd.otherwise;
28120
+ }
28121
+ else {
28122
+ const subj = this.fastValueWord(cmd.subject, rawState, io, false, false, false, false, undefined, part.line);
28123
+ if (typeof subj !== "string")
28124
+ return undefined;
28125
+ const work = { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
28126
+ for (const cl of cmd.clauses) {
28127
+ let matched = false;
28128
+ for (const pw of cl.patterns) {
28129
+ if (pw.plain !== undefined && tryMatchesPatternSync(pw.plain, subj, work, false, false) === true) {
28130
+ matched = true;
28131
+ break;
27373
28132
  }
27374
28133
  }
28134
+ if (matched) {
28135
+ selectedBody = cl.body;
28136
+ break;
28137
+ }
27375
28138
  }
27376
28139
  }
28140
+ if (!selectedBody) {
28141
+ rawState.substitutionStatus = 0;
28142
+ rawState.status = 0;
28143
+ return "";
28144
+ }
28145
+ cmd = selectedBody.lists[0].pipelines[0].commands[0];
27377
28146
  }
27378
28147
  const hasSingleStdinRedir = cmd.kind === "simple" &&
27379
28148
  cmd.redirects.length === 1 &&
@@ -27404,9 +28173,10 @@ export class Runtime {
27404
28173
  w0Plain === "cut" ||
27405
28174
  w0Plain === "wc" ||
27406
28175
  w0Plain === "sort" ||
28176
+ w0Plain === "uniq" ||
27407
28177
  w0Plain === "tr" ||
27408
28178
  (hasSingleHereStringRedir && (w0Plain === "base64" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl"));
27409
- if (isSingleFileTool && !hasShellFunction(rawState, w0Plain) && this.commands.has(w0Plain)) {
28179
+ if (isSingleFileTool && !hasShellFunction(rawState, w0Plain) && (this.commands.has(w0Plain) || ((w0Plain === "rev" || w0Plain === "tac") && Boolean(this.getExternalCommand(w0Plain))))) {
27410
28180
  const allArgs = [];
27411
28181
  let fOk = true;
27412
28182
  for (let i = 1; i < cmd.words.length; i++) {
@@ -27454,130 +28224,55 @@ export class Runtime {
27454
28224
  }
27455
28225
  else if ((w0Plain === "head" || w0Plain === "tail") && opArgs.length <= 2) {
27456
28226
  let count = opArgs.length === 0 ? 10 : undefined;
28227
+ let byteCount;
27457
28228
  if (opArgs.length === 1 && opArgs[0].startsWith("-n") && /^[0-9]{1,5}$/.test(opArgs[0].slice(2)))
27458
28229
  count = Number(opArgs[0].slice(2));
28230
+ else if (opArgs.length === 1 && opArgs[0].startsWith("-c") && /^[0-9]{1,5}$/.test(opArgs[0].slice(2)))
28231
+ byteCount = Number(opArgs[0].slice(2));
27459
28232
  else if (opArgs.length === 1 && /^-[0-9]{1,5}$/.test(opArgs[0]))
27460
28233
  count = Number(opArgs[0].slice(1));
27461
28234
  else if (opArgs.length === 2 && opArgs[0] === "-n" && /^[0-9]{1,5}$/.test(opArgs[1]))
27462
28235
  count = Number(opArgs[1]);
28236
+ else if (opArgs.length === 2 && opArgs[0] === "-c" && /^[0-9]{1,5}$/.test(opArgs[1]))
28237
+ byteCount = Number(opArgs[1]);
27463
28238
  if (count !== undefined) {
27464
28239
  const sliced = w0Plain === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
27465
28240
  fileRes = sliced.join("\n");
27466
28241
  }
27467
- }
27468
- else if (w0Plain === "jq" && (opArgs.length === 1 || opArgs.length === 2)) {
27469
- const rawMode = opArgs.length === 2 && (opArgs[0] === "-r" || opArgs[0] === "--raw-output");
27470
- const jqFilter = opArgs.length === 1 ? opArgs[0] : (rawMode ? opArgs[1] : "");
27471
- if (/^\.[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/.test(jqFilter)) {
27472
- try {
27473
- let cur = JSON.parse(fileStr.trim());
27474
- const keys = jqFilter.slice(1).split(".");
27475
- let validJq = true;
27476
- for (const k of keys) {
27477
- if (cur !== null && typeof cur === "object" && !Array.isArray(cur)) {
27478
- cur = cur[k];
27479
- }
27480
- else {
27481
- validJq = false;
27482
- break;
27483
- }
27484
- }
27485
- if (validJq && cur !== undefined && (cur === null || typeof cur === "string" || typeof cur === "number" || typeof cur === "boolean")) {
27486
- fileRes = cur === null ? "null" : typeof cur === "string" ? (rawMode ? cur : JSON.stringify(cur)) : String(cur);
27487
- }
27488
- }
27489
- catch { }
28242
+ else if (byteCount !== undefined) {
28243
+ const slicedBytes = w0Plain === "head"
28244
+ ? view.subarray(0, Math.min(view.byteLength, byteCount))
28245
+ : (byteCount === 0 ? view.subarray(0, 0) : view.subarray(Math.max(0, view.byteLength - byteCount), view.byteLength));
28246
+ fileRes = sharedSyncPipeDecoder.decode(slicedBytes);
27490
28247
  }
27491
28248
  }
27492
- else if (w0Plain === "awk" && (opArgs.length >= 1 && opArgs.length <= 3)) {
27493
- const normAwk = opArgs.length === 3 && opArgs[0] === "-F" ? ["-F" + opArgs[1], opArgs[2]] : opArgs;
27494
- let awkSep;
27495
- let awkProg;
27496
- if (normAwk.length === 1)
27497
- awkProg = normAwk[0];
27498
- else if (normAwk.length === 2 && normAwk[0].length === 3 && normAwk[0].startsWith("-F") && normAwk[0][2] !== "\\") {
27499
- awkSep = normAwk[0].slice(2);
27500
- awkProg = normAwk[1];
27501
- }
27502
- const awkM = awkProg ? /^\s*\{\s*print(?:\s+(\$(?:[0-9]+|NF)(?:\s*,\s*\$(?:[0-9]+|NF))*))?\s*;?\s*\}\s*$/.exec(awkProg) : null;
27503
- if (awkM) {
27504
- const toks = awkM[1] ? awkM[1].split(",").map(t => t.trim().slice(1)) : ["0"];
27505
- const outLines = [];
27506
- for (let li = 0; li < rawLines.length; li++) {
27507
- const l = rawLines[li];
27508
- if (toks.length === 1 && toks[0] === "0") {
27509
- outLines.push(l);
27510
- }
27511
- else {
27512
- const fields = awkSep !== undefined ? (awkSep === " " ? l.trim().split(/\s+/).filter(Boolean) : l.split(awkSep)) : l.trim().split(/\s+/).filter(Boolean);
27513
- const vals = toks.map(tk => {
27514
- if (tk === "0")
27515
- return l;
27516
- const idx = tk === "NF" ? fields.length : Number(tk);
27517
- if (idx === 0)
27518
- return l;
27519
- return idx >= 1 && idx <= fields.length ? fields[idx - 1] : "";
27520
- });
27521
- outLines.push(vals.join(" "));
27522
- }
27523
- }
27524
- fileRes = outLines.join("\n");
27525
- }
27526
- }
27527
- else if (w0Plain === "grep" && !rawState.errexit && (opArgs.length === 1 || opArgs.length === 2)) {
27528
- const mode = opArgs.length === 2 ? opArgs[0] : "";
27529
- const pat = opArgs[opArgs.length - 1];
27530
- if ((opArgs.length === 1 || mode === "-v" || mode === "-i" || mode === "-c" || mode === "-F") && /^[a-zA-Z0-9_ :;,/-]+$/.test(pat)) {
27531
- const needle = mode === "-i" ? pat.toLowerCase() : pat;
27532
- const matched = [];
27533
- for (let li = 0; li < rawLines.length; li++) {
27534
- const l = rawLines[li];
27535
- const hay = mode === "-i" ? l.toLowerCase() : l;
27536
- const ok = hay.includes(needle);
27537
- if (mode === "-v" ? !ok : ok)
27538
- matched.push(l);
27539
- }
27540
- exitStatus = matched.length > 0 ? 0 : 1;
27541
- fileRes = mode === "-c" ? String(matched.length) : matched.join("\n");
27542
- }
28249
+ else if (w0Plain === "jq") {
28250
+ const jqRes = this.evalSyncJq(fileStr.trim(), opArgs);
28251
+ if (jqRes !== undefined)
28252
+ fileRes = jqRes.join("\n");
27543
28253
  }
27544
- else if (w0Plain === "sed" && (opArgs.length === 1 || (opArgs.length === 2 && opArgs[0] === "-e"))) {
27545
- const sedExpr = opArgs[opArgs.length - 1];
27546
- const sedM = /^s\/([a-zA-Z0-9_ :;,-]+)\/([a-zA-Z0-9_ :;,./-]*)\/(g?)$/.exec(sedExpr);
27547
- if (sedM) {
27548
- const pat = sedM[1];
27549
- const rep = sedM[2];
27550
- const isGlobal = sedM[3] === "g";
27551
- fileRes = rawLines.map(l => (isGlobal ? l.split(pat).join(rep) : l.replace(pat, () => rep))).join("\n");
27552
- }
28254
+ else if (w0Plain === "awk") {
28255
+ const awkRes = this.evalSyncAwk(rawLines, opArgs);
28256
+ if (awkRes !== undefined)
28257
+ fileRes = awkRes.join("\n");
27553
28258
  }
27554
- else if (w0Plain === "cut" && opArgs.length >= 1 && opArgs.length <= 4) {
27555
- const normCut = [];
27556
- for (let ci = 0; ci < opArgs.length; ci++) {
27557
- const ca = opArgs[ci];
27558
- if ((ca === "-d" || ca === "-f") && ci + 1 < opArgs.length)
27559
- normCut.push(ca + opArgs[++ci]);
27560
- else
27561
- normCut.push(ca);
27562
- }
27563
- if (normCut.length === 2 && normCut[0] === "-c" && /^[1-9][0-9]*-[1-9][0-9]*$/.test(normCut[1])) {
27564
- const [cStart, cEnd] = normCut[1].split("-").map(Number);
27565
- fileRes = rawLines.map(l => Array.from(l).slice(cStart - 1, cEnd).join("")).join("\n");
27566
- }
27567
- else {
27568
- const delim = normCut.length === 2 && normCut[0].length === 3 && normCut[0].startsWith("-d") ? normCut[0][2] : (normCut.length === 1 ? "\t" : undefined);
27569
- const fSpec = normCut.length === 2 ? normCut[1] : normCut[0];
27570
- if (delim !== undefined && fSpec && fSpec.startsWith("-f") && /^[1-9][0-9]*(?:,[1-9][0-9]*)*$/.test(fSpec.slice(2))) {
27571
- const fIdxs = fSpec.slice(2).split(",").map(s => Number(s) - 1);
27572
- fileRes = rawLines.map(l => {
27573
- if (!l.includes(delim))
27574
- return l;
27575
- const parts = l.split(delim);
27576
- return fIdxs.map(idx => (idx < parts.length ? parts[idx] : "")).join(delim);
27577
- }).join("\n");
27578
- }
28259
+ else if (w0Plain === "grep") {
28260
+ const grepRes = this.evalSyncGrep(rawLines, opArgs, Boolean(rawState.errexit));
28261
+ if (grepRes !== undefined) {
28262
+ exitStatus = grepRes.status;
28263
+ fileRes = grepRes.lines.join("\n");
27579
28264
  }
27580
28265
  }
28266
+ else if (w0Plain === "sed") {
28267
+ const sedRes = this.evalSyncSed(rawLines, opArgs);
28268
+ if (sedRes !== undefined)
28269
+ fileRes = sedRes.join("\n");
28270
+ }
28271
+ else if (w0Plain === "cut") {
28272
+ const cutRes = this.evalSyncCut(rawLines, opArgs, byteLocale(rawState.variables));
28273
+ if (cutRes !== undefined)
28274
+ fileRes = cutRes.join("\n");
28275
+ }
27581
28276
  else if (w0Plain === "wc" && opArgs.length === 1 && (opArgs[0] === "-l" || opArgs[0] === "-c" || opArgs[0] === "-w")) {
27582
28277
  let count = 0;
27583
28278
  if (opArgs[0] === "-l") {
@@ -27594,44 +28289,27 @@ export class Runtime {
27594
28289
  }
27595
28290
  fileRes = (hasSingleStdinRedir || hasSingleHereStringRedir) ? String(count) : `${count} ${fileArg}`;
27596
28291
  }
27597
- else if (w0Plain === "sort" && !byteLocale(rawState.variables) && (opArgs.length === 0 || (opArgs.length === 1 && opArgs[0] === "-r"))) {
27598
- const revSort = opArgs[0] === "-r";
27599
- fileRes = [...rawLines].sort((a, b) => (a < b ? (revSort ? 1 : -1) : a > b ? (revSort ? -1 : 1) : 0)).join("\n");
27600
- }
27601
- else if (w0Plain === "tr" && (hasSingleStdinRedir || hasSingleHereStringRedir) && opArgs.length === 2) {
27602
- if (opArgs[0] === "a-z" && opArgs[1] === "A-Z")
27603
- fileRes = fileStr.toUpperCase();
27604
- else if (opArgs[0] === "A-Z" && opArgs[1] === "a-z")
27605
- fileRes = fileStr.toLowerCase();
27606
- else if (opArgs[0] === "-d" && /^[a-zA-Z0-9_ :;,./-]+$/.test(opArgs[1])) {
27607
- const delSet = this.expandSyncTrCharSet(opArgs[1]);
27608
- let out = "";
27609
- for (let ci = 0; ci < fileStr.length; ci++) {
27610
- const ch = fileStr[ci];
27611
- if (!delSet.has(ch))
27612
- out += ch;
27613
- }
27614
- fileRes = out;
27615
- }
27616
- else if (opArgs[0] === "-s" && opArgs[1].length === 1) {
27617
- const sq = opArgs[1];
27618
- let out = "";
27619
- let prevSq = false;
27620
- for (let ci = 0; ci < fileStr.length; ci++) {
27621
- const ch = fileStr[ci];
27622
- if (ch === sq) {
27623
- if (!prevSq) {
27624
- out += ch;
27625
- prevSq = true;
27626
- }
27627
- }
27628
- else {
27629
- out += ch;
27630
- prevSq = false;
27631
- }
28292
+ else if (w0Plain === "sort" && !byteLocale(rawState.variables) && (opArgs.length === 0 || (opArgs.length === 1 && (opArgs[0] === "-r" || opArgs[0] === "-u" || opArgs[0] === "-n" || opArgs[0] === "-rn" || opArgs[0] === "-nr" || opArgs[0] === "-ru" || opArgs[0] === "-ur")))) {
28293
+ fileRes = this.sortSyncLines(rawLines, opArgs[0]).join("\n");
28294
+ }
28295
+ else if (w0Plain === "uniq" && (opArgs.length === 0 || (opArgs.length === 1 && (opArgs[0] === "-d" || opArgs[0] === "-u" || opArgs[0] === "-c")))) {
28296
+ const mode = opArgs[0];
28297
+ const outLines = [];
28298
+ let uIdx = 0;
28299
+ while (uIdx < rawLines.length) {
28300
+ let uEnd = uIdx + 1;
28301
+ while (uEnd < rawLines.length && rawLines[uEnd] === rawLines[uIdx])
28302
+ uEnd++;
28303
+ const count = uEnd - uIdx;
28304
+ if (mode === "-d" ? count > 1 : mode === "-u" ? count === 1 : true) {
28305
+ outLines.push(mode === "-c" ? `${String(count).padStart(7, " ")} ${rawLines[uIdx]}` : rawLines[uIdx]);
27632
28306
  }
27633
- fileRes = out;
28307
+ uIdx = uEnd;
27634
28308
  }
28309
+ fileRes = outLines.join("\n");
28310
+ }
28311
+ else if (w0Plain === "tr" && (hasSingleStdinRedir || hasSingleHereStringRedir)) {
28312
+ fileRes = this.evalSyncTr(fileStr, opArgs);
27635
28313
  }
27636
28314
  else if (hasSingleHereStringRedir && w0Plain === "base64" && (opArgs.length === 0 || (opArgs.length === 1 && (opArgs[0] === "-d" || opArgs[0] === "--decode")))) {
27637
28315
  if (opArgs.length === 0)