@poe-platform/safe-bash 0.1.747 → 0.1.749

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.
@@ -1,6 +1,6 @@
1
1
  import { splitSyncJqExpression } from "./sync-jq-expression.js";
2
2
  import { wcDisplayWidth } from "../commands/wc-width.js";
3
- import { text as awkValueText, compare as awkCompare, inputValue as awkInputValue, numeric as awkNumeric, string as awkString } from "../commands/text-programs/awk-values.js";
3
+ import { text as awkValueText, compare as awkCompare, inputValue as awkInputValue, numeric as awkNumeric, number as awkNumber, string as awkString } from "../commands/text-programs/awk-values.js";
4
4
  import { bytesToHex, latin1Text } from "../byte-encoding.js";
5
5
  const sharedCaptureDecoder = new TextDecoder();
6
6
  const cachedCaptureAsciiBytes = new Uint8Array(4096);
@@ -9405,7 +9405,7 @@ export class Runtime {
9405
9405
  }
9406
9406
  }
9407
9407
  if (command.kind === "conditional") {
9408
- if (rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(command.expression, 0, rawState))
9408
+ if (rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(command.expression, 0, rawState, true))
9409
9409
  return false;
9410
9410
  const checkCondNoFile = (e) => {
9411
9411
  if (e.kind === "nonempty")
@@ -9775,9 +9775,15 @@ export class Runtime {
9775
9775
  }
9776
9776
  return false;
9777
9777
  }
9778
- canSyncConditional(expr, depth = 0, rawState) {
9778
+ canSyncConditional(expr, depth = 0, rawState, requireTotal = false) {
9779
9779
  if (depth > 8)
9780
9780
  return false;
9781
+ // Loops and pure substitutions cannot replay after an unsupported result.
9782
+ // These operators depend on arithmetic, filesystem or collation state that
9783
+ // the speculative synchronous evaluator does not fully support.
9784
+ if (requireTotal && ((expr.kind === "unary" && syncConditionalFileUnaryOps.has(expr.operator)) ||
9785
+ (expr.kind === "binary" && expr.operator !== "==" && expr.operator !== "=" && expr.operator !== "!=" && expr.operator !== "=~")))
9786
+ return false;
9781
9787
  const okWord = (w) => !Runtime.wordMayMutate(w) || (rawState !== undefined && this.isPureSyncValueWord(w, rawState));
9782
9788
  if (expr.kind === "nonempty")
9783
9789
  return okWord(expr.operand);
@@ -9800,9 +9806,9 @@ export class Runtime {
9800
9806
  return (expr.operator === "<" || expr.operator === ">" || expr.operator === "-eq" || expr.operator === "-ne" || expr.operator === "-lt" || expr.operator === "-le" || expr.operator === "-gt" || expr.operator === "-ge");
9801
9807
  }
9802
9808
  if (expr.kind === "not")
9803
- return this.canSyncConditional(expr.operand, depth + 1, rawState);
9809
+ return this.canSyncConditional(expr.operand, depth + 1, rawState, requireTotal);
9804
9810
  if (expr.kind === "and" || expr.kind === "or")
9805
- return this.canSyncConditional(expr.left, depth + 1, rawState) && this.canSyncConditional(expr.right, depth + 1, rawState);
9811
+ return this.canSyncConditional(expr.left, depth + 1, rawState, requireTotal) && this.canSyncConditional(expr.right, depth + 1, rawState, requireTotal);
9806
9812
  return false;
9807
9813
  }
9808
9814
  tryEvalFastMemoryFileUnary(op, rawVal, rawState) {
@@ -13637,60 +13643,11 @@ export class Runtime {
13637
13643
  // command. A late fallback would replay effects already performed.
13638
13644
  return false;
13639
13645
  }
13640
- const sel = getArraySelector(part);
13641
- if (sel !== undefined) {
13642
- if (rawState.nounset)
13643
- return false;
13644
- if (sel.kind === "members" || sel.kind === "keys") {
13645
- if (part.operator !== undefined) {
13646
- if (sel.kind === "members" &&
13647
- !part.keys &&
13648
- !part.length &&
13649
- !part.substring &&
13650
- part.quoted &&
13651
- (sel.separator === "*" || (allowAt && sel.separator === "@")) &&
13652
- this.budget.limits.maxExpansionBytes === Infinity &&
13653
- !rawState.nocasematch) {
13654
- if ((part.operator === "#" || part.operator === "##" || part.operator === "%" || part.operator === "%%") && part.alternate && part.alternate.parts.length === 1 && part.alternate.parts[0].kind === "text" && (!hasGlobOrEscape(part.alternate.parts[0].value, true) || (!part.alternate.parts[0].quoted && Boolean(tryCompileTrimGlobToRegex(part.alternate.parts[0].value, part.operator, !!rawState.extglob)))))
13655
- continue;
13656
- if ((part.operator === "/" || part.operator === "//" || part.operator === "/#" || part.operator === "/%") && part.alternate && part.alternate.parts.length === 1 && part.alternate.parts[0].kind === "text" && part.alternate.parts[0].value.length > 0 && (!hasGlobOrEscape(part.alternate.parts[0].value, true) || (!part.alternate.parts[0].quoted && Boolean(tryCompileFixedGlobToRegex(part.alternate.parts[0].value, part.operator, !!rawState.extglob)))) && (!part.replacement || part.replacement.parts.length === 0 || (part.replacement.parts.length === 1 && part.replacement.parts[0].kind === "text" && !part.replacement.parts[0].value.includes("&") && !part.replacement.parts[0].value.includes("\\") && !part.replacement.parts[0].value.startsWith("~"))))
13657
- continue;
13658
- if ((part.operator === "^" || part.operator === "^^" || part.operator === "," || part.operator === ",,") && (!part.alternate || part.alternate.parts.length === 0))
13659
- continue;
13660
- }
13661
- return false;
13662
- }
13663
- if (part.length) {
13664
- if (part.substring)
13665
- return false;
13666
- continue;
13667
- }
13668
- if (part.quoted && (sel.separator === "*" || (allowAt && sel.separator === "@")) && this.budget.limits.maxExpansionBytes === Infinity && (!part.substring || (sel.kind === "members" && part.substring.offset.parts.every(p => p.kind === "text" || (p.kind === "variable" && !p.operator && !p.substring && !p.length)) && (!part.substring.length || part.substring.length.parts.every(p => p.kind === "text" || (p.kind === "variable" && !p.operator && !p.substring && !p.length)))))) {
13669
- continue;
13670
- }
13671
- return false;
13672
- }
13673
- if (sel.kind === "element") {
13674
- if (part.substring) {
13675
- // Bindings, indices and character widths can change each
13676
- // iteration. The limited element slicer may then need fallback;
13677
- // do not speculate after earlier compound effects have run.
13678
- return false;
13679
- }
13680
- if (part.operator === undefined)
13681
- continue;
13682
- if ((part.operator === "-" || part.operator === ":-" || part.operator === "+" || part.operator === ":+") && part.alternate && !part.length)
13683
- continue;
13684
- if ((part.operator === "^" || part.operator === "^^" || part.operator === "," || part.operator === ",,") && (!part.alternate || part.alternate.parts.length === 0) && !part.length)
13685
- continue;
13686
- if (!part.length && !rawState.nocasematch && this.budget.limits.maxExpansionBytes === Infinity) {
13687
- if ((part.operator === "#" || part.operator === "##" || part.operator === "%" || part.operator === "%%") && part.alternate && part.alternate.parts.length === 1 && part.alternate.parts[0].kind === "text" && !hasGlobOrEscape(part.alternate.parts[0].value, true))
13688
- continue;
13689
- if ((part.operator === "/" || part.operator === "//" || part.operator === "/#" || part.operator === "/%") && part.alternate && part.alternate.parts.length === 1 && part.alternate.parts[0].kind === "text" && part.alternate.parts[0].value.length > 0 && !hasGlobOrEscape(part.alternate.parts[0].value, true) && (!part.replacement || part.replacement.parts.length === 0 || (part.replacement.parts.length === 1 && part.replacement.parts[0].kind === "text" && !part.replacement.parts[0].value.includes("&") && !part.replacement.parts[0].value.includes("\\") && !part.replacement.parts[0].value.startsWith("~"))))
13690
- continue;
13691
- }
13692
- return false;
13693
- }
13646
+ if (getArraySelector(part) !== undefined) {
13647
+ // A loop can change the binding or subscript after preflight. The
13648
+ // limited fast evaluator may then return undefined, after earlier
13649
+ // effects have already run. Use normal expansion for selectors and
13650
+ // their alternates rather than risking corruption or replay.
13694
13651
  return false;
13695
13652
  }
13696
13653
  if (part.length || part.substring || part.operator !== undefined) {
@@ -13925,13 +13882,19 @@ export class Runtime {
13925
13882
  return false;
13926
13883
  }
13927
13884
  else if (w0Plain === "awk") {
13928
- if (plainOps.length !== opCount || this.evalSyncAwk([], plainOps) === undefined)
13885
+ const input = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
13886
+ if (plainOps.length !== opCount || input === undefined)
13887
+ return false;
13888
+ const lines = input.length === 0 ? [] : input.split("\n");
13889
+ if (input.endsWith("\n"))
13890
+ lines.pop();
13891
+ if (this.evalSyncAwk(lines, plainOps) === undefined)
13929
13892
  return false;
13930
13893
  }
13931
13894
  else if (w0Plain === "jq") {
13932
13895
  if (plainOps.length !== opCount)
13933
13896
  return false;
13934
- const jqIn = this.extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
13897
+ const jqIn = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState)?.trim();
13935
13898
  if (jqIn === undefined || this.evalSyncJq(jqIn, plainOps) === undefined)
13936
13899
  return false;
13937
13900
  }
@@ -13964,7 +13927,7 @@ export class Runtime {
13964
13927
  return false;
13965
13928
  }
13966
13929
  else if (w0Plain === "numfmt") {
13967
- const nmCheck = this.extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
13930
+ const nmCheck = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState)?.trim();
13968
13931
  if (nmCheck === undefined || plainOps.length !== opCount || this.evalSyncNumfmt(nmCheck.split("\n"), plainOps) === undefined)
13969
13932
  return false;
13970
13933
  }
@@ -14087,7 +14050,15 @@ export class Runtime {
14087
14050
  return false;
14088
14051
  }
14089
14052
  else if (sName === "awk") {
14090
- if (this.evalSyncAwk([], sPlainArgs) === undefined)
14053
+ if (sIdx !== 1)
14054
+ return false;
14055
+ const input = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
14056
+ if (input === undefined)
14057
+ return false;
14058
+ const lines = input.length === 0 ? [] : input.split("\n");
14059
+ if (input.endsWith("\n"))
14060
+ lines.pop();
14061
+ if (this.evalSyncAwk(lines, sPlainArgs) === undefined)
14091
14062
  return false;
14092
14063
  }
14093
14064
  else if (sName === "grep") {
@@ -14101,7 +14072,7 @@ export class Runtime {
14101
14072
  else if (sName === "jq") {
14102
14073
  if (sIdx !== 1)
14103
14074
  return false;
14104
- const jqIn = this.extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
14075
+ const jqIn = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState)?.trim();
14105
14076
  if (jqIn === undefined || this.evalSyncJq(jqIn, sPlainArgs) === undefined)
14106
14077
  return false;
14107
14078
  }
@@ -14124,7 +14095,7 @@ export class Runtime {
14124
14095
  else if (sName === "numfmt") {
14125
14096
  if (sIdx !== 1)
14126
14097
  return false;
14127
- const nmCheck = this.extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
14098
+ const nmCheck = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState)?.trim();
14128
14099
  if (nmCheck === undefined || this.evalSyncNumfmt(nmCheck.split("\n"), sPlainArgs) === undefined)
14129
14100
  return false;
14130
14101
  }
@@ -14201,50 +14172,15 @@ export class Runtime {
14201
14172
  }
14202
14173
  return undefined;
14203
14174
  }
14204
- execSyncUnsetStep(step, rawState, io, monitor, touched) {
14175
+ execSyncUnsetStep(step, rawState, monitor, touched) {
14205
14176
  const uVars = step.unsetVars;
14206
- const store = monitor.store;
14207
14177
  let lastExpanded = "";
14208
- for (let ui = 0; ui < uVars.length; ui++) {
14209
- const uvn = uVars[ui];
14210
- if (uvn.charCodeAt(0) === 64) {
14211
- const wIdx = Number(uvn.slice(1));
14212
- const wu = step.cmd.words[wIdx];
14213
- const tExpanded = this.fastValueWord(wu, rawState, io, false, false, false, false, 0, step.line);
14214
- lastExpanded = tExpanded;
14215
- const bracket = tExpanded.indexOf("[");
14216
- if (bracket > 0 && tExpanded.endsWith("]") && store) {
14217
- const arrName = tExpanded.slice(0, bracket);
14218
- const subStr = tExpanded.slice(bracket + 1, -1);
14219
- const binding = store.get(arrName);
14220
- if (binding) {
14221
- if (binding.associative) {
14222
- const id = fastStringHexIdentity(subStr);
14223
- const existingKey = binding.keys.get(id);
14224
- if (existingKey !== undefined) {
14225
- binding.remove(existingKey.index);
14226
- store.changed(monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets), arrName);
14227
- }
14228
- }
14229
- else {
14230
- const idxNum = /^(?:0|[1-9][0-9]{0,8})$/.test(subStr)
14231
- ? Number(subStr)
14232
- : resolveSimpleArithOperand(subStr, rawState, monitor, store, this._syncArithRawWriteOnly ? this._syncArithTouched : undefined);
14233
- const resolvedIdx = idxNum !== undefined ? (idxNum >= 0 ? idxNum : binding.maximum + 1 + idxNum) : undefined;
14234
- if (resolvedIdx !== undefined && resolvedIdx >= 0 && binding.values.has(resolvedIdx)) {
14235
- binding.remove(resolvedIdx);
14236
- store.changed(monitor.chargeInternal(syncRestorationCharge, syncRestorationTickets), arrName);
14237
- }
14238
- }
14239
- }
14240
- }
14241
- }
14242
- else {
14243
- delete rawState.variables[uvn];
14244
- monitor.proxy.exported.delete(uvn);
14245
- touched.add(uvn);
14246
- lastExpanded = uvn;
14247
- }
14178
+ for (const uvn of uVars) {
14179
+ delete rawState.variables[uvn];
14180
+ rawState.exported.delete(uvn);
14181
+ monitor.proxy.exported.delete(uvn);
14182
+ touched.add(uvn);
14183
+ lastExpanded = uvn;
14248
14184
  }
14249
14185
  return lastExpanded;
14250
14186
  }
@@ -14268,26 +14204,7 @@ export class Runtime {
14268
14204
  if (vn && startIdx === 1 && hasShellFunction(rawState, vn))
14269
14205
  return undefined;
14270
14206
  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))) {
14271
- const wu = cmd.words[i];
14272
- const p0 = wu.parts[0];
14273
- const pLast = wu.parts[wu.parts.length - 1];
14274
- const m = p0?.kind === "text" ? /^([a-zA-Z_][a-zA-Z0-9_]*)\[/.exec(p0.value) : undefined;
14275
- const arrName = m?.[1];
14276
- const b = arrName ? store?.get(arrName) : undefined;
14277
- if (arrName &&
14278
- b &&
14279
- b.references === 1 &&
14280
- !store.watches.has(arrName) &&
14281
- !rawState.readonlyVariables?.has(arrName) &&
14282
- !hasUnpreparedLocal(rawState, arrName) &&
14283
- !stateMonitor(rawState)?.hasOverlay(arrName) &&
14284
- pLast?.kind === "text" &&
14285
- pLast.value.endsWith("]") &&
14286
- this.isPureSyncValueWord(wu, rawState) &&
14287
- (p0.quoted || rawState.noglob || (!rawState.nullglob && this._isMemoryBackingFs && tryGetMemoryDirectoryEntryNamesSync(this.backingFs, rawState.cwd)?.size === 0))) {
14288
- vars.push(`@${i}`);
14289
- continue;
14290
- }
14207
+ // Array unsets need the full builtin: arithmetic, @/* and maximum-index updates.
14291
14208
  return undefined;
14292
14209
  }
14293
14210
  vars.push(vn);
@@ -14672,7 +14589,7 @@ export class Runtime {
14672
14589
  return false;
14673
14590
  }
14674
14591
  else if (bc.kind === "conditional") {
14675
- if (bc.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(bc.expression, 0, rawState))
14592
+ if (bc.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(bc.expression, 0, rawState, true))
14676
14593
  return false;
14677
14594
  if (bc.expression.kind === "binary" && bc.expression.operator === "=~")
14678
14595
  return false;
@@ -14714,7 +14631,7 @@ export class Runtime {
14714
14631
  return false;
14715
14632
  const cmd = pipeline.commands[0];
14716
14633
  const canSyncLoopCondCmd = (c) => {
14717
- if (c.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(c.expression, 0, rawState))
14634
+ if (c.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(c.expression, 0, rawState, true))
14718
14635
  return false;
14719
14636
  const checkLeaf = (e) => {
14720
14637
  if (e.kind === "nonempty")
@@ -15052,6 +14969,10 @@ export class Runtime {
15052
14969
  return false;
15053
14970
  }
15054
14971
  const assignment = this.assignment(w0);
14972
+ // Eligibility is checked against the entry locale. Changing it can make
14973
+ // otherwise pure expansions require the asynchronous byte-aware path.
14974
+ if (assignment && (assignment.name === "LC_ALL" || assignment.name === "LC_CTYPE" || assignment.name === "LC_COLLATE" || assignment.name === "LANG"))
14975
+ return false;
15055
14976
  if (!assignment || (assignment.append && this.budget.limits.maxExpansionBytes !== Infinity) || assignment.name === "OPTIND" || assignment.name === "PIPESTATUS" || assignment.name.includes("[") || rawState.readonlyVariables?.has(assignment.name) || rawState.variableAttributes?.get(assignment.name) || store?.get(assignment.name) || !this.isPureSyncValueWord(assignment.value, rawState)) {
15056
14977
  return false;
15057
14978
  }
@@ -16159,7 +16080,7 @@ export class Runtime {
16159
16080
  whileArithProg = condCmd.expression;
16160
16081
  }
16161
16082
  else if (condCmd.kind === "conditional") {
16162
- if (condCmd.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(condCmd.expression, 0, rawState))
16083
+ if (condCmd.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(condCmd.expression, 0, rawState, true))
16163
16084
  return undefined;
16164
16085
  const checkWhileCond = (e, depth) => {
16165
16086
  if (e.kind === "nonempty")
@@ -16855,7 +16776,8 @@ export class Runtime {
16855
16776
  }
16856
16777
  else {
16857
16778
  monitor.values.invalidate(varName);
16858
- monitor.proxy.exported.delete(varName);
16779
+ if (!rawState.exported.has(varName))
16780
+ monitor.proxy.exported.delete(varName);
16859
16781
  }
16860
16782
  }
16861
16783
  }
@@ -17120,7 +17042,7 @@ export class Runtime {
17120
17042
  };
17121
17043
  return visit(script);
17122
17044
  }
17123
- extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState) {
17045
+ extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState) {
17124
17046
  const w0 = cmd.words[0]?.plain;
17125
17047
  const resolveInvariantWord = (w) => {
17126
17048
  if (w.plain !== undefined)
@@ -17137,10 +17059,10 @@ export class Runtime {
17137
17059
  }
17138
17060
  return undefined;
17139
17061
  };
17140
- if (w0 === "jq" || w0 === "numfmt") {
17062
+ if (w0 === "jq" || w0 === "numfmt" || w0 === "awk") {
17141
17063
  if (hasSingleHereStringRedir) {
17142
17064
  const resolved = resolveInvariantWord(cmd.redirects[0].target);
17143
- return resolved !== undefined ? resolved.trim() : undefined;
17065
+ return resolved !== undefined ? resolved + "\n" : undefined;
17144
17066
  }
17145
17067
  if (this._fileWrites?.size || this._outputFiles?.size)
17146
17068
  return undefined;
@@ -17152,12 +17074,12 @@ export class Runtime {
17152
17074
  const vCheck = this.tryReadMemoryFileViewSync(resolvePath(rawState.cwd, fPlain), false);
17153
17075
  if (!vCheck || vCheck.byteLength > 16384 || vCheck.includes(0))
17154
17076
  return undefined;
17155
- return sharedSyncPipeDecoder.decode(vCheck).trim();
17077
+ return sharedSyncPipeDecoder.decode(vCheck);
17156
17078
  }
17157
17079
  if (w0 === "cat") {
17158
17080
  if (hasSingleHereStringRedir) {
17159
17081
  const resolved = resolveInvariantWord(cmd.redirects[0].target);
17160
- return resolved !== undefined ? resolved.trim() : undefined;
17082
+ return resolved !== undefined ? resolved + "\n" : undefined;
17161
17083
  }
17162
17084
  if (this._fileWrites?.size || this._outputFiles?.size)
17163
17085
  return undefined;
@@ -17169,12 +17091,12 @@ export class Runtime {
17169
17091
  const vCheck = this.tryReadMemoryFileViewSync(resolvePath(rawState.cwd, fPlain), false);
17170
17092
  if (!vCheck || vCheck.byteLength > 16384 || vCheck.includes(0))
17171
17093
  return undefined;
17172
- return sharedSyncPipeDecoder.decode(vCheck).trim();
17094
+ return sharedSyncPipeDecoder.decode(vCheck);
17173
17095
  }
17174
17096
  if (w0 === "echo" && cmd.words.length === 2) {
17175
17097
  const resolved = resolveInvariantWord(cmd.words[1]);
17176
17098
  if (resolved !== undefined && !resolved.startsWith("-"))
17177
- return resolved.trim();
17099
+ return resolved + "\n";
17178
17100
  return undefined;
17179
17101
  }
17180
17102
  if (w0 === "printf" && cmd.words.length >= 2) {
@@ -17186,7 +17108,7 @@ export class Runtime {
17186
17108
  args.push(r);
17187
17109
  }
17188
17110
  const formatted = tryFastPrintf(args);
17189
- return formatted !== undefined ? formatted.trim() : undefined;
17111
+ return formatted;
17190
17112
  }
17191
17113
  return undefined;
17192
17114
  }
@@ -17662,7 +17584,7 @@ export class Runtime {
17662
17584
  break;
17663
17585
  }
17664
17586
  if (step.unsetVars !== undefined) {
17665
- lastArg = this.execSyncUnsetStep(step, rawState, io, monitor, touched);
17587
+ lastArg = this.execSyncUnsetStep(step, rawState, monitor, touched);
17666
17588
  rawState.status = 0;
17667
17589
  continue;
17668
17590
  }
@@ -18240,7 +18162,7 @@ export class Runtime {
18240
18162
  break;
18241
18163
  }
18242
18164
  if (step.unsetVars !== undefined) {
18243
- lastArg = this.execSyncUnsetStep(step, rawState, io, monitor, touched);
18165
+ lastArg = this.execSyncUnsetStep(step, rawState, monitor, touched);
18244
18166
  rawState.status = 0;
18245
18167
  onUpdate({ lastCmd, lastArg });
18246
18168
  continue;
@@ -18425,7 +18347,7 @@ export class Runtime {
18425
18347
  break;
18426
18348
  }
18427
18349
  if (step.unsetVars !== undefined) {
18428
- lastArg = this.execSyncUnsetStep(step, rawState, io, monitor, touched);
18350
+ lastArg = this.execSyncUnsetStep(step, rawState, monitor, touched);
18429
18351
  rawState.status = 0;
18430
18352
  continue;
18431
18353
  }
@@ -27530,17 +27452,17 @@ export class Runtime {
27530
27452
  if (st === "ceil")
27531
27453
  return typeof item === "number" ? [Math.ceil(item)] : undefined;
27532
27454
  if (st === "round")
27533
- return typeof item === "number" ? [Math.round(item)] : undefined;
27455
+ return typeof item === "number" ? [Math.sign(item) * Math.round(Math.abs(item))] : undefined;
27534
27456
  if (st === "abs")
27535
27457
  return typeof item === "number" ? [Math.abs(item)] : undefined;
27536
27458
  if (st === "utf8bytelength")
27537
- return typeof item === "string" ? [Buffer.byteLength(item, "utf8")] : undefined;
27459
+ return typeof item === "string" ? [fastSharedTextEncoder.encode(item).byteLength] : undefined;
27538
27460
  if (st === "explode")
27539
27461
  return typeof item === "string" ? [Array.from(item, c => c.codePointAt(0))] : undefined;
27540
27462
  if (st === "implode") {
27541
27463
  if (!Array.isArray(item) || !item.every(x => typeof x === "number" && Number.isInteger(x) && x >= 0 && x <= 0x10ffff))
27542
27464
  return undefined;
27543
- return [String.fromCodePoint(...item)];
27465
+ return [item.map(cp => String.fromCodePoint(cp >= 0xd800 && cp <= 0xdfff ? 0xfffd : cp)).join("")];
27544
27466
  }
27545
27467
  if (st === "transpose") {
27546
27468
  if (!Array.isArray(item) || !item.every(row => Array.isArray(row)))
@@ -27554,14 +27476,20 @@ export class Runtime {
27554
27476
  return [trans];
27555
27477
  }
27556
27478
  const idxFnM = /^(index|rindex|indices)\(\s*"([^"\\]*)"\s*\)$/.exec(st);
27557
- if (idxFnM && typeof item === "string") {
27479
+ if (idxFnM && (typeof item === "string" || Array.isArray(item))) {
27558
27480
  const fn = idxFnM[1];
27559
27481
  const sub = idxFnM[2];
27560
27482
  const found = [];
27561
- if (sub.length > 0) {
27483
+ if (Array.isArray(item)) {
27484
+ for (let i = 0; i < item.length; i++) {
27485
+ if (item[i] === sub)
27486
+ found.push(i);
27487
+ }
27488
+ }
27489
+ else if (sub.length > 0) {
27562
27490
  let pos = 0;
27563
27491
  while ((pos = item.indexOf(sub, pos)) !== -1) {
27564
- found.push(Array.from(item.slice(0, pos)).length);
27492
+ found.push(fastSharedTextEncoder.encode(item.slice(0, pos)).byteLength);
27565
27493
  pos += 1;
27566
27494
  }
27567
27495
  }
@@ -28967,6 +28895,8 @@ export class Runtime {
28967
28895
  const tm = /^(\$(?:[0-9]+|NF)|NR|NF)\s*(==|!=|>=|<=|>|<)\s*(-?[0-9]+(?:\.[0-9]+)?|"[^"$\\]*")\s*\?\s*(\$(?:[0-9]+|NF)|"[^"$\\]*"|-?[0-9]+(?:\.[0-9]+)?)\s*:\s*(\$(?:[0-9]+|NF)|"[^"$\\]*"|-?[0-9]+(?:\.[0-9]+)?)$/.exec(m[1].trim());
28968
28896
  if (!tm)
28969
28897
  return undefined;
28898
+ if (tm[2] === ">")
28899
+ return undefined; // Unparenthesized print > is output redirection.
28970
28900
  parts.push({ kind: "ternary", lhs: tm[1], op: tm[2], rhs: tm[3], yes: tm[4], no: tm[5] });
28971
28901
  }
28972
28902
  else if (m[2] !== undefined)
@@ -29080,8 +29010,10 @@ export class Runtime {
29080
29010
  else if (p.kind === "int") {
29081
29011
  const idx = p.token === "NF" ? fields.length : Number(p.token);
29082
29012
  const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29083
- const n = Number(/^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(s)?.[1] ?? 0);
29084
- out += String(Math.trunc(n));
29013
+ const n = awkNumber(awkInputValue(s));
29014
+ if (!Number.isFinite(n))
29015
+ return undefined;
29016
+ out += awkValueText(awkNumeric(Math.trunc(n)));
29085
29017
  }
29086
29018
  else if (p.kind === "ternary") {
29087
29019
  const resolveAtom = (tok) => {
@@ -29099,16 +29031,10 @@ export class Runtime {
29099
29031
  };
29100
29032
  const lv = resolveAtom(p.lhs);
29101
29033
  const rv = resolveAtom(p.rhs);
29102
- const isStrCmp = p.rhs.startsWith("\"");
29103
- let cond = false;
29104
- if (isStrCmp) {
29105
- cond = p.op === "==" ? lv === rv : p.op === "!=" ? lv !== rv : p.op === ">" ? lv > rv : p.op === "<" ? lv < rv : p.op === ">=" ? lv >= rv : lv <= rv;
29106
- }
29107
- else {
29108
- const ln = Number(lv) || 0;
29109
- const rn = Number(rv) || 0;
29110
- cond = p.op === "==" ? ln === rn : p.op === "!=" ? ln !== rn : p.op === ">" ? ln > rn : p.op === "<" ? ln < rn : p.op === ">=" ? ln >= rn : ln <= rn;
29111
- }
29034
+ const left = p.lhs === "NR" || p.lhs === "NF" ? awkNumeric(Number(lv)) : awkInputValue(lv);
29035
+ const right = p.rhs.startsWith('"') ? awkString(rv) : awkNumeric(Number(rv));
29036
+ const comparison = awkCompare(left, right, "%.6g");
29037
+ const cond = p.op === "==" ? comparison === 0 : p.op === "!=" ? comparison !== 0 : p.op === ">" ? comparison > 0 : p.op === "<" ? comparison < 0 : p.op === ">=" ? comparison >= 0 : comparison <= 0;
29112
29038
  out += resolveAtom(cond ? p.yes : p.no);
29113
29039
  }
29114
29040
  else if (p.kind === "arith") {
@@ -29373,13 +29299,14 @@ export class Runtime {
29373
29299
  if (s.length === 0)
29374
29300
  return [""];
29375
29301
  const res = [];
29302
+ const chars = Array.from(s);
29376
29303
  const escapes = { b: "\b", f: "\f", n: "\n", r: "\r", t: "\t", v: "\v", "0": "" };
29377
- for (let i = 0; i < s.length; i++) {
29378
- const ch = s[i];
29304
+ for (let i = 0; i < chars.length; i++) {
29305
+ const ch = chars[i];
29379
29306
  if (ch === "\\") {
29380
- if (i + 1 >= s.length)
29307
+ if (i + 1 >= chars.length)
29381
29308
  return undefined;
29382
- const esc = s[++i];
29309
+ const esc = chars[++i];
29383
29310
  res.push(escapes[esc] ?? esc);
29384
29311
  }
29385
29312
  else {