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