@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.
Files changed (97) hide show
  1. package/README.md +19 -4
  2. package/client/asset_bundle/bundle.ts +3 -9
  3. package/client/data/datastore.ts +4 -5
  4. package/client/markdown_parser/constants.ts +5 -4
  5. package/client/plugos/hooks/code_widget.ts +3 -8
  6. package/client/plugos/hooks/command.ts +8 -8
  7. package/client/plugos/hooks/document_editor.ts +10 -15
  8. package/client/plugos/hooks/event.ts +33 -36
  9. package/client/plugos/hooks/mq.ts +17 -17
  10. package/client/plugos/hooks/plug_namespace.ts +3 -8
  11. package/client/plugos/hooks/slash_command.ts +13 -28
  12. package/client/plugos/hooks/syscall.ts +3 -3
  13. package/client/plugos/manifest_cache.ts +22 -15
  14. package/client/plugos/plug.ts +2 -6
  15. package/client/plugos/plug_compile.ts +79 -78
  16. package/client/plugos/protocol.ts +28 -28
  17. package/client/plugos/proxy_fetch.ts +7 -6
  18. package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
  19. package/client/plugos/sandboxes/worker_sandbox.ts +18 -18
  20. package/client/plugos/syscalls/asset.ts +1 -3
  21. package/client/plugos/syscalls/code_widget.ts +1 -3
  22. package/client/plugos/syscalls/config.ts +1 -5
  23. package/client/plugos/syscalls/datastore.ts +1 -1
  24. package/client/plugos/syscalls/editor.ts +72 -69
  25. package/client/plugos/syscalls/event.ts +9 -12
  26. package/client/plugos/syscalls/fetch.ts +31 -23
  27. package/client/plugos/syscalls/index.ts +10 -1
  28. package/client/plugos/syscalls/jsonschema.ts +72 -32
  29. package/client/plugos/syscalls/language.ts +9 -5
  30. package/client/plugos/syscalls/markdown.ts +29 -7
  31. package/client/plugos/syscalls/mq.ts +4 -12
  32. package/client/plugos/syscalls/service_registry.ts +1 -4
  33. package/client/plugos/syscalls/shell.ts +2 -5
  34. package/client/plugos/syscalls/space.ts +1 -1
  35. package/client/plugos/syscalls/sync.ts +69 -60
  36. package/client/plugos/syscalls/system.ts +2 -3
  37. package/client/plugos/system.ts +6 -12
  38. package/client/plugos/worker_runtime.ts +12 -33
  39. package/client/space_lua/aggregates.ts +782 -0
  40. package/client/space_lua/ast.ts +42 -8
  41. package/client/space_lua/ast_narrow.ts +4 -2
  42. package/client/space_lua/eval.ts +886 -575
  43. package/client/space_lua/labels.ts +7 -12
  44. package/client/space_lua/liq_null.ts +6 -0
  45. package/client/space_lua/numeric.ts +5 -8
  46. package/client/space_lua/parse.ts +346 -120
  47. package/client/space_lua/query_collection.ts +926 -82
  48. package/client/space_lua/query_env.ts +26 -0
  49. package/client/space_lua/render_lua_markdown.ts +369 -0
  50. package/client/space_lua/rp.ts +5 -4
  51. package/client/space_lua/runtime.ts +288 -155
  52. package/client/space_lua/stdlib/format.ts +53 -39
  53. package/client/space_lua/stdlib/js.ts +3 -7
  54. package/client/space_lua/stdlib/load.ts +1 -3
  55. package/client/space_lua/stdlib/math.ts +84 -58
  56. package/client/space_lua/stdlib/net.ts +27 -17
  57. package/client/space_lua/stdlib/os.ts +81 -85
  58. package/client/space_lua/stdlib/pattern.ts +695 -0
  59. package/client/space_lua/stdlib/prng.ts +148 -0
  60. package/client/space_lua/stdlib/space_lua.ts +17 -23
  61. package/client/space_lua/stdlib/string.ts +102 -190
  62. package/client/space_lua/stdlib/string_pack.ts +490 -0
  63. package/client/space_lua/stdlib/table.ts +76 -16
  64. package/client/space_lua/stdlib.ts +53 -39
  65. package/client/space_lua/tonumber.ts +82 -42
  66. package/client/space_lua/util.ts +53 -15
  67. package/dist/plug-compile.js +55 -98
  68. package/package.json +27 -20
  69. package/plug-api/constants.ts +0 -32
  70. package/plug-api/lib/async.ts +20 -7
  71. package/plug-api/lib/crypto.ts +16 -17
  72. package/plug-api/lib/dates.ts +15 -7
  73. package/plug-api/lib/json.ts +11 -5
  74. package/plug-api/lib/limited_map.ts +1 -1
  75. package/plug-api/lib/native_fetch.ts +2 -0
  76. package/plug-api/lib/ref.ts +23 -23
  77. package/plug-api/lib/resolve.ts +7 -11
  78. package/plug-api/lib/tags.ts +13 -4
  79. package/plug-api/lib/transclusion.ts +10 -21
  80. package/plug-api/lib/tree.ts +165 -45
  81. package/plug-api/lib/yaml.ts +35 -25
  82. package/plug-api/syscalls/asset.ts +1 -1
  83. package/plug-api/syscalls/config.ts +1 -4
  84. package/plug-api/syscalls/editor.ts +15 -15
  85. package/plug-api/syscalls/jsonschema.ts +1 -3
  86. package/plug-api/syscalls/lua.ts +3 -9
  87. package/plug-api/syscalls/mq.ts +1 -4
  88. package/plug-api/syscalls/shell.ts +4 -1
  89. package/plug-api/syscalls/space.ts +3 -10
  90. package/plug-api/syscalls/system.ts +1 -4
  91. package/plug-api/syscalls/yaml.ts +2 -6
  92. package/plug-api/system_mock.ts +0 -1
  93. package/plug-api/types/client.ts +16 -1
  94. package/plug-api/types/event.ts +6 -4
  95. package/plug-api/types/manifest.ts +8 -9
  96. package/plugs/builtin_plugs.ts +2 -2
  97. package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
@@ -1,4 +1,5 @@
1
1
  import {
2
+ getMetatable,
2
3
  type ILuaFunction,
3
4
  isILuaFunction,
4
5
  isLuaTable,
@@ -17,6 +18,7 @@ import {
17
18
  luaToString,
18
19
  luaTypeOf,
19
20
  type LuaValue,
21
+ singleResult,
20
22
  } from "./runtime.ts";
21
23
  import { stringApi } from "./stdlib/string.ts";
22
24
  import { tableApi } from "./stdlib/table.ts";
@@ -32,17 +34,16 @@ import { luaLoad } from "./stdlib/load.ts";
32
34
  import { cryptoApi } from "./stdlib/crypto.ts";
33
35
  import { netApi } from "./stdlib/net.ts";
34
36
  import { isTaggedFloat, makeLuaFloat } from "./numeric.ts";
37
+ import { isPromise } from "./rp.ts";
38
+ import { isSqlNull } from "./liq_null.ts";
35
39
 
36
40
  const printFunction = new LuaBuiltinFunction(async (_sf, ...args) => {
37
- console.log(
38
- "[Lua]",
39
- ...(await Promise.all(args.map((v) => luaToString(v)))),
40
- );
41
+ console.log("[Lua]", ...(await Promise.all(args.map((v) => luaToString(v)))));
41
42
  });
42
43
 
43
44
  const assertFunction = new LuaBuiltinFunction(
44
45
  async (sf, value: any, message?: string) => {
45
- if (!await value) {
46
+ if (!(await value)) {
46
47
  throw new LuaRuntimeError(`Assertion failed: ${message}`, sf);
47
48
  }
48
49
  },
@@ -121,23 +122,38 @@ export const eachFunction = new LuaBuiltinFunction(
121
122
  },
122
123
  );
123
124
 
124
- const unpackFunction = new LuaBuiltinFunction(async (sf, t: LuaTable) => {
125
- const values: LuaValue[] = [];
126
- for (let i = 1; i <= (t as any).length; i++) {
127
- values.push(await luaGet(t, i, sf.astCtx ?? null, sf));
128
- }
129
- return new LuaMultiRes(values);
130
- });
131
-
132
125
  const typeFunction = new LuaBuiltinFunction(
133
126
  (_sf, value: LuaValue): string | Promise<string> => {
134
127
  return luaTypeOf(value);
135
128
  },
136
129
  );
137
130
 
138
- const tostringFunction = new LuaBuiltinFunction((_sf, value: any) => {
139
- return luaToString(value);
140
- });
131
+ // tostring() checks `__tostring` metamethod first (with live SF), then
132
+ // falls back to the default `luaToString` representation.
133
+ const tostringFunction = new LuaBuiltinFunction(
134
+ (sf, value: any): string | Promise<string> => {
135
+ const mt = getMetatable(value, sf);
136
+ if (mt) {
137
+ const mm = mt.rawGet("__tostring");
138
+ if (mm !== undefined && mm !== null) {
139
+ const ctx = sf.astCtx ?? {};
140
+ const r = luaCall(mm, [value], ctx as any, sf);
141
+ const unwrap = (v: any): string => {
142
+ const s = singleResult(v);
143
+ if (typeof s !== "string") {
144
+ throw new LuaRuntimeError("'__tostring' must return a string", sf);
145
+ }
146
+ return s;
147
+ };
148
+ if (isPromise(r)) {
149
+ return (r as Promise<any>).then(unwrap);
150
+ }
151
+ return unwrap(r);
152
+ }
153
+ }
154
+ return luaToString(value);
155
+ },
156
+ );
141
157
 
142
158
  const tonumberFunction = new LuaBuiltinFunction(
143
159
  (sf, value: LuaValue, base?: number) => {
@@ -182,10 +198,7 @@ async function pcallBoundary(
182
198
  sf: LuaStackFrame,
183
199
  fn: ILuaFunction,
184
200
  args: LuaValue[],
185
- ): Promise<
186
- | { ok: true; values: LuaValue[] }
187
- | { ok: false; message: string }
188
- > {
201
+ ): Promise<{ ok: true; values: LuaValue[] } | { ok: false; message: string }> {
189
202
  const closeStack = luaEnsureCloseStack(sf);
190
203
  const mark = closeStack.length;
191
204
 
@@ -253,11 +266,9 @@ const setmetatableFunction = new LuaBuiltinFunction(
253
266
  },
254
267
  );
255
268
 
256
- const rawlenFunction = new LuaBuiltinFunction(
257
- (_sf, value: LuaValue) => {
258
- return luaLen(value, _sf);
259
- },
260
- );
269
+ const rawlenFunction = new LuaBuiltinFunction((_sf, value: LuaValue) => {
270
+ return luaLen(value, _sf, true);
271
+ });
261
272
 
262
273
  const rawsetFunction = new LuaBuiltinFunction(
263
274
  (_sf, table: LuaTable, key: LuaValue, value: LuaValue) => {
@@ -269,7 +280,8 @@ const rawgetFunction = new LuaBuiltinFunction(
269
280
  (_sf, table: any, key: LuaValue) => {
270
281
  const isArray = Array.isArray(table);
271
282
 
272
- const isPlainObj = typeof table === "object" &&
283
+ const isPlainObj =
284
+ typeof table === "object" &&
273
285
  table !== null &&
274
286
  (table as any).constructor === Object;
275
287
 
@@ -299,7 +311,7 @@ const rawgetFunction = new LuaBuiltinFunction(
299
311
 
300
312
  if (isLuaTable(table)) {
301
313
  const v = table.rawGet(key);
302
- return v === undefined ? null : v;
314
+ return v === undefined || isSqlNull(v) ? null : v;
303
315
  }
304
316
 
305
317
  const k = isTaggedFloat(key) ? key.value : key;
@@ -318,13 +330,11 @@ const rawgetFunction = new LuaBuiltinFunction(
318
330
  },
319
331
  );
320
332
 
321
- const rawequalFunction = new LuaBuiltinFunction(
322
- (_sf, a: any, b: any) => {
323
- const av = isTaggedFloat(a) ? a.value : a;
324
- const bv = isTaggedFloat(b) ? b.value : b;
325
- return av === bv;
326
- },
327
- );
333
+ const rawequalFunction = new LuaBuiltinFunction((_sf, a: any, b: any) => {
334
+ const av = isTaggedFloat(a) ? a.value : a;
335
+ const bv = isTaggedFloat(b) ? b.value : b;
336
+ return av === bv;
337
+ });
328
338
 
329
339
  const getmetatableFunction = new LuaBuiltinFunction((_sf, table: LuaTable) => {
330
340
  return (table as any).metatable;
@@ -332,12 +342,12 @@ const getmetatableFunction = new LuaBuiltinFunction((_sf, table: LuaTable) => {
332
342
 
333
343
  const dofileFunction = new LuaBuiltinFunction(async (sf, filename: string) => {
334
344
  const global = sf.threadLocal.get("_GLOBAL") as LuaEnv;
335
- const file = await luaCall(
345
+ const file = (await luaCall(
336
346
  (global.get("space") as any).get("readFile"),
337
347
  [filename],
338
348
  sf.astCtx!,
339
349
  sf,
340
- ) as Uint8Array;
350
+ )) as Uint8Array;
341
351
  const code = new TextDecoder().decode(file);
342
352
  try {
343
353
  const parsedExpr = parse(code);
@@ -413,7 +423,8 @@ const nextFunction = new LuaBuiltinFunction(
413
423
  }
414
424
  // Find index in the key list
415
425
  const idx = keys.indexOf(index);
416
- if (idx === -1) { // Not found
426
+ if (idx === -1) {
427
+ // Not found
417
428
  throw new LuaRuntimeError("invalid key to 'next': key not found", sf);
418
429
  }
419
430
  const key = keys[idx + 1];
@@ -429,7 +440,7 @@ const nextFunction = new LuaBuiltinFunction(
429
440
  const someFunction = new LuaBuiltinFunction(async (_sf, value: any) => {
430
441
  switch (await luaTypeOf(value)) {
431
442
  case "number":
432
- if (!isFinite(value)) return null;
443
+ if (!Number.isFinite(value)) return null;
433
444
  break;
434
445
  case "string":
435
446
  if (value.trim() === "") return null;
@@ -446,13 +457,16 @@ export function luaBuildStandardEnv() {
446
457
  const env = new LuaEnv();
447
458
  // _G global
448
459
  env.set("_G", env);
460
+ // Lua version string - for now it signals Lua 5.4 compatibility with
461
+ // selective 5.5 features; kept non-standard so callers can distinguish
462
+ // Space Lua from a plain Lua runtime.
463
+ env.set("_VERSION", "Lua 5.4+");
449
464
  // Top-level builtins
450
465
  env.set("print", printFunction);
451
466
  env.set("assert", assertFunction);
452
467
  env.set("type", typeFunction);
453
468
  env.set("tostring", tostringFunction);
454
469
  env.set("tonumber", tonumberFunction);
455
- env.set("unpack", unpackFunction);
456
470
  env.set("select", selectFunction);
457
471
  env.set("next", nextFunction);
458
472
  // Iterators
@@ -18,7 +18,8 @@ function skipSpace(s: string, i: number): number {
18
18
  const n = s.length;
19
19
  while (i < n) {
20
20
  const c = s.charCodeAt(i);
21
- if (c === 32 || (c >= 9 && c <= 13)) { // SP, HT, LF, VT, FF, CR
21
+ if (c === 32 || (c >= 9 && c <= 13)) {
22
+ // SP, HT, LF, VT, FF, CR
22
23
  i++;
23
24
  } else {
24
25
  break;
@@ -28,21 +29,24 @@ function skipSpace(s: string, i: number): number {
28
29
  }
29
30
 
30
31
  function charToDigitBase(c: number, base: number): number {
31
- if (c >= 48 && c <= 57) { // '0'..'9'
32
+ if (c >= 48 && c <= 57) {
33
+ // '0'..'9'
32
34
  const v = c - 48;
33
35
  if (v < base) {
34
36
  return v;
35
37
  }
36
38
  return -1;
37
39
  }
38
- if (c >= 65 && c <= 90) { // 'A'..'Z'
40
+ if (c >= 65 && c <= 90) {
41
+ // 'A'..'Z'
39
42
  const v = 10 + (c - 65);
40
43
  if (v < base) {
41
44
  return v;
42
45
  }
43
46
  return -1;
44
47
  }
45
- if (c >= 97 && c <= 122) { // 'a'..'z'
48
+ if (c >= 97 && c <= 122) {
49
+ // 'a'..'z'
46
50
  const v = 10 + (c - 97);
47
51
  if (v < base) {
48
52
  return v;
@@ -64,10 +68,12 @@ function parseIntWithBase(
64
68
  }
65
69
 
66
70
  let sign = 1;
67
- if (s.charCodeAt(i) === 45) { // '-'
71
+ if (s.charCodeAt(i) === 45) {
72
+ // '-'
68
73
  sign = -1;
69
74
  i++;
70
- } else if (s.charCodeAt(i) === 43) { // '+'
75
+ } else if (s.charCodeAt(i) === 43) {
76
+ // '+'
71
77
  i++;
72
78
  }
73
79
 
@@ -80,7 +86,8 @@ function parseIntWithBase(
80
86
 
81
87
  while (i < n) {
82
88
  const c = s.charCodeAt(i);
83
- const isAlnum = (c >= 48 && c <= 57) || // '0'..'9'
89
+ const isAlnum =
90
+ (c >= 48 && c <= 57) || // '0'..'9'
84
91
  (c >= 65 && c <= 90) || // 'A'..'Z'
85
92
  (c >= 97 && c <= 122); // 'a'..'z'
86
93
 
@@ -119,10 +126,12 @@ function parseInt(s: string): { ok: boolean; value: number } {
119
126
  }
120
127
 
121
128
  let neg = false;
122
- if (s.charCodeAt(i) === 45) { // '-'
129
+ if (s.charCodeAt(i) === 45) {
130
+ // '-'
123
131
  neg = true;
124
132
  i++;
125
- } else if (s.charCodeAt(i) === 43) { // '+'
133
+ } else if (s.charCodeAt(i) === 43) {
134
+ // '+'
126
135
  i++;
127
136
  }
128
137
 
@@ -134,18 +143,23 @@ function parseInt(s: string): { ok: boolean; value: number } {
134
143
  let any = false;
135
144
 
136
145
  // hex?
137
- if (s.charCodeAt(i) === 48 && i + 1 < n) { // '0'
146
+ if (s.charCodeAt(i) === 48 && i + 1 < n) {
147
+ // '0'
138
148
  const x = s.charCodeAt(i + 1);
139
- if (x === 120 || x === 88) { // 'x' or 'X'
149
+ if (x === 120 || x === 88) {
150
+ // 'x' or 'X'
140
151
  i += 2;
141
152
  while (i < n) {
142
153
  const c = s.charCodeAt(i);
143
154
  let d = -1;
144
- if (c >= 48 && c <= 57) { // '0'..'9'
155
+ if (c >= 48 && c <= 57) {
156
+ // '0'..'9'
145
157
  d = c - 48; // '0'
146
- } else if (c >= 65 && c <= 70) { // 'A'..'F'
158
+ } else if (c >= 65 && c <= 70) {
159
+ // 'A'..'F'
147
160
  d = 10 + (c - 65);
148
- } else if (c >= 97 && c <= 102) { // 'a'..'f'
161
+ } else if (c >= 97 && c <= 102) {
162
+ // 'a'..'f'
149
163
  d = 10 + (c - 97);
150
164
  } else {
151
165
  d = -1;
@@ -168,7 +182,8 @@ function parseInt(s: string): { ok: boolean; value: number } {
168
182
  // decimal integer
169
183
  while (i < n) {
170
184
  const c = s.charCodeAt(i);
171
- if (c < 48 || c > 57) { // not '0'..'9'
185
+ if (c < 48 || c > 57) {
186
+ // not '0'..'9'
172
187
  break;
173
188
  }
174
189
  acc = acc * 10 + (c - 48);
@@ -193,10 +208,12 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
193
208
 
194
209
  let sign = 1;
195
210
  const c0 = s.charCodeAt(i);
196
- if (c0 === 45) { // '-'
211
+ if (c0 === 45) {
212
+ // '-'
197
213
  sign = -1;
198
214
  i++;
199
- } else if (c0 === 43) { // '+'
215
+ } else if (c0 === 43) {
216
+ // '+'
200
217
  i++;
201
218
  }
202
219
 
@@ -207,7 +224,8 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
207
224
  // integer part
208
225
  while (i < n) {
209
226
  const c = s.charCodeAt(i);
210
- if (c < 48 || c > 57) { // not '0'..'9'
227
+ if (c < 48 || c > 57) {
228
+ // not '0'..'9'
211
229
  break;
212
230
  }
213
231
  val = val * 10 + (c - 48);
@@ -217,12 +235,14 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
217
235
 
218
236
  // fractional part
219
237
  if (i < n) {
220
- if (s.charCodeAt(i) === 46) { // '.'
238
+ if (s.charCodeAt(i) === 46) {
239
+ // '.'
221
240
  i++;
222
241
  let scale = 1;
223
242
  while (i < n) {
224
243
  const c = s.charCodeAt(i);
225
- if (c < 48 || c > 57) { // not '0'..'9'
244
+ if (c < 48 || c > 57) {
245
+ // not '0'..'9'
226
246
  break;
227
247
  }
228
248
  scale *= 0.1;
@@ -238,7 +258,8 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
238
258
  let hasExp = false;
239
259
  if (i < n) {
240
260
  const ec = s.charCodeAt(i);
241
- if (ec === 101 || ec === 69) { // 'e' or 'E'
261
+ if (ec === 101 || ec === 69) {
262
+ // 'e' or 'E'
242
263
  hasExp = true;
243
264
  i++;
244
265
  if (i >= n) {
@@ -247,10 +268,12 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
247
268
  let expSign = 1;
248
269
  if (i < n) {
249
270
  const es = s.charCodeAt(i);
250
- if (es === 45) { // '-'
271
+ if (es === 45) {
272
+ // '-'
251
273
  expSign = -1;
252
274
  i++;
253
- } else if (es === 43) { // '+'
275
+ } else if (es === 43) {
276
+ // '+'
254
277
  i++;
255
278
  }
256
279
  }
@@ -260,7 +283,8 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
260
283
  let anyExp = false;
261
284
  while (i < n) {
262
285
  const c = s.charCodeAt(i);
263
- if (c < 48 || c > 57) { // not '0'..'9'
286
+ if (c < 48 || c > 57) {
287
+ // not '0'..'9'
264
288
  break;
265
289
  }
266
290
  exp = exp * 10 + (c - 48);
@@ -283,7 +307,7 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
283
307
  return { ok: false, value: 0 };
284
308
  }
285
309
 
286
- const result = sign * (hasExp ? val * Math.pow(10, exp) : val);
310
+ const result = sign * (hasExp ? val * 10 ** exp : val);
287
311
  return { ok: true, value: result };
288
312
  }
289
313
 
@@ -297,18 +321,22 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
297
321
 
298
322
  let sign = 1;
299
323
  const c0 = s.charCodeAt(i);
300
- if (c0 === 45) { // '-'
324
+ if (c0 === 45) {
325
+ // '-'
301
326
  sign = -1;
302
327
  i++;
303
- } else if (c0 === 43) { // '+'
328
+ } else if (c0 === 43) {
329
+ // '+'
304
330
  i++;
305
331
  }
306
332
 
307
- if (!(i + 1 < n && s.charCodeAt(i) === 48)) { // '0'
333
+ if (!(i + 1 < n && s.charCodeAt(i) === 48)) {
334
+ // '0'
308
335
  return { ok: false, value: 0 };
309
336
  }
310
337
  const x = s.charCodeAt(i + 1);
311
- if (!(x === 120 || x === 88)) { // 'x' or 'X'
338
+ if (!(x === 120 || x === 88)) {
339
+ // 'x' or 'X'
312
340
  return { ok: false, value: 0 };
313
341
  }
314
342
  i += 2;
@@ -323,11 +351,14 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
323
351
  while (i < n) {
324
352
  const c = s.charCodeAt(i);
325
353
  let d = -1;
326
- if (c >= 48 && c <= 57) { // '0'..'9'
354
+ if (c >= 48 && c <= 57) {
355
+ // '0'..'9'
327
356
  d = c - 48;
328
- } else if (c >= 65 && c <= 70) { // 'A'..'F'
357
+ } else if (c >= 65 && c <= 70) {
358
+ // 'A'..'F'
329
359
  d = 10 + (c - 65);
330
- } else if (c >= 97 && c <= 102) { // 'a'..'f'
360
+ } else if (c >= 97 && c <= 102) {
361
+ // 'a'..'f'
331
362
  d = 10 + (c - 97);
332
363
  } else {
333
364
  d = -1;
@@ -342,17 +373,21 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
342
373
 
343
374
  // optional fractional part
344
375
  if (i < n) {
345
- if (s.charCodeAt(i) === 46) { // '.'
376
+ if (s.charCodeAt(i) === 46) {
377
+ // '.'
346
378
  sawDot = true;
347
379
  i++;
348
380
  while (i < n) {
349
381
  const c = s.charCodeAt(i);
350
382
  let d = -1;
351
- if (c >= 48 && c <= 57) { // '0'..'9'
383
+ if (c >= 48 && c <= 57) {
384
+ // '0'..'9'
352
385
  d = c - 48;
353
- } else if (c >= 65 && c <= 70) { // 'A'..'F'
386
+ } else if (c >= 65 && c <= 70) {
387
+ // 'A'..'F'
354
388
  d = 10 + (c - 65);
355
- } else if (c >= 97 && c <= 102) { // 'a'..'f'
389
+ } else if (c >= 97 && c <= 102) {
390
+ // 'a'..'f'
356
391
  d = 10 + (c - 97);
357
392
  } else {
358
393
  d = -1;
@@ -376,14 +411,17 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
376
411
 
377
412
  if (i < n) {
378
413
  const ec = s.charCodeAt(i);
379
- if (ec === 112 || ec === 80) { // 'p' or 'P'
414
+ if (ec === 112 || ec === 80) {
415
+ // 'p' or 'P'
380
416
  i++;
381
417
  if (i < n) {
382
418
  const sc = s.charCodeAt(i);
383
- if (sc === 45) { // '-'
419
+ if (sc === 45) {
420
+ // '-'
384
421
  expSign = -1;
385
422
  i++;
386
- } else if (sc === 43) { // '+'
423
+ } else if (sc === 43) {
424
+ // '+'
387
425
  i++;
388
426
  }
389
427
  }
@@ -394,7 +432,8 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
394
432
  let anyExp = false;
395
433
  while (i < n) {
396
434
  const c = s.charCodeAt(i);
397
- if (c < 48 || c > 57) { // not '0'..'9'
435
+ if (c < 48 || c > 57) {
436
+ // not '0'..'9'
398
437
  break;
399
438
  }
400
439
  exp = exp * 10 + (c - 48);
@@ -433,8 +472,8 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
433
472
  return { ok: false, value: 0 };
434
473
  }
435
474
 
436
- const frac = fracVal === 0 ? 0 : (fracVal / fracScale);
437
- const result = sign * (intVal + frac) * Math.pow(2, expSign * exp);
475
+ const frac = fracVal === 0 ? 0 : fracVal / fracScale;
476
+ const result = sign * (intVal + frac) * 2 ** (expSign * exp);
438
477
  return { ok: true, value: result };
439
478
  }
440
479
 
@@ -460,6 +499,7 @@ export function luaToNumberDetailed(
460
499
  }
461
500
 
462
501
  {
502
+ // biome-ignore lint/correctness/useParseIntRadix: local parseInt function, not global
463
503
  const parsed = parseInt(s);
464
504
  if (parsed.ok) {
465
505
  const v = parsed.value;
@@ -6,7 +6,7 @@ export function evalPromiseValues(vals: any[]): Promise<any[]> | any[] {
6
6
  for (let i = 0; i < vals.length; i++) {
7
7
  if (isPromise(vals[i])) {
8
8
  promises.push(
9
- (vals[i] as Promise<any>).then((v: any) => promiseResults[i] = v),
9
+ (vals[i] as Promise<any>).then((v: any) => (promiseResults[i] = v)),
10
10
  );
11
11
  } else {
12
12
  promiseResults[i] = vals[i];
@@ -14,9 +14,8 @@ export function evalPromiseValues(vals: any[]): Promise<any[]> | any[] {
14
14
  }
15
15
  if (promises.length === 0) {
16
16
  return promiseResults;
17
- } else {
18
- return Promise.all(promises).then(() => promiseResults);
19
17
  }
18
+ return Promise.all(promises).then(() => promiseResults);
20
19
  }
21
20
 
22
21
  /**
@@ -33,21 +32,22 @@ async function getPivot(
33
32
  z: any,
34
33
  compare: (a: any, b: any) => Promise<number>,
35
34
  ) {
36
- if (await compare(x, y) < 0) {
37
- if (await compare(y, z) < 0) {
35
+ if ((await compare(x, y)) < 0) {
36
+ if ((await compare(y, z)) < 0) {
38
37
  return y;
39
- } else if (await compare(z, x) < 0) {
38
+ }
39
+ if ((await compare(z, x)) < 0) {
40
40
  return x;
41
- } else {
42
- return z;
43
41
  }
44
- } else if (await compare(y, z) > 0) {
42
+ return z;
43
+ }
44
+ if ((await compare(y, z)) > 0) {
45
45
  return y;
46
- } else if (await compare(z, x) > 0) {
46
+ }
47
+ if ((await compare(z, x)) > 0) {
47
48
  return x;
48
- } else {
49
- return z;
50
49
  }
50
+ return z;
51
51
  }
52
52
 
53
53
  /**
@@ -65,7 +65,9 @@ export async function asyncQuickSort(
65
65
  right = arr.length - 1,
66
66
  ) {
67
67
  if (left < right) {
68
- let i = left, j = right, tmp;
68
+ let i = left,
69
+ j = right,
70
+ tmp;
69
71
  const pivot = await getPivot(
70
72
  arr[i],
71
73
  arr[i + Math.floor((j - i) / 2)],
@@ -73,10 +75,10 @@ export async function asyncQuickSort(
73
75
  compare,
74
76
  );
75
77
  while (true) {
76
- while (await compare(arr[i], pivot) < 0) {
78
+ while ((await compare(arr[i], pivot)) < 0) {
77
79
  i++;
78
80
  }
79
- while (await compare(pivot, arr[j]) < 0) {
81
+ while ((await compare(pivot, arr[j])) < 0) {
80
82
  j--;
81
83
  }
82
84
  if (i >= j) {
@@ -94,3 +96,39 @@ export async function asyncQuickSort(
94
96
  }
95
97
  return arr;
96
98
  }
99
+
100
+ /**
101
+ * iterative async merge sort
102
+ * @param arr tagged array of { val, idx } elements
103
+ * @param compare async comparator returning <0, 0, or >0
104
+ */
105
+ export async function asyncMergeSort(
106
+ arr: { val: any; idx: number }[],
107
+ compare: (
108
+ a: { val: any; idx: number },
109
+ b: { val: any; idx: number },
110
+ ) => Promise<number>,
111
+ ): Promise<void> {
112
+ const n = arr.length;
113
+ if (n <= 1) return;
114
+ const work = new Array(n);
115
+
116
+ for (let size = 1; size < n; size *= 2) {
117
+ for (let left = 0; left < n; left += 2 * size) {
118
+ const mid = Math.min(left + size, n);
119
+ const right = Math.min(left + 2 * size, n);
120
+
121
+ let i = left,
122
+ j = mid,
123
+ k = left;
124
+ while (i < mid && j < right) {
125
+ const cmp = await compare(arr[i], arr[j]);
126
+ if (cmp <= 0) work[k++] = arr[i++];
127
+ else work[k++] = arr[j++];
128
+ }
129
+ while (i < mid) work[k++] = arr[i++];
130
+ while (j < right) work[k++] = arr[j++];
131
+ for (let m = left; m < right; m++) arr[m] = work[m];
132
+ }
133
+ }
134
+ }