@silverbulletmd/silverbullet 2.4.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 (117) hide show
  1. package/LICENSE.md +18 -0
  2. package/README.md +98 -0
  3. package/client/asset_bundle/bundle.ts +95 -0
  4. package/client/data/datastore.ts +85 -0
  5. package/client/data/kv_primitives.ts +25 -0
  6. package/client/markdown_parser/constants.ts +13 -0
  7. package/client/plugos/event.ts +36 -0
  8. package/client/plugos/eventhook.ts +8 -0
  9. package/client/plugos/hooks/code_widget.ts +59 -0
  10. package/client/plugos/hooks/command.ts +104 -0
  11. package/client/plugos/hooks/document_editor.ts +77 -0
  12. package/client/plugos/hooks/event.ts +187 -0
  13. package/client/plugos/hooks/mq.ts +154 -0
  14. package/client/plugos/hooks/plug_namespace.ts +85 -0
  15. package/client/plugos/hooks/slash_command.ts +192 -0
  16. package/client/plugos/hooks/syscall.ts +66 -0
  17. package/client/plugos/manifest_cache.ts +67 -0
  18. package/client/plugos/plug.ts +99 -0
  19. package/client/plugos/plug_compile.ts +202 -0
  20. package/client/plugos/protocol.ts +40 -0
  21. package/client/plugos/proxy_fetch.ts +53 -0
  22. package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
  23. package/client/plugos/sandboxes/sandbox.ts +14 -0
  24. package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
  25. package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
  26. package/client/plugos/syscalls/asset.ts +35 -0
  27. package/client/plugos/syscalls/clientStore.ts +21 -0
  28. package/client/plugos/syscalls/client_code_widget.ts +12 -0
  29. package/client/plugos/syscalls/code_widget.ts +24 -0
  30. package/client/plugos/syscalls/config.ts +46 -0
  31. package/client/plugos/syscalls/datastore.ts +89 -0
  32. package/client/plugos/syscalls/editor.ts +673 -0
  33. package/client/plugos/syscalls/event.ts +36 -0
  34. package/client/plugos/syscalls/fetch.ts +128 -0
  35. package/client/plugos/syscalls/index.ts +102 -0
  36. package/client/plugos/syscalls/jsonschema.ts +69 -0
  37. package/client/plugos/syscalls/language.ts +23 -0
  38. package/client/plugos/syscalls/lua.ts +58 -0
  39. package/client/plugos/syscalls/markdown.ts +84 -0
  40. package/client/plugos/syscalls/mq.ts +52 -0
  41. package/client/plugos/syscalls/service_registry.ts +43 -0
  42. package/client/plugos/syscalls/shell.ts +39 -0
  43. package/client/plugos/syscalls/space.ts +139 -0
  44. package/client/plugos/syscalls/sync.ts +77 -0
  45. package/client/plugos/syscalls/system.ts +150 -0
  46. package/client/plugos/system.ts +201 -0
  47. package/client/plugos/types.ts +60 -0
  48. package/client/plugos/util.ts +14 -0
  49. package/client/plugos/worker_runtime.ts +195 -0
  50. package/client/space_lua/ast.ts +328 -0
  51. package/client/space_lua/ast_narrow.ts +81 -0
  52. package/client/space_lua/eval.ts +2478 -0
  53. package/client/space_lua/labels.ts +416 -0
  54. package/client/space_lua/numeric.ts +240 -0
  55. package/client/space_lua/parse.ts +1522 -0
  56. package/client/space_lua/query_collection.ts +232 -0
  57. package/client/space_lua/rp.ts +27 -0
  58. package/client/space_lua/runtime.ts +1702 -0
  59. package/client/space_lua/stdlib/crypto.ts +10 -0
  60. package/client/space_lua/stdlib/encoding.ts +19 -0
  61. package/client/space_lua/stdlib/format.ts +770 -0
  62. package/client/space_lua/stdlib/js.ts +73 -0
  63. package/client/space_lua/stdlib/load.ts +52 -0
  64. package/client/space_lua/stdlib/math.ts +193 -0
  65. package/client/space_lua/stdlib/net.ts +113 -0
  66. package/client/space_lua/stdlib/os.ts +368 -0
  67. package/client/space_lua/stdlib/space_lua.ts +153 -0
  68. package/client/space_lua/stdlib/string.ts +286 -0
  69. package/client/space_lua/stdlib/table.ts +401 -0
  70. package/client/space_lua/stdlib.ts +489 -0
  71. package/client/space_lua/tonumber.ts +501 -0
  72. package/client/space_lua/util.ts +96 -0
  73. package/dist/plug-compile.js +1513 -0
  74. package/package.json +120 -0
  75. package/plug-api/constants.ts +42 -0
  76. package/plug-api/lib/async.ts +162 -0
  77. package/plug-api/lib/crypto.ts +202 -0
  78. package/plug-api/lib/dates.ts +13 -0
  79. package/plug-api/lib/json.ts +136 -0
  80. package/plug-api/lib/limited_map.ts +72 -0
  81. package/plug-api/lib/memory_cache.ts +21 -0
  82. package/plug-api/lib/native_fetch.ts +6 -0
  83. package/plug-api/lib/ref.ts +275 -0
  84. package/plug-api/lib/resolve.ts +90 -0
  85. package/plug-api/lib/tags.ts +15 -0
  86. package/plug-api/lib/transclusion.ts +122 -0
  87. package/plug-api/lib/tree.ts +232 -0
  88. package/plug-api/lib/yaml.ts +284 -0
  89. package/plug-api/syscall.ts +15 -0
  90. package/plug-api/syscalls/asset.ts +36 -0
  91. package/plug-api/syscalls/client_store.ts +33 -0
  92. package/plug-api/syscalls/code_widget.ts +8 -0
  93. package/plug-api/syscalls/config.ts +58 -0
  94. package/plug-api/syscalls/datastore.ts +96 -0
  95. package/plug-api/syscalls/editor.ts +517 -0
  96. package/plug-api/syscalls/event.ts +47 -0
  97. package/plug-api/syscalls/index.ts +77 -0
  98. package/plug-api/syscalls/jsonschema.ts +25 -0
  99. package/plug-api/syscalls/language.ts +23 -0
  100. package/plug-api/syscalls/lua.ts +20 -0
  101. package/plug-api/syscalls/markdown.ts +38 -0
  102. package/plug-api/syscalls/mq.ts +79 -0
  103. package/plug-api/syscalls/shell.ts +14 -0
  104. package/plug-api/syscalls/space.ts +212 -0
  105. package/plug-api/syscalls/sync.ts +28 -0
  106. package/plug-api/syscalls/system.ts +102 -0
  107. package/plug-api/syscalls/yaml.ts +28 -0
  108. package/plug-api/syscalls.ts +21 -0
  109. package/plug-api/system_mock.ts +89 -0
  110. package/plug-api/types/client.ts +116 -0
  111. package/plug-api/types/config.ts +22 -0
  112. package/plug-api/types/datastore.ts +28 -0
  113. package/plug-api/types/event.ts +27 -0
  114. package/plug-api/types/index.ts +56 -0
  115. package/plug-api/types/manifest.ts +98 -0
  116. package/plug-api/types/namespace.ts +6 -0
  117. package/plugs/builtin_plugs.ts +14 -0
@@ -0,0 +1,286 @@
1
+ import {
2
+ jsToLuaValue,
3
+ LuaBuiltinFunction,
4
+ LuaMultiRes,
5
+ LuaRuntimeError,
6
+ LuaTable,
7
+ luaToString,
8
+ } from "../runtime.ts";
9
+ import { untagNumber } from "../numeric.ts";
10
+ import { luaFormat } from "./format.ts";
11
+
12
+ // Bits and pieces borrowed from https://github.com/paulcuth/starlight/blob/master/src/runtime/lib/string.js
13
+
14
+ const ROSETTA_STONE = {
15
+ "([^a-zA-Z0-9%(])-": "$1*?",
16
+ "([^%])-([^a-zA-Z0-9?])": "$1*?$2",
17
+ "([^%])-$": "$1*?",
18
+ "%a": "[a-zA-Z]",
19
+ "%A": "[^a-zA-Z]",
20
+ "%c": "[\x00-\x1f]",
21
+ "%C": "[^\x00-\x1f]",
22
+ "%d": "\\d",
23
+ "%D": "[^\d]",
24
+ "%l": "[a-z]",
25
+ "%L": "[^a-z]",
26
+ "%p": "[\.\,\"'\?\!\;\:\#\$\%\&\(\)\*\+\-\/\<\>\=\@\\[\\]\\\\^\_\{\}\|\~]",
27
+ "%P": "[^\.\,\"'\?\!\;\:\#\$\%\&\(\)\*\+\-\/\<\>\=\@\\[\\]\\\\^\_\{\}\|\~]",
28
+ "%s": "[ \\t\\n\\f\\v\\r]",
29
+ "%S": "[^ \t\n\f\v\r]",
30
+ "%u": "[A-Z]",
31
+ "%U": "[^A-Z]",
32
+ "%w": "[a-zA-Z0-9]",
33
+ "%W": "[^a-zA-Z0-9]",
34
+ "%x": "[a-fA-F0-9]",
35
+ "%X": "[^a-fA-F0-9]",
36
+ "%([^a-zA-Z])": "\\$1",
37
+ };
38
+
39
+ function translatePattern(pattern: string): string {
40
+ pattern = "" + pattern;
41
+
42
+ // Replace single backslash with double backslashes
43
+ pattern = pattern.replace(new RegExp("\\\\", "g"), "\\\\");
44
+ pattern = pattern.replace(new RegExp("\\|", "g"), "\\|");
45
+
46
+ for (const [key, value] of Object.entries(ROSETTA_STONE)) {
47
+ pattern = pattern.replace(new RegExp(key, "g"), value);
48
+ }
49
+
50
+ let l = pattern.length;
51
+ let n = 0;
52
+
53
+ for (let i = 0; i < l; i++) {
54
+ const character = pattern.slice(i, 1);
55
+ if (i && pattern.slice(i - 1, 1) == "\\") {
56
+ continue;
57
+ }
58
+
59
+ let addSlash = false;
60
+
61
+ if (character == "[") {
62
+ if (n) addSlash = true;
63
+ n++;
64
+ } else if (character == "]" && pattern.slice(i - 1, 1) !== "\\") {
65
+ n--;
66
+ if (n) addSlash = true;
67
+ }
68
+
69
+ if (addSlash) {
70
+ pattern = pattern.slice(0, i) + pattern.slice(i++ + 1);
71
+ l++;
72
+ }
73
+ }
74
+
75
+ return pattern;
76
+ }
77
+
78
+ export const stringApi = new LuaTable({
79
+ byte: new LuaBuiltinFunction((_sf, s: string, i?: number, j?: number) => {
80
+ i = i ?? 1;
81
+ j = j ?? i;
82
+ const result = [];
83
+ for (let k = i; k <= j; k++) {
84
+ result.push(s.charCodeAt(k - 1));
85
+ }
86
+ return new LuaMultiRes(result);
87
+ }),
88
+ char: new LuaBuiltinFunction((_sf, ...args: number[]) => {
89
+ return String.fromCharCode(...args);
90
+ }),
91
+ find: new LuaBuiltinFunction(
92
+ (_sf, s: string, pattern: string, init = 1, plain = false) => {
93
+ // Regex
94
+ if (!plain) {
95
+ pattern = translatePattern(pattern);
96
+ const reg = new RegExp(pattern);
97
+ const index = s.slice(init - 1).search(reg);
98
+
99
+ if (index < 0) return null;
100
+
101
+ const match = s.slice(init - 1).match(reg);
102
+ const result = [index + init, index + init + match![0].length - 1];
103
+
104
+ match!.shift();
105
+ return new LuaMultiRes(result.concat(match));
106
+ }
107
+
108
+ // Plain
109
+ const index = s.indexOf(pattern, init - 1);
110
+ return (index === -1)
111
+ ? null
112
+ : new LuaMultiRes([index + 1, index + pattern.length]);
113
+ },
114
+ ),
115
+ format: new LuaBuiltinFunction((_sf, format: string, ...args: any[]) => {
116
+ // Unwrap tagged floats so luaFormat sees plain numbers
117
+ for (let i = 0; i < args.length; i++) {
118
+ args[i] = untagNumber(args[i]);
119
+ }
120
+ return luaFormat(format, ...args);
121
+ }),
122
+ gmatch: new LuaBuiltinFunction((_sf, s: string, pattern: string) => {
123
+ pattern = translatePattern(pattern);
124
+ const reg = new RegExp(pattern, "g"),
125
+ matches = s.match(reg);
126
+ return () => {
127
+ if (!matches) {
128
+ return;
129
+ }
130
+ const match = matches.shift();
131
+ if (!match) {
132
+ return;
133
+ }
134
+ const groups = new RegExp(pattern).exec(match) || [];
135
+
136
+ groups.shift();
137
+ return groups.length ? new LuaMultiRes(groups) : match;
138
+ };
139
+ }),
140
+ gsub: new LuaBuiltinFunction(
141
+ async (
142
+ sf,
143
+ s: string,
144
+ pattern: string,
145
+ repl: any, // string or LuaFunction
146
+ n = Infinity,
147
+ ) => {
148
+ pattern = translatePattern(pattern);
149
+ const replIsFunction = repl.call;
150
+
151
+ let count = 0,
152
+ result = "",
153
+ str,
154
+ prefix,
155
+ match: any,
156
+ lastMatch;
157
+
158
+ while (
159
+ count < n &&
160
+ s &&
161
+ (match = s.match(pattern))
162
+ ) {
163
+ if (replIsFunction) {
164
+ // If no captures, pass in the whole match
165
+ if (match[1] === undefined) {
166
+ str = await repl.call(sf, match[0]);
167
+ } else {
168
+ // Else pass in the captures
169
+ str = await repl.call(sf, ...match.slice(1));
170
+ }
171
+ if (str instanceof LuaMultiRes) {
172
+ str = str.values[0];
173
+ }
174
+ if (str === undefined || str === null) {
175
+ str = match[0];
176
+ }
177
+ } else if (repl instanceof LuaTable) {
178
+ str = repl.get(match[0]);
179
+ } else if (typeof repl === "string") {
180
+ str = repl.replaceAll(/%([0-9]+)/g, (_, i) => match[i]);
181
+ } else {
182
+ throw new LuaRuntimeError(
183
+ "string.gsub replacement argument should be a function, table or string",
184
+ sf,
185
+ );
186
+ }
187
+
188
+ if (match[0].length === 0) {
189
+ if (lastMatch === void 0) {
190
+ prefix = "";
191
+ } else {
192
+ prefix = s.slice(0, 1);
193
+ }
194
+ } else {
195
+ prefix = s.slice(0, match.index);
196
+ }
197
+
198
+ lastMatch = match[0];
199
+ result += `${prefix}${str}`;
200
+ s = s.slice(`${prefix}${lastMatch}`.length);
201
+
202
+ count++;
203
+ }
204
+
205
+ return new LuaMultiRes([`${result}${s}`, count]);
206
+ },
207
+ ),
208
+ len: new LuaBuiltinFunction((_sf, s: string) => {
209
+ return s.length;
210
+ }),
211
+ lower: new LuaBuiltinFunction((_sf, s: string) => {
212
+ return luaToString(s.toLowerCase());
213
+ }),
214
+ upper: new LuaBuiltinFunction((_sf, s: string) => {
215
+ return luaToString(s.toUpperCase());
216
+ }),
217
+ match: new LuaBuiltinFunction(
218
+ (_sf, s: string, pattern: string, init = 1) => {
219
+ s = s.slice(init - 1);
220
+ const matches = s.match(new RegExp(translatePattern(pattern)));
221
+
222
+ if (!matches) {
223
+ return null;
224
+ }
225
+ if (matches[1] === undefined) {
226
+ // No captures
227
+ return matches[0];
228
+ }
229
+
230
+ matches.shift();
231
+ return new LuaMultiRes(matches);
232
+ },
233
+ ),
234
+ rep: new LuaBuiltinFunction((_sf, s: string, n: number, sep?: string) => {
235
+ sep = sep ?? "";
236
+ return s.repeat(n) + sep;
237
+ }),
238
+ reverse: new LuaBuiltinFunction((_sf, s: string) => {
239
+ return s.split("").reverse().join("");
240
+ }),
241
+ sub: new LuaBuiltinFunction((_sf, s: string, i: number, j?: number) => {
242
+ j = j ?? s.length;
243
+ if (i < 0) {
244
+ i = s.length + i + 1;
245
+ }
246
+ if (j < 0) {
247
+ j = s.length + j + 1;
248
+ }
249
+ return s.slice(i - 1, j);
250
+ }),
251
+ split: new LuaBuiltinFunction((_sf, s: string, sep: string) => {
252
+ return s.split(sep);
253
+ }),
254
+
255
+ // Non-standard
256
+ startsWith: new LuaBuiltinFunction((_sf, s: string, prefix: string) => {
257
+ return s.startsWith(prefix);
258
+ }),
259
+ endsWith: new LuaBuiltinFunction((_sf, s: string, suffix: string) => {
260
+ return s.endsWith(suffix);
261
+ }),
262
+ trim: new LuaBuiltinFunction((_sf, s: string) => {
263
+ return s.trim();
264
+ }),
265
+ trimStart: new LuaBuiltinFunction((_sf, s: string) => {
266
+ return s.trimStart();
267
+ }),
268
+ trimEnd: new LuaBuiltinFunction((_sf, s: string) => {
269
+ return s.trimEnd();
270
+ }),
271
+ matchRegex: new LuaBuiltinFunction((_sf, s: string, pattern: string) => {
272
+ const regex = new RegExp(pattern);
273
+ const result = s.match(regex);
274
+ return jsToLuaValue(result);
275
+ }),
276
+ matchRegexAll: new LuaBuiltinFunction((_sf, s: string, pattern: string) => {
277
+ const regex = new RegExp(pattern, "g");
278
+ return () => {
279
+ const match = regex.exec(s);
280
+ if (!match) {
281
+ return;
282
+ }
283
+ return jsToLuaValue(match);
284
+ };
285
+ }),
286
+ });
@@ -0,0 +1,401 @@
1
+ import {
2
+ getMetatable,
3
+ type ILuaFunction,
4
+ LuaBuiltinFunction,
5
+ luaCall,
6
+ type LuaEnv,
7
+ luaEquals,
8
+ luaGet,
9
+ LuaMultiRes,
10
+ LuaRuntimeError,
11
+ luaSet,
12
+ LuaTable,
13
+ type LuaValue,
14
+ luaValueToJS,
15
+ singleResult,
16
+ } from "../runtime.ts";
17
+ import { asyncQuickSort, evalPromiseValues } from "../util.ts";
18
+ import { isTaggedFloat } from "../numeric.ts";
19
+
20
+ // For `LuaTable` honor `__len` when present; otherwise use raw array
21
+ // length. For JS arrays use `.length`.
22
+ function luaLenForTableLib(
23
+ sf: any,
24
+ tbl: LuaTable | any[],
25
+ ): number | Promise<number> {
26
+ if (Array.isArray(tbl)) {
27
+ return tbl.length;
28
+ }
29
+ if (!(tbl instanceof LuaTable)) {
30
+ return 0;
31
+ }
32
+
33
+ const mt = getMetatable(tbl, sf);
34
+ const mm = mt ? mt.rawGet("__len") : null;
35
+ if (!(mm === undefined || mm === null)) {
36
+ const r = luaCall(mm, [tbl], sf.astCtx ?? {}, sf);
37
+ if (r instanceof Promise) {
38
+ return r.then((v: any) => Number(singleResult(v)));
39
+ }
40
+ return Number(singleResult(r));
41
+ }
42
+
43
+ return tbl.length;
44
+ }
45
+
46
+ async function luaLenForTableLibAsync(sf: any, tbl: LuaTable | any[]) {
47
+ const r = luaLenForTableLib(sf, tbl);
48
+ return r instanceof Promise ? await r : r;
49
+ }
50
+
51
+ export const tableApi = new LuaTable({
52
+ /**
53
+ * Concatenates the elements of a table into a string, using a separator.
54
+ * @param tbl - The table to concatenate.
55
+ * @param sep - The separator to use between elements.
56
+ * @param i - The start index.
57
+ * @param j - The end index.
58
+ * @returns The concatenated string.
59
+ */
60
+ concat: new LuaBuiltinFunction(
61
+ async (sf, tbl: LuaTable | any[], sep?: string, i?: number, j?: number) => {
62
+ sep = sep ?? "";
63
+ i = i ?? 1;
64
+ if (j === undefined || j === null) {
65
+ j = await luaLenForTableLibAsync(sf, tbl);
66
+ }
67
+
68
+ const luaConcatElemToString = (v: any, idx: number): string => {
69
+ // Concat errors on nil and non-string or non-number values.
70
+ if (v === null || v === undefined) {
71
+ throw new LuaRuntimeError(
72
+ `invalid value (nil) at index ${idx} in table for 'concat'`,
73
+ sf,
74
+ );
75
+ }
76
+ if (typeof v === "string") {
77
+ return v;
78
+ }
79
+ if (typeof v === "number") {
80
+ return String(v);
81
+ }
82
+ if (isTaggedFloat(v)) {
83
+ return String(v.value);
84
+ }
85
+
86
+ const ty = typeof v === "object" && v instanceof LuaTable
87
+ ? "table"
88
+ : typeof v;
89
+ throw new LuaRuntimeError(
90
+ `invalid value (${ty}) at index ${idx} in table for 'concat'`,
91
+ sf,
92
+ );
93
+ };
94
+
95
+ if (Array.isArray(tbl)) {
96
+ const out: string[] = [];
97
+ for (let k = i; k <= j; k++) {
98
+ const v = tbl[k - 1];
99
+ out.push(luaConcatElemToString(v, k));
100
+ }
101
+ return out.join(sep);
102
+ }
103
+
104
+ const out: string[] = [];
105
+ for (let k = i; k <= j; k++) {
106
+ const v = await luaGet(tbl, k, sf.astCtx ?? null, sf);
107
+ out.push(luaConcatElemToString(v, k));
108
+ }
109
+ return out.join(sep);
110
+ },
111
+ ),
112
+
113
+ /**
114
+ * Inserts an element into a table at a specified position.
115
+ * @param tbl - The table to insert the element into.
116
+ * @param posOrValue - The position or value to insert.
117
+ * @param value - The value to insert.
118
+ */
119
+ insert: new LuaBuiltinFunction(
120
+ async (
121
+ sf,
122
+ tbl: LuaTable | any[],
123
+ posOrValue: number | any,
124
+ value?: any,
125
+ ) => {
126
+ if (Array.isArray(tbl)) {
127
+ // Since we're inserting/appending to a native JS array, we'll also convert the value to a JS value on the fly
128
+ // this seems like a reasonable heuristic
129
+ if (value === undefined) {
130
+ tbl.push(luaValueToJS(posOrValue, sf));
131
+ } else {
132
+ tbl.splice(posOrValue - 1, 0, luaValueToJS(value, sf));
133
+ }
134
+ return;
135
+ }
136
+
137
+ if (!(tbl instanceof LuaTable)) {
138
+ return;
139
+ }
140
+
141
+ let pos: number;
142
+ let v: any;
143
+
144
+ if (value === undefined) {
145
+ v = posOrValue;
146
+ pos = (await luaLenForTableLibAsync(sf, tbl)) + 1;
147
+ } else {
148
+ pos = posOrValue;
149
+ v = value;
150
+ }
151
+
152
+ const n = await luaLenForTableLibAsync(sf, tbl);
153
+
154
+ // Shift up: for k = n, pos, -1 do t[k+1] = t[k] end
155
+ for (let k = n; k >= pos; k--) {
156
+ const cur = await luaGet(tbl, k, sf.astCtx ?? null, sf);
157
+ await luaSet(tbl, k + 1, cur, sf);
158
+ }
159
+
160
+ await luaSet(tbl, pos, v, sf);
161
+ },
162
+ ),
163
+
164
+ /**
165
+ * Removes an element from a table at a specified position.
166
+ * @param tbl - The table to remove the element from.
167
+ * @param pos - The position of the element to remove.
168
+ */
169
+ remove: new LuaBuiltinFunction(
170
+ async (sf, tbl: LuaTable | any[], pos?: number) => {
171
+ if (Array.isArray(tbl)) {
172
+ const n = tbl.length;
173
+ const p = pos ?? n;
174
+ if (p < 1 || p > n) {
175
+ throw new LuaRuntimeError("position out of bounds", sf);
176
+ }
177
+ const idx = p - 1;
178
+ const v = tbl[idx];
179
+ tbl.splice(idx, 1);
180
+ return v;
181
+ }
182
+
183
+ if (!(tbl instanceof LuaTable)) {
184
+ return null;
185
+ }
186
+
187
+ const n = await luaLenForTableLibAsync(sf, tbl);
188
+ const p = pos ?? n;
189
+
190
+ if (p < 1 || p > n) {
191
+ throw new LuaRuntimeError("position out of bounds", sf);
192
+ }
193
+
194
+ const v = await luaGet(tbl, p, sf.astCtx ?? null, sf);
195
+
196
+ // Shift down: for k = p, n-1 do t[k] = t[k+1] end; t[n] = nil
197
+ for (let k = p; k < n; k++) {
198
+ const next = await luaGet(tbl, k + 1, sf.astCtx ?? null, sf);
199
+ await luaSet(tbl, k, next, sf);
200
+ }
201
+ await luaSet(tbl, n, null, sf);
202
+
203
+ return v;
204
+ },
205
+ ),
206
+
207
+ /**
208
+ * Sorts a table.
209
+ * @param tbl - The table to sort.
210
+ * @param comp - The comparison function.
211
+ * @returns The sorted table.
212
+ */
213
+ sort: new LuaBuiltinFunction(
214
+ async (sf, tbl: LuaTable | any[], comp?: ILuaFunction) => {
215
+ if (Array.isArray(tbl)) {
216
+ return await asyncQuickSort(tbl, async (a, b) => {
217
+ if (comp) {
218
+ return (await comp.call(sf, a, b)) ? -1 : 1;
219
+ }
220
+ return (a as any) < (b as any) ? -1 : 1;
221
+ });
222
+ }
223
+
224
+ if (!(tbl instanceof LuaTable)) {
225
+ return tbl;
226
+ }
227
+
228
+ const n = await luaLenForTableLibAsync(sf, tbl);
229
+
230
+ const values: any[] = [];
231
+ for (let i = 1; i <= n; i++) {
232
+ values.push(await luaGet(tbl, i, sf.astCtx ?? null, sf));
233
+ }
234
+
235
+ const cmp = async (a: any, b: any): Promise<number> => {
236
+ if (comp) {
237
+ const r = await luaCall(comp, [a, b], sf.astCtx ?? {}, sf);
238
+ return r ? -1 : 1;
239
+ }
240
+
241
+ const av = isTaggedFloat(a) ? a.value : a;
242
+ const bv = isTaggedFloat(b) ? b.value : b;
243
+
244
+ if (typeof av === "number" && typeof bv === "number") {
245
+ return av < bv ? -1 : 1;
246
+ }
247
+ if (typeof av === "string" && typeof bv === "string") {
248
+ return av < bv ? -1 : 1;
249
+ }
250
+
251
+ const ta = typeof av;
252
+ const tb = typeof bv;
253
+ throw new LuaRuntimeError(
254
+ `attempt to compare ${ta} with ${tb}`,
255
+ sf,
256
+ );
257
+ };
258
+
259
+ const sorted = await asyncQuickSort(values, cmp);
260
+
261
+ for (let i = 1; i <= n; i++) {
262
+ await luaSet(tbl, i, sorted[i - 1], sf);
263
+ }
264
+
265
+ return tbl;
266
+ },
267
+ ),
268
+
269
+ /**
270
+ * Returns the keys of a table.
271
+ * Note: Space Lua specific
272
+ * @param tbl - The table to get the keys from.
273
+ * @returns The keys of the table.
274
+ */
275
+ keys: new LuaBuiltinFunction((_sf, tbl: LuaTable | LuaEnv | any) => {
276
+ if (tbl.keys) {
277
+ return tbl.keys();
278
+ }
279
+ return Object.keys(tbl);
280
+ }),
281
+
282
+ /**
283
+ * Checks if a table (used as an array) contains a value.
284
+ * Note: Space Lua specific
285
+ * @param tbl - The table to check.
286
+ * @param value - The value to check for.
287
+ * @returns True if the value is in the table, false otherwise.
288
+ */
289
+ includes: new LuaBuiltinFunction(
290
+ (sf, tbl: LuaTable | any[], value: LuaValue) => {
291
+ if (!tbl) {
292
+ return false;
293
+ }
294
+ if (tbl instanceof LuaTable) {
295
+ // Iterate over the table
296
+ for (const key of tbl.keys()) {
297
+ if (luaEquals(tbl.get(key), value)) {
298
+ return true;
299
+ }
300
+ }
301
+ return false;
302
+ }
303
+ if (Array.isArray(tbl)) {
304
+ return !!tbl.find((item) => luaEquals(item, value));
305
+ }
306
+ throw new LuaRuntimeError(
307
+ `Cannot use includes on a non-table or non-array value`,
308
+ sf,
309
+ );
310
+ },
311
+ ),
312
+
313
+ /**
314
+ * Returns a new table from an old one, only with selected keys
315
+ * @param tbl a Lua table or JS object
316
+ * @param keys a list of keys to select from the table, if keys[0] is a table or array, assumed to contain the keys to select
317
+ * @returns a new table with only the selected keys
318
+ */
319
+ select: new LuaBuiltinFunction(
320
+ (sf, tbl: LuaTable | Record<string, any>, ...keys: LuaValue[]) => {
321
+ // Normalize arguments
322
+ if (Array.isArray(keys[0])) {
323
+ // First argument is key array, let's unpack
324
+ keys = keys[0];
325
+ } else if (keys[0] instanceof LuaTable) {
326
+ keys = keys[0].toJSArray();
327
+ }
328
+ const resultTable = new LuaTable();
329
+ const setPromises: (void | Promise<void>)[] = [];
330
+ for (const key of keys) {
331
+ setPromises.push(resultTable.set(key, luaGet(tbl, key, null, sf)));
332
+ }
333
+ const promised = evalPromiseValues(setPromises);
334
+ if (promised instanceof Promise) {
335
+ return promised.then(() => resultTable);
336
+ }
337
+ return resultTable;
338
+ },
339
+ ),
340
+
341
+ pack: new LuaBuiltinFunction((_sf, ...args: any[]) => {
342
+ const tbl = new LuaTable();
343
+ for (let i = 0; i < args.length; i++) {
344
+ tbl.set(i + 1, args[i]);
345
+ }
346
+ tbl.set("n", args.length);
347
+ return tbl;
348
+ }),
349
+
350
+ unpack: new LuaBuiltinFunction(
351
+ async (sf, tbl: LuaTable | any[], i?: number, j?: number) => {
352
+ i = i ?? 1;
353
+ if (j === undefined || j === null) {
354
+ j = Array.isArray(tbl)
355
+ ? tbl.length
356
+ : await luaLenForTableLibAsync(sf, tbl);
357
+ }
358
+
359
+ const result: LuaValue[] = [];
360
+ for (let k = i; k <= j; k++) {
361
+ const v = Array.isArray(tbl)
362
+ ? tbl[k - 1]
363
+ : await luaGet(tbl, k, sf.astCtx ?? null, sf);
364
+ result.push(v);
365
+ }
366
+ return new LuaMultiRes(result);
367
+ },
368
+ ),
369
+
370
+ // Non-standard Lua functions
371
+ /**
372
+ * Finds an element in a table that matches a criteria function. Returns the first matching element.
373
+ * @param tbl - The table to search.
374
+ * @param criteriaFn - The criteria function.
375
+ * @param fromIndex - The index to start searching from.
376
+ * @returns Lua multi value of index, value, or nil if no element is found.
377
+ */
378
+ find: new LuaBuiltinFunction(
379
+ async (
380
+ sf,
381
+ tbl: LuaTable | any[],
382
+ criteriaFn: ILuaFunction,
383
+ fromIndex = 1,
384
+ ) => {
385
+ if (!tbl) {
386
+ return null;
387
+ }
388
+ const startIndex = fromIndex < 1 ? 1 : fromIndex;
389
+ const n = Array.isArray(tbl)
390
+ ? tbl.length
391
+ : await luaLenForTableLibAsync(sf, tbl);
392
+ for (let i = startIndex; i <= n; i++) {
393
+ const val = await luaGet(tbl, i, sf.astCtx ?? null, sf);
394
+ if (await luaCall(criteriaFn, [val], sf.astCtx!, sf)) {
395
+ return new LuaMultiRes([i, val]);
396
+ }
397
+ }
398
+ return null;
399
+ },
400
+ ),
401
+ });