@poe-platform/safe-bash 0.1.74 → 0.1.76
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.
- package/dist/safe-bash/browser.js +435 -176
- package/dist/safe-bash/browser.js.map +4 -4
- package/dist/safe-bash/commands/structured/limits.js +3 -1
- package/dist/safe-bash/commands/structured/limits.js.map +1 -1
- package/dist/safe-bash/commands/structured/split.d.ts.map +1 -1
- package/dist/safe-bash/commands/structured/split.js +7 -2
- package/dist/safe-bash/commands/structured/split.js.map +1 -1
- package/dist/safe-bash/commands/structured/values.d.ts.map +1 -1
- package/dist/safe-bash/commands/structured/values.js +4 -2
- package/dist/safe-bash/commands/structured/values.js.map +1 -1
- package/dist/safe-bash/commands/text-programs/awk-reader.d.ts +25 -0
- package/dist/safe-bash/commands/text-programs/awk-reader.d.ts.map +1 -0
- package/dist/safe-bash/commands/text-programs/awk-reader.js +180 -0
- package/dist/safe-bash/commands/text-programs/awk-reader.js.map +1 -0
- package/dist/safe-bash/commands/text-programs/awk-retention.d.ts +11 -0
- package/dist/safe-bash/commands/text-programs/awk-retention.d.ts.map +1 -0
- package/dist/safe-bash/commands/text-programs/awk-retention.js +31 -0
- package/dist/safe-bash/commands/text-programs/awk-retention.js.map +1 -0
- package/dist/safe-bash/commands/text-programs/awk-runtime.d.ts +11 -1
- package/dist/safe-bash/commands/text-programs/awk-runtime.d.ts.map +1 -1
- package/dist/safe-bash/commands/text-programs/awk-runtime.js +291 -191
- package/dist/safe-bash/commands/text-programs/awk-runtime.js.map +1 -1
- package/dist/safe-bash/commands/text-programs/awk.d.ts.map +1 -1
- package/dist/safe-bash/commands/text-programs/awk.js +2 -1
- package/dist/safe-bash/commands/text-programs/awk.js.map +1 -1
- package/dist/safe-bash/commands/text.d.ts.map +1 -1
- package/dist/safe-bash/commands/text.js +194 -52
- package/dist/safe-bash/commands/text.js.map +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.d.ts +2 -0
- package/dist/safe-bash/shell/arithmetic-parameters.d.ts.map +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.js +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.js.map +1 -1
- package/dist/safe-bash/shell/arithmetic.d.ts +4 -3
- package/dist/safe-bash/shell/arithmetic.d.ts.map +1 -1
- package/dist/safe-bash/shell/arithmetic.js +19 -7
- package/dist/safe-bash/shell/arithmetic.js.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.d.ts +6 -5
- package/dist/safe-bash/shell/arrays/syntax.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.js +22 -12
- package/dist/safe-bash/shell/arrays/syntax.js.map +1 -1
- package/dist/safe-bash/shell/index.d.ts +1 -1
- package/dist/safe-bash/shell/index.d.ts.map +1 -1
- package/dist/safe-bash/shell/parse-budget.d.ts +10 -0
- package/dist/safe-bash/shell/parse-budget.d.ts.map +1 -0
- package/dist/safe-bash/shell/parse-budget.js +29 -0
- package/dist/safe-bash/shell/parse-budget.js.map +1 -0
- package/dist/safe-bash/shell/parser.d.ts +6 -4
- package/dist/safe-bash/shell/parser.d.ts.map +1 -1
- package/dist/safe-bash/shell/parser.js +80 -29
- package/dist/safe-bash/shell/parser.js.map +1 -1
- package/dist/safe-bash/shell/runtime.d.ts +3 -0
- package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
- package/dist/safe-bash/shell/runtime.js +136 -78
- package/dist/safe-bash/shell/runtime.js.map +1 -1
- package/dist/safe-bash/shell/shell.js +2 -2
- package/dist/safe-bash/shell/shell.js.map +1 -1
- package/dist/safe-bash/shell/types.d.ts +4 -0
- package/dist/safe-bash/shell/types.d.ts.map +1 -1
- package/dist/safe-bash/shell/types.js.map +1 -1
- package/dist/safe-bash/shell/worker-limits.d.ts.map +1 -1
- package/dist/safe-bash/shell/worker-limits.js +1 -0
- package/dist/safe-bash/shell/worker-limits.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { FsError
|
|
1
|
+
import { FsError } from "../../contracts/index.js";
|
|
2
2
|
import { decodeString } from "./awk-syntax.js";
|
|
3
3
|
import { AwkArray, compare, formatted, inputValue, number, numeric, scalar, string, text, truth, unset } from "./awk-values.js";
|
|
4
4
|
import { Pattern, substitute } from "./regex.js";
|
|
5
5
|
import { Budget, ProgramError, byteString, bytes, input, virtualPath, write } from "./shared.js";
|
|
6
|
+
import { AwkRetention } from "./awk-retention.js";
|
|
7
|
+
import { Reader } from "./awk-reader.js";
|
|
8
|
+
function textSize(value) {
|
|
9
|
+
return value && !(value instanceof AwkArray) && (value.kind === "string" || value.kind === "numeric") ? value.text.length : 0;
|
|
10
|
+
}
|
|
11
|
+
function ownScalar(value) {
|
|
12
|
+
return value.kind === "string" || value.kind === "numeric"
|
|
13
|
+
? { ...value, text: Buffer.from(value.text, "latin1").toString("latin1") } : value;
|
|
14
|
+
}
|
|
6
15
|
class Flow {
|
|
7
16
|
kind;
|
|
8
17
|
value;
|
|
@@ -11,106 +20,88 @@ class Flow {
|
|
|
11
20
|
this.value = value;
|
|
12
21
|
}
|
|
13
22
|
}
|
|
14
|
-
class Reader {
|
|
15
|
-
budget;
|
|
16
|
-
iterator;
|
|
17
|
-
buffer = "";
|
|
18
|
-
ended = false;
|
|
19
|
-
constructor(source, budget) {
|
|
20
|
-
this.budget = budget;
|
|
21
|
-
this.iterator = source[Symbol.asyncIterator]();
|
|
22
|
-
}
|
|
23
|
-
async fill() {
|
|
24
|
-
this.budget.step();
|
|
25
|
-
const next = await this.iterator.next();
|
|
26
|
-
if (next.done)
|
|
27
|
-
this.ended = true;
|
|
28
|
-
else {
|
|
29
|
-
if (next.value.byteLength > this.budget.maxBufferBytes - this.buffer.length)
|
|
30
|
-
throw new ProgramError("text buffer limit exceeded");
|
|
31
|
-
this.buffer = this.budget.check(this.buffer + Buffer.from(next.value).toString("latin1"));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
async read(separator) {
|
|
35
|
-
if (separator.length > 1)
|
|
36
|
-
throw new ProgramError("RS must be one byte or empty for paragraph records");
|
|
37
|
-
while (true) {
|
|
38
|
-
this.budget.step();
|
|
39
|
-
if (separator === "") {
|
|
40
|
-
this.buffer = this.buffer.replace(/^\n+/u, "");
|
|
41
|
-
const start = this.buffer.indexOf("\n\n");
|
|
42
|
-
if (start >= 0) {
|
|
43
|
-
let end = start + 2;
|
|
44
|
-
while (this.buffer[end] === "\n")
|
|
45
|
-
end++;
|
|
46
|
-
if (end === this.buffer.length && !this.ended) {
|
|
47
|
-
await this.fill();
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const record = this.buffer.slice(0, start);
|
|
51
|
-
this.buffer = this.buffer.slice(end);
|
|
52
|
-
return record;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
const end = this.buffer.indexOf(separator);
|
|
57
|
-
if (end >= 0) {
|
|
58
|
-
const record = this.buffer.slice(0, end);
|
|
59
|
-
this.buffer = this.buffer.slice(end + 1);
|
|
60
|
-
return record;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (this.ended) {
|
|
64
|
-
if (!this.buffer)
|
|
65
|
-
return undefined;
|
|
66
|
-
const record = separator === "" ? this.buffer.replace(/\n+$/u, "") : this.buffer;
|
|
67
|
-
this.buffer = "";
|
|
68
|
-
return record;
|
|
69
|
-
}
|
|
70
|
-
await this.fill();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
async close() { await this.iterator.return?.(); this.buffer = ""; this.ended = true; }
|
|
74
|
-
}
|
|
75
23
|
export class AwkRuntime {
|
|
76
24
|
program;
|
|
77
25
|
context;
|
|
78
26
|
budget;
|
|
27
|
+
retention;
|
|
79
28
|
variables = new Map();
|
|
80
29
|
frames = [];
|
|
30
|
+
arrays = new Map();
|
|
81
31
|
regexes = new Map();
|
|
82
32
|
outputs = new Set();
|
|
83
33
|
inputs = new Map();
|
|
34
|
+
mainReader;
|
|
84
35
|
fields = [];
|
|
36
|
+
fieldBytes = 0;
|
|
85
37
|
record = "";
|
|
86
38
|
entries = 0;
|
|
87
39
|
phase = "BEGIN";
|
|
88
40
|
status = 0;
|
|
89
|
-
constructor(program, context, budget, args, assignments, separator) {
|
|
41
|
+
constructor(program, context, budget, retention, args, assignments, separator) {
|
|
90
42
|
this.program = program;
|
|
91
43
|
this.context = context;
|
|
92
44
|
this.budget = budget;
|
|
45
|
+
this.retention = retention;
|
|
93
46
|
const defaults = { FS: string(" "), RS: string("\n"), OFS: string(" "), ORS: string("\n"), OFMT: string("%.6g"), CONVFMT: string("%.6g"), SUBSEP: string("\x1c"), NR: numeric(0), FNR: numeric(0), NF: numeric(0), FILENAME: string(""), RSTART: numeric(0), RLENGTH: numeric(0), ARGC: numeric(args.length + 1) };
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
47
|
+
try {
|
|
48
|
+
for (const [name, value] of Object.entries(defaults))
|
|
49
|
+
this.storeScalar(this.variables, name, value);
|
|
50
|
+
const environment = this.array("ENVIRON");
|
|
51
|
+
for (const [name, value] of Object.entries(context.env))
|
|
52
|
+
this.arraySet(environment, byteString(name), inputValue(byteString(value)));
|
|
53
|
+
const argv = this.array("ARGV");
|
|
54
|
+
this.arraySet(argv, "0", string("awk"));
|
|
55
|
+
args.forEach((argument, index) => this.arraySet(argv, String(index + 1), inputValue(byteString(argument))));
|
|
56
|
+
if (separator !== undefined)
|
|
57
|
+
this.set("FS", string(separator));
|
|
58
|
+
for (const assignment of assignments)
|
|
59
|
+
this.assignment(assignment);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
this.releaseStore(this.variables);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
108
65
|
}
|
|
109
66
|
store(name) { return this.frames.at(-1)?.has(name) ? this.frames.at(-1) : this.variables; }
|
|
110
67
|
get(name) { return this.store(name).get(name) ?? unset; }
|
|
111
68
|
getScalar(name) { return scalar(this.get(name)); }
|
|
112
69
|
asText(value) { return text(value, text(this.getScalar("CONVFMT"))); }
|
|
113
70
|
varText(name) { return this.asText(this.getScalar(name)); }
|
|
71
|
+
retainName(path) {
|
|
72
|
+
this.context.signal.throwIfAborted();
|
|
73
|
+
return this.retention.replace(0, Buffer.byteLength(path, "utf8"), () => Buffer.from(path, "utf16le").toString("utf16le"));
|
|
74
|
+
}
|
|
75
|
+
storeScalar(store, name, value) {
|
|
76
|
+
if (value.kind === "string" || value.kind === "numeric")
|
|
77
|
+
this.budget.check(value.text);
|
|
78
|
+
const owned = this.retention.replace(textSize(store.get(name)), textSize(value), () => ownScalar(value));
|
|
79
|
+
store.set(name, owned);
|
|
80
|
+
}
|
|
81
|
+
bindArray(store, name, array) {
|
|
82
|
+
let allocation = this.arrays.get(array);
|
|
83
|
+
if (!allocation) {
|
|
84
|
+
allocation = { bytes: 0, references: 0 };
|
|
85
|
+
this.arrays.set(array, allocation);
|
|
86
|
+
}
|
|
87
|
+
allocation.references++;
|
|
88
|
+
store.set(name, array);
|
|
89
|
+
}
|
|
90
|
+
releaseStore(store) {
|
|
91
|
+
for (const value of store.values()) {
|
|
92
|
+
if (value instanceof AwkArray) {
|
|
93
|
+
const allocation = this.arrays.get(value);
|
|
94
|
+
if (--allocation.references === 0) {
|
|
95
|
+
this.retention.release(allocation.bytes);
|
|
96
|
+
this.entries -= value.entries.size;
|
|
97
|
+
this.arrays.delete(value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
this.retention.release(textSize(value));
|
|
102
|
+
}
|
|
103
|
+
store.clear();
|
|
104
|
+
}
|
|
114
105
|
set(name, value) {
|
|
115
106
|
if (this.get(name) instanceof AwkArray)
|
|
116
107
|
throw new ProgramError(`cannot assign a scalar to array '${name}'`);
|
|
@@ -118,15 +109,13 @@ export class AwkRuntime {
|
|
|
118
109
|
const length = Math.trunc(number(value));
|
|
119
110
|
if (!Number.isSafeInteger(length) || length < 0 || length > 100000)
|
|
120
111
|
throw new ProgramError("invalid or excessive NF");
|
|
121
|
-
|
|
122
|
-
while (
|
|
123
|
-
|
|
124
|
-
this.rebuild();
|
|
112
|
+
const fields = this.fields.slice(0, length);
|
|
113
|
+
while (fields.length < length)
|
|
114
|
+
fields.push(unset);
|
|
115
|
+
this.rebuild(fields);
|
|
125
116
|
return;
|
|
126
117
|
}
|
|
127
|
-
|
|
128
|
-
this.budget.check(value.text);
|
|
129
|
-
this.store(name).set(name, value);
|
|
118
|
+
this.storeScalar(this.store(name), name, value);
|
|
130
119
|
}
|
|
131
120
|
array(name) {
|
|
132
121
|
const value = this.get(name);
|
|
@@ -135,14 +124,24 @@ export class AwkRuntime {
|
|
|
135
124
|
if (value.kind !== "unset")
|
|
136
125
|
throw new ProgramError(`scalar '${name}' used as an array`);
|
|
137
126
|
const array = new AwkArray();
|
|
138
|
-
this.store(name)
|
|
127
|
+
this.bindArray(this.store(name), name, array);
|
|
139
128
|
return array;
|
|
140
129
|
}
|
|
141
130
|
arraySet(array, key, value) {
|
|
142
131
|
this.budget.check(key);
|
|
143
|
-
if (
|
|
132
|
+
if (value.kind === "string" || value.kind === "numeric")
|
|
133
|
+
this.budget.check(value.text);
|
|
134
|
+
const existing = array.entries.has(key);
|
|
135
|
+
if (!existing && this.entries >= 100000)
|
|
144
136
|
throw new ProgramError("array entry limit exceeded");
|
|
145
|
-
array.entries.
|
|
137
|
+
const previous = textSize(array.entries.get(key)), next = textSize(value) + (existing ? 0 : key.length);
|
|
138
|
+
const owned = this.retention.replace(previous, next, () => ({
|
|
139
|
+
key: existing ? key : Buffer.from(key, "latin1").toString("latin1"), value: ownScalar(value),
|
|
140
|
+
}));
|
|
141
|
+
array.entries.set(owned.key, owned.value);
|
|
142
|
+
this.arrays.get(array).bytes += next - previous;
|
|
143
|
+
if (!existing)
|
|
144
|
+
this.entries++;
|
|
146
145
|
}
|
|
147
146
|
pattern(source) {
|
|
148
147
|
let pattern = this.regexes.get(source);
|
|
@@ -238,9 +237,25 @@ export class AwkRuntime {
|
|
|
238
237
|
return parts;
|
|
239
238
|
}
|
|
240
239
|
async setRecord(record) {
|
|
241
|
-
this.
|
|
242
|
-
|
|
243
|
-
this.
|
|
240
|
+
this.budget.check(record);
|
|
241
|
+
const fields = await this.split(record, this.varText("FS"), this.varText("RS") === "");
|
|
242
|
+
this.replaceRecord(record, fields);
|
|
243
|
+
}
|
|
244
|
+
replaceRecord(record, fields) {
|
|
245
|
+
let fieldBytes = 0;
|
|
246
|
+
for (const field of fields) {
|
|
247
|
+
if (field.kind === "string" || field.kind === "numeric")
|
|
248
|
+
this.budget.check(field.text);
|
|
249
|
+
fieldBytes += textSize(field);
|
|
250
|
+
}
|
|
251
|
+
const owned = this.retention.replace(this.record.length + this.fieldBytes, record.length + fieldBytes, () => ({
|
|
252
|
+
record: Buffer.from(record, "latin1").toString("latin1"),
|
|
253
|
+
fields: fields.map((field, index) => field === this.fields[index] ? field : ownScalar(field)),
|
|
254
|
+
}));
|
|
255
|
+
this.record = owned.record;
|
|
256
|
+
this.fields = owned.fields;
|
|
257
|
+
this.fieldBytes = fieldBytes;
|
|
258
|
+
this.variables.set("NF", numeric(fields.length));
|
|
244
259
|
}
|
|
245
260
|
join(parts, separator, suffix = "") {
|
|
246
261
|
this.budget.step(parts.length + 1);
|
|
@@ -261,9 +276,9 @@ export class AwkRuntime {
|
|
|
261
276
|
this.budget.step(this.budget.maxBufferBytes - remaining);
|
|
262
277
|
return parts.join(separator) + suffix;
|
|
263
278
|
}
|
|
264
|
-
rebuild() {
|
|
265
|
-
|
|
266
|
-
this.
|
|
279
|
+
rebuild(fields) {
|
|
280
|
+
const record = this.join(fields.map(value => this.asText(value)), this.varText("OFS"));
|
|
281
|
+
this.replaceRecord(record, fields);
|
|
267
282
|
}
|
|
268
283
|
async key(items) {
|
|
269
284
|
const pieces = [];
|
|
@@ -283,10 +298,11 @@ export class AwkRuntime {
|
|
|
283
298
|
set: value => {
|
|
284
299
|
if (index === 0)
|
|
285
300
|
return this.setRecord(this.asText(value));
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
301
|
+
const fields = this.fields.slice();
|
|
302
|
+
while (fields.length < index)
|
|
303
|
+
fields.push(unset);
|
|
304
|
+
fields[index - 1] = value;
|
|
305
|
+
this.rebuild(fields);
|
|
290
306
|
},
|
|
291
307
|
};
|
|
292
308
|
}
|
|
@@ -389,16 +405,24 @@ export class AwkRuntime {
|
|
|
389
405
|
if (this.inputs.size >= 256)
|
|
390
406
|
throw new ProgramError("getline open-file limit exceeded");
|
|
391
407
|
const { context, budget } = this;
|
|
408
|
+
const name = this.retainName(path);
|
|
409
|
+
const useStdin = file === "-";
|
|
392
410
|
const source = (async function* () {
|
|
393
|
-
if (
|
|
411
|
+
if (useStdin)
|
|
394
412
|
yield* context.stdin;
|
|
395
413
|
else if (context.fs.readStream)
|
|
396
|
-
yield* context.fs.readStream(
|
|
414
|
+
yield* context.fs.readStream(name, { signal: context.signal });
|
|
397
415
|
else
|
|
398
|
-
yield await context.fs.readFile(
|
|
416
|
+
yield await context.fs.readFile(name, { signal: context.signal, maxBytes: budget.maxBufferBytes });
|
|
399
417
|
})();
|
|
400
|
-
|
|
401
|
-
|
|
418
|
+
try {
|
|
419
|
+
reader = new Reader(source, budget, this.retention);
|
|
420
|
+
this.inputs.set(name, reader);
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
this.retention.release(Buffer.byteLength(name, "utf8"));
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
402
426
|
}
|
|
403
427
|
let record;
|
|
404
428
|
try {
|
|
@@ -409,6 +433,7 @@ export class AwkRuntime {
|
|
|
409
433
|
if (!(error instanceof FsError))
|
|
410
434
|
throw error;
|
|
411
435
|
this.inputs.delete(path);
|
|
436
|
+
this.retention.release(Buffer.byteLength(path, "utf8"));
|
|
412
437
|
await reader.close();
|
|
413
438
|
this.set("ERRNO", string(byteString(error.message)));
|
|
414
439
|
return numeric(-1);
|
|
@@ -427,16 +452,27 @@ export class AwkRuntime {
|
|
|
427
452
|
if (this.frames.length >= 64)
|
|
428
453
|
throw new ProgramError("function recursion limit exceeded");
|
|
429
454
|
const frame = new Map();
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
|
|
455
|
+
try {
|
|
456
|
+
for (let index = 0; index < definition.parameters.length; index++) {
|
|
457
|
+
const parameter = definition.parameters[index];
|
|
458
|
+
const argument = args[index];
|
|
459
|
+
if (definition.arrays.has(parameter)) {
|
|
460
|
+
if (argument !== undefined && argument.kind !== "variable")
|
|
461
|
+
throw new ProgramError("array parameter requires an array variable");
|
|
462
|
+
this.bindArray(frame, parameter, argument ? this.array(argument.name) : new AwkArray());
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
const value = argument ? await this.evaluate(argument) : unset;
|
|
466
|
+
if (value instanceof AwkArray)
|
|
467
|
+
this.bindArray(frame, parameter, value);
|
|
468
|
+
else
|
|
469
|
+
this.storeScalar(frame, parameter, value);
|
|
470
|
+
}
|
|
437
471
|
}
|
|
438
|
-
|
|
439
|
-
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
this.releaseStore(frame);
|
|
475
|
+
throw error;
|
|
440
476
|
}
|
|
441
477
|
this.frames.push(frame);
|
|
442
478
|
try {
|
|
@@ -450,6 +486,7 @@ export class AwkRuntime {
|
|
|
450
486
|
}
|
|
451
487
|
finally {
|
|
452
488
|
this.frames.pop();
|
|
489
|
+
this.releaseStore(frame);
|
|
453
490
|
}
|
|
454
491
|
}
|
|
455
492
|
if (name === "length") {
|
|
@@ -472,9 +509,24 @@ export class AwkRuntime {
|
|
|
472
509
|
const target = this.array(args[1].name);
|
|
473
510
|
const separator = args[2]?.kind === "regex" ? args[2].pattern : args[2] ? this.asText(await this.scalarExpression(args[2])) : this.varText("FS");
|
|
474
511
|
const parts = await this.split(value, separator);
|
|
475
|
-
this.entries
|
|
512
|
+
if (this.entries - target.entries.size + parts.length > 100000)
|
|
513
|
+
throw new ProgramError("array entry limit exceeded");
|
|
514
|
+
let size = 0;
|
|
515
|
+
for (let index = 0; index < parts.length; index++) {
|
|
516
|
+
const part = parts[index];
|
|
517
|
+
if (part.kind === "string" || part.kind === "numeric")
|
|
518
|
+
this.budget.check(part.text);
|
|
519
|
+
const key = String(index + 1);
|
|
520
|
+
this.budget.check(key);
|
|
521
|
+
size += key.length + textSize(part);
|
|
522
|
+
}
|
|
523
|
+
const allocation = this.arrays.get(target);
|
|
524
|
+
const entries = this.retention.replace(allocation.bytes, size, () => parts.map((part, index) => [String(index + 1), ownScalar(part)]));
|
|
525
|
+
this.entries += entries.length - target.entries.size;
|
|
476
526
|
target.entries.clear();
|
|
477
|
-
|
|
527
|
+
for (const [key, part] of entries)
|
|
528
|
+
target.entries.set(key, part);
|
|
529
|
+
allocation.bytes = size;
|
|
478
530
|
return numeric(parts.length);
|
|
479
531
|
}
|
|
480
532
|
if (name === "match") {
|
|
@@ -505,8 +557,13 @@ export class AwkRuntime {
|
|
|
505
557
|
const path = virtualPath(this.context, Buffer.from(this.asText(first), "latin1").toString("utf8"));
|
|
506
558
|
const reader = this.inputs.get(path);
|
|
507
559
|
this.inputs.delete(path);
|
|
560
|
+
if (reader)
|
|
561
|
+
this.retention.release(Buffer.byteLength(path, "utf8"));
|
|
508
562
|
await reader?.close();
|
|
509
|
-
|
|
563
|
+
const output = this.outputs.delete(path);
|
|
564
|
+
if (output)
|
|
565
|
+
this.retention.release(Buffer.byteLength(path, "utf8"));
|
|
566
|
+
return numeric(output || reader !== undefined ? 0 : -1);
|
|
510
567
|
}
|
|
511
568
|
const amount = number(first);
|
|
512
569
|
const result = name === "int" ? Math.trunc(amount) : name === "sqrt" ? Math.sqrt(amount) : name === "exp" ? Math.exp(amount) : name === "log" ? Math.log(amount) : name === "sin" ? Math.sin(amount) : name === "cos" ? Math.cos(amount) : name === "atan2" ? Math.atan2(amount, number(values[1])) : NaN;
|
|
@@ -541,8 +598,16 @@ export class AwkRuntime {
|
|
|
541
598
|
if (this.outputs.has(path))
|
|
542
599
|
await this.context.fs.appendFile(path, bytes(output), { signal: this.context.signal });
|
|
543
600
|
else {
|
|
544
|
-
|
|
545
|
-
|
|
601
|
+
const name = this.retainName(path);
|
|
602
|
+
try {
|
|
603
|
+
await this.context.fs.writeFile(name, bytes(output), { flag: statement.redirect.append ? "a" : "w", signal: this.context.signal });
|
|
604
|
+
this.context.signal.throwIfAborted();
|
|
605
|
+
this.outputs.add(name);
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
this.retention.release(Buffer.byteLength(name, "utf8"));
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
546
611
|
}
|
|
547
612
|
return;
|
|
548
613
|
}
|
|
@@ -559,12 +624,23 @@ export class AwkRuntime {
|
|
|
559
624
|
}
|
|
560
625
|
case "delete": {
|
|
561
626
|
const array = this.array(statement.target.name);
|
|
627
|
+
const allocation = this.arrays.get(array);
|
|
562
628
|
if (statement.target.kind === "variable") {
|
|
629
|
+
this.retention.release(allocation.bytes);
|
|
630
|
+
allocation.bytes = 0;
|
|
563
631
|
this.entries -= array.entries.size;
|
|
564
632
|
array.entries.clear();
|
|
565
633
|
}
|
|
566
|
-
else
|
|
567
|
-
this.
|
|
634
|
+
else {
|
|
635
|
+
const key = await this.key(statement.target.indexes);
|
|
636
|
+
const value = array.entries.get(key);
|
|
637
|
+
if (array.entries.delete(key)) {
|
|
638
|
+
const size = key.length + textSize(value);
|
|
639
|
+
this.retention.release(size);
|
|
640
|
+
allocation.bytes -= size;
|
|
641
|
+
this.entries--;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
568
644
|
return;
|
|
569
645
|
}
|
|
570
646
|
case "foreach": {
|
|
@@ -628,14 +704,36 @@ export class AwkRuntime {
|
|
|
628
704
|
}
|
|
629
705
|
}
|
|
630
706
|
async run() {
|
|
707
|
+
let status = 0, failed = false;
|
|
708
|
+
let failure;
|
|
631
709
|
try {
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
finally {
|
|
635
|
-
const readers = [...this.inputs.values()];
|
|
636
|
-
this.inputs.clear();
|
|
637
|
-
await Promise.all(readers.map(reader => reader.close()));
|
|
710
|
+
status = await this.runProgram();
|
|
638
711
|
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
failed = true;
|
|
714
|
+
failure = error;
|
|
715
|
+
}
|
|
716
|
+
const readers = [...this.mainReader ? [this.mainReader] : [], ...this.inputs.values()];
|
|
717
|
+
this.mainReader = undefined;
|
|
718
|
+
for (const name of this.inputs.keys())
|
|
719
|
+
this.retention.release(Buffer.byteLength(name, "utf8"));
|
|
720
|
+
this.inputs.clear();
|
|
721
|
+
const cleanup = await Promise.allSettled(readers.map(async (reader) => { await reader.close(); }));
|
|
722
|
+
for (const name of this.outputs)
|
|
723
|
+
this.retention.release(Buffer.byteLength(name, "utf8"));
|
|
724
|
+
this.outputs.clear();
|
|
725
|
+
this.releaseStore(this.variables);
|
|
726
|
+
this.retention.release(this.record.length + this.fieldBytes);
|
|
727
|
+
this.record = "";
|
|
728
|
+
this.fields = [];
|
|
729
|
+
this.fieldBytes = 0;
|
|
730
|
+
this.context.signal.throwIfAborted();
|
|
731
|
+
if (failed)
|
|
732
|
+
throw failure;
|
|
733
|
+
for (const result of cleanup)
|
|
734
|
+
if (result.status === "rejected")
|
|
735
|
+
throw result.reason;
|
|
736
|
+
return status;
|
|
639
737
|
}
|
|
640
738
|
async runProgram() {
|
|
641
739
|
let stopped = false;
|
|
@@ -657,80 +755,82 @@ export class AwkRuntime {
|
|
|
657
755
|
let sawFile = false;
|
|
658
756
|
let defaultUsed = false;
|
|
659
757
|
const ranges = new Set();
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
continue;
|
|
676
|
-
}
|
|
677
|
-
file = next;
|
|
678
|
-
sawFile = true;
|
|
679
|
-
break;
|
|
680
|
-
}
|
|
681
|
-
if (file === undefined && !sawFile && !defaultUsed) {
|
|
682
|
-
file = "-";
|
|
683
|
-
defaultUsed = true;
|
|
758
|
+
if (!stopped && (this.program.rules.length || this.program.end.length))
|
|
759
|
+
while (true) {
|
|
760
|
+
this.budget.step();
|
|
761
|
+
if (!reader) {
|
|
762
|
+
let file;
|
|
763
|
+
while (argument < number(this.getScalar("ARGC"))) {
|
|
764
|
+
this.budget.step();
|
|
765
|
+
if (argument > 100000)
|
|
766
|
+
throw new ProgramError("argument count limit exceeded");
|
|
767
|
+
const next = this.asText(this.array("ARGV").entries.get(String(argument++)) ?? unset);
|
|
768
|
+
if (!next)
|
|
769
|
+
continue;
|
|
770
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/u.test(next)) {
|
|
771
|
+
this.assignment(next);
|
|
772
|
+
continue;
|
|
684
773
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
this.set("FNR", numeric(0));
|
|
689
|
-
reader = new Reader(input(this.context, Buffer.from(file, "latin1").toString("utf8")), this.budget);
|
|
774
|
+
file = next;
|
|
775
|
+
sawFile = true;
|
|
776
|
+
break;
|
|
690
777
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
reader = undefined;
|
|
695
|
-
continue;
|
|
778
|
+
if (file === undefined && !sawFile && !defaultUsed) {
|
|
779
|
+
file = "-";
|
|
780
|
+
defaultUsed = true;
|
|
696
781
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
782
|
+
if (file === undefined)
|
|
783
|
+
break;
|
|
784
|
+
this.set("FILENAME", string(file));
|
|
785
|
+
this.set("FNR", numeric(0));
|
|
786
|
+
reader = new Reader(input(this.context, Buffer.from(file, "latin1").toString("utf8")), this.budget, this.retention);
|
|
787
|
+
this.mainReader = reader;
|
|
788
|
+
}
|
|
789
|
+
const record = await reader.read(this.varText("RS"));
|
|
790
|
+
if (record === undefined) {
|
|
791
|
+
await reader.close();
|
|
792
|
+
reader = undefined;
|
|
793
|
+
this.mainReader = undefined;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
this.set("NR", numeric(number(this.getScalar("NR")) + 1));
|
|
797
|
+
this.set("FNR", numeric(number(this.getScalar("FNR")) + 1));
|
|
798
|
+
await this.setRecord(record);
|
|
799
|
+
try {
|
|
800
|
+
for (let index = 0; index < this.program.rules.length; index++) {
|
|
801
|
+
const rule = this.program.rules[index];
|
|
802
|
+
const selected = !rule.pattern || ranges.has(index) || truth(await this.scalarExpression(rule.pattern));
|
|
803
|
+
if (!selected)
|
|
804
|
+
continue;
|
|
805
|
+
if (rule.end) {
|
|
806
|
+
if (truth(await this.scalarExpression(rule.end)))
|
|
807
|
+
ranges.delete(index);
|
|
808
|
+
else
|
|
809
|
+
ranges.add(index);
|
|
713
810
|
}
|
|
811
|
+
await this.execute(rule.action);
|
|
714
812
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
if (error.kind === "nextfile") {
|
|
723
|
-
await reader.close();
|
|
724
|
-
reader = undefined;
|
|
725
|
-
}
|
|
726
|
-
else if (error.kind !== "next")
|
|
727
|
-
throw error;
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
if (!(error instanceof Flow))
|
|
816
|
+
throw error;
|
|
817
|
+
if (error.kind === "exit") {
|
|
818
|
+
this.exit(error);
|
|
819
|
+
break;
|
|
728
820
|
}
|
|
821
|
+
if (error.kind === "nextfile") {
|
|
822
|
+
await reader.close();
|
|
823
|
+
reader = undefined;
|
|
824
|
+
this.mainReader = undefined;
|
|
825
|
+
}
|
|
826
|
+
else if (error.kind !== "next")
|
|
827
|
+
throw error;
|
|
729
828
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
829
|
+
}
|
|
830
|
+
// Free the terminal main blocks before END, but do not wait ahead of named
|
|
831
|
+
// readers that END may still use. The invocation barrier retains this close.
|
|
832
|
+
if (reader)
|
|
833
|
+
void reader.close().catch(() => undefined);
|
|
734
834
|
this.phase = "END";
|
|
735
835
|
try {
|
|
736
836
|
for (const statement of this.program.end)
|