@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.
- package/LICENSE.md +18 -0
- package/README.md +98 -0
- package/client/asset_bundle/bundle.ts +95 -0
- package/client/data/datastore.ts +85 -0
- package/client/data/kv_primitives.ts +25 -0
- package/client/markdown_parser/constants.ts +13 -0
- package/client/plugos/event.ts +36 -0
- package/client/plugos/eventhook.ts +8 -0
- package/client/plugos/hooks/code_widget.ts +59 -0
- package/client/plugos/hooks/command.ts +104 -0
- package/client/plugos/hooks/document_editor.ts +77 -0
- package/client/plugos/hooks/event.ts +187 -0
- package/client/plugos/hooks/mq.ts +154 -0
- package/client/plugos/hooks/plug_namespace.ts +85 -0
- package/client/plugos/hooks/slash_command.ts +192 -0
- package/client/plugos/hooks/syscall.ts +66 -0
- package/client/plugos/manifest_cache.ts +67 -0
- package/client/plugos/plug.ts +99 -0
- package/client/plugos/plug_compile.ts +202 -0
- package/client/plugos/protocol.ts +40 -0
- package/client/plugos/proxy_fetch.ts +53 -0
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
- package/client/plugos/sandboxes/sandbox.ts +14 -0
- package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
- package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
- package/client/plugos/syscalls/asset.ts +35 -0
- package/client/plugos/syscalls/clientStore.ts +21 -0
- package/client/plugos/syscalls/client_code_widget.ts +12 -0
- package/client/plugos/syscalls/code_widget.ts +24 -0
- package/client/plugos/syscalls/config.ts +46 -0
- package/client/plugos/syscalls/datastore.ts +89 -0
- package/client/plugos/syscalls/editor.ts +673 -0
- package/client/plugos/syscalls/event.ts +36 -0
- package/client/plugos/syscalls/fetch.ts +128 -0
- package/client/plugos/syscalls/index.ts +102 -0
- package/client/plugos/syscalls/jsonschema.ts +69 -0
- package/client/plugos/syscalls/language.ts +23 -0
- package/client/plugos/syscalls/lua.ts +58 -0
- package/client/plugos/syscalls/markdown.ts +84 -0
- package/client/plugos/syscalls/mq.ts +52 -0
- package/client/plugos/syscalls/service_registry.ts +43 -0
- package/client/plugos/syscalls/shell.ts +39 -0
- package/client/plugos/syscalls/space.ts +139 -0
- package/client/plugos/syscalls/sync.ts +77 -0
- package/client/plugos/syscalls/system.ts +150 -0
- package/client/plugos/system.ts +201 -0
- package/client/plugos/types.ts +60 -0
- package/client/plugos/util.ts +14 -0
- package/client/plugos/worker_runtime.ts +195 -0
- package/client/space_lua/ast.ts +328 -0
- package/client/space_lua/ast_narrow.ts +81 -0
- package/client/space_lua/eval.ts +2478 -0
- package/client/space_lua/labels.ts +416 -0
- package/client/space_lua/numeric.ts +240 -0
- package/client/space_lua/parse.ts +1522 -0
- package/client/space_lua/query_collection.ts +232 -0
- package/client/space_lua/rp.ts +27 -0
- package/client/space_lua/runtime.ts +1702 -0
- package/client/space_lua/stdlib/crypto.ts +10 -0
- package/client/space_lua/stdlib/encoding.ts +19 -0
- package/client/space_lua/stdlib/format.ts +770 -0
- package/client/space_lua/stdlib/js.ts +73 -0
- package/client/space_lua/stdlib/load.ts +52 -0
- package/client/space_lua/stdlib/math.ts +193 -0
- package/client/space_lua/stdlib/net.ts +113 -0
- package/client/space_lua/stdlib/os.ts +368 -0
- package/client/space_lua/stdlib/space_lua.ts +153 -0
- package/client/space_lua/stdlib/string.ts +286 -0
- package/client/space_lua/stdlib/table.ts +401 -0
- package/client/space_lua/stdlib.ts +489 -0
- package/client/space_lua/tonumber.ts +501 -0
- package/client/space_lua/util.ts +96 -0
- package/dist/plug-compile.js +1513 -0
- package/package.json +120 -0
- package/plug-api/constants.ts +42 -0
- package/plug-api/lib/async.ts +162 -0
- package/plug-api/lib/crypto.ts +202 -0
- package/plug-api/lib/dates.ts +13 -0
- package/plug-api/lib/json.ts +136 -0
- package/plug-api/lib/limited_map.ts +72 -0
- package/plug-api/lib/memory_cache.ts +21 -0
- package/plug-api/lib/native_fetch.ts +6 -0
- package/plug-api/lib/ref.ts +275 -0
- package/plug-api/lib/resolve.ts +90 -0
- package/plug-api/lib/tags.ts +15 -0
- package/plug-api/lib/transclusion.ts +122 -0
- package/plug-api/lib/tree.ts +232 -0
- package/plug-api/lib/yaml.ts +284 -0
- package/plug-api/syscall.ts +15 -0
- package/plug-api/syscalls/asset.ts +36 -0
- package/plug-api/syscalls/client_store.ts +33 -0
- package/plug-api/syscalls/code_widget.ts +8 -0
- package/plug-api/syscalls/config.ts +58 -0
- package/plug-api/syscalls/datastore.ts +96 -0
- package/plug-api/syscalls/editor.ts +517 -0
- package/plug-api/syscalls/event.ts +47 -0
- package/plug-api/syscalls/index.ts +77 -0
- package/plug-api/syscalls/jsonschema.ts +25 -0
- package/plug-api/syscalls/language.ts +23 -0
- package/plug-api/syscalls/lua.ts +20 -0
- package/plug-api/syscalls/markdown.ts +38 -0
- package/plug-api/syscalls/mq.ts +79 -0
- package/plug-api/syscalls/shell.ts +14 -0
- package/plug-api/syscalls/space.ts +212 -0
- package/plug-api/syscalls/sync.ts +28 -0
- package/plug-api/syscalls/system.ts +102 -0
- package/plug-api/syscalls/yaml.ts +28 -0
- package/plug-api/syscalls.ts +21 -0
- package/plug-api/system_mock.ts +89 -0
- package/plug-api/types/client.ts +116 -0
- package/plug-api/types/config.ts +22 -0
- package/plug-api/types/datastore.ts +28 -0
- package/plug-api/types/event.ts +27 -0
- package/plug-api/types/index.ts +56 -0
- package/plug-api/types/manifest.ts +98 -0
- package/plug-api/types/namespace.ts +6 -0
- 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
|
+
});
|