@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.
Files changed (57) hide show
  1. package/README.md +20 -4
  2. package/client/markdown_parser/constants.ts +2 -2
  3. package/client/plugos/hooks/code_widget.ts +0 -3
  4. package/client/plugos/hooks/document_editor.ts +0 -3
  5. package/client/plugos/hooks/event.ts +1 -1
  6. package/client/plugos/hooks/mq.ts +1 -1
  7. package/client/plugos/hooks/plug_namespace.ts +0 -3
  8. package/client/plugos/hooks/slash_command.ts +2 -2
  9. package/client/plugos/plug.ts +0 -1
  10. package/client/plugos/plug_compile.ts +28 -29
  11. package/client/plugos/proxy_fetch.ts +1 -1
  12. package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
  13. package/client/plugos/sandboxes/worker_sandbox.ts +2 -3
  14. package/client/plugos/syscalls/editor.ts +12 -12
  15. package/client/plugos/syscalls/fetch.ts +1 -1
  16. package/client/plugos/syscalls/jsonschema.ts +1 -1
  17. package/client/plugos/syscalls/mq.ts +1 -1
  18. package/client/plugos/syscalls/space.ts +1 -1
  19. package/client/plugos/system.ts +2 -2
  20. package/client/plugos/worker_runtime.ts +8 -29
  21. package/client/space_lua/aggregates.ts +209 -0
  22. package/client/space_lua/ast.ts +24 -2
  23. package/client/space_lua/eval.ts +58 -53
  24. package/client/space_lua/labels.ts +1 -1
  25. package/client/space_lua/parse.ts +117 -12
  26. package/client/space_lua/query_collection.ts +850 -70
  27. package/client/space_lua/query_env.ts +26 -0
  28. package/client/space_lua/runtime.ts +47 -17
  29. package/client/space_lua/stdlib/format.ts +19 -19
  30. package/client/space_lua/stdlib/math.ts +73 -48
  31. package/client/space_lua/stdlib/net.ts +2 -2
  32. package/client/space_lua/stdlib/os.ts +5 -0
  33. package/client/space_lua/stdlib/pattern.ts +702 -0
  34. package/client/space_lua/stdlib/prng.ts +145 -0
  35. package/client/space_lua/stdlib/space_lua.ts +3 -8
  36. package/client/space_lua/stdlib/string.ts +103 -181
  37. package/client/space_lua/stdlib/string_pack.ts +486 -0
  38. package/client/space_lua/stdlib/table.ts +73 -9
  39. package/client/space_lua/stdlib.ts +38 -14
  40. package/client/space_lua/tonumber.ts +3 -2
  41. package/client/space_lua/util.ts +43 -9
  42. package/dist/plug-compile.js +23 -69
  43. package/dist/worker_runtime_bundle.js +233 -0
  44. package/package.json +16 -11
  45. package/plug-api/constants.ts +0 -32
  46. package/plug-api/lib/async.ts +2 -2
  47. package/plug-api/lib/crypto.ts +11 -11
  48. package/plug-api/lib/json.ts +1 -1
  49. package/plug-api/lib/limited_map.ts +1 -1
  50. package/plug-api/lib/native_fetch.ts +2 -0
  51. package/plug-api/lib/ref.ts +5 -5
  52. package/plug-api/lib/transclusion.ts +5 -5
  53. package/plug-api/lib/tree.ts +50 -2
  54. package/plug-api/lib/yaml.ts +10 -10
  55. package/plug-api/syscalls/editor.ts +1 -1
  56. package/plug-api/system_mock.ts +0 -1
  57. package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
@@ -0,0 +1,26 @@
1
+ import { LuaEnv, luaGet, luaKeys, type LuaStackFrame } from "./runtime.ts";
2
+
3
+ /**
4
+ * Build an environment for evaluating per-item expressions in queries.
5
+ * Extracted to its own module to avoid circular imports between
6
+ * query_collection.ts and aggregates.ts.
7
+ */
8
+ export function buildItemEnv(
9
+ objectVariable: string | undefined,
10
+ item: any,
11
+ env: LuaEnv,
12
+ sf: LuaStackFrame,
13
+ ): LuaEnv {
14
+ const itemEnv = new LuaEnv(env);
15
+ if (!objectVariable) {
16
+ // Inject all item keys as variables
17
+ for (const key of luaKeys(item)) {
18
+ itemEnv.setLocal(key, luaGet(item, key, sf.astCtx ?? null, sf));
19
+ }
20
+ // As well as _
21
+ itemEnv.setLocal("_", item);
22
+ } else {
23
+ itemEnv.setLocal(objectVariable, item);
24
+ }
25
+ return itemEnv;
26
+ }
@@ -128,7 +128,7 @@ export function luaIsCallable(
128
128
  }
129
129
  if (v instanceof LuaTable) {
130
130
  const mt = getMetatable(v, sf);
131
- if (mt && mt.has("__call")) {
131
+ if (mt?.has("__call")) {
132
132
  const mm = mt.get("__call", sf);
133
133
  return !!mm && (typeof mm === "function" || isILuaFunction(mm));
134
134
  }
@@ -614,7 +614,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
614
614
 
615
615
  if (init && !Array.isArray(init)) {
616
616
  for (const k in init) {
617
- if (Object.prototype.hasOwnProperty.call(init, k)) {
617
+ if (Object.hasOwn(init, k)) {
618
618
  this.stringKeys[k] = (init as any)[k];
619
619
  }
620
620
  }
@@ -689,7 +689,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
689
689
  keys(): any[] {
690
690
  const keys: any[] = [];
691
691
  for (const k in this.stringKeys) {
692
- if (Object.prototype.hasOwnProperty.call(this.stringKeys, k)) {
692
+ if (Object.hasOwn(this.stringKeys, k)) {
693
693
  keys.push(k);
694
694
  }
695
695
  }
@@ -706,7 +706,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
706
706
 
707
707
  empty(): boolean {
708
708
  for (const k in this.stringKeys) {
709
- if (Object.prototype.hasOwnProperty.call(this.stringKeys, k)) {
709
+ if (Object.hasOwn(this.stringKeys, k)) {
710
710
  return false;
711
711
  }
712
712
  }
@@ -927,6 +927,20 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
927
927
  const errSf = sf || LuaStackFrame.lostFrame;
928
928
  const ctx = sf?.astCtx ?? EMPTY_CTX;
929
929
 
930
+ if (key === null || key === undefined) {
931
+ throw new LuaRuntimeError(
932
+ "table index is nil",
933
+ errSf,
934
+ );
935
+ }
936
+
937
+ if (typeof key === "number" && Number.isNaN(key)) {
938
+ throw new LuaRuntimeError(
939
+ "table index is NaN",
940
+ errSf,
941
+ );
942
+ }
943
+
930
944
  if (this.has(key)) {
931
945
  return this.rawSet(key, value, numType);
932
946
  }
@@ -1121,9 +1135,9 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
1121
1135
  if (typeof key === "string") {
1122
1136
  result += key;
1123
1137
  } else {
1124
- result += "[" + key + "]";
1138
+ result += `[${key}]`;
1125
1139
  }
1126
- result += " = " + await luaToString(this.get(key));
1140
+ result += ` = ${await luaToString(this.get(key))}`;
1127
1141
  }
1128
1142
  result += "}";
1129
1143
  return result;
@@ -1243,21 +1257,24 @@ export function luaGet(
1243
1257
  errSf,
1244
1258
  );
1245
1259
  }
1260
+
1261
+ // In Lua reading with a nil key returns nil silently
1246
1262
  if (key === null || key === undefined) {
1247
- throw new LuaRuntimeError(
1248
- `attempt to index with a nil key`,
1249
- errSf,
1250
- );
1263
+ return null;
1251
1264
  }
1252
1265
 
1253
1266
  if (obj instanceof LuaTable || obj instanceof LuaEnv) {
1254
1267
  return obj.get(key, sf);
1255
1268
  }
1269
+ // Native JS array access: normalize undefined → null (Lua nil).
1270
+ // Without this, accessing an out-of-bounds index on a JS array
1271
+ // leaks JS undefined into the Lua runtime, breaking nil checks
1272
+ // such as `tags[1] ~= nil` on an empty array.
1256
1273
  if (typeof key === "number") {
1257
- return (obj as any[])[key - 1];
1274
+ return (obj as any[])[key - 1] ?? null;
1258
1275
  }
1259
1276
  if (isTaggedFloat(key)) {
1260
- return (obj as any[])[key.value - 1];
1277
+ return (obj as any[])[key.value - 1] ?? null;
1261
1278
  }
1262
1279
  // Native JS object
1263
1280
  const k = toNumKey(key);
@@ -1275,7 +1292,8 @@ export function luaGet(
1275
1292
  export function luaLen(
1276
1293
  obj: any,
1277
1294
  sf?: LuaStackFrame,
1278
- ): number {
1295
+ raw = false,
1296
+ ): number | Promise<number> {
1279
1297
  if (typeof obj === "string") {
1280
1298
  return obj.length;
1281
1299
  }
@@ -1283,6 +1301,18 @@ export function luaLen(
1283
1301
  return obj.length;
1284
1302
  }
1285
1303
  if (obj instanceof LuaTable) {
1304
+ // Check __len metamethod unless raw access is requested
1305
+ if (!raw) {
1306
+ const mt = getMetatable(obj, sf || LuaStackFrame.lostFrame);
1307
+ const mm = mt ? mt.rawGet("__len") : null;
1308
+ if (mm !== undefined && mm !== null) {
1309
+ const r = luaCall(mm, [obj], (sf?.astCtx ?? {}) as ASTCtx, sf);
1310
+ if (isPromise(r)) {
1311
+ return (r as Promise<any>).then((v: any) => Number(singleResult(v)));
1312
+ }
1313
+ return Number(singleResult(r));
1314
+ }
1315
+ }
1286
1316
  return obj.rawLength;
1287
1317
  }
1288
1318
 
@@ -1462,7 +1492,7 @@ export class LuaRuntimeError extends Error {
1462
1492
  );
1463
1493
 
1464
1494
  // Add position indicator
1465
- const pointer = " ".repeat(column) + "^";
1495
+ const pointer = `${" ".repeat(column)}^`;
1466
1496
 
1467
1497
  traceStr += `* ${ctx.ref || "(unknown source)"} @ ${line}:${column}:\n` +
1468
1498
  ` ${codeLine}\n` +
@@ -1549,7 +1579,7 @@ export function luaToString(
1549
1579
  const strVal = await luaToString(val, visited);
1550
1580
  result += strVal;
1551
1581
  }
1552
- return result + "}";
1582
+ return `${result}}`;
1553
1583
  }
1554
1584
 
1555
1585
  // Handle objects
@@ -1576,7 +1606,7 @@ export function luaToString(
1576
1606
  }
1577
1607
 
1578
1608
  export function luaFormatNumber(n: number, kind?: "int" | "float"): string {
1579
- if (kind !== "float" && Number.isInteger(n) && isFinite(n)) {
1609
+ if (kind !== "float" && Number.isInteger(n) && Number.isFinite(n)) {
1580
1610
  return String(n);
1581
1611
  }
1582
1612
  if (n !== n) return "-nan";
@@ -1589,7 +1619,7 @@ export function luaFormatNumber(n: number, kind?: "int" | "float"): string {
1589
1619
  const s = luaFormat("%.14g", n);
1590
1620
  // Guarantee `.01 suffix for integer-valued floats
1591
1621
  if (s.indexOf(".") === -1 && s.indexOf("e") === -1) {
1592
- return s + ".0";
1622
+ return `${s}.0`;
1593
1623
  }
1594
1624
  return s;
1595
1625
  }
@@ -128,8 +128,8 @@ function pad(s: string, width: number, flags: number, numPad: boolean): string {
128
128
  }
129
129
 
130
130
  function addSign(s: string, flags: number): string {
131
- if (flags & FLAG_PLUS) return "+" + s;
132
- if (flags & FLAG_SPACE) return " " + s;
131
+ if (flags & FLAG_PLUS) return `+${s}`;
132
+ if (flags & FLAG_SPACE) return ` ${s}`;
133
133
  return s;
134
134
  }
135
135
 
@@ -200,7 +200,7 @@ function formatInt(n: number, spec: FormatSpec): string {
200
200
 
201
201
  let result: string;
202
202
  if (neg) {
203
- result = "-" + prefix + digits;
203
+ result = `-${prefix}${digits}`;
204
204
  } else {
205
205
  result = addSign(prefix + digits, spec.flags);
206
206
  }
@@ -216,7 +216,7 @@ function formatFloat(n: number, spec: FormatSpec): string {
216
216
  const lower = code | 32; // to lowercase
217
217
 
218
218
  // Lua convention
219
- if (!isFinite(n)) {
219
+ if (!Number.isFinite(n)) {
220
220
  let s: string;
221
221
  if (n !== n) {
222
222
  s = upper ? "-NAN" : "-nan";
@@ -275,9 +275,9 @@ function formatFloat(n: number, spec: FormatSpec): string {
275
275
  const EIdx = body.indexOf("E");
276
276
  const expIdx = eIdx !== -1 ? eIdx : EIdx;
277
277
  if (expIdx !== -1) {
278
- body = body.slice(0, expIdx) + "." + body.slice(expIdx);
278
+ body = `${body.slice(0, expIdx)}.${body.slice(expIdx)}`;
279
279
  } else {
280
- body = body + ".";
280
+ body = `${body}.`;
281
281
  }
282
282
  }
283
283
  }
@@ -287,16 +287,16 @@ function formatFloat(n: number, spec: FormatSpec): string {
287
287
  if (body.indexOf(".") === -1) {
288
288
  const expIdx = findExpIndex(body);
289
289
  if (expIdx !== -1) {
290
- body = body.slice(0, expIdx) + "." + body.slice(expIdx);
290
+ body = `${body.slice(0, expIdx)}.${body.slice(expIdx)}`;
291
291
  } else {
292
- body = body + ".";
292
+ body = `${body}.`;
293
293
  }
294
294
  }
295
295
  }
296
296
 
297
297
  let result: string;
298
298
  if (neg) {
299
- result = "-" + body;
299
+ result = `-${body}`;
300
300
  } else {
301
301
  result = addSign(body, spec.flags);
302
302
  }
@@ -322,7 +322,7 @@ function ensureExpTwoDigits(s: string): string {
322
322
  const digitStart = signIdx + 1;
323
323
  const expLen = s.length - digitStart;
324
324
  if (expLen < 2) {
325
- return s.slice(0, digitStart) + "0" + s.slice(digitStart);
325
+ return `${s.slice(0, digitStart)}0${s.slice(digitStart)}`;
326
326
  }
327
327
  return s;
328
328
  }
@@ -353,7 +353,7 @@ function formatHexFloat(n: number, spec: FormatSpec): string {
353
353
  const code = spec.spec;
354
354
  const upper = code === 65; // 'A'
355
355
 
356
- if (!isFinite(n)) {
356
+ if (!Number.isFinite(n)) {
357
357
  let s: string;
358
358
  if (n !== n) {
359
359
  s = upper ? "-NAN" : "-nan";
@@ -373,7 +373,7 @@ function formatHexFloat(n: number, spec: FormatSpec): string {
373
373
  if (abs === 0) {
374
374
  const prec = spec.hasPrec ? spec.prec : 0;
375
375
  if (prec > 0) {
376
- body = "0x0." + "0".repeat(prec) + "p+0";
376
+ body = `0x0.${"0".repeat(prec)}p+0`;
377
377
  } else {
378
378
  body = "0x0p+0";
379
379
  }
@@ -395,14 +395,14 @@ function formatHexFloat(n: number, spec: FormatSpec): string {
395
395
  }
396
396
  }
397
397
  if (!hasDot) {
398
- body = body.slice(0, pIdx) + "." + body.slice(pIdx);
398
+ body = `${body.slice(0, pIdx)}.${body.slice(pIdx)}`;
399
399
  }
400
400
  }
401
401
  }
402
402
 
403
403
  let result: string;
404
404
  if (neg) {
405
- result = "-" + body;
405
+ result = `-${body}`;
406
406
  } else {
407
407
  result = addSign(body, spec.flags);
408
408
  }
@@ -474,9 +474,9 @@ function hexFloatBody(abs: number, spec: FormatSpec): string {
474
474
 
475
475
  const expSign = exponent >= 0 ? "+" : "";
476
476
  if (fracHex.length > 0) {
477
- return "0x" + firstDigit + "." + fracHex + "p" + expSign + exponent;
477
+ return `0x${firstDigit}.${fracHex}p${expSign}${exponent}`;
478
478
  }
479
- return "0x" + firstDigit + "p" + expSign + exponent;
479
+ return `0x${firstDigit}p${expSign}${exponent}`;
480
480
  }
481
481
 
482
482
  // Convert 52-bit value to 13 hex digits, zero-padded
@@ -581,7 +581,7 @@ function quoteString(s: string): string {
581
581
  if (ds.length < 3) out += "0".repeat(3 - ds.length);
582
582
  out += ds;
583
583
  } else {
584
- out += "\\" + c.toString();
584
+ out += `\\${c.toString()}`;
585
585
  }
586
586
  } else {
587
587
  out += String.fromCharCode(c);
@@ -643,7 +643,7 @@ function toPointer(v: unknown): string {
643
643
  id = nextId++;
644
644
  stringIds.set(key, id);
645
645
  }
646
- return "0x" + id.toString(16).padStart(14, "0");
646
+ return `0x${id.toString(16).padStart(14, "0")}`;
647
647
  }
648
648
 
649
649
  const obj = v as object;
@@ -652,7 +652,7 @@ function toPointer(v: unknown): string {
652
652
  id = nextId++;
653
653
  objectIds.set(obj, id);
654
654
  }
655
- return "0x" + id.toString(16).padStart(14, "0");
655
+ return `0x${id.toString(16).padStart(14, "0")}`;
656
656
  }
657
657
 
658
658
  function formatPointer(v: unknown, spec: FormatSpec): string {
@@ -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,28 @@ 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)) return null;
64
+ return n;
65
+ }
66
+ return null;
67
+ }),
68
+
43
69
  /**
44
70
  * When called without arguments, returns a pseudo-random float with
45
71
  * uniform distribution in the range [0,1). When called with two
@@ -52,51 +78,21 @@ export const mathApi = new LuaTable({
52
78
  random: new LuaBuiltinFunction((_sf, m?: number, n?: number) => {
53
79
  if (m !== undefined) m = untagNumber(m);
54
80
  if (n !== undefined) n = untagNumber(n);
55
-
56
- if (m === undefined && n === undefined) {
57
- return Math.random();
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;
81
+ try {
82
+ return prng.random(m, n);
83
+ } catch (e: any) {
84
+ throw new LuaRuntimeError(e.message, _sf);
84
85
  }
85
-
86
- if (!Number.isInteger(n!)) {
87
- throw new LuaRuntimeError(
88
- "bad argument #2 to 'math.random' (integer expected)",
89
- _sf,
90
- );
91
- }
92
-
93
- if (n! < m!) {
94
- throw new LuaRuntimeError(
95
- "bad argument #1 to 'math.random' (interval is empty)",
96
- _sf,
97
- );
98
- }
99
- return Math.floor(Math.random() * (n! - m! + 1)) + m!;
86
+ }),
87
+ /**
88
+ * Seeds the pseudo-random generator. With no arguments, uses a
89
+ * time-based seed. Returns the two seed integers used (Lua 5.4 contract).
90
+ */
91
+ randomseed: new LuaBuiltinFunction((_sf, x?: number, y?: number) => {
92
+ if (x !== undefined) x = untagNumber(x);
93
+ if (y !== undefined) y = untagNumber(y);
94
+ const [s1, s2] = prng.randomseed(x, y);
95
+ return new LuaMultiRes([s1, s2]);
100
96
  }),
101
97
 
102
98
  // Basic functions
@@ -117,10 +113,38 @@ export const mathApi = new LuaTable({
117
113
  modf: new LuaBuiltinFunction((_sf, x: number) => {
118
114
  const xn = untagNumber(x);
119
115
  const int = Math.trunc(xn);
120
- const frac = xn - int;
116
+ // Guarantee that the `frac` part is always Lua float
117
+ const frac = makeLuaFloat(xn - int);
121
118
  return new LuaMultiRes([int, frac]);
122
119
  }),
123
120
 
121
+ // Returns m and e such that x = m * 2^e, 0.5 <= |m| < 1 (or m=0 when x=0).
122
+ // e is an integer. Mirrors C99/Lua.
123
+ // Special cases: frexp(0) = (0, 0); frexp(+-inf/nan) = (x, 0).
124
+ frexp: new LuaBuiltinFunction((_sf, x: number) => {
125
+ const xn = untagNumber(x);
126
+ if (xn === 0 || !Number.isFinite(xn) || Number.isNaN(xn)) {
127
+ return new LuaMultiRes([xn, 0]);
128
+ }
129
+ const abs = Math.abs(xn);
130
+ let e = Math.floor(Math.log2(abs)) + 1;
131
+ let m = xn / 2 ** e;
132
+ if (Math.abs(m) >= 1.0) {
133
+ e += 1;
134
+ m /= 2;
135
+ }
136
+ if (Math.abs(m) < 0.5) {
137
+ e -= 1;
138
+ m *= 2;
139
+ }
140
+ return new LuaMultiRes([m, e]);
141
+ }),
142
+
143
+ // Returns m * 2^e (the inverse of frexp). Mirrors C99/Lua.
144
+ ldexp: new LuaBuiltinFunction((_sf, m: number, e: number) =>
145
+ untagNumber(m) * 2 ** untagNumber(e)
146
+ ),
147
+
124
148
  // Power and logarithms
125
149
  exp: new LuaBuiltinFunction((_sf, x: number) => Math.exp(untagNumber(x))),
126
150
  log: new LuaBuiltinFunction((_sf, x: number, base?: number) => {
@@ -129,8 +153,9 @@ export const mathApi = new LuaTable({
129
153
  }
130
154
  return Math.log(untagNumber(x)) / Math.log(untagNumber(base));
131
155
  }),
156
+ // Power function (deprecated in Lua 5.4 but retained for compatibility)
132
157
  pow: new LuaBuiltinFunction((_sf, x: number, y: number) =>
133
- Math.pow(untagNumber(x), untagNumber(y))
158
+ untagNumber(x) ** untagNumber(y)
134
159
  ),
135
160
  sqrt: new LuaBuiltinFunction((_sf, x: number) => Math.sqrt(untagNumber(x))),
136
161
 
@@ -147,7 +172,7 @@ export const mathApi = new LuaTable({
147
172
  return Math.atan2(untagNumber(y), untagNumber(x));
148
173
  }),
149
174
 
150
- // Hyperbolic functions
175
+ // Hyperbolic functions (deprecated in Lua 5.4 but retained for compatibility)
151
176
  cosh: new LuaBuiltinFunction((_sf, x: number) => Math.cosh(untagNumber(x))),
152
177
  sinh: new LuaBuiltinFunction((_sf, x: number) => Math.sinh(untagNumber(x))),
153
178
  tanh: new LuaBuiltinFunction((_sf, x: number) => Math.tanh(untagNumber(x))),
@@ -66,7 +66,7 @@ export const netApi = new LuaTable({
66
66
  (uri: string, options: { uri?: string; encoding?: string } = {}) => {
67
67
  options.uri = uri;
68
68
  return client.clientSystem.serviceRegistry.invokeBestMatch(
69
- "net.readURI:" + uri,
69
+ `net.readURI:${uri}`,
70
70
  options,
71
71
  );
72
72
  },
@@ -74,7 +74,7 @@ export const netApi = new LuaTable({
74
74
  writeURI: new LuaNativeJSFunction(
75
75
  (uri: string, content: string | Uint8Array) => {
76
76
  return client.clientSystem.serviceRegistry.invokeBestMatch(
77
- "net.writeURI:" + uri,
77
+ `net.writeURI:${uri}`,
78
78
  { uri, content },
79
79
  );
80
80
  },
@@ -365,4 +365,9 @@ export const osApi = new LuaTable({
365
365
  return luaFormatTime(fmt, d, utc);
366
366
  },
367
367
  ),
368
+
369
+ // Returns an approximation of CPU time used by the program in seconds.
370
+ clock: new LuaBuiltinFunction((_sf): number => {
371
+ return performance.now() / 1000.0;
372
+ }),
368
373
  });