@silverbulletmd/silverbullet 2.4.2 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -4
- package/client/asset_bundle/bundle.ts +3 -9
- package/client/data/datastore.ts +4 -5
- package/client/markdown_parser/constants.ts +5 -4
- package/client/plugos/hooks/code_widget.ts +3 -8
- package/client/plugos/hooks/command.ts +8 -8
- package/client/plugos/hooks/document_editor.ts +10 -15
- package/client/plugos/hooks/event.ts +33 -36
- package/client/plugos/hooks/mq.ts +17 -17
- package/client/plugos/hooks/plug_namespace.ts +3 -8
- package/client/plugos/hooks/slash_command.ts +13 -28
- package/client/plugos/hooks/syscall.ts +3 -3
- package/client/plugos/manifest_cache.ts +22 -15
- package/client/plugos/plug.ts +2 -6
- package/client/plugos/plug_compile.ts +79 -78
- package/client/plugos/protocol.ts +28 -28
- package/client/plugos/proxy_fetch.ts +7 -6
- package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
- package/client/plugos/sandboxes/worker_sandbox.ts +18 -18
- package/client/plugos/syscalls/asset.ts +1 -3
- package/client/plugos/syscalls/code_widget.ts +1 -3
- package/client/plugos/syscalls/config.ts +1 -5
- package/client/plugos/syscalls/datastore.ts +1 -1
- package/client/plugos/syscalls/editor.ts +72 -69
- package/client/plugos/syscalls/event.ts +9 -12
- package/client/plugos/syscalls/fetch.ts +31 -23
- package/client/plugos/syscalls/index.ts +10 -1
- package/client/plugos/syscalls/jsonschema.ts +72 -32
- package/client/plugos/syscalls/language.ts +9 -5
- package/client/plugos/syscalls/markdown.ts +29 -7
- package/client/plugos/syscalls/mq.ts +4 -12
- package/client/plugos/syscalls/service_registry.ts +1 -4
- package/client/plugos/syscalls/shell.ts +2 -5
- package/client/plugos/syscalls/space.ts +1 -1
- package/client/plugos/syscalls/sync.ts +69 -60
- package/client/plugos/syscalls/system.ts +2 -3
- package/client/plugos/system.ts +6 -12
- package/client/plugos/worker_runtime.ts +12 -33
- package/client/space_lua/aggregates.ts +782 -0
- package/client/space_lua/ast.ts +42 -8
- package/client/space_lua/ast_narrow.ts +4 -2
- package/client/space_lua/eval.ts +886 -575
- package/client/space_lua/labels.ts +7 -12
- package/client/space_lua/liq_null.ts +6 -0
- package/client/space_lua/numeric.ts +5 -8
- package/client/space_lua/parse.ts +346 -120
- package/client/space_lua/query_collection.ts +926 -82
- package/client/space_lua/query_env.ts +26 -0
- package/client/space_lua/render_lua_markdown.ts +369 -0
- package/client/space_lua/rp.ts +5 -4
- package/client/space_lua/runtime.ts +288 -155
- package/client/space_lua/stdlib/format.ts +53 -39
- package/client/space_lua/stdlib/js.ts +3 -7
- package/client/space_lua/stdlib/load.ts +1 -3
- package/client/space_lua/stdlib/math.ts +84 -58
- package/client/space_lua/stdlib/net.ts +27 -17
- package/client/space_lua/stdlib/os.ts +81 -85
- package/client/space_lua/stdlib/pattern.ts +695 -0
- package/client/space_lua/stdlib/prng.ts +148 -0
- package/client/space_lua/stdlib/space_lua.ts +17 -23
- package/client/space_lua/stdlib/string.ts +102 -190
- package/client/space_lua/stdlib/string_pack.ts +490 -0
- package/client/space_lua/stdlib/table.ts +76 -16
- package/client/space_lua/stdlib.ts +53 -39
- package/client/space_lua/tonumber.ts +82 -42
- package/client/space_lua/util.ts +53 -15
- package/dist/plug-compile.js +55 -98
- package/package.json +27 -20
- package/plug-api/constants.ts +0 -32
- package/plug-api/lib/async.ts +20 -7
- package/plug-api/lib/crypto.ts +16 -17
- package/plug-api/lib/dates.ts +15 -7
- package/plug-api/lib/json.ts +11 -5
- package/plug-api/lib/limited_map.ts +1 -1
- package/plug-api/lib/native_fetch.ts +2 -0
- package/plug-api/lib/ref.ts +23 -23
- package/plug-api/lib/resolve.ts +7 -11
- package/plug-api/lib/tags.ts +13 -4
- package/plug-api/lib/transclusion.ts +10 -21
- package/plug-api/lib/tree.ts +165 -45
- package/plug-api/lib/yaml.ts +35 -25
- package/plug-api/syscalls/asset.ts +1 -1
- package/plug-api/syscalls/config.ts +1 -4
- package/plug-api/syscalls/editor.ts +15 -15
- package/plug-api/syscalls/jsonschema.ts +1 -3
- package/plug-api/syscalls/lua.ts +3 -9
- package/plug-api/syscalls/mq.ts +1 -4
- package/plug-api/syscalls/shell.ts +4 -1
- package/plug-api/syscalls/space.ts +3 -10
- package/plug-api/syscalls/system.ts +1 -4
- package/plug-api/syscalls/yaml.ts +2 -6
- package/plug-api/system_mock.ts +0 -1
- package/plug-api/types/client.ts +16 -1
- package/plug-api/types/event.ts +6 -4
- package/plug-api/types/manifest.ts +8 -9
- package/plugs/builtin_plugs.ts +2 -2
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// PRNG based on xoshiro256** for Space Lua
|
|
2
|
+
|
|
3
|
+
export class LuaPRNG {
|
|
4
|
+
private state: BigUint64Array;
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this.state = new BigUint64Array(4);
|
|
8
|
+
this.autoSeed();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private rotl(x: bigint, k: number): bigint {
|
|
12
|
+
k = k & 63;
|
|
13
|
+
return ((x << BigInt(k)) | (x >> BigInt(64 - k))) & 0xffffffffffffffffn;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private nextrand(): bigint {
|
|
17
|
+
const s = this.state;
|
|
18
|
+
const s0 = s[0];
|
|
19
|
+
const s1 = s[1];
|
|
20
|
+
const s2 = s[2];
|
|
21
|
+
const s3 = s[3];
|
|
22
|
+
|
|
23
|
+
const res =
|
|
24
|
+
(this.rotl((s1 * 5n) & 0xffffffffffffffffn, 7) * 9n) &
|
|
25
|
+
0xffffffffffffffffn;
|
|
26
|
+
|
|
27
|
+
const t = (s1 << 17n) & 0xffffffffffffffffn;
|
|
28
|
+
s[2] = s2 ^ s0;
|
|
29
|
+
s[3] = s3 ^ s1;
|
|
30
|
+
s[1] = s1 ^ s[2];
|
|
31
|
+
s[0] = s0 ^ s[3];
|
|
32
|
+
s[2] = s[2] ^ t;
|
|
33
|
+
s[3] = this.rotl(s[3], 45);
|
|
34
|
+
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public setSeed(seed1: bigint, seed2: bigint = 0n): [bigint, bigint] {
|
|
39
|
+
const MASK = 0xffffffffffffffffn;
|
|
40
|
+
const s = this.state;
|
|
41
|
+
|
|
42
|
+
const sm64 = (x: bigint): bigint => {
|
|
43
|
+
x = ((x ^ (x >> 30n)) * 0xbf58476d1ce4e5b9n) & MASK;
|
|
44
|
+
x = ((x ^ (x >> 27n)) * 0x94d049bb133111ebn) & MASK;
|
|
45
|
+
return (x ^ (x >> 31n)) & MASK;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
s[0] = sm64(seed1 & MASK);
|
|
49
|
+
s[1] = sm64((seed1 & MASK) | 0xffn);
|
|
50
|
+
s[2] = sm64(seed2 & MASK);
|
|
51
|
+
s[3] = sm64(0n);
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < 16; i++) {
|
|
54
|
+
this.nextrand();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return [seed1, seed2];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private autoSeed(): [bigint, bigint] {
|
|
61
|
+
const t = BigInt(Date.now());
|
|
62
|
+
const entropy = BigInt(Math.floor(performance.now() * 1000));
|
|
63
|
+
return this.setSeed(t, entropy);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private project(ran: bigint, n: bigint): bigint {
|
|
67
|
+
if (n === 0n) return 0n;
|
|
68
|
+
|
|
69
|
+
let lim = n;
|
|
70
|
+
lim |= lim >> 1n;
|
|
71
|
+
lim |= lim >> 2n;
|
|
72
|
+
lim |= lim >> 4n;
|
|
73
|
+
lim |= lim >> 8n;
|
|
74
|
+
lim |= lim >> 16n;
|
|
75
|
+
lim |= lim >> 32n;
|
|
76
|
+
|
|
77
|
+
while (true) {
|
|
78
|
+
ran &= lim;
|
|
79
|
+
if (ran <= n) return ran;
|
|
80
|
+
ran = this.nextrand();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// `random()` yields float in [0, 1)
|
|
85
|
+
// `random(0)` yields raw 64-bit signed integer (all bits random)
|
|
86
|
+
// `random(n)` yields integer in [1, n]
|
|
87
|
+
// `random(m, n)` yields integer in [m, n]
|
|
88
|
+
public random(arg1?: number, arg2?: number): number | bigint {
|
|
89
|
+
const rv = this.nextrand();
|
|
90
|
+
|
|
91
|
+
if (arg1 === undefined) {
|
|
92
|
+
// Top 53 bits for full double precision
|
|
93
|
+
return Number(rv >> 11n) * (1.0 / 9007199254740992.0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!Number.isFinite(arg1) || !Number.isInteger(arg1)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"bad argument #1 to 'random' (number has no integer representation)",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (arg2 === undefined) {
|
|
103
|
+
if (arg1 === 0) {
|
|
104
|
+
// Raw 64-bit as signed bigint
|
|
105
|
+
const signed =
|
|
106
|
+
rv > 0x7fffffffffffffffn ? rv - 0x10000000000000000n : rv;
|
|
107
|
+
return signed;
|
|
108
|
+
}
|
|
109
|
+
if (arg1 < 1) {
|
|
110
|
+
throw new Error("bad argument #1 to 'random' (interval is empty)");
|
|
111
|
+
}
|
|
112
|
+
return Number(this.project(rv, BigInt(arg1) - 1n) + 1n);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!Number.isFinite(arg2) || !Number.isInteger(arg2)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"bad argument #2 to 'random' (number has no integer representation)",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (arg2 < arg1) {
|
|
121
|
+
throw new Error("bad argument #2 to 'random' (interval is empty)");
|
|
122
|
+
}
|
|
123
|
+
return Number(this.project(rv, BigInt(arg2) - BigInt(arg1)) + BigInt(arg1));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Returns [seed1, seed2]
|
|
127
|
+
public randomseed(arg1?: number, arg2?: number): [bigint, bigint] {
|
|
128
|
+
if (arg1 === undefined) {
|
|
129
|
+
return this.autoSeed();
|
|
130
|
+
}
|
|
131
|
+
if (!Number.isFinite(arg1) || !Number.isInteger(arg1)) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
"bad argument #1 to 'randomseed' (number has no integer representation)",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
arg2 !== undefined &&
|
|
138
|
+
(!Number.isFinite(arg2) || !Number.isInteger(arg2))
|
|
139
|
+
) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
"bad argument #2 to 'randomseed' (number has no integer representation)",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const s1 = BigInt(Math.trunc(arg1));
|
|
145
|
+
const s2 = arg2 !== undefined ? BigInt(Math.trunc(arg2)) : 0n;
|
|
146
|
+
return this.setSeed(s1, s2);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -2,6 +2,7 @@ import { parseExpressionString } from "../parse.ts";
|
|
|
2
2
|
import type { LuaExpression } from "../ast.ts";
|
|
3
3
|
import { evalExpression } from "../eval.ts";
|
|
4
4
|
import {
|
|
5
|
+
jsToLuaValue,
|
|
5
6
|
LuaBuiltinFunction,
|
|
6
7
|
LuaEnv,
|
|
7
8
|
LuaRuntimeError,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
luaValueToJS,
|
|
12
13
|
singleResult,
|
|
13
14
|
} from "../runtime.ts";
|
|
15
|
+
import { isSqlNull } from "../liq_null.ts";
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* These are Space Lua specific functions that are available to all scripts, but are not part of the standard Lua language.
|
|
@@ -31,7 +33,8 @@ function createAugmentedEnv(
|
|
|
31
33
|
if (envAugmentation) {
|
|
32
34
|
env.setLocal("_", envAugmentation);
|
|
33
35
|
for (const key of envAugmentation.keys()) {
|
|
34
|
-
|
|
36
|
+
const v = envAugmentation.rawGet(key);
|
|
37
|
+
env.setLocal(key, isSqlNull(v) ? null : v);
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
return env;
|
|
@@ -89,10 +92,7 @@ export async function interpolateLuaString(
|
|
|
89
92
|
const luaResult = singleResult(await evalExpression(parsedExpr, env, sf));
|
|
90
93
|
result += await luaToString(luaResult);
|
|
91
94
|
} catch (e: any) {
|
|
92
|
-
throw new LuaRuntimeError(
|
|
93
|
-
`Error evaluating "${expr}": ${e.message}`,
|
|
94
|
-
sf,
|
|
95
|
-
);
|
|
95
|
+
throw new LuaRuntimeError(`Error evaluating "${expr}": ${e.message}`, sf);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
currentIndex = endIndex + 1;
|
|
@@ -109,11 +109,9 @@ export const spaceluaApi = new LuaTable({
|
|
|
109
109
|
* @param luaExpression - The lua expression to parse.
|
|
110
110
|
* @returns The parsed expression.
|
|
111
111
|
*/
|
|
112
|
-
parseExpression: new LuaBuiltinFunction(
|
|
113
|
-
(
|
|
114
|
-
|
|
115
|
-
},
|
|
116
|
-
),
|
|
112
|
+
parseExpression: new LuaBuiltinFunction((_sf, luaExpression: string) => {
|
|
113
|
+
return parseExpressionString(luaExpression);
|
|
114
|
+
}),
|
|
117
115
|
/**
|
|
118
116
|
* Evaluates a parsed lua expression and returns the result.
|
|
119
117
|
*
|
|
@@ -132,22 +130,18 @@ export const spaceluaApi = new LuaTable({
|
|
|
132
130
|
* Interpolates a string with lua expressions and returns the result.
|
|
133
131
|
*/
|
|
134
132
|
interpolate: new LuaBuiltinFunction(
|
|
135
|
-
(sf, template: string, envAugmentation?: LuaTable) => {
|
|
133
|
+
(sf, template: string, envAugmentation?: LuaTable | any) => {
|
|
134
|
+
if (envAugmentation && !(envAugmentation instanceof LuaTable)) {
|
|
135
|
+
envAugmentation = jsToLuaValue(envAugmentation);
|
|
136
|
+
}
|
|
136
137
|
return interpolateLuaString(sf, template, envAugmentation);
|
|
137
138
|
},
|
|
138
139
|
),
|
|
139
140
|
/**
|
|
140
|
-
* Returns your SilverBullet instance's base URL
|
|
141
|
+
* Returns your SilverBullet instance's base URL
|
|
141
142
|
*/
|
|
142
|
-
baseUrl: new LuaBuiltinFunction(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return null;
|
|
147
|
-
} else {
|
|
148
|
-
//NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;`
|
|
149
|
-
return document.baseURI.replace(/\/*$/, "");
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
),
|
|
143
|
+
baseUrl: new LuaBuiltinFunction(() => {
|
|
144
|
+
//NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;`
|
|
145
|
+
return document.baseURI.replace(/\/*$/, "");
|
|
146
|
+
}),
|
|
153
147
|
});
|
|
@@ -6,79 +6,33 @@ import {
|
|
|
6
6
|
LuaTable,
|
|
7
7
|
luaToString,
|
|
8
8
|
} from "../runtime.ts";
|
|
9
|
-
import { untagNumber } from "../numeric.ts";
|
|
9
|
+
import { isTaggedFloat, untagNumber } from "../numeric.ts";
|
|
10
10
|
import { luaFormat } from "./format.ts";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
}
|
|
11
|
+
import {
|
|
12
|
+
type CaptureResult,
|
|
13
|
+
type GsubCallbacks,
|
|
14
|
+
patternFind,
|
|
15
|
+
patternGmatch,
|
|
16
|
+
patternGsub,
|
|
17
|
+
patternMatch,
|
|
18
|
+
} from "./pattern.ts";
|
|
19
|
+
import { strPackFn, strPackSizeFn, strUnpackFn } from "./string_pack.ts";
|
|
20
|
+
|
|
21
|
+
function capturesToLua(caps: CaptureResult[]): any {
|
|
22
|
+
if (caps.length === 0) return null;
|
|
23
|
+
if (caps.length === 1) {
|
|
24
|
+
const c = caps[0];
|
|
25
|
+
return "s" in c ? c.s : c.position;
|
|
73
26
|
}
|
|
74
|
-
|
|
75
|
-
return pattern;
|
|
27
|
+
return new LuaMultiRes(caps.map((c) => ("s" in c ? c.s : c.position)));
|
|
76
28
|
}
|
|
77
29
|
|
|
78
30
|
export const stringApi = new LuaTable({
|
|
79
31
|
byte: new LuaBuiltinFunction((_sf, s: string, i?: number, j?: number) => {
|
|
80
32
|
i = i ?? 1;
|
|
81
33
|
j = j ?? i;
|
|
34
|
+
if (j > s.length) j = s.length;
|
|
35
|
+
if (i < 1) i = 1;
|
|
82
36
|
const result = [];
|
|
83
37
|
for (let k = i; k <= j; k++) {
|
|
84
38
|
result.push(s.charCodeAt(k - 1));
|
|
@@ -90,119 +44,64 @@ export const stringApi = new LuaTable({
|
|
|
90
44
|
}),
|
|
91
45
|
find: new LuaBuiltinFunction(
|
|
92
46
|
(_sf, s: string, pattern: string, init = 1, plain = false) => {
|
|
93
|
-
|
|
94
|
-
if (!
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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));
|
|
47
|
+
const r = patternFind(s, pattern, init, plain);
|
|
48
|
+
if (!r) return null;
|
|
49
|
+
const result: any[] = [r.start, r.end];
|
|
50
|
+
for (const c of r.captures) {
|
|
51
|
+
result.push("s" in c ? c.s : c.position);
|
|
106
52
|
}
|
|
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]);
|
|
53
|
+
return new LuaMultiRes(result);
|
|
113
54
|
},
|
|
114
55
|
),
|
|
115
56
|
format: new LuaBuiltinFunction((_sf, format: string, ...args: any[]) => {
|
|
116
|
-
// Unwrap tagged floats so luaFormat sees plain numbers
|
|
117
57
|
for (let i = 0; i < args.length; i++) {
|
|
118
58
|
args[i] = untagNumber(args[i]);
|
|
119
59
|
}
|
|
120
60
|
return luaFormat(format, ...args);
|
|
121
61
|
}),
|
|
122
|
-
gmatch: new LuaBuiltinFunction(
|
|
123
|
-
pattern =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
}),
|
|
62
|
+
gmatch: new LuaBuiltinFunction(
|
|
63
|
+
(_sf, s: string, pattern: string, init = 1) => {
|
|
64
|
+
const iter = patternGmatch(s, pattern, init);
|
|
65
|
+
return () => {
|
|
66
|
+
const caps = iter();
|
|
67
|
+
if (!caps) return;
|
|
68
|
+
return capturesToLua(caps);
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
),
|
|
140
72
|
gsub: new LuaBuiltinFunction(
|
|
141
|
-
async (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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];
|
|
73
|
+
async (sf, s: string, pattern: string, repl: any, n?: number) => {
|
|
74
|
+
const callbacks: GsubCallbacks = {};
|
|
75
|
+
if (typeof repl === "string") {
|
|
76
|
+
callbacks.replString = repl;
|
|
77
|
+
} else if (repl instanceof LuaTable) {
|
|
78
|
+
callbacks.replTable = (key: string) => {
|
|
79
|
+
const v = repl.get(key);
|
|
80
|
+
if (v === null || v === undefined || v === false) return null;
|
|
81
|
+
return typeof v === "number"
|
|
82
|
+
? String(v)
|
|
83
|
+
: String(isTaggedFloat(v) ? v.value : v);
|
|
84
|
+
};
|
|
85
|
+
} else if (repl.call) {
|
|
86
|
+
callbacks.replFunction = async (...caps: CaptureResult[]) => {
|
|
87
|
+
const args = caps.map((c) => ("s" in c ? c.s : c.position));
|
|
88
|
+
let result = await repl.call(sf, ...args);
|
|
89
|
+
if (result instanceof LuaMultiRes) {
|
|
90
|
+
result = result.values[0];
|
|
176
91
|
}
|
|
177
|
-
|
|
178
|
-
|
|
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);
|
|
92
|
+
if (result === null || result === undefined || result === false) {
|
|
93
|
+
return null;
|
|
193
94
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
count++;
|
|
95
|
+
return luaToString(result);
|
|
96
|
+
};
|
|
97
|
+
} else {
|
|
98
|
+
throw new LuaRuntimeError(
|
|
99
|
+
"string.gsub replacement argument should be a function, table or string",
|
|
100
|
+
sf,
|
|
101
|
+
);
|
|
203
102
|
}
|
|
204
|
-
|
|
205
|
-
return new LuaMultiRes([
|
|
103
|
+
const [result, count] = await patternGsub(s, pattern, callbacks, n);
|
|
104
|
+
return new LuaMultiRes([result, count]);
|
|
206
105
|
},
|
|
207
106
|
),
|
|
208
107
|
len: new LuaBuiltinFunction((_sf, s: string) => {
|
|
@@ -214,45 +113,58 @@ export const stringApi = new LuaTable({
|
|
|
214
113
|
upper: new LuaBuiltinFunction((_sf, s: string) => {
|
|
215
114
|
return luaToString(s.toUpperCase());
|
|
216
115
|
}),
|
|
217
|
-
match: new LuaBuiltinFunction(
|
|
218
|
-
(
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
),
|
|
116
|
+
match: new LuaBuiltinFunction((_sf, s: string, pattern: string, init = 1) => {
|
|
117
|
+
const caps = patternMatch(s, pattern, init);
|
|
118
|
+
if (!caps) return null;
|
|
119
|
+
return capturesToLua(caps);
|
|
120
|
+
}),
|
|
234
121
|
rep: new LuaBuiltinFunction((_sf, s: string, n: number, sep?: string) => {
|
|
122
|
+
if (n <= 0) return "";
|
|
235
123
|
sep = sep ?? "";
|
|
236
|
-
|
|
124
|
+
const parts: string[] = [];
|
|
125
|
+
for (let i = 0; i < n; i++) {
|
|
126
|
+
parts.push(s);
|
|
127
|
+
}
|
|
128
|
+
return parts.join(sep);
|
|
237
129
|
}),
|
|
238
130
|
reverse: new LuaBuiltinFunction((_sf, s: string) => {
|
|
239
131
|
return s.split("").reverse().join("");
|
|
240
132
|
}),
|
|
241
133
|
sub: new LuaBuiltinFunction((_sf, s: string, i: number, j?: number) => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
134
|
+
const len = s.length;
|
|
135
|
+
let start: number;
|
|
136
|
+
if (i > 0) {
|
|
137
|
+
start = i;
|
|
138
|
+
} else if (i < -len) {
|
|
139
|
+
start = 1;
|
|
140
|
+
} else {
|
|
141
|
+
start = i === 0 ? 1 : len + i + 1;
|
|
245
142
|
}
|
|
246
|
-
|
|
247
|
-
|
|
143
|
+
let end: number;
|
|
144
|
+
if (j === undefined || j === null || j > len) {
|
|
145
|
+
end = len;
|
|
146
|
+
} else if (j >= 0) {
|
|
147
|
+
end = j;
|
|
148
|
+
} else if (j < -len) {
|
|
149
|
+
end = 0;
|
|
150
|
+
} else {
|
|
151
|
+
end = len + j + 1;
|
|
248
152
|
}
|
|
249
|
-
|
|
153
|
+
if (start <= end) {
|
|
154
|
+
return s.substring(start - 1, end);
|
|
155
|
+
}
|
|
156
|
+
return "";
|
|
250
157
|
}),
|
|
158
|
+
|
|
251
159
|
split: new LuaBuiltinFunction((_sf, s: string, sep: string) => {
|
|
252
160
|
return s.split(sep);
|
|
253
161
|
}),
|
|
254
162
|
|
|
255
|
-
|
|
163
|
+
pack: strPackFn,
|
|
164
|
+
unpack: strUnpackFn,
|
|
165
|
+
packsize: strPackSizeFn,
|
|
166
|
+
|
|
167
|
+
// Non-standard extensions
|
|
256
168
|
startsWith: new LuaBuiltinFunction((_sf, s: string, prefix: string) => {
|
|
257
169
|
return s.startsWith(prefix);
|
|
258
170
|
}),
|