@poe-platform/safe-bash 0.1.745 → 0.1.747

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +5 -1
  2. package/dist/image-ast/browser/sharp.d.ts +1 -1
  3. package/dist/image-ast/index.js +133 -35
  4. package/dist/image-ast/sharp.d.ts +1 -1
  5. package/dist/safe-bash/chunks/{chunk-2F7Q532M.js → chunk-4GK4FAT6.js} +3 -3
  6. package/dist/safe-bash/chunks/{chunk-EAUCLWEZ.js → chunk-55AGFLG5.js} +540 -284
  7. package/dist/safe-bash/chunks/{chunk-WCNFCN2G.js → chunk-BS6QFRNX.js} +2 -2
  8. package/dist/safe-bash/chunks/{chunk-IZZ7KX4E.js → chunk-JCXAGOEK.js} +40 -3
  9. package/dist/safe-bash/chunks/{chunk-I3DOFJSJ.js → chunk-MUWJPSGN.js} +2 -2
  10. package/dist/safe-bash/chunks/{chunk-7RP6WOWK.js → chunk-OF2PDE3W.js} +2 -2
  11. package/dist/safe-bash/chunks/{src-FTMTU65U.js → src-T672TQJC.js} +2 -2
  12. package/dist/safe-bash/command-chunks/{chunk-DRCPJVZF.js → chunk-44N2IRVP.js} +17 -5
  13. package/dist/safe-bash/command-chunks/{chunk-LYKC7WJB.js → chunk-6742OVAC.js} +37 -3
  14. package/dist/safe-bash/command-chunks/{chunk-BMBXSLVX.js → chunk-6QU5OLPJ.js} +2 -2
  15. package/dist/safe-bash/command-chunks/{chunk-YHANNN7I.js → chunk-S3BO2NJT.js} +7 -0
  16. package/dist/safe-bash/commands/basic.d.ts +2 -0
  17. package/dist/safe-bash/commands/basic.js +236 -9
  18. package/dist/safe-bash/commands/pr/index.browser.js +1 -1
  19. package/dist/safe-bash/commands/streams.js +9 -56
  20. package/dist/safe-bash/commands/table-text/index.js +5 -1
  21. package/dist/safe-bash/commands/text.js +13 -2
  22. package/dist/safe-bash/commands/time-env/shared.js +2 -2
  23. package/dist/safe-bash/commands/time-env/sleep.js +6 -4
  24. package/dist/safe-bash/commands/timeout/index.d.ts +7 -0
  25. package/dist/safe-bash/commands/timeout/index.js +52 -32
  26. package/dist/safe-bash/commands/timeout/scheduler.js +7 -6
  27. package/dist/safe-bash/core.browser.js +2698 -543
  28. package/dist/safe-bash/opt-in/commands/shuf/random.js +2 -3
  29. package/dist/safe-bash/opt-in/shell/extensions/read/index.js +1 -1
  30. package/dist/safe-bash/opt-in/shell/extensions/trap/index.d.ts +1 -2
  31. package/dist/safe-bash/opt-in/shell/extensions/trap/index.js +1 -8
  32. package/dist/safe-bash/playwright/adapter.d.ts +2 -0
  33. package/dist/safe-bash/playwright/private-target-transport.d.ts +1 -0
  34. package/dist/safe-bash/playwright/private-target-transport.js +21 -21
  35. package/dist/safe-bash/shell/runtime.d.ts +20 -1
  36. package/dist/safe-bash/shell/runtime.js +3021 -532
  37. package/dist/safe-bash/shell/sync-jq-expression.d.ts +6 -0
  38. package/dist/safe-bash/shell/sync-jq-expression.js +46 -0
  39. package/dist/safe-bash-command-cal/index.js +32 -12
  40. package/dist/safe-bash-command-hostname/index.js +22 -7
  41. package/dist/safe-bash-command-id/index.js +28 -9
  42. package/dist/safe-bash-command-pandoc/command.js +1 -1
  43. package/dist/safe-bash-command-pandoc/index.js +1 -1
  44. package/dist/safe-bash-command-ssconvert/command.d.ts +2 -2
  45. package/dist/safe-bash-command-ssconvert/contracts.d.ts +4 -0
  46. package/dist/safe-bash-command-ssconvert/engine.d.ts +2 -2
  47. package/dist/safe-bash-command-ssconvert/index.js +524 -276
  48. package/dist/safe-bash-command-whoami/index.js +26 -12
  49. package/dist/safe-bash-query-engine/index.js +3 -3
  50. package/dist/safe-bash-query-engine/interpreter.js +1 -1
  51. package/dist/safe-bash-query-engine/parser.js +1 -1
  52. package/dist/safe-bash-query-engine/query-core.js +3 -3
  53. package/dist/safe-playwright-cloudflare/browser-private-transport.js +8 -13
  54. package/dist/safe-playwright-cloudflare/browser-storage-control.js +13 -4
  55. package/dist/safe-playwright-cloudflare/browser-trace-archive.js +1 -1
  56. package/package.json +18 -14
  57. package/dist/image-ast/streams/node.d.ts +0 -10
  58. package/dist/image-ast/streams/node.js +0 -5
  59. package/dist/image-ast/streams/web.js +0 -94
@@ -1,3 +1,6 @@
1
+ import { splitSyncJqExpression } from "./sync-jq-expression.js";
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";
1
4
  import { bytesToHex, latin1Text } from "../byte-encoding.js";
2
5
  const sharedCaptureDecoder = new TextDecoder();
3
6
  const cachedCaptureAsciiBytes = new Uint8Array(4096);
@@ -10,6 +13,8 @@ import { writeDiagnostic } from "../escaping.js";
10
13
  import { cancelTurn, hasYieldCheckpoint, inheritYieldCheckpoint, monotonicNow, registerInternalYieldCheckpoint, runYieldCheckpoint, scheduleTurn, yieldTurn } from "../contracts/yield.js";
11
14
  import { ACCESS_MODES, FsError, basename, composeMiddleware, createBytePipe, dirname, normalizePath, pipeBytes, resolvePath, validateExitCode, writeBytes, writeText, } from "../contracts/index.js";
12
15
  import { createBcCommands } from "../commands/bc/index.js";
16
+ import { createStreamInspectionCommands } from "../commands/stream-inspection/index.js";
17
+ import { createStreamFormatCommands } from "../commands/stream-format/index.js";
13
18
  import { createSpongeCommands } from "../commands/sponge/index.js";
14
19
  import { createFdCommands } from "../commands/fd/index.js";
15
20
  import { createLessCommands } from "../commands/less/index.js";
@@ -57,7 +62,7 @@ import { invocationScope, throwCleanupFailures, InvocationScope } from "./cleanu
57
62
  import { bindFileOutputBudget, openFileOutput } from "../contracts/filesystem-output.js";
58
63
  import { outputFailure } from "../contracts/io.js";
59
64
  import { executionCommands } from "../commands/execution.js";
60
- import { defaultEchoExecutors, formatPrintf, printfCommand, tryFastPrintf } from "../commands/basic.js";
65
+ import { defaultEchoExecutors, extractFastPrintfSpecifiers, formatPrintf, printfCommand, tryFastPrintf } from "../commands/basic.js";
61
66
  import { defaultMkdirExecutors, defaultRmExecutors } from "../commands/filesystem.js";
62
67
  export const customRegisteredCommands = new WeakSet();
63
68
  export const customRegisteredRegistries = new WeakSet();
@@ -2617,6 +2622,12 @@ const closedSource = { [Symbol.asyncIterator]() {
2617
2622
  return iterator;
2618
2623
  } };
2619
2624
  /** Internal to the shell/runtime pair; it is not exported by the package root. */
2625
+ const syncAwkArithAtom = `(?:\\$(?:[0-9]+|NF)|NR|NF|-?[0-9]+(?:\\.[0-9]+)?|[a-zA-Z_][a-zA-Z0-9_]*)`;
2626
+ const syncAwkArithPat = `(?:${syncAwkArithAtom}(?:\\s*[+*\\/%-]\\s*${syncAwkArithAtom})+)`;
2627
+ const syncAwkTernaryPat = `(?:(?:\\$(?:[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]+)?))`;
2628
+ const syncAwkItemPat = `(?:${syncAwkTernaryPat}|int\\(\\$(?:[0-9]+|NF)\\)|(?:toupper|tolower)\\(\\$(?:[0-9]+|NF)\\)|substr\\(\\$(?:[0-9]+|NF)\\s*,\\s*[0-9]+(?:\\s*,\\s*[0-9]+)?\\)|index\\(\\$(?:[0-9]+|NF)\\s*,\\s*"[^"$\\\\]*"\\)|${syncAwkArithPat}|\\$(?:[0-9]+|NF)|\\$\\(NF\\s*-\\s*[0-9]+\\)|length(?:\\(\\$(?:[0-9]+|NF)\\))?|NR|NF|[a-zA-Z_][a-zA-Z0-9_]*|"[^"$\\\\]*")`;
2629
+ const syncAwkPrintRe = new RegExp(`^\\{\\s*(?:(g?sub)\\(\\s*\\/(\\^?[a-zA-Z0-9_ :;,=-]+\\$?)\\/\\s*,\\s*"([^"\\\\]*)"(?:\\s*,\\s*\\$([0-9]+|NF))?\\s*\\)\\s*;\\s*)?(?:print(?:\\s+(${syncAwkItemPat}(?:\\s*,?\\s*${syncAwkItemPat})*))?|printf\\s+"([^"$\\\\]*(?:\\\\[nt\\\\"][^"$\\\\]*)*)"\\s*,\\s*(${syncAwkItemPat}(?:\\s*,\\s*${syncAwkItemPat})*))\\s*;?\\s*\\}\\s*$`);
2630
+ const syncAwkTokenRe = new RegExp(`(${syncAwkTernaryPat})|int\\(\\$([0-9]+|NF)\\)|(toupper|tolower)\\(\\$([0-9]+|NF)\\)|substr\\(\\$([0-9]+|NF)\\s*,\\s*([0-9]+)(?:\\s*,\\s*([0-9]+))?\\)|index\\(\\$([0-9]+|NF)\\s*,\\s*"([^"$\\\\]*)"\\)|(${syncAwkArithPat})|\\$\\(NF\\s*-\\s*([0-9]+)\\)|\\$([0-9]+|NF)|length\\b(?:\\(\\$([0-9]+|NF)\\))?|(NR|NF)\\b|"([^"$\\\\]*)"|([a-zA-Z_][a-zA-Z0-9_]*)|(,)`, "g");
2620
2631
  export class RuntimeCancellationState {
2621
2632
  _records;
2622
2633
  _diagnostics;
@@ -2916,7 +2927,7 @@ function hasGlobOrEscape(text, extglob = false) {
2916
2927
  let nextProcessSubstitutionId = 0;
2917
2928
  let defaultRuntimeMuscleMemoryMap;
2918
2929
  function getRuntimeMuscleMemoryCommand(name) {
2919
- defaultRuntimeMuscleMemoryMap ??= new Map([...createBcCommands(), ...createSpongeCommands(), ...createFdCommands(), ...createLessCommands(), ...createIdCommands(), ...createWhoamiCommands(), ...createUnameCommands(), ...createHostnameCommands(), ...createNprocCommands(), ...createShufCommands(), ...createDdCommands(), ...createNumfmtCommands(), ...createEnvsubstCommands(), ...createCalCommands(), ...createPathchkCommands(), ...createGetconfCommands(), ...createLocaleCommands(), ...createDfCommands(), ...createSqlite3Commands()].map(cmd => [cmd.name, cmd]));
2930
+ defaultRuntimeMuscleMemoryMap ??= new Map([...createStreamFormatCommands(), ...createStreamInspectionCommands(), ...createBcCommands(), ...createSpongeCommands(), ...createFdCommands(), ...createLessCommands(), ...createIdCommands(), ...createWhoamiCommands(), ...createUnameCommands(), ...createHostnameCommands(), ...createNprocCommands(), ...createShufCommands(), ...createDdCommands(), ...createNumfmtCommands(), ...createEnvsubstCommands(), ...createCalCommands(), ...createPathchkCommands(), ...createGetconfCommands(), ...createLocaleCommands(), ...createDfCommands(), ...createSqlite3Commands()].map(cmd => [cmd.name, cmd]));
2920
2931
  return defaultRuntimeMuscleMemoryMap.get(name);
2921
2932
  }
2922
2933
  const fastSubScratchArgs = [];
@@ -5902,6 +5913,8 @@ export class Runtime {
5902
5913
  const entry = assignment.entries[i];
5903
5914
  if (entry.append)
5904
5915
  return false;
5916
+ if (entry.value.parts.some(p => p.kind === "variable" && p.prefixNames === "@") && !this.canSyncArrayMembersWord(entry.value, rawState))
5917
+ return false;
5905
5918
  if (entry.index === undefined && rawState.braceexpand !== false && this.budget.limits.maxExpansionFields === Infinity && this.budget.limits.maxExpansionBytes === Infinity && entry.value.parts.length === 1 && !entry.value.parts[0].quoted && entry.value.parts[0].kind === "text" && entry.value.parts[0].value.includes("{")) {
5906
5919
  const braceItems = tryFastExpandBraceRange(entry.value, this.budget, undefined);
5907
5920
  if (braceItems && braceItems.length <= 1500) {
@@ -13572,6 +13585,10 @@ export class Runtime {
13572
13585
  if (this._syncLoopFnCheckDepth > 0 && (part.substring || getArraySelector(part)?.kind === "element"))
13573
13586
  return false;
13574
13587
  if (part.prefixNames !== undefined) {
13588
+ // Prefix @ may produce zero or multiple fields as loop state changes.
13589
+ // Scalar loop steps cannot preserve that argument cardinality.
13590
+ if (part.prefixNames === "@")
13591
+ return false;
13575
13592
  if (part.indirect ||
13576
13593
  part.specialParameter ||
13577
13594
  part.length ||
@@ -13812,6 +13829,10 @@ export class Runtime {
13812
13829
  }
13813
13830
  }
13814
13831
  }
13832
+ // These evaluators can decline based on runtime input. Loop execution
13833
+ // has no continuation for a declined substitution after earlier effects.
13834
+ if (w0Plain === "tac" || w0Plain === "nl" || (w0Plain === "base64" && cmd.words.length > 1))
13835
+ return false;
13815
13836
  if (w0Plain === "pwd") {
13816
13837
  if (p.commands.length === 1 && (cmd.words.length === 1 || (cmd.words.length === 2 && (cmd.words[1]?.plain === "-L" || cmd.words[1]?.plain === "--logical"))) && !rawState.functions.has("pwd") && !rawState.extensions?.builtins.has("pwd"))
13817
13838
  continue;
@@ -13841,13 +13862,13 @@ export class Runtime {
13841
13862
  w0Plain === "sort" ||
13842
13863
  w0Plain === "uniq" ||
13843
13864
  w0Plain === "tr" ||
13844
- (hasSingleHereStringRedir && (w0Plain === "base64" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl"));
13845
- const def = w0Plain ? (this.commands.get(w0Plain) ?? ((w0Plain === "rev" || w0Plain === "tac") ? this.getExternalCommand(w0Plain) : undefined)) : undefined;
13865
+ (hasSingleHereStringRedir && (w0Plain === "base64" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste" || w0Plain === "numfmt"));
13866
+ const def = w0Plain ? (this.commands.get(w0Plain) ?? ((w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste" || w0Plain === "jq") ? this.getExternalCommand(w0Plain) : undefined)) : undefined;
13846
13867
  if (!w0Plain || (w0Plain !== "printf" && w0Plain !== "echo" && w0Plain !== "dirname" && w0Plain !== "basename" && w0Plain !== "seq" && !isFileCommand) || !def || rawState.functions.has(w0Plain) || rawState.extensions?.builtins.has(w0Plain))
13847
13868
  return false;
13848
13869
  if ((hasSingleStdinRedir || hasSingleHereStringRedir) && !isFileCommand)
13849
13870
  return false;
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))
13871
+ if (w0Plain === "printf" ? def.execute !== printfCommand.execute : w0Plain === "echo" ? !defaultEchoExecutors.has(def.execute) : (w0Plain === "seq" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste" || w0Plain === "jq") ? (!builtInDirectContextExecutors.has(def.execute) && customRegisteredCommands.has(def.execute)) : !builtInDirectContextExecutors.has(def.execute))
13851
13872
  return false;
13852
13873
  if (!cmd.words.slice(1).every(w => this.isPureSyncValueWord(w, rawState)))
13853
13874
  return false;
@@ -13868,7 +13889,7 @@ export class Runtime {
13868
13889
  const opCount = opWords.length;
13869
13890
  const op0 = opWords[0]?.plain;
13870
13891
  const op1 = opWords[1]?.plain;
13871
- if (w0Plain === "cat" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl") {
13892
+ if (w0Plain === "cat" || w0Plain === "rev" || w0Plain === "tac") {
13872
13893
  if (opCount !== 0)
13873
13894
  return false;
13874
13895
  }
@@ -13877,8 +13898,8 @@ export class Runtime {
13877
13898
  return false;
13878
13899
  if (w0Plain === "head" || w0Plain === "tail") {
13879
13900
  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));
13901
+ (opCount === 1 && op0 !== undefined && (/^-(?:n|c|--lines=|--bytes=)?[0-9]{1,5}$/.test(op0) || /^--(?:lines|bytes)=[0-9]{1,5}$/.test(op0) || (w0Plain === "tail" && /^(?:-n|--lines=)\+[0-9]{1,5}$/.test(op0)) || (w0Plain === "head" && /^(?:-n|--lines=)-[0-9]{1,5}$/.test(op0)))) ||
13902
+ (opCount === 2 && ((op0 === "-n" && op1 !== undefined && (/^[0-9]{1,5}$/.test(op1) || (w0Plain === "tail" && /^\+[0-9]{1,5}$/.test(op1)) || (w0Plain === "head" && /^-[0-9]{1,5}$/.test(op1)))) || (op0 === "-c" && op1 !== undefined && /^[0-9]{1,5}$/.test(op1))));
13882
13903
  if (!validHT)
13883
13904
  return false;
13884
13905
  }
@@ -13891,7 +13912,8 @@ export class Runtime {
13891
13912
  plainOps.push(pw);
13892
13913
  }
13893
13914
  if (w0Plain === "cut") {
13894
- if (plainOps.length !== opCount || this.evalSyncCut([], plainOps, byteLocale(rawState.variables)) === undefined)
13915
+ // Probe non-ASCII input: loop data can change after admission.
13916
+ if (plainOps.length !== opCount || this.evalSyncCut(["é"], plainOps, byteLocale(rawState.variables)) === undefined)
13895
13917
  return false;
13896
13918
  }
13897
13919
  else if (w0Plain === "sed") {
@@ -13907,19 +13929,22 @@ export class Runtime {
13907
13929
  return false;
13908
13930
  }
13909
13931
  else if (w0Plain === "jq") {
13910
- if (plainOps.length !== opCount || this.evalSyncJq("null", plainOps) === undefined)
13932
+ if (plainOps.length !== opCount)
13933
+ return false;
13934
+ const jqIn = this.extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
13935
+ if (jqIn === undefined || this.evalSyncJq(jqIn, plainOps) === undefined)
13911
13936
  return false;
13912
13937
  }
13913
13938
  else if (w0Plain === "wc") {
13914
- if (opCount !== 1 || (op0 !== "-l" && op0 !== "-c" && op0 !== "-w"))
13939
+ if (opCount !== 1 || this.normalizeSyncWcFlag(op0) === undefined)
13915
13940
  return false;
13916
13941
  }
13917
13942
  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"))
13943
+ if (plainOps.length !== opCount || this.evalSyncSort([], plainOps, byteLocale(rawState.variables)) === undefined)
13919
13944
  return false;
13920
13945
  }
13921
13946
  else if (w0Plain === "uniq") {
13922
- if (opCount > 1 || (opCount === 1 && op0 !== "-d" && op0 !== "-u" && op0 !== "-c"))
13947
+ if (plainOps.length !== opCount || this.evalSyncUniq([], plainOps) === undefined)
13923
13948
  return false;
13924
13949
  }
13925
13950
  else if (w0Plain === "base64") {
@@ -13930,24 +13955,57 @@ export class Runtime {
13930
13955
  if (plainOps.length !== opCount || this.evalSyncTr("", plainOps) === undefined)
13931
13956
  return false;
13932
13957
  }
13958
+ else if (w0Plain === "nl") {
13959
+ if (plainOps.length !== opCount || this.evalSyncNl([], plainOps) === undefined)
13960
+ return false;
13961
+ }
13962
+ else if (w0Plain === "paste") {
13963
+ if (plainOps.length !== opCount || this.evalSyncPaste([], plainOps) === undefined)
13964
+ return false;
13965
+ }
13966
+ else if (w0Plain === "numfmt") {
13967
+ const nmCheck = this.extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
13968
+ if (nmCheck === undefined || plainOps.length !== opCount || this.evalSyncNumfmt(nmCheck.split("\n"), plainOps) === undefined)
13969
+ return false;
13970
+ }
13933
13971
  }
13934
13972
  }
13935
13973
  if (w0Plain === "printf") {
13936
- const fmtW = cmd.words[1];
13937
- const fmtP = fmtW?.plain ?? (fmtW?.parts.length === 1 && fmtW.parts[0].kind === "text" ? fmtW.parts[0].value : undefined);
13938
- if (fmtP === undefined || fmtP.startsWith("-") || !/^(?:[^%]|%%|%[-0]*\d*(?:\.\d+)?[sdxXouc]|\\n)*$/.test(fmtP))
13974
+ if (!this.isSyncPrintfCallOk(cmd, 1, rawState, this._activePrintfInductionName))
13939
13975
  return false;
13940
13976
  }
13941
13977
  else if (w0Plain === "echo") {
13942
- if (cmd.words.length > 1 && (cmd.words[1].plain === undefined ? cmd.words[1].parts[0]?.kind === "text" && cmd.words[1].parts[0].value.startsWith("-") : cmd.words[1].plain.startsWith("-")))
13943
- return false;
13944
- }
13945
- else if (w0Plain === "dirname") {
13946
- if (cmd.words.length !== 2)
13947
- return false;
13978
+ const firstArg = cmd.words[1];
13979
+ if (firstArg) {
13980
+ const prefix = firstArg.parts.find(part => part.kind !== "text" || part.value.length > 0);
13981
+ // Substitution execution rejects options, including those that appear
13982
+ // only on later iterations. Require a stable non-option prefix.
13983
+ if (prefix?.kind !== "text" || prefix.value.startsWith("-"))
13984
+ return false;
13985
+ }
13948
13986
  }
13949
- else if (w0Plain === "basename") {
13950
- if (cmd.words.length !== 2 && cmd.words.length !== 3 && !(cmd.words.length === 4 && cmd.words[1]?.plain === "-s"))
13987
+ else if (w0Plain === "dirname" || w0Plain === "basename") {
13988
+ const simArgs = [];
13989
+ let seenDoubleDash = false;
13990
+ for (let wi = 1; wi < cmd.words.length; wi++) {
13991
+ const w = cmd.words[wi];
13992
+ // Tilde prefixes depend on mutable HOME/PWD/OLDPWD; use normal expansion.
13993
+ if (w.parts[0]?.kind === "text" && !w.parts[0].quoted && w.parts[0].value.startsWith("~"))
13994
+ return false;
13995
+ const st = w.plain ?? (w.parts.length > 0 && w.parts.every(pt => pt.kind === "text") ? w.parts.map(pt => pt.value).join("") : undefined);
13996
+ if (st !== undefined) {
13997
+ if (st === "--")
13998
+ seenDoubleDash = true;
13999
+ simArgs.push(st);
14000
+ }
14001
+ else {
14002
+ // A loop operand can become an option after admission.
14003
+ if (!seenDoubleDash)
14004
+ return false;
14005
+ simArgs.push("x");
14006
+ }
14007
+ }
14008
+ if ((w0Plain === "dirname" ? this.evalSyncDirname(simArgs) : this.evalSyncBasename(simArgs)) === undefined)
13951
14009
  return false;
13952
14010
  }
13953
14011
  else if (w0Plain === "seq") {
@@ -13971,18 +14029,20 @@ export class Runtime {
13971
14029
  if (p.commands.length >= 2) {
13972
14030
  if ((w0Plain !== "printf" && w0Plain !== "echo" && w0Plain !== "seq" && w0Plain !== "cat" && !hasSingleHereStringRedir) || syncPurePipelineSlotInUse || !this.budget.canSyncPurePipe || rawState.errexit || rawState.nounset)
13973
14031
  return false;
13974
- if (w0Plain === "printf" && !this.isSyncPrintfCallOk(cmd, 1, rawState))
13975
- return false;
14032
+ if (w0Plain === "printf") {
14033
+ if (!this.isSyncPrintfCallOk(cmd, 1, rawState, this._activePrintfInductionName))
14034
+ return false;
14035
+ }
13976
14036
  for (let sIdx = 1; sIdx < p.commands.length; sIdx++) {
13977
14037
  const sCmd = p.commands[sIdx];
13978
14038
  if (sCmd.kind !== "simple" || sCmd.redirects.length !== 0 || sCmd.words.length === 0)
13979
14039
  return false;
13980
14040
  const sName = sCmd.words[0].plain;
13981
14041
  // Range-aware tr and Buffer-free 76-col base64 are handled in pipeline stages.
13982
- if (!sName || (sName !== "cut" && sName !== "tr" && sName !== "sort" && sName !== "head" && sName !== "tail" && sName !== "wc" && sName !== "sed" && sName !== "uniq" && sName !== "rev" && sName !== "awk" && sName !== "grep" && sName !== "jq" && sName !== "base64" && sName !== "tac" && sName !== "nl"))
14042
+ if (!sName || (sName !== "cut" && sName !== "tr" && sName !== "sort" && sName !== "head" && sName !== "tail" && sName !== "wc" && sName !== "sed" && sName !== "uniq" && sName !== "rev" && sName !== "awk" && sName !== "grep" && sName !== "jq" && sName !== "base64" && sName !== "tac" && sName !== "nl" && sName !== "paste" && sName !== "numfmt"))
13983
14043
  return false;
13984
- const sDef = this.commands.get(sName);
13985
- if (!sDef || customRegisteredCommands.has(sDef.execute) || (sName !== "rev" && sName !== "tac" && !builtInDirectContextExecutors.has(sDef.execute)))
14044
+ const sDef = this.commands.get(sName) ?? ((sName === "rev" || sName === "tac" || sName === "nl" || sName === "paste" || sName === "jq") ? this.getExternalCommand(sName) : undefined);
14045
+ if (!sDef || customRegisteredCommands.has(sDef.execute) || (sName !== "rev" && sName !== "tac" && sName !== "nl" && sName !== "paste" && sName !== "jq" && !builtInDirectContextExecutors.has(sDef.execute)))
13986
14046
  return false;
13987
14047
  if (rawState.functions.has(sName) || rawState.extensions?.builtins.has(sName))
13988
14048
  return false;
@@ -13991,7 +14051,7 @@ export class Runtime {
13991
14051
  let allPlainStageArgs = true;
13992
14052
  for (let k = 0; k < sArgWords.length; k++) {
13993
14053
  const sw = sArgWords[k];
13994
- const sp = sw.plain ?? (sw.parts.length === 1 && sw.parts[0].kind === "text" ? sw.parts[0].value : undefined);
14054
+ const sp = sw.plain ?? (sw.parts.length > 0 && sw.parts.every(pt => pt.kind === "text") ? sw.parts.map(pt => pt.value).join("") : undefined);
13995
14055
  if (sp === undefined) {
13996
14056
  allPlainStageArgs = false;
13997
14057
  break;
@@ -14000,55 +14060,78 @@ export class Runtime {
14000
14060
  }
14001
14061
  if (!allPlainStageArgs)
14002
14062
  return false;
14003
- if (sName === "sort" && (sPlainArgs.length > 1 || (sPlainArgs.length === 1 && sPlainArgs[0] !== "-r")))
14004
- return false;
14005
- if (sName === "wc" && (sPlainArgs.length !== 1 || (sPlainArgs[0] !== "-l" && sPlainArgs[0] !== "-c" && sPlainArgs[0] !== "-w")))
14006
- return false;
14007
- if (sName === "head" || sName === "tail") {
14008
- const okHT = (sPlainArgs.length === 2 && sPlainArgs[0] === "-n" && /^[0-9]+$/.test(sPlainArgs[1])) || (sPlainArgs.length === 1 && /^-(?:n)?[0-9]+$/.test(sPlainArgs[0]));
14063
+ if (sName === "sort") {
14064
+ if (this.evalSyncSort([], sPlainArgs, byteLocale(rawState.variables)) === undefined)
14065
+ return false;
14066
+ }
14067
+ else if (sName === "uniq") {
14068
+ if (this.evalSyncUniq([], sPlainArgs) === undefined)
14069
+ return false;
14070
+ }
14071
+ else if (sName === "wc") {
14072
+ if (sPlainArgs.length !== 1 || this.normalizeSyncWcFlag(sPlainArgs[0]) === undefined)
14073
+ return false;
14074
+ }
14075
+ else if (sName === "head" || sName === "tail") {
14076
+ const okHT = sPlainArgs.length === 0 || (sPlainArgs.length === 1 && (/^-(?:n|c)?[0-9]{1,5}$/.test(sPlainArgs[0]) || /^--(?:lines|bytes)=[0-9]{1,5}$/.test(sPlainArgs[0]) || (sName === "tail" && /^(?:-n|--lines=)\+[0-9]{1,5}$/.test(sPlainArgs[0])) || (sName === "head" && /^(?:-n|--lines=)-[0-9]{1,5}$/.test(sPlainArgs[0])))) || (sPlainArgs.length === 2 && ((sPlainArgs[0] === "-n" && (/^[0-9]{1,5}$/.test(sPlainArgs[1]) || (sName === "tail" && /^\+[0-9]{1,5}$/.test(sPlainArgs[1])) || (sName === "head" && /^-[0-9]{1,5}$/.test(sPlainArgs[1])))) || (sPlainArgs[0] === "-c" && /^[0-9]{1,5}$/.test(sPlainArgs[1]))));
14009
14077
  if (!okHT)
14010
14078
  return false;
14011
14079
  }
14012
- if (sName === "cut") {
14013
- const normCut = [];
14014
- for (let ci = 0; ci < sPlainArgs.length; ci++) {
14015
- const ca = sPlainArgs[ci];
14016
- if ((ca === "-d" || ca === "-f") && ci + 1 < sPlainArgs.length) {
14017
- normCut.push(ca + sPlainArgs[++ci]);
14018
- }
14019
- else {
14020
- normCut.push(ca);
14021
- }
14022
- }
14023
- const vc2 = normCut.length === 2 && ((normCut[0].length === 3 && normCut[0].startsWith("-d") && normCut[1].length >= 3 && normCut[1].startsWith("-f")) || (normCut[0] === "-c" && /^[1-9][0-9]*-[1-9][0-9]*$/.test(normCut[1])));
14024
- const vc1 = normCut.length === 1 && normCut[0].length >= 3 && normCut[0].startsWith("-f");
14025
- if (!vc1 && !vc2)
14080
+ else if (sName === "cut") {
14081
+ // Byte cuts may reject non-ASCII data on a later iteration.
14082
+ if (this.evalSyncCut(["é"], sPlainArgs, byteLocale(rawState.variables)) === undefined)
14026
14083
  return false;
14027
14084
  }
14028
- if (sName === "awk") {
14029
- const normAwk = sPlainArgs.length === 3 && sPlainArgs[0] === "-F" ? ["-F" + sPlainArgs[1], sPlainArgs[2]] : sPlainArgs;
14030
- const awkSepOk = normAwk.length === 1 || (normAwk.length === 2 && normAwk[0].length === 3 && normAwk[0].startsWith("-F") && normAwk[0][2] !== "\\");
14031
- const awkProg = normAwk[normAwk.length - 1] ?? "";
14032
- if (!awkSepOk || !/^\s*\{\s*print(?:\s+(\$(?:[0-9]+|NF)(?:\s*,\s*\$(?:[0-9]+|NF))*))?\s*;?\s*\}\s*$/.test(awkProg))
14085
+ else if (sName === "sed") {
14086
+ if (this.evalSyncSed([], sPlainArgs) === undefined)
14033
14087
  return false;
14034
14088
  }
14035
- if (sName === "grep") {
14036
- const grepOk = (sPlainArgs.length === 1 && !sPlainArgs[0].startsWith("-") && /^[a-zA-Z0-9_ :;,/-]+$/.test(sPlainArgs[0])) ||
14037
- (sPlainArgs.length === 2 && (sPlainArgs[0] === "-v" || sPlainArgs[0] === "-i" || sPlainArgs[0] === "-c" || sPlainArgs[0] === "-F") && /^[a-zA-Z0-9_ :;,/-]+$/.test(sPlainArgs[1]));
14038
- if (!grepOk)
14089
+ else if (sName === "awk") {
14090
+ if (this.evalSyncAwk([], sPlainArgs) === undefined)
14039
14091
  return false;
14040
14092
  }
14041
- if (sName === "jq") {
14042
- // JSON types are runtime inputs: the speculative evaluator may
14043
- // refuse array indexing and must leave diagnostics to jq.
14044
- return false;
14093
+ else if (sName === "grep") {
14094
+ if (this.evalSyncGrep([], sPlainArgs, Boolean(rawState.errexit)) === undefined)
14095
+ return false;
14045
14096
  }
14046
- if (sName === "base64") {
14047
- if (sPlainArgs.length > 1 || (sPlainArgs.length === 1 && sPlainArgs[0] !== "-d" && sPlainArgs[0] !== "--decode"))
14097
+ else if (sName === "tr") {
14098
+ if (this.evalSyncTr("", sPlainArgs) === undefined)
14099
+ return false;
14100
+ }
14101
+ else if (sName === "jq") {
14102
+ if (sIdx !== 1)
14103
+ return false;
14104
+ const jqIn = this.extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
14105
+ if (jqIn === undefined || this.evalSyncJq(jqIn, sPlainArgs) === undefined)
14106
+ return false;
14107
+ }
14108
+ else if (sName === "base64") {
14109
+ if (sPlainArgs.length !== 0)
14110
+ return false;
14111
+ }
14112
+ else if (sName === "tac") {
14113
+ if (sPlainArgs.length !== 0 || (w0Plain !== "echo" && !hasSingleHereStringRedir))
14114
+ return false;
14115
+ }
14116
+ else if (sName === "nl") {
14117
+ if ((w0Plain !== "echo" && !hasSingleHereStringRedir) || this.evalSyncNl([], sPlainArgs) === undefined)
14118
+ return false;
14119
+ }
14120
+ else if (sName === "paste") {
14121
+ if (this.evalSyncPaste([], sPlainArgs) === undefined)
14122
+ return false;
14123
+ }
14124
+ else if (sName === "numfmt") {
14125
+ if (sIdx !== 1)
14126
+ return false;
14127
+ const nmCheck = this.extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
14128
+ if (nmCheck === undefined || this.evalSyncNumfmt(nmCheck.split("\n"), sPlainArgs) === undefined)
14129
+ return false;
14130
+ }
14131
+ else if (sName === "rev") {
14132
+ if (sPlainArgs.length !== 0)
14048
14133
  return false;
14049
14134
  }
14050
- if ((sName === "tac" || sName === "nl") && sPlainArgs.length !== 0)
14051
- return false;
14052
14135
  }
14053
14136
  }
14054
14137
  continue;
@@ -14482,7 +14565,7 @@ export class Runtime {
14482
14565
  else if (p === "-n" || p === "-rn" || p === "-nr" || p === "-N" || p === "-rN" || p === "-Nr") {
14483
14566
  if (p.includes("r"))
14484
14567
  rawMode = true;
14485
- exactChars = p.includes("N");
14568
+ exactChars ||= p.includes("N");
14486
14569
  const nPlain = cmd.words[argIdx + 1]?.plain;
14487
14570
  if (!nPlain || !/^[1-9][0-9]{0,5}$/.test(nPlain) || byteLocale(rawState.variables))
14488
14571
  return undefined;
@@ -14494,7 +14577,7 @@ export class Runtime {
14494
14577
  return undefined;
14495
14578
  if (p.startsWith("-r"))
14496
14579
  rawMode = true;
14497
- exactChars = p.includes("N");
14580
+ exactChars ||= p.includes("N");
14498
14581
  maxChars = Number(p.slice(p.startsWith("-r") ? 3 : 2));
14499
14582
  argIdx++;
14500
14583
  }
@@ -14707,12 +14790,12 @@ export class Runtime {
14707
14790
  ifOk = false;
14708
14791
  break;
14709
14792
  }
14710
- if (!this.canSyncLoopBody(br.body, rawState, io, true)) {
14793
+ if (!this.canSyncLoopBody(br.body, rawState, io, true, printfInductionName)) {
14711
14794
  ifOk = false;
14712
14795
  break;
14713
14796
  }
14714
14797
  }
14715
- if (ifOk && cmd.otherwise && !this.canSyncLoopBody(cmd.otherwise, rawState, io, true))
14798
+ if (ifOk && cmd.otherwise && !this.canSyncLoopBody(cmd.otherwise, rawState, io, true, printfInductionName))
14716
14799
  ifOk = false;
14717
14800
  if (ifOk)
14718
14801
  continue;
@@ -14733,7 +14816,7 @@ export class Runtime {
14733
14816
  caseOk = false;
14734
14817
  break;
14735
14818
  }
14736
- if (!this.canSyncLoopBody(cl.body, rawState, io, true)) {
14819
+ if (!this.canSyncLoopBody(cl.body, rawState, io, true, printfInductionName)) {
14737
14820
  caseOk = false;
14738
14821
  break;
14739
14822
  }
@@ -15455,17 +15538,45 @@ export class Runtime {
15455
15538
  const condition = command.kind === "arithmetic-for" ? command.expressions[1]?.tree : undefined;
15456
15539
  const increment = command.kind === "arithmetic-for" ? command.expressions[2]?.tree : undefined;
15457
15540
  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));
15541
+ const whileTree = whileCondCmd?.kind === "arithmetic" && !whileCondCmd.expression.error ? whileCondCmd.expression.tree : undefined;
15542
+ const whileName = whileTree?.kind === "binary" && (whileTree.operator === "<" || whileTree.operator === "<=") &&
15543
+ whileTree.left.kind === "name" && whileTree.right.kind === "literal" &&
15544
+ whileTree.right.value >= 0n && whileTree.right.value <= 2000n ? whileTree.left.name : undefined;
15545
+ const whileValue = whileName === undefined ? undefined : rawState.variables[whileName];
15546
+ const whileStart = typeof whileValue === "string" ? Number(whileValue) : NaN;
15547
+ const whilePrintfInd = Number.isInteger(whileStart) && whileStart >= 0 && whileStart <= 2000 &&
15548
+ String(whileStart) === whileValue ? whileName : undefined;
15549
+ const forPrintfInd = command.kind === "for" && command.words?.length === 1 && /^\{[0-9]{1,4}\.\.[0-9]{1,4}(?:\.\.[1-9][0-9]{0,2})?\}$/.test(command.words[0].plain ?? "") ? command.name : undefined;
15459
15550
  const printfInductionName = initializer?.kind === "binary" && initializer.operator === "=" && initializer.left.kind === "name" &&
15460
15551
  initializer.right.kind === "literal" && initializer.right.value >= 0n && initializer.right.value <= 2000n &&
15461
15552
  condition?.kind === "binary" && (condition.operator === "<" || condition.operator === "<=") &&
15462
15553
  condition.left.kind === "name" && condition.left.name === initializer.left.name &&
15463
15554
  condition.right.kind === "literal" && condition.right.value >= 0n && condition.right.value <= 2000n &&
15464
15555
  increment?.kind === "unary" && increment.operator === "++" && increment.operand.kind === "name" && increment.operand.name === initializer.left.name &&
15465
- this.canSyncPrintfInductionBody(command.body, rawState)
15556
+ this.canSyncPrintfInductionBody(command.body, rawState, initializer.left.name)
15466
15557
  ? initializer.left.name
15467
- : (whilePrintfInd && this.canSyncPrintfInductionBody(command.body, rawState) ? whilePrintfInd : undefined);
15468
- if ((guestArrays(rawState) && (guestArrays(rawState).watches.size > 0 || this.budget.limits.maxExpansionBytes < 65536)) || !this.canSyncLoopBody(command.body, rawState, io, false, printfInductionName)) {
15558
+ : (whilePrintfInd && this.canSyncPrintfInductionBody(command.body, rawState, whilePrintfInd)
15559
+ ? whilePrintfInd
15560
+ : (forPrintfInd && this.canSyncPrintfInductionBody(command.body, rawState, forPrintfInd) ? forPrintfInd : undefined));
15561
+ const prevActivePrintfInd = this._activePrintfInductionName;
15562
+ const prevActiveLoopScript = this._activeLoopBodyScript;
15563
+ const prevActiveLoopCond = this._activeLoopCondScript;
15564
+ const prevActiveLoopVar = this._activeLoopVarName;
15565
+ this._activePrintfInductionName = printfInductionName;
15566
+ this._activeLoopBodyScript = command.body;
15567
+ this._activeLoopCondScript = command.kind === "while" ? command.condition : undefined;
15568
+ this._activeLoopVarName = command.kind === "for" ? command.name : (printfInductionName ?? (whilePrintfInd || undefined));
15569
+ let bodySyncOk = false;
15570
+ try {
15571
+ bodySyncOk = !(guestArrays(rawState) && (guestArrays(rawState).watches.size > 0 || this.budget.limits.maxExpansionBytes < 65536)) && this.canSyncLoopBody(command.body, rawState, io, false, printfInductionName);
15572
+ }
15573
+ finally {
15574
+ this._activePrintfInductionName = prevActivePrintfInd;
15575
+ this._activeLoopBodyScript = prevActiveLoopScript;
15576
+ this._activeLoopCondScript = prevActiveLoopCond;
15577
+ this._activeLoopVarName = prevActiveLoopVar;
15578
+ }
15579
+ if (!bodySyncOk) {
15469
15580
  command._skipTrySyncLoop = true;
15470
15581
  return undefined;
15471
15582
  }
@@ -15648,7 +15759,8 @@ export class Runtime {
15648
15759
  }
15649
15760
  const wordsToScan = step.arrayAssign?.kind === "compound"
15650
15761
  ? step.arrayAssign.entries.map(e => e.value)
15651
- : step.value ? [step.value] : [];
15762
+ : step.printfVArgs ? step.printfVArgs
15763
+ : step.value ? [step.value] : [];
15652
15764
  for (let wIdx = 0; wIdx < wordsToScan.length; wIdx++) {
15653
15765
  const wVal = wordsToScan[wIdx];
15654
15766
  for (let i = 0; i < wVal.parts.length; i++) {
@@ -16896,29 +17008,230 @@ export class Runtime {
16896
17008
  }
16897
17009
  return pos <= 128 ? fastRedirectScratchViews[pos] : fastRedirectScratchBytes.subarray(0, pos);
16898
17010
  }
16899
- canSyncPrintfInductionBody(script, rawState) {
16900
- // Arithmetic, parameter operators and functions can mutate the counter
16901
- // indirectly. Keep this proof to simple commands and isolated substitutions;
16902
- // the loop plan separately rejects direct assignments to its counter.
17011
+ _activePrintfInductionName = undefined;
17012
+ _activeLoopBodyScript = undefined;
17013
+ _activeLoopCondScript = undefined;
17014
+ _activeLoopVarName = undefined;
17015
+ isVarUnmutatedInScript(varName, script) {
17016
+ for (const list of script.lists) {
17017
+ for (const pipeline of list.pipelines) {
17018
+ for (const cmd of pipeline.commands) {
17019
+ if (cmd.kind === "arithmetic") {
17020
+ if (cmd.expression.hasMutation && cmd.expression.source.includes(varName))
17021
+ return false;
17022
+ continue;
17023
+ }
17024
+ if (cmd.kind === "if") {
17025
+ for (const br of cmd.branches) {
17026
+ if (!this.isVarUnmutatedInScript(varName, br.condition) || !this.isVarUnmutatedInScript(varName, br.body))
17027
+ return false;
17028
+ }
17029
+ if (cmd.otherwise && !this.isVarUnmutatedInScript(varName, cmd.otherwise))
17030
+ return false;
17031
+ continue;
17032
+ }
17033
+ if (cmd.kind === "case") {
17034
+ for (const cl of cmd.clauses) {
17035
+ if (!this.isVarUnmutatedInScript(varName, cl.body))
17036
+ return false;
17037
+ }
17038
+ continue;
17039
+ }
17040
+ if (cmd.kind === "for") {
17041
+ if (cmd.name === varName || !this.isVarUnmutatedInScript(varName, cmd.body))
17042
+ return false;
17043
+ continue;
17044
+ }
17045
+ if (cmd.kind === "arithmetic-for") {
17046
+ if (cmd.expressions.some(e => e && e.hasMutation && e.source.includes(varName)) || !this.isVarUnmutatedInScript(varName, cmd.body))
17047
+ return false;
17048
+ continue;
17049
+ }
17050
+ if (cmd.kind === "while") {
17051
+ if (!this.isVarUnmutatedInScript(varName, cmd.condition) || !this.isVarUnmutatedInScript(varName, cmd.body))
17052
+ return false;
17053
+ continue;
17054
+ }
17055
+ if (cmd.kind === "group" || cmd.kind === "subshell") {
17056
+ if (!this.isVarUnmutatedInScript(varName, cmd.body))
17057
+ return false;
17058
+ continue;
17059
+ }
17060
+ if (cmd.kind !== "simple")
17061
+ return false;
17062
+ for (const w of cmd.words) {
17063
+ const arrAss = getArrayAssignment(w);
17064
+ if (arrAss && arrAss.name === varName)
17065
+ return false;
17066
+ const p0 = w.parts[0];
17067
+ if (p0?.kind === "text" && !p0.quoted && (p0.value.startsWith(varName + "=") || p0.value.startsWith(varName + "+=") || p0.value.startsWith(varName + "[")))
17068
+ return false;
17069
+ }
17070
+ const w0 = cmd.words[0]?.plain;
17071
+ if (w0 === "read" || w0 === "unset" || w0 === "declare" || w0 === "typeset" || w0 === "local" || w0 === "export" || w0 === "mapfile" || w0 === "readarray") {
17072
+ if (w0 === "read" && varName === "REPLY")
17073
+ return false;
17074
+ if (cmd.words.slice(1).some(w => (w.plain ?? "").includes(varName)))
17075
+ return false;
17076
+ }
17077
+ if (w0 === "printf" && cmd.words[1]?.plain === "-v" && (cmd.words[2]?.plain ?? "").startsWith(varName))
17078
+ return false;
17079
+ for (const w of [...cmd.words, ...cmd.redirects.map(r => r.target)]) {
17080
+ for (const pt of w.parts) {
17081
+ if (pt.kind === "arithmetic" && pt.expression.hasMutation && pt.expression.source.includes(varName))
17082
+ return false;
17083
+ if (pt.kind === "substitution" && !this.isVarUnmutatedInScript(varName, pt.script))
17084
+ return false;
17085
+ }
17086
+ }
17087
+ }
17088
+ }
17089
+ }
17090
+ return true;
17091
+ }
17092
+ isVarUnmutatedInActiveLoop(varName) {
17093
+ if (varName === this._activeLoopVarName || varName === this._activePrintfInductionName)
17094
+ return false;
17095
+ if (this._activeLoopCondScript && !this.isVarUnmutatedInScript(varName, this._activeLoopCondScript))
17096
+ return false;
17097
+ if (this._activeLoopBodyScript && !this.isVarUnmutatedInScript(varName, this._activeLoopBodyScript))
17098
+ return false;
17099
+ return true;
17100
+ }
17101
+ isFileReadOnlyScript(script, rawState) {
17102
+ const visit = (value) => {
17103
+ if (value === null || typeof value !== "object")
17104
+ return true;
17105
+ if (Array.isArray(value))
17106
+ return value.every(visit);
17107
+ const node = value;
17108
+ if (typeof node.operator === "string" && "target" in node && !["<", "<<<", "<<", "<<-"].includes(node.operator))
17109
+ return false;
17110
+ if (node.kind === "simple") {
17111
+ const cmd = value;
17112
+ // Unknown commands (including shell functions) cannot promise read-only I/O.
17113
+ const name = cmd.words[0]?.plain;
17114
+ if (name === undefined && !cmd.words.every(word => this.assignment(word) || getArrayAssignment(word)))
17115
+ return false;
17116
+ if (name !== undefined && (hasShellFunction(rawState, name) || !["jq", "cat", "echo", "printf", "test", "[", ":", "true", "false"].includes(name)))
17117
+ return false;
17118
+ }
17119
+ return Object.values(node).every(visit);
17120
+ };
17121
+ return visit(script);
17122
+ }
17123
+ extractLoopInvariantJqInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState) {
17124
+ const w0 = cmd.words[0]?.plain;
17125
+ const resolveInvariantWord = (w) => {
17126
+ if (w.plain !== undefined)
17127
+ return w.plain;
17128
+ if (w.parts.length > 0 && w.parts.every(pt => pt.kind === "text"))
17129
+ return w.parts.map(pt => pt.value).join("");
17130
+ if (w.parts.length === 1 && w.parts[0].kind === "variable") {
17131
+ const vp = w.parts[0];
17132
+ if (!vp.indirect && !vp.operator && !vp.substring && !vp.transform && !vp.length && !vp.prefixNames && getArraySelector(vp) === undefined && this.isVarUnmutatedInActiveLoop(vp.name)) {
17133
+ const val = rawState.variables[vp.name];
17134
+ if (typeof val === "string")
17135
+ return val;
17136
+ }
17137
+ }
17138
+ return undefined;
17139
+ };
17140
+ if (w0 === "jq" || w0 === "numfmt") {
17141
+ if (hasSingleHereStringRedir) {
17142
+ const resolved = resolveInvariantWord(cmd.redirects[0].target);
17143
+ return resolved !== undefined ? resolved.trim() : undefined;
17144
+ }
17145
+ if (this._fileWrites?.size || this._outputFiles?.size)
17146
+ return undefined;
17147
+ if ([this._activeLoopBodyScript, this._activeLoopCondScript].some(script => script && !this.isFileReadOnlyScript(script, rawState)))
17148
+ return undefined;
17149
+ const fPlain = hasSingleStdinRedir ? cmd.redirects[0].target.plain : cmd.words[cmd.words.length - 1]?.plain;
17150
+ if (!fPlain || fPlain.startsWith("-") || fPlain === "/dev/stdin")
17151
+ return undefined;
17152
+ const vCheck = this.tryReadMemoryFileViewSync(resolvePath(rawState.cwd, fPlain), false);
17153
+ if (!vCheck || vCheck.byteLength > 16384 || vCheck.includes(0))
17154
+ return undefined;
17155
+ return sharedSyncPipeDecoder.decode(vCheck).trim();
17156
+ }
17157
+ if (w0 === "cat") {
17158
+ if (hasSingleHereStringRedir) {
17159
+ const resolved = resolveInvariantWord(cmd.redirects[0].target);
17160
+ return resolved !== undefined ? resolved.trim() : undefined;
17161
+ }
17162
+ if (this._fileWrites?.size || this._outputFiles?.size)
17163
+ return undefined;
17164
+ if ([this._activeLoopBodyScript, this._activeLoopCondScript].some(script => script && !this.isFileReadOnlyScript(script, rawState)))
17165
+ return undefined;
17166
+ const fPlain = hasSingleStdinRedir ? cmd.redirects[0].target.plain : cmd.words[1]?.plain;
17167
+ if (!fPlain || fPlain.startsWith("-") || fPlain === "/dev/stdin")
17168
+ return undefined;
17169
+ const vCheck = this.tryReadMemoryFileViewSync(resolvePath(rawState.cwd, fPlain), false);
17170
+ if (!vCheck || vCheck.byteLength > 16384 || vCheck.includes(0))
17171
+ return undefined;
17172
+ return sharedSyncPipeDecoder.decode(vCheck).trim();
17173
+ }
17174
+ if (w0 === "echo" && cmd.words.length === 2) {
17175
+ const resolved = resolveInvariantWord(cmd.words[1]);
17176
+ if (resolved !== undefined && !resolved.startsWith("-"))
17177
+ return resolved.trim();
17178
+ return undefined;
17179
+ }
17180
+ if (w0 === "printf" && cmd.words.length >= 2) {
17181
+ const args = [];
17182
+ for (let i = 1; i < cmd.words.length; i++) {
17183
+ const r = resolveInvariantWord(cmd.words[i]);
17184
+ if (r === undefined)
17185
+ return undefined;
17186
+ args.push(r);
17187
+ }
17188
+ const formatted = tryFastPrintf(args);
17189
+ return formatted !== undefined ? formatted.trim() : undefined;
17190
+ }
17191
+ return undefined;
17192
+ }
17193
+ canSyncPrintfInductionBody(script, rawState, inductionName) {
16903
17194
  for (const list of script.lists)
16904
17195
  for (const pipeline of list.pipelines)
16905
17196
  for (const command of pipeline.commands) {
16906
17197
  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")
17198
+ 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" && command.expression.tree.operand.name !== inductionName)) && command.expression.tree.operand.kind === "name")
16908
17199
  continue;
16909
17200
  return false;
16910
17201
  }
16911
17202
  if (command.kind !== "simple" || hasShellFunction(rawState, command.words[0]?.plain ?? "") || command.words.some(word => getArrayAssignment(word) !== undefined))
16912
17203
  return false;
17204
+ const w0 = command.words[0]?.plain;
17205
+ if (w0 === "let" || w0 === "declare" || w0 === "typeset" || w0 === "local" || w0 === "export" || w0 === "unset" || w0 === "mapfile" || w0 === "readarray")
17206
+ return false;
17207
+ if (inductionName !== undefined) {
17208
+ if (command.words.some(w => {
17209
+ const p0 = w.parts[0];
17210
+ return p0?.kind === "text" && !p0.quoted && (p0.value.startsWith(inductionName + "=") || p0.value.startsWith(inductionName + "+="));
17211
+ }))
17212
+ return false;
17213
+ if (w0 === "read" && (inductionName === "REPLY" || command.words.slice(1).some(w => w.plain === inductionName || w.plain === "-a" || w.plain === "-ra" || w.plain === "-ar")))
17214
+ return false;
17215
+ if (w0 === "printf" && command.words[1]?.plain === "-v" && command.words[2]?.plain === inductionName)
17216
+ return false;
17217
+ }
16913
17218
  for (const word of [...command.words, ...command.redirects.map(redirect => redirect.target)])
16914
17219
  for (const part of word.parts) {
16915
17220
  if (part.kind === "text")
16916
17221
  continue;
16917
- if (part.kind === "variable" && !part.indirect && !part.length && !part.substring && !part.transform && !part.prefixNames && part.operator === undefined && getArraySelector(part) === undefined)
17222
+ if (part.kind === "variable" &&
17223
+ !part.indirect &&
17224
+ !part.substring &&
17225
+ !part.prefixNames &&
17226
+ getArraySelector(part) === undefined &&
17227
+ (part.operator === undefined ||
17228
+ ((part.operator === "#" || part.operator === "##" || part.operator === "%" || part.operator === "%%") &&
17229
+ part.alternate !== undefined &&
17230
+ part.alternate.parts.every(p => p.kind === "text"))))
16918
17231
  continue;
16919
17232
  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
17233
  continue;
16921
- if (part.kind === "substitution" && this.canSyncPrintfInductionBody(part.script, rawState))
17234
+ if (part.kind === "substitution" && this.canSyncPrintfInductionBody(part.script, rawState, inductionName))
16922
17235
  continue;
16923
17236
  return false;
16924
17237
  }
@@ -16929,41 +17242,86 @@ export class Runtime {
16929
17242
  const printfDef = this.commands.get("printf");
16930
17243
  if (hasShellFunction(rawState, "printf") || rawState.extensions?.builtins.has("printf") || printfDef?.execute !== printfCommand.execute)
16931
17244
  return false;
16932
- // Runtime operands can leave the fast formatter's subset after earlier
16933
- // effects. Admit only literals and the arithmetic plan's bounded induction.
16934
- const args = [];
17245
+ const effInductionName = inductionName ?? this._activePrintfInductionName;
17246
+ // Prove unsigned arithmetic stays non-negative and within the Smi evaluator's
17247
+ // range. Names are matched as AST nodes, never as substrings of other names.
17248
+ const unsignedUpperBound = (tree) => {
17249
+ if (!tree)
17250
+ return undefined;
17251
+ let bound;
17252
+ if (tree.kind === "literal")
17253
+ bound = tree.value;
17254
+ else if (tree.kind === "name" && tree.name === effInductionName && tree.subscript === undefined)
17255
+ bound = 2147483647n;
17256
+ else if (tree.kind === "binary" && (tree.operator === "+" || tree.operator === "*")) {
17257
+ const left = unsignedUpperBound(tree.left);
17258
+ const right = unsignedUpperBound(tree.right);
17259
+ if (left !== undefined && right !== undefined)
17260
+ bound = tree.operator === "+" ? left + right : left * right;
17261
+ }
17262
+ return bound !== undefined && bound >= 0n && bound <= 2147483647n ? bound : undefined;
17263
+ };
16935
17264
  const fmtWordIdx = cmd.words[argStartIdx]?.plain === "--" ? argStartIdx + 1 : argStartIdx;
16936
17265
  if (fmtWordIdx >= cmd.words.length)
16937
17266
  return false;
16938
- for (let i = argStartIdx; i < cmd.words.length; i++) {
17267
+ const fmtWord = cmd.words[fmtWordIdx];
17268
+ if (!this.isPureSyncValueWord(fmtWord, rawState))
17269
+ return false;
17270
+ let fmtStr = "";
17271
+ for (const part of fmtWord.parts) {
17272
+ 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))))
17273
+ return false;
17274
+ fmtStr += part.value;
17275
+ }
17276
+ if (fmtWordIdx === argStartIdx && fmtStr.startsWith("-"))
17277
+ return false;
17278
+ const specs = extractFastPrintfSpecifiers(fmtStr);
17279
+ if (!specs)
17280
+ return false;
17281
+ const args = [];
17282
+ if (fmtWordIdx > argStartIdx)
17283
+ args.push("--");
17284
+ args.push(fmtStr);
17285
+ for (let i = fmtWordIdx + 1; i < cmd.words.length; i++) {
16939
17286
  const w = cmd.words[i];
16940
17287
  if (!this.isPureSyncValueWord(w, rawState))
16941
17288
  return false;
16942
- const valueParts = w.parts.filter(part => part.kind !== "text" || part.value.length > 0);
16943
- const part = valueParts[0];
16944
- if (i > fmtWordIdx && valueParts.length === 1 && part?.kind === "variable" && part.name === inductionName && part.quoted && !part.indirect && !part.length && !part.substring && !part.transform && part.operator === undefined && getArraySelector(part) === undefined) {
16945
- args.push("0");
17289
+ const argIdx = i - fmtWordIdx - 1;
17290
+ const spec = specs.length > 0 ? specs[argIdx % specs.length] : "s";
17291
+ const nonSplitting = w.parts.length > 0 && w.parts.every(part => part.quoted ||
17292
+ (part.kind === "text" && part.value.length > 0 && !part.value.includes(" ") && !part.value.includes("\t") && !part.value.includes("\n") && !part.value.includes("{") && !hasGlobOrEscape(part.value, true)));
17293
+ if (spec === "s" && nonSplitting) {
17294
+ args.push("");
16946
17295
  continue;
16947
17296
  }
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;
17297
+ const valueParts = w.parts.filter(part => part.kind !== "text" || part.value.length > 0);
17298
+ const part = valueParts[0];
17299
+ if ((spec === "d" || spec === "u") && valueParts.length === 1 && part?.quoted) {
17300
+ if (part.kind === "variable" && part.name === effInductionName && !part.indirect && !part.length && !part.substring && !part.transform && part.operator === undefined && getArraySelector(part) === undefined) {
17301
+ args.push("0");
17302
+ continue;
17303
+ }
17304
+ if (part.kind === "variable" && part.length && !part.indirect && !part.prefixNames && getArraySelector(part) === undefined) {
17305
+ args.push("0");
17306
+ continue;
17307
+ }
17308
+ if (part.kind === "arithmetic" &&
17309
+ !part.expression.error &&
17310
+ !part.expression.hasMutation &&
17311
+ !part.expression.hasSubscript &&
17312
+ isSafeSmiProgram(part.expression) &&
17313
+ /^[a-zA-Z0-9_ +*-]+$/.test(part.expression.source) &&
17314
+ this.canSyncArithmeticWithoutFault(part.expression.tree, rawState, cmd.line ?? 1) &&
17315
+ (spec === "d" || unsignedUpperBound(part.expression.tree) !== undefined)) {
17316
+ args.push("0");
17317
+ continue;
17318
+ }
16961
17319
  }
16962
17320
  let arg = "";
16963
- for (const part of w.parts) {
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))))
17321
+ for (const p of w.parts) {
17322
+ if (p.kind !== "text" || (!p.quoted && (p.value.length === 0 || p.value.includes(" ") || p.value.includes("\t") || p.value.includes("\n") || p.value.includes("{") || hasGlobOrEscape(p.value, true))))
16965
17323
  return false;
16966
- arg += part.value;
17324
+ arg += p.value;
16967
17325
  }
16968
17326
  args.push(arg);
16969
17327
  }
@@ -16975,6 +17333,15 @@ export class Runtime {
16975
17333
  return false;
16976
17334
  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));
16977
17335
  }
17336
+ syncStringLength(value, byteCount) {
17337
+ let length = 0;
17338
+ for (let offset = 0; offset < value.length;) {
17339
+ const point = value.codePointAt(offset);
17340
+ length += byteCount ? (point <= 0x7f ? 1 : point <= 0x7ff ? 2 : point <= 0xffff ? 3 : 4) : 1;
17341
+ offset += point > 0xffff ? 2 : 1;
17342
+ }
17343
+ return length;
17344
+ }
16978
17345
  execSyncReadHereStringStep(rs, step, rawState, io, monitor, touched) {
16979
17346
  const rawInput = this.fastValueWord(rs.inputWord, rawState, io, false, false, false, false, 0, step.line);
16980
17347
  const delim = rs.delimChar ?? "\n";
@@ -16982,16 +17349,16 @@ export class Runtime {
16982
17349
  let status = 0;
16983
17350
  if (rs.maxChars !== undefined) {
16984
17351
  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;
17352
+ const nlIdx = rs.exactChars ? -1 : fullInput.indexOf(delim);
17353
+ const end = nlIdx === -1 ? fullInput.length : nlIdx;
17354
+ let position = 0;
17355
+ let count = 0;
17356
+ while (position < end && count < rs.maxChars) {
17357
+ position = nextCodePointOffset(fullInput, position);
17358
+ count++;
16994
17359
  }
17360
+ lineStr = fullInput.slice(0, position);
17361
+ status = (count >= rs.maxChars || nlIdx !== -1) ? 0 : 1;
16995
17362
  }
16996
17363
  else {
16997
17364
  const fullInput = delim === "\n" ? rawInput : rawInput + "\n";
@@ -24600,8 +24967,8 @@ export class Runtime {
24600
24967
  invalid = true;
24601
24968
  break;
24602
24969
  }
24603
- if (flag === "n" || flag === "N")
24604
- exact = flag === "N";
24970
+ if (flag === "N")
24971
+ exact = true;
24605
24972
  const value = option.slice(index + 1) || names.shift();
24606
24973
  if (value === undefined)
24607
24974
  invalid = true;
@@ -25071,7 +25438,7 @@ export class Runtime {
25071
25438
  }
25072
25439
  if (part.kind === "variable" && part.prefixNames) {
25073
25440
  const ifs = state.variables.IFS ?? " ";
25074
- const separator = io.nameExpansionContext === "document" || io.nameExpansionContext === "conditional" && part.prefixNames === "@"
25441
+ const separator = io.nameExpansionContext === "document" || part.prefixNames === "@" && (io.nameExpansionContext === "conditional" || ifs.length === 0)
25075
25442
  ? " " : ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
25076
25443
  const fragments = [];
25077
25444
  let bytes = 0;
@@ -26091,7 +26458,7 @@ export class Runtime {
26091
26458
  if (pNames === undefined)
26092
26459
  return undefined;
26093
26460
  const ifs = rawState.variables.IFS ?? " ";
26094
- const sep = io.nameExpansionContext === "document" || (io.nameExpansionContext === "conditional" && part.prefixNames === "@")
26461
+ const sep = io.nameExpansionContext === "document" || (part.prefixNames === "@" && (io.nameExpansionContext === "conditional" || ifs.length === 0))
26095
26462
  ? " "
26096
26463
  : ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
26097
26464
  const joined = pNames.join(sep);
@@ -26406,11 +26773,7 @@ export class Runtime {
26406
26773
  if (part.operator !== undefined)
26407
26774
  return undefined;
26408
26775
  const s = elemVal ?? "";
26409
- for (let k = 0; k < s.length; k++) {
26410
- if (s.charCodeAt(k) >= 128)
26411
- return undefined;
26412
- }
26413
- out += String(s.length);
26776
+ out += String(this.syncStringLength(s, byteLocale(rawVars)));
26414
26777
  continue;
26415
26778
  }
26416
26779
  let resolvedElem;
@@ -26560,11 +26923,7 @@ export class Runtime {
26560
26923
  const val = raw === undefined ? "" : (this._syncArithRawWriteOnly && this._syncArithTouched?.has(part.name) ? raw : (monitor?.values.get(part.name, raw) ?? raw));
26561
26924
  if (typeof val !== "string")
26562
26925
  return undefined;
26563
- for (let k = 0; k < val.length; k++) {
26564
- if (val.charCodeAt(k) >= 128)
26565
- return undefined;
26566
- }
26567
- out += String(val.length);
26926
+ out += String(this.syncStringLength(val, byteLocale(rawVars)));
26568
26927
  continue;
26569
26928
  }
26570
26929
  if (part.operator !== undefined) {
@@ -26881,6 +27240,9 @@ export class Runtime {
26881
27240
  expandSyncTrCharArray(spec) {
26882
27241
  if (spec.length === 0)
26883
27242
  return undefined;
27243
+ // Escaped endpoints need the tr parser before range expansion.
27244
+ if (spec.includes("\\") && spec.includes("-"))
27245
+ return undefined;
26884
27246
  const posixClasses = {
26885
27247
  "[:lower:]": "abcdefghijklmnopqrstuvwxyz",
26886
27248
  "[:upper:]": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
@@ -26921,6 +27283,16 @@ export class Runtime {
26921
27283
  out.push("\r");
26922
27284
  else if (next === "\\")
26923
27285
  out.push("\\");
27286
+ else if (next >= "0" && next <= "7") {
27287
+ let oct = next;
27288
+ while (oct.length < 3 && i + 1 < spec.length && spec[i + 1] >= "0" && spec[i + 1] <= "7") {
27289
+ oct += spec[++i];
27290
+ }
27291
+ const code = parseInt(oct, 8);
27292
+ if (code <= 0 || code >= 128)
27293
+ return undefined;
27294
+ out.push(String.fromCharCode(code));
27295
+ }
26924
27296
  else
26925
27297
  return undefined;
26926
27298
  continue;
@@ -26941,11 +27313,125 @@ export class Runtime {
26941
27313
  return out;
26942
27314
  }
26943
27315
  evalSyncTr(input, opArgs) {
26944
- if (opArgs.length === 2 && !opArgs[0].startsWith("-") && !opArgs[1].startsWith("-")) {
26945
- return this.translateSyncTr(input, opArgs[0], opArgs[1]);
27316
+ let idx = 0;
27317
+ let flagC = false;
27318
+ let flagD = false;
27319
+ let flagS = false;
27320
+ let flagT = false;
27321
+ while (idx < opArgs.length) {
27322
+ const a = opArgs[idx];
27323
+ if (a === "--") {
27324
+ idx++;
27325
+ break;
27326
+ }
27327
+ if (!a.startsWith("-") || a === "-")
27328
+ break;
27329
+ if (!/^-[cCdst]+$/.test(a))
27330
+ return undefined;
27331
+ for (let k = 1; k < a.length; k++) {
27332
+ const ch = a[k];
27333
+ if (ch === "c" || ch === "C")
27334
+ flagC = true;
27335
+ else if (ch === "d")
27336
+ flagD = true;
27337
+ else if (ch === "s")
27338
+ flagS = true;
27339
+ else if (ch === "t")
27340
+ flagT = true;
27341
+ }
27342
+ idx++;
27343
+ }
27344
+ if (flagT && flagC)
27345
+ return undefined;
27346
+ const sets = opArgs.slice(idx);
27347
+ if (flagC) {
27348
+ if (sets.some(s => { for (let i = 0; i < s.length; i++)
27349
+ if (s.charCodeAt(i) >= 128)
27350
+ return true; return false; }))
27351
+ return undefined;
27352
+ if (flagD && !flagS && sets.length === 1) {
27353
+ const arr = this.expandSyncTrCharArray(sets[0]);
27354
+ if (!arr)
27355
+ return undefined;
27356
+ const keepSet = new Set(arr);
27357
+ let out = "";
27358
+ for (let i = 0; i < input.length; i++) {
27359
+ const ch = input[i];
27360
+ if (keepSet.has(ch))
27361
+ out += ch;
27362
+ }
27363
+ return out;
27364
+ }
27365
+ if (!flagD && sets.length === 2) {
27366
+ // Delegate multi-character complements and class validation to tr.
27367
+ if (sets[1].includes("[:"))
27368
+ return undefined;
27369
+ const arr1 = this.expandSyncTrCharArray(sets[0]);
27370
+ const arr2 = this.expandSyncTrCharArray(sets[1]);
27371
+ if (!arr1 || !arr2 || arr2.length !== 1)
27372
+ return undefined;
27373
+ const keepSet = new Set(arr1);
27374
+ const repl = arr2[arr2.length - 1];
27375
+ const sqSet = flagS ? new Set(arr2) : undefined;
27376
+ let hasNonAscii = false;
27377
+ for (let i = 0; i < input.length; i++) {
27378
+ if (input.charCodeAt(i) >= 128) {
27379
+ hasNonAscii = true;
27380
+ break;
27381
+ }
27382
+ }
27383
+ let out = "";
27384
+ let prev = "";
27385
+ if (hasNonAscii) {
27386
+ const bytes = fastSharedTextEncoder.encode(input);
27387
+ for (let i = 0; i < bytes.length; i++) {
27388
+ const rawCh = String.fromCharCode(bytes[i]);
27389
+ const ch = keepSet.has(rawCh) ? rawCh : repl;
27390
+ if (sqSet && sqSet.has(ch) && ch === prev)
27391
+ continue;
27392
+ out += ch;
27393
+ prev = ch;
27394
+ }
27395
+ }
27396
+ else {
27397
+ for (let i = 0; i < input.length; i++) {
27398
+ const rawCh = input[i];
27399
+ const ch = keepSet.has(rawCh) ? rawCh : repl;
27400
+ if (sqSet && sqSet.has(ch) && ch === prev)
27401
+ continue;
27402
+ out += ch;
27403
+ prev = ch;
27404
+ }
27405
+ }
27406
+ return out;
27407
+ }
27408
+ if (flagD && flagS && sets.length === 2) {
27409
+ const arr1 = this.expandSyncTrCharArray(sets[0]);
27410
+ const arr2 = this.expandSyncTrCharArray(sets[1]);
27411
+ if (!arr1 || !arr2)
27412
+ return undefined;
27413
+ const keepSet = new Set(arr1);
27414
+ const sqSet = new Set(arr2);
27415
+ let out = "";
27416
+ let prev = "";
27417
+ for (let i = 0; i < input.length; i++) {
27418
+ const ch = input[i];
27419
+ if (!keepSet.has(ch))
27420
+ continue;
27421
+ if (sqSet.has(ch) && ch === prev)
27422
+ continue;
27423
+ out += ch;
27424
+ prev = ch;
27425
+ }
27426
+ return out;
27427
+ }
27428
+ return undefined;
26946
27429
  }
26947
- if (opArgs.length === 2 && opArgs[0] === "-d" && !opArgs[1].startsWith("-")) {
26948
- const arr = this.expandSyncTrCharArray(opArgs[1]);
27430
+ if (!flagD && !flagS && sets.length === 2) {
27431
+ return this.translateSyncTr(input, sets[0], sets[1], flagT);
27432
+ }
27433
+ if (flagD && !flagS && sets.length === 1) {
27434
+ const arr = this.expandSyncTrCharArray(sets[0]);
26949
27435
  if (!arr)
26950
27436
  return undefined;
26951
27437
  const delSet = new Set(arr);
@@ -26957,8 +27443,8 @@ export class Runtime {
26957
27443
  }
26958
27444
  return out;
26959
27445
  }
26960
- if (opArgs.length === 2 && opArgs[0] === "-s" && !opArgs[1].startsWith("-")) {
26961
- const arr = this.expandSyncTrCharArray(opArgs[1]);
27446
+ if (!flagD && flagS && sets.length === 1) {
27447
+ const arr = this.expandSyncTrCharArray(sets[0]);
26962
27448
  if (!arr)
26963
27449
  return undefined;
26964
27450
  const sqSet = new Set(arr);
@@ -26973,140 +27459,654 @@ export class Runtime {
26973
27459
  }
26974
27460
  return out;
26975
27461
  }
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]]);
27462
+ if (sets.length === 2 && (flagS || flagD)) {
27463
+ const stage1 = !flagD
27464
+ ? this.translateSyncTr(input, sets[0], sets[1], flagT)
27465
+ : this.evalSyncTr(input, ["-d", sets[0]]);
26980
27466
  if (stage1 === undefined)
26981
27467
  return undefined;
26982
- return this.evalSyncTr(stage1, ["-s", opArgs[2]]);
27468
+ return this.evalSyncTr(stage1, ["-s", sets[1]]);
26983
27469
  }
26984
27470
  return undefined;
26985
27471
  }
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)
27472
+ evalSyncJqPathOps(item, expr) {
27473
+ let st = expr.trim();
27474
+ let alternatives = splitSyncJqExpression(st, "//");
27475
+ if (!alternatives)
26997
27476
  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("."))
27477
+ while (alternatives.wrapped) {
27478
+ st = st.slice(1, -1).trim();
27479
+ alternatives = splitSyncJqExpression(st, "//");
27480
+ if (!alternatives)
27018
27481
  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;
27482
+ }
27483
+ if (alternatives.parts.length > 1) {
27484
+ const lhs = alternatives.parts[0];
27485
+ const rhs = alternatives.parts.slice(1).join("//");
27486
+ const lVals = this.evalSyncJqPathOps(item, lhs);
27487
+ if (lVals === undefined)
27488
+ return undefined;
27489
+ // Validate both sides before admitting the fast path, even if lhs wins.
27490
+ let rVals = this.evalSyncJqPathOps(item, rhs);
27491
+ if (rVals === undefined) {
27492
+ try {
27493
+ rVals = [JSON.parse(rhs)];
27029
27494
  }
27030
- if (rest.startsWith("[]")) {
27031
- ops.push({ kind: "iter" });
27032
- rest = rest.slice(2);
27033
- continue;
27495
+ catch {
27496
+ return undefined;
27034
27497
  }
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;
27498
+ }
27499
+ const truthy = lVals.filter(v => v !== null && v !== undefined && v !== false);
27500
+ return truthy.length > 0 ? truthy : rVals;
27501
+ }
27502
+ const pipeSplit = splitSyncJqExpression(st, "|");
27503
+ if (pipeSplit && pipeSplit.parts.length > 1) {
27504
+ let cur = [item];
27505
+ for (const pStage of pipeSplit.parts) {
27506
+ const next = [];
27507
+ for (const it of cur) {
27508
+ const r = this.evalSyncJqPathOps(it, pStage);
27509
+ if (r === undefined)
27510
+ return undefined;
27511
+ for (const rv of r)
27512
+ next.push(rv);
27040
27513
  }
27041
- return undefined;
27514
+ cur = next;
27042
27515
  }
27043
- compiledStages.push(ops);
27516
+ return cur;
27044
27517
  }
27045
- let parsed;
27046
- try {
27047
- parsed = JSON.parse(input);
27518
+ if (st === ".")
27519
+ return [item];
27520
+ const dotArithM = /^\.\s*([+*\/%-])\s*(-?[0-9]+(?:\.[0-9]+)?)$/.exec(st);
27521
+ if (dotArithM && typeof item === "number") {
27522
+ const op = dotArithM[1];
27523
+ const rhs = Number(dotArithM[2]);
27524
+ if ((op === "/" || op === "%") && rhs === 0)
27525
+ return undefined;
27526
+ return [op === "+" ? item + rhs : op === "-" ? item - rhs : op === "*" ? item * rhs : op === "/" ? item / rhs : item % rhs];
27527
+ }
27528
+ if (st === "floor")
27529
+ return typeof item === "number" ? [Math.floor(item)] : undefined;
27530
+ if (st === "ceil")
27531
+ return typeof item === "number" ? [Math.ceil(item)] : undefined;
27532
+ if (st === "round")
27533
+ return typeof item === "number" ? [Math.round(item)] : undefined;
27534
+ if (st === "abs")
27535
+ return typeof item === "number" ? [Math.abs(item)] : undefined;
27536
+ if (st === "utf8bytelength")
27537
+ return typeof item === "string" ? [Buffer.byteLength(item, "utf8")] : undefined;
27538
+ if (st === "explode")
27539
+ return typeof item === "string" ? [Array.from(item, c => c.codePointAt(0))] : undefined;
27540
+ if (st === "implode") {
27541
+ if (!Array.isArray(item) || !item.every(x => typeof x === "number" && Number.isInteger(x) && x >= 0 && x <= 0x10ffff))
27542
+ return undefined;
27543
+ return [String.fromCodePoint(...item)];
27048
27544
  }
27049
- catch {
27545
+ if (st === "transpose") {
27546
+ if (!Array.isArray(item) || !item.every(row => Array.isArray(row)))
27547
+ return undefined;
27548
+ const rows = item;
27549
+ const maxCols = rows.reduce((m, r) => Math.max(m, r.length), 0);
27550
+ const trans = [];
27551
+ for (let c = 0; c < maxCols; c++) {
27552
+ trans.push(rows.map(r => c < r.length ? r[c] : null));
27553
+ }
27554
+ return [trans];
27555
+ }
27556
+ const idxFnM = /^(index|rindex|indices)\(\s*"([^"\\]*)"\s*\)$/.exec(st);
27557
+ if (idxFnM && typeof item === "string") {
27558
+ const fn = idxFnM[1];
27559
+ const sub = idxFnM[2];
27560
+ const found = [];
27561
+ if (sub.length > 0) {
27562
+ let pos = 0;
27563
+ while ((pos = item.indexOf(sub, pos)) !== -1) {
27564
+ found.push(Array.from(item.slice(0, pos)).length);
27565
+ pos += 1;
27566
+ }
27567
+ }
27568
+ if (fn === "indices")
27569
+ return [found];
27570
+ if (fn === "index")
27571
+ return [found.length > 0 ? found[0] : null];
27572
+ return [found.length > 0 ? found[found.length - 1] : null];
27573
+ }
27574
+ if (st === "length") {
27575
+ if (item === null || item === undefined)
27576
+ return [0];
27577
+ if (typeof item === "string")
27578
+ return [Array.from(item).length];
27579
+ if (typeof item === "number")
27580
+ return [Math.abs(item)];
27581
+ if (Array.isArray(item))
27582
+ return [item.length];
27583
+ if (typeof item === "object")
27584
+ return [Object.keys(item).length];
27050
27585
  return undefined;
27051
27586
  }
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;
27587
+ if (st === "keys") {
27588
+ if (Array.isArray(item))
27589
+ return [item.map((_, idx) => idx)];
27590
+ if (item && typeof item === "object")
27591
+ return [Object.keys(item).sort()];
27592
+ return undefined;
27593
+ }
27594
+ if (st === "keys[]") {
27595
+ if (Array.isArray(item))
27596
+ return item.map((_, idx) => idx);
27597
+ if (item && typeof item === "object")
27598
+ return Object.keys(item).sort();
27599
+ return undefined;
27600
+ }
27601
+ if (st === "values") {
27602
+ return item === null || item === undefined ? [] : [item];
27603
+ }
27604
+ if (st === "empty")
27605
+ return [];
27606
+ if (st === "not")
27607
+ return [!(item !== false && item !== null && item !== undefined)];
27608
+ if (st === "any")
27609
+ return Array.isArray(item) ? [item.some(x => x !== false && x !== null && x !== undefined)] : undefined;
27610
+ if (st === "all")
27611
+ return Array.isArray(item) ? [item.every(x => x !== false && x !== null && x !== undefined)] : undefined;
27612
+ if (st === "keys_unsorted") {
27613
+ if (Array.isArray(item))
27614
+ return [item.map((_, idx) => idx)];
27615
+ if (item && typeof item === "object")
27616
+ return [Object.keys(item)];
27617
+ return undefined;
27618
+ }
27619
+ if (st === "flatten")
27620
+ return Array.isArray(item) ? [item.flat(Infinity)] : undefined;
27621
+ const flatM = /^flatten\(\s*([0-9]{1,2})\s*\)$/.exec(st);
27622
+ if (flatM)
27623
+ return Array.isArray(item) ? [item.flat(Number(flatM[1]))] : undefined;
27624
+ if (st === "sort") {
27625
+ if (!Array.isArray(item))
27626
+ return undefined;
27627
+ if (item.every(x => typeof x === "number"))
27628
+ return [[...item].sort((a, b) => a - b)];
27629
+ if (item.every(x => typeof x === "string"))
27630
+ return [[...item].sort()];
27631
+ return undefined;
27632
+ }
27633
+ if (st === "tojson")
27634
+ return [JSON.stringify(item)];
27635
+ if (st === "fromjson") {
27636
+ if (typeof item !== "string")
27637
+ return undefined;
27638
+ try {
27639
+ return [JSON.parse(item)];
27640
+ }
27641
+ catch {
27642
+ return undefined;
27643
+ }
27644
+ }
27645
+ const mvM = /^map_values\(\s*(.+)\s*\)$/.exec(st);
27646
+ if (mvM && item && typeof item === "object") {
27647
+ const subExpr = mvM[1];
27648
+ if (Array.isArray(item)) {
27649
+ const outArr = [];
27650
+ for (const el of item) {
27651
+ const r = this.evalSyncJqPathOps(el, subExpr);
27652
+ if (!r || r.length !== 1)
27653
+ return undefined;
27654
+ outArr.push(r[0]);
27655
+ }
27656
+ return [outArr];
27657
+ }
27658
+ const outObj = {};
27659
+ for (const [k, v] of Object.entries(item)) {
27660
+ const r = this.evalSyncJqPathOps(v, subExpr);
27661
+ if (!r || r.length !== 1)
27662
+ return undefined;
27663
+ outObj[k] = r[0];
27664
+ }
27665
+ return [outObj];
27666
+ }
27667
+ const byM = /^(sort_by|unique_by|group_by|min_by|max_by)\(\s*(\.[a-zA-Z_][a-zA-Z0-9_.]*)\s*\)$/.exec(st);
27668
+ if (byM && Array.isArray(item)) {
27669
+ const fn = byM[1];
27670
+ const kPath = byM[2];
27671
+ const keyed = [];
27672
+ for (const el of item) {
27673
+ const kv = this.evalSyncJqPathOps(el, kPath);
27674
+ if (!kv || kv.length !== 1)
27675
+ return undefined;
27676
+ const k0 = kv[0];
27677
+ if (typeof k0 !== "string" && typeof k0 !== "number" && typeof k0 !== "boolean" && k0 !== null)
27678
+ return undefined;
27679
+ keyed.push({ el, k: k0 });
27680
+ }
27681
+ const cmpK = (a, b) => {
27682
+ const rank = (value) => value === null ? 0 : value === false ? 1 : value === true ? 2 : typeof value === "number" ? 3 : 4;
27683
+ const typeOrder = rank(a) - rank(b);
27684
+ if (typeOrder !== 0)
27685
+ return typeOrder;
27686
+ if (typeof a === "number" && typeof b === "number")
27687
+ return a - b;
27688
+ const sa = String(a);
27689
+ const sb = String(b);
27690
+ return sa < sb ? -1 : sa > sb ? 1 : 0;
27691
+ };
27692
+ keyed.sort((a, b) => cmpK(a.k, b.k));
27693
+ if (fn === "min_by")
27694
+ return [keyed.length > 0 ? keyed[0].el : null];
27695
+ if (fn === "max_by")
27696
+ return [keyed.length > 0 ? keyed[keyed.length - 1].el : null];
27697
+ if (fn === "sort_by")
27698
+ return [keyed.map(p => p.el)];
27699
+ if (fn === "unique_by") {
27700
+ const u = [];
27701
+ for (let i = 0; i < keyed.length; i++) {
27702
+ if (i === 0 || cmpK(keyed[i].k, keyed[i - 1].k) !== 0)
27703
+ u.push(keyed[i].el);
27704
+ }
27705
+ return [u];
27706
+ }
27707
+ const groups = [];
27708
+ for (let i = 0; i < keyed.length; i++) {
27709
+ if (i === 0 || cmpK(keyed[i].k, keyed[i - 1].k) !== 0)
27710
+ groups.push([keyed[i].el]);
27711
+ else
27712
+ groups[groups.length - 1].push(keyed[i].el);
27713
+ }
27714
+ return [groups];
27715
+ }
27716
+ if (st === "first") {
27717
+ return Array.isArray(item) ? [item.length > 0 ? item[0] : null] : undefined;
27718
+ }
27719
+ if (st === "last") {
27720
+ return Array.isArray(item) ? [item.length > 0 ? item[item.length - 1] : null] : undefined;
27721
+ }
27722
+ if (st === "reverse") {
27723
+ return Array.isArray(item) ? [[...item].reverse()] : undefined;
27724
+ }
27725
+ // Use the jq engine for its complete type ordering and equality semantics.
27726
+ if (st === "unique")
27727
+ return undefined;
27728
+ if (st === "add") {
27729
+ if (!Array.isArray(item))
27730
+ return undefined;
27731
+ if (item.length === 0)
27732
+ return [null];
27733
+ if (item.every(x => typeof x === "number"))
27734
+ return [item.reduce((a, b) => a + b, 0)];
27735
+ if (item.every(x => typeof x === "string"))
27736
+ return [item.join("")];
27737
+ if (item.every(x => Array.isArray(x)))
27738
+ return [item.flat()];
27739
+ return undefined;
27740
+ }
27741
+ if (st === "min" || st === "max") {
27742
+ if (!Array.isArray(item))
27743
+ return undefined;
27744
+ if (item.length === 0)
27745
+ return [null];
27746
+ if (item.every(x => typeof x === "number")) {
27747
+ return [st === "min" ? Math.min(...item) : Math.max(...item)];
27748
+ }
27749
+ if (item.every(x => typeof x === "string")) {
27750
+ const sorted = [...item].sort();
27751
+ return [st === "min" ? sorted[0] : sorted[sorted.length - 1]];
27752
+ }
27753
+ return undefined;
27754
+ }
27755
+ if (st === "to_entries") {
27756
+ if (item && typeof item === "object" && !Array.isArray(item)) {
27757
+ return [Object.entries(item).map(([key, value]) => ({ key, value }))];
27758
+ }
27759
+ return undefined;
27760
+ }
27761
+ if (st === "from_entries") {
27762
+ if (!Array.isArray(item))
27763
+ return undefined;
27764
+ const obj = {};
27765
+ for (const el of item) {
27766
+ if (!el || typeof el !== "object" || Array.isArray(el))
27767
+ return undefined;
27768
+ const rec = el;
27769
+ const k = rec.key ?? rec.name ?? rec.k;
27770
+ if (typeof k !== "string")
27771
+ return undefined;
27772
+ obj[k] = Object.hasOwn(rec, "value") ? rec.value : (Object.hasOwn(rec, "v") ? rec.v : null);
27773
+ }
27774
+ return [obj];
27775
+ }
27776
+ if (st === "tonumber") {
27777
+ if (typeof item === "number")
27778
+ return [item];
27779
+ if (typeof item === "string" && /^[ \t]*[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[ \t]*$/.test(item))
27780
+ return [Number(item)];
27781
+ return undefined;
27782
+ }
27783
+ if (st === "tostring") {
27784
+ return [typeof item === "string" ? item : JSON.stringify(item)];
27785
+ }
27786
+ if (st === "type") {
27787
+ return [item === null ? "null" : Array.isArray(item) ? "array" : typeof item];
27788
+ }
27789
+ if (st === "ascii_upcase") {
27790
+ return typeof item === "string" ? [item.replace(/[a-z]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 32))] : undefined;
27791
+ }
27792
+ if (st === "ascii_downcase") {
27793
+ return typeof item === "string" ? [item.replace(/[A-Z]/g, ch => String.fromCharCode(ch.charCodeAt(0) + 32))] : undefined;
27794
+ }
27795
+ const strFnM = /^(join|split|ltrimstr|rtrimstr|startswith|endswith|contains)\(\s*"([^"\\]*)"\s*\)$/.exec(st);
27796
+ if (strFnM) {
27797
+ const fn = strFnM[1];
27798
+ const arg = strFnM[2];
27799
+ if (fn === "join") {
27800
+ if (!Array.isArray(item) || !item.every(x => typeof x === "string" || typeof x === "number" || typeof x === "boolean" || x === null))
27801
+ return undefined;
27802
+ return [item.map(x => x === null ? "" : String(x)).join(arg)];
27803
+ }
27804
+ if (fn === "split")
27805
+ return typeof item === "string" ? [item.split(arg)] : undefined;
27806
+ if (fn === "ltrimstr")
27807
+ return typeof item === "string" ? [item.startsWith(arg) ? item.slice(arg.length) : item] : undefined;
27808
+ if (fn === "rtrimstr")
27809
+ return typeof item === "string" ? [arg.length > 0 && item.endsWith(arg) ? item.slice(0, -arg.length) : item] : undefined;
27810
+ if (fn === "startswith")
27811
+ return typeof item === "string" ? [item.startsWith(arg)] : undefined;
27812
+ if (fn === "endswith")
27813
+ return typeof item === "string" ? [item.endsWith(arg)] : undefined;
27814
+ if (fn === "contains") {
27815
+ if (typeof item === "string")
27816
+ return [item.includes(arg)];
27817
+ return undefined;
27818
+ }
27819
+ }
27820
+ const hasM = /^has\(\s*"([^"\\]+)"\s*\)$/.exec(st);
27821
+ if (hasM) {
27822
+ if (item && typeof item === "object" && !Array.isArray(item))
27823
+ return [Object.hasOwn(item, hasM[1])];
27824
+ return undefined;
27825
+ }
27826
+ const mapM = /^map\(\s*(.+)\s*\)$/.exec(st);
27827
+ if (mapM) {
27828
+ if (!Array.isArray(item))
27829
+ return undefined;
27830
+ const mapped = [];
27831
+ for (const el of item) {
27832
+ const subRes = this.evalSyncJqPathOps(el, mapM[1]);
27833
+ if (subRes === undefined)
27834
+ return undefined;
27835
+ for (const v of subRes)
27836
+ mapped.push(v);
27837
+ }
27838
+ return [mapped];
27839
+ }
27840
+ const selM = /^select\(\s*(\.[a-zA-Z_][a-zA-Z0-9_.]*)\s*(?:(==|!=|>=|<=|>|<)\s*(.+))?\s*\)$/.exec(st);
27841
+ if (selM) {
27842
+ const fVals = this.evalSyncJqPathOps(item, selM[1]);
27843
+ if (!fVals || fVals.length !== 1)
27844
+ return undefined;
27845
+ const lv = fVals[0];
27846
+ if (!selM[2]) {
27847
+ return lv !== null && lv !== undefined && lv !== false ? [item] : [];
27848
+ }
27849
+ let rv;
27850
+ try {
27851
+ rv = JSON.parse(selM[3].trim());
27852
+ }
27853
+ catch {
27854
+ return undefined;
27855
+ }
27856
+ // The full jq evaluator owns structural equality and jq ordering.
27857
+ if ((typeof lv === "object" && lv !== null) || (typeof rv === "object" && rv !== null))
27858
+ return undefined;
27859
+ const op = selM[2];
27860
+ let ok = false;
27861
+ if (op === "==")
27862
+ ok = lv === rv;
27863
+ else if (op === "!=")
27864
+ ok = lv !== rv;
27865
+ else if (typeof lv === "number" && typeof rv === "number") {
27866
+ if (op === ">")
27867
+ ok = lv > rv;
27868
+ else if (op === ">=")
27869
+ ok = lv >= rv;
27870
+ else if (op === "<")
27871
+ ok = lv < rv;
27872
+ else if (op === "<=")
27873
+ ok = lv <= rv;
27874
+ }
27875
+ else
27876
+ return undefined;
27877
+ return ok ? [item] : [];
27878
+ }
27879
+ if (!st.startsWith("."))
27880
+ return undefined;
27881
+ let rest = st.slice(1);
27882
+ const ops = [];
27883
+ while (rest.length > 0) {
27884
+ if (rest.startsWith("."))
27885
+ rest = rest.slice(1);
27886
+ const propM = /^([a-zA-Z_][a-zA-Z0-9_]*)/.exec(rest);
27887
+ if (propM) {
27888
+ ops.push({ kind: "prop", key: propM[1] });
27889
+ rest = rest.slice(propM[1].length);
27890
+ continue;
27891
+ }
27892
+ if (rest.startsWith("[]")) {
27893
+ ops.push({ kind: "iter" });
27894
+ rest = rest.slice(2);
27895
+ continue;
27896
+ }
27897
+ const idxM = /^\[(-?[0-9]+)\]/.exec(rest);
27898
+ if (idxM) {
27899
+ ops.push({ kind: "index", idx: Number(idxM[1]) });
27900
+ rest = rest.slice(idxM[0].length);
27901
+ continue;
27902
+ }
27903
+ return undefined;
27904
+ }
27905
+ let cur = [item];
27906
+ for (const op of ops) {
27907
+ const next = [];
27908
+ for (const it of cur) {
27909
+ if (op.kind === "prop") {
27910
+ if (it === null || it === undefined)
27911
+ next.push(null);
27912
+ else if (typeof it === "object" && !Array.isArray(it))
27913
+ next.push(Object.hasOwn(it, op.key) ? it[op.key] ?? null : null);
27914
+ else
27915
+ return undefined;
27916
+ }
27917
+ else if (op.kind === "index") {
27918
+ if (it === null || it === undefined)
27919
+ next.push(null);
27920
+ else if (Array.isArray(it)) {
27921
+ const i = op.idx < 0 ? it.length + op.idx : op.idx;
27922
+ next.push(i >= 0 && i < it.length ? it[i] : null);
27074
27923
  }
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;
27924
+ else
27925
+ return undefined;
27926
+ }
27927
+ else if (op.kind === "iter") {
27928
+ if (Array.isArray(it)) {
27929
+ for (const el of it)
27930
+ next.push(el);
27086
27931
  }
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;
27932
+ else if (it && typeof it === "object") {
27933
+ for (const k of Object.keys(it))
27934
+ next.push(it[k]);
27096
27935
  }
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;
27936
+ else
27937
+ return undefined;
27938
+ }
27939
+ }
27940
+ cur = next;
27941
+ }
27942
+ return cur;
27943
+ }
27944
+ evalSyncJq(input, opArgs) {
27945
+ let rawOut = false;
27946
+ let compactOut = false;
27947
+ let filter;
27948
+ if (opArgs.length === 1 && !opArgs[0].startsWith("-")) {
27949
+ filter = opArgs[0];
27950
+ }
27951
+ 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")) {
27952
+ rawOut = ["-r", "-rc", "-cr", "--raw-output"].includes(opArgs[0]);
27953
+ compactOut = ["-c", "-rc", "-cr", "--compact-output"].includes(opArgs[0]);
27954
+ filter = opArgs[1];
27955
+ }
27956
+ if (!filter)
27957
+ return undefined;
27958
+ let current = [];
27959
+ try {
27960
+ current = [JSON.parse(input)];
27961
+ }
27962
+ catch {
27963
+ if (!input.includes("\n"))
27964
+ return undefined;
27965
+ try {
27966
+ for (const line of input.split("\n")) {
27967
+ const t = line.trim();
27968
+ if (t.length > 0)
27969
+ current.push(JSON.parse(t));
27970
+ }
27971
+ if (current.length === 0)
27972
+ return undefined;
27973
+ }
27974
+ catch {
27975
+ return undefined;
27976
+ }
27977
+ }
27978
+ const trimmedFilter = filter.trim();
27979
+ if (trimmedFilter.startsWith("[") && trimmedFilter.endsWith("]")) {
27980
+ const innerFilter = trimmedFilter.slice(1, -1).trim();
27981
+ const innerArgs = rawOut || compactOut ? [compactOut ? "-c" : "-r", innerFilter] : [innerFilter];
27982
+ const outArrays = [];
27983
+ for (const item of current) {
27984
+ const innerStages = splitSyncJqExpression(innerFilter, "|");
27985
+ if (!innerStages)
27986
+ return undefined;
27987
+ let subCur = [item];
27988
+ for (const st of innerStages.parts) {
27989
+ const commas = splitSyncJqExpression(st, ",");
27990
+ if (!commas)
27991
+ return undefined;
27992
+ const commaParts = commas.parts;
27993
+ const next = [];
27994
+ for (const it of subCur) {
27995
+ for (const part of commaParts) {
27996
+ const res = this.evalSyncJqPathOps(it, part);
27997
+ if (res === undefined)
27998
+ return undefined;
27999
+ for (const v of res)
28000
+ next.push(v);
28001
+ }
27104
28002
  }
28003
+ subCur = next;
27105
28004
  }
27106
- current = next;
28005
+ outArrays.push(subCur);
28006
+ }
28007
+ void innerArgs;
28008
+ return outArrays.map(val => JSON.stringify(val ?? null, null, compactOut ? undefined : 2));
28009
+ }
28010
+ const stages = splitSyncJqExpression(filter, "|");
28011
+ if (!stages)
28012
+ return undefined;
28013
+ for (const st of stages.parts) {
28014
+ const commas = splitSyncJqExpression(st, ",");
28015
+ if (!commas)
28016
+ return undefined;
28017
+ const commaParts = commas.parts;
28018
+ const next = [];
28019
+ for (const item of current) {
28020
+ for (const part of commaParts) {
28021
+ const res = this.evalSyncJqPathOps(item, part);
28022
+ if (res === undefined)
28023
+ return undefined;
28024
+ for (const v of res)
28025
+ next.push(v);
28026
+ }
28027
+ }
28028
+ current = next;
28029
+ }
28030
+ return current.map(val => (rawOut && typeof val === "string" ? val : JSON.stringify(val ?? null, null, compactOut ? undefined : 2)));
28031
+ }
28032
+ evalSyncDirname(args) {
28033
+ let idx = 0;
28034
+ if (args[0] === "--")
28035
+ idx = 1;
28036
+ else if (args[0] !== undefined && args[0].startsWith("-") && args[0] !== "-")
28037
+ return undefined;
28038
+ if (idx >= args.length)
28039
+ return undefined;
28040
+ const out = [];
28041
+ for (let i = idx; i < args.length; i++) {
28042
+ const p = args[i];
28043
+ if (p.includes("\0"))
28044
+ return undefined;
28045
+ if (idx === 0 && p.startsWith("-") && p !== "-")
28046
+ return undefined;
28047
+ const parent = dirname(p);
28048
+ let pEnd = parent.length;
28049
+ while (pEnd > 1 && parent[pEnd - 1] === "/")
28050
+ pEnd--;
28051
+ out.push(parent.slice(0, pEnd));
28052
+ }
28053
+ return out.join("\n");
28054
+ }
28055
+ evalSyncBasename(args) {
28056
+ let multiple = false;
28057
+ let suffix;
28058
+ let idx = 0;
28059
+ const operands = [];
28060
+ while (idx < args.length) {
28061
+ const a = args[idx];
28062
+ if (a === "--") {
28063
+ operands.push(...args.slice(idx + 1));
28064
+ break;
28065
+ }
28066
+ if (!a.startsWith("-") || a === "-") {
28067
+ operands.push(a);
28068
+ idx++;
28069
+ continue;
28070
+ }
28071
+ if (a === "-a" || a === "--multiple") {
28072
+ multiple = true;
28073
+ idx++;
28074
+ }
28075
+ else if (a === "-s" || a === "-as" || a === "-sa") {
28076
+ multiple = true;
28077
+ if (idx + 1 >= args.length)
28078
+ return undefined;
28079
+ suffix = args[idx + 1];
28080
+ idx += 2;
28081
+ }
28082
+ else if (a.startsWith("-s") && a.length > 2) {
28083
+ multiple = true;
28084
+ suffix = a.slice(2);
28085
+ idx++;
28086
+ }
28087
+ else {
28088
+ return undefined;
27107
28089
  }
27108
28090
  }
27109
- return current.map(val => (rawOut && typeof val === "string" ? val : JSON.stringify(val ?? null)));
28091
+ if (operands.length === 0)
28092
+ return undefined;
28093
+ if (!multiple && operands.length > 2)
28094
+ return undefined;
28095
+ const effSuffix = multiple ? suffix : operands[1];
28096
+ if (effSuffix !== undefined && effSuffix.includes("\0"))
28097
+ return undefined;
28098
+ const paths = multiple ? operands : [operands[0]];
28099
+ const out = [];
28100
+ for (const p of paths) {
28101
+ if (p.includes("\0"))
28102
+ return undefined;
28103
+ let res = /^\/+$/u.test(p) ? "/" : basename(p);
28104
+ if (effSuffix && res !== effSuffix && res.endsWith(effSuffix)) {
28105
+ res = res.slice(0, -effSuffix.length);
28106
+ }
28107
+ out.push(res);
28108
+ }
28109
+ return out.join("\n");
27110
28110
  }
27111
28111
  parseSyncCutSpec(spec) {
27112
28112
  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)) {
@@ -27152,11 +28152,28 @@ export class Runtime {
27152
28152
  evalSyncCut(rawLines, opArgs, byteLocaleMode) {
27153
28153
  const norm = [];
27154
28154
  let suppressNoDelim = false;
28155
+ let isComplement = false;
28156
+ let outDelim;
27155
28157
  for (let ci = 0; ci < opArgs.length; ci++) {
27156
28158
  const ca = opArgs[ci];
27157
28159
  if (ca === "-s" || ca === "--only-delimited") {
27158
28160
  suppressNoDelim = true;
27159
28161
  }
28162
+ else if (ca === "--complement") {
28163
+ isComplement = true;
28164
+ }
28165
+ else if (ca.startsWith("--output-delimiter=")) {
28166
+ outDelim = ca.slice("--output-delimiter=".length);
28167
+ if (outDelim.length === 0 || outDelim.includes("\0"))
28168
+ return undefined;
28169
+ }
28170
+ else if (ca === "--output-delimiter") {
28171
+ if (ci + 1 >= opArgs.length)
28172
+ return undefined;
28173
+ outDelim = opArgs[++ci];
28174
+ if (outDelim.length === 0 || outDelim.includes("\0"))
28175
+ return undefined;
28176
+ }
27160
28177
  else if (ca.startsWith("-sd") || ca.startsWith("-ds")) {
27161
28178
  suppressNoDelim = true;
27162
28179
  const rest = ca.slice(3);
@@ -27167,21 +28184,29 @@ export class Runtime {
27167
28184
  else
27168
28185
  return undefined;
27169
28186
  }
27170
- else if ((ca === "-d" || ca === "-f" || ca === "-c") && ci + 1 < opArgs.length) {
28187
+ else if ((ca === "-d" || ca === "-f" || ca === "-c" || ca === "-b") && ci + 1 < opArgs.length) {
27171
28188
  norm.push(ca + opArgs[++ci]);
27172
28189
  }
27173
28190
  else {
27174
28191
  norm.push(ca);
27175
28192
  }
27176
28193
  }
27177
- if (!suppressNoDelim && norm.length === 1 && norm[0].startsWith("-c")) {
27178
- if (byteLocaleMode)
28194
+ if (!suppressNoDelim && outDelim === undefined && norm.length === 1 && (norm[0].startsWith("-c") || norm[0].startsWith("-b"))) {
28195
+ const isByteMode = norm[0].startsWith("-b");
28196
+ if (!isByteMode && byteLocaleMode)
28197
+ return undefined;
28198
+ if (isByteMode && rawLines.some(l => { for (let i = 0; i < l.length; i++)
28199
+ if (l.charCodeAt(i) >= 128)
28200
+ return true; return false; }))
27179
28201
  return undefined;
27180
- const picker = this.parseSyncCutSpec(norm[0].slice(2));
27181
- if (!picker)
28202
+ const basePicker = this.parseSyncCutSpec(norm[0].slice(2));
28203
+ if (!basePicker)
27182
28204
  return undefined;
28205
+ const picker = isComplement ? (len) => { const s = new Set(basePicker(len)); const r = []; for (let i = 0; i < len; i++)
28206
+ if (!s.has(i))
28207
+ r.push(i); return r; } : basePicker;
27183
28208
  return rawLines.map(l => {
27184
- const chars = Array.from(l);
28209
+ const chars = isByteMode ? l.split("") : Array.from(l);
27185
28210
  const idxs = picker(chars.length);
27186
28211
  return idxs.map(i => chars[i]).join("");
27187
28212
  });
@@ -27201,9 +28226,13 @@ export class Runtime {
27201
28226
  }
27202
28227
  if (!fArg || !fArg.startsWith("-f"))
27203
28228
  return undefined;
27204
- const picker = this.parseSyncCutSpec(fArg.slice(2));
27205
- if (!picker)
28229
+ const basePicker = this.parseSyncCutSpec(fArg.slice(2));
28230
+ if (!basePicker)
27206
28231
  return undefined;
28232
+ const picker = isComplement ? (len) => { const s = new Set(basePicker(len)); const r = []; for (let i = 0; i < len; i++)
28233
+ if (!s.has(i))
28234
+ r.push(i); return r; } : basePicker;
28235
+ const joinDelim = outDelim ?? delim;
27207
28236
  const out = [];
27208
28237
  for (let li = 0; li < rawLines.length; li++) {
27209
28238
  const l = rawLines[li];
@@ -27214,146 +28243,519 @@ export class Runtime {
27214
28243
  }
27215
28244
  const parts = l.split(delim);
27216
28245
  const idxs = picker(parts.length);
27217
- out.push(idxs.map(i => parts[i]).join(delim));
28246
+ out.push(idxs.map(i => parts[i]).join(joinDelim));
27218
28247
  }
27219
28248
  return out;
27220
28249
  }
27221
28250
  evalSyncSed(rawLines, opArgs) {
27222
28251
  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"))
28252
+ let isExtended = false;
28253
+ const rawExprs = [];
28254
+ for (let i = 0; i < opArgs.length; i++) {
28255
+ const a = opArgs[i];
28256
+ if (a === "-n" || a === "--quiet" || a === "--silent")
28257
+ quiet = true;
28258
+ else if (a === "-E" || a === "-r" || a === "--regexp-extended")
28259
+ isExtended = true;
28260
+ else if (a === "-En" || a === "-nE" || a === "-rn" || a === "-nr") {
28261
+ quiet = true;
28262
+ isExtended = true;
28263
+ }
28264
+ else if (a === "-e") {
28265
+ if (i + 1 >= opArgs.length)
28266
+ return undefined;
28267
+ rawExprs.push(opArgs[++i]);
28268
+ }
28269
+ else if (!a.startsWith("-") && rawExprs.length === 0 && i === opArgs.length - 1) {
28270
+ rawExprs.push(a);
28271
+ }
28272
+ else {
27244
28273
  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
28274
  }
27276
- return out;
27277
28275
  }
27278
- if (quiet)
27279
- return undefined;
27280
- if (!expr.startsWith("s") || expr.length < 4)
28276
+ if (rawExprs.length === 0)
27281
28277
  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))
28278
+ const subExprs = [];
28279
+ for (const re of rawExprs) {
28280
+ if (re.includes(";") && !re.startsWith("s;") && !re.startsWith("y;")) {
28281
+ for (const p of re.split(";")) {
28282
+ const t = p.trim();
28283
+ if (t.length > 0)
28284
+ subExprs.push(t);
28285
+ }
28286
+ }
28287
+ else {
28288
+ subExprs.push(re.trim());
28289
+ }
28290
+ }
28291
+ if (subExprs.length === 0)
27290
28292
  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))
28293
+ const steps = [];
28294
+ for (const rawE of subExprs) {
28295
+ let rest = rawE;
28296
+ let addr;
28297
+ let negated = false;
28298
+ const addrM = /^(?:([1-9][0-9]{0,4}|\$)(?:,([1-9][0-9]{0,4}|\$))?|\/(\^?[a-zA-Z0-9_ :;,=-]+\$?)\/)\s*/.exec(rest);
28299
+ if (addrM) {
28300
+ const startSpec = addrM[1];
28301
+ const endSpec = addrM[2];
28302
+ const patSpec = addrM[3];
28303
+ rest = rest.slice(addrM[0].length);
28304
+ addr = (l, idx1, total) => {
28305
+ if (patSpec !== undefined) {
28306
+ const aStart = patSpec.startsWith("^");
28307
+ const c1 = aStart ? patSpec.slice(1) : patSpec;
28308
+ const aEnd = c1.endsWith("$");
28309
+ const core = aEnd ? c1.slice(0, -1) : c1;
28310
+ if (aStart && aEnd)
28311
+ return l === core;
28312
+ if (aStart)
28313
+ return l.startsWith(core);
28314
+ if (aEnd)
28315
+ return l.endsWith(core);
28316
+ return l.includes(core);
28317
+ }
28318
+ const sNum = startSpec === "$" ? total : Number(startSpec);
28319
+ if (endSpec === undefined)
28320
+ return idx1 === sNum;
28321
+ const eNum = endSpec === "$" ? total : Number(endSpec);
28322
+ return idx1 >= sNum && idx1 <= Math.max(sNum, eNum);
28323
+ };
28324
+ }
28325
+ if (rest.startsWith("!")) {
28326
+ negated = true;
28327
+ rest = rest.slice(1).trim();
28328
+ }
28329
+ if (rest === "d") {
28330
+ steps.push({ addr, negated, kind: "d" });
28331
+ continue;
28332
+ }
28333
+ if (rest === "p") {
28334
+ steps.push({ addr, negated, kind: "p" });
28335
+ continue;
28336
+ }
28337
+ if (rest === "q") {
28338
+ steps.push({ addr, negated, kind: "q" });
28339
+ continue;
28340
+ }
28341
+ if (rest === "=") {
28342
+ steps.push({ addr, negated, kind: "=" });
28343
+ continue;
28344
+ }
28345
+ // Text commands need range state and independent output terminators.
28346
+ // Let the canonical sed executor preserve those byte-level semantics.
28347
+ if (rest.startsWith("i") || rest.startsWith("a") || rest.startsWith("c"))
28348
+ return undefined;
28349
+ if (rest.startsWith("y") && rest.length >= 4) {
28350
+ const delim = rest[1];
28351
+ if (!"/#|:@,;%!".includes(delim))
28352
+ return undefined;
28353
+ const parts = rest.slice(2).split(delim);
28354
+ if (parts.length !== 3 || parts[2] !== "")
28355
+ return undefined;
28356
+ const srcStr = parts[0];
28357
+ const dstStr = parts[1];
28358
+ if (srcStr.includes("\\") || dstStr.includes("\\"))
28359
+ return undefined;
28360
+ const srcChars = Array.from(srcStr);
28361
+ const dstChars = Array.from(dstStr);
28362
+ if (srcChars.length !== dstChars.length)
28363
+ return undefined;
28364
+ const map = new Map();
28365
+ for (let i = 0; i < srcChars.length; i++) {
28366
+ if (!map.has(srcChars[i]))
28367
+ map.set(srcChars[i], dstChars[i]);
28368
+ }
28369
+ steps.push({ addr, negated, kind: "y", map });
28370
+ continue;
28371
+ }
28372
+ if (rest.startsWith("s") && rest.length >= 4) {
28373
+ const delim = rest[1];
28374
+ if (!"/#|:@,;%!".includes(delim))
28375
+ return undefined;
28376
+ const parts = rest.slice(2).split(delim);
28377
+ if (parts.length !== 3)
28378
+ return undefined;
28379
+ const [pat, rep, flags] = parts;
28380
+ if (!/^[giIp1-9]*$/.test(flags) || /\n|\\(?![1-9])/.test(rep))
28381
+ return undefined;
28382
+ const nthDigits = flags.match(/[1-9]/g);
28383
+ if (nthDigits && (nthDigits.length > 1 || flags.includes("g")))
28384
+ return undefined;
28385
+ const nth = nthDigits ? Number(nthDigits[0]) : 0;
28386
+ const global = flags.includes("g") || nth > 0;
28387
+ const ignoreCase = flags.includes("i") || flags.includes("I");
28388
+ const printOnMatch = flags.includes("p");
28389
+ const anchorStart = pat.startsWith("^");
28390
+ const core1 = anchorStart ? pat.slice(1) : pat;
28391
+ const anchorEnd = core1.endsWith("$");
28392
+ const core = anchorEnd ? core1.slice(0, -1) : core1;
28393
+ if (core.length === 0 && !anchorStart && !anchorEnd)
28394
+ return undefined;
28395
+ let reSrc;
28396
+ if (/^[a-zA-Z0-9_ :;,=-]*$/.test(core)) {
28397
+ if (/\\[1-9]/.test(rep))
28398
+ return undefined;
28399
+ reSrc = core.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28400
+ }
28401
+ else if (core === "[ \t]*" || core === "[ \t]+" || core === "[ \\t]*" || core === "[ \\t]+" ||
28402
+ core === "[[:space:]]*" || core === "[[:space:]]+" ||
28403
+ core === "[0-9]+" || core === "[0-9]*" ||
28404
+ core === "[a-zA-Z]+" || core === "[a-zA-Z0-9_]+") {
28405
+ if (/\\[1-9]/.test(rep))
28406
+ return undefined;
28407
+ reSrc = core.replace("[[:space:]]", "[ \\t\\r\\n\\v\\f]");
28408
+ }
28409
+ else if (isExtended && /^[a-zA-Z0-9_ :;,=.+*?|()^\-[\]]+$/.test(core) && !/\([^)]*[+*][^)]*\)[+*?]/.test(core)) {
28410
+ try {
28411
+ const expanded = core.replace(/\[:space:\]/g, " \\t\\r\\n\\v\\f");
28412
+ void new RegExp(expanded);
28413
+ reSrc = expanded;
28414
+ }
28415
+ catch {
28416
+ reSrc = undefined;
28417
+ }
28418
+ }
28419
+ if (reSrc === undefined)
28420
+ return undefined;
28421
+ const fullRe = new RegExp((anchorStart ? "^" : "") + reSrc + (anchorEnd ? "$" : ""), (global ? "g" : "") + (ignoreCase ? "i" : ""));
28422
+ steps.push({ addr, negated, kind: "s", re: fullRe, rep, printOnMatch, nth });
28423
+ continue;
28424
+ }
27296
28425
  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
- });
28426
+ }
28427
+ const out = [];
28428
+ const total = rawLines.length;
28429
+ let lastInputIndex = -1;
28430
+ for (let i = 0; i < total; i++) {
28431
+ let l = rawLines[i];
28432
+ const idx1 = i + 1;
28433
+ let deleted = false;
28434
+ let quitNow = false;
28435
+ for (let si = 0; si < steps.length; si++) {
28436
+ const st = steps[si];
28437
+ const addrMatched = st.addr ? st.addr(l, idx1, total) : true;
28438
+ if (st.negated ? addrMatched : !addrMatched)
28439
+ continue;
28440
+ if (st.kind === "d") {
28441
+ deleted = true;
28442
+ break;
28443
+ }
28444
+ if (st.kind === "p") {
28445
+ out.push(l);
28446
+ lastInputIndex = i;
28447
+ continue;
28448
+ }
28449
+ if (st.kind === "=") {
28450
+ out.push(String(idx1));
28451
+ lastInputIndex = i;
28452
+ continue;
28453
+ }
28454
+ if (st.kind === "q") {
28455
+ if (!quiet) {
28456
+ out.push(l);
28457
+ lastInputIndex = i;
28458
+ }
28459
+ quitNow = true;
28460
+ break;
28461
+ }
28462
+ if (st.kind === "y") {
28463
+ l = Array.from(l).map(c => st.map.get(c) ?? c).join("");
28464
+ continue;
28465
+ }
28466
+ let matchedOnce = false;
28467
+ let matchIdx = 0;
28468
+ let previousEnd = -1;
28469
+ l = l.replace(st.re, (matched, ...groups) => {
28470
+ const offset = groups[groups.length - 2];
28471
+ if (matched.length === 0 && offset === previousEnd)
28472
+ return "";
28473
+ previousEnd = offset + matched.length;
28474
+ matchIdx++;
28475
+ if (st.nth > 0 && matchIdx !== st.nth)
28476
+ return matched;
28477
+ matchedOnce = true;
28478
+ // Expand only replacement tokens; inserted match bytes are literal.
28479
+ let replacement = "";
28480
+ for (let ri = 0; ri < st.rep.length; ri++) {
28481
+ const c = st.rep[ri];
28482
+ if (c === "&")
28483
+ replacement += matched;
28484
+ else if (c === "\\" && ri + 1 < st.rep.length) {
28485
+ const next = st.rep[++ri];
28486
+ const group = next >= "1" && next <= "9" ? groups[Number(next) - 1] : next;
28487
+ replacement += typeof group === "string" ? group : "";
28488
+ }
28489
+ else
28490
+ replacement += c;
28491
+ }
28492
+ return replacement;
28493
+ });
28494
+ if (matchedOnce && st.printOnMatch)
28495
+ out.push(l);
28496
+ }
28497
+ if (quitNow) {
28498
+ break;
28499
+ }
28500
+ if (!deleted && !quiet) {
28501
+ out.push(l);
28502
+ lastInputIndex = i;
28503
+ }
28504
+ }
28505
+ return { lines: out, lastInputIndex };
27307
28506
  }
27308
28507
  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") {
28508
+ if (errexit || opArgs.length < 1 || opArgs.length > 8)
27316
28509
  return undefined;
28510
+ let mode = "";
28511
+ let maxCount = Infinity;
28512
+ let beforeCtx = 0;
28513
+ let afterCtx = 0;
28514
+ const rawPatterns = [];
28515
+ for (let i = 0; i < opArgs.length; i++) {
28516
+ const a = opArgs[i];
28517
+ if ((a === "-A" || a === "-B" || a === "-C") && i + 1 < opArgs.length && /^[0-9]{1,4}$/.test(opArgs[i + 1])) {
28518
+ const n = Number(opArgs[++i]);
28519
+ if (a === "-A" || a === "-C")
28520
+ afterCtx = n;
28521
+ if (a === "-B" || a === "-C")
28522
+ beforeCtx = n;
28523
+ }
28524
+ else if (/^-[ABC][0-9]{1,4}$/.test(a)) {
28525
+ const n = Number(a.slice(2));
28526
+ if (a[1] === "A" || a[1] === "C")
28527
+ afterCtx = n;
28528
+ if (a[1] === "B" || a[1] === "C")
28529
+ beforeCtx = n;
28530
+ }
28531
+ else if (/^--(?:after-context|before-context|context)=[0-9]{1,4}$/.test(a)) {
28532
+ const eq = a.indexOf("=");
28533
+ const n = Number(a.slice(eq + 1));
28534
+ if (a.startsWith("--after") || a.startsWith("--context"))
28535
+ afterCtx = n;
28536
+ if (a.startsWith("--before") || a.startsWith("--context"))
28537
+ beforeCtx = n;
28538
+ }
28539
+ else if (a === "-e") {
28540
+ if (i + 1 >= opArgs.length)
28541
+ return undefined;
28542
+ rawPatterns.push(opArgs[++i]);
28543
+ }
28544
+ else if (a.startsWith("-e") && a.length > 2) {
28545
+ rawPatterns.push(a.slice(2));
28546
+ }
28547
+ else if (a === "-m") {
28548
+ if (i + 1 >= opArgs.length || !/^[1-9][0-9]{0,5}$/.test(opArgs[i + 1]))
28549
+ return undefined;
28550
+ maxCount = Number(opArgs[++i]);
28551
+ }
28552
+ else if (a.startsWith("-m") && /^[1-9][0-9]{0,5}$/.test(a.slice(2))) {
28553
+ maxCount = Number(a.slice(2));
28554
+ }
28555
+ else if (a.startsWith("--max-count=") && /^[1-9][0-9]{0,5}$/.test(a.slice(12))) {
28556
+ maxCount = Number(a.slice(12));
28557
+ }
28558
+ else if (a.startsWith("--") && a !== "--" && rawPatterns.length === 0 && i < opArgs.length - 1) {
28559
+ if (a === "--extended-regexp")
28560
+ mode += "E";
28561
+ else if (a === "--fixed-strings")
28562
+ mode += "F";
28563
+ else if (a === "--ignore-case")
28564
+ mode += "i";
28565
+ else if (a === "--invert-match")
28566
+ mode += "v";
28567
+ else if (a === "--only-matching")
28568
+ mode += "o";
28569
+ else if (a === "--line-number")
28570
+ mode += "n";
28571
+ else if (a === "--count")
28572
+ mode += "c";
28573
+ else if (a === "--word-regexp")
28574
+ mode += "w";
28575
+ else if (a === "--line-regexp")
28576
+ mode += "x";
28577
+ else
28578
+ return undefined;
28579
+ }
28580
+ else if (a.startsWith("-") && a !== "-" && a !== "--" && rawPatterns.length === 0 && i < opArgs.length - 1) {
28581
+ mode += a.slice(1);
28582
+ }
28583
+ else if (!a.startsWith("-") && rawPatterns.length === 0 && i === opArgs.length - 1) {
28584
+ rawPatterns.push(a);
28585
+ }
28586
+ else {
28587
+ return undefined;
28588
+ }
27317
28589
  }
28590
+ if (rawPatterns.length === 0 || !/^[vicFEonxw]*$/.test(mode))
28591
+ return undefined;
28592
+ const isLineNumber = mode.includes("n");
28593
+ const isLineRegexp = mode.includes("x");
28594
+ const isWordRegexp = mode.includes("w");
27318
28595
  const isCaseInsensitive = mode.includes("i");
28596
+ const foldCase = (value) => value.replace(/[A-Z]/g, ch => ch.toLowerCase());
27319
28597
  const isInvert = mode.includes("v");
27320
- const isCount = mode === "-c";
28598
+ const isCount = mode.includes("c");
27321
28599
  const isExtended = mode.includes("E");
27322
28600
  const isFixed = mode.includes("F");
27323
- const branches = isExtended ? pat.split("|") : [pat];
28601
+ const isOnlyMatching = mode.includes("o");
28602
+ // POSIX bracket classes, collating symbols and equivalence classes have
28603
+ // different meanings in JS RegExp. Let the command matcher parse them.
28604
+ if (isExtended && !isFixed && rawPatterns.some(pattern => pattern.includes("[:") || pattern.includes("[.") || pattern.includes("[=")))
28605
+ return undefined;
28606
+ if (isCount && (isLineNumber || isOnlyMatching || beforeCtx > 0 || afterCtx > 0))
28607
+ return undefined;
28608
+ if (isOnlyMatching && (beforeCtx > 0 || afterCtx > 0))
28609
+ return undefined;
28610
+ if (isOnlyMatching) {
28611
+ if (rawPatterns.length !== 1 || isInvert || isLineRegexp || isWordRegexp || isFixed)
28612
+ return undefined;
28613
+ const pat = rawPatterns[0];
28614
+ const reFlags = isCaseInsensitive ? "gi" : "g";
28615
+ let re;
28616
+ // JS alternatives are ordered; only literal alternatives below can be
28617
+ // sorted to implement grep's leftmost-longest choice. Other alternatives
28618
+ // use the command's POSIX matcher through the normal fallback.
28619
+ if (isExtended && !pat.includes("|") && /^[a-zA-Z0-9_ :;,=.+*?()^\-[\]]+$/.test(pat) && !/\([^)]*[+*][^)]*\)[+*?]/.test(pat)) {
28620
+ try {
28621
+ re = new RegExp(pat, reFlags);
28622
+ }
28623
+ catch {
28624
+ re = undefined;
28625
+ }
28626
+ }
28627
+ else {
28628
+ const branches = isExtended ? pat.split("|") : [pat];
28629
+ if (branches.every(b => b.length > 0 && /^[a-zA-Z0-9_ :;,/-]+$/.test(b))) {
28630
+ branches.sort((a, b) => b.length - a.length);
28631
+ re = new RegExp(branches.join("|"), reFlags);
28632
+ }
28633
+ }
28634
+ if (!re)
28635
+ return undefined;
28636
+ const out = [];
28637
+ for (let li = 0; li < rawLines.length; li++) {
28638
+ const matches = rawLines[li].match(re);
28639
+ if (matches) {
28640
+ for (let mi = 0; mi < matches.length; mi++) {
28641
+ out.push(isLineNumber ? `${li + 1}:${matches[mi]}` : matches[mi]);
28642
+ }
28643
+ }
28644
+ }
28645
+ return { lines: out, status: out.length > 0 ? 0 : 1 };
28646
+ }
28647
+ if (isExtended && isFixed)
28648
+ return undefined;
28649
+ const branches = [];
28650
+ for (const rp of rawPatterns) {
28651
+ for (const b of (isFixed ? rp.split("\n") : (isExtended ? rp.split("|") : [rp]))) {
28652
+ branches.push(b);
28653
+ }
28654
+ }
27324
28655
  const compiledBranches = [];
27325
28656
  for (const br of branches) {
27326
28657
  if (br.length === 0)
27327
28658
  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 });
28659
+ if (isFixed) {
28660
+ if (br.includes("\0"))
28661
+ return undefined;
28662
+ compiledBranches.push({ anchorStart: isLineRegexp, anchorEnd: isLineRegexp, needle: isCaseInsensitive ? foldCase(br) : br, re: undefined });
28663
+ }
28664
+ else {
28665
+ const anchorStart = isLineRegexp || br.startsWith("^");
28666
+ const c1 = br.startsWith("^") ? br.slice(1) : br;
28667
+ const anchorEnd = isLineRegexp || c1.endsWith("$");
28668
+ const core = c1.endsWith("$") ? c1.slice(0, -1) : c1;
28669
+ if (core.length > 0 && /^[a-zA-Z0-9_ :;,/-]+$/.test(core)) {
28670
+ compiledBranches.push({ anchorStart, anchorEnd, needle: isCaseInsensitive ? foldCase(core) : core, re: undefined });
28671
+ }
28672
+ else if (isExtended && !isWordRegexp && core.length > 0 && /^[a-zA-Z0-9_ :;,=.+*?^\-[\]]+$/.test(core) && !/\([^)]*[+*][^)]*\)[+*?]/.test(core)) {
28673
+ try {
28674
+ const fullSrc = (anchorStart ? "^" : "") + core + (anchorEnd ? "$" : "");
28675
+ compiledBranches.push({ anchorStart, anchorEnd, needle: "", re: new RegExp(fullSrc, isCaseInsensitive ? "i" : "") });
28676
+ }
28677
+ catch {
28678
+ return undefined;
28679
+ }
28680
+ }
28681
+ else {
28682
+ return undefined;
28683
+ }
28684
+ }
27335
28685
  }
28686
+ const isWordChar = (ch) => ch !== undefined && /^[a-zA-Z0-9_]$/.test(ch);
28687
+ const hasWordMatch = (hay, needle, aStart, aEnd) => {
28688
+ if (aStart && aEnd)
28689
+ return hay === needle;
28690
+ if (aStart)
28691
+ return hay.startsWith(needle) && !isWordChar(hay[needle.length]);
28692
+ if (aEnd)
28693
+ return hay.endsWith(needle) && !isWordChar(hay[hay.length - needle.length - 1]);
28694
+ let from = 0;
28695
+ while (from <= hay.length - needle.length) {
28696
+ const idx = hay.indexOf(needle, from);
28697
+ if (idx === -1)
28698
+ return false;
28699
+ if (!isWordChar(hay[idx - 1]) && !isWordChar(hay[idx + needle.length]))
28700
+ return true;
28701
+ from = idx + 1;
28702
+ }
28703
+ return false;
28704
+ };
27336
28705
  const matched = [];
28706
+ const matchedIndices = [];
27337
28707
  for (let li = 0; li < rawLines.length; li++) {
27338
28708
  const l = rawLines[li];
27339
- const hay = isCaseInsensitive ? l.toLowerCase() : l;
28709
+ const hay = isCaseInsensitive ? foldCase(l) : l;
27340
28710
  let hit = false;
27341
28711
  for (let bi = 0; bi < compiledBranches.length; bi++) {
27342
28712
  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);
28713
+ const ok = b.re !== undefined
28714
+ ? b.re.test(l)
28715
+ : isWordRegexp
28716
+ ? hasWordMatch(hay, b.needle, b.anchorStart, b.anchorEnd)
28717
+ : b.anchorStart && b.anchorEnd
28718
+ ? hay === b.needle
28719
+ : b.anchorStart
28720
+ ? hay.startsWith(b.needle)
28721
+ : b.anchorEnd
28722
+ ? hay.endsWith(b.needle)
28723
+ : hay.includes(b.needle);
27350
28724
  if (ok) {
27351
28725
  hit = true;
27352
28726
  break;
27353
28727
  }
27354
28728
  }
27355
- if (isInvert ? !hit : hit)
27356
- matched.push(l);
28729
+ if (isInvert ? !hit : hit) {
28730
+ matchedIndices.push(li);
28731
+ matched.push(isLineNumber ? `${li + 1}:${l}` : l);
28732
+ if (matched.length >= maxCount)
28733
+ break;
28734
+ }
28735
+ }
28736
+ if ((beforeCtx > 0 || afterCtx > 0) && matchedIndices.length > 0) {
28737
+ const matchedSet = new Set(matchedIndices);
28738
+ const outCtx = [];
28739
+ let prevEnd = -1;
28740
+ for (const mIdx of matchedIndices) {
28741
+ const start = Math.max(0, mIdx - beforeCtx);
28742
+ const end = Math.min(rawLines.length - 1, mIdx + afterCtx);
28743
+ if (prevEnd !== -1 && start > prevEnd + 1) {
28744
+ outCtx.push("--");
28745
+ }
28746
+ const from = prevEnd !== -1 ? Math.max(start, prevEnd + 1) : start;
28747
+ for (let k = from; k <= end; k++) {
28748
+ const lineText = rawLines[k];
28749
+ if (isLineNumber) {
28750
+ outCtx.push(`${k + 1}${matchedSet.has(k) ? ":" : "-"}${lineText}`);
28751
+ }
28752
+ else {
28753
+ outCtx.push(lineText);
28754
+ }
28755
+ }
28756
+ prevEnd = Math.max(prevEnd, end);
28757
+ }
28758
+ return { lines: outCtx, status: 0 };
27357
28759
  }
27358
28760
  return {
27359
28761
  lines: isCount ? [String(matched.length)] : matched,
@@ -27361,50 +28763,413 @@ export class Runtime {
27361
28763
  };
27362
28764
  }
27363
28765
  evalSyncAwk(rawLines, opArgs) {
27364
- if (opArgs.length < 1 || opArgs.length > 3)
28766
+ if (opArgs.length < 1 || opArgs.length > 8)
27365
28767
  return undefined;
27366
- const norm = opArgs.length === 3 && opArgs[0] === "-F" ? ["-F" + opArgs[1], opArgs[2]] : opArgs;
27367
28768
  let awkSep;
27368
28769
  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];
28770
+ let ofs = " ";
28771
+ const userVars = new Map();
28772
+ for (let i = 0; i < opArgs.length; i++) {
28773
+ const a = opArgs[i];
28774
+ if (a === "-F") {
28775
+ if (i + 1 >= opArgs.length || opArgs[i + 1].length !== 1 || opArgs[i + 1] === "\\")
28776
+ return undefined;
28777
+ awkSep = opArgs[++i];
28778
+ }
28779
+ else if (a.startsWith("-F") && a.length === 3 && a[2] !== "\\") {
28780
+ awkSep = a.slice(2);
28781
+ }
28782
+ else if (a === "-v") {
28783
+ if (i + 1 >= opArgs.length)
28784
+ return undefined;
28785
+ const kv = opArgs[++i];
28786
+ const eq = kv.indexOf("=");
28787
+ if (eq <= 0 || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(kv.slice(0, eq)))
28788
+ return undefined;
28789
+ const k = kv.slice(0, eq);
28790
+ const v = kv.slice(eq + 1);
28791
+ if (v.includes("\\"))
28792
+ return undefined;
28793
+ if (k === "OFS")
28794
+ ofs = v;
28795
+ else if (k === "FS") {
28796
+ if (v.length !== 1)
28797
+ return undefined;
28798
+ awkSep = v;
28799
+ }
28800
+ else
28801
+ userVars.set(k, v);
28802
+ }
28803
+ else if (a.startsWith("-v") && a.length > 2) {
28804
+ const kv = a.slice(2);
28805
+ const eq = kv.indexOf("=");
28806
+ if (eq <= 0 || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(kv.slice(0, eq)))
28807
+ return undefined;
28808
+ const k = kv.slice(0, eq);
28809
+ const v = kv.slice(eq + 1);
28810
+ if (v.includes("\\"))
28811
+ return undefined;
28812
+ if (k === "OFS")
28813
+ ofs = v;
28814
+ else if (k === "FS") {
28815
+ if (v.length !== 1)
28816
+ return undefined;
28817
+ awkSep = v;
28818
+ }
28819
+ else
28820
+ userVars.set(k, v);
28821
+ }
28822
+ else if (!a.startsWith("-") && i === opArgs.length - 1) {
28823
+ awkProg = a;
28824
+ }
28825
+ else {
28826
+ return undefined;
28827
+ }
27374
28828
  }
27375
28829
  if (!awkProg)
27376
28830
  return undefined;
27377
- const awkM = /^\s*\{\s*print(?:\s+((?:\$(?:[0-9]+|NF)|"[^"$\\]*")(?:\s*,?\s*(?:\$(?:[0-9]+|NF)|"[^"$\\]*"))*))?\s*;?\s*\}\s*$/.exec(awkProg);
28831
+ let progRest = awkProg.trim();
28832
+ const beginFsM = /^BEGIN\s*\{([^}]*)\}\s*/.exec(progRest);
28833
+ let initVarName;
28834
+ let initVarVal = 0;
28835
+ if (beginFsM) {
28836
+ const bStmts = beginFsM[1].split(";").map(s => s.trim()).filter(Boolean);
28837
+ for (const bs of bStmts) {
28838
+ const ofsM = /^OFS\s*=\s*"([^"\\]*)"$/.exec(bs);
28839
+ if (ofsM) {
28840
+ ofs = ofsM[1];
28841
+ continue;
28842
+ }
28843
+ const fsM = /^FS\s*=\s*"([^"\\])"$/.exec(bs);
28844
+ if (fsM) {
28845
+ awkSep = fsM[1];
28846
+ continue;
28847
+ }
28848
+ const numInitM = /^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(-?[0-9]+(?:\.[0-9]+)?)$/.exec(bs);
28849
+ if (numInitM) {
28850
+ initVarName = numInitM[1];
28851
+ initVarVal = Number(numInitM[2]);
28852
+ continue;
28853
+ }
28854
+ return undefined;
28855
+ }
28856
+ progRest = progRest.slice(beginFsM[0].length).trim();
28857
+ }
28858
+ const sumEndM = /^\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(\+=|-=)\s*(?:\$([0-9]+|NF)|(-?[0-9]+(?:\.[0-9]+)?))\s*;?\s*\}\s*END\s*\{\s*print\s+\1\s*;?\s*\}$/.exec(progRest);
28859
+ if (sumEndM) {
28860
+ const varName = sumEndM[1];
28861
+ if (["NR", "NF", "FNR", "OFS", "FS", "ORS", "RS", "OFMT", "CONVFMT", "ARGC", "ARGIND", "FILENAME", "RSTART", "RLENGTH", "SUBSEP", "IGNORECASE", "FIELDWIDTHS", "FPAT", "BINMODE", "TEXTDOMAIN"].includes(varName))
28862
+ return undefined;
28863
+ if (initVarName !== undefined && initVarName !== varName)
28864
+ return undefined;
28865
+ const op = sumEndM[2];
28866
+ const fTok = sumEndM[3];
28867
+ const constVal = sumEndM[4] !== undefined ? Number(sumEndM[4]) : undefined;
28868
+ const initial = userVars.get(varName);
28869
+ if (initial !== undefined && !Number.isFinite(Number(initial)))
28870
+ return undefined;
28871
+ let acc = initVarName === varName ? initVarVal : Number(initial ?? 0);
28872
+ let touched = initVarName === varName || initial !== undefined;
28873
+ for (let li = 0; li < rawLines.length; li++) {
28874
+ const l = rawLines[li];
28875
+ const fields = awkSep === undefined || awkSep === " "
28876
+ ? l.split(/[ \t]+/).filter(Boolean)
28877
+ : (l.length === 0 ? [] : l.split(awkSep));
28878
+ let delta = 0;
28879
+ if (constVal !== undefined) {
28880
+ delta = constVal;
28881
+ }
28882
+ else {
28883
+ const idx = fTok === "NF" ? fields.length : Number(fTok);
28884
+ const rawField = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
28885
+ const n = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(rawField);
28886
+ delta = n ? Number(n[1]) : 0;
28887
+ }
28888
+ acc = op === "+=" ? acc + delta : acc - delta;
28889
+ touched = true;
28890
+ }
28891
+ return [touched ? awkValueText({ kind: "number", number: acc }) : ""];
28892
+ }
28893
+ if (initVarName !== undefined)
28894
+ return undefined;
28895
+ let rowPred;
28896
+ const patCondM = /^\/(\^?[a-zA-Z0-9_ :;,=-]+\$?)\/\s*/.exec(progRest);
28897
+ if (patCondM) {
28898
+ const pSpec = patCondM[1];
28899
+ const aS = pSpec.startsWith("^");
28900
+ const c1 = aS ? pSpec.slice(1) : pSpec;
28901
+ const aE = c1.endsWith("$");
28902
+ const core = aE ? c1.slice(0, -1) : c1;
28903
+ rowPred = (l) => aS && aE ? l === core : aS ? l.startsWith(core) : aE ? l.endsWith(core) : l.includes(core);
28904
+ progRest = progRest.slice(patCondM[0].length).trim();
28905
+ }
28906
+ else {
28907
+ const cmpCondM = /^(?:(NR|NF)|\$([0-9]+|NF))\s*(==|!=|>=|<=|>|<)\s*(?:"([^"\\]*)"|(-?[0-9]+(?:\.[0-9]+)?)|([a-zA-Z_][a-zA-Z0-9_]*))\s*/.exec(progRest);
28908
+ if (cmpCondM) {
28909
+ const nrNf = cmpCondM[1];
28910
+ const fTok = cmpCondM[2];
28911
+ const op = cmpCondM[3];
28912
+ const varRhs = cmpCondM[6] !== undefined ? userVars.get(cmpCondM[6]) : undefined;
28913
+ if (cmpCondM[6] !== undefined && varRhs === undefined)
28914
+ return undefined;
28915
+ const varAsNum = varRhs !== undefined && /^-?(?:\d+(?:\.\d*)?|\.\d+)$/.test(varRhs) ? Number(varRhs) : undefined;
28916
+ const strRhs = cmpCondM[4] ?? (varAsNum === undefined ? varRhs : undefined);
28917
+ const numRhs = cmpCondM[5] !== undefined ? Number(cmpCondM[5]) : varAsNum;
28918
+ if (strRhs !== undefined && op !== "==" && op !== "!=")
28919
+ return undefined;
28920
+ rowPred = (l, fields, nr) => {
28921
+ let rawVal;
28922
+ if (nrNf !== undefined)
28923
+ rawVal = nrNf === "NR" ? nr : fields.length;
28924
+ else {
28925
+ const idx = fTok === "NF" ? fields.length : Number(fTok);
28926
+ rawVal = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
28927
+ }
28928
+ const left = typeof rawVal === "number" ? awkNumeric(rawVal) : awkInputValue(rawVal);
28929
+ const right = strRhs !== undefined ? awkString(strRhs) : awkNumeric(numRhs);
28930
+ const comparison = awkCompare(left, right, "%.6g");
28931
+ return op === "==" ? comparison === 0 : op === "!=" ? comparison !== 0 : op === ">=" ? comparison >= 0 : op === "<=" ? comparison <= 0 : op === ">" ? comparison > 0 : comparison < 0;
28932
+ };
28933
+ progRest = progRest.slice(cmpCondM[0].length).trim();
28934
+ }
28935
+ }
28936
+ const awkM = syncAwkPrintRe.exec(progRest);
27378
28937
  if (!awkM)
27379
28938
  return undefined;
27380
- const exprBody = awkM[1]?.trim();
28939
+ const subFn = awkM[1];
28940
+ const subPat = awkM[2];
28941
+ const subRep = awkM[3] ?? "";
28942
+ const subTarget = awkM[4] ?? "0";
28943
+ // printf must consume typed values before OFMT string conversion and use
28944
+ // the complete formatter, including integer precision and exponent parsing.
28945
+ if (awkM[6] !== undefined)
28946
+ return undefined;
28947
+ const exprBody = awkM[5]?.trim();
28948
+ let subRe;
28949
+ if (subFn && subPat !== undefined) {
28950
+ const aS = subPat.startsWith("^");
28951
+ const c1 = aS ? subPat.slice(1) : subPat;
28952
+ const aE = c1.endsWith("$");
28953
+ const core = aE ? c1.slice(0, -1) : c1;
28954
+ const esc = core.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28955
+ subRe = new RegExp((aS ? "^" : "") + esc + (aE ? "$" : ""), subFn === "gsub" ? "g" : "");
28956
+ }
27381
28957
  const parts = [];
27382
28958
  if (!exprBody) {
27383
28959
  parts.push({ kind: "field", token: "0" });
27384
28960
  }
27385
28961
  else {
27386
- const tokenRe = /\$([0-9]+|NF)|"([^"$\\]*)"|(,)/g;
28962
+ syncAwkTokenRe.lastIndex = 0;
28963
+ const tokenRe = syncAwkTokenRe;
27387
28964
  let m;
27388
28965
  while ((m = tokenRe.exec(exprBody)) !== null) {
27389
- if (m[1] !== undefined)
27390
- parts.push({ kind: "field", token: m[1] });
28966
+ if (m[1] !== undefined) {
28967
+ 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
+ if (!tm)
28969
+ return undefined;
28970
+ parts.push({ kind: "ternary", lhs: tm[1], op: tm[2], rhs: tm[3], yes: tm[4], no: tm[5] });
28971
+ }
27391
28972
  else if (m[2] !== undefined)
27392
- parts.push({ kind: "lit", text: m[2] });
28973
+ parts.push({ kind: "int", token: m[2] });
27393
28974
  else if (m[3] !== undefined)
27394
- parts.push({ kind: "lit", text: " " });
28975
+ parts.push({ kind: "case", fn: m[3] === "toupper" ? "upper" : "lower", token: m[4] });
28976
+ else if (m[5] !== undefined)
28977
+ parts.push({ kind: "substr", token: m[5], start: Number(m[6]), len: m[7] !== undefined ? Number(m[7]) : undefined });
28978
+ else if (m[8] !== undefined)
28979
+ parts.push({ kind: "index", token: m[8], sub: m[9] });
28980
+ else if (m[10] !== undefined) {
28981
+ const aToks = m[10].trim().split(/\s*([+*\/%-])\s*/);
28982
+ for (let ti = 0; ti < aToks.length; ti += 2) {
28983
+ const at = aToks[ti];
28984
+ if (!at.startsWith("$") && at !== "NR" && at !== "NF" && !/^-?[0-9]+(?:\.[0-9]+)?$/.test(at) && !userVars.has(at))
28985
+ return undefined;
28986
+ }
28987
+ for (let ti = 1; ti < aToks.length; ti += 2) {
28988
+ if (aToks[ti] === "/" || aToks[ti] === "%") {
28989
+ const divisor = Number(aToks[ti + 1]);
28990
+ if (!Number.isFinite(divisor) || divisor === 0)
28991
+ return undefined;
28992
+ }
28993
+ }
28994
+ parts.push({ kind: "arith", tokens: aToks });
28995
+ }
28996
+ else if (m[11] !== undefined) {
28997
+ const offset = Number(m[11]);
28998
+ // Refuse potentially fatal fields before loop iterations mutate state.
28999
+ if (offset > 0)
29000
+ return undefined;
29001
+ parts.push({ kind: "nf_minus", offset });
29002
+ }
29003
+ else if (m[12] !== undefined)
29004
+ parts.push({ kind: "field", token: m[12] });
29005
+ else if (m[0] === "length" || m[0].startsWith("length("))
29006
+ parts.push({ kind: "length", token: m[13] ?? "0" });
29007
+ else if (m[14] !== undefined)
29008
+ parts.push({ kind: "var", name: m[14] });
29009
+ else if (m[15] !== undefined)
29010
+ parts.push({ kind: "lit", text: m[15] });
29011
+ else if (m[16] !== undefined) {
29012
+ const uv = userVars.get(m[16]);
29013
+ if (uv === undefined)
29014
+ return undefined;
29015
+ parts.push({ kind: "lit", text: uv });
29016
+ }
29017
+ else if (m[17] !== undefined)
29018
+ parts.push({ kind: "lit", text: ofs });
27395
29019
  }
27396
29020
  }
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);
29021
+ const outLines = [];
29022
+ for (let li = 0; li < rawLines.length; li++) {
29023
+ let l = rawLines[li];
29024
+ let fields = awkSep === undefined || awkSep === " "
29025
+ ? l.split(/[ \t]+/).filter(Boolean)
29026
+ : (l.length === 0 ? [] : l.split(awkSep));
29027
+ if (rowPred && !rowPred(l, fields, li + 1))
29028
+ continue;
29029
+ if (subRe) {
29030
+ if (subTarget === "0") {
29031
+ l = l.replace(subRe, matched => subRep.split("&").join(matched));
29032
+ fields = awkSep === undefined || awkSep === " "
29033
+ ? l.split(/[ \t]+/).filter(Boolean)
29034
+ : (l.length === 0 ? [] : l.split(awkSep));
29035
+ }
29036
+ else {
29037
+ const tIdx = subTarget === "NF" ? fields.length : Number(subTarget);
29038
+ if (tIdx >= 1 && tIdx <= fields.length) {
29039
+ fields = [...fields];
29040
+ fields[tIdx - 1] = fields[tIdx - 1].replace(subRe, matched => subRep.split("&").join(matched));
29041
+ l = fields.join(ofs);
29042
+ }
29043
+ }
29044
+ }
29045
+ if (parts.length === 1 && parts[0].kind === "field" && parts[0].token === "0") {
29046
+ outLines.push(l);
29047
+ continue;
29048
+ }
27403
29049
  let out = "";
27404
29050
  for (let pi = 0; pi < parts.length; pi++) {
27405
29051
  const p = parts[pi];
27406
29052
  if (p.kind === "lit")
27407
29053
  out += p.text;
29054
+ else if (p.kind === "var")
29055
+ out += String(p.name === "NR" ? li + 1 : fields.length);
29056
+ else if (p.kind === "nf_minus") {
29057
+ const idx = fields.length - p.offset;
29058
+ if (idx < 0)
29059
+ return undefined;
29060
+ out += idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29061
+ }
29062
+ else if (p.kind === "case") {
29063
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29064
+ const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29065
+ out += p.fn === "upper" ? s.toUpperCase() : s.toLowerCase();
29066
+ }
29067
+ else if (p.kind === "substr") {
29068
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29069
+ const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29070
+ const chars = Array.from(s);
29071
+ const s0 = Math.max(0, p.start - 1);
29072
+ out += (p.len !== undefined ? chars.slice(s0, s0 + Math.max(0, p.len)) : chars.slice(s0)).join("");
29073
+ }
29074
+ else if (p.kind === "index") {
29075
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29076
+ const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29077
+ const pos = p.sub.length === 0 ? -1 : s.indexOf(p.sub);
29078
+ out += String(pos === -1 ? 0 : pos + 1);
29079
+ }
29080
+ else if (p.kind === "int") {
29081
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29082
+ 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));
29085
+ }
29086
+ else if (p.kind === "ternary") {
29087
+ const resolveAtom = (tok) => {
29088
+ if (tok.startsWith("\"") && tok.endsWith("\""))
29089
+ return tok.slice(1, -1);
29090
+ if (tok === "NR")
29091
+ return String(li + 1);
29092
+ if (tok === "NF")
29093
+ return String(fields.length);
29094
+ if (tok.startsWith("$")) {
29095
+ const fIdx = tok === "$NF" ? fields.length : Number(tok.slice(1));
29096
+ return fIdx === 0 ? l : (fIdx >= 1 && fIdx <= fields.length ? fields[fIdx - 1] : "");
29097
+ }
29098
+ return tok;
29099
+ };
29100
+ const lv = resolveAtom(p.lhs);
29101
+ 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
+ }
29112
+ out += resolveAtom(cond ? p.yes : p.no);
29113
+ }
29114
+ else if (p.kind === "arith") {
29115
+ const vals = [];
29116
+ const ops = [];
29117
+ for (let ti = 0; ti < p.tokens.length; ti++) {
29118
+ const tk = p.tokens[ti];
29119
+ if (ti % 2 === 1) {
29120
+ ops.push(tk);
29121
+ }
29122
+ else {
29123
+ let v = 0;
29124
+ if (tk === "NR")
29125
+ v = li + 1;
29126
+ else if (tk === "NF")
29127
+ v = fields.length;
29128
+ else if (tk.startsWith("$")) {
29129
+ const fSub = tk.slice(1);
29130
+ const idx = fSub === "NF" ? fields.length : Number(fSub);
29131
+ const rawF = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29132
+ const nm = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(rawF);
29133
+ v = nm ? Number(nm[1]) : 0;
29134
+ }
29135
+ else if (/^-?[0-9]+(?:\.[0-9]+)?$/.test(tk)) {
29136
+ v = Number(tk);
29137
+ }
29138
+ else {
29139
+ const uv = userVars.get(tk) ?? "";
29140
+ const nm = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(uv);
29141
+ v = nm ? Number(nm[1]) : 0;
29142
+ }
29143
+ vals.push(v);
29144
+ }
29145
+ }
29146
+ const addVals = [vals[0]];
29147
+ const addOps = [];
29148
+ for (let oi = 0; oi < ops.length; oi++) {
29149
+ const op = ops[oi];
29150
+ const rhs = vals[oi + 1];
29151
+ if (op === "*" || op === "/" || op === "%") {
29152
+ if ((op === "/" || op === "%") && rhs === 0)
29153
+ return undefined;
29154
+ const lhs = addVals.pop();
29155
+ addVals.push(op === "*" ? lhs * rhs : op === "/" ? lhs / rhs : lhs % rhs);
29156
+ }
29157
+ else {
29158
+ addOps.push(op);
29159
+ addVals.push(rhs);
29160
+ }
29161
+ }
29162
+ let acc = addVals[0];
29163
+ for (let oi = 0; oi < addOps.length; oi++) {
29164
+ acc = addOps[oi] === "+" ? acc + addVals[oi + 1] : acc - addVals[oi + 1];
29165
+ }
29166
+ out += awkValueText(awkNumeric(acc));
29167
+ }
29168
+ else if (p.kind === "length") {
29169
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29170
+ const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29171
+ out += String(Array.from(s).length);
29172
+ }
27408
29173
  else if (p.token === "0")
27409
29174
  out += l;
27410
29175
  else {
@@ -27412,8 +29177,9 @@ export class Runtime {
27412
29177
  out += idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
27413
29178
  }
27414
29179
  }
27415
- return out;
27416
- });
29180
+ outLines.push(out);
29181
+ }
29182
+ return outLines;
27417
29183
  }
27418
29184
  formatSyncTransformQ(val) {
27419
29185
  let ansi = false;
@@ -27456,41 +29222,716 @@ export class Runtime {
27456
29222
  }
27457
29223
  return out + "'";
27458
29224
  }
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");
29225
+ wcMaxLineWidth(text, singleByte) {
29226
+ let columns = 0;
29227
+ let maximum = 0;
29228
+ for (const char of text) {
29229
+ const point = char.codePointAt(0);
29230
+ if (point === 10 || point === 13 || point === 12) {
29231
+ maximum = Math.max(maximum, columns);
29232
+ columns = 0;
29233
+ }
29234
+ else if (point === 9)
29235
+ columns += 8 - columns % 8;
29236
+ else
29237
+ columns += singleByte ? Number(point >= 32 && point < 127) : wcDisplayWidth(point);
29238
+ }
29239
+ return Math.max(maximum, columns);
29240
+ }
29241
+ compareSyncVersion(a, b) {
29242
+ let ia = 0;
29243
+ let ib = 0;
29244
+ while (ia < a.length || ib < b.length) {
29245
+ while ((ia < a.length && !(a[ia] >= "0" && a[ia] <= "9")) || (ib < b.length && !(b[ib] >= "0" && b[ib] <= "9"))) {
29246
+ const ca = ia < a.length ? a[ia] : undefined;
29247
+ const cb = ib < b.length ? b[ib] : undefined;
29248
+ const da = ca !== undefined && ca >= "0" && ca <= "9";
29249
+ const db = cb !== undefined && cb >= "0" && cb <= "9";
29250
+ if (da && db)
29251
+ break;
29252
+ if (ca !== cb) {
29253
+ if (ca === undefined)
29254
+ return -1;
29255
+ if (cb === undefined)
29256
+ return 1;
29257
+ if (da)
29258
+ return 1;
29259
+ if (db)
29260
+ return -1;
29261
+ return ca < cb ? -1 : 1;
29262
+ }
29263
+ ia++;
29264
+ ib++;
29265
+ }
29266
+ const sa = ia;
29267
+ while (ia < a.length && a[ia] >= "0" && a[ia] <= "9")
29268
+ ia++;
29269
+ const sb = ib;
29270
+ while (ib < b.length && b[ib] >= "0" && b[ib] <= "9")
29271
+ ib++;
29272
+ if (sa < ia || sb < ib) {
29273
+ const na = BigInt(sa < ia ? a.slice(sa, ia) : "0");
29274
+ const nb = BigInt(sb < ib ? b.slice(sb, ib) : "0");
29275
+ if (na !== nb)
29276
+ return na < nb ? -1 : 1;
29277
+ }
29278
+ }
29279
+ return 0;
29280
+ }
29281
+ evalSyncNl(rawLines, opArgs) {
29282
+ let bodyStyle = "t";
29283
+ let format = "rn";
29284
+ let width = 6;
29285
+ let sep = "\t";
29286
+ let start = 1;
29287
+ let inc = 1;
29288
+ for (let i = 0; i < opArgs.length; i++) {
29289
+ const a = opArgs[i];
29290
+ if ((a === "-b" || a === "--body-numbering") && i + 1 < opArgs.length) {
29291
+ const v = opArgs[++i];
29292
+ if (v !== "a" && v !== "t" && v !== "n")
29293
+ return undefined;
29294
+ bodyStyle = v;
29295
+ }
29296
+ else if (a === "-ba" || a === "-bt" || a === "-bn") {
29297
+ bodyStyle = a[2];
29298
+ }
29299
+ else if ((a === "-n" || a === "--number-format") && i + 1 < opArgs.length) {
29300
+ const v = opArgs[++i];
29301
+ if (v !== "ln" && v !== "rn" && v !== "rz")
29302
+ return undefined;
29303
+ format = v;
29304
+ }
29305
+ else if (a === "-nln" || a === "-nrn" || a === "-nrz") {
29306
+ format = a.slice(2);
29307
+ }
29308
+ else if ((a === "-w" || a === "--number-width") && i + 1 < opArgs.length && /^[1-9][0-9]{0,2}$/.test(opArgs[i + 1])) {
29309
+ width = Number(opArgs[++i]);
29310
+ }
29311
+ else if (/^-w[1-9][0-9]{0,2}$/.test(a)) {
29312
+ width = Number(a.slice(2));
29313
+ }
29314
+ else if ((a === "-s" || a === "--number-separator") && i + 1 < opArgs.length) {
29315
+ sep = opArgs[++i];
29316
+ }
29317
+ else if (a.startsWith("-s") && a.length > 2) {
29318
+ sep = a.slice(2);
29319
+ }
29320
+ else if ((a === "-v" || a === "--starting-line-number") && i + 1 < opArgs.length && /^-?[0-9]{1,9}$/.test(opArgs[i + 1])) {
29321
+ start = Number(opArgs[++i]);
29322
+ }
29323
+ else if (/^-v-?[0-9]{1,9}$/.test(a)) {
29324
+ start = Number(a.slice(2));
29325
+ }
29326
+ else if ((a === "-i" || a === "--line-increment") && i + 1 < opArgs.length && /^-?[0-9]{1,9}$/.test(opArgs[i + 1])) {
29327
+ inc = Number(opArgs[++i]);
29328
+ }
29329
+ else if (/^-i-?[0-9]{1,9}$/.test(a)) {
29330
+ inc = Number(a.slice(2));
29331
+ }
29332
+ else {
29333
+ return undefined;
29334
+ }
29335
+ }
29336
+ const out = [];
29337
+ let curStyle = bodyStyle;
29338
+ let num = start;
29339
+ const unnumberedPrefix = " ".repeat(width + sep.length);
29340
+ for (let i = 0; i < rawLines.length; i++) {
29341
+ const l = rawLines[i];
29342
+ if (l === "\\:" || l === "\\:\\:" || l === "\\:\\:\\:") {
29343
+ curStyle = l === "\\:\\:" ? bodyStyle : "n";
29344
+ num = start;
29345
+ out.push("");
29346
+ continue;
29347
+ }
29348
+ const numbered = curStyle === "a" || (curStyle === "t" && l.length > 0);
29349
+ if (!numbered) {
29350
+ out.push(l.length === 0 && sep === "\t" ? " ".repeat(width + 1) : unnumberedPrefix + l);
29351
+ continue;
29352
+ }
29353
+ const label = String(num);
29354
+ const pad = Math.max(0, width - label.length);
29355
+ let prefix;
29356
+ if (format === "ln")
29357
+ prefix = label + " ".repeat(pad);
29358
+ else if (format === "rz")
29359
+ prefix = num < 0 ? "-" + "0".repeat(pad) + label.slice(1) : "0".repeat(pad) + label;
29360
+ else
29361
+ prefix = " ".repeat(pad) + label;
29362
+ out.push(prefix + sep + l);
29363
+ num += inc;
29364
+ }
29365
+ return out;
29366
+ }
29367
+ evalSyncPaste(rawLines, opArgs) {
29368
+ let serial = false;
29369
+ let delims = ["\t"];
29370
+ let literal = false;
29371
+ const files = [];
29372
+ const parseDelims = (s) => {
29373
+ if (s.length === 0)
29374
+ return [""];
29375
+ const res = [];
29376
+ 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];
29379
+ if (ch === "\\") {
29380
+ if (i + 1 >= s.length)
29381
+ return undefined;
29382
+ const esc = s[++i];
29383
+ res.push(escapes[esc] ?? esc);
29384
+ }
29385
+ else {
29386
+ res.push(ch);
29387
+ }
29388
+ }
29389
+ return res.length > 0 ? res : [""];
29390
+ };
29391
+ for (let i = 0; i < opArgs.length; i++) {
29392
+ const a = opArgs[i];
29393
+ if (literal || a === "-" || !a.startsWith("-")) {
29394
+ files.push(a);
29395
+ continue;
29396
+ }
29397
+ if (a === "--") {
29398
+ literal = true;
29399
+ continue;
29400
+ }
29401
+ if (a === "--serial") {
29402
+ serial = true;
29403
+ continue;
29404
+ }
29405
+ if (a === "--delimiters" && i + 1 < opArgs.length) {
29406
+ const d = parseDelims(opArgs[++i]);
29407
+ if (!d)
29408
+ return undefined;
29409
+ delims = d;
29410
+ continue;
29411
+ }
29412
+ if (a.startsWith("--delimiters=")) {
29413
+ const d = parseDelims(a.slice(13));
29414
+ if (!d)
29415
+ return undefined;
29416
+ delims = d;
29417
+ continue;
29418
+ }
29419
+ if (a.startsWith("--"))
29420
+ return undefined;
29421
+ for (let k = 1; k < a.length; k++) {
29422
+ const f = a[k];
29423
+ if (f === "s")
29424
+ serial = true;
29425
+ else if (f === "d") {
29426
+ const rest = a.slice(k + 1);
29427
+ const rawD = rest.length > 0 ? rest : (i + 1 < opArgs.length ? opArgs[++i] : undefined);
29428
+ if (rawD === undefined)
29429
+ return undefined;
29430
+ const d = parseDelims(rawD);
29431
+ if (!d)
29432
+ return undefined;
29433
+ delims = d;
29434
+ break;
29435
+ }
29436
+ else
29437
+ return undefined;
29438
+ }
29439
+ }
29440
+ if (files.length === 0)
29441
+ files.push("-");
29442
+ if (!files.every(f => f === "-"))
29443
+ return undefined;
29444
+ if (serial) {
29445
+ if (files.length !== 1)
29446
+ return undefined;
29447
+ if (rawLines.length === 0)
29448
+ return [];
29449
+ let joined = rawLines[0];
29450
+ for (let i = 1; i < rawLines.length; i++) {
29451
+ joined += delims[(i - 1) % delims.length] + rawLines[i];
29452
+ }
29453
+ return joined.split("\n");
29454
+ }
29455
+ const cols = files.length;
29456
+ const out = [];
29457
+ for (let i = 0; i < rawLines.length; i += cols) {
29458
+ let row = rawLines[i] ?? "";
29459
+ for (let c = 1; c < cols; c++) {
29460
+ row += delims[(c - 1) % delims.length] + (rawLines[i + c] ?? "");
29461
+ }
29462
+ out.push(row);
29463
+ }
29464
+ return out;
29465
+ }
29466
+ evalSyncNumfmt(rawLines, opArgs) {
29467
+ let fromScale = "none";
29468
+ let toScale = "none";
29469
+ let padding = 0;
29470
+ let suffix = "";
29471
+ for (let i = 0; i < opArgs.length; i++) {
29472
+ const a = opArgs[i];
29473
+ if (a.startsWith("--from=")) {
29474
+ const v = a.slice(7);
29475
+ if (v === "none" || v === "iec" || v === "iec-i" || v === "si" || v === "auto")
29476
+ fromScale = v;
29477
+ else
29478
+ return undefined;
29479
+ }
29480
+ else if (a.startsWith("--to=")) {
29481
+ const v = a.slice(5);
29482
+ if (v === "none" || v === "iec" || v === "iec-i" || v === "si")
29483
+ toScale = v;
29484
+ else
29485
+ return undefined;
29486
+ }
29487
+ else if (a.startsWith("--padding=")) {
29488
+ const v = a.slice(10);
29489
+ if (!/^-?[1-9][0-9]{0,2}$/.test(v))
29490
+ return undefined;
29491
+ padding = Number(v);
29492
+ }
29493
+ else if (a.startsWith("--suffix=")) {
29494
+ suffix = a.slice(9);
29495
+ }
29496
+ else {
29497
+ return undefined;
29498
+ }
29499
+ }
29500
+ const units = "KMGTPEZY";
29501
+ const out = [];
29502
+ for (let i = 0; i < rawLines.length; i++) {
29503
+ const raw = rawLines[i].trim();
29504
+ if (raw.length === 0) {
29505
+ out.push("");
29506
+ continue;
29507
+ }
29508
+ const m = /^([+-]?(?:\d+(?:\.\d*)?|\.\d+))([KMGTPEZYkmgtpezy]i?)?$/.exec(raw);
29509
+ if (!m)
29510
+ return undefined;
29511
+ let val = Number(m[1]);
29512
+ const suf = m[2] ?? "";
29513
+ if (suf.length > 0) {
29514
+ if (fromScale === "none")
29515
+ return undefined;
29516
+ const uChar = suf[0].toUpperCase();
29517
+ const pIdx = units.indexOf(uChar) + 1;
29518
+ if (pIdx <= 0)
29519
+ return undefined;
29520
+ const hasI = suf.length === 2 && suf[1].toLowerCase() === "i";
29521
+ if (fromScale === "iec-i" && !hasI)
29522
+ return undefined;
29523
+ if ((fromScale === "iec" || fromScale === "si") && hasI)
29524
+ return undefined;
29525
+ const fBase = (fromScale === "iec" || fromScale === "iec-i" || (fromScale === "auto" && hasI)) ? 1024 : 1000;
29526
+ val = val * Math.pow(fBase, pIdx);
29527
+ }
29528
+ else if (fromScale === "iec-i") {
29529
+ // plain number ok
29530
+ }
29531
+ val = val >= 0 ? Math.ceil(val - 1e-12) : -Math.ceil(Math.abs(val) - 1e-12);
29532
+ let rendered;
29533
+ if (toScale === "none") {
29534
+ rendered = String(Math.trunc(val));
29535
+ }
29536
+ else {
29537
+ const tBase = (toScale === "iec" || toScale === "iec-i") ? 1024 : 1000;
29538
+ let pIdx = 0;
29539
+ let scaled = val;
29540
+ while (Math.abs(scaled) >= tBase && pIdx < units.length) {
29541
+ scaled /= tBase;
29542
+ pIdx++;
29543
+ }
29544
+ if (pIdx === 0) {
29545
+ rendered = String(Math.trunc(scaled));
29546
+ }
29547
+ else {
29548
+ if (Math.abs(scaled) < 10) {
29549
+ scaled = (scaled >= 0 ? Math.ceil(scaled * 10 - 1e-12) : -Math.ceil(Math.abs(scaled) * 10 - 1e-12)) / 10;
29550
+ if (Math.abs(scaled) >= tBase && pIdx < units.length) {
29551
+ scaled /= tBase;
29552
+ pIdx++;
29553
+ }
29554
+ }
29555
+ else {
29556
+ scaled = scaled >= 0 ? Math.ceil(scaled - 1e-12) : -Math.ceil(Math.abs(scaled) - 1e-12);
29557
+ if (Math.abs(scaled) >= tBase && pIdx < units.length) {
29558
+ scaled /= tBase;
29559
+ pIdx++;
29560
+ }
29561
+ }
29562
+ const numText = Math.abs(scaled) < 10 ? scaled.toFixed(1) : scaled.toFixed(0);
29563
+ const uText = (toScale === "si" && pIdx === 1 ? "k" : units[pIdx - 1]) + (toScale === "iec-i" ? "i" : "");
29564
+ rendered = numText + uText;
29565
+ }
29566
+ }
29567
+ rendered += suffix;
29568
+ if (padding !== 0) {
29569
+ const w = Math.abs(padding);
29570
+ if (rendered.length < w) {
29571
+ const pad = " ".repeat(w - rendered.length);
29572
+ rendered = padding < 0 ? rendered + pad : pad + rendered;
29573
+ }
29574
+ }
29575
+ out.push(rendered);
29576
+ }
29577
+ return out;
29578
+ }
29579
+ evalSyncSort(rawLines, opArgs, isByteLocale) {
29580
+ if (isByteLocale)
29581
+ return undefined;
29582
+ let rev = false;
29583
+ let num = false;
29584
+ let human = false;
29585
+ let month = false;
29586
+ let dict = false;
29587
+ let uniq = false;
29588
+ let fold = false;
29589
+ let blanks = false;
29590
+ let stable = false;
29591
+ let ver = false;
29592
+ let sep;
29593
+ let keySpec;
29594
+ for (let i = 0; i < opArgs.length; i++) {
29595
+ const a = opArgs[i];
29596
+ if (/^-[runfbsVhMd]+$/.test(a)) {
29597
+ if (a.includes("r"))
29598
+ rev = true;
29599
+ if (a.includes("n"))
29600
+ num = true;
29601
+ if (a.includes("h"))
29602
+ human = true;
29603
+ if (a.includes("M"))
29604
+ month = true;
29605
+ if (a.includes("d"))
29606
+ dict = true;
29607
+ if (a.includes("u"))
29608
+ uniq = true;
29609
+ if (a.includes("f"))
29610
+ fold = true;
29611
+ if (a.includes("b"))
29612
+ blanks = true;
29613
+ if (a.includes("s"))
29614
+ stable = true;
29615
+ if (a.includes("V"))
29616
+ ver = true;
29617
+ }
29618
+ else if (a === "--reverse")
29619
+ rev = true;
29620
+ else if (a === "--numeric-sort")
29621
+ num = true;
29622
+ else if (a === "--human-numeric-sort")
29623
+ human = true;
29624
+ else if (a === "--month-sort")
29625
+ month = true;
29626
+ else if (a === "--dictionary-order")
29627
+ dict = true;
29628
+ else if (a === "--unique")
29629
+ uniq = true;
29630
+ else if (a === "--ignore-case")
29631
+ fold = true;
29632
+ else if (a === "--ignore-leading-blanks")
29633
+ blanks = true;
29634
+ else if (a === "--stable")
29635
+ stable = true;
29636
+ else if (a === "--version-sort")
29637
+ ver = true;
29638
+ else if (a === "-t" && i + 1 < opArgs.length && opArgs[i + 1].length === 1) {
29639
+ sep = opArgs[++i];
29640
+ }
29641
+ else if (a.startsWith("-t") && a.length === 3) {
29642
+ sep = a[2];
29643
+ }
29644
+ else if (a.startsWith("--field-separator=") && a.length === 19) {
29645
+ sep = a[18];
29646
+ }
29647
+ else if (a === "-k" && i + 1 < opArgs.length) {
29648
+ if (keySpec !== undefined)
29649
+ return undefined;
29650
+ keySpec = opArgs[++i];
29651
+ }
29652
+ else if (a.startsWith("-k") && a.length > 2) {
29653
+ if (keySpec !== undefined)
29654
+ return undefined;
29655
+ keySpec = a.slice(2);
29656
+ }
29657
+ else {
29658
+ return undefined;
29659
+ }
29660
+ }
29661
+ if (ver || keySpec?.includes("V"))
29662
+ return undefined;
29663
+ let startField = 1;
29664
+ let endField;
29665
+ let keyNum = num;
29666
+ let keyHuman = human;
29667
+ let keyMonth = month;
29668
+ let keyDict = dict;
29669
+ let keyRev = rev;
29670
+ let keyFold = fold;
29671
+ let keyBlanks = blanks;
29672
+ let keyVer = ver;
29673
+ if (keySpec !== undefined) {
29674
+ const km = /^([1-9][0-9]{0,2})(?:,([1-9][0-9]{0,2}))?([nrbfVhMd]*)$/.exec(keySpec);
29675
+ if (!km)
29676
+ return undefined;
29677
+ startField = Number(km[1]);
29678
+ endField = km[2] !== undefined ? Number(km[2]) : undefined;
29679
+ if (endField !== undefined && endField < startField)
29680
+ return undefined;
29681
+ const kf = km[3] ?? "";
29682
+ if (kf.includes("n"))
29683
+ keyNum = true;
29684
+ if (kf.includes("h"))
29685
+ keyHuman = true;
29686
+ if (kf.includes("M"))
29687
+ keyMonth = true;
29688
+ if (kf.includes("d"))
29689
+ keyDict = true;
29690
+ if (kf.includes("r"))
29691
+ keyRev = true;
29692
+ if (kf.includes("f"))
29693
+ keyFold = true;
29694
+ if (kf.includes("b"))
29695
+ keyBlanks = true;
29696
+ if (kf.includes("V"))
29697
+ keyVer = true;
29698
+ }
29699
+ const extractKey = (l) => {
29700
+ let raw = keySpec === undefined
29701
+ ? l
29702
+ : (() => {
29703
+ if (sep !== undefined) {
29704
+ const fields = l.split(sep);
29705
+ return fields.slice(startField - 1, endField).join(sep);
29706
+ }
29707
+ // Default fields include their leading blanks; retain exact bytes.
29708
+ let offset = 0;
29709
+ let start = l.length;
29710
+ let end = l.length;
29711
+ for (let field = 1; offset < l.length; field++) {
29712
+ if (field === startField)
29713
+ start = offset;
29714
+ while (l[offset] === " " || l[offset] === "\t")
29715
+ offset++;
29716
+ while (offset < l.length && l[offset] !== " " && l[offset] !== "\t")
29717
+ offset++;
29718
+ if (field === endField) {
29719
+ end = offset;
29720
+ break;
29721
+ }
29722
+ }
29723
+ return l.slice(start, end);
29724
+ })();
29725
+ if (keyBlanks)
29726
+ raw = raw.replace(/^[ \t]+/, "");
29727
+ if (keyDict)
29728
+ raw = raw.replace(/[^a-zA-Z0-9 \t]+/g, "");
29729
+ return keyFold ? raw.toUpperCase() : raw;
29730
+ };
27463
29731
  const parseNum = (s) => {
27464
- const m = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(s);
29732
+ const m = /^[ \t]*(-?(?:\d+(?:\.\d*)?|\.\d+))/.exec(s);
27465
29733
  return m ? Number(m[1]) : 0;
27466
29734
  };
29735
+ const compareHuman = (sa, sb) => {
29736
+ const parseH = (s) => {
29737
+ const m = /^[ \t]*(-?(?:\d+(?:\.\d*)?|\.\d+))([KMGTPEZYkmgtpezy])?/.exec(s);
29738
+ if (!m)
29739
+ return { sign: 0, unit: 0, val: 0 };
29740
+ const val = Number(m[1]);
29741
+ if (val === 0)
29742
+ return { sign: 0, unit: 0, val: 0 };
29743
+ const uChar = m[2] ? m[2].toUpperCase() : "";
29744
+ const unit = uChar === "" ? 0 : "KMGTPEZY".indexOf(uChar) + 1;
29745
+ return { sign: val < 0 ? -1 : 1, unit, val };
29746
+ };
29747
+ const ha = parseH(sa);
29748
+ const hb = parseH(sb);
29749
+ if (ha.sign !== hb.sign)
29750
+ return ha.sign - hb.sign;
29751
+ if (ha.unit !== hb.unit)
29752
+ return ha.sign < 0 ? hb.unit - ha.unit : ha.unit - hb.unit;
29753
+ return ha.val - hb.val;
29754
+ };
29755
+ const parseMonth = (s) => {
29756
+ const m = /^[ \t]*([a-zA-Z]{3})/.exec(s);
29757
+ if (!m)
29758
+ return 0;
29759
+ const idx = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"].indexOf(m[1].toUpperCase());
29760
+ return idx === -1 ? 0 : idx + 1;
29761
+ };
27467
29762
  const sorted = [...rawLines].sort((a, b) => {
27468
- if (num) {
27469
- const na = parseNum(a);
27470
- const nb = parseNum(b);
29763
+ const ka = extractKey(a);
29764
+ const kb = extractKey(b);
29765
+ if (keyHuman) {
29766
+ const hc = compareHuman(ka, kb);
29767
+ if (hc !== 0)
29768
+ return keyRev ? -hc : hc;
29769
+ }
29770
+ else if (keyMonth) {
29771
+ const ma = parseMonth(ka);
29772
+ const mb = parseMonth(kb);
29773
+ if (ma !== mb)
29774
+ return keyRev ? mb - ma : ma - mb;
29775
+ }
29776
+ else if (keyNum) {
29777
+ const na = parseNum(ka);
29778
+ const nb = parseNum(kb);
27471
29779
  if (na !== nb)
27472
- return rev ? nb - na : na - nb;
29780
+ return keyRev ? nb - na : na - nb;
29781
+ }
29782
+ else if (keyVer) {
29783
+ const vc = this.compareSyncVersion(ka, kb);
29784
+ if (vc !== 0)
29785
+ return keyRev ? -vc : vc;
27473
29786
  }
29787
+ else if (ka !== kb) {
29788
+ return ka < kb ? (keyRev ? 1 : -1) : (keyRev ? -1 : 1);
29789
+ }
29790
+ if (uniq || stable)
29791
+ return 0;
29792
+ const fa = fold ? a.toUpperCase() : a;
29793
+ const fb = fold ? b.toUpperCase() : b;
29794
+ if (fa !== fb)
29795
+ return fa < fb ? (rev ? 1 : -1) : (rev ? -1 : 1);
27474
29796
  return a < b ? (rev ? 1 : -1) : a > b ? (rev ? -1 : 1) : 0;
27475
29797
  });
27476
29798
  if (!uniq)
27477
29799
  return sorted;
27478
29800
  const dedup = [];
27479
29801
  for (let i = 0; i < sorted.length; i++) {
27480
- if (i === 0 || sorted[i] !== sorted[i - 1])
29802
+ if (i === 0) {
27481
29803
  dedup.push(sorted[i]);
29804
+ }
29805
+ else {
29806
+ const ka = extractKey(sorted[i]);
29807
+ const kb = extractKey(sorted[i - 1]);
29808
+ const same = keyHuman ? compareHuman(ka, kb) === 0 : keyMonth ? parseMonth(ka) === parseMonth(kb) : keyNum ? parseNum(ka) === parseNum(kb) : keyVer ? this.compareSyncVersion(ka, kb) === 0 : ka === kb;
29809
+ if (!same)
29810
+ dedup.push(sorted[i]);
29811
+ }
27482
29812
  }
27483
29813
  return dedup;
27484
29814
  }
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);
29815
+ normalizeSyncWcFlag(arg) {
29816
+ if (arg === "-l" || arg === "--lines")
29817
+ return "-l";
29818
+ if (arg === "-c" || arg === "--bytes")
29819
+ return "-c";
29820
+ if (arg === "-w" || arg === "--words")
29821
+ return "-w";
29822
+ if (arg === "-m" || arg === "--chars")
29823
+ return "-m";
29824
+ if (arg === "-L" || arg === "--max-line-length")
29825
+ return "-L";
29826
+ return undefined;
29827
+ }
29828
+ evalSyncUniq(rawLines, opArgs) {
29829
+ let ignoreCase = false;
29830
+ let hasCount = false;
29831
+ let onlyRepeated = false;
29832
+ let onlyUnique = false;
29833
+ let skipFields = 0;
29834
+ let skipChars = 0;
29835
+ let checkChars = Infinity;
29836
+ for (let i = 0; i < opArgs.length; i++) {
29837
+ const a = opArgs[i];
29838
+ if (a === "--ignore-case")
29839
+ ignoreCase = true;
29840
+ else if (a === "--count")
29841
+ hasCount = true;
29842
+ else if (a === "--repeated")
29843
+ onlyRepeated = true;
29844
+ else if (a === "--unique")
29845
+ onlyUnique = true;
29846
+ else if (/^--skip-fields=[0-9]{1,4}$/.test(a))
29847
+ skipFields = Number(a.slice(14));
29848
+ else if (/^--skip-chars=[0-9]{1,4}$/.test(a))
29849
+ skipChars = Number(a.slice(13));
29850
+ else if (/^--check-chars=[0-9]{1,4}$/.test(a))
29851
+ checkChars = Number(a.slice(14));
29852
+ else if ((a === "-f" || a === "-s" || a === "-w") && i + 1 < opArgs.length && /^[0-9]{1,4}$/.test(opArgs[i + 1])) {
29853
+ const n = Number(opArgs[++i]);
29854
+ if (a === "-f")
29855
+ skipFields = n;
29856
+ else if (a === "-s")
29857
+ skipChars = n;
29858
+ else
29859
+ checkChars = n;
29860
+ }
29861
+ else if (/^-[fsw][0-9]{1,4}$/.test(a)) {
29862
+ const n = Number(a.slice(2));
29863
+ if (a[1] === "f")
29864
+ skipFields = n;
29865
+ else if (a[1] === "s")
29866
+ skipChars = n;
29867
+ else
29868
+ checkChars = n;
29869
+ }
29870
+ else if (/^-[cdui]+$/.test(a)) {
29871
+ for (let k = 1; k < a.length; k++) {
29872
+ const ch = a[k];
29873
+ if (ch === "i")
29874
+ ignoreCase = true;
29875
+ else if (ch === "c")
29876
+ hasCount = true;
29877
+ else if (ch === "d")
29878
+ onlyRepeated = true;
29879
+ else if (ch === "u")
29880
+ onlyUnique = true;
29881
+ }
29882
+ }
29883
+ else {
29884
+ return undefined;
29885
+ }
29886
+ }
29887
+ const keyOf = (l) => {
29888
+ let offset = 0;
29889
+ for (let f = 0; f < skipFields; f++) {
29890
+ while (offset < l.length && (l[offset] === " " || l[offset] === "\t"))
29891
+ offset++;
29892
+ while (offset < l.length && l[offset] !== " " && l[offset] !== "\t")
29893
+ offset++;
29894
+ }
29895
+ if (skipChars === 0 && checkChars === Infinity) {
29896
+ const sub = l.slice(offset);
29897
+ return ignoreCase ? sub.toLowerCase() : sub;
29898
+ }
29899
+ const chars = Array.from(l.slice(offset));
29900
+ const sub = (checkChars === Infinity ? chars.slice(skipChars) : chars.slice(skipChars, skipChars + checkChars)).join("");
29901
+ return ignoreCase ? sub.toLowerCase() : sub;
29902
+ };
29903
+ const outLines = [];
29904
+ let uIdx = 0;
29905
+ while (uIdx < rawLines.length) {
29906
+ const k0 = keyOf(rawLines[uIdx]);
29907
+ let uEnd = uIdx + 1;
29908
+ while (uEnd < rawLines.length && keyOf(rawLines[uEnd]) === k0)
29909
+ uEnd++;
29910
+ const count = uEnd - uIdx;
29911
+ if ((!onlyRepeated || count > 1) && (!onlyUnique || count === 1)) {
29912
+ outLines.push(hasCount ? `${String(count).padStart(7, " ")} ${rawLines[uIdx]}` : rawLines[uIdx]);
29913
+ }
29914
+ uIdx = uEnd;
29915
+ }
29916
+ return outLines;
29917
+ }
29918
+ sortSyncLines(rawLines, flag) {
29919
+ return this.evalSyncSort(rawLines, flag !== undefined ? [flag] : [], false) ?? [...rawLines];
29920
+ }
29921
+ translateSyncTr(input, set1Spec, set2Spec, truncateSet1 = false) {
29922
+ if ((set1Spec === "a-z" || set1Spec === "[:lower:]") && (set2Spec === "A-Z" || set2Spec === "[:upper:]")) {
29923
+ return input.replace(/[a-z]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 32));
29924
+ }
29925
+ if ((set1Spec === "A-Z" || set1Spec === "[:upper:]") && (set2Spec === "a-z" || set2Spec === "[:lower:]")) {
29926
+ return input.replace(/[A-Z]/g, ch => String.fromCharCode(ch.charCodeAt(0) + 32));
29927
+ }
29928
+ const rawS1 = this.expandSyncTrCharArray(set1Spec);
27491
29929
  const s2 = this.expandSyncTrCharArray(set2Spec);
27492
- if (!s1 || !s2 || s1.length === 0 || s2.length === 0)
29930
+ if (!rawS1 || !s2 || rawS1.length === 0 || s2.length === 0)
27493
29931
  return undefined;
29932
+ const s1 = truncateSet1 ? rawS1.slice(0, s2.length) : rawS1;
29933
+ if (s1.length === 0)
29934
+ return input;
27494
29935
  if (s1.length === 1 && s2.length === 1)
27495
29936
  return input.split(s1[0]).join(s2[0]);
27496
29937
  const map = new Map();
@@ -27684,7 +30125,7 @@ export class Runtime {
27684
30125
  if (!sName || hasShellFunction(rawState, sName) || rawState.extensions?.builtins.has(sName))
27685
30126
  return undefined;
27686
30127
  const extDef = this.getExternalCommand(sName);
27687
- if (!extDef || (sName !== "rev" && sName !== "tac" && !builtInDirectContextExecutors.has(extDef.execute)) || ((sName === "rev" || sName === "tac") && customRegisteredRegistries.has(this.commands)) || customRegisteredCommands.has(extDef.execute))
30128
+ if (!extDef || (sName !== "rev" && sName !== "tac" && sName !== "nl" && sName !== "paste" && sName !== "jq" && !builtInDirectContextExecutors.has(extDef.execute)) || customRegisteredCommands.has(extDef.execute))
27688
30129
  return undefined;
27689
30130
  if (!this.arePureArgWords(sCmd.words, rawState))
27690
30131
  return undefined;
@@ -27704,29 +30145,32 @@ export class Runtime {
27704
30145
  return undefined;
27705
30146
  }
27706
30147
  else if (sName === "uniq") {
27707
- if (sArgs.length > 1 || (sArgs.length === 1 && sArgs[0] !== "-c" && sArgs[0] !== "-d" && sArgs[0] !== "-u" && sArgs[0] !== "-i"))
30148
+ if (this.evalSyncUniq([], sArgs) === undefined)
27708
30149
  return undefined;
27709
30150
  }
27710
30151
  else if (sName === "sort") {
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"))
30152
+ if (this.evalSyncSort([], sArgs, byteLocale(rawState.variables)) === undefined)
27712
30153
  return undefined;
27713
30154
  }
27714
30155
  else if (sName === "head" || sName === "tail") {
27715
30156
  if (sArgs.length === 0) {
27716
30157
  sArgs.push("-n", "10");
27717
30158
  }
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);
30159
+ else if (sArgs.length === 1 && (/^-(?:n|c)?[0-9]+$/.test(sArgs[0]) || /^--(?:lines|bytes)=[0-9]+$/.test(sArgs[0]) || (sName === "tail" && /^(?:-n|--lines=)\+[0-9]+$/.test(sArgs[0])) || (sName === "head" && /^(?:-n|--lines=)-[0-9]+$/.test(sArgs[0])))) {
30160
+ const flag = (sArgs[0].startsWith("-c") || sArgs[0].startsWith("--bytes=")) ? "-c" : "-n";
30161
+ const numStr = sArgs[0].startsWith("--") ? sArgs[0].slice(sArgs[0].indexOf("=") + 1) : ((sArgs[0].startsWith("-n") || sArgs[0].startsWith("-c")) ? sArgs[0].slice(2) : sArgs[0].slice(1));
27721
30162
  sArgs.length = 0;
27722
30163
  sArgs.push(flag, numStr);
27723
30164
  }
27724
- if (sArgs.length !== 2 || (sArgs[0] !== "-n" && sArgs[0] !== "-c") || !/^[0-9]+$/.test(sArgs[1]))
30165
+ const validNum = sArgs.length === 2 && ((sArgs[0] === "-n" && (/^[0-9]+$/.test(sArgs[1]) || (sName === "tail" && /^\+[0-9]+$/.test(sArgs[1])) || (sName === "head" && /^-[0-9]+$/.test(sArgs[1])))) || (sArgs[0] === "-c" && /^[0-9]+$/.test(sArgs[1])));
30166
+ if (!validNum)
27725
30167
  return undefined;
27726
30168
  }
27727
30169
  else if (sName === "wc") {
27728
- if (sArgs.length !== 1 || (sArgs[0] !== "-l" && sArgs[0] !== "-c" && sArgs[0] !== "-w"))
30170
+ const normWc = sArgs.length === 1 ? this.normalizeSyncWcFlag(sArgs[0]) : undefined;
30171
+ if (!normWc)
27729
30172
  return undefined;
30173
+ sArgs[0] = normWc;
27730
30174
  }
27731
30175
  else if (sName === "sed") {
27732
30176
  if (this.evalSyncSed([], sArgs) === undefined)
@@ -27745,17 +30189,33 @@ export class Runtime {
27745
30189
  return undefined;
27746
30190
  }
27747
30191
  else if (sName === "jq") {
27748
- if (this.evalSyncJq("null", sArgs) === undefined)
30192
+ if (sArgs.length === 0 || sArgs.length > 2)
30193
+ return undefined;
30194
+ if (sArgs.length === 1 && sArgs[0].startsWith("-"))
30195
+ return undefined;
30196
+ if (sArgs.length === 2 && !["-r", "--raw-output", "-c", "--compact-output", "-rc", "-cr"].includes(sArgs[0]))
27749
30197
  return undefined;
27750
30198
  }
27751
30199
  else if (sName === "base64") {
27752
30200
  if (sArgs.length > 1 || (sArgs.length === 1 && sArgs[0] !== "-d" && sArgs[0] !== "--decode"))
27753
30201
  return undefined;
27754
30202
  }
27755
- else if (sName === "tac" || sName === "nl") {
30203
+ else if (sName === "tac") {
27756
30204
  if (sArgs.length !== 0)
27757
30205
  return undefined;
27758
30206
  }
30207
+ else if (sName === "nl") {
30208
+ if (this.evalSyncNl([], sArgs) === undefined)
30209
+ return undefined;
30210
+ }
30211
+ else if (sName === "paste") {
30212
+ if (this.evalSyncPaste([], sArgs) === undefined)
30213
+ return undefined;
30214
+ }
30215
+ else if (sName === "numfmt") {
30216
+ if (this.evalSyncNumfmt([], sArgs) === undefined)
30217
+ return undefined;
30218
+ }
27759
30219
  else {
27760
30220
  return undefined;
27761
30221
  }
@@ -27843,8 +30303,10 @@ export class Runtime {
27843
30303
  const isInlineBase64 = firstName === "base64";
27844
30304
  const isInlineTac = firstName === "tac";
27845
30305
  const isInlineNl = firstName === "nl";
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")));
30306
+ const isInlinePaste = firstName === "paste";
30307
+ const isInlineNumfmt = firstName === "numfmt";
30308
+ const isInlineSort = firstName === "sort" && !byteLocale(rawState.variables);
30309
+ const isInlineUniq = firstName === "uniq";
27848
30310
  const inlineSedMatch = firstName === "sed";
27849
30311
  if (firstName === "rev" ||
27850
30312
  firstName === "head" ||
@@ -27861,10 +30323,13 @@ export class Runtime {
27861
30323
  isInlineJq ||
27862
30324
  isInlineBase64 ||
27863
30325
  isInlineTac ||
27864
- isInlineNl) {
30326
+ isInlineNl ||
30327
+ isInlinePaste ||
30328
+ isInlineNumfmt) {
27865
30329
  const inStr = prevLen === 0 ? "" : sharedSyncPipeDecoder.decode(prevBuf.subarray(0, prevLen));
27866
30330
  const rawLines = inStr.length === 0 ? [] : (inStr.endsWith("\n") ? inStr.slice(0, -1).split("\n") : inStr.split("\n"));
27867
30331
  let outLines = [];
30332
+ let sedTerminated = false;
27868
30333
  if (firstName === "rev") {
27869
30334
  outLines = rawLines.map(l => Array.from(l).reverse().join(""));
27870
30335
  }
@@ -27883,7 +30348,16 @@ export class Runtime {
27883
30348
  prevLen = sub.byteLength;
27884
30349
  continue;
27885
30350
  }
27886
- outLines = firstName === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
30351
+ if (firstName === "tail" && stageArgs[1].startsWith("+")) {
30352
+ outLines = rawLines.slice(Math.max(0, count - 1));
30353
+ }
30354
+ else if (firstName === "head" && stageArgs[1].startsWith("-")) {
30355
+ const drop = Math.abs(count);
30356
+ outLines = drop === 0 ? [...rawLines] : rawLines.slice(0, Math.max(0, rawLines.length - drop));
30357
+ }
30358
+ else {
30359
+ outLines = firstName === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
30360
+ }
27887
30361
  }
27888
30362
  else if (firstName === "wc") {
27889
30363
  if (stageArgs[0] === "-l") {
@@ -27897,6 +30371,12 @@ export class Runtime {
27897
30371
  const trimmed = inStr.trim();
27898
30372
  outLines = [String(trimmed.length === 0 ? 0 : trimmed.split(/[ \t\n\r\f\v]+/).length)];
27899
30373
  }
30374
+ else if (stageArgs[0] === "-m") {
30375
+ outLines = [String(byteLocale(rawState.variables) ? prevLen : Array.from(inStr).length)];
30376
+ }
30377
+ else if (stageArgs[0] === "-L") {
30378
+ outLines = [String(this.wcMaxLineWidth(inStr, byteLocale(rawState.variables)))];
30379
+ }
27900
30380
  else {
27901
30381
  outLines = [String(prevLen)];
27902
30382
  }
@@ -27924,28 +30404,20 @@ export class Runtime {
27924
30404
  continue;
27925
30405
  }
27926
30406
  else if (isInlineSort) {
27927
- outLines = this.sortSyncLines(rawLines, stageArgs[0]);
30407
+ outLines = this.evalSyncSort(rawLines, stageArgs, false) ?? [];
27928
30408
  }
27929
30409
  else if (isInlineUniq) {
27930
- const mode = stageArgs[0];
27931
- outLines = [];
27932
- let uIdx = 0;
27933
- while (uIdx < rawLines.length) {
27934
- let uEnd = uIdx + 1;
27935
- while (uEnd < rawLines.length && rawLines[uEnd] === rawLines[uIdx])
27936
- uEnd++;
27937
- const count = uEnd - uIdx;
27938
- if (mode === "-d" ? count > 1 : mode === "-u" ? count === 1 : true) {
27939
- outLines.push(mode === "-c" ? `${String(count).padStart(7, " ")} ${rawLines[uIdx]}` : rawLines[uIdx]);
27940
- }
27941
- uIdx = uEnd;
27942
- }
30410
+ const uniqRes = this.evalSyncUniq(rawLines, stageArgs);
30411
+ if (uniqRes === undefined)
30412
+ return undefined;
30413
+ outLines = uniqRes;
27943
30414
  }
27944
30415
  else if (inlineSedMatch) {
27945
30416
  const sedRes = this.evalSyncSed(rawLines, stageArgs);
27946
30417
  if (sedRes === undefined)
27947
30418
  return undefined;
27948
- outLines = sedRes;
30419
+ outLines = sedRes.lines;
30420
+ sedTerminated = sedRes.lastInputIndex < rawLines.length - 1;
27949
30421
  }
27950
30422
  else if (isInlineAwk) {
27951
30423
  const awkRes = this.evalSyncAwk(rawLines, stageArgs);
@@ -27968,17 +30440,22 @@ export class Runtime {
27968
30440
  outLines = [...rawLines].reverse();
27969
30441
  }
27970
30442
  else if (isInlineNl) {
27971
- outLines = [];
27972
- let nlNum = 1;
27973
- for (let li = 0; li < rawLines.length; li++) {
27974
- const l = rawLines[li];
27975
- if (l === "\\:" || l === "\\:\\:" || l === "\\:\\:\\:")
27976
- return undefined;
27977
- if (l.length === 0)
27978
- outLines.push(" ");
27979
- else
27980
- outLines.push(String(nlNum++).padStart(6, " ") + "\t" + l);
27981
- }
30443
+ const nlRes = this.evalSyncNl(rawLines, stageArgs);
30444
+ if (nlRes === undefined)
30445
+ return undefined;
30446
+ outLines = nlRes;
30447
+ }
30448
+ else if (isInlinePaste) {
30449
+ const pasteRes = this.evalSyncPaste(rawLines, stageArgs);
30450
+ if (pasteRes === undefined)
30451
+ return undefined;
30452
+ outLines = pasteRes;
30453
+ }
30454
+ else if (isInlineNumfmt) {
30455
+ const nmRes = this.evalSyncNumfmt(rawLines, stageArgs);
30456
+ if (nmRes === undefined)
30457
+ return undefined;
30458
+ outLines = nmRes;
27982
30459
  }
27983
30460
  else if (isInlineBase64) {
27984
30461
  if (stageArgs.length === 0) {
@@ -27990,7 +30467,7 @@ export class Runtime {
27990
30467
  if (cleaned.length % 4 !== 0 || (cleaned.length > 0 && !/^[A-Za-z0-9+/]+={0,2}$/.test(cleaned)))
27991
30468
  return undefined;
27992
30469
  const decoded = this.syncBase64DecodeBytes(cleaned);
27993
- if (decoded.includes(0) || decoded.byteLength > nextBuf.byteLength)
30470
+ if (decoded.some(byte => byte === 0 || byte >= 128) || decoded.byteLength > nextBuf.byteLength)
27994
30471
  return undefined;
27995
30472
  const nextTotalBytes = this.budget.bytes + decoded.byteLength;
27996
30473
  if (nextTotalBytes > this.budget.maxOutputBytesSmi && decoded.byteLength > this.budget.limits.maxOutputBytes - this.budget.bytes)
@@ -28010,7 +30487,7 @@ export class Runtime {
28010
30487
  }
28011
30488
  // These filters preserve the terminator of the final selected input line.
28012
30489
  const preservesTerminator = firstName === "head" || firstName === "tail" || firstName === "rev" || firstName === "sed";
28013
- const terminated = !preservesTerminator || inStr.endsWith("\n") || firstName === "head" && outLines.length < rawLines.length;
30490
+ const terminated = sedTerminated || !preservesTerminator || inStr.endsWith("\n") || firstName === "head" && outLines.length < rawLines.length;
28014
30491
  const outStr = outLines.length > 0 ? outLines.join("\n") + (terminated ? "\n" : "") : "";
28015
30492
  const outByteLen = shellValueByteLength(outStr);
28016
30493
  if (outByteLen > nextBuf.byteLength)
@@ -28175,8 +30652,8 @@ export class Runtime {
28175
30652
  w0Plain === "sort" ||
28176
30653
  w0Plain === "uniq" ||
28177
30654
  w0Plain === "tr" ||
28178
- (hasSingleHereStringRedir && (w0Plain === "base64" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl"));
28179
- if (isSingleFileTool && !hasShellFunction(rawState, w0Plain) && (this.commands.has(w0Plain) || ((w0Plain === "rev" || w0Plain === "tac") && Boolean(this.getExternalCommand(w0Plain))))) {
30655
+ (hasSingleHereStringRedir && (w0Plain === "base64" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste" || w0Plain === "numfmt"));
30656
+ if (isSingleFileTool && !hasShellFunction(rawState, w0Plain) && (this.commands.has(w0Plain) || ((w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste") && Boolean(this.getExternalCommand(w0Plain))))) {
28180
30657
  const allArgs = [];
28181
30658
  let fOk = true;
28182
30659
  for (let i = 1; i < cmd.words.length; i++) {
@@ -28216,6 +30693,12 @@ export class Runtime {
28216
30693
  // Non-ASCII file inputs use normal execution, which owns their raw ShellValue.
28217
30694
  if (view && view.byteLength <= 16384 && !view.includes(0) && (hasSingleHereStringRedir || view.every(byte => byte < 128))) {
28218
30695
  const fileStr = hasSingleHereStringRedir ? hereStrVal : sharedSyncPipeDecoder.decode(view);
30696
+ // Record-based shortcuts below serialize complete newline-terminated records.
30697
+ // Let normal commands preserve partial final records when reading files.
30698
+ if (fileStr.length > 0 && !fileStr.endsWith("\n") &&
30699
+ (w0Plain === "head" || w0Plain === "tail" || w0Plain === "sed" || w0Plain === "cut" || w0Plain === "uniq"))
30700
+ return undefined;
30701
+ const renderLines = (lines) => lines.length === 0 ? "" : lines.join("\n") + "\n";
28219
30702
  const rawLines = fileStr.endsWith("\n") ? fileStr.slice(0, -1).split("\n") : (fileStr.length === 0 ? [] : fileStr.split("\n"));
28220
30703
  let fileRes;
28221
30704
  let exitStatus = 0;
@@ -28225,19 +30708,36 @@ export class Runtime {
28225
30708
  else if ((w0Plain === "head" || w0Plain === "tail") && opArgs.length <= 2) {
28226
30709
  let count = opArgs.length === 0 ? 10 : undefined;
28227
30710
  let byteCount;
28228
- if (opArgs.length === 1 && opArgs[0].startsWith("-n") && /^[0-9]{1,5}$/.test(opArgs[0].slice(2)))
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));
30711
+ let countRaw = opArgs.length === 0 ? "10" : undefined;
30712
+ if (opArgs.length === 1 && (opArgs[0].startsWith("-n") || opArgs[0].startsWith("--lines="))) {
30713
+ const sub = opArgs[0].startsWith("--lines=") ? opArgs[0].slice(8) : opArgs[0].slice(2);
30714
+ if (/^[0-9]{1,5}$/.test(sub) || (w0Plain === "tail" && /^\+[0-9]{1,5}$/.test(sub)) || (w0Plain === "head" && /^-[0-9]{1,5}$/.test(sub)))
30715
+ countRaw = sub;
30716
+ }
30717
+ else if (opArgs.length === 1 && (opArgs[0].startsWith("-c") || opArgs[0].startsWith("--bytes="))) {
30718
+ const sub = opArgs[0].startsWith("--bytes=") ? opArgs[0].slice(8) : opArgs[0].slice(2);
30719
+ if (/^[0-9]{1,5}$/.test(sub))
30720
+ byteCount = Number(sub);
30721
+ }
28232
30722
  else if (opArgs.length === 1 && /^-[0-9]{1,5}$/.test(opArgs[0]))
28233
- count = Number(opArgs[0].slice(1));
28234
- else if (opArgs.length === 2 && opArgs[0] === "-n" && /^[0-9]{1,5}$/.test(opArgs[1]))
28235
- count = Number(opArgs[1]);
30723
+ countRaw = opArgs[0].slice(1);
30724
+ else if (opArgs.length === 2 && opArgs[0] === "-n" && (/^[0-9]{1,5}$/.test(opArgs[1]) || (w0Plain === "tail" && /^\+[0-9]{1,5}$/.test(opArgs[1])) || (w0Plain === "head" && /^-[0-9]{1,5}$/.test(opArgs[1]))))
30725
+ countRaw = opArgs[1];
28236
30726
  else if (opArgs.length === 2 && opArgs[0] === "-c" && /^[0-9]{1,5}$/.test(opArgs[1]))
28237
30727
  byteCount = Number(opArgs[1]);
28238
- if (count !== undefined) {
28239
- const sliced = w0Plain === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
28240
- fileRes = sliced.join("\n");
30728
+ if (countRaw !== undefined) {
30729
+ count = Number(countRaw);
30730
+ let sliced;
30731
+ if (w0Plain === "tail" && countRaw.startsWith("+"))
30732
+ sliced = rawLines.slice(Math.max(0, count - 1));
30733
+ else if (w0Plain === "head" && countRaw.startsWith("-")) {
30734
+ const drop = Math.abs(count);
30735
+ sliced = drop === 0 ? [...rawLines] : rawLines.slice(0, Math.max(0, rawLines.length - drop));
30736
+ }
30737
+ else {
30738
+ sliced = w0Plain === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
30739
+ }
30740
+ fileRes = renderLines(sliced);
28241
30741
  }
28242
30742
  else if (byteCount !== undefined) {
28243
30743
  const slicedBytes = w0Plain === "head"
@@ -28249,91 +30749,109 @@ export class Runtime {
28249
30749
  else if (w0Plain === "jq") {
28250
30750
  const jqRes = this.evalSyncJq(fileStr.trim(), opArgs);
28251
30751
  if (jqRes !== undefined)
28252
- fileRes = jqRes.join("\n");
30752
+ fileRes = renderLines(jqRes);
28253
30753
  }
28254
30754
  else if (w0Plain === "awk") {
28255
30755
  const awkRes = this.evalSyncAwk(rawLines, opArgs);
28256
30756
  if (awkRes !== undefined)
28257
- fileRes = awkRes.join("\n");
30757
+ fileRes = renderLines(awkRes);
28258
30758
  }
28259
30759
  else if (w0Plain === "grep") {
28260
30760
  const grepRes = this.evalSyncGrep(rawLines, opArgs, Boolean(rawState.errexit));
28261
30761
  if (grepRes !== undefined) {
28262
30762
  exitStatus = grepRes.status;
28263
- fileRes = grepRes.lines.join("\n");
30763
+ fileRes = renderLines(grepRes.lines);
28264
30764
  }
28265
30765
  }
28266
30766
  else if (w0Plain === "sed") {
28267
30767
  const sedRes = this.evalSyncSed(rawLines, opArgs);
28268
30768
  if (sedRes !== undefined)
28269
- fileRes = sedRes.join("\n");
30769
+ fileRes = renderLines(sedRes.lines);
28270
30770
  }
28271
30771
  else if (w0Plain === "cut") {
28272
30772
  const cutRes = this.evalSyncCut(rawLines, opArgs, byteLocale(rawState.variables));
28273
30773
  if (cutRes !== undefined)
28274
- fileRes = cutRes.join("\n");
30774
+ fileRes = renderLines(cutRes);
28275
30775
  }
28276
- else if (w0Plain === "wc" && opArgs.length === 1 && (opArgs[0] === "-l" || opArgs[0] === "-c" || opArgs[0] === "-w")) {
30776
+ else if (w0Plain === "wc" && opArgs.length === 1 && this.normalizeSyncWcFlag(opArgs[0]) !== undefined) {
30777
+ const wcMode = this.normalizeSyncWcFlag(opArgs[0]);
28277
30778
  let count = 0;
28278
- if (opArgs[0] === "-l") {
30779
+ if (wcMode === "-l") {
28279
30780
  for (let k = 0; k < view.byteLength; k++)
28280
30781
  if (view[k] === 10)
28281
30782
  count++;
28282
30783
  }
28283
- else if (opArgs[0] === "-c") {
30784
+ else if (wcMode === "-c") {
28284
30785
  count = view.byteLength;
28285
30786
  }
30787
+ else if (wcMode === "-m") {
30788
+ count = byteLocale(rawState.variables) ? view.byteLength : Array.from(fileStr).length;
30789
+ }
30790
+ else if (wcMode === "-L") {
30791
+ count = this.wcMaxLineWidth(fileStr, byteLocale(rawState.variables));
30792
+ }
28286
30793
  else {
28287
- const trimmed = fileStr.trim();
28288
- count = trimmed.length === 0 ? 0 : trimmed.split(/\s+/).length;
28289
- }
28290
- fileRes = (hasSingleStdinRedir || hasSingleHereStringRedir) ? String(count) : `${count} ${fileArg}`;
28291
- }
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]);
28306
- }
28307
- uIdx = uEnd;
30794
+ // Unicode word separators depend on locale and POSIXLY_CORRECT.
30795
+ // Use the maintained command for those inputs rather than JS whitespace.
30796
+ if (!byteLocale(rawState.variables) && view.some(byte => byte > 127))
30797
+ return undefined;
30798
+ count = fileStr.split(/[ \t\n\r\f\v]+/).filter(Boolean).length;
28308
30799
  }
28309
- fileRes = outLines.join("\n");
30800
+ fileRes = ((hasSingleStdinRedir || hasSingleHereStringRedir) ? String(count) : `${count} ${fileArg}`) + "\n";
30801
+ }
30802
+ else if (w0Plain === "sort" && !byteLocale(rawState.variables)) {
30803
+ const sortRes = this.evalSyncSort(rawLines, opArgs, false);
30804
+ if (sortRes !== undefined)
30805
+ fileRes = renderLines(sortRes);
30806
+ }
30807
+ else if (w0Plain === "uniq") {
30808
+ const uniqRes = this.evalSyncUniq(rawLines, opArgs);
30809
+ if (uniqRes !== undefined)
30810
+ fileRes = renderLines(uniqRes);
28310
30811
  }
28311
30812
  else if (w0Plain === "tr" && (hasSingleStdinRedir || hasSingleHereStringRedir)) {
28312
30813
  fileRes = this.evalSyncTr(fileStr, opArgs);
28313
30814
  }
28314
30815
  else if (hasSingleHereStringRedir && w0Plain === "base64" && (opArgs.length === 0 || (opArgs.length === 1 && (opArgs[0] === "-d" || opArgs[0] === "--decode")))) {
28315
30816
  if (opArgs.length === 0)
28316
- fileRes = this.syncBase64Encode(view);
28317
- else
28318
- fileRes = sharedSyncPipeDecoder.decode(this.syncBase64DecodeBytes(fileStr));
30817
+ fileRes = this.syncBase64Encode(view) + "\n";
30818
+ else {
30819
+ const decoded = this.syncBase64DecodeBytes(fileStr);
30820
+ // Text-only substitutions cannot retain binary byte provenance.
30821
+ if (decoded.some(byte => byte === 0 || byte >= 128))
30822
+ return undefined;
30823
+ fileRes = sharedSyncPipeDecoder.decode(decoded);
30824
+ }
28319
30825
  }
28320
30826
  else if (hasSingleHereStringRedir && w0Plain === "rev" && opArgs.length === 0) {
28321
- fileRes = rawLines.map(l => Array.from(l).reverse().join("")).join("\n");
30827
+ fileRes = renderLines(rawLines.map(l => Array.from(l).reverse().join("")));
28322
30828
  }
28323
30829
  else if (hasSingleHereStringRedir && w0Plain === "tac" && opArgs.length === 0) {
28324
- fileRes = [...rawLines].reverse().join("\n");
30830
+ fileRes = renderLines([...rawLines].reverse());
28325
30831
  }
28326
- else if (hasSingleHereStringRedir && w0Plain === "nl" && opArgs.length === 0) {
28327
- let lineNo = 0;
28328
- fileRes = rawLines.map(l => l.length === 0 ? " " : `${String(++lineNo).padStart(6, " ")}\t${l}`).join("\n");
30832
+ else if (hasSingleHereStringRedir && w0Plain === "nl") {
30833
+ const nlRes = this.evalSyncNl(rawLines, opArgs);
30834
+ if (nlRes !== undefined)
30835
+ fileRes = renderLines(nlRes);
30836
+ }
30837
+ else if (hasSingleHereStringRedir && w0Plain === "paste") {
30838
+ const pasteRes = this.evalSyncPaste(rawLines, opArgs);
30839
+ if (pasteRes !== undefined)
30840
+ fileRes = renderLines(pasteRes);
30841
+ }
30842
+ else if (hasSingleHereStringRedir && w0Plain === "numfmt") {
30843
+ const nmRes = this.evalSyncNumfmt(rawLines, opArgs);
30844
+ if (nmRes !== undefined)
30845
+ fileRes = renderLines(nmRes);
28329
30846
  }
28330
30847
  if (fileRes !== undefined) {
30848
+ // Charge emitted bytes before substitution removes trailing newlines.
30849
+ const outBytes = shellValueByteLength(fileRes);
28331
30850
  let end = fileRes.length;
28332
30851
  while (end > 0 && fileRes.charCodeAt(end - 1) === 10)
28333
30852
  end--;
28334
30853
  if (end < fileRes.length)
28335
30854
  fileRes = fileRes.slice(0, end);
28336
- const outBytes = (fileRes.length * 3 > 127 ? shellValueByteLength(fileRes) : fileRes.length) + 1;
28337
30855
  const nextTotalBytes = this.budget.bytes + outBytes;
28338
30856
  if (nextTotalBytes > this.budget.maxOutputBytesSmi && outBytes > this.budget.limits.maxOutputBytes - this.budget.bytes)
28339
30857
  this.budget.fail("maxOutputBytes");
@@ -28435,46 +30953,17 @@ export class Runtime {
28435
30953
  if (!this.isPureArgWord(cmd.words[i], rawState))
28436
30954
  return undefined;
28437
30955
  }
28438
- if (w0Plain === "dirname" && cmd.words.length === 2) {
28439
- const wVal = this.fastValueWord(cmd.words[1], state, io, true, false, false, true, undefined, part.line);
28440
- if (typeof wVal !== "string" || wVal.startsWith("-") || wVal.includes("\0"))
28441
- return undefined;
28442
- const parent = dirname(wVal);
28443
- let pEnd = parent.length;
28444
- while (pEnd > 1 && parent[pEnd - 1] === "/")
28445
- pEnd--;
28446
- let res = parent.slice(0, pEnd);
28447
- const byteLength = shellValueByteLength(res) + 1;
28448
- while (res.endsWith("\n"))
28449
- res = res.slice(0, -1);
28450
- const nextBytes = this.budget.bytes + byteLength;
28451
- if (nextBytes > this.budget.maxOutputBytesSmi && byteLength > this.budget.limits.maxOutputBytes - this.budget.bytes)
28452
- this.budget.fail("maxOutputBytes");
28453
- this.budget.bytes = nextBytes;
28454
- this.budget.tick();
28455
- if (fnPositional !== undefined)
28456
- this.budget.tick();
28457
- rawState.substitutionStatus = 0;
28458
- rawState.status = 0;
28459
- return res;
28460
- }
28461
- if (w0Plain === "basename" && (cmd.words.length === 2 || cmd.words.length === 3 || (cmd.words.length === 4 && cmd.words[1]?.plain === "-s"))) {
28462
- const isFlagS = cmd.words.length === 4 && cmd.words[1]?.plain === "-s";
28463
- const pathWord = isFlagS ? cmd.words[3] : cmd.words[1];
28464
- const suffixWord = isFlagS ? cmd.words[2] : (cmd.words.length === 3 ? cmd.words[2] : undefined);
28465
- const wVal1 = this.fastValueWord(pathWord, state, io, true, false, false, true, undefined, part.line);
28466
- if (typeof wVal1 !== "string" || wVal1.startsWith("-") || wVal1.includes("\0"))
28467
- return undefined;
28468
- let wVal2;
28469
- if (suffixWord !== undefined) {
28470
- const v2 = this.fastValueWord(suffixWord, state, io, true, false, false, true, undefined, part.line);
28471
- if (typeof v2 !== "string" || v2.includes("\0"))
30956
+ if (w0Plain === "dirname" || w0Plain === "basename") {
30957
+ const dbArgs = [];
30958
+ for (let i = 1; i < cmd.words.length; i++) {
30959
+ const v = this.fastValueWord(cmd.words[i], state, io, true, false, false, true, undefined, part.line);
30960
+ if (typeof v !== "string")
28472
30961
  return undefined;
28473
- wVal2 = v2;
30962
+ dbArgs.push(v);
28474
30963
  }
28475
- let res = /^\/+$/u.test(wVal1) ? "/" : basename(wVal1);
28476
- if (wVal2 && res !== wVal2 && res.endsWith(wVal2))
28477
- res = res.slice(0, -wVal2.length);
30964
+ let res = w0Plain === "dirname" ? this.evalSyncDirname(dbArgs) : this.evalSyncBasename(dbArgs);
30965
+ if (res === undefined)
30966
+ return undefined;
28478
30967
  const byteLength = shellValueByteLength(res) + 1;
28479
30968
  while (res.endsWith("\n"))
28480
30969
  res = res.slice(0, -1);