typescript-virtual-container 1.5.4 → 1.5.6

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 (40) hide show
  1. package/README.md +107 -541
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/VirtualShell/shell.js +158 -11
  4. package/dist/commands/awk.d.ts +6 -11
  5. package/dist/commands/awk.js +462 -109
  6. package/dist/commands/bc.d.ts +2 -0
  7. package/dist/commands/bc.js +28 -0
  8. package/dist/commands/bzip2.d.ts +11 -0
  9. package/dist/commands/bzip2.js +91 -0
  10. package/dist/commands/find.d.ts +2 -2
  11. package/dist/commands/find.js +212 -37
  12. package/dist/commands/{ifconfig.d.ts → ip.d.ts} +1 -1
  13. package/dist/commands/{ifconfig.js → ip.js} +3 -3
  14. package/dist/commands/jobs.d.ts +4 -0
  15. package/dist/commands/jobs.js +27 -0
  16. package/dist/commands/lsof.d.ts +6 -0
  17. package/dist/commands/lsof.js +30 -0
  18. package/dist/commands/perl.d.ts +6 -0
  19. package/dist/commands/perl.js +76 -0
  20. package/dist/commands/registry.js +22 -3
  21. package/dist/commands/runtime.js +2 -1
  22. package/dist/commands/sed.d.ts +2 -2
  23. package/dist/commands/sed.js +216 -34
  24. package/dist/commands/set.js +20 -0
  25. package/dist/commands/sh.js +111 -1
  26. package/dist/commands/strace.d.ts +6 -0
  27. package/dist/commands/strace.js +26 -0
  28. package/dist/commands/tar.d.ts +2 -1
  29. package/dist/commands/tar.js +138 -52
  30. package/dist/commands/zip.d.ts +11 -0
  31. package/dist/commands/zip.js +232 -0
  32. package/dist/modules/linuxRootfs.js +1 -1
  33. package/dist/utils/expand.js +67 -1
  34. package/package.json +5 -4
  35. package/dist/self-standalone.d.ts +0 -1
  36. package/dist/self-standalone.js +0 -444
  37. package/dist/standalone-wo-sftp.d.ts +0 -1
  38. package/dist/standalone-wo-sftp.js +0 -30
  39. package/dist/standalone.d.ts +0 -1
  40. package/dist/standalone.js +0 -61
@@ -1,28 +1,53 @@
1
- import { getFlag } from "./command-helpers";
2
1
  import { assertPathAccess, resolvePath } from "./helpers";
3
2
  /**
4
- * Minimal awk-like pattern scanner.
5
- *
6
- * Supported:
7
- * - `NR==N` pattern (line number condition)
8
- * - `NF` (number of fields)
9
- * - `/regex/` pattern
10
- * - `{ print $N, $M, ... }` action
11
- * - `{ print }` / `{ print $0 }`
12
- * - `BEGIN { ... }` and `END { ... }` blocks (no side effects)
13
- * - `$NF` (last field)
14
- * - `-F sep` field separator
3
+ * AWK pattern scanning — supports BEGIN/END, /regex/, NR/NF conditions, field ops,
4
+ * variable assignment (-v), arithmetic, string functions (sub/gsub/substr/split/length/index/sprintf/tolower/toupper/match),
5
+ * printf, getline (from stdin), next, and multi-statement blocks.
6
+ * @category text
7
+ * @params ["[-F sep] [-v var=val] '<program>' [file]"]
15
8
  */
16
9
  export const awkCommand = {
17
10
  name: "awk",
18
11
  description: "Pattern scanning and processing language",
19
12
  category: "text",
20
- params: ["[-F <sep>] '<program>' [file]"],
13
+ params: ["[-F sep] [-v var=val] '<program>' [file]"],
21
14
  run: ({ authUser, args, stdin, cwd, shell }) => {
22
- const sep = getFlag(args, ["-F"]) ?? " ";
23
- const nonFlagArgs = args.filter((a) => !a.startsWith("-") && a !== sep);
24
- const prog = nonFlagArgs[0];
25
- const fileArg = nonFlagArgs[1];
15
+ // Parse flags
16
+ let sep = " ";
17
+ const initVars = {};
18
+ const positionals = [];
19
+ let i = 0;
20
+ while (i < args.length) {
21
+ const a = args[i];
22
+ if (a === "-F") {
23
+ sep = args[++i] ?? " ";
24
+ i++;
25
+ }
26
+ else if (a.startsWith("-F")) {
27
+ sep = a.slice(2);
28
+ i++;
29
+ }
30
+ else if (a === "-v") {
31
+ const kv = args[++i] ?? "";
32
+ const eq = kv.indexOf("=");
33
+ if (eq !== -1)
34
+ initVars[kv.slice(0, eq)] = kv.slice(eq + 1);
35
+ i++;
36
+ }
37
+ else if (a.startsWith("-v")) {
38
+ const kv = a.slice(2);
39
+ const eq = kv.indexOf("=");
40
+ if (eq !== -1)
41
+ initVars[kv.slice(0, eq)] = kv.slice(eq + 1);
42
+ i++;
43
+ }
44
+ else {
45
+ positionals.push(a);
46
+ i++;
47
+ }
48
+ }
49
+ const prog = positionals[0];
50
+ const fileArg = positionals[1];
26
51
  if (!prog)
27
52
  return { stderr: "awk: no program", exitCode: 1 };
28
53
  let input = stdin ?? "";
@@ -36,100 +61,395 @@ export const awkCommand = {
36
61
  return { stderr: `awk: ${fileArg}: No such file or directory`, exitCode: 1 };
37
62
  }
38
63
  }
39
- const lines = input.split("\n");
40
- // Remove empty last element if input ends with \n
41
- if (lines[lines.length - 1] === "")
42
- lines.pop();
43
- const clauses = [];
44
- const progTrim = prog.trim();
45
- // Handle single unbraced pattern (NR==2, /regex/)
46
- if (!progTrim.startsWith("{") && !progTrim.includes("{")) {
47
- clauses.push({ pattern: progTrim, action: "print $0" });
64
+ function numVal(v) {
65
+ if (v === undefined || v === "")
66
+ return 0;
67
+ const n = Number(v);
68
+ return Number.isNaN(n) ? 0 : n;
69
+ }
70
+ function strVal(v) {
71
+ if (v === undefined)
72
+ return "";
73
+ return String(v);
48
74
  }
49
- else {
50
- // Parse "pattern { action } pattern2 { action2 }"
51
- const clauseRe = /([^{]*)\{([^}]*)\}/g;
52
- let m2 = clauseRe.exec(progTrim);
53
- while (m2 !== null) {
54
- clauses.push({ pattern: m2[1].trim(), action: m2[2].trim() });
55
- m2 = clauseRe.exec(progTrim);
75
+ function splitFields(line, fs) {
76
+ if (fs === " ")
77
+ return line.trim().split(/\s+/).filter(Boolean);
78
+ if (fs.length === 1)
79
+ return line.split(fs);
80
+ return line.split(new RegExp(fs));
81
+ }
82
+ // Evaluate an AWK expression string with given context
83
+ function evalExpr(expr, vars, fields, nr, nf) {
84
+ expr = expr.trim();
85
+ if (expr === "")
86
+ return "";
87
+ // String literal
88
+ if (expr.startsWith('"') && expr.endsWith('"'))
89
+ return expr.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, "\t");
90
+ // Numeric literal
91
+ if (/^-?\d+(\.\d+)?$/.test(expr))
92
+ return parseFloat(expr);
93
+ // $0, $N, $NF
94
+ if (expr === "$0")
95
+ return fields.join(sep === " " ? " " : sep) || "";
96
+ if (expr === "$NF")
97
+ return fields[nf - 1] ?? "";
98
+ if (/^\$\d+$/.test(expr))
99
+ return fields[parseInt(expr.slice(1), 10) - 1] ?? "";
100
+ if (/^\$/.test(expr)) {
101
+ const inner = expr.slice(1);
102
+ const idx = numVal(evalExpr(inner, vars, fields, nr, nf));
103
+ if (idx === 0)
104
+ return fields.join(sep === " " ? " " : sep) || "";
105
+ return fields[idx - 1] ?? "";
56
106
  }
57
- if (clauses.length === 0) {
58
- clauses.push({ pattern: "", action: progTrim.replace(/[{}]/g, "").trim() });
107
+ // NR, NF
108
+ if (expr === "NR")
109
+ return nr;
110
+ if (expr === "NF")
111
+ return nf;
112
+ // Variable
113
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(expr))
114
+ return vars[expr] ?? "";
115
+ // String functions
116
+ const lenM = expr.match(/^length\s*\(([^)]*)\)$/);
117
+ if (lenM)
118
+ return strVal(evalExpr(lenM[1].trim(), vars, fields, nr, nf)).length;
119
+ const substrM = expr.match(/^substr\s*\((.+)\)$/);
120
+ if (substrM) {
121
+ const parts2 = splitCSV(substrM[1]);
122
+ const s = strVal(evalExpr(parts2[0]?.trim() ?? "", vars, fields, nr, nf));
123
+ const start = numVal(evalExpr(parts2[1]?.trim() ?? "1", vars, fields, nr, nf)) - 1;
124
+ const len2 = parts2[2] !== undefined ? numVal(evalExpr(parts2[2].trim(), vars, fields, nr, nf)) : undefined;
125
+ return len2 !== undefined ? s.slice(Math.max(0, start), start + len2) : s.slice(Math.max(0, start));
59
126
  }
127
+ const indexM = expr.match(/^index\s*\((.+)\)$/);
128
+ if (indexM) {
129
+ const parts2 = splitCSV(indexM[1]);
130
+ const s = strVal(evalExpr(parts2[0]?.trim() ?? "", vars, fields, nr, nf));
131
+ const t = strVal(evalExpr(parts2[1]?.trim() ?? "", vars, fields, nr, nf));
132
+ return s.indexOf(t) + 1;
133
+ }
134
+ const tolowerM = expr.match(/^tolower\s*\((.+)\)$/);
135
+ if (tolowerM)
136
+ return strVal(evalExpr(tolowerM[1].trim(), vars, fields, nr, nf)).toLowerCase();
137
+ const toupperM = expr.match(/^toupper\s*\((.+)\)$/);
138
+ if (toupperM)
139
+ return strVal(evalExpr(toupperM[1].trim(), vars, fields, nr, nf)).toUpperCase();
140
+ const matchM = expr.match(/^match\s*\((.+),\s*\/(.+)\/\)$/);
141
+ if (matchM) {
142
+ const s = strVal(evalExpr(matchM[1].trim(), vars, fields, nr, nf));
143
+ try {
144
+ const m = s.match(new RegExp(matchM[2]));
145
+ if (m) {
146
+ vars.RSTART = (m.index ?? 0) + 1;
147
+ vars.RLENGTH = m[0].length;
148
+ return (m.index ?? 0) + 1;
149
+ }
150
+ }
151
+ catch { /* ignore */ }
152
+ vars.RSTART = 0;
153
+ vars.RLENGTH = -1;
154
+ return 0;
155
+ }
156
+ // Ternary: a ? b : c
157
+ const ternM = expr.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
158
+ if (ternM) {
159
+ const cond = evalExpr(ternM[1].trim(), vars, fields, nr, nf);
160
+ return numVal(cond) !== 0 || (typeof cond === "string" && cond !== "")
161
+ ? evalExpr(ternM[2].trim(), vars, fields, nr, nf)
162
+ : evalExpr(ternM[3].trim(), vars, fields, nr, nf);
163
+ }
164
+ // String concatenation (space between two quoted/var exprs)
165
+ const concatM = expr.match(/^((?:"[^"]*"|[A-Za-z_$][A-Za-z0-9_$]*|\d+))\s+((?:"[^"]*"|[A-Za-z_$][A-Za-z0-9_$]*|\d+))$/);
166
+ if (concatM) {
167
+ return strVal(evalExpr(concatM[1], vars, fields, nr, nf)) + strVal(evalExpr(concatM[2], vars, fields, nr, nf));
168
+ }
169
+ // Arithmetic / comparison — substitute known tokens and eval
170
+ try {
171
+ const subst = expr
172
+ .replace(/\bNR\b/g, String(nr))
173
+ .replace(/\bNF\b/g, String(nf))
174
+ .replace(/\$NF\b/g, String(nf > 0 ? numVal(fields[nf - 1]) : 0))
175
+ .replace(/\$(\d+)/g, (_, n) => String(numVal(fields[parseInt(n, 10) - 1])))
176
+ .replace(/\b([A-Za-z_][A-Za-z0-9_]*)\b/g, (_, v) => String(numVal(vars[v])));
177
+ const result = Function(`"use strict"; return (${subst});`)();
178
+ if (typeof result === "number" || typeof result === "boolean")
179
+ return Number(result);
180
+ }
181
+ catch { /* fall through */ }
182
+ return strVal(vars[expr] ?? expr);
60
183
  }
61
- const out = [];
62
- // BEGIN / END
63
- const beginClause = clauses.find((c) => c.pattern === "BEGIN");
64
- const endClause = clauses.find((c) => c.pattern === "END");
65
- const mainClauses = clauses.filter((c) => c.pattern !== "BEGIN" && c.pattern !== "END");
66
- function splitFields(line) {
67
- if (sep === " ")
68
- return line.trim().split(/\s+/).filter(Boolean);
69
- return line.split(sep);
184
+ function splitCSV(s) {
185
+ const parts = [];
186
+ let cur = "";
187
+ let depth = 0;
188
+ for (let ci = 0; ci < s.length; ci++) {
189
+ const c = s[ci];
190
+ if (c === "(")
191
+ depth++;
192
+ else if (c === ")")
193
+ depth--;
194
+ else if (c === "," && depth === 0) {
195
+ parts.push(cur);
196
+ cur = "";
197
+ continue;
198
+ }
199
+ cur += c;
200
+ }
201
+ parts.push(cur);
202
+ return parts;
70
203
  }
71
- function evalAction(action, line, nr) {
72
- const parts = splitFields(line);
73
- const nf = parts.length;
74
- // Expand variables
75
- const resolve = (expr) => {
76
- expr = expr.trim();
77
- if (expr === "NR")
78
- return String(nr);
79
- if (expr === "NF")
80
- return String(nf);
81
- if (expr === "$0")
82
- return line;
83
- if (expr === "$NF")
84
- return parts[nf - 1] ?? "";
85
- if (/^\$\d+$/.test(expr))
86
- return parts[parseInt(expr.slice(1), 10) - 1] ?? "";
87
- // Arithmetic NR+1, NF-1
88
- const arith = expr.replace(/\bNR\b/g, String(nr)).replace(/\bNF\b/g, String(nf));
89
- if (/^[\d\s+\-*/()]+$/.test(arith)) {
90
- try {
91
- return String(Function(`"use strict"; return (${arith});`)());
204
+ // Execute one statement, return false to stop (next/exit)
205
+ function execStmt(stmt, vars, fields, nr, nf, out) {
206
+ stmt = stmt.trim();
207
+ if (!stmt || stmt.startsWith("#"))
208
+ return "ok";
209
+ // next / exit
210
+ if (stmt === "next")
211
+ return "next";
212
+ if (stmt === "exit" || stmt.startsWith("exit "))
213
+ return "exit";
214
+ // print / printf
215
+ if (stmt === "print" || stmt === "print $0") {
216
+ out.push(fields.join(sep === " " ? " " : sep));
217
+ return "ok";
218
+ }
219
+ if (stmt.startsWith("printf ")) {
220
+ const fmtRest = stmt.slice(7).trim();
221
+ out.push(sprintfAWK(fmtRest, vars, fields, nr, nf));
222
+ return "ok";
223
+ }
224
+ if (stmt.startsWith("print ")) {
225
+ const argStr = stmt.slice(6);
226
+ const parts2 = splitCSV(argStr);
227
+ out.push(parts2.map((p) => strVal(evalExpr(p.trim(), vars, fields, nr, nf))).join("\t"));
228
+ return "ok";
229
+ }
230
+ // delete var / delete arr[k]
231
+ if (stmt.startsWith("delete ")) {
232
+ const key = stmt.slice(7).trim();
233
+ delete vars[key];
234
+ return "ok";
235
+ }
236
+ // sub(re, rep) / sub(re, rep, target) and gsub
237
+ const subM = stmt.match(/^(g?sub)\s*\(\s*\/([^/]*)\//);
238
+ if (subM) {
239
+ const global2 = subM[1] === "gsub";
240
+ const reStr = subM[2];
241
+ const rest2 = stmt.slice(subM[0].length).replace(/^\s*,\s*/, "");
242
+ const parts2 = splitCSV(rest2.replace(/\)\s*$/, ""));
243
+ const rep = strVal(evalExpr(parts2[0]?.trim() ?? '""', vars, fields, nr, nf));
244
+ // target: if 3rd arg is $N, modify field; else modify $0
245
+ const target = parts2[1]?.trim();
246
+ const wholeRec = fields.join(sep === " " ? " " : sep);
247
+ try {
248
+ const re2 = new RegExp(reStr, global2 ? "g" : "");
249
+ if (target && /^\$\d+$/.test(target)) {
250
+ const idx = parseInt(target.slice(1), 10) - 1;
251
+ if (idx >= 0 && idx < fields.length)
252
+ fields[idx] = (fields[idx] ?? "").replace(re2, rep);
253
+ }
254
+ else {
255
+ const replaced = wholeRec.replace(re2, rep);
256
+ const newFields = splitFields(replaced, sep);
257
+ fields.splice(0, fields.length, ...newFields);
258
+ }
259
+ }
260
+ catch { /* ignore */ }
261
+ return "ok";
262
+ }
263
+ // split(str, arr, sep)
264
+ const splitM = stmt.match(/^split\s*\((.+)\)$/);
265
+ if (splitM) {
266
+ const parts2 = splitCSV(splitM[1]);
267
+ const s = strVal(evalExpr(parts2[0]?.trim() ?? "", vars, fields, nr, nf));
268
+ const arrName = parts2[1]?.trim() ?? "arr";
269
+ const fs2 = parts2[2] ? strVal(evalExpr(parts2[2].trim(), vars, fields, nr, nf)) : sep;
270
+ const elems = splitFields(s, fs2);
271
+ for (let ei = 0; ei < elems.length; ei++)
272
+ vars[`${arrName}[${ei + 1}]`] = elems[ei] ?? "";
273
+ vars[arrName] = String(elems.length);
274
+ return "ok";
275
+ }
276
+ // var++ / var--
277
+ const incM = stmt.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*(\+\+|--)$/);
278
+ if (incM) {
279
+ vars[incM[1]] = numVal(vars[incM[1]]) + (incM[2] === "++" ? 1 : -1);
280
+ return "ok";
281
+ }
282
+ // var += / -= / *= / /= / %= expr
283
+ const assignOpM = stmt.match(/^([A-Za-z_][A-Za-z0-9_[\]]*)\s*(\+=|-=|\*=|\/=|%=)\s*(.+)$/);
284
+ if (assignOpM) {
285
+ const cur = numVal(vars[assignOpM[1]]);
286
+ const rhs = numVal(evalExpr(assignOpM[3], vars, fields, nr, nf));
287
+ const op2 = assignOpM[2];
288
+ let res = cur;
289
+ if (op2 === "+=")
290
+ res = cur + rhs;
291
+ else if (op2 === "-=")
292
+ res = cur - rhs;
293
+ else if (op2 === "*=")
294
+ res = cur * rhs;
295
+ else if (op2 === "/=")
296
+ res = rhs !== 0 ? cur / rhs : 0;
297
+ else if (op2 === "%=")
298
+ res = cur % rhs;
299
+ vars[assignOpM[1]] = res;
300
+ return "ok";
301
+ }
302
+ // var = expr (assignment)
303
+ const assignM = stmt.match(/^([A-Za-z_][A-Za-z0-9_[\]]*)\s*=\s*(.+)$/);
304
+ if (assignM) {
305
+ vars[assignM[1]] = evalExpr(assignM[2], vars, fields, nr, nf);
306
+ return "ok";
307
+ }
308
+ // Fallthrough: try as expression (e.g. bare function call)
309
+ evalExpr(stmt, vars, fields, nr, nf);
310
+ return "ok";
311
+ }
312
+ function sprintfAWK(fmtStr, vars, fields, nr, nf) {
313
+ const parts2 = splitCSV(fmtStr);
314
+ const fmt = strVal(evalExpr(parts2[0]?.trim() ?? '""', vars, fields, nr, nf));
315
+ const fmtArgs = parts2.slice(1).map((p) => evalExpr(p.trim(), vars, fields, nr, nf));
316
+ let ai = 0;
317
+ return fmt.replace(/%(-?\d*\.?\d*)?([diouxXeEfgGsq%])/g, (_, spec, type2) => {
318
+ if (type2 === "%")
319
+ return "%";
320
+ const val2 = fmtArgs[ai++];
321
+ const width = spec ? parseInt(spec, 10) : 0;
322
+ let result = "";
323
+ if (type2 === "d" || type2 === "i")
324
+ result = String(Math.trunc(numVal(val2)));
325
+ else if (type2 === "f")
326
+ result = numVal(val2).toFixed(spec?.includes(".") ? parseInt(spec.split(".")[1] ?? "6", 10) : 6);
327
+ else if (type2 === "s" || type2 === "q")
328
+ result = strVal(val2);
329
+ else if (type2 === "x")
330
+ result = Math.trunc(numVal(val2)).toString(16);
331
+ else if (type2 === "X")
332
+ result = Math.trunc(numVal(val2)).toString(16).toUpperCase();
333
+ else if (type2 === "o")
334
+ result = Math.trunc(numVal(val2)).toString(8);
335
+ else
336
+ result = strVal(val2);
337
+ if (width > 0 && result.length < width)
338
+ result = result.padStart(width);
339
+ else if (width < 0 && result.length < -width)
340
+ result = result.padEnd(-width);
341
+ return result;
342
+ });
343
+ }
344
+ const clauses = [];
345
+ const progTrim = prog.trim();
346
+ // Split top-level { } blocks respecting nesting and strings
347
+ {
348
+ let j = 0;
349
+ while (j < progTrim.length) {
350
+ // Skip whitespace
351
+ while (j < progTrim.length && /\s/.test(progTrim[j]))
352
+ j++;
353
+ if (j >= progTrim.length)
354
+ break;
355
+ // Collect pattern (everything before `{`)
356
+ let pat = "";
357
+ while (j < progTrim.length && progTrim[j] !== "{") {
358
+ pat += progTrim[j++];
359
+ }
360
+ pat = pat.trim();
361
+ if (progTrim[j] !== "{") {
362
+ if (pat)
363
+ clauses.push({ pattern: pat, action: "print $0" });
364
+ break;
365
+ }
366
+ j++; // skip {
367
+ // Collect action (balanced braces)
368
+ let action = "";
369
+ let depth = 1;
370
+ while (j < progTrim.length && depth > 0) {
371
+ const c = progTrim[j];
372
+ if (c === "{")
373
+ depth++;
374
+ else if (c === "}") {
375
+ depth--;
376
+ if (depth === 0) {
377
+ j++;
378
+ break;
379
+ }
92
380
  }
93
- catch { }
381
+ action += c;
382
+ j++;
94
383
  }
95
- return expr.replace(/"/g, "");
96
- };
97
- const stmts = action.split(";").map((s) => s.trim()).filter(Boolean);
384
+ clauses.push({ pattern: pat, action: action.trim() });
385
+ }
386
+ }
387
+ if (clauses.length === 0)
388
+ clauses.push({ pattern: "", action: progTrim.replace(/[{}]/g, "").trim() });
389
+ // ── Execute ────────────────────────────────────────────────────────────
390
+ const out = [];
391
+ const vars = { FS: sep, OFS: sep === " " ? " " : sep, ORS: "\n", ...initVars };
392
+ const beginClauses = clauses.filter((c) => c.pattern === "BEGIN");
393
+ const endClauses = clauses.filter((c) => c.pattern === "END");
394
+ const mainClauses = clauses.filter((c) => c.pattern !== "BEGIN" && c.pattern !== "END");
395
+ function runAction(action, fields, nr, nf) {
396
+ // Split action into statements by ; or newline (not inside strings/parens)
397
+ const stmts = splitStmts(action);
98
398
  for (const stmt of stmts) {
99
- if (stmt === "print" || stmt === "print $0") {
100
- out.push(line);
399
+ const res = execStmt(stmt, vars, fields, nr, nf, out);
400
+ if (res !== "ok")
401
+ return res;
402
+ }
403
+ return "ok";
404
+ }
405
+ function splitStmts(action) {
406
+ const stmts = [];
407
+ let cur = "";
408
+ let depth = 0;
409
+ let inStr = false;
410
+ let strCh = "";
411
+ for (let ci = 0; ci < action.length; ci++) {
412
+ const c = action[ci];
413
+ if (!inStr && (c === '"' || c === "'")) {
414
+ inStr = true;
415
+ strCh = c;
416
+ cur += c;
417
+ continue;
418
+ }
419
+ if (inStr && c === strCh) {
420
+ inStr = false;
421
+ cur += c;
422
+ continue;
101
423
  }
102
- else if (stmt.startsWith("print ")) {
103
- const args2 = stmt.slice(6).split(/\s*,\s*/);
104
- out.push(args2.map(resolve).join("\t"));
424
+ if (inStr) {
425
+ cur += c;
426
+ continue;
427
+ }
428
+ if (c === "(" || c === "[")
429
+ depth++;
430
+ else if (c === ")" || c === "]")
431
+ depth--;
432
+ if ((c === ";" || c === "\n") && depth === 0) {
433
+ if (cur.trim())
434
+ stmts.push(cur.trim());
435
+ cur = "";
436
+ }
437
+ else {
438
+ cur += c;
105
439
  }
106
440
  }
441
+ if (cur.trim())
442
+ stmts.push(cur.trim());
443
+ return stmts;
107
444
  }
108
- function matchPattern(pattern, line, nr) {
445
+ function matchClause(pattern, line, fields, nr, nf) {
109
446
  if (!pattern)
110
447
  return true;
111
448
  if (pattern === "1")
112
449
  return true;
113
- // NR==N or NR>N etc.
114
- const nrCond = pattern.match(/^NR\s*([=!<>]=?|==)\s*(\d+)$/);
115
- if (nrCond) {
116
- const op = nrCond[1];
117
- const val = parseInt(nrCond[2], 10);
118
- switch (op) {
119
- case "==": return nr === val;
120
- case "!=": return nr !== val;
121
- case ">": return nr > val;
122
- case ">=": return nr >= val;
123
- case "<": return nr < val;
124
- case "<=": return nr <= val;
125
- }
126
- }
127
- // NR%N==M
128
- const nrMod = pattern.match(/^NR%(\d+)==(\d+)$/);
129
- if (nrMod) {
130
- return nr % parseInt(nrMod[1], 10) === parseInt(nrMod[2], 10);
131
- }
132
- // /regex/ pattern
450
+ if (/^-?\d+$/.test(pattern))
451
+ return numVal(pattern) !== 0;
452
+ // /regex/
133
453
  if (pattern.startsWith("/") && pattern.endsWith("/")) {
134
454
  try {
135
455
  return new RegExp(pattern.slice(1, -1)).test(line);
@@ -138,32 +458,65 @@ export const awkCommand = {
138
458
  return false;
139
459
  }
140
460
  }
461
+ // NR/NF comparisons
462
+ const cmpM = pattern.match(/^(.+?)\s*([=!<>]=?|==)\s*(.+)$/);
463
+ if (cmpM) {
464
+ const lhs = numVal(evalExpr(cmpM[1].trim(), vars, fields, nr, nf));
465
+ const rhs = numVal(evalExpr(cmpM[3].trim(), vars, fields, nr, nf));
466
+ switch (cmpM[2]) {
467
+ case "==": return lhs === rhs;
468
+ case "!=": return lhs !== rhs;
469
+ case ">": return lhs > rhs;
470
+ case ">=": return lhs >= rhs;
471
+ case "<": return lhs < rhs;
472
+ case "<=": return lhs <= rhs;
473
+ }
474
+ }
141
475
  // $N~/regex/
142
- const fieldMatch = pattern.match(/^\$(\d+)~\/(.*)\/$/);
476
+ const fieldMatch = pattern.match(/^\$(\w+)\s*~\s*\/(.*)\/$/);
143
477
  if (fieldMatch) {
144
- const parts = splitFields(line);
145
- const field = parts[parseInt(fieldMatch[1], 10) - 1] ?? "";
478
+ const fv = strVal(evalExpr(`$${fieldMatch[1]}`, vars, fields, nr, nf));
146
479
  try {
147
- return new RegExp(fieldMatch[2]).test(field);
480
+ return new RegExp(fieldMatch[2]).test(fv);
148
481
  }
149
482
  catch {
150
483
  return false;
151
484
  }
152
485
  }
153
- return false;
486
+ // Boolean expr via evalExpr
487
+ const v = evalExpr(pattern, vars, fields, nr, nf);
488
+ return numVal(v) !== 0 || (typeof v === "string" && v !== "");
154
489
  }
155
- if (beginClause)
156
- evalAction(beginClause.action, "", 0);
157
- for (let nr = 1; nr <= lines.length; nr++) {
158
- const line = lines[nr - 1];
490
+ // BEGIN
491
+ for (const c of beginClauses)
492
+ runAction(c.action, [], 0, 0);
493
+ const lines = input.split("\n");
494
+ if (lines[lines.length - 1] === "")
495
+ lines.pop();
496
+ let stopped = false;
497
+ for (let li = 0; li < lines.length && !stopped; li++) {
498
+ const line = lines[li];
499
+ vars.NR = li + 1;
500
+ const fields = splitFields(line, sep);
501
+ vars.NF = fields.length;
502
+ const nr = li + 1;
503
+ const nf = fields.length;
159
504
  for (const clause of mainClauses) {
160
- if (matchPattern(clause.pattern, line, nr)) {
161
- evalAction(clause.action, line, nr);
505
+ if (!matchClause(clause.pattern, line, fields, nr, nf))
506
+ continue;
507
+ const res = runAction(clause.action, fields, nr, nf);
508
+ if (res === "next")
509
+ break;
510
+ if (res === "exit") {
511
+ stopped = true;
512
+ break;
162
513
  }
163
514
  }
164
515
  }
165
- if (endClause)
166
- evalAction(endClause.action, "", lines.length + 1);
167
- return { stdout: out.join("\n") + (out.length > 0 ? "\n" : ""), exitCode: 0 };
516
+ // END
517
+ for (const c of endClauses)
518
+ runAction(c.action, [], numVal(vars.NR), 0);
519
+ const output = out.join("\n");
520
+ return { stdout: output + (output && !output.endsWith("\n") ? "\n" : ""), exitCode: 0 };
168
521
  },
169
522
  };
@@ -0,0 +1,2 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const bcCommand: ShellModule;
@@ -0,0 +1,28 @@
1
+ import { evalArith } from "../utils/expand";
2
+ export const bcCommand = {
3
+ name: "bc",
4
+ description: "Arbitrary precision calculator language",
5
+ category: "system",
6
+ params: ["[expression]"],
7
+ run: ({ args, stdin }) => {
8
+ const input = (stdin ?? args.join(" ")).trim();
9
+ if (!input)
10
+ return { stdout: "", exitCode: 0 };
11
+ const results = [];
12
+ for (const line of input.split("\n")) {
13
+ const expr = line.trim();
14
+ if (!expr || expr.startsWith("#"))
15
+ continue;
16
+ // Strip trailing semicolons
17
+ const cleaned = expr.replace(/;+$/, "").trim();
18
+ const val = evalArith(cleaned, {});
19
+ if (!Number.isNaN(val)) {
20
+ results.push(String(val));
21
+ }
22
+ else {
23
+ return { stderr: `bc: syntax error on line: ${expr}`, exitCode: 1 };
24
+ }
25
+ }
26
+ return { stdout: results.join("\n"), exitCode: 0 };
27
+ },
28
+ };