@poe-platform/safe-bash 0.1.745 → 0.1.746

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-EAUCLWEZ.js → chunk-3ZNXN3XO.js} +423 -245
  6. package/dist/safe-bash/chunks/{chunk-2F7Q532M.js → chunk-4GK4FAT6.js} +3 -3
  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-T2WAWJXF.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 +2697 -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 +3014 -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 +410 -237
  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);
27107
28006
  }
28007
+ void innerArgs;
28008
+ return outArrays.map(val => JSON.stringify(val ?? null, null, compactOut ? undefined : 2));
27108
28009
  }
27109
- return current.map(val => (rawOut && typeof val === "string" ? val : JSON.stringify(val ?? null)));
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;
28089
+ }
28090
+ }
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)
27179
28197
  return undefined;
27180
- const picker = this.parseSyncCutSpec(norm[0].slice(2));
27181
- if (!picker)
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; }))
27182
28201
  return undefined;
28202
+ const basePicker = this.parseSyncCutSpec(norm[0].slice(2));
28203
+ if (!basePicker)
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,512 @@ 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)
27281
- return undefined;
27282
- const delim = expr[1];
27283
- if (!"/#|:@,;%!".includes(delim))
27284
- return undefined;
27285
- const parts = expr.slice(2).split(delim);
27286
- if (parts.length !== 3)
28276
+ if (rawExprs.length === 0)
27287
28277
  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
+ if (isCount && (isLineNumber || isOnlyMatching || beforeCtx > 0 || afterCtx > 0))
28603
+ return undefined;
28604
+ if (isOnlyMatching && (beforeCtx > 0 || afterCtx > 0))
28605
+ return undefined;
28606
+ if (isOnlyMatching) {
28607
+ if (rawPatterns.length !== 1 || isInvert || isLineRegexp || isWordRegexp || isFixed)
28608
+ return undefined;
28609
+ const pat = rawPatterns[0];
28610
+ const reFlags = isCaseInsensitive ? "gi" : "g";
28611
+ let re;
28612
+ if (isExtended && /^[a-zA-Z0-9_ :;,=.+*?|()^\-[\]]+$/.test(pat) && !/\([^)]*[+*][^)]*\)[+*?]/.test(pat)) {
28613
+ try {
28614
+ re = new RegExp(pat, reFlags);
28615
+ }
28616
+ catch {
28617
+ re = undefined;
28618
+ }
28619
+ }
28620
+ else {
28621
+ const branches = isExtended ? pat.split("|") : [pat];
28622
+ if (branches.every(b => b.length > 0 && /^[a-zA-Z0-9_ :;,/-]+$/.test(b))) {
28623
+ branches.sort((a, b) => b.length - a.length);
28624
+ re = new RegExp(branches.join("|"), reFlags);
28625
+ }
28626
+ }
28627
+ if (!re)
28628
+ return undefined;
28629
+ const out = [];
28630
+ for (let li = 0; li < rawLines.length; li++) {
28631
+ const matches = rawLines[li].match(re);
28632
+ if (matches) {
28633
+ for (let mi = 0; mi < matches.length; mi++) {
28634
+ out.push(isLineNumber ? `${li + 1}:${matches[mi]}` : matches[mi]);
28635
+ }
28636
+ }
28637
+ }
28638
+ return { lines: out, status: out.length > 0 ? 0 : 1 };
28639
+ }
28640
+ if (isExtended && isFixed)
28641
+ return undefined;
28642
+ const branches = [];
28643
+ for (const rp of rawPatterns) {
28644
+ for (const b of (isFixed ? rp.split("\n") : (isExtended ? rp.split("|") : [rp]))) {
28645
+ branches.push(b);
28646
+ }
28647
+ }
27324
28648
  const compiledBranches = [];
27325
28649
  for (const br of branches) {
27326
28650
  if (br.length === 0)
27327
28651
  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 });
28652
+ if (isFixed) {
28653
+ if (br.includes("\0"))
28654
+ return undefined;
28655
+ compiledBranches.push({ anchorStart: isLineRegexp, anchorEnd: isLineRegexp, needle: isCaseInsensitive ? foldCase(br) : br, re: undefined });
28656
+ }
28657
+ else {
28658
+ const anchorStart = isLineRegexp || br.startsWith("^");
28659
+ const c1 = br.startsWith("^") ? br.slice(1) : br;
28660
+ const anchorEnd = isLineRegexp || c1.endsWith("$");
28661
+ const core = c1.endsWith("$") ? c1.slice(0, -1) : c1;
28662
+ if (core.length > 0 && /^[a-zA-Z0-9_ :;,/-]+$/.test(core)) {
28663
+ compiledBranches.push({ anchorStart, anchorEnd, needle: isCaseInsensitive ? foldCase(core) : core, re: undefined });
28664
+ }
28665
+ else if (isExtended && !isWordRegexp && core.length > 0 && /^[a-zA-Z0-9_ :;,=.+*?^\-[\]]+$/.test(core) && !/\([^)]*[+*][^)]*\)[+*?]/.test(core)) {
28666
+ try {
28667
+ const fullSrc = (anchorStart ? "^" : "") + core + (anchorEnd ? "$" : "");
28668
+ compiledBranches.push({ anchorStart, anchorEnd, needle: "", re: new RegExp(fullSrc, isCaseInsensitive ? "i" : "") });
28669
+ }
28670
+ catch {
28671
+ return undefined;
28672
+ }
28673
+ }
28674
+ else {
28675
+ return undefined;
28676
+ }
28677
+ }
27335
28678
  }
28679
+ const isWordChar = (ch) => ch !== undefined && /^[a-zA-Z0-9_]$/.test(ch);
28680
+ const hasWordMatch = (hay, needle, aStart, aEnd) => {
28681
+ if (aStart && aEnd)
28682
+ return hay === needle;
28683
+ if (aStart)
28684
+ return hay.startsWith(needle) && !isWordChar(hay[needle.length]);
28685
+ if (aEnd)
28686
+ return hay.endsWith(needle) && !isWordChar(hay[hay.length - needle.length - 1]);
28687
+ let from = 0;
28688
+ while (from <= hay.length - needle.length) {
28689
+ const idx = hay.indexOf(needle, from);
28690
+ if (idx === -1)
28691
+ return false;
28692
+ if (!isWordChar(hay[idx - 1]) && !isWordChar(hay[idx + needle.length]))
28693
+ return true;
28694
+ from = idx + 1;
28695
+ }
28696
+ return false;
28697
+ };
27336
28698
  const matched = [];
28699
+ const matchedIndices = [];
27337
28700
  for (let li = 0; li < rawLines.length; li++) {
27338
28701
  const l = rawLines[li];
27339
- const hay = isCaseInsensitive ? l.toLowerCase() : l;
28702
+ const hay = isCaseInsensitive ? foldCase(l) : l;
27340
28703
  let hit = false;
27341
28704
  for (let bi = 0; bi < compiledBranches.length; bi++) {
27342
28705
  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);
28706
+ const ok = b.re !== undefined
28707
+ ? b.re.test(l)
28708
+ : isWordRegexp
28709
+ ? hasWordMatch(hay, b.needle, b.anchorStart, b.anchorEnd)
28710
+ : b.anchorStart && b.anchorEnd
28711
+ ? hay === b.needle
28712
+ : b.anchorStart
28713
+ ? hay.startsWith(b.needle)
28714
+ : b.anchorEnd
28715
+ ? hay.endsWith(b.needle)
28716
+ : hay.includes(b.needle);
27350
28717
  if (ok) {
27351
28718
  hit = true;
27352
28719
  break;
27353
28720
  }
27354
28721
  }
27355
- if (isInvert ? !hit : hit)
27356
- matched.push(l);
28722
+ if (isInvert ? !hit : hit) {
28723
+ matchedIndices.push(li);
28724
+ matched.push(isLineNumber ? `${li + 1}:${l}` : l);
28725
+ if (matched.length >= maxCount)
28726
+ break;
28727
+ }
28728
+ }
28729
+ if ((beforeCtx > 0 || afterCtx > 0) && matchedIndices.length > 0) {
28730
+ const matchedSet = new Set(matchedIndices);
28731
+ const outCtx = [];
28732
+ let prevEnd = -1;
28733
+ for (const mIdx of matchedIndices) {
28734
+ const start = Math.max(0, mIdx - beforeCtx);
28735
+ const end = Math.min(rawLines.length - 1, mIdx + afterCtx);
28736
+ if (prevEnd !== -1 && start > prevEnd + 1) {
28737
+ outCtx.push("--");
28738
+ }
28739
+ const from = prevEnd !== -1 ? Math.max(start, prevEnd + 1) : start;
28740
+ for (let k = from; k <= end; k++) {
28741
+ const lineText = rawLines[k];
28742
+ if (isLineNumber) {
28743
+ outCtx.push(`${k + 1}${matchedSet.has(k) ? ":" : "-"}${lineText}`);
28744
+ }
28745
+ else {
28746
+ outCtx.push(lineText);
28747
+ }
28748
+ }
28749
+ prevEnd = Math.max(prevEnd, end);
28750
+ }
28751
+ return { lines: outCtx, status: 0 };
27357
28752
  }
27358
28753
  return {
27359
28754
  lines: isCount ? [String(matched.length)] : matched,
@@ -27361,50 +28756,413 @@ export class Runtime {
27361
28756
  };
27362
28757
  }
27363
28758
  evalSyncAwk(rawLines, opArgs) {
27364
- if (opArgs.length < 1 || opArgs.length > 3)
28759
+ if (opArgs.length < 1 || opArgs.length > 8)
27365
28760
  return undefined;
27366
- const norm = opArgs.length === 3 && opArgs[0] === "-F" ? ["-F" + opArgs[1], opArgs[2]] : opArgs;
27367
28761
  let awkSep;
27368
28762
  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];
28763
+ let ofs = " ";
28764
+ const userVars = new Map();
28765
+ for (let i = 0; i < opArgs.length; i++) {
28766
+ const a = opArgs[i];
28767
+ if (a === "-F") {
28768
+ if (i + 1 >= opArgs.length || opArgs[i + 1].length !== 1 || opArgs[i + 1] === "\\")
28769
+ return undefined;
28770
+ awkSep = opArgs[++i];
28771
+ }
28772
+ else if (a.startsWith("-F") && a.length === 3 && a[2] !== "\\") {
28773
+ awkSep = a.slice(2);
28774
+ }
28775
+ else if (a === "-v") {
28776
+ if (i + 1 >= opArgs.length)
28777
+ return undefined;
28778
+ const kv = opArgs[++i];
28779
+ const eq = kv.indexOf("=");
28780
+ if (eq <= 0 || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(kv.slice(0, eq)))
28781
+ return undefined;
28782
+ const k = kv.slice(0, eq);
28783
+ const v = kv.slice(eq + 1);
28784
+ if (v.includes("\\"))
28785
+ return undefined;
28786
+ if (k === "OFS")
28787
+ ofs = v;
28788
+ else if (k === "FS") {
28789
+ if (v.length !== 1)
28790
+ return undefined;
28791
+ awkSep = v;
28792
+ }
28793
+ else
28794
+ userVars.set(k, v);
28795
+ }
28796
+ else if (a.startsWith("-v") && a.length > 2) {
28797
+ const kv = a.slice(2);
28798
+ const eq = kv.indexOf("=");
28799
+ if (eq <= 0 || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(kv.slice(0, eq)))
28800
+ return undefined;
28801
+ const k = kv.slice(0, eq);
28802
+ const v = kv.slice(eq + 1);
28803
+ if (v.includes("\\"))
28804
+ return undefined;
28805
+ if (k === "OFS")
28806
+ ofs = v;
28807
+ else if (k === "FS") {
28808
+ if (v.length !== 1)
28809
+ return undefined;
28810
+ awkSep = v;
28811
+ }
28812
+ else
28813
+ userVars.set(k, v);
28814
+ }
28815
+ else if (!a.startsWith("-") && i === opArgs.length - 1) {
28816
+ awkProg = a;
28817
+ }
28818
+ else {
28819
+ return undefined;
28820
+ }
27374
28821
  }
27375
28822
  if (!awkProg)
27376
28823
  return undefined;
27377
- const awkM = /^\s*\{\s*print(?:\s+((?:\$(?:[0-9]+|NF)|"[^"$\\]*")(?:\s*,?\s*(?:\$(?:[0-9]+|NF)|"[^"$\\]*"))*))?\s*;?\s*\}\s*$/.exec(awkProg);
28824
+ let progRest = awkProg.trim();
28825
+ const beginFsM = /^BEGIN\s*\{([^}]*)\}\s*/.exec(progRest);
28826
+ let initVarName;
28827
+ let initVarVal = 0;
28828
+ if (beginFsM) {
28829
+ const bStmts = beginFsM[1].split(";").map(s => s.trim()).filter(Boolean);
28830
+ for (const bs of bStmts) {
28831
+ const ofsM = /^OFS\s*=\s*"([^"\\]*)"$/.exec(bs);
28832
+ if (ofsM) {
28833
+ ofs = ofsM[1];
28834
+ continue;
28835
+ }
28836
+ const fsM = /^FS\s*=\s*"([^"\\])"$/.exec(bs);
28837
+ if (fsM) {
28838
+ awkSep = fsM[1];
28839
+ continue;
28840
+ }
28841
+ const numInitM = /^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(-?[0-9]+(?:\.[0-9]+)?)$/.exec(bs);
28842
+ if (numInitM) {
28843
+ initVarName = numInitM[1];
28844
+ initVarVal = Number(numInitM[2]);
28845
+ continue;
28846
+ }
28847
+ return undefined;
28848
+ }
28849
+ progRest = progRest.slice(beginFsM[0].length).trim();
28850
+ }
28851
+ 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);
28852
+ if (sumEndM) {
28853
+ const varName = sumEndM[1];
28854
+ if (["NR", "NF", "FNR", "OFS", "FS", "ORS", "RS", "OFMT", "CONVFMT", "ARGC", "ARGIND", "FILENAME", "RSTART", "RLENGTH", "SUBSEP", "IGNORECASE", "FIELDWIDTHS", "FPAT", "BINMODE", "TEXTDOMAIN"].includes(varName))
28855
+ return undefined;
28856
+ if (initVarName !== undefined && initVarName !== varName)
28857
+ return undefined;
28858
+ const op = sumEndM[2];
28859
+ const fTok = sumEndM[3];
28860
+ const constVal = sumEndM[4] !== undefined ? Number(sumEndM[4]) : undefined;
28861
+ const initial = userVars.get(varName);
28862
+ if (initial !== undefined && !Number.isFinite(Number(initial)))
28863
+ return undefined;
28864
+ let acc = initVarName === varName ? initVarVal : Number(initial ?? 0);
28865
+ let touched = initVarName === varName || initial !== undefined;
28866
+ for (let li = 0; li < rawLines.length; li++) {
28867
+ const l = rawLines[li];
28868
+ const fields = awkSep === undefined || awkSep === " "
28869
+ ? l.split(/[ \t]+/).filter(Boolean)
28870
+ : (l.length === 0 ? [] : l.split(awkSep));
28871
+ let delta = 0;
28872
+ if (constVal !== undefined) {
28873
+ delta = constVal;
28874
+ }
28875
+ else {
28876
+ const idx = fTok === "NF" ? fields.length : Number(fTok);
28877
+ const rawField = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
28878
+ const n = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(rawField);
28879
+ delta = n ? Number(n[1]) : 0;
28880
+ }
28881
+ acc = op === "+=" ? acc + delta : acc - delta;
28882
+ touched = true;
28883
+ }
28884
+ return [touched ? awkValueText({ kind: "number", number: acc }) : ""];
28885
+ }
28886
+ if (initVarName !== undefined)
28887
+ return undefined;
28888
+ let rowPred;
28889
+ const patCondM = /^\/(\^?[a-zA-Z0-9_ :;,=-]+\$?)\/\s*/.exec(progRest);
28890
+ if (patCondM) {
28891
+ const pSpec = patCondM[1];
28892
+ const aS = pSpec.startsWith("^");
28893
+ const c1 = aS ? pSpec.slice(1) : pSpec;
28894
+ const aE = c1.endsWith("$");
28895
+ const core = aE ? c1.slice(0, -1) : c1;
28896
+ rowPred = (l) => aS && aE ? l === core : aS ? l.startsWith(core) : aE ? l.endsWith(core) : l.includes(core);
28897
+ progRest = progRest.slice(patCondM[0].length).trim();
28898
+ }
28899
+ else {
28900
+ const cmpCondM = /^(?:(NR|NF)|\$([0-9]+|NF))\s*(==|!=|>=|<=|>|<)\s*(?:"([^"\\]*)"|(-?[0-9]+(?:\.[0-9]+)?)|([a-zA-Z_][a-zA-Z0-9_]*))\s*/.exec(progRest);
28901
+ if (cmpCondM) {
28902
+ const nrNf = cmpCondM[1];
28903
+ const fTok = cmpCondM[2];
28904
+ const op = cmpCondM[3];
28905
+ const varRhs = cmpCondM[6] !== undefined ? userVars.get(cmpCondM[6]) : undefined;
28906
+ if (cmpCondM[6] !== undefined && varRhs === undefined)
28907
+ return undefined;
28908
+ const varAsNum = varRhs !== undefined && /^-?(?:\d+(?:\.\d*)?|\.\d+)$/.test(varRhs) ? Number(varRhs) : undefined;
28909
+ const strRhs = cmpCondM[4] ?? (varAsNum === undefined ? varRhs : undefined);
28910
+ const numRhs = cmpCondM[5] !== undefined ? Number(cmpCondM[5]) : varAsNum;
28911
+ if (strRhs !== undefined && op !== "==" && op !== "!=")
28912
+ return undefined;
28913
+ rowPred = (l, fields, nr) => {
28914
+ let rawVal;
28915
+ if (nrNf !== undefined)
28916
+ rawVal = nrNf === "NR" ? nr : fields.length;
28917
+ else {
28918
+ const idx = fTok === "NF" ? fields.length : Number(fTok);
28919
+ rawVal = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
28920
+ }
28921
+ const left = typeof rawVal === "number" ? awkNumeric(rawVal) : awkInputValue(rawVal);
28922
+ const right = strRhs !== undefined ? awkString(strRhs) : awkNumeric(numRhs);
28923
+ const comparison = awkCompare(left, right, "%.6g");
28924
+ return op === "==" ? comparison === 0 : op === "!=" ? comparison !== 0 : op === ">=" ? comparison >= 0 : op === "<=" ? comparison <= 0 : op === ">" ? comparison > 0 : comparison < 0;
28925
+ };
28926
+ progRest = progRest.slice(cmpCondM[0].length).trim();
28927
+ }
28928
+ }
28929
+ const awkM = syncAwkPrintRe.exec(progRest);
27378
28930
  if (!awkM)
27379
28931
  return undefined;
27380
- const exprBody = awkM[1]?.trim();
28932
+ const subFn = awkM[1];
28933
+ const subPat = awkM[2];
28934
+ const subRep = awkM[3] ?? "";
28935
+ const subTarget = awkM[4] ?? "0";
28936
+ // printf must consume typed values before OFMT string conversion and use
28937
+ // the complete formatter, including integer precision and exponent parsing.
28938
+ if (awkM[6] !== undefined)
28939
+ return undefined;
28940
+ const exprBody = awkM[5]?.trim();
28941
+ let subRe;
28942
+ if (subFn && subPat !== undefined) {
28943
+ const aS = subPat.startsWith("^");
28944
+ const c1 = aS ? subPat.slice(1) : subPat;
28945
+ const aE = c1.endsWith("$");
28946
+ const core = aE ? c1.slice(0, -1) : c1;
28947
+ const esc = core.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28948
+ subRe = new RegExp((aS ? "^" : "") + esc + (aE ? "$" : ""), subFn === "gsub" ? "g" : "");
28949
+ }
27381
28950
  const parts = [];
27382
28951
  if (!exprBody) {
27383
28952
  parts.push({ kind: "field", token: "0" });
27384
28953
  }
27385
28954
  else {
27386
- const tokenRe = /\$([0-9]+|NF)|"([^"$\\]*)"|(,)/g;
28955
+ syncAwkTokenRe.lastIndex = 0;
28956
+ const tokenRe = syncAwkTokenRe;
27387
28957
  let m;
27388
28958
  while ((m = tokenRe.exec(exprBody)) !== null) {
27389
- if (m[1] !== undefined)
27390
- parts.push({ kind: "field", token: m[1] });
28959
+ if (m[1] !== undefined) {
28960
+ 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());
28961
+ if (!tm)
28962
+ return undefined;
28963
+ parts.push({ kind: "ternary", lhs: tm[1], op: tm[2], rhs: tm[3], yes: tm[4], no: tm[5] });
28964
+ }
27391
28965
  else if (m[2] !== undefined)
27392
- parts.push({ kind: "lit", text: m[2] });
28966
+ parts.push({ kind: "int", token: m[2] });
27393
28967
  else if (m[3] !== undefined)
27394
- parts.push({ kind: "lit", text: " " });
28968
+ parts.push({ kind: "case", fn: m[3] === "toupper" ? "upper" : "lower", token: m[4] });
28969
+ else if (m[5] !== undefined)
28970
+ parts.push({ kind: "substr", token: m[5], start: Number(m[6]), len: m[7] !== undefined ? Number(m[7]) : undefined });
28971
+ else if (m[8] !== undefined)
28972
+ parts.push({ kind: "index", token: m[8], sub: m[9] });
28973
+ else if (m[10] !== undefined) {
28974
+ const aToks = m[10].trim().split(/\s*([+*\/%-])\s*/);
28975
+ for (let ti = 0; ti < aToks.length; ti += 2) {
28976
+ const at = aToks[ti];
28977
+ if (!at.startsWith("$") && at !== "NR" && at !== "NF" && !/^-?[0-9]+(?:\.[0-9]+)?$/.test(at) && !userVars.has(at))
28978
+ return undefined;
28979
+ }
28980
+ for (let ti = 1; ti < aToks.length; ti += 2) {
28981
+ if (aToks[ti] === "/" || aToks[ti] === "%") {
28982
+ const divisor = Number(aToks[ti + 1]);
28983
+ if (!Number.isFinite(divisor) || divisor === 0)
28984
+ return undefined;
28985
+ }
28986
+ }
28987
+ parts.push({ kind: "arith", tokens: aToks });
28988
+ }
28989
+ else if (m[11] !== undefined) {
28990
+ const offset = Number(m[11]);
28991
+ // Refuse potentially fatal fields before loop iterations mutate state.
28992
+ if (offset > 0)
28993
+ return undefined;
28994
+ parts.push({ kind: "nf_minus", offset });
28995
+ }
28996
+ else if (m[12] !== undefined)
28997
+ parts.push({ kind: "field", token: m[12] });
28998
+ else if (m[0] === "length" || m[0].startsWith("length("))
28999
+ parts.push({ kind: "length", token: m[13] ?? "0" });
29000
+ else if (m[14] !== undefined)
29001
+ parts.push({ kind: "var", name: m[14] });
29002
+ else if (m[15] !== undefined)
29003
+ parts.push({ kind: "lit", text: m[15] });
29004
+ else if (m[16] !== undefined) {
29005
+ const uv = userVars.get(m[16]);
29006
+ if (uv === undefined)
29007
+ return undefined;
29008
+ parts.push({ kind: "lit", text: uv });
29009
+ }
29010
+ else if (m[17] !== undefined)
29011
+ parts.push({ kind: "lit", text: ofs });
27395
29012
  }
27396
29013
  }
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);
29014
+ const outLines = [];
29015
+ for (let li = 0; li < rawLines.length; li++) {
29016
+ let l = rawLines[li];
29017
+ let fields = awkSep === undefined || awkSep === " "
29018
+ ? l.split(/[ \t]+/).filter(Boolean)
29019
+ : (l.length === 0 ? [] : l.split(awkSep));
29020
+ if (rowPred && !rowPred(l, fields, li + 1))
29021
+ continue;
29022
+ if (subRe) {
29023
+ if (subTarget === "0") {
29024
+ l = l.replace(subRe, matched => subRep.split("&").join(matched));
29025
+ fields = awkSep === undefined || awkSep === " "
29026
+ ? l.split(/[ \t]+/).filter(Boolean)
29027
+ : (l.length === 0 ? [] : l.split(awkSep));
29028
+ }
29029
+ else {
29030
+ const tIdx = subTarget === "NF" ? fields.length : Number(subTarget);
29031
+ if (tIdx >= 1 && tIdx <= fields.length) {
29032
+ fields = [...fields];
29033
+ fields[tIdx - 1] = fields[tIdx - 1].replace(subRe, matched => subRep.split("&").join(matched));
29034
+ l = fields.join(ofs);
29035
+ }
29036
+ }
29037
+ }
29038
+ if (parts.length === 1 && parts[0].kind === "field" && parts[0].token === "0") {
29039
+ outLines.push(l);
29040
+ continue;
29041
+ }
27403
29042
  let out = "";
27404
29043
  for (let pi = 0; pi < parts.length; pi++) {
27405
29044
  const p = parts[pi];
27406
29045
  if (p.kind === "lit")
27407
29046
  out += p.text;
29047
+ else if (p.kind === "var")
29048
+ out += String(p.name === "NR" ? li + 1 : fields.length);
29049
+ else if (p.kind === "nf_minus") {
29050
+ const idx = fields.length - p.offset;
29051
+ if (idx < 0)
29052
+ return undefined;
29053
+ out += idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29054
+ }
29055
+ else if (p.kind === "case") {
29056
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29057
+ const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29058
+ out += p.fn === "upper" ? s.toUpperCase() : s.toLowerCase();
29059
+ }
29060
+ else if (p.kind === "substr") {
29061
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29062
+ const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29063
+ const chars = Array.from(s);
29064
+ const s0 = Math.max(0, p.start - 1);
29065
+ out += (p.len !== undefined ? chars.slice(s0, s0 + Math.max(0, p.len)) : chars.slice(s0)).join("");
29066
+ }
29067
+ else if (p.kind === "index") {
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 pos = p.sub.length === 0 ? -1 : s.indexOf(p.sub);
29071
+ out += String(pos === -1 ? 0 : pos + 1);
29072
+ }
29073
+ else if (p.kind === "int") {
29074
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29075
+ const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29076
+ const n = Number(/^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(s)?.[1] ?? 0);
29077
+ out += String(Math.trunc(n));
29078
+ }
29079
+ else if (p.kind === "ternary") {
29080
+ const resolveAtom = (tok) => {
29081
+ if (tok.startsWith("\"") && tok.endsWith("\""))
29082
+ return tok.slice(1, -1);
29083
+ if (tok === "NR")
29084
+ return String(li + 1);
29085
+ if (tok === "NF")
29086
+ return String(fields.length);
29087
+ if (tok.startsWith("$")) {
29088
+ const fIdx = tok === "$NF" ? fields.length : Number(tok.slice(1));
29089
+ return fIdx === 0 ? l : (fIdx >= 1 && fIdx <= fields.length ? fields[fIdx - 1] : "");
29090
+ }
29091
+ return tok;
29092
+ };
29093
+ const lv = resolveAtom(p.lhs);
29094
+ const rv = resolveAtom(p.rhs);
29095
+ const isStrCmp = p.rhs.startsWith("\"");
29096
+ let cond = false;
29097
+ if (isStrCmp) {
29098
+ cond = p.op === "==" ? lv === rv : p.op === "!=" ? lv !== rv : p.op === ">" ? lv > rv : p.op === "<" ? lv < rv : p.op === ">=" ? lv >= rv : lv <= rv;
29099
+ }
29100
+ else {
29101
+ const ln = Number(lv) || 0;
29102
+ const rn = Number(rv) || 0;
29103
+ cond = p.op === "==" ? ln === rn : p.op === "!=" ? ln !== rn : p.op === ">" ? ln > rn : p.op === "<" ? ln < rn : p.op === ">=" ? ln >= rn : ln <= rn;
29104
+ }
29105
+ out += resolveAtom(cond ? p.yes : p.no);
29106
+ }
29107
+ else if (p.kind === "arith") {
29108
+ const vals = [];
29109
+ const ops = [];
29110
+ for (let ti = 0; ti < p.tokens.length; ti++) {
29111
+ const tk = p.tokens[ti];
29112
+ if (ti % 2 === 1) {
29113
+ ops.push(tk);
29114
+ }
29115
+ else {
29116
+ let v = 0;
29117
+ if (tk === "NR")
29118
+ v = li + 1;
29119
+ else if (tk === "NF")
29120
+ v = fields.length;
29121
+ else if (tk.startsWith("$")) {
29122
+ const fSub = tk.slice(1);
29123
+ const idx = fSub === "NF" ? fields.length : Number(fSub);
29124
+ const rawF = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29125
+ const nm = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(rawF);
29126
+ v = nm ? Number(nm[1]) : 0;
29127
+ }
29128
+ else if (/^-?[0-9]+(?:\.[0-9]+)?$/.test(tk)) {
29129
+ v = Number(tk);
29130
+ }
29131
+ else {
29132
+ const uv = userVars.get(tk) ?? "";
29133
+ const nm = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(uv);
29134
+ v = nm ? Number(nm[1]) : 0;
29135
+ }
29136
+ vals.push(v);
29137
+ }
29138
+ }
29139
+ const addVals = [vals[0]];
29140
+ const addOps = [];
29141
+ for (let oi = 0; oi < ops.length; oi++) {
29142
+ const op = ops[oi];
29143
+ const rhs = vals[oi + 1];
29144
+ if (op === "*" || op === "/" || op === "%") {
29145
+ if ((op === "/" || op === "%") && rhs === 0)
29146
+ return undefined;
29147
+ const lhs = addVals.pop();
29148
+ addVals.push(op === "*" ? lhs * rhs : op === "/" ? lhs / rhs : lhs % rhs);
29149
+ }
29150
+ else {
29151
+ addOps.push(op);
29152
+ addVals.push(rhs);
29153
+ }
29154
+ }
29155
+ let acc = addVals[0];
29156
+ for (let oi = 0; oi < addOps.length; oi++) {
29157
+ acc = addOps[oi] === "+" ? acc + addVals[oi + 1] : acc - addVals[oi + 1];
29158
+ }
29159
+ out += awkValueText(awkNumeric(acc));
29160
+ }
29161
+ else if (p.kind === "length") {
29162
+ const idx = p.token === "NF" ? fields.length : Number(p.token);
29163
+ const s = idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
29164
+ out += String(Array.from(s).length);
29165
+ }
27408
29166
  else if (p.token === "0")
27409
29167
  out += l;
27410
29168
  else {
@@ -27412,8 +29170,9 @@ export class Runtime {
27412
29170
  out += idx === 0 ? l : (idx >= 1 && idx <= fields.length ? fields[idx - 1] : "");
27413
29171
  }
27414
29172
  }
27415
- return out;
27416
- });
29173
+ outLines.push(out);
29174
+ }
29175
+ return outLines;
27417
29176
  }
27418
29177
  formatSyncTransformQ(val) {
27419
29178
  let ansi = false;
@@ -27456,41 +29215,716 @@ export class Runtime {
27456
29215
  }
27457
29216
  return out + "'";
27458
29217
  }
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");
29218
+ wcMaxLineWidth(text, singleByte) {
29219
+ let columns = 0;
29220
+ let maximum = 0;
29221
+ for (const char of text) {
29222
+ const point = char.codePointAt(0);
29223
+ if (point === 10 || point === 13 || point === 12) {
29224
+ maximum = Math.max(maximum, columns);
29225
+ columns = 0;
29226
+ }
29227
+ else if (point === 9)
29228
+ columns += 8 - columns % 8;
29229
+ else
29230
+ columns += singleByte ? Number(point >= 32 && point < 127) : wcDisplayWidth(point);
29231
+ }
29232
+ return Math.max(maximum, columns);
29233
+ }
29234
+ compareSyncVersion(a, b) {
29235
+ let ia = 0;
29236
+ let ib = 0;
29237
+ while (ia < a.length || ib < b.length) {
29238
+ while ((ia < a.length && !(a[ia] >= "0" && a[ia] <= "9")) || (ib < b.length && !(b[ib] >= "0" && b[ib] <= "9"))) {
29239
+ const ca = ia < a.length ? a[ia] : undefined;
29240
+ const cb = ib < b.length ? b[ib] : undefined;
29241
+ const da = ca !== undefined && ca >= "0" && ca <= "9";
29242
+ const db = cb !== undefined && cb >= "0" && cb <= "9";
29243
+ if (da && db)
29244
+ break;
29245
+ if (ca !== cb) {
29246
+ if (ca === undefined)
29247
+ return -1;
29248
+ if (cb === undefined)
29249
+ return 1;
29250
+ if (da)
29251
+ return 1;
29252
+ if (db)
29253
+ return -1;
29254
+ return ca < cb ? -1 : 1;
29255
+ }
29256
+ ia++;
29257
+ ib++;
29258
+ }
29259
+ const sa = ia;
29260
+ while (ia < a.length && a[ia] >= "0" && a[ia] <= "9")
29261
+ ia++;
29262
+ const sb = ib;
29263
+ while (ib < b.length && b[ib] >= "0" && b[ib] <= "9")
29264
+ ib++;
29265
+ if (sa < ia || sb < ib) {
29266
+ const na = BigInt(sa < ia ? a.slice(sa, ia) : "0");
29267
+ const nb = BigInt(sb < ib ? b.slice(sb, ib) : "0");
29268
+ if (na !== nb)
29269
+ return na < nb ? -1 : 1;
29270
+ }
29271
+ }
29272
+ return 0;
29273
+ }
29274
+ evalSyncNl(rawLines, opArgs) {
29275
+ let bodyStyle = "t";
29276
+ let format = "rn";
29277
+ let width = 6;
29278
+ let sep = "\t";
29279
+ let start = 1;
29280
+ let inc = 1;
29281
+ for (let i = 0; i < opArgs.length; i++) {
29282
+ const a = opArgs[i];
29283
+ if ((a === "-b" || a === "--body-numbering") && i + 1 < opArgs.length) {
29284
+ const v = opArgs[++i];
29285
+ if (v !== "a" && v !== "t" && v !== "n")
29286
+ return undefined;
29287
+ bodyStyle = v;
29288
+ }
29289
+ else if (a === "-ba" || a === "-bt" || a === "-bn") {
29290
+ bodyStyle = a[2];
29291
+ }
29292
+ else if ((a === "-n" || a === "--number-format") && i + 1 < opArgs.length) {
29293
+ const v = opArgs[++i];
29294
+ if (v !== "ln" && v !== "rn" && v !== "rz")
29295
+ return undefined;
29296
+ format = v;
29297
+ }
29298
+ else if (a === "-nln" || a === "-nrn" || a === "-nrz") {
29299
+ format = a.slice(2);
29300
+ }
29301
+ else if ((a === "-w" || a === "--number-width") && i + 1 < opArgs.length && /^[1-9][0-9]{0,2}$/.test(opArgs[i + 1])) {
29302
+ width = Number(opArgs[++i]);
29303
+ }
29304
+ else if (/^-w[1-9][0-9]{0,2}$/.test(a)) {
29305
+ width = Number(a.slice(2));
29306
+ }
29307
+ else if ((a === "-s" || a === "--number-separator") && i + 1 < opArgs.length) {
29308
+ sep = opArgs[++i];
29309
+ }
29310
+ else if (a.startsWith("-s") && a.length > 2) {
29311
+ sep = a.slice(2);
29312
+ }
29313
+ else if ((a === "-v" || a === "--starting-line-number") && i + 1 < opArgs.length && /^-?[0-9]{1,9}$/.test(opArgs[i + 1])) {
29314
+ start = Number(opArgs[++i]);
29315
+ }
29316
+ else if (/^-v-?[0-9]{1,9}$/.test(a)) {
29317
+ start = Number(a.slice(2));
29318
+ }
29319
+ else if ((a === "-i" || a === "--line-increment") && i + 1 < opArgs.length && /^-?[0-9]{1,9}$/.test(opArgs[i + 1])) {
29320
+ inc = Number(opArgs[++i]);
29321
+ }
29322
+ else if (/^-i-?[0-9]{1,9}$/.test(a)) {
29323
+ inc = Number(a.slice(2));
29324
+ }
29325
+ else {
29326
+ return undefined;
29327
+ }
29328
+ }
29329
+ const out = [];
29330
+ let curStyle = bodyStyle;
29331
+ let num = start;
29332
+ const unnumberedPrefix = " ".repeat(width + sep.length);
29333
+ for (let i = 0; i < rawLines.length; i++) {
29334
+ const l = rawLines[i];
29335
+ if (l === "\\:" || l === "\\:\\:" || l === "\\:\\:\\:") {
29336
+ curStyle = l === "\\:\\:" ? bodyStyle : "n";
29337
+ num = start;
29338
+ out.push("");
29339
+ continue;
29340
+ }
29341
+ const numbered = curStyle === "a" || (curStyle === "t" && l.length > 0);
29342
+ if (!numbered) {
29343
+ out.push(l.length === 0 && sep === "\t" ? " ".repeat(width + 1) : unnumberedPrefix + l);
29344
+ continue;
29345
+ }
29346
+ const label = String(num);
29347
+ const pad = Math.max(0, width - label.length);
29348
+ let prefix;
29349
+ if (format === "ln")
29350
+ prefix = label + " ".repeat(pad);
29351
+ else if (format === "rz")
29352
+ prefix = num < 0 ? "-" + "0".repeat(pad) + label.slice(1) : "0".repeat(pad) + label;
29353
+ else
29354
+ prefix = " ".repeat(pad) + label;
29355
+ out.push(prefix + sep + l);
29356
+ num += inc;
29357
+ }
29358
+ return out;
29359
+ }
29360
+ evalSyncPaste(rawLines, opArgs) {
29361
+ let serial = false;
29362
+ let delims = ["\t"];
29363
+ let literal = false;
29364
+ const files = [];
29365
+ const parseDelims = (s) => {
29366
+ if (s.length === 0)
29367
+ return [""];
29368
+ const res = [];
29369
+ const escapes = { b: "\b", f: "\f", n: "\n", r: "\r", t: "\t", v: "\v", "0": "" };
29370
+ for (let i = 0; i < s.length; i++) {
29371
+ const ch = s[i];
29372
+ if (ch === "\\") {
29373
+ if (i + 1 >= s.length)
29374
+ return undefined;
29375
+ const esc = s[++i];
29376
+ res.push(escapes[esc] ?? esc);
29377
+ }
29378
+ else {
29379
+ res.push(ch);
29380
+ }
29381
+ }
29382
+ return res.length > 0 ? res : [""];
29383
+ };
29384
+ for (let i = 0; i < opArgs.length; i++) {
29385
+ const a = opArgs[i];
29386
+ if (literal || a === "-" || !a.startsWith("-")) {
29387
+ files.push(a);
29388
+ continue;
29389
+ }
29390
+ if (a === "--") {
29391
+ literal = true;
29392
+ continue;
29393
+ }
29394
+ if (a === "--serial") {
29395
+ serial = true;
29396
+ continue;
29397
+ }
29398
+ if (a === "--delimiters" && i + 1 < opArgs.length) {
29399
+ const d = parseDelims(opArgs[++i]);
29400
+ if (!d)
29401
+ return undefined;
29402
+ delims = d;
29403
+ continue;
29404
+ }
29405
+ if (a.startsWith("--delimiters=")) {
29406
+ const d = parseDelims(a.slice(13));
29407
+ if (!d)
29408
+ return undefined;
29409
+ delims = d;
29410
+ continue;
29411
+ }
29412
+ if (a.startsWith("--"))
29413
+ return undefined;
29414
+ for (let k = 1; k < a.length; k++) {
29415
+ const f = a[k];
29416
+ if (f === "s")
29417
+ serial = true;
29418
+ else if (f === "d") {
29419
+ const rest = a.slice(k + 1);
29420
+ const rawD = rest.length > 0 ? rest : (i + 1 < opArgs.length ? opArgs[++i] : undefined);
29421
+ if (rawD === undefined)
29422
+ return undefined;
29423
+ const d = parseDelims(rawD);
29424
+ if (!d)
29425
+ return undefined;
29426
+ delims = d;
29427
+ break;
29428
+ }
29429
+ else
29430
+ return undefined;
29431
+ }
29432
+ }
29433
+ if (files.length === 0)
29434
+ files.push("-");
29435
+ if (!files.every(f => f === "-"))
29436
+ return undefined;
29437
+ if (serial) {
29438
+ if (files.length !== 1)
29439
+ return undefined;
29440
+ if (rawLines.length === 0)
29441
+ return [];
29442
+ let joined = rawLines[0];
29443
+ for (let i = 1; i < rawLines.length; i++) {
29444
+ joined += delims[(i - 1) % delims.length] + rawLines[i];
29445
+ }
29446
+ return joined.split("\n");
29447
+ }
29448
+ const cols = files.length;
29449
+ const out = [];
29450
+ for (let i = 0; i < rawLines.length; i += cols) {
29451
+ let row = rawLines[i] ?? "";
29452
+ for (let c = 1; c < cols; c++) {
29453
+ row += delims[(c - 1) % delims.length] + (rawLines[i + c] ?? "");
29454
+ }
29455
+ out.push(row);
29456
+ }
29457
+ return out;
29458
+ }
29459
+ evalSyncNumfmt(rawLines, opArgs) {
29460
+ let fromScale = "none";
29461
+ let toScale = "none";
29462
+ let padding = 0;
29463
+ let suffix = "";
29464
+ for (let i = 0; i < opArgs.length; i++) {
29465
+ const a = opArgs[i];
29466
+ if (a.startsWith("--from=")) {
29467
+ const v = a.slice(7);
29468
+ if (v === "none" || v === "iec" || v === "iec-i" || v === "si" || v === "auto")
29469
+ fromScale = v;
29470
+ else
29471
+ return undefined;
29472
+ }
29473
+ else if (a.startsWith("--to=")) {
29474
+ const v = a.slice(5);
29475
+ if (v === "none" || v === "iec" || v === "iec-i" || v === "si")
29476
+ toScale = v;
29477
+ else
29478
+ return undefined;
29479
+ }
29480
+ else if (a.startsWith("--padding=")) {
29481
+ const v = a.slice(10);
29482
+ if (!/^-?[1-9][0-9]{0,2}$/.test(v))
29483
+ return undefined;
29484
+ padding = Number(v);
29485
+ }
29486
+ else if (a.startsWith("--suffix=")) {
29487
+ suffix = a.slice(9);
29488
+ }
29489
+ else {
29490
+ return undefined;
29491
+ }
29492
+ }
29493
+ const units = "KMGTPEZY";
29494
+ const out = [];
29495
+ for (let i = 0; i < rawLines.length; i++) {
29496
+ const raw = rawLines[i].trim();
29497
+ if (raw.length === 0) {
29498
+ out.push("");
29499
+ continue;
29500
+ }
29501
+ const m = /^([+-]?(?:\d+(?:\.\d*)?|\.\d+))([KMGTPEZYkmgtpezy]i?)?$/.exec(raw);
29502
+ if (!m)
29503
+ return undefined;
29504
+ let val = Number(m[1]);
29505
+ const suf = m[2] ?? "";
29506
+ if (suf.length > 0) {
29507
+ if (fromScale === "none")
29508
+ return undefined;
29509
+ const uChar = suf[0].toUpperCase();
29510
+ const pIdx = units.indexOf(uChar) + 1;
29511
+ if (pIdx <= 0)
29512
+ return undefined;
29513
+ const hasI = suf.length === 2 && suf[1].toLowerCase() === "i";
29514
+ if (fromScale === "iec-i" && !hasI)
29515
+ return undefined;
29516
+ if ((fromScale === "iec" || fromScale === "si") && hasI)
29517
+ return undefined;
29518
+ const fBase = (fromScale === "iec" || fromScale === "iec-i" || (fromScale === "auto" && hasI)) ? 1024 : 1000;
29519
+ val = val * Math.pow(fBase, pIdx);
29520
+ }
29521
+ else if (fromScale === "iec-i") {
29522
+ // plain number ok
29523
+ }
29524
+ val = val >= 0 ? Math.ceil(val - 1e-12) : -Math.ceil(Math.abs(val) - 1e-12);
29525
+ let rendered;
29526
+ if (toScale === "none") {
29527
+ rendered = String(Math.trunc(val));
29528
+ }
29529
+ else {
29530
+ const tBase = (toScale === "iec" || toScale === "iec-i") ? 1024 : 1000;
29531
+ let pIdx = 0;
29532
+ let scaled = val;
29533
+ while (Math.abs(scaled) >= tBase && pIdx < units.length) {
29534
+ scaled /= tBase;
29535
+ pIdx++;
29536
+ }
29537
+ if (pIdx === 0) {
29538
+ rendered = String(Math.trunc(scaled));
29539
+ }
29540
+ else {
29541
+ if (Math.abs(scaled) < 10) {
29542
+ scaled = (scaled >= 0 ? Math.ceil(scaled * 10 - 1e-12) : -Math.ceil(Math.abs(scaled) * 10 - 1e-12)) / 10;
29543
+ if (Math.abs(scaled) >= tBase && pIdx < units.length) {
29544
+ scaled /= tBase;
29545
+ pIdx++;
29546
+ }
29547
+ }
29548
+ else {
29549
+ scaled = scaled >= 0 ? Math.ceil(scaled - 1e-12) : -Math.ceil(Math.abs(scaled) - 1e-12);
29550
+ if (Math.abs(scaled) >= tBase && pIdx < units.length) {
29551
+ scaled /= tBase;
29552
+ pIdx++;
29553
+ }
29554
+ }
29555
+ const numText = Math.abs(scaled) < 10 ? scaled.toFixed(1) : scaled.toFixed(0);
29556
+ const uText = (toScale === "si" && pIdx === 1 ? "k" : units[pIdx - 1]) + (toScale === "iec-i" ? "i" : "");
29557
+ rendered = numText + uText;
29558
+ }
29559
+ }
29560
+ rendered += suffix;
29561
+ if (padding !== 0) {
29562
+ const w = Math.abs(padding);
29563
+ if (rendered.length < w) {
29564
+ const pad = " ".repeat(w - rendered.length);
29565
+ rendered = padding < 0 ? rendered + pad : pad + rendered;
29566
+ }
29567
+ }
29568
+ out.push(rendered);
29569
+ }
29570
+ return out;
29571
+ }
29572
+ evalSyncSort(rawLines, opArgs, isByteLocale) {
29573
+ if (isByteLocale)
29574
+ return undefined;
29575
+ let rev = false;
29576
+ let num = false;
29577
+ let human = false;
29578
+ let month = false;
29579
+ let dict = false;
29580
+ let uniq = false;
29581
+ let fold = false;
29582
+ let blanks = false;
29583
+ let stable = false;
29584
+ let ver = false;
29585
+ let sep;
29586
+ let keySpec;
29587
+ for (let i = 0; i < opArgs.length; i++) {
29588
+ const a = opArgs[i];
29589
+ if (/^-[runfbsVhMd]+$/.test(a)) {
29590
+ if (a.includes("r"))
29591
+ rev = true;
29592
+ if (a.includes("n"))
29593
+ num = true;
29594
+ if (a.includes("h"))
29595
+ human = true;
29596
+ if (a.includes("M"))
29597
+ month = true;
29598
+ if (a.includes("d"))
29599
+ dict = true;
29600
+ if (a.includes("u"))
29601
+ uniq = true;
29602
+ if (a.includes("f"))
29603
+ fold = true;
29604
+ if (a.includes("b"))
29605
+ blanks = true;
29606
+ if (a.includes("s"))
29607
+ stable = true;
29608
+ if (a.includes("V"))
29609
+ ver = true;
29610
+ }
29611
+ else if (a === "--reverse")
29612
+ rev = true;
29613
+ else if (a === "--numeric-sort")
29614
+ num = true;
29615
+ else if (a === "--human-numeric-sort")
29616
+ human = true;
29617
+ else if (a === "--month-sort")
29618
+ month = true;
29619
+ else if (a === "--dictionary-order")
29620
+ dict = true;
29621
+ else if (a === "--unique")
29622
+ uniq = true;
29623
+ else if (a === "--ignore-case")
29624
+ fold = true;
29625
+ else if (a === "--ignore-leading-blanks")
29626
+ blanks = true;
29627
+ else if (a === "--stable")
29628
+ stable = true;
29629
+ else if (a === "--version-sort")
29630
+ ver = true;
29631
+ else if (a === "-t" && i + 1 < opArgs.length && opArgs[i + 1].length === 1) {
29632
+ sep = opArgs[++i];
29633
+ }
29634
+ else if (a.startsWith("-t") && a.length === 3) {
29635
+ sep = a[2];
29636
+ }
29637
+ else if (a.startsWith("--field-separator=") && a.length === 19) {
29638
+ sep = a[18];
29639
+ }
29640
+ else if (a === "-k" && i + 1 < opArgs.length) {
29641
+ if (keySpec !== undefined)
29642
+ return undefined;
29643
+ keySpec = opArgs[++i];
29644
+ }
29645
+ else if (a.startsWith("-k") && a.length > 2) {
29646
+ if (keySpec !== undefined)
29647
+ return undefined;
29648
+ keySpec = a.slice(2);
29649
+ }
29650
+ else {
29651
+ return undefined;
29652
+ }
29653
+ }
29654
+ if (ver || keySpec?.includes("V"))
29655
+ return undefined;
29656
+ let startField = 1;
29657
+ let endField;
29658
+ let keyNum = num;
29659
+ let keyHuman = human;
29660
+ let keyMonth = month;
29661
+ let keyDict = dict;
29662
+ let keyRev = rev;
29663
+ let keyFold = fold;
29664
+ let keyBlanks = blanks;
29665
+ let keyVer = ver;
29666
+ if (keySpec !== undefined) {
29667
+ const km = /^([1-9][0-9]{0,2})(?:,([1-9][0-9]{0,2}))?([nrbfVhMd]*)$/.exec(keySpec);
29668
+ if (!km)
29669
+ return undefined;
29670
+ startField = Number(km[1]);
29671
+ endField = km[2] !== undefined ? Number(km[2]) : undefined;
29672
+ if (endField !== undefined && endField < startField)
29673
+ return undefined;
29674
+ const kf = km[3] ?? "";
29675
+ if (kf.includes("n"))
29676
+ keyNum = true;
29677
+ if (kf.includes("h"))
29678
+ keyHuman = true;
29679
+ if (kf.includes("M"))
29680
+ keyMonth = true;
29681
+ if (kf.includes("d"))
29682
+ keyDict = true;
29683
+ if (kf.includes("r"))
29684
+ keyRev = true;
29685
+ if (kf.includes("f"))
29686
+ keyFold = true;
29687
+ if (kf.includes("b"))
29688
+ keyBlanks = true;
29689
+ if (kf.includes("V"))
29690
+ keyVer = true;
29691
+ }
29692
+ const extractKey = (l) => {
29693
+ let raw = keySpec === undefined
29694
+ ? l
29695
+ : (() => {
29696
+ if (sep !== undefined) {
29697
+ const fields = l.split(sep);
29698
+ return fields.slice(startField - 1, endField).join(sep);
29699
+ }
29700
+ // Default fields include their leading blanks; retain exact bytes.
29701
+ let offset = 0;
29702
+ let start = l.length;
29703
+ let end = l.length;
29704
+ for (let field = 1; offset < l.length; field++) {
29705
+ if (field === startField)
29706
+ start = offset;
29707
+ while (l[offset] === " " || l[offset] === "\t")
29708
+ offset++;
29709
+ while (offset < l.length && l[offset] !== " " && l[offset] !== "\t")
29710
+ offset++;
29711
+ if (field === endField) {
29712
+ end = offset;
29713
+ break;
29714
+ }
29715
+ }
29716
+ return l.slice(start, end);
29717
+ })();
29718
+ if (keyBlanks)
29719
+ raw = raw.replace(/^[ \t]+/, "");
29720
+ if (keyDict)
29721
+ raw = raw.replace(/[^a-zA-Z0-9 \t]+/g, "");
29722
+ return keyFold ? raw.toUpperCase() : raw;
29723
+ };
27463
29724
  const parseNum = (s) => {
27464
- const m = /^[ \t]*([+-]?(?:\d+(?:\.\d*)?|\.\d+))/.exec(s);
29725
+ const m = /^[ \t]*(-?(?:\d+(?:\.\d*)?|\.\d+))/.exec(s);
27465
29726
  return m ? Number(m[1]) : 0;
27466
29727
  };
29728
+ const compareHuman = (sa, sb) => {
29729
+ const parseH = (s) => {
29730
+ const m = /^[ \t]*(-?(?:\d+(?:\.\d*)?|\.\d+))([KMGTPEZYkmgtpezy])?/.exec(s);
29731
+ if (!m)
29732
+ return { sign: 0, unit: 0, val: 0 };
29733
+ const val = Number(m[1]);
29734
+ if (val === 0)
29735
+ return { sign: 0, unit: 0, val: 0 };
29736
+ const uChar = m[2] ? m[2].toUpperCase() : "";
29737
+ const unit = uChar === "" ? 0 : "KMGTPEZY".indexOf(uChar) + 1;
29738
+ return { sign: val < 0 ? -1 : 1, unit, val };
29739
+ };
29740
+ const ha = parseH(sa);
29741
+ const hb = parseH(sb);
29742
+ if (ha.sign !== hb.sign)
29743
+ return ha.sign - hb.sign;
29744
+ if (ha.unit !== hb.unit)
29745
+ return ha.sign < 0 ? hb.unit - ha.unit : ha.unit - hb.unit;
29746
+ return ha.val - hb.val;
29747
+ };
29748
+ const parseMonth = (s) => {
29749
+ const m = /^[ \t]*([a-zA-Z]{3})/.exec(s);
29750
+ if (!m)
29751
+ return 0;
29752
+ const idx = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"].indexOf(m[1].toUpperCase());
29753
+ return idx === -1 ? 0 : idx + 1;
29754
+ };
27467
29755
  const sorted = [...rawLines].sort((a, b) => {
27468
- if (num) {
27469
- const na = parseNum(a);
27470
- const nb = parseNum(b);
29756
+ const ka = extractKey(a);
29757
+ const kb = extractKey(b);
29758
+ if (keyHuman) {
29759
+ const hc = compareHuman(ka, kb);
29760
+ if (hc !== 0)
29761
+ return keyRev ? -hc : hc;
29762
+ }
29763
+ else if (keyMonth) {
29764
+ const ma = parseMonth(ka);
29765
+ const mb = parseMonth(kb);
29766
+ if (ma !== mb)
29767
+ return keyRev ? mb - ma : ma - mb;
29768
+ }
29769
+ else if (keyNum) {
29770
+ const na = parseNum(ka);
29771
+ const nb = parseNum(kb);
27471
29772
  if (na !== nb)
27472
- return rev ? nb - na : na - nb;
29773
+ return keyRev ? nb - na : na - nb;
29774
+ }
29775
+ else if (keyVer) {
29776
+ const vc = this.compareSyncVersion(ka, kb);
29777
+ if (vc !== 0)
29778
+ return keyRev ? -vc : vc;
27473
29779
  }
29780
+ else if (ka !== kb) {
29781
+ return ka < kb ? (keyRev ? 1 : -1) : (keyRev ? -1 : 1);
29782
+ }
29783
+ if (uniq || stable)
29784
+ return 0;
29785
+ const fa = fold ? a.toUpperCase() : a;
29786
+ const fb = fold ? b.toUpperCase() : b;
29787
+ if (fa !== fb)
29788
+ return fa < fb ? (rev ? 1 : -1) : (rev ? -1 : 1);
27474
29789
  return a < b ? (rev ? 1 : -1) : a > b ? (rev ? -1 : 1) : 0;
27475
29790
  });
27476
29791
  if (!uniq)
27477
29792
  return sorted;
27478
29793
  const dedup = [];
27479
29794
  for (let i = 0; i < sorted.length; i++) {
27480
- if (i === 0 || sorted[i] !== sorted[i - 1])
29795
+ if (i === 0) {
27481
29796
  dedup.push(sorted[i]);
29797
+ }
29798
+ else {
29799
+ const ka = extractKey(sorted[i]);
29800
+ const kb = extractKey(sorted[i - 1]);
29801
+ 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;
29802
+ if (!same)
29803
+ dedup.push(sorted[i]);
29804
+ }
27482
29805
  }
27483
29806
  return dedup;
27484
29807
  }
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);
29808
+ normalizeSyncWcFlag(arg) {
29809
+ if (arg === "-l" || arg === "--lines")
29810
+ return "-l";
29811
+ if (arg === "-c" || arg === "--bytes")
29812
+ return "-c";
29813
+ if (arg === "-w" || arg === "--words")
29814
+ return "-w";
29815
+ if (arg === "-m" || arg === "--chars")
29816
+ return "-m";
29817
+ if (arg === "-L" || arg === "--max-line-length")
29818
+ return "-L";
29819
+ return undefined;
29820
+ }
29821
+ evalSyncUniq(rawLines, opArgs) {
29822
+ let ignoreCase = false;
29823
+ let hasCount = false;
29824
+ let onlyRepeated = false;
29825
+ let onlyUnique = false;
29826
+ let skipFields = 0;
29827
+ let skipChars = 0;
29828
+ let checkChars = Infinity;
29829
+ for (let i = 0; i < opArgs.length; i++) {
29830
+ const a = opArgs[i];
29831
+ if (a === "--ignore-case")
29832
+ ignoreCase = true;
29833
+ else if (a === "--count")
29834
+ hasCount = true;
29835
+ else if (a === "--repeated")
29836
+ onlyRepeated = true;
29837
+ else if (a === "--unique")
29838
+ onlyUnique = true;
29839
+ else if (/^--skip-fields=[0-9]{1,4}$/.test(a))
29840
+ skipFields = Number(a.slice(14));
29841
+ else if (/^--skip-chars=[0-9]{1,4}$/.test(a))
29842
+ skipChars = Number(a.slice(13));
29843
+ else if (/^--check-chars=[0-9]{1,4}$/.test(a))
29844
+ checkChars = Number(a.slice(14));
29845
+ else if ((a === "-f" || a === "-s" || a === "-w") && i + 1 < opArgs.length && /^[0-9]{1,4}$/.test(opArgs[i + 1])) {
29846
+ const n = Number(opArgs[++i]);
29847
+ if (a === "-f")
29848
+ skipFields = n;
29849
+ else if (a === "-s")
29850
+ skipChars = n;
29851
+ else
29852
+ checkChars = n;
29853
+ }
29854
+ else if (/^-[fsw][0-9]{1,4}$/.test(a)) {
29855
+ const n = Number(a.slice(2));
29856
+ if (a[1] === "f")
29857
+ skipFields = n;
29858
+ else if (a[1] === "s")
29859
+ skipChars = n;
29860
+ else
29861
+ checkChars = n;
29862
+ }
29863
+ else if (/^-[cdui]+$/.test(a)) {
29864
+ for (let k = 1; k < a.length; k++) {
29865
+ const ch = a[k];
29866
+ if (ch === "i")
29867
+ ignoreCase = true;
29868
+ else if (ch === "c")
29869
+ hasCount = true;
29870
+ else if (ch === "d")
29871
+ onlyRepeated = true;
29872
+ else if (ch === "u")
29873
+ onlyUnique = true;
29874
+ }
29875
+ }
29876
+ else {
29877
+ return undefined;
29878
+ }
29879
+ }
29880
+ const keyOf = (l) => {
29881
+ let offset = 0;
29882
+ for (let f = 0; f < skipFields; f++) {
29883
+ while (offset < l.length && (l[offset] === " " || l[offset] === "\t"))
29884
+ offset++;
29885
+ while (offset < l.length && l[offset] !== " " && l[offset] !== "\t")
29886
+ offset++;
29887
+ }
29888
+ if (skipChars === 0 && checkChars === Infinity) {
29889
+ const sub = l.slice(offset);
29890
+ return ignoreCase ? sub.toLowerCase() : sub;
29891
+ }
29892
+ const chars = Array.from(l.slice(offset));
29893
+ const sub = (checkChars === Infinity ? chars.slice(skipChars) : chars.slice(skipChars, skipChars + checkChars)).join("");
29894
+ return ignoreCase ? sub.toLowerCase() : sub;
29895
+ };
29896
+ const outLines = [];
29897
+ let uIdx = 0;
29898
+ while (uIdx < rawLines.length) {
29899
+ const k0 = keyOf(rawLines[uIdx]);
29900
+ let uEnd = uIdx + 1;
29901
+ while (uEnd < rawLines.length && keyOf(rawLines[uEnd]) === k0)
29902
+ uEnd++;
29903
+ const count = uEnd - uIdx;
29904
+ if ((!onlyRepeated || count > 1) && (!onlyUnique || count === 1)) {
29905
+ outLines.push(hasCount ? `${String(count).padStart(7, " ")} ${rawLines[uIdx]}` : rawLines[uIdx]);
29906
+ }
29907
+ uIdx = uEnd;
29908
+ }
29909
+ return outLines;
29910
+ }
29911
+ sortSyncLines(rawLines, flag) {
29912
+ return this.evalSyncSort(rawLines, flag !== undefined ? [flag] : [], false) ?? [...rawLines];
29913
+ }
29914
+ translateSyncTr(input, set1Spec, set2Spec, truncateSet1 = false) {
29915
+ if ((set1Spec === "a-z" || set1Spec === "[:lower:]") && (set2Spec === "A-Z" || set2Spec === "[:upper:]")) {
29916
+ return input.replace(/[a-z]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 32));
29917
+ }
29918
+ if ((set1Spec === "A-Z" || set1Spec === "[:upper:]") && (set2Spec === "a-z" || set2Spec === "[:lower:]")) {
29919
+ return input.replace(/[A-Z]/g, ch => String.fromCharCode(ch.charCodeAt(0) + 32));
29920
+ }
29921
+ const rawS1 = this.expandSyncTrCharArray(set1Spec);
27491
29922
  const s2 = this.expandSyncTrCharArray(set2Spec);
27492
- if (!s1 || !s2 || s1.length === 0 || s2.length === 0)
29923
+ if (!rawS1 || !s2 || rawS1.length === 0 || s2.length === 0)
27493
29924
  return undefined;
29925
+ const s1 = truncateSet1 ? rawS1.slice(0, s2.length) : rawS1;
29926
+ if (s1.length === 0)
29927
+ return input;
27494
29928
  if (s1.length === 1 && s2.length === 1)
27495
29929
  return input.split(s1[0]).join(s2[0]);
27496
29930
  const map = new Map();
@@ -27684,7 +30118,7 @@ export class Runtime {
27684
30118
  if (!sName || hasShellFunction(rawState, sName) || rawState.extensions?.builtins.has(sName))
27685
30119
  return undefined;
27686
30120
  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))
30121
+ if (!extDef || (sName !== "rev" && sName !== "tac" && sName !== "nl" && sName !== "paste" && sName !== "jq" && !builtInDirectContextExecutors.has(extDef.execute)) || customRegisteredCommands.has(extDef.execute))
27688
30122
  return undefined;
27689
30123
  if (!this.arePureArgWords(sCmd.words, rawState))
27690
30124
  return undefined;
@@ -27704,29 +30138,32 @@ export class Runtime {
27704
30138
  return undefined;
27705
30139
  }
27706
30140
  else if (sName === "uniq") {
27707
- if (sArgs.length > 1 || (sArgs.length === 1 && sArgs[0] !== "-c" && sArgs[0] !== "-d" && sArgs[0] !== "-u" && sArgs[0] !== "-i"))
30141
+ if (this.evalSyncUniq([], sArgs) === undefined)
27708
30142
  return undefined;
27709
30143
  }
27710
30144
  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"))
30145
+ if (this.evalSyncSort([], sArgs, byteLocale(rawState.variables)) === undefined)
27712
30146
  return undefined;
27713
30147
  }
27714
30148
  else if (sName === "head" || sName === "tail") {
27715
30149
  if (sArgs.length === 0) {
27716
30150
  sArgs.push("-n", "10");
27717
30151
  }
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);
30152
+ 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])))) {
30153
+ const flag = (sArgs[0].startsWith("-c") || sArgs[0].startsWith("--bytes=")) ? "-c" : "-n";
30154
+ 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
30155
  sArgs.length = 0;
27722
30156
  sArgs.push(flag, numStr);
27723
30157
  }
27724
- if (sArgs.length !== 2 || (sArgs[0] !== "-n" && sArgs[0] !== "-c") || !/^[0-9]+$/.test(sArgs[1]))
30158
+ 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])));
30159
+ if (!validNum)
27725
30160
  return undefined;
27726
30161
  }
27727
30162
  else if (sName === "wc") {
27728
- if (sArgs.length !== 1 || (sArgs[0] !== "-l" && sArgs[0] !== "-c" && sArgs[0] !== "-w"))
30163
+ const normWc = sArgs.length === 1 ? this.normalizeSyncWcFlag(sArgs[0]) : undefined;
30164
+ if (!normWc)
27729
30165
  return undefined;
30166
+ sArgs[0] = normWc;
27730
30167
  }
27731
30168
  else if (sName === "sed") {
27732
30169
  if (this.evalSyncSed([], sArgs) === undefined)
@@ -27745,17 +30182,33 @@ export class Runtime {
27745
30182
  return undefined;
27746
30183
  }
27747
30184
  else if (sName === "jq") {
27748
- if (this.evalSyncJq("null", sArgs) === undefined)
30185
+ if (sArgs.length === 0 || sArgs.length > 2)
30186
+ return undefined;
30187
+ if (sArgs.length === 1 && sArgs[0].startsWith("-"))
30188
+ return undefined;
30189
+ if (sArgs.length === 2 && !["-r", "--raw-output", "-c", "--compact-output", "-rc", "-cr"].includes(sArgs[0]))
27749
30190
  return undefined;
27750
30191
  }
27751
30192
  else if (sName === "base64") {
27752
30193
  if (sArgs.length > 1 || (sArgs.length === 1 && sArgs[0] !== "-d" && sArgs[0] !== "--decode"))
27753
30194
  return undefined;
27754
30195
  }
27755
- else if (sName === "tac" || sName === "nl") {
30196
+ else if (sName === "tac") {
27756
30197
  if (sArgs.length !== 0)
27757
30198
  return undefined;
27758
30199
  }
30200
+ else if (sName === "nl") {
30201
+ if (this.evalSyncNl([], sArgs) === undefined)
30202
+ return undefined;
30203
+ }
30204
+ else if (sName === "paste") {
30205
+ if (this.evalSyncPaste([], sArgs) === undefined)
30206
+ return undefined;
30207
+ }
30208
+ else if (sName === "numfmt") {
30209
+ if (this.evalSyncNumfmt([], sArgs) === undefined)
30210
+ return undefined;
30211
+ }
27759
30212
  else {
27760
30213
  return undefined;
27761
30214
  }
@@ -27843,8 +30296,10 @@ export class Runtime {
27843
30296
  const isInlineBase64 = firstName === "base64";
27844
30297
  const isInlineTac = firstName === "tac";
27845
30298
  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")));
30299
+ const isInlinePaste = firstName === "paste";
30300
+ const isInlineNumfmt = firstName === "numfmt";
30301
+ const isInlineSort = firstName === "sort" && !byteLocale(rawState.variables);
30302
+ const isInlineUniq = firstName === "uniq";
27848
30303
  const inlineSedMatch = firstName === "sed";
27849
30304
  if (firstName === "rev" ||
27850
30305
  firstName === "head" ||
@@ -27861,10 +30316,13 @@ export class Runtime {
27861
30316
  isInlineJq ||
27862
30317
  isInlineBase64 ||
27863
30318
  isInlineTac ||
27864
- isInlineNl) {
30319
+ isInlineNl ||
30320
+ isInlinePaste ||
30321
+ isInlineNumfmt) {
27865
30322
  const inStr = prevLen === 0 ? "" : sharedSyncPipeDecoder.decode(prevBuf.subarray(0, prevLen));
27866
30323
  const rawLines = inStr.length === 0 ? [] : (inStr.endsWith("\n") ? inStr.slice(0, -1).split("\n") : inStr.split("\n"));
27867
30324
  let outLines = [];
30325
+ let sedTerminated = false;
27868
30326
  if (firstName === "rev") {
27869
30327
  outLines = rawLines.map(l => Array.from(l).reverse().join(""));
27870
30328
  }
@@ -27883,7 +30341,16 @@ export class Runtime {
27883
30341
  prevLen = sub.byteLength;
27884
30342
  continue;
27885
30343
  }
27886
- outLines = firstName === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
30344
+ if (firstName === "tail" && stageArgs[1].startsWith("+")) {
30345
+ outLines = rawLines.slice(Math.max(0, count - 1));
30346
+ }
30347
+ else if (firstName === "head" && stageArgs[1].startsWith("-")) {
30348
+ const drop = Math.abs(count);
30349
+ outLines = drop === 0 ? [...rawLines] : rawLines.slice(0, Math.max(0, rawLines.length - drop));
30350
+ }
30351
+ else {
30352
+ outLines = firstName === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
30353
+ }
27887
30354
  }
27888
30355
  else if (firstName === "wc") {
27889
30356
  if (stageArgs[0] === "-l") {
@@ -27897,6 +30364,12 @@ export class Runtime {
27897
30364
  const trimmed = inStr.trim();
27898
30365
  outLines = [String(trimmed.length === 0 ? 0 : trimmed.split(/[ \t\n\r\f\v]+/).length)];
27899
30366
  }
30367
+ else if (stageArgs[0] === "-m") {
30368
+ outLines = [String(byteLocale(rawState.variables) ? prevLen : Array.from(inStr).length)];
30369
+ }
30370
+ else if (stageArgs[0] === "-L") {
30371
+ outLines = [String(this.wcMaxLineWidth(inStr, byteLocale(rawState.variables)))];
30372
+ }
27900
30373
  else {
27901
30374
  outLines = [String(prevLen)];
27902
30375
  }
@@ -27924,28 +30397,20 @@ export class Runtime {
27924
30397
  continue;
27925
30398
  }
27926
30399
  else if (isInlineSort) {
27927
- outLines = this.sortSyncLines(rawLines, stageArgs[0]);
30400
+ outLines = this.evalSyncSort(rawLines, stageArgs, false) ?? [];
27928
30401
  }
27929
30402
  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
- }
30403
+ const uniqRes = this.evalSyncUniq(rawLines, stageArgs);
30404
+ if (uniqRes === undefined)
30405
+ return undefined;
30406
+ outLines = uniqRes;
27943
30407
  }
27944
30408
  else if (inlineSedMatch) {
27945
30409
  const sedRes = this.evalSyncSed(rawLines, stageArgs);
27946
30410
  if (sedRes === undefined)
27947
30411
  return undefined;
27948
- outLines = sedRes;
30412
+ outLines = sedRes.lines;
30413
+ sedTerminated = sedRes.lastInputIndex < rawLines.length - 1;
27949
30414
  }
27950
30415
  else if (isInlineAwk) {
27951
30416
  const awkRes = this.evalSyncAwk(rawLines, stageArgs);
@@ -27968,17 +30433,22 @@ export class Runtime {
27968
30433
  outLines = [...rawLines].reverse();
27969
30434
  }
27970
30435
  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
- }
30436
+ const nlRes = this.evalSyncNl(rawLines, stageArgs);
30437
+ if (nlRes === undefined)
30438
+ return undefined;
30439
+ outLines = nlRes;
30440
+ }
30441
+ else if (isInlinePaste) {
30442
+ const pasteRes = this.evalSyncPaste(rawLines, stageArgs);
30443
+ if (pasteRes === undefined)
30444
+ return undefined;
30445
+ outLines = pasteRes;
30446
+ }
30447
+ else if (isInlineNumfmt) {
30448
+ const nmRes = this.evalSyncNumfmt(rawLines, stageArgs);
30449
+ if (nmRes === undefined)
30450
+ return undefined;
30451
+ outLines = nmRes;
27982
30452
  }
27983
30453
  else if (isInlineBase64) {
27984
30454
  if (stageArgs.length === 0) {
@@ -27990,7 +30460,7 @@ export class Runtime {
27990
30460
  if (cleaned.length % 4 !== 0 || (cleaned.length > 0 && !/^[A-Za-z0-9+/]+={0,2}$/.test(cleaned)))
27991
30461
  return undefined;
27992
30462
  const decoded = this.syncBase64DecodeBytes(cleaned);
27993
- if (decoded.includes(0) || decoded.byteLength > nextBuf.byteLength)
30463
+ if (decoded.some(byte => byte === 0 || byte >= 128) || decoded.byteLength > nextBuf.byteLength)
27994
30464
  return undefined;
27995
30465
  const nextTotalBytes = this.budget.bytes + decoded.byteLength;
27996
30466
  if (nextTotalBytes > this.budget.maxOutputBytesSmi && decoded.byteLength > this.budget.limits.maxOutputBytes - this.budget.bytes)
@@ -28010,7 +30480,7 @@ export class Runtime {
28010
30480
  }
28011
30481
  // These filters preserve the terminator of the final selected input line.
28012
30482
  const preservesTerminator = firstName === "head" || firstName === "tail" || firstName === "rev" || firstName === "sed";
28013
- const terminated = !preservesTerminator || inStr.endsWith("\n") || firstName === "head" && outLines.length < rawLines.length;
30483
+ const terminated = sedTerminated || !preservesTerminator || inStr.endsWith("\n") || firstName === "head" && outLines.length < rawLines.length;
28014
30484
  const outStr = outLines.length > 0 ? outLines.join("\n") + (terminated ? "\n" : "") : "";
28015
30485
  const outByteLen = shellValueByteLength(outStr);
28016
30486
  if (outByteLen > nextBuf.byteLength)
@@ -28175,8 +30645,8 @@ export class Runtime {
28175
30645
  w0Plain === "sort" ||
28176
30646
  w0Plain === "uniq" ||
28177
30647
  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))))) {
30648
+ (hasSingleHereStringRedir && (w0Plain === "base64" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste" || w0Plain === "numfmt"));
30649
+ if (isSingleFileTool && !hasShellFunction(rawState, w0Plain) && (this.commands.has(w0Plain) || ((w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste") && Boolean(this.getExternalCommand(w0Plain))))) {
28180
30650
  const allArgs = [];
28181
30651
  let fOk = true;
28182
30652
  for (let i = 1; i < cmd.words.length; i++) {
@@ -28216,6 +30686,12 @@ export class Runtime {
28216
30686
  // Non-ASCII file inputs use normal execution, which owns their raw ShellValue.
28217
30687
  if (view && view.byteLength <= 16384 && !view.includes(0) && (hasSingleHereStringRedir || view.every(byte => byte < 128))) {
28218
30688
  const fileStr = hasSingleHereStringRedir ? hereStrVal : sharedSyncPipeDecoder.decode(view);
30689
+ // Record-based shortcuts below serialize complete newline-terminated records.
30690
+ // Let normal commands preserve partial final records when reading files.
30691
+ if (fileStr.length > 0 && !fileStr.endsWith("\n") &&
30692
+ (w0Plain === "head" || w0Plain === "tail" || w0Plain === "sed" || w0Plain === "cut" || w0Plain === "uniq"))
30693
+ return undefined;
30694
+ const renderLines = (lines) => lines.length === 0 ? "" : lines.join("\n") + "\n";
28219
30695
  const rawLines = fileStr.endsWith("\n") ? fileStr.slice(0, -1).split("\n") : (fileStr.length === 0 ? [] : fileStr.split("\n"));
28220
30696
  let fileRes;
28221
30697
  let exitStatus = 0;
@@ -28225,19 +30701,36 @@ export class Runtime {
28225
30701
  else if ((w0Plain === "head" || w0Plain === "tail") && opArgs.length <= 2) {
28226
30702
  let count = opArgs.length === 0 ? 10 : undefined;
28227
30703
  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));
30704
+ let countRaw = opArgs.length === 0 ? "10" : undefined;
30705
+ if (opArgs.length === 1 && (opArgs[0].startsWith("-n") || opArgs[0].startsWith("--lines="))) {
30706
+ const sub = opArgs[0].startsWith("--lines=") ? opArgs[0].slice(8) : opArgs[0].slice(2);
30707
+ if (/^[0-9]{1,5}$/.test(sub) || (w0Plain === "tail" && /^\+[0-9]{1,5}$/.test(sub)) || (w0Plain === "head" && /^-[0-9]{1,5}$/.test(sub)))
30708
+ countRaw = sub;
30709
+ }
30710
+ else if (opArgs.length === 1 && (opArgs[0].startsWith("-c") || opArgs[0].startsWith("--bytes="))) {
30711
+ const sub = opArgs[0].startsWith("--bytes=") ? opArgs[0].slice(8) : opArgs[0].slice(2);
30712
+ if (/^[0-9]{1,5}$/.test(sub))
30713
+ byteCount = Number(sub);
30714
+ }
28232
30715
  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]);
30716
+ countRaw = opArgs[0].slice(1);
30717
+ 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]))))
30718
+ countRaw = opArgs[1];
28236
30719
  else if (opArgs.length === 2 && opArgs[0] === "-c" && /^[0-9]{1,5}$/.test(opArgs[1]))
28237
30720
  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");
30721
+ if (countRaw !== undefined) {
30722
+ count = Number(countRaw);
30723
+ let sliced;
30724
+ if (w0Plain === "tail" && countRaw.startsWith("+"))
30725
+ sliced = rawLines.slice(Math.max(0, count - 1));
30726
+ else if (w0Plain === "head" && countRaw.startsWith("-")) {
30727
+ const drop = Math.abs(count);
30728
+ sliced = drop === 0 ? [...rawLines] : rawLines.slice(0, Math.max(0, rawLines.length - drop));
30729
+ }
30730
+ else {
30731
+ sliced = w0Plain === "head" ? rawLines.slice(0, count) : (count === 0 ? [] : rawLines.slice(-count));
30732
+ }
30733
+ fileRes = renderLines(sliced);
28241
30734
  }
28242
30735
  else if (byteCount !== undefined) {
28243
30736
  const slicedBytes = w0Plain === "head"
@@ -28249,91 +30742,109 @@ export class Runtime {
28249
30742
  else if (w0Plain === "jq") {
28250
30743
  const jqRes = this.evalSyncJq(fileStr.trim(), opArgs);
28251
30744
  if (jqRes !== undefined)
28252
- fileRes = jqRes.join("\n");
30745
+ fileRes = renderLines(jqRes);
28253
30746
  }
28254
30747
  else if (w0Plain === "awk") {
28255
30748
  const awkRes = this.evalSyncAwk(rawLines, opArgs);
28256
30749
  if (awkRes !== undefined)
28257
- fileRes = awkRes.join("\n");
30750
+ fileRes = renderLines(awkRes);
28258
30751
  }
28259
30752
  else if (w0Plain === "grep") {
28260
30753
  const grepRes = this.evalSyncGrep(rawLines, opArgs, Boolean(rawState.errexit));
28261
30754
  if (grepRes !== undefined) {
28262
30755
  exitStatus = grepRes.status;
28263
- fileRes = grepRes.lines.join("\n");
30756
+ fileRes = renderLines(grepRes.lines);
28264
30757
  }
28265
30758
  }
28266
30759
  else if (w0Plain === "sed") {
28267
30760
  const sedRes = this.evalSyncSed(rawLines, opArgs);
28268
30761
  if (sedRes !== undefined)
28269
- fileRes = sedRes.join("\n");
30762
+ fileRes = renderLines(sedRes.lines);
28270
30763
  }
28271
30764
  else if (w0Plain === "cut") {
28272
30765
  const cutRes = this.evalSyncCut(rawLines, opArgs, byteLocale(rawState.variables));
28273
30766
  if (cutRes !== undefined)
28274
- fileRes = cutRes.join("\n");
30767
+ fileRes = renderLines(cutRes);
28275
30768
  }
28276
- else if (w0Plain === "wc" && opArgs.length === 1 && (opArgs[0] === "-l" || opArgs[0] === "-c" || opArgs[0] === "-w")) {
30769
+ else if (w0Plain === "wc" && opArgs.length === 1 && this.normalizeSyncWcFlag(opArgs[0]) !== undefined) {
30770
+ const wcMode = this.normalizeSyncWcFlag(opArgs[0]);
28277
30771
  let count = 0;
28278
- if (opArgs[0] === "-l") {
30772
+ if (wcMode === "-l") {
28279
30773
  for (let k = 0; k < view.byteLength; k++)
28280
30774
  if (view[k] === 10)
28281
30775
  count++;
28282
30776
  }
28283
- else if (opArgs[0] === "-c") {
30777
+ else if (wcMode === "-c") {
28284
30778
  count = view.byteLength;
28285
30779
  }
30780
+ else if (wcMode === "-m") {
30781
+ count = byteLocale(rawState.variables) ? view.byteLength : Array.from(fileStr).length;
30782
+ }
30783
+ else if (wcMode === "-L") {
30784
+ count = this.wcMaxLineWidth(fileStr, byteLocale(rawState.variables));
30785
+ }
28286
30786
  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;
30787
+ // Unicode word separators depend on locale and POSIXLY_CORRECT.
30788
+ // Use the maintained command for those inputs rather than JS whitespace.
30789
+ if (!byteLocale(rawState.variables) && view.some(byte => byte > 127))
30790
+ return undefined;
30791
+ count = fileStr.split(/[ \t\n\r\f\v]+/).filter(Boolean).length;
28308
30792
  }
28309
- fileRes = outLines.join("\n");
30793
+ fileRes = ((hasSingleStdinRedir || hasSingleHereStringRedir) ? String(count) : `${count} ${fileArg}`) + "\n";
30794
+ }
30795
+ else if (w0Plain === "sort" && !byteLocale(rawState.variables)) {
30796
+ const sortRes = this.evalSyncSort(rawLines, opArgs, false);
30797
+ if (sortRes !== undefined)
30798
+ fileRes = renderLines(sortRes);
30799
+ }
30800
+ else if (w0Plain === "uniq") {
30801
+ const uniqRes = this.evalSyncUniq(rawLines, opArgs);
30802
+ if (uniqRes !== undefined)
30803
+ fileRes = renderLines(uniqRes);
28310
30804
  }
28311
30805
  else if (w0Plain === "tr" && (hasSingleStdinRedir || hasSingleHereStringRedir)) {
28312
30806
  fileRes = this.evalSyncTr(fileStr, opArgs);
28313
30807
  }
28314
30808
  else if (hasSingleHereStringRedir && w0Plain === "base64" && (opArgs.length === 0 || (opArgs.length === 1 && (opArgs[0] === "-d" || opArgs[0] === "--decode")))) {
28315
30809
  if (opArgs.length === 0)
28316
- fileRes = this.syncBase64Encode(view);
28317
- else
28318
- fileRes = sharedSyncPipeDecoder.decode(this.syncBase64DecodeBytes(fileStr));
30810
+ fileRes = this.syncBase64Encode(view) + "\n";
30811
+ else {
30812
+ const decoded = this.syncBase64DecodeBytes(fileStr);
30813
+ // Text-only substitutions cannot retain binary byte provenance.
30814
+ if (decoded.some(byte => byte === 0 || byte >= 128))
30815
+ return undefined;
30816
+ fileRes = sharedSyncPipeDecoder.decode(decoded);
30817
+ }
28319
30818
  }
28320
30819
  else if (hasSingleHereStringRedir && w0Plain === "rev" && opArgs.length === 0) {
28321
- fileRes = rawLines.map(l => Array.from(l).reverse().join("")).join("\n");
30820
+ fileRes = renderLines(rawLines.map(l => Array.from(l).reverse().join("")));
28322
30821
  }
28323
30822
  else if (hasSingleHereStringRedir && w0Plain === "tac" && opArgs.length === 0) {
28324
- fileRes = [...rawLines].reverse().join("\n");
30823
+ fileRes = renderLines([...rawLines].reverse());
28325
30824
  }
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");
30825
+ else if (hasSingleHereStringRedir && w0Plain === "nl") {
30826
+ const nlRes = this.evalSyncNl(rawLines, opArgs);
30827
+ if (nlRes !== undefined)
30828
+ fileRes = renderLines(nlRes);
30829
+ }
30830
+ else if (hasSingleHereStringRedir && w0Plain === "paste") {
30831
+ const pasteRes = this.evalSyncPaste(rawLines, opArgs);
30832
+ if (pasteRes !== undefined)
30833
+ fileRes = renderLines(pasteRes);
30834
+ }
30835
+ else if (hasSingleHereStringRedir && w0Plain === "numfmt") {
30836
+ const nmRes = this.evalSyncNumfmt(rawLines, opArgs);
30837
+ if (nmRes !== undefined)
30838
+ fileRes = renderLines(nmRes);
28329
30839
  }
28330
30840
  if (fileRes !== undefined) {
30841
+ // Charge emitted bytes before substitution removes trailing newlines.
30842
+ const outBytes = shellValueByteLength(fileRes);
28331
30843
  let end = fileRes.length;
28332
30844
  while (end > 0 && fileRes.charCodeAt(end - 1) === 10)
28333
30845
  end--;
28334
30846
  if (end < fileRes.length)
28335
30847
  fileRes = fileRes.slice(0, end);
28336
- const outBytes = (fileRes.length * 3 > 127 ? shellValueByteLength(fileRes) : fileRes.length) + 1;
28337
30848
  const nextTotalBytes = this.budget.bytes + outBytes;
28338
30849
  if (nextTotalBytes > this.budget.maxOutputBytesSmi && outBytes > this.budget.limits.maxOutputBytes - this.budget.bytes)
28339
30850
  this.budget.fail("maxOutputBytes");
@@ -28435,46 +30946,17 @@ export class Runtime {
28435
30946
  if (!this.isPureArgWord(cmd.words[i], rawState))
28436
30947
  return undefined;
28437
30948
  }
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"))
30949
+ if (w0Plain === "dirname" || w0Plain === "basename") {
30950
+ const dbArgs = [];
30951
+ for (let i = 1; i < cmd.words.length; i++) {
30952
+ const v = this.fastValueWord(cmd.words[i], state, io, true, false, false, true, undefined, part.line);
30953
+ if (typeof v !== "string")
28472
30954
  return undefined;
28473
- wVal2 = v2;
30955
+ dbArgs.push(v);
28474
30956
  }
28475
- let res = /^\/+$/u.test(wVal1) ? "/" : basename(wVal1);
28476
- if (wVal2 && res !== wVal2 && res.endsWith(wVal2))
28477
- res = res.slice(0, -wVal2.length);
30957
+ let res = w0Plain === "dirname" ? this.evalSyncDirname(dbArgs) : this.evalSyncBasename(dbArgs);
30958
+ if (res === undefined)
30959
+ return undefined;
28478
30960
  const byteLength = shellValueByteLength(res) + 1;
28479
30961
  while (res.endsWith("\n"))
28480
30962
  res = res.slice(0, -1);