@silverbulletmd/silverbullet 2.4.1 → 2.5.3
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/README.md +20 -4
- package/client/markdown_parser/constants.ts +2 -2
- package/client/plugos/hooks/code_widget.ts +0 -3
- package/client/plugos/hooks/document_editor.ts +0 -3
- package/client/plugos/hooks/event.ts +1 -1
- package/client/plugos/hooks/mq.ts +1 -1
- package/client/plugos/hooks/plug_namespace.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +2 -2
- package/client/plugos/plug.ts +0 -1
- package/client/plugos/plug_compile.ts +28 -29
- package/client/plugos/proxy_fetch.ts +1 -1
- package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
- package/client/plugos/sandboxes/worker_sandbox.ts +2 -3
- package/client/plugos/syscalls/editor.ts +12 -12
- package/client/plugos/syscalls/fetch.ts +1 -1
- package/client/plugos/syscalls/jsonschema.ts +1 -1
- package/client/plugos/syscalls/mq.ts +1 -1
- package/client/plugos/syscalls/space.ts +1 -1
- package/client/plugos/system.ts +2 -2
- package/client/plugos/worker_runtime.ts +8 -29
- package/client/space_lua/aggregates.ts +209 -0
- package/client/space_lua/ast.ts +24 -2
- package/client/space_lua/eval.ts +58 -53
- package/client/space_lua/labels.ts +1 -1
- package/client/space_lua/parse.ts +117 -12
- package/client/space_lua/query_collection.ts +850 -70
- package/client/space_lua/query_env.ts +26 -0
- package/client/space_lua/runtime.ts +47 -17
- package/client/space_lua/stdlib/format.ts +19 -19
- package/client/space_lua/stdlib/math.ts +73 -48
- package/client/space_lua/stdlib/net.ts +2 -2
- package/client/space_lua/stdlib/os.ts +5 -0
- package/client/space_lua/stdlib/pattern.ts +702 -0
- package/client/space_lua/stdlib/prng.ts +145 -0
- package/client/space_lua/stdlib/space_lua.ts +3 -8
- package/client/space_lua/stdlib/string.ts +103 -181
- package/client/space_lua/stdlib/string_pack.ts +486 -0
- package/client/space_lua/stdlib/table.ts +73 -9
- package/client/space_lua/stdlib.ts +38 -14
- package/client/space_lua/tonumber.ts +3 -2
- package/client/space_lua/util.ts +43 -9
- package/dist/plug-compile.js +23 -69
- package/dist/worker_runtime_bundle.js +233 -0
- package/package.json +16 -11
- package/plug-api/constants.ts +0 -32
- package/plug-api/lib/async.ts +2 -2
- package/plug-api/lib/crypto.ts +11 -11
- package/plug-api/lib/json.ts +1 -1
- package/plug-api/lib/limited_map.ts +1 -1
- package/plug-api/lib/native_fetch.ts +2 -0
- package/plug-api/lib/ref.ts +5 -5
- package/plug-api/lib/transclusion.ts +5 -5
- package/plug-api/lib/tree.ts +50 -2
- package/plug-api/lib/yaml.ts +10 -10
- package/plug-api/syscalls/editor.ts +1 -1
- package/plug-api/system_mock.ts +0 -1
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LuaBuiltinFunction,
|
|
3
|
+
LuaMultiRes,
|
|
4
|
+
LuaRuntimeError,
|
|
5
|
+
} from "../runtime.ts";
|
|
6
|
+
|
|
7
|
+
import { isTaggedFloat } from "../numeric.ts";
|
|
8
|
+
|
|
9
|
+
function untagN(x: any): number {
|
|
10
|
+
if (typeof x === "number") return x;
|
|
11
|
+
if (isTaggedFloat(x)) return x.value;
|
|
12
|
+
return Number(x);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const NATIVE_LITTLE = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
|
|
16
|
+
const NATIVE_MAXALIGN = 8; // JS doubles are 8-byte aligned
|
|
17
|
+
|
|
18
|
+
type KOption =
|
|
19
|
+
| "int"
|
|
20
|
+
| "uint"
|
|
21
|
+
| "float"
|
|
22
|
+
| "double"
|
|
23
|
+
| "number"
|
|
24
|
+
| "char"
|
|
25
|
+
| "string"
|
|
26
|
+
| "zstr"
|
|
27
|
+
| "padding"
|
|
28
|
+
| "paddalign"
|
|
29
|
+
| "nop";
|
|
30
|
+
|
|
31
|
+
interface ParsedOption {
|
|
32
|
+
opt: KOption;
|
|
33
|
+
size: number; // byte width
|
|
34
|
+
ntoalign: number; // padding bytes before this field
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Header {
|
|
38
|
+
islittle: boolean;
|
|
39
|
+
maxalign: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function makeHeader(): Header {
|
|
43
|
+
return { islittle: NATIVE_LITTLE, maxalign: NATIVE_MAXALIGN };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Read digits from fmt starting at pos; return [value, newPos]
|
|
47
|
+
function readNum(fmt: string, pos: number, dflt: number): [number, number] {
|
|
48
|
+
if (pos >= fmt.length || fmt[pos] < "0" || fmt[pos] > "9") return [dflt, pos];
|
|
49
|
+
let v = 0;
|
|
50
|
+
while (pos < fmt.length && fmt[pos] >= "0" && fmt[pos] <= "9") {
|
|
51
|
+
v = v * 10 + (fmt.charCodeAt(pos) - 48);
|
|
52
|
+
pos++;
|
|
53
|
+
}
|
|
54
|
+
return [v, pos];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function numLimit(
|
|
58
|
+
fmt: string,
|
|
59
|
+
pos: number,
|
|
60
|
+
dflt: number,
|
|
61
|
+
src: string,
|
|
62
|
+
): [number, number] {
|
|
63
|
+
const [sz, np] = readNum(fmt, pos, dflt);
|
|
64
|
+
if (sz < 1 || sz > 16) {
|
|
65
|
+
throw new Error(`integral size (${sz}) out of limits [1,16] in '${src}'`);
|
|
66
|
+
}
|
|
67
|
+
return [sz, np];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Parse one option from fmt[pos], return [parsed, newPos]
|
|
71
|
+
// Modifies header in place for '<', '>', '=', '!'
|
|
72
|
+
function getOption(
|
|
73
|
+
fmt: string,
|
|
74
|
+
pos: number,
|
|
75
|
+
h: Header,
|
|
76
|
+
): [KOption, number, number] { // [opt, size, newPos]
|
|
77
|
+
const c = fmt[pos++];
|
|
78
|
+
switch (c) {
|
|
79
|
+
case "b":
|
|
80
|
+
return ["int", 1, pos];
|
|
81
|
+
case "B":
|
|
82
|
+
return ["uint", 1, pos];
|
|
83
|
+
case "h":
|
|
84
|
+
return ["int", 2, pos];
|
|
85
|
+
case "H":
|
|
86
|
+
return ["uint", 2, pos];
|
|
87
|
+
case "l":
|
|
88
|
+
return ["int", 8, pos];
|
|
89
|
+
case "L":
|
|
90
|
+
return ["uint", 8, pos];
|
|
91
|
+
case "j":
|
|
92
|
+
return ["int", 8, pos];
|
|
93
|
+
case "J":
|
|
94
|
+
return ["uint", 8, pos];
|
|
95
|
+
case "T":
|
|
96
|
+
return ["uint", 8, pos];
|
|
97
|
+
case "f":
|
|
98
|
+
return ["float", 4, pos];
|
|
99
|
+
case "n":
|
|
100
|
+
return ["number", 8, pos];
|
|
101
|
+
case "d":
|
|
102
|
+
return ["double", 8, pos];
|
|
103
|
+
case "i": {
|
|
104
|
+
const [sz, np] = numLimit(fmt, pos, 4, "i");
|
|
105
|
+
return ["int", sz, np];
|
|
106
|
+
}
|
|
107
|
+
case "I": {
|
|
108
|
+
const [sz, np] = numLimit(fmt, pos, 4, "I");
|
|
109
|
+
return ["uint", sz, np];
|
|
110
|
+
}
|
|
111
|
+
case "s": {
|
|
112
|
+
const [sz, np] = numLimit(fmt, pos, 8, "s");
|
|
113
|
+
return ["string", sz, np];
|
|
114
|
+
}
|
|
115
|
+
case "c": {
|
|
116
|
+
const [sz, np] = readNum(fmt, pos, -1);
|
|
117
|
+
if (sz === -1) throw new Error("missing size for format option 'c'");
|
|
118
|
+
return ["char", sz, np];
|
|
119
|
+
}
|
|
120
|
+
case "z":
|
|
121
|
+
return ["zstr", 0, pos];
|
|
122
|
+
case "x":
|
|
123
|
+
return ["padding", 1, pos];
|
|
124
|
+
case "X":
|
|
125
|
+
return ["paddalign", 0, pos];
|
|
126
|
+
case " ":
|
|
127
|
+
return ["nop", 0, pos];
|
|
128
|
+
case "<":
|
|
129
|
+
h.islittle = true;
|
|
130
|
+
return ["nop", 0, pos];
|
|
131
|
+
case ">":
|
|
132
|
+
h.islittle = false;
|
|
133
|
+
return ["nop", 0, pos];
|
|
134
|
+
case "=":
|
|
135
|
+
h.islittle = NATIVE_LITTLE;
|
|
136
|
+
return ["nop", 0, pos];
|
|
137
|
+
case "!": {
|
|
138
|
+
const [sz, np] = readNum(fmt, pos, NATIVE_MAXALIGN);
|
|
139
|
+
h.maxalign = sz;
|
|
140
|
+
return ["nop", 0, np];
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
throw new Error(`invalid format option '${c}'`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Compute alignment padding
|
|
148
|
+
function getDetails(
|
|
149
|
+
fmt: string,
|
|
150
|
+
pos: number,
|
|
151
|
+
h: Header,
|
|
152
|
+
totalsize: number,
|
|
153
|
+
): [ParsedOption, number] {
|
|
154
|
+
let opt: KOption, size: number;
|
|
155
|
+
[opt, size, pos] = getOption(fmt, pos, h);
|
|
156
|
+
|
|
157
|
+
let align = size;
|
|
158
|
+
|
|
159
|
+
if (opt === "paddalign") {
|
|
160
|
+
if (pos >= fmt.length) {
|
|
161
|
+
throw new Error("invalid next option for option 'X'");
|
|
162
|
+
}
|
|
163
|
+
const hCopy = { ...h };
|
|
164
|
+
let nextOpt: KOption, nextSize: number;
|
|
165
|
+
[nextOpt, nextSize, pos] = getOption(fmt, pos, hCopy);
|
|
166
|
+
if (nextOpt === "char" || nextSize === 0) {
|
|
167
|
+
throw new Error("invalid next option for option 'X'");
|
|
168
|
+
}
|
|
169
|
+
align = nextSize;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let ntoalign = 0;
|
|
173
|
+
if (
|
|
174
|
+
opt !== "char" && opt !== "nop" && opt !== "padding" && opt !== "paddalign"
|
|
175
|
+
) {
|
|
176
|
+
const realign = Math.min(align, h.maxalign);
|
|
177
|
+
if (realign > 0) {
|
|
178
|
+
ntoalign = (realign - (totalsize % realign)) % realign;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return [{ opt, size, ntoalign }, pos];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function packInt(v: bigint, size: number, islittle: boolean): Uint8Array {
|
|
186
|
+
const buf = new Uint8Array(size);
|
|
187
|
+
let val = v;
|
|
188
|
+
// Two's complement mask
|
|
189
|
+
const mask = (1n << BigInt(size * 8)) - 1n;
|
|
190
|
+
val = ((val % (mask + 1n)) + (mask + 1n)) & mask; // normalise to unsigned
|
|
191
|
+
for (let i = 0; i < size; i++) {
|
|
192
|
+
buf[islittle ? i : size - 1 - i] = Number(val & 0xffn);
|
|
193
|
+
val >>= 8n;
|
|
194
|
+
}
|
|
195
|
+
return buf;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function unpackInt(
|
|
199
|
+
buf: Uint8Array,
|
|
200
|
+
pos: number,
|
|
201
|
+
size: number,
|
|
202
|
+
islittle: boolean,
|
|
203
|
+
issigned: boolean,
|
|
204
|
+
): bigint {
|
|
205
|
+
let res = 0n;
|
|
206
|
+
const limit = Math.min(size, 8);
|
|
207
|
+
for (let i = limit - 1; i >= 0; i--) {
|
|
208
|
+
res = (res << 8n) | BigInt(buf[pos + (islittle ? i : size - 1 - i)]);
|
|
209
|
+
}
|
|
210
|
+
if (issigned && size <= 8) {
|
|
211
|
+
const mask = 1n << BigInt(size * 8 - 1);
|
|
212
|
+
if (res & mask) res -= mask << 1n;
|
|
213
|
+
}
|
|
214
|
+
return res;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function packFloat32(v: number, islittle: boolean): Uint8Array {
|
|
218
|
+
const buf = new ArrayBuffer(4);
|
|
219
|
+
new DataView(buf).setFloat32(0, v, islittle);
|
|
220
|
+
return new Uint8Array(buf);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function unpackFloat32(
|
|
224
|
+
buf: Uint8Array,
|
|
225
|
+
pos: number,
|
|
226
|
+
islittle: boolean,
|
|
227
|
+
): number {
|
|
228
|
+
return new DataView(buf.buffer, buf.byteOffset + pos, 4).getFloat32(
|
|
229
|
+
0,
|
|
230
|
+
islittle,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function packFloat64(v: number, islittle: boolean): Uint8Array {
|
|
235
|
+
const buf = new ArrayBuffer(8);
|
|
236
|
+
new DataView(buf).setFloat64(0, v, islittle);
|
|
237
|
+
return new Uint8Array(buf);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function unpackFloat64(
|
|
241
|
+
buf: Uint8Array,
|
|
242
|
+
pos: number,
|
|
243
|
+
islittle: boolean,
|
|
244
|
+
): number {
|
|
245
|
+
return new DataView(buf.buffer, buf.byteOffset + pos, 8).getFloat64(
|
|
246
|
+
0,
|
|
247
|
+
islittle,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const strPackFn = new LuaBuiltinFunction(
|
|
252
|
+
(sf, fmt: string, ...args: any[]) => {
|
|
253
|
+
const h = makeHeader();
|
|
254
|
+
const parts: Uint8Array[] = [];
|
|
255
|
+
let totalsize = 0;
|
|
256
|
+
let argIdx = 0;
|
|
257
|
+
|
|
258
|
+
let pos = 0;
|
|
259
|
+
while (pos < fmt.length) {
|
|
260
|
+
let opt: ParsedOption;
|
|
261
|
+
[opt, pos] = getDetails(fmt, pos, h, totalsize);
|
|
262
|
+
|
|
263
|
+
// alignment padding
|
|
264
|
+
if (opt.ntoalign > 0) {
|
|
265
|
+
parts.push(new Uint8Array(opt.ntoalign));
|
|
266
|
+
totalsize += opt.ntoalign;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
switch (opt.opt) {
|
|
270
|
+
case "nop":
|
|
271
|
+
case "paddalign":
|
|
272
|
+
break;
|
|
273
|
+
|
|
274
|
+
case "padding":
|
|
275
|
+
parts.push(new Uint8Array(1)); // LUAL_PACKPADBYTE = 0
|
|
276
|
+
totalsize += 1;
|
|
277
|
+
break;
|
|
278
|
+
|
|
279
|
+
case "int":
|
|
280
|
+
case "uint": {
|
|
281
|
+
const v = args[argIdx++];
|
|
282
|
+
if (v === undefined || v === null) {
|
|
283
|
+
throw new LuaRuntimeError(
|
|
284
|
+
`bad argument #${argIdx} to 'pack' (value expected)`,
|
|
285
|
+
sf,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
let bi: bigint;
|
|
289
|
+
if (typeof v === "bigint") bi = v;
|
|
290
|
+
else bi = BigInt(Math.trunc(untagN(v)));
|
|
291
|
+
parts.push(packInt(bi, opt.size, h.islittle));
|
|
292
|
+
totalsize += opt.size;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
case "float": {
|
|
297
|
+
const v = untagN(args[argIdx++]);
|
|
298
|
+
parts.push(packFloat32(v, h.islittle));
|
|
299
|
+
totalsize += 4;
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
case "double":
|
|
304
|
+
case "number": {
|
|
305
|
+
const v = untagN(args[argIdx++]);
|
|
306
|
+
parts.push(packFloat64(v, h.islittle));
|
|
307
|
+
totalsize += 8;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
case "char": {
|
|
312
|
+
const s: string = String(args[argIdx++]);
|
|
313
|
+
const enc = new TextEncoder().encode(s);
|
|
314
|
+
const buf = new Uint8Array(opt.size);
|
|
315
|
+
buf.set(enc.subarray(0, opt.size));
|
|
316
|
+
parts.push(buf);
|
|
317
|
+
totalsize += opt.size;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case "string": {
|
|
322
|
+
const s: string = String(args[argIdx++]);
|
|
323
|
+
const enc = new TextEncoder().encode(s);
|
|
324
|
+
const lenBuf = packInt(BigInt(enc.length), opt.size, h.islittle);
|
|
325
|
+
parts.push(lenBuf);
|
|
326
|
+
parts.push(enc);
|
|
327
|
+
totalsize += opt.size + enc.length;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
case "zstr": {
|
|
332
|
+
const s: string = String(args[argIdx++]);
|
|
333
|
+
if (s.includes("\0")) {
|
|
334
|
+
throw new LuaRuntimeError(
|
|
335
|
+
"string contains zeros for format 'z'",
|
|
336
|
+
sf,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const enc = new TextEncoder().encode(s);
|
|
340
|
+
parts.push(enc);
|
|
341
|
+
parts.push(new Uint8Array(1)); // null terminator
|
|
342
|
+
totalsize += enc.length + 1;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Concatenate all parts into one binary string (latin-1 encoding)
|
|
349
|
+
let total = 0;
|
|
350
|
+
for (const p of parts) total += p.length;
|
|
351
|
+
const out = new Uint8Array(total);
|
|
352
|
+
let off = 0;
|
|
353
|
+
for (const p of parts) {
|
|
354
|
+
out.set(p, off);
|
|
355
|
+
off += p.length;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Return as a Lua binary string (each byte is a char code 0-255)
|
|
359
|
+
let result = "";
|
|
360
|
+
for (let i = 0; i < out.length; i++) result += String.fromCharCode(out[i]);
|
|
361
|
+
return result;
|
|
362
|
+
},
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
export const strUnpackFn = new LuaBuiltinFunction(
|
|
366
|
+
(sf, fmt: string, data: string, init?: number) => {
|
|
367
|
+
const h = makeHeader();
|
|
368
|
+
|
|
369
|
+
const buf = new Uint8Array(data.length);
|
|
370
|
+
for (let i = 0; i < data.length; i++) {
|
|
371
|
+
buf[i] = data.charCodeAt(i) & 0xff;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let pos = (init !== undefined && init !== null ? init : 1) - 1;
|
|
375
|
+
const results: any[] = [];
|
|
376
|
+
|
|
377
|
+
let fmtPos = 0;
|
|
378
|
+
while (fmtPos < fmt.length) {
|
|
379
|
+
let opt: ParsedOption;
|
|
380
|
+
[opt, fmtPos] = getDetails(fmt, fmtPos, h, pos);
|
|
381
|
+
|
|
382
|
+
if (opt.ntoalign + opt.size > buf.length - pos) {
|
|
383
|
+
if (
|
|
384
|
+
opt.opt !== "nop" && opt.opt !== "paddalign" && opt.opt !== "padding"
|
|
385
|
+
) {
|
|
386
|
+
throw new LuaRuntimeError("data string too short", sf);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
pos += opt.ntoalign; // skip alignment padding
|
|
391
|
+
|
|
392
|
+
switch (opt.opt) {
|
|
393
|
+
case "nop":
|
|
394
|
+
case "paddalign":
|
|
395
|
+
break;
|
|
396
|
+
|
|
397
|
+
case "padding":
|
|
398
|
+
pos += 1;
|
|
399
|
+
break;
|
|
400
|
+
|
|
401
|
+
case "int": {
|
|
402
|
+
const v = unpackInt(buf, pos, opt.size, h.islittle, true);
|
|
403
|
+
const n = Number(v);
|
|
404
|
+
results.push(Number.isSafeInteger(n) ? n : v);
|
|
405
|
+
pos += opt.size;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
case "uint": {
|
|
410
|
+
const v = unpackInt(buf, pos, opt.size, h.islittle, false);
|
|
411
|
+
const n = Number(v);
|
|
412
|
+
results.push(Number.isSafeInteger(n) ? n : v);
|
|
413
|
+
pos += opt.size;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
case "float": {
|
|
418
|
+
results.push(unpackFloat32(buf, pos, h.islittle));
|
|
419
|
+
pos += 4;
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
case "double":
|
|
424
|
+
case "number": {
|
|
425
|
+
results.push(unpackFloat64(buf, pos, h.islittle));
|
|
426
|
+
pos += 8;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
case "char": {
|
|
431
|
+
const s = String.fromCharCode(...buf.subarray(pos, pos + opt.size));
|
|
432
|
+
results.push(s);
|
|
433
|
+
pos += opt.size;
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
case "string": {
|
|
438
|
+
const len = Number(unpackInt(buf, pos, opt.size, h.islittle, false));
|
|
439
|
+
if (len > buf.length - pos - opt.size) {
|
|
440
|
+
throw new LuaRuntimeError("data string too short", sf);
|
|
441
|
+
}
|
|
442
|
+
pos += opt.size;
|
|
443
|
+
const s = new TextDecoder().decode(buf.subarray(pos, pos + len));
|
|
444
|
+
results.push(s);
|
|
445
|
+
pos += len;
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
case "zstr": {
|
|
450
|
+
let end = pos;
|
|
451
|
+
while (end < buf.length && buf[end] !== 0) end++;
|
|
452
|
+
if (end >= buf.length) {
|
|
453
|
+
throw new LuaRuntimeError("unfinished string for format 'z'", sf);
|
|
454
|
+
}
|
|
455
|
+
results.push(new TextDecoder().decode(buf.subarray(pos, end)));
|
|
456
|
+
pos = end + 1;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
results.push(pos + 1);
|
|
463
|
+
return new LuaMultiRes(results);
|
|
464
|
+
},
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
export const strPackSizeFn = new LuaBuiltinFunction(
|
|
468
|
+
(_sf, fmt: string) => {
|
|
469
|
+
const h = makeHeader();
|
|
470
|
+
let totalsize = 0;
|
|
471
|
+
let pos = 0;
|
|
472
|
+
|
|
473
|
+
while (pos < fmt.length) {
|
|
474
|
+
let opt: ParsedOption;
|
|
475
|
+
[opt, pos] = getDetails(fmt, pos, h, totalsize);
|
|
476
|
+
|
|
477
|
+
if (opt.opt === "string" || opt.opt === "zstr") {
|
|
478
|
+
throw new LuaRuntimeError("variable-length format", _sf);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
totalsize += opt.ntoalign + opt.size;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return totalsize;
|
|
485
|
+
},
|
|
486
|
+
);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
luaCall,
|
|
6
6
|
type LuaEnv,
|
|
7
7
|
luaEquals,
|
|
8
|
+
luaFormatNumber,
|
|
8
9
|
luaGet,
|
|
9
10
|
LuaMultiRes,
|
|
10
11
|
LuaRuntimeError,
|
|
@@ -77,10 +78,10 @@ export const tableApi = new LuaTable({
|
|
|
77
78
|
return v;
|
|
78
79
|
}
|
|
79
80
|
if (typeof v === "number") {
|
|
80
|
-
return
|
|
81
|
+
return luaFormatNumber(v);
|
|
81
82
|
}
|
|
82
83
|
if (isTaggedFloat(v)) {
|
|
83
|
-
return
|
|
84
|
+
return luaFormatNumber(v.value, "float");
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
const ty = typeof v === "object" && v instanceof LuaTable
|
|
@@ -204,6 +205,56 @@ export const tableApi = new LuaTable({
|
|
|
204
205
|
},
|
|
205
206
|
),
|
|
206
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Moves elements from table a1 into table a2 (defaults to a1).
|
|
210
|
+
* Equivalent to: `for i = f, e do a2[t+(i-f)] = a1[i] end`
|
|
211
|
+
* Handles overlapping ranges within the same table correctly.
|
|
212
|
+
* @param a1 - Source table.
|
|
213
|
+
* @param f - First source index (inclusive).
|
|
214
|
+
* @param e - Last source index (inclusive).
|
|
215
|
+
* @param t - Destination start index.
|
|
216
|
+
* @param a2 - Destination table (defaults to a1).
|
|
217
|
+
* @returns a2.
|
|
218
|
+
*/
|
|
219
|
+
move: new LuaBuiltinFunction(
|
|
220
|
+
async (
|
|
221
|
+
sf,
|
|
222
|
+
a1: LuaTable | any[],
|
|
223
|
+
f: number,
|
|
224
|
+
e: number,
|
|
225
|
+
t: number,
|
|
226
|
+
a2?: LuaTable | any[],
|
|
227
|
+
) => {
|
|
228
|
+
// a2 defaults to a1
|
|
229
|
+
if (a2 === undefined || a2 === null) {
|
|
230
|
+
a2 = a1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Empty range: nothing to do, return destination
|
|
234
|
+
if (e < f) {
|
|
235
|
+
return a2;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const count = e - f + 1;
|
|
239
|
+
|
|
240
|
+
// When source and destination overlap and destination is ahead of
|
|
241
|
+
// source then copy backwards to avoid clobbering unread values.
|
|
242
|
+
if (t > f && a2 === a1) {
|
|
243
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
244
|
+
const v = await luaGet(a1, f + i, sf.astCtx ?? null, sf);
|
|
245
|
+
await luaSet(a2, t + i, v, sf);
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
for (let i = 0; i < count; i++) {
|
|
249
|
+
const v = await luaGet(a1, f + i, sf.astCtx ?? null, sf);
|
|
250
|
+
await luaSet(a2, t + i, v, sf);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return a2;
|
|
255
|
+
},
|
|
256
|
+
),
|
|
257
|
+
|
|
207
258
|
/**
|
|
208
259
|
* Sorts a table.
|
|
209
260
|
* @param tbl - The table to sort.
|
|
@@ -215,7 +266,7 @@ export const tableApi = new LuaTable({
|
|
|
215
266
|
if (Array.isArray(tbl)) {
|
|
216
267
|
return await asyncQuickSort(tbl, async (a, b) => {
|
|
217
268
|
if (comp) {
|
|
218
|
-
return (await comp.call(sf, a, b)) ? -1 :
|
|
269
|
+
return (await comp.call(sf, a, b)) ? -1 : 0;
|
|
219
270
|
}
|
|
220
271
|
return (a as any) < (b as any) ? -1 : 1;
|
|
221
272
|
});
|
|
@@ -235,7 +286,7 @@ export const tableApi = new LuaTable({
|
|
|
235
286
|
const cmp = async (a: any, b: any): Promise<number> => {
|
|
236
287
|
if (comp) {
|
|
237
288
|
const r = await luaCall(comp, [a, b], sf.astCtx ?? {}, sf);
|
|
238
|
-
return r ? -1 :
|
|
289
|
+
return r ? -1 : 0;
|
|
239
290
|
}
|
|
240
291
|
|
|
241
292
|
const av = isTaggedFloat(a) ? a.value : a;
|
|
@@ -338,24 +389,37 @@ export const tableApi = new LuaTable({
|
|
|
338
389
|
},
|
|
339
390
|
),
|
|
340
391
|
|
|
341
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Returns a new table with all arguments stored in keys 1, 2, ..., n
|
|
394
|
+
* and t.n = n (the total number of arguments).
|
|
395
|
+
*/
|
|
396
|
+
pack: new LuaBuiltinFunction(async (sf, ...args: any[]) => {
|
|
342
397
|
const tbl = new LuaTable();
|
|
343
|
-
|
|
344
|
-
|
|
398
|
+
const n = args.length;
|
|
399
|
+
for (let i = 0; i < n; i++) {
|
|
400
|
+
await luaSet(tbl, i + 1, args[i], sf);
|
|
345
401
|
}
|
|
346
|
-
tbl.
|
|
402
|
+
tbl.rawSet("n", n);
|
|
347
403
|
return tbl;
|
|
348
404
|
}),
|
|
349
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Returns all values t[i], t[i+1], ..., t[j].
|
|
408
|
+
* i defaults to 1, j defaults to #t (honours __len).
|
|
409
|
+
* Empty range returns no values (null), not an empty multi-res.
|
|
410
|
+
*/
|
|
350
411
|
unpack: new LuaBuiltinFunction(
|
|
351
412
|
async (sf, tbl: LuaTable | any[], i?: number, j?: number) => {
|
|
352
|
-
i = i
|
|
413
|
+
i = (i === undefined || i === null) ? 1 : i;
|
|
353
414
|
if (j === undefined || j === null) {
|
|
354
415
|
j = Array.isArray(tbl)
|
|
355
416
|
? tbl.length
|
|
356
417
|
: await luaLenForTableLibAsync(sf, tbl);
|
|
357
418
|
}
|
|
358
419
|
|
|
420
|
+
if (i > j) {
|
|
421
|
+
return new LuaMultiRes([]);
|
|
422
|
+
}
|
|
359
423
|
const result: LuaValue[] = [];
|
|
360
424
|
for (let k = i; k <= j; k++) {
|
|
361
425
|
const v = Array.isArray(tbl)
|