@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
|
@@ -58,7 +58,8 @@ function parseSpec(
|
|
|
58
58
|
|
|
59
59
|
// Parse width
|
|
60
60
|
let width = 0;
|
|
61
|
-
if (i < len && fmt.charCodeAt(i) === 42) {
|
|
61
|
+
if (i < len && fmt.charCodeAt(i) === 42) {
|
|
62
|
+
// '*'
|
|
62
63
|
width = -1;
|
|
63
64
|
i++;
|
|
64
65
|
} else {
|
|
@@ -71,10 +72,12 @@ function parseSpec(
|
|
|
71
72
|
// Parse precision
|
|
72
73
|
let hasPrec = false;
|
|
73
74
|
let prec = 0;
|
|
74
|
-
if (i < len && fmt.charCodeAt(i) === 46) {
|
|
75
|
+
if (i < len && fmt.charCodeAt(i) === 46) {
|
|
76
|
+
// '.'
|
|
75
77
|
hasPrec = true;
|
|
76
78
|
i++;
|
|
77
|
-
if (i < len && fmt.charCodeAt(i) === 42) {
|
|
79
|
+
if (i < len && fmt.charCodeAt(i) === 42) {
|
|
80
|
+
// '*'
|
|
78
81
|
prec = -1;
|
|
79
82
|
i++;
|
|
80
83
|
} else {
|
|
@@ -109,9 +112,10 @@ function parseSpec(
|
|
|
109
112
|
function pad(s: string, width: number, flags: number, numPad: boolean): string {
|
|
110
113
|
if (width <= 0 || s.length >= width) return s;
|
|
111
114
|
const n = width - s.length;
|
|
112
|
-
if (numPad &&
|
|
115
|
+
if (numPad && flags & FLAG_ZERO && !(flags & FLAG_MINUS)) {
|
|
113
116
|
let signLen = 0;
|
|
114
|
-
if (s.charCodeAt(0) === 45 || s.charCodeAt(0) === 43) {
|
|
117
|
+
if (s.charCodeAt(0) === 45 || s.charCodeAt(0) === 43) {
|
|
118
|
+
// '-' or '+'
|
|
115
119
|
signLen = 1;
|
|
116
120
|
} else if (
|
|
117
121
|
s.charCodeAt(0) === 48 &&
|
|
@@ -128,8 +132,8 @@ function pad(s: string, width: number, flags: number, numPad: boolean): string {
|
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
function addSign(s: string, flags: number): string {
|
|
131
|
-
if (flags & FLAG_PLUS) return
|
|
132
|
-
if (flags & FLAG_SPACE) return
|
|
135
|
+
if (flags & FLAG_PLUS) return `+${s}`;
|
|
136
|
+
if (flags & FLAG_SPACE) return ` ${s}`;
|
|
133
137
|
return s;
|
|
134
138
|
}
|
|
135
139
|
|
|
@@ -200,7 +204,7 @@ function formatInt(n: number, spec: FormatSpec): string {
|
|
|
200
204
|
|
|
201
205
|
let result: string;
|
|
202
206
|
if (neg) {
|
|
203
|
-
result =
|
|
207
|
+
result = `-${prefix}${digits}`;
|
|
204
208
|
} else {
|
|
205
209
|
result = addSign(prefix + digits, spec.flags);
|
|
206
210
|
}
|
|
@@ -216,7 +220,7 @@ function formatFloat(n: number, spec: FormatSpec): string {
|
|
|
216
220
|
const lower = code | 32; // to lowercase
|
|
217
221
|
|
|
218
222
|
// Lua convention
|
|
219
|
-
if (!isFinite(n)) {
|
|
223
|
+
if (!Number.isFinite(n)) {
|
|
220
224
|
let s: string;
|
|
221
225
|
if (n !== n) {
|
|
222
226
|
s = upper ? "-NAN" : "-nan";
|
|
@@ -235,14 +239,17 @@ function formatFloat(n: number, spec: FormatSpec): string {
|
|
|
235
239
|
|
|
236
240
|
let body: string;
|
|
237
241
|
|
|
238
|
-
if (lower === 102) {
|
|
242
|
+
if (lower === 102) {
|
|
243
|
+
// 'f'
|
|
239
244
|
body = abs.toFixed(prec);
|
|
240
|
-
} else if (lower === 101) {
|
|
245
|
+
} else if (lower === 101) {
|
|
246
|
+
// 'e'
|
|
241
247
|
body = abs.toExponential(prec);
|
|
242
248
|
// Ensure exponent has at least 2 digits
|
|
243
249
|
body = ensureExpTwoDigits(body);
|
|
244
|
-
} else {
|
|
245
|
-
|
|
250
|
+
} else {
|
|
251
|
+
// 'g'
|
|
252
|
+
const gPrec = prec === 0 ? 1 : prec;
|
|
246
253
|
if (abs === 0) {
|
|
247
254
|
body = "0";
|
|
248
255
|
} else {
|
|
@@ -268,35 +275,35 @@ function formatFloat(n: number, spec: FormatSpec): string {
|
|
|
268
275
|
}
|
|
269
276
|
|
|
270
277
|
// Alt flag for 'f'/'e': ensure decimal point exists
|
|
271
|
-
if (
|
|
278
|
+
if (spec.flags & FLAG_HASH && lower !== 103) {
|
|
272
279
|
if (body.indexOf(".") === -1) {
|
|
273
280
|
// Insert dot before 'e' if present, else append
|
|
274
281
|
const eIdx = body.indexOf("e");
|
|
275
282
|
const EIdx = body.indexOf("E");
|
|
276
283
|
const expIdx = eIdx !== -1 ? eIdx : EIdx;
|
|
277
284
|
if (expIdx !== -1) {
|
|
278
|
-
body = body.slice(0, expIdx)
|
|
285
|
+
body = `${body.slice(0, expIdx)}.${body.slice(expIdx)}`;
|
|
279
286
|
} else {
|
|
280
|
-
body = body
|
|
287
|
+
body = `${body}.`;
|
|
281
288
|
}
|
|
282
289
|
}
|
|
283
290
|
}
|
|
284
291
|
|
|
285
292
|
// Alt flag for 'g': keep trailing zeros but ensure decimal point
|
|
286
|
-
if (
|
|
293
|
+
if (spec.flags & FLAG_HASH && lower === 103) {
|
|
287
294
|
if (body.indexOf(".") === -1) {
|
|
288
295
|
const expIdx = findExpIndex(body);
|
|
289
296
|
if (expIdx !== -1) {
|
|
290
|
-
body = body.slice(0, expIdx)
|
|
297
|
+
body = `${body.slice(0, expIdx)}.${body.slice(expIdx)}`;
|
|
291
298
|
} else {
|
|
292
|
-
body = body
|
|
299
|
+
body = `${body}.`;
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
}
|
|
296
303
|
|
|
297
304
|
let result: string;
|
|
298
305
|
if (neg) {
|
|
299
|
-
result =
|
|
306
|
+
result = `-${body}`;
|
|
300
307
|
} else {
|
|
301
308
|
result = addSign(body, spec.flags);
|
|
302
309
|
}
|
|
@@ -322,7 +329,7 @@ function ensureExpTwoDigits(s: string): string {
|
|
|
322
329
|
const digitStart = signIdx + 1;
|
|
323
330
|
const expLen = s.length - digitStart;
|
|
324
331
|
if (expLen < 2) {
|
|
325
|
-
return s.slice(0, digitStart)
|
|
332
|
+
return `${s.slice(0, digitStart)}0${s.slice(digitStart)}`;
|
|
326
333
|
}
|
|
327
334
|
return s;
|
|
328
335
|
}
|
|
@@ -337,7 +344,8 @@ function stripTrailingZerosG(s: string): string {
|
|
|
337
344
|
if (dotIdx === -1) return s; // nothing to strip
|
|
338
345
|
|
|
339
346
|
let end = mantissa.length;
|
|
340
|
-
while (end > dotIdx + 1 && mantissa.charCodeAt(end - 1) === 48) {
|
|
347
|
+
while (end > dotIdx + 1 && mantissa.charCodeAt(end - 1) === 48) {
|
|
348
|
+
// '0'
|
|
341
349
|
end--;
|
|
342
350
|
}
|
|
343
351
|
// Remove dot if nothing after it
|
|
@@ -353,7 +361,7 @@ function formatHexFloat(n: number, spec: FormatSpec): string {
|
|
|
353
361
|
const code = spec.spec;
|
|
354
362
|
const upper = code === 65; // 'A'
|
|
355
363
|
|
|
356
|
-
if (!isFinite(n)) {
|
|
364
|
+
if (!Number.isFinite(n)) {
|
|
357
365
|
let s: string;
|
|
358
366
|
if (n !== n) {
|
|
359
367
|
s = upper ? "-NAN" : "-nan";
|
|
@@ -373,7 +381,7 @@ function formatHexFloat(n: number, spec: FormatSpec): string {
|
|
|
373
381
|
if (abs === 0) {
|
|
374
382
|
const prec = spec.hasPrec ? spec.prec : 0;
|
|
375
383
|
if (prec > 0) {
|
|
376
|
-
body =
|
|
384
|
+
body = `0x0.${"0".repeat(prec)}p+0`;
|
|
377
385
|
} else {
|
|
378
386
|
body = "0x0p+0";
|
|
379
387
|
}
|
|
@@ -389,20 +397,21 @@ function formatHexFloat(n: number, spec: FormatSpec): string {
|
|
|
389
397
|
if (pIdx !== -1) {
|
|
390
398
|
let hasDot = false;
|
|
391
399
|
for (let k = 0; k < pIdx; k++) {
|
|
392
|
-
if (body.charCodeAt(k) === 46) {
|
|
400
|
+
if (body.charCodeAt(k) === 46) {
|
|
401
|
+
// '.'
|
|
393
402
|
hasDot = true;
|
|
394
403
|
break;
|
|
395
404
|
}
|
|
396
405
|
}
|
|
397
406
|
if (!hasDot) {
|
|
398
|
-
body = body.slice(0, pIdx)
|
|
407
|
+
body = `${body.slice(0, pIdx)}.${body.slice(pIdx)}`;
|
|
399
408
|
}
|
|
400
409
|
}
|
|
401
410
|
}
|
|
402
411
|
|
|
403
412
|
let result: string;
|
|
404
413
|
if (neg) {
|
|
405
|
-
result =
|
|
414
|
+
result = `-${body}`;
|
|
406
415
|
} else {
|
|
407
416
|
result = addSign(body, spec.flags);
|
|
408
417
|
}
|
|
@@ -436,8 +445,8 @@ function hexFloatBody(abs: number, spec: FormatSpec): string {
|
|
|
436
445
|
const view = new DataView(buf.buffer);
|
|
437
446
|
view.setFloat64(0, abs);
|
|
438
447
|
const bits = view.getBigUint64(0);
|
|
439
|
-
const biasedExp = Number((bits >> 52n) &
|
|
440
|
-
const frac = bits &
|
|
448
|
+
const biasedExp = Number((bits >> 52n) & 0x7ffn);
|
|
449
|
+
const frac = bits & 0xfffffffffffffn;
|
|
441
450
|
|
|
442
451
|
let exponent: number;
|
|
443
452
|
let mantBits: bigint;
|
|
@@ -474,9 +483,9 @@ function hexFloatBody(abs: number, spec: FormatSpec): string {
|
|
|
474
483
|
|
|
475
484
|
const expSign = exponent >= 0 ? "+" : "";
|
|
476
485
|
if (fracHex.length > 0) {
|
|
477
|
-
return
|
|
486
|
+
return `0x${firstDigit}.${fracHex}p${expSign}${exponent}`;
|
|
478
487
|
}
|
|
479
|
-
return
|
|
488
|
+
return `0x${firstDigit}p${expSign}${exponent}`;
|
|
480
489
|
}
|
|
481
490
|
|
|
482
491
|
// Convert 52-bit value to 13 hex digits, zero-padded
|
|
@@ -557,7 +566,8 @@ function padHexRight(s: string, len: number): string {
|
|
|
557
566
|
|
|
558
567
|
function stripHexTrailingZeros(s: string): string {
|
|
559
568
|
let end = s.length;
|
|
560
|
-
while (end > 0 && s.charCodeAt(end - 1) === 48) {
|
|
569
|
+
while (end > 0 && s.charCodeAt(end - 1) === 48) {
|
|
570
|
+
// '0'
|
|
561
571
|
end--;
|
|
562
572
|
}
|
|
563
573
|
if (end === s.length) return s;
|
|
@@ -581,7 +591,7 @@ function quoteString(s: string): string {
|
|
|
581
591
|
if (ds.length < 3) out += "0".repeat(3 - ds.length);
|
|
582
592
|
out += ds;
|
|
583
593
|
} else {
|
|
584
|
-
out +=
|
|
594
|
+
out += `\\${c.toString()}`;
|
|
585
595
|
}
|
|
586
596
|
} else {
|
|
587
597
|
out += String.fromCharCode(c);
|
|
@@ -643,7 +653,7 @@ function toPointer(v: unknown): string {
|
|
|
643
653
|
id = nextId++;
|
|
644
654
|
stringIds.set(key, id);
|
|
645
655
|
}
|
|
646
|
-
return
|
|
656
|
+
return `0x${id.toString(16).padStart(14, "0")}`;
|
|
647
657
|
}
|
|
648
658
|
|
|
649
659
|
const obj = v as object;
|
|
@@ -652,7 +662,7 @@ function toPointer(v: unknown): string {
|
|
|
652
662
|
id = nextId++;
|
|
653
663
|
objectIds.set(obj, id);
|
|
654
664
|
}
|
|
655
|
-
return
|
|
665
|
+
return `0x${id.toString(16).padStart(14, "0")}`;
|
|
656
666
|
}
|
|
657
667
|
|
|
658
668
|
function formatPointer(v: unknown, spec: FormatSpec): string {
|
|
@@ -669,7 +679,8 @@ export function luaFormat(fmt: string, ...args: any[]): string {
|
|
|
669
679
|
|
|
670
680
|
while (i < len) {
|
|
671
681
|
const c = fmt.charCodeAt(i);
|
|
672
|
-
if (c !== 37) {
|
|
682
|
+
if (c !== 37) {
|
|
683
|
+
// not '%'
|
|
673
684
|
// Fast path: scan for next '%' or end
|
|
674
685
|
let j = i + 1;
|
|
675
686
|
while (j < len && fmt.charCodeAt(j) !== 37) j++;
|
|
@@ -743,15 +754,18 @@ export function luaFormat(fmt: string, ...args: any[]): string {
|
|
|
743
754
|
false,
|
|
744
755
|
);
|
|
745
756
|
break;
|
|
746
|
-
case 112: {
|
|
757
|
+
case 112: {
|
|
758
|
+
// 'p'
|
|
747
759
|
out += formatPointer(args[ai++], spec);
|
|
748
760
|
break;
|
|
749
761
|
}
|
|
750
|
-
case 113: {
|
|
762
|
+
case 113: {
|
|
763
|
+
// 'q'
|
|
751
764
|
out += formatQ(args[ai++]);
|
|
752
765
|
break;
|
|
753
766
|
}
|
|
754
|
-
case 115: {
|
|
767
|
+
case 115: {
|
|
768
|
+
// 's'
|
|
755
769
|
let s = String(args[ai++]);
|
|
756
770
|
if (spec.hasPrec && s.length > spec.prec) {
|
|
757
771
|
s = s.slice(0, spec.prec);
|
|
@@ -12,13 +12,9 @@ export const jsApi = new LuaTable({
|
|
|
12
12
|
* @param args - The arguments to pass to the constructor.
|
|
13
13
|
* @returns The new instance.
|
|
14
14
|
*/
|
|
15
|
-
new: new LuaBuiltinFunction(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
...args.map((v) => luaValueToJS(v, sf)),
|
|
19
|
-
);
|
|
20
|
-
},
|
|
21
|
-
),
|
|
15
|
+
new: new LuaBuiltinFunction((sf, constructorFn: any, ...args) => {
|
|
16
|
+
return new constructorFn(...args.map((v) => luaValueToJS(v, sf)));
|
|
17
|
+
}),
|
|
22
18
|
/**
|
|
23
19
|
* Imports a JavaScript module.
|
|
24
20
|
* @param url - The URL of the module to import.
|
|
@@ -18,9 +18,7 @@ export function luaLoad(code: LuaValue, sf: LuaStackFrame): LuaValue {
|
|
|
18
18
|
|
|
19
19
|
// Be vocal when no _GLOBAL is set
|
|
20
20
|
if (!globalEnvMaybe) {
|
|
21
|
-
console.warn(
|
|
22
|
-
"load() called without _GLOBAL in thread-local environment",
|
|
23
|
-
);
|
|
21
|
+
console.warn("load() called without _GLOBAL in thread-local environment");
|
|
24
22
|
return new LuaMultiRes([null, "Global environment not set"]);
|
|
25
23
|
}
|
|
26
24
|
|
|
@@ -4,7 +4,11 @@ import {
|
|
|
4
4
|
LuaRuntimeError,
|
|
5
5
|
LuaTable,
|
|
6
6
|
} from "../runtime.ts";
|
|
7
|
-
import { isNegativeZero, isTaggedFloat } from "../numeric.ts";
|
|
7
|
+
import { isNegativeZero, isTaggedFloat, makeLuaFloat } from "../numeric.ts";
|
|
8
|
+
import { LuaPRNG } from "./prng.ts";
|
|
9
|
+
|
|
10
|
+
// One PRNG per module load, auto-seeded at startup
|
|
11
|
+
const prng = new LuaPRNG();
|
|
8
12
|
|
|
9
13
|
// Fast unwrap: avoids function call overhead for the common plain-number case
|
|
10
14
|
function untagNumber(x: any): number {
|
|
@@ -40,6 +44,29 @@ export const mathApi = new LuaTable({
|
|
|
40
44
|
}
|
|
41
45
|
return null;
|
|
42
46
|
}),
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* If the value x is representable as a Lua integer, returns an integer
|
|
50
|
+
* with that value. Otherwise returns nil.
|
|
51
|
+
* Strings are NOT accepted — only Lua number values.
|
|
52
|
+
*/
|
|
53
|
+
tointeger: new LuaBuiltinFunction((_sf, x?: any) => {
|
|
54
|
+
if (typeof x === "number") {
|
|
55
|
+
return Number.isInteger(x) && Number.isFinite(x) ? x : null;
|
|
56
|
+
}
|
|
57
|
+
if (isTaggedFloat(x)) {
|
|
58
|
+
const n = x.value;
|
|
59
|
+
return Number.isInteger(n) && Number.isFinite(n) ? n : null;
|
|
60
|
+
}
|
|
61
|
+
if (typeof x === "string") {
|
|
62
|
+
const n = untagNumber(x); // Number(x) coerces the string
|
|
63
|
+
if (Number.isNaN(n) || !Number.isFinite(n) || !Number.isInteger(n))
|
|
64
|
+
return null;
|
|
65
|
+
return n;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}),
|
|
69
|
+
|
|
43
70
|
/**
|
|
44
71
|
* When called without arguments, returns a pseudo-random float with
|
|
45
72
|
* uniform distribution in the range [0,1). When called with two
|
|
@@ -52,51 +79,21 @@ export const mathApi = new LuaTable({
|
|
|
52
79
|
random: new LuaBuiltinFunction((_sf, m?: number, n?: number) => {
|
|
53
80
|
if (m !== undefined) m = untagNumber(m);
|
|
54
81
|
if (n !== undefined) n = untagNumber(n);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (!Number.isInteger(m)) {
|
|
61
|
-
throw new LuaRuntimeError(
|
|
62
|
-
"bad argument #1 to 'math.random' (integer expected)",
|
|
63
|
-
_sf,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (n === undefined) {
|
|
68
|
-
if (m! == 0) {
|
|
69
|
-
const high = Math.floor(Math.random() * 0x100000000);
|
|
70
|
-
const low = Math.floor(Math.random() * 0x100000000);
|
|
71
|
-
let result = (BigInt(high) << 32n) | BigInt(low);
|
|
72
|
-
if (result & (1n << 63n)) {
|
|
73
|
-
result -= 1n << 64n;
|
|
74
|
-
}
|
|
75
|
-
return result;
|
|
76
|
-
}
|
|
77
|
-
if (m! < 1) {
|
|
78
|
-
throw new LuaRuntimeError(
|
|
79
|
-
"bad argument #1 to 'math.random' (interval is empty)",
|
|
80
|
-
_sf,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
return Math.floor(Math.random() * m!) + 1;
|
|
82
|
+
try {
|
|
83
|
+
return prng.random(m, n);
|
|
84
|
+
} catch (e: any) {
|
|
85
|
+
throw new LuaRuntimeError(e.message, _sf);
|
|
84
86
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"bad argument #1 to 'math.random' (interval is empty)",
|
|
96
|
-
_sf,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
return Math.floor(Math.random() * (n! - m! + 1)) + m!;
|
|
87
|
+
}),
|
|
88
|
+
/**
|
|
89
|
+
* Seeds the pseudo-random generator. With no arguments, uses a
|
|
90
|
+
* time-based seed. Returns the two seed integers used (Lua 5.4 contract).
|
|
91
|
+
*/
|
|
92
|
+
randomseed: new LuaBuiltinFunction((_sf, x?: number, y?: number) => {
|
|
93
|
+
if (x !== undefined) x = untagNumber(x);
|
|
94
|
+
if (y !== undefined) y = untagNumber(y);
|
|
95
|
+
const [s1, s2] = prng.randomseed(x, y);
|
|
96
|
+
return new LuaMultiRes([s1, s2]);
|
|
100
97
|
}),
|
|
101
98
|
|
|
102
99
|
// Basic functions
|
|
@@ -104,23 +101,51 @@ export const mathApi = new LuaTable({
|
|
|
104
101
|
ceil: new LuaBuiltinFunction((_sf, x: number) => Math.ceil(untagNumber(x))),
|
|
105
102
|
floor: new LuaBuiltinFunction((_sf, x: number) => Math.floor(untagNumber(x))),
|
|
106
103
|
max: new LuaBuiltinFunction((_sf, ...args: number[]) =>
|
|
107
|
-
Math.max(...args.map(untagNumber))
|
|
104
|
+
Math.max(...args.map(untagNumber)),
|
|
108
105
|
),
|
|
109
106
|
min: new LuaBuiltinFunction((_sf, ...args: number[]) =>
|
|
110
|
-
Math.min(...args.map(untagNumber))
|
|
107
|
+
Math.min(...args.map(untagNumber)),
|
|
111
108
|
),
|
|
112
109
|
|
|
113
110
|
// Rounding and remainder
|
|
114
|
-
fmod: new LuaBuiltinFunction(
|
|
115
|
-
untagNumber(x) % untagNumber(y)
|
|
111
|
+
fmod: new LuaBuiltinFunction(
|
|
112
|
+
(_sf, x: number, y: number) => untagNumber(x) % untagNumber(y),
|
|
116
113
|
),
|
|
117
114
|
modf: new LuaBuiltinFunction((_sf, x: number) => {
|
|
118
115
|
const xn = untagNumber(x);
|
|
119
116
|
const int = Math.trunc(xn);
|
|
120
|
-
|
|
117
|
+
// Guarantee that the `frac` part is always Lua float
|
|
118
|
+
const frac = makeLuaFloat(xn - int);
|
|
121
119
|
return new LuaMultiRes([int, frac]);
|
|
122
120
|
}),
|
|
123
121
|
|
|
122
|
+
// Returns m and e such that x = m * 2^e, 0.5 <= |m| < 1 (or m=0 when x=0).
|
|
123
|
+
// e is an integer. Mirrors C99/Lua.
|
|
124
|
+
// Special cases: frexp(0) = (0, 0); frexp(+-inf/nan) = (x, 0).
|
|
125
|
+
frexp: new LuaBuiltinFunction((_sf, x: number) => {
|
|
126
|
+
const xn = untagNumber(x);
|
|
127
|
+
if (xn === 0 || !Number.isFinite(xn) || Number.isNaN(xn)) {
|
|
128
|
+
return new LuaMultiRes([xn, 0]);
|
|
129
|
+
}
|
|
130
|
+
const abs = Math.abs(xn);
|
|
131
|
+
let e = Math.floor(Math.log2(abs)) + 1;
|
|
132
|
+
let m = xn / 2 ** e;
|
|
133
|
+
if (Math.abs(m) >= 1.0) {
|
|
134
|
+
e += 1;
|
|
135
|
+
m /= 2;
|
|
136
|
+
}
|
|
137
|
+
if (Math.abs(m) < 0.5) {
|
|
138
|
+
e -= 1;
|
|
139
|
+
m *= 2;
|
|
140
|
+
}
|
|
141
|
+
return new LuaMultiRes([m, e]);
|
|
142
|
+
}),
|
|
143
|
+
|
|
144
|
+
// Returns m * 2^e (the inverse of frexp). Mirrors C99/Lua.
|
|
145
|
+
ldexp: new LuaBuiltinFunction(
|
|
146
|
+
(_sf, m: number, e: number) => untagNumber(m) * 2 ** untagNumber(e),
|
|
147
|
+
),
|
|
148
|
+
|
|
124
149
|
// Power and logarithms
|
|
125
150
|
exp: new LuaBuiltinFunction((_sf, x: number) => Math.exp(untagNumber(x))),
|
|
126
151
|
log: new LuaBuiltinFunction((_sf, x: number, base?: number) => {
|
|
@@ -129,8 +154,9 @@ export const mathApi = new LuaTable({
|
|
|
129
154
|
}
|
|
130
155
|
return Math.log(untagNumber(x)) / Math.log(untagNumber(base));
|
|
131
156
|
}),
|
|
132
|
-
|
|
133
|
-
|
|
157
|
+
// Power function (deprecated in Lua 5.4 but retained for compatibility)
|
|
158
|
+
pow: new LuaBuiltinFunction(
|
|
159
|
+
(_sf, x: number, y: number) => untagNumber(x) ** untagNumber(y),
|
|
134
160
|
),
|
|
135
161
|
sqrt: new LuaBuiltinFunction((_sf, x: number) => Math.sqrt(untagNumber(x))),
|
|
136
162
|
|
|
@@ -147,20 +173,20 @@ export const mathApi = new LuaTable({
|
|
|
147
173
|
return Math.atan2(untagNumber(y), untagNumber(x));
|
|
148
174
|
}),
|
|
149
175
|
|
|
150
|
-
// Hyperbolic functions
|
|
176
|
+
// Hyperbolic functions (deprecated in Lua 5.4 but retained for compatibility)
|
|
151
177
|
cosh: new LuaBuiltinFunction((_sf, x: number) => Math.cosh(untagNumber(x))),
|
|
152
178
|
sinh: new LuaBuiltinFunction((_sf, x: number) => Math.sinh(untagNumber(x))),
|
|
153
179
|
tanh: new LuaBuiltinFunction((_sf, x: number) => Math.tanh(untagNumber(x))),
|
|
154
180
|
|
|
155
181
|
// Additional utility
|
|
156
|
-
deg: new LuaBuiltinFunction(
|
|
157
|
-
untagNumber(x) * 180 / Math.PI
|
|
182
|
+
deg: new LuaBuiltinFunction(
|
|
183
|
+
(_sf, x: number) => (untagNumber(x) * 180) / Math.PI,
|
|
158
184
|
),
|
|
159
|
-
rad: new LuaBuiltinFunction(
|
|
160
|
-
untagNumber(x) * Math.PI / 180
|
|
185
|
+
rad: new LuaBuiltinFunction(
|
|
186
|
+
(_sf, x: number) => (untagNumber(x) * Math.PI) / 180,
|
|
161
187
|
),
|
|
162
188
|
ult: new LuaBuiltinFunction((_sf, m: number, n: number) => {
|
|
163
|
-
return
|
|
189
|
+
return untagNumber(m) >>> 0 < untagNumber(n) >>> 0;
|
|
164
190
|
}),
|
|
165
191
|
|
|
166
192
|
// Keep the cosineSimilarity utility function
|
|
@@ -14,17 +14,18 @@ export const netApi = new LuaTable({
|
|
|
14
14
|
): Promise<ProxyFetchResponse> => {
|
|
15
15
|
// JSONify any non-serializable body
|
|
16
16
|
if (
|
|
17
|
-
options?.body &&
|
|
17
|
+
options?.body &&
|
|
18
|
+
typeof options.body !== "string" &&
|
|
18
19
|
!(options.body instanceof Uint8Array)
|
|
19
20
|
) {
|
|
20
21
|
options.body = JSON.stringify(options.body);
|
|
21
22
|
}
|
|
22
23
|
const fetchOptions = options
|
|
23
24
|
? {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
method: options.method,
|
|
26
|
+
headers: {} as Record<string, string>,
|
|
27
|
+
body: options.body,
|
|
28
|
+
}
|
|
28
29
|
: {};
|
|
29
30
|
fetchOptions.headers = buildProxyHeaders(options.headers);
|
|
30
31
|
const resp = await client.httpSpacePrimitives.authenticatedFetch(
|
|
@@ -40,19 +41,27 @@ export const netApi = new LuaTable({
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
// Do sensible things with the body based on the content type
|
|
44
|
+
// Read as ArrayBuffer first to safely handle empty responses (e.g.
|
|
45
|
+
// PUT/DELETE returning 204 with Content-Type: application/json).
|
|
46
|
+
// resp.arrayBuffer() never throws on an empty body, whereas
|
|
47
|
+
// resp.json() would throw a SyntaxError.
|
|
48
|
+
const rawBytes = new Uint8Array(await resp.arrayBuffer());
|
|
43
49
|
let body: any;
|
|
44
|
-
const contentTypeHeader =
|
|
50
|
+
const contentTypeHeader =
|
|
51
|
+
options.responseEncoding ||
|
|
45
52
|
resp.headers.get("x-proxy-header-content-type");
|
|
46
53
|
const statusCode = +(resp.headers.get("x-proxy-status-code") || "200");
|
|
47
|
-
if (
|
|
48
|
-
body =
|
|
54
|
+
if (rawBytes.length === 0) {
|
|
55
|
+
body = null;
|
|
56
|
+
} else if (contentTypeHeader?.startsWith("application/json")) {
|
|
57
|
+
body = JSON.parse(new TextDecoder().decode(rawBytes));
|
|
49
58
|
} else if (
|
|
50
59
|
contentTypeHeader?.startsWith("application/xml") ||
|
|
51
60
|
contentTypeHeader?.startsWith("text/")
|
|
52
61
|
) {
|
|
53
|
-
body =
|
|
62
|
+
body = new TextDecoder().decode(rawBytes);
|
|
54
63
|
} else {
|
|
55
|
-
body =
|
|
64
|
+
body = rawBytes;
|
|
56
65
|
}
|
|
57
66
|
return {
|
|
58
67
|
ok: resp.ok,
|
|
@@ -66,7 +75,7 @@ export const netApi = new LuaTable({
|
|
|
66
75
|
(uri: string, options: { uri?: string; encoding?: string } = {}) => {
|
|
67
76
|
options.uri = uri;
|
|
68
77
|
return client.clientSystem.serviceRegistry.invokeBestMatch(
|
|
69
|
-
|
|
78
|
+
`net.readURI:${uri}`,
|
|
70
79
|
options,
|
|
71
80
|
);
|
|
72
81
|
},
|
|
@@ -74,7 +83,7 @@ export const netApi = new LuaTable({
|
|
|
74
83
|
writeURI: new LuaNativeJSFunction(
|
|
75
84
|
(uri: string, content: string | Uint8Array) => {
|
|
76
85
|
return client.clientSystem.serviceRegistry.invokeBestMatch(
|
|
77
|
-
|
|
86
|
+
`net.writeURI:${uri}`,
|
|
78
87
|
{ uri, content },
|
|
79
88
|
);
|
|
80
89
|
},
|
|
@@ -85,8 +94,11 @@ export const netApi = new LuaTable({
|
|
|
85
94
|
function buildProxyUrl(client: Client, url: string) {
|
|
86
95
|
url = url.replace(/^https?:\/\//, "");
|
|
87
96
|
// Strip off the /.fs and replace with /.proxy
|
|
88
|
-
return
|
|
89
|
-
|
|
97
|
+
return (
|
|
98
|
+
client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) +
|
|
99
|
+
"/.proxy/" +
|
|
100
|
+
url
|
|
101
|
+
);
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
function buildProxyHeaders(headers?: Record<string, any>): Record<string, any> {
|
|
@@ -100,9 +112,7 @@ function buildProxyHeaders(headers?: Record<string, any>): Record<string, any> {
|
|
|
100
112
|
return newHeaders;
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
function extractProxyHeaders(
|
|
104
|
-
headers: Headers,
|
|
105
|
-
): Record<string, any> {
|
|
115
|
+
function extractProxyHeaders(headers: Headers): Record<string, any> {
|
|
106
116
|
const newHeaders: Record<string, any> = {};
|
|
107
117
|
for (const [key, value] of headers.entries()) {
|
|
108
118
|
if (key.toLowerCase().startsWith("x-proxy-header-")) {
|