@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.
- package/README.md +20 -4
- package/client/markdown_parser/constants.ts +2 -2
- package/client/plugos/hooks/code_widget.ts +0 -3
- package/client/plugos/hooks/document_editor.ts +0 -3
- package/client/plugos/hooks/event.ts +1 -1
- package/client/plugos/hooks/mq.ts +1 -1
- package/client/plugos/hooks/plug_namespace.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +2 -2
- package/client/plugos/plug.ts +0 -1
- package/client/plugos/plug_compile.ts +28 -29
- package/client/plugos/proxy_fetch.ts +1 -1
- package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
- package/client/plugos/sandboxes/worker_sandbox.ts +2 -3
- package/client/plugos/syscalls/editor.ts +12 -12
- package/client/plugos/syscalls/fetch.ts +1 -1
- package/client/plugos/syscalls/jsonschema.ts +1 -1
- package/client/plugos/syscalls/mq.ts +1 -1
- package/client/plugos/syscalls/space.ts +1 -1
- package/client/plugos/system.ts +2 -2
- package/client/plugos/worker_runtime.ts +8 -29
- package/client/space_lua/aggregates.ts +209 -0
- package/client/space_lua/ast.ts +24 -2
- package/client/space_lua/eval.ts +58 -53
- package/client/space_lua/labels.ts +1 -1
- package/client/space_lua/parse.ts +117 -12
- package/client/space_lua/query_collection.ts +850 -70
- package/client/space_lua/query_env.ts +26 -0
- package/client/space_lua/runtime.ts +47 -17
- package/client/space_lua/stdlib/format.ts +19 -19
- package/client/space_lua/stdlib/math.ts +73 -48
- package/client/space_lua/stdlib/net.ts +2 -2
- package/client/space_lua/stdlib/os.ts +5 -0
- package/client/space_lua/stdlib/pattern.ts +702 -0
- package/client/space_lua/stdlib/prng.ts +145 -0
- package/client/space_lua/stdlib/space_lua.ts +3 -8
- package/client/space_lua/stdlib/string.ts +103 -181
- package/client/space_lua/stdlib/string_pack.ts +486 -0
- package/client/space_lua/stdlib/table.ts +73 -9
- package/client/space_lua/stdlib.ts +38 -14
- package/client/space_lua/tonumber.ts +3 -2
- package/client/space_lua/util.ts +43 -9
- package/dist/plug-compile.js +23 -69
- package/dist/worker_runtime_bundle.js +233 -0
- package/package.json +16 -11
- package/plug-api/constants.ts +0 -32
- package/plug-api/lib/async.ts +2 -2
- package/plug-api/lib/crypto.ts +11 -11
- package/plug-api/lib/json.ts +1 -1
- package/plug-api/lib/limited_map.ts +1 -1
- package/plug-api/lib/native_fetch.ts +2 -0
- package/plug-api/lib/ref.ts +5 -5
- package/plug-api/lib/transclusion.ts +5 -5
- package/plug-api/lib/tree.ts +50 -2
- package/plug-api/lib/yaml.ts +10 -10
- package/plug-api/syscalls/editor.ts +1 -1
- package/plug-api/system_mock.ts +0 -1
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
|
@@ -0,0 +1,145 @@
|
|
|
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 = this.rotl((s1 * 5n) & 0xFFFFFFFFFFFFFFFFn, 7) * 9n &
|
|
24
|
+
0xFFFFFFFFFFFFFFFFn;
|
|
25
|
+
|
|
26
|
+
const t = (s1 << 17n) & 0xFFFFFFFFFFFFFFFFn;
|
|
27
|
+
s[2] = s2 ^ s0;
|
|
28
|
+
s[3] = s3 ^ s1;
|
|
29
|
+
s[1] = s1 ^ s[2];
|
|
30
|
+
s[0] = s0 ^ s[3];
|
|
31
|
+
s[2] = s[2] ^ t;
|
|
32
|
+
s[3] = this.rotl(s[3], 45);
|
|
33
|
+
|
|
34
|
+
return res;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public setSeed(seed1: bigint, seed2: bigint = 0n): [bigint, bigint] {
|
|
38
|
+
const MASK = 0xFFFFFFFFFFFFFFFFn;
|
|
39
|
+
const s = this.state;
|
|
40
|
+
|
|
41
|
+
const sm64 = (x: bigint): bigint => {
|
|
42
|
+
x = (x ^ (x >> 30n)) * 0xBF58476D1CE4E5B9n & MASK;
|
|
43
|
+
x = (x ^ (x >> 27n)) * 0x94D049BB133111EBn & MASK;
|
|
44
|
+
return (x ^ (x >> 31n)) & MASK;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
s[0] = sm64(seed1 & MASK);
|
|
48
|
+
s[1] = sm64((seed1 & MASK) | 0xFFn);
|
|
49
|
+
s[2] = sm64(seed2 & MASK);
|
|
50
|
+
s[3] = sm64(0n);
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < 16; i++) {
|
|
53
|
+
this.nextrand();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [seed1, seed2];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private autoSeed(): [bigint, bigint] {
|
|
60
|
+
const t = BigInt(Date.now());
|
|
61
|
+
const entropy = BigInt(Math.floor(performance.now() * 1000));
|
|
62
|
+
return this.setSeed(t, entropy);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private project(ran: bigint, n: bigint): bigint {
|
|
66
|
+
if (n === 0n) return 0n;
|
|
67
|
+
|
|
68
|
+
let lim = n;
|
|
69
|
+
lim |= lim >> 1n;
|
|
70
|
+
lim |= lim >> 2n;
|
|
71
|
+
lim |= lim >> 4n;
|
|
72
|
+
lim |= lim >> 8n;
|
|
73
|
+
lim |= lim >> 16n;
|
|
74
|
+
lim |= lim >> 32n;
|
|
75
|
+
|
|
76
|
+
while (true) {
|
|
77
|
+
ran &= lim;
|
|
78
|
+
if (ran <= n) return ran;
|
|
79
|
+
ran = this.nextrand();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// `random()` yields float in [0, 1)
|
|
84
|
+
// `random(0)` yields raw 64-bit signed integer (all bits random)
|
|
85
|
+
// `random(n)` yields integer in [1, n]
|
|
86
|
+
// `random(m, n)` yields integer in [m, n]
|
|
87
|
+
public random(arg1?: number, arg2?: number): number | bigint {
|
|
88
|
+
const rv = this.nextrand();
|
|
89
|
+
|
|
90
|
+
if (arg1 === undefined) {
|
|
91
|
+
// Top 53 bits for full double precision
|
|
92
|
+
return Number(rv >> 11n) * (1.0 / 9007199254740992.0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!Number.isFinite(arg1) || !Number.isInteger(arg1)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"bad argument #1 to 'random' (number has no integer representation)",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (arg2 === undefined) {
|
|
102
|
+
if (arg1 === 0) {
|
|
103
|
+
// Raw 64-bit as signed bigint
|
|
104
|
+
const signed = rv > 0x7FFFFFFFFFFFFFFFn
|
|
105
|
+
? rv - 0x10000000000000000n
|
|
106
|
+
: 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 (arg2 !== undefined && (!Number.isFinite(arg2) || !Number.isInteger(arg2))) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
"bad argument #2 to 'randomseed' (number has no integer representation)",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const s1 = BigInt(Math.trunc(arg1));
|
|
142
|
+
const s2 = arg2 !== undefined ? BigInt(Math.trunc(arg2)) : 0n;
|
|
143
|
+
return this.setSeed(s1, s2);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -137,17 +137,12 @@ export const spaceluaApi = new LuaTable({
|
|
|
137
137
|
},
|
|
138
138
|
),
|
|
139
139
|
/**
|
|
140
|
-
* Returns your SilverBullet instance's base URL
|
|
140
|
+
* Returns your SilverBullet instance's base URL
|
|
141
141
|
*/
|
|
142
142
|
baseUrl: new LuaBuiltinFunction(
|
|
143
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
|
-
}
|
|
144
|
+
//NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;`
|
|
145
|
+
return document.baseURI.replace(/\/*$/, "");
|
|
151
146
|
},
|
|
152
147
|
),
|
|
153
148
|
});
|
|
@@ -6,79 +6,35 @@ 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
|
-
|
|
27
|
+
return new LuaMultiRes(
|
|
28
|
+
caps.map((c) => ("s" in c ? c.s : c.position)),
|
|
29
|
+
);
|
|
76
30
|
}
|
|
77
31
|
|
|
78
32
|
export const stringApi = new LuaTable({
|
|
79
33
|
byte: new LuaBuiltinFunction((_sf, s: string, i?: number, j?: number) => {
|
|
80
34
|
i = i ?? 1;
|
|
81
35
|
j = j ?? i;
|
|
36
|
+
if (j > s.length) j = s.length;
|
|
37
|
+
if (i < 1) i = 1;
|
|
82
38
|
const result = [];
|
|
83
39
|
for (let k = i; k <= j; k++) {
|
|
84
40
|
result.push(s.charCodeAt(k - 1));
|
|
@@ -90,119 +46,70 @@ export const stringApi = new LuaTable({
|
|
|
90
46
|
}),
|
|
91
47
|
find: new LuaBuiltinFunction(
|
|
92
48
|
(_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));
|
|
49
|
+
const r = patternFind(s, pattern, init, plain);
|
|
50
|
+
if (!r) return null;
|
|
51
|
+
const result: any[] = [r.start, r.end];
|
|
52
|
+
for (const c of r.captures) {
|
|
53
|
+
result.push("s" in c ? c.s : c.position);
|
|
106
54
|
}
|
|
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]);
|
|
55
|
+
return new LuaMultiRes(result);
|
|
113
56
|
},
|
|
114
57
|
),
|
|
115
58
|
format: new LuaBuiltinFunction((_sf, format: string, ...args: any[]) => {
|
|
116
|
-
// Unwrap tagged floats so luaFormat sees plain numbers
|
|
117
59
|
for (let i = 0; i < args.length; i++) {
|
|
118
60
|
args[i] = untagNumber(args[i]);
|
|
119
61
|
}
|
|
120
62
|
return luaFormat(format, ...args);
|
|
121
63
|
}),
|
|
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
|
-
}),
|
|
64
|
+
gmatch: new LuaBuiltinFunction(
|
|
65
|
+
(_sf, s: string, pattern: string, init = 1) => {
|
|
66
|
+
const iter = patternGmatch(s, pattern, init);
|
|
67
|
+
return () => {
|
|
68
|
+
const caps = iter();
|
|
69
|
+
if (!caps) return;
|
|
70
|
+
return capturesToLua(caps);
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
),
|
|
140
74
|
gsub: new LuaBuiltinFunction(
|
|
141
75
|
async (
|
|
142
76
|
sf,
|
|
143
77
|
s: string,
|
|
144
78
|
pattern: string,
|
|
145
|
-
repl: any,
|
|
146
|
-
n
|
|
79
|
+
repl: any,
|
|
80
|
+
n?: number,
|
|
147
81
|
) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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];
|
|
82
|
+
const callbacks: GsubCallbacks = {};
|
|
83
|
+
if (typeof repl === "string") {
|
|
84
|
+
callbacks.replString = repl;
|
|
85
|
+
} else if (repl instanceof LuaTable) {
|
|
86
|
+
callbacks.replTable = (key: string) => {
|
|
87
|
+
const v = repl.get(key);
|
|
88
|
+
if (v === null || v === undefined || v === false) return null;
|
|
89
|
+
return typeof v === "number"
|
|
90
|
+
? String(v)
|
|
91
|
+
: String(isTaggedFloat(v) ? v.value : v);
|
|
92
|
+
};
|
|
93
|
+
} else if (repl.call) {
|
|
94
|
+
callbacks.replFunction = async (...caps: CaptureResult[]) => {
|
|
95
|
+
const args = caps.map((c) => ("s" in c ? c.s : c.position));
|
|
96
|
+
let result = await repl.call(sf, ...args);
|
|
97
|
+
if (result instanceof LuaMultiRes) {
|
|
98
|
+
result = result.values[0];
|
|
176
99
|
}
|
|
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);
|
|
100
|
+
if (result === null || result === undefined || result === false) {
|
|
101
|
+
return null;
|
|
193
102
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
count++;
|
|
103
|
+
return luaToString(result);
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
throw new LuaRuntimeError(
|
|
107
|
+
"string.gsub replacement argument should be a function, table or string",
|
|
108
|
+
sf,
|
|
109
|
+
);
|
|
203
110
|
}
|
|
204
|
-
|
|
205
|
-
return new LuaMultiRes([
|
|
111
|
+
const [result, count] = await patternGsub(s, pattern, callbacks, n);
|
|
112
|
+
return new LuaMultiRes([result, count]);
|
|
206
113
|
},
|
|
207
114
|
),
|
|
208
115
|
len: new LuaBuiltinFunction((_sf, s: string) => {
|
|
@@ -216,43 +123,58 @@ export const stringApi = new LuaTable({
|
|
|
216
123
|
}),
|
|
217
124
|
match: new LuaBuiltinFunction(
|
|
218
125
|
(_sf, s: string, pattern: string, init = 1) => {
|
|
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);
|
|
126
|
+
const caps = patternMatch(s, pattern, init);
|
|
127
|
+
if (!caps) return null;
|
|
128
|
+
return capturesToLua(caps);
|
|
232
129
|
},
|
|
233
130
|
),
|
|
234
131
|
rep: new LuaBuiltinFunction((_sf, s: string, n: number, sep?: string) => {
|
|
132
|
+
if (n <= 0) return "";
|
|
235
133
|
sep = sep ?? "";
|
|
236
|
-
|
|
134
|
+
const parts: string[] = [];
|
|
135
|
+
for (let i = 0; i < n; i++) {
|
|
136
|
+
parts.push(s);
|
|
137
|
+
}
|
|
138
|
+
return parts.join(sep);
|
|
237
139
|
}),
|
|
238
140
|
reverse: new LuaBuiltinFunction((_sf, s: string) => {
|
|
239
141
|
return s.split("").reverse().join("");
|
|
240
142
|
}),
|
|
241
143
|
sub: new LuaBuiltinFunction((_sf, s: string, i: number, j?: number) => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
144
|
+
const len = s.length;
|
|
145
|
+
let start: number;
|
|
146
|
+
if (i > 0) {
|
|
147
|
+
start = i;
|
|
148
|
+
} else if (i < -len) {
|
|
149
|
+
start = 1;
|
|
150
|
+
} else {
|
|
151
|
+
start = i === 0 ? 1 : len + i + 1;
|
|
245
152
|
}
|
|
246
|
-
|
|
247
|
-
|
|
153
|
+
let end: number;
|
|
154
|
+
if (j === undefined || j === null || j > len) {
|
|
155
|
+
end = len;
|
|
156
|
+
} else if (j >= 0) {
|
|
157
|
+
end = j;
|
|
158
|
+
} else if (j < -len) {
|
|
159
|
+
end = 0;
|
|
160
|
+
} else {
|
|
161
|
+
end = len + j + 1;
|
|
248
162
|
}
|
|
249
|
-
|
|
163
|
+
if (start <= end) {
|
|
164
|
+
return s.substring(start - 1, end);
|
|
165
|
+
}
|
|
166
|
+
return "";
|
|
250
167
|
}),
|
|
168
|
+
|
|
251
169
|
split: new LuaBuiltinFunction((_sf, s: string, sep: string) => {
|
|
252
170
|
return s.split(sep);
|
|
253
171
|
}),
|
|
254
172
|
|
|
255
|
-
|
|
173
|
+
pack: strPackFn,
|
|
174
|
+
unpack: strUnpackFn,
|
|
175
|
+
packsize: strPackSizeFn,
|
|
176
|
+
|
|
177
|
+
// Non-standard extensions
|
|
256
178
|
startsWith: new LuaBuiltinFunction((_sf, s: string, prefix: string) => {
|
|
257
179
|
return s.startsWith(prefix);
|
|
258
180
|
}),
|