@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,770 @@
|
|
|
1
|
+
// Supported specifiers set: [diuoxXaAfeEgGcspq%]
|
|
2
|
+
// Supported flags set: [-+0# ]
|
|
3
|
+
// Width and precision via digits or `*`
|
|
4
|
+
|
|
5
|
+
type FormatSpec = {
|
|
6
|
+
flags: number; // FLAG_*
|
|
7
|
+
width: number;
|
|
8
|
+
hasPrec: boolean;
|
|
9
|
+
prec: number;
|
|
10
|
+
spec: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const FLAG_MINUS = 1;
|
|
14
|
+
const FLAG_PLUS = 2;
|
|
15
|
+
const FLAG_ZERO = 4;
|
|
16
|
+
const FLAG_HASH = 8;
|
|
17
|
+
const FLAG_SPACE = 16;
|
|
18
|
+
|
|
19
|
+
function isDigit(c: number): boolean {
|
|
20
|
+
return c >= 48 && c <= 57; // '0'..'9'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Parse a format spec starting after '%' and return index of specifier.
|
|
24
|
+
function parseSpec(
|
|
25
|
+
fmt: string,
|
|
26
|
+
start: number,
|
|
27
|
+
): { spec: FormatSpec; end: number } {
|
|
28
|
+
let i = start;
|
|
29
|
+
const len = fmt.length;
|
|
30
|
+
let flags = 0;
|
|
31
|
+
|
|
32
|
+
outer: while (i < len) {
|
|
33
|
+
switch (fmt.charCodeAt(i)) {
|
|
34
|
+
case 45:
|
|
35
|
+
flags |= FLAG_MINUS;
|
|
36
|
+
i++;
|
|
37
|
+
break; // '-'
|
|
38
|
+
case 43:
|
|
39
|
+
flags |= FLAG_PLUS;
|
|
40
|
+
i++;
|
|
41
|
+
break; // '+'
|
|
42
|
+
case 48:
|
|
43
|
+
flags |= FLAG_ZERO;
|
|
44
|
+
i++;
|
|
45
|
+
break; // '0'
|
|
46
|
+
case 35:
|
|
47
|
+
flags |= FLAG_HASH;
|
|
48
|
+
i++;
|
|
49
|
+
break; // '#'
|
|
50
|
+
case 32:
|
|
51
|
+
flags |= FLAG_SPACE;
|
|
52
|
+
i++;
|
|
53
|
+
break; // ' '
|
|
54
|
+
default:
|
|
55
|
+
break outer;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse width
|
|
60
|
+
let width = 0;
|
|
61
|
+
if (i < len && fmt.charCodeAt(i) === 42) { // '*'
|
|
62
|
+
width = -1;
|
|
63
|
+
i++;
|
|
64
|
+
} else {
|
|
65
|
+
while (i < len && isDigit(fmt.charCodeAt(i))) {
|
|
66
|
+
width = width * 10 + (fmt.charCodeAt(i) - 48);
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Parse precision
|
|
72
|
+
let hasPrec = false;
|
|
73
|
+
let prec = 0;
|
|
74
|
+
if (i < len && fmt.charCodeAt(i) === 46) { // '.'
|
|
75
|
+
hasPrec = true;
|
|
76
|
+
i++;
|
|
77
|
+
if (i < len && fmt.charCodeAt(i) === 42) { // '*'
|
|
78
|
+
prec = -1;
|
|
79
|
+
i++;
|
|
80
|
+
} else {
|
|
81
|
+
while (i < len && isDigit(fmt.charCodeAt(i))) {
|
|
82
|
+
prec = prec * 10 + (fmt.charCodeAt(i) - 48);
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Skip length modifiers [hlL] ignored in Lua
|
|
89
|
+
while (
|
|
90
|
+
i < len &&
|
|
91
|
+
(fmt.charCodeAt(i) === 104 || // 'h'
|
|
92
|
+
fmt.charCodeAt(i) === 108 || // 'l'
|
|
93
|
+
fmt.charCodeAt(i) === 76) // 'L'
|
|
94
|
+
) {
|
|
95
|
+
i++;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (i >= len) {
|
|
99
|
+
throw new Error("invalid format (missing specifier)");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
spec: { flags, width, hasPrec, prec, spec: fmt.charCodeAt(i) },
|
|
104
|
+
end: i,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// pad a string to `width` respecting `FLAG_MINUS` and `FLAG_ZERO`
|
|
109
|
+
function pad(s: string, width: number, flags: number, numPad: boolean): string {
|
|
110
|
+
if (width <= 0 || s.length >= width) return s;
|
|
111
|
+
const n = width - s.length;
|
|
112
|
+
if (numPad && (flags & FLAG_ZERO) && !(flags & FLAG_MINUS)) {
|
|
113
|
+
let signLen = 0;
|
|
114
|
+
if (s.charCodeAt(0) === 45 || s.charCodeAt(0) === 43) { // '-' or '+'
|
|
115
|
+
signLen = 1;
|
|
116
|
+
} else if (
|
|
117
|
+
s.charCodeAt(0) === 48 &&
|
|
118
|
+
(s.charCodeAt(1) === 120 || s.charCodeAt(1) === 88)
|
|
119
|
+
) {
|
|
120
|
+
signLen = 2; // '0x' or '0X'
|
|
121
|
+
}
|
|
122
|
+
return s.slice(0, signLen) + "0".repeat(n) + s.slice(signLen);
|
|
123
|
+
}
|
|
124
|
+
if (flags & FLAG_MINUS) {
|
|
125
|
+
return s + " ".repeat(n);
|
|
126
|
+
}
|
|
127
|
+
return " ".repeat(n) + s;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function addSign(s: string, flags: number): string {
|
|
131
|
+
if (flags & FLAG_PLUS) return "+" + s;
|
|
132
|
+
if (flags & FLAG_SPACE) return " " + s;
|
|
133
|
+
return s;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function formatInt(n: number, spec: FormatSpec): string {
|
|
137
|
+
const code = spec.spec;
|
|
138
|
+
const v = Math.trunc(n);
|
|
139
|
+
|
|
140
|
+
let base = 10;
|
|
141
|
+
let unsigned = false;
|
|
142
|
+
let upper = false;
|
|
143
|
+
|
|
144
|
+
switch (code) {
|
|
145
|
+
case 100:
|
|
146
|
+
case 105: // 'd', 'i'
|
|
147
|
+
break;
|
|
148
|
+
case 117: // 'u'
|
|
149
|
+
unsigned = true;
|
|
150
|
+
break;
|
|
151
|
+
case 111: // 'o'
|
|
152
|
+
base = 8;
|
|
153
|
+
unsigned = true;
|
|
154
|
+
break;
|
|
155
|
+
case 120: // 'x'
|
|
156
|
+
base = 16;
|
|
157
|
+
unsigned = true;
|
|
158
|
+
break;
|
|
159
|
+
case 88: // 'X'
|
|
160
|
+
base = 16;
|
|
161
|
+
unsigned = true;
|
|
162
|
+
upper = true;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let neg = false;
|
|
167
|
+
let digits: string;
|
|
168
|
+
|
|
169
|
+
if (unsigned && v < 0) {
|
|
170
|
+
// Reinterpret as 64-bit unsigned
|
|
171
|
+
const bv = BigInt(v) + (1n << 64n);
|
|
172
|
+
digits = bv.toString(base);
|
|
173
|
+
} else if (unsigned) {
|
|
174
|
+
digits = v.toString(base);
|
|
175
|
+
} else {
|
|
176
|
+
neg = v < 0;
|
|
177
|
+
digits = (neg ? -v : v).toString(base);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (upper) digits = digits.toUpperCase();
|
|
181
|
+
|
|
182
|
+
// Precision
|
|
183
|
+
if (spec.hasPrec) {
|
|
184
|
+
if (spec.prec === 0 && v === 0) {
|
|
185
|
+
digits = "";
|
|
186
|
+
} else if (digits.length < spec.prec) {
|
|
187
|
+
digits = "0".repeat(spec.prec - digits.length) + digits;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Alt flag
|
|
192
|
+
let prefix = "";
|
|
193
|
+
if (spec.flags & FLAG_HASH) {
|
|
194
|
+
if (base === 8 && (digits.length === 0 || digits.charCodeAt(0) !== 48)) {
|
|
195
|
+
prefix = "0";
|
|
196
|
+
} else if (base === 16 && v !== 0) {
|
|
197
|
+
prefix = upper ? "0X" : "0x";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let result: string;
|
|
202
|
+
if (neg) {
|
|
203
|
+
result = "-" + prefix + digits;
|
|
204
|
+
} else {
|
|
205
|
+
result = addSign(prefix + digits, spec.flags);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const numPad = !spec.hasPrec;
|
|
209
|
+
return pad(result, spec.width, spec.flags, numPad);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function formatFloat(n: number, spec: FormatSpec): string {
|
|
213
|
+
const code = spec.spec;
|
|
214
|
+
const upper = code === 69 || code === 71 || code === 70;
|
|
215
|
+
// 'E'=69 'G'=71 'F'=70 'e'=101 'g'=103 'f'=102
|
|
216
|
+
const lower = code | 32; // to lowercase
|
|
217
|
+
|
|
218
|
+
// Lua convention
|
|
219
|
+
if (!isFinite(n)) {
|
|
220
|
+
let s: string;
|
|
221
|
+
if (n !== n) {
|
|
222
|
+
s = upper ? "-NAN" : "-nan";
|
|
223
|
+
} else if (n > 0) {
|
|
224
|
+
s = upper ? "INF" : "inf";
|
|
225
|
+
s = addSign(s, spec.flags);
|
|
226
|
+
} else {
|
|
227
|
+
s = upper ? "-INF" : "-inf";
|
|
228
|
+
}
|
|
229
|
+
return pad(s, spec.width, spec.flags, false);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const neg = n < 0 || (n === 0 && 1 / n === -Infinity);
|
|
233
|
+
const abs = neg ? -n : n;
|
|
234
|
+
const prec = spec.hasPrec ? spec.prec : 6;
|
|
235
|
+
|
|
236
|
+
let body: string;
|
|
237
|
+
|
|
238
|
+
if (lower === 102) { // 'f'
|
|
239
|
+
body = abs.toFixed(prec);
|
|
240
|
+
} else if (lower === 101) { // 'e'
|
|
241
|
+
body = abs.toExponential(prec);
|
|
242
|
+
// Ensure exponent has at least 2 digits
|
|
243
|
+
body = ensureExpTwoDigits(body);
|
|
244
|
+
} else { // 'g'
|
|
245
|
+
const gPrec = (prec === 0) ? 1 : prec;
|
|
246
|
+
if (abs === 0) {
|
|
247
|
+
body = "0";
|
|
248
|
+
} else {
|
|
249
|
+
// C rule: use 'e' if exponent < -4 or exponent >= precision
|
|
250
|
+
const exp = Math.floor(Math.log10(abs));
|
|
251
|
+
if (exp < -4 || exp >= gPrec) {
|
|
252
|
+
body = abs.toExponential(gPrec - 1);
|
|
253
|
+
body = ensureExpTwoDigits(body);
|
|
254
|
+
} else {
|
|
255
|
+
// Number of decimals = precision - (exponent + 1)
|
|
256
|
+
const decimals = gPrec - (exp + 1);
|
|
257
|
+
body = abs.toFixed(decimals);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Strip trailing zeros unless '#' flag
|
|
261
|
+
if (!(spec.flags & FLAG_HASH)) {
|
|
262
|
+
body = stripTrailingZerosG(body);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (upper) {
|
|
267
|
+
body = body.toUpperCase();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Alt flag for 'f'/'e': ensure decimal point exists
|
|
271
|
+
if ((spec.flags & FLAG_HASH) && lower !== 103) {
|
|
272
|
+
if (body.indexOf(".") === -1) {
|
|
273
|
+
// Insert dot before 'e' if present, else append
|
|
274
|
+
const eIdx = body.indexOf("e");
|
|
275
|
+
const EIdx = body.indexOf("E");
|
|
276
|
+
const expIdx = eIdx !== -1 ? eIdx : EIdx;
|
|
277
|
+
if (expIdx !== -1) {
|
|
278
|
+
body = body.slice(0, expIdx) + "." + body.slice(expIdx);
|
|
279
|
+
} else {
|
|
280
|
+
body = body + ".";
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Alt flag for 'g': keep trailing zeros but ensure decimal point
|
|
286
|
+
if ((spec.flags & FLAG_HASH) && lower === 103) {
|
|
287
|
+
if (body.indexOf(".") === -1) {
|
|
288
|
+
const expIdx = findExpIndex(body);
|
|
289
|
+
if (expIdx !== -1) {
|
|
290
|
+
body = body.slice(0, expIdx) + "." + body.slice(expIdx);
|
|
291
|
+
} else {
|
|
292
|
+
body = body + ".";
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
let result: string;
|
|
298
|
+
if (neg) {
|
|
299
|
+
result = "-" + body;
|
|
300
|
+
} else {
|
|
301
|
+
result = addSign(body, spec.flags);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return pad(result, spec.width, spec.flags, true);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function findExpIndex(s: string): number {
|
|
308
|
+
for (let i = 0; i < s.length; i++) {
|
|
309
|
+
const c = s.charCodeAt(i);
|
|
310
|
+
if (c === 101 || c === 69) return i; // 'e' or 'E'
|
|
311
|
+
}
|
|
312
|
+
return -1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Ensure exponent part has at least 2 digits
|
|
316
|
+
function ensureExpTwoDigits(s: string): string {
|
|
317
|
+
const idx = findExpIndex(s);
|
|
318
|
+
if (idx === -1) return s;
|
|
319
|
+
// idx+1 is sign, idx+2... are digits
|
|
320
|
+
const signIdx = idx + 1;
|
|
321
|
+
if (signIdx >= s.length) return s;
|
|
322
|
+
const digitStart = signIdx + 1;
|
|
323
|
+
const expLen = s.length - digitStart;
|
|
324
|
+
if (expLen < 2) {
|
|
325
|
+
return s.slice(0, digitStart) + "0" + s.slice(digitStart);
|
|
326
|
+
}
|
|
327
|
+
return s;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Strip trailing zeros from '%g' output
|
|
331
|
+
function stripTrailingZerosG(s: string): string {
|
|
332
|
+
const expIdx = findExpIndex(s);
|
|
333
|
+
const mantissa = expIdx !== -1 ? s.slice(0, expIdx) : s;
|
|
334
|
+
const exp = expIdx !== -1 ? s.slice(expIdx) : "";
|
|
335
|
+
|
|
336
|
+
const dotIdx = mantissa.indexOf(".");
|
|
337
|
+
if (dotIdx === -1) return s; // nothing to strip
|
|
338
|
+
|
|
339
|
+
let end = mantissa.length;
|
|
340
|
+
while (end > dotIdx + 1 && mantissa.charCodeAt(end - 1) === 48) { // '0'
|
|
341
|
+
end--;
|
|
342
|
+
}
|
|
343
|
+
// Remove dot if nothing after it
|
|
344
|
+
if (end === dotIdx + 1) {
|
|
345
|
+
end = dotIdx;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return mantissa.slice(0, end) + exp;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Format a number as hexadecimal floating-point (%a/%A)
|
|
352
|
+
function formatHexFloat(n: number, spec: FormatSpec): string {
|
|
353
|
+
const code = spec.spec;
|
|
354
|
+
const upper = code === 65; // 'A'
|
|
355
|
+
|
|
356
|
+
if (!isFinite(n)) {
|
|
357
|
+
let s: string;
|
|
358
|
+
if (n !== n) {
|
|
359
|
+
s = upper ? "-NAN" : "-nan";
|
|
360
|
+
} else if (n > 0) {
|
|
361
|
+
s = upper ? "INF" : "inf";
|
|
362
|
+
s = addSign(s, spec.flags);
|
|
363
|
+
} else {
|
|
364
|
+
s = upper ? "-INF" : "-inf";
|
|
365
|
+
}
|
|
366
|
+
return pad(s, spec.width, spec.flags, false);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const neg = n < 0 || (n === 0 && 1 / n === -Infinity);
|
|
370
|
+
const abs = neg ? -n : n;
|
|
371
|
+
|
|
372
|
+
let body: string;
|
|
373
|
+
if (abs === 0) {
|
|
374
|
+
const prec = spec.hasPrec ? spec.prec : 0;
|
|
375
|
+
if (prec > 0) {
|
|
376
|
+
body = "0x0." + "0".repeat(prec) + "p+0";
|
|
377
|
+
} else {
|
|
378
|
+
body = "0x0p+0";
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
body = hexFloatBody(abs, spec);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (upper) body = body.toUpperCase();
|
|
385
|
+
|
|
386
|
+
// Alt flag: ensure decimal point
|
|
387
|
+
if (spec.flags & FLAG_HASH) {
|
|
388
|
+
const pIdx = findPIndex(body);
|
|
389
|
+
if (pIdx !== -1) {
|
|
390
|
+
let hasDot = false;
|
|
391
|
+
for (let k = 0; k < pIdx; k++) {
|
|
392
|
+
if (body.charCodeAt(k) === 46) { // '.'
|
|
393
|
+
hasDot = true;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (!hasDot) {
|
|
398
|
+
body = body.slice(0, pIdx) + "." + body.slice(pIdx);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let result: string;
|
|
404
|
+
if (neg) {
|
|
405
|
+
result = "-" + body;
|
|
406
|
+
} else {
|
|
407
|
+
result = addSign(body, spec.flags);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return pad(result, spec.width, spec.flags, true);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Find index of 'p' or 'P' in hex float string
|
|
414
|
+
function findPIndex(s: string): number {
|
|
415
|
+
for (let i = 0; i < s.length; i++) {
|
|
416
|
+
const c = s.charCodeAt(i);
|
|
417
|
+
if (c === 112 || c === 80) return i; // 'p' or 'P'
|
|
418
|
+
}
|
|
419
|
+
return -1;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Number of bits needed to represent a positive bigint
|
|
423
|
+
function bitLength(n: bigint): number {
|
|
424
|
+
let bits = 0;
|
|
425
|
+
let v = n;
|
|
426
|
+
while (v > 0n) {
|
|
427
|
+
bits++;
|
|
428
|
+
v >>= 1n;
|
|
429
|
+
}
|
|
430
|
+
return bits;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Decompose a positive non-zero finite float into `0xH.HHHpN` form
|
|
434
|
+
function hexFloatBody(abs: number, spec: FormatSpec): string {
|
|
435
|
+
const buf = new Float64Array(1);
|
|
436
|
+
const view = new DataView(buf.buffer);
|
|
437
|
+
view.setFloat64(0, abs);
|
|
438
|
+
const bits = view.getBigUint64(0);
|
|
439
|
+
const biasedExp = Number((bits >> 52n) & 0x7FFn);
|
|
440
|
+
const frac = bits & 0xFFFFFFFFFFFFFn;
|
|
441
|
+
|
|
442
|
+
let exponent: number;
|
|
443
|
+
let mantBits: bigint;
|
|
444
|
+
|
|
445
|
+
if (biasedExp === 0) {
|
|
446
|
+
// Subnormal
|
|
447
|
+
if (frac === 0n) return "0x0p+0";
|
|
448
|
+
const shift = 52 - bitLength(frac) + 1;
|
|
449
|
+
mantBits = frac << BigInt(shift);
|
|
450
|
+
exponent = -1022 - shift;
|
|
451
|
+
} else {
|
|
452
|
+
// Normal
|
|
453
|
+
exponent = biasedExp - 1023;
|
|
454
|
+
mantBits = frac | (1n << 52n);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
let firstDigit = Number(mantBits >> 52n);
|
|
458
|
+
const restBits = mantBits & ((1n << 52n) - 1n);
|
|
459
|
+
|
|
460
|
+
// 13 hex digits from 52 bits
|
|
461
|
+
let fracHex = hexDigits52(restBits);
|
|
462
|
+
|
|
463
|
+
if (spec.hasPrec) {
|
|
464
|
+
if (spec.prec < 13) {
|
|
465
|
+
const carry = roundHexInPlace(fracHex, spec.prec);
|
|
466
|
+
if (carry) firstDigit++;
|
|
467
|
+
fracHex = truncHexDigits(fracHex, spec.prec);
|
|
468
|
+
} else {
|
|
469
|
+
fracHex = padHexRight(fracHex, spec.prec);
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
fracHex = stripHexTrailingZeros(fracHex);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const expSign = exponent >= 0 ? "+" : "";
|
|
476
|
+
if (fracHex.length > 0) {
|
|
477
|
+
return "0x" + firstDigit + "." + fracHex + "p" + expSign + exponent;
|
|
478
|
+
}
|
|
479
|
+
return "0x" + firstDigit + "p" + expSign + exponent;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Convert 52-bit value to 13 hex digits, zero-padded
|
|
483
|
+
function hexDigits52(bits: bigint): string {
|
|
484
|
+
const s = bits.toString(16);
|
|
485
|
+
if (s.length >= 13) return s;
|
|
486
|
+
return "0".repeat(13 - s.length) + s;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Parse one hex char to its numeric value
|
|
490
|
+
function hexVal(c: number): number {
|
|
491
|
+
if (c >= 48 && c <= 57) return c - 48; // '0'..'9'
|
|
492
|
+
if (c >= 97 && c <= 102) return c - 87; // 'a'..'f'
|
|
493
|
+
if (c >= 65 && c <= 70) return c - 55; // 'A'..'F'
|
|
494
|
+
return 0;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function roundHexInPlace(digits: string, prec: number): boolean {
|
|
498
|
+
if (prec >= digits.length) return false;
|
|
499
|
+
|
|
500
|
+
const nextVal = hexVal(digits.charCodeAt(prec));
|
|
501
|
+
if (nextVal < 8) return false;
|
|
502
|
+
|
|
503
|
+
if (prec === 0) return true;
|
|
504
|
+
|
|
505
|
+
const arr = new Array<number>(prec);
|
|
506
|
+
for (let i = 0; i < prec; i++) {
|
|
507
|
+
arr[i] = hexVal(digits.charCodeAt(i));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
let carry = 1;
|
|
511
|
+
for (let i = prec - 1; i >= 0 && carry; i--) {
|
|
512
|
+
arr[i] += carry;
|
|
513
|
+
if (arr[i] >= 16) {
|
|
514
|
+
arr[i] = 0;
|
|
515
|
+
carry = 1;
|
|
516
|
+
} else {
|
|
517
|
+
carry = 0;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return carry === 1;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function truncHexDigits(digits: string, prec: number): string {
|
|
525
|
+
if (prec === 0) return "";
|
|
526
|
+
|
|
527
|
+
const nextVal = hexVal(digits.charCodeAt(prec));
|
|
528
|
+
if (nextVal < 8) return digits.slice(0, prec);
|
|
529
|
+
|
|
530
|
+
const arr = new Array<number>(prec);
|
|
531
|
+
for (let i = 0; i < prec; i++) {
|
|
532
|
+
arr[i] = hexVal(digits.charCodeAt(i));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let carry = 1;
|
|
536
|
+
for (let i = prec - 1; i >= 0 && carry; i--) {
|
|
537
|
+
arr[i] += carry;
|
|
538
|
+
if (arr[i] >= 16) {
|
|
539
|
+
arr[i] = 0;
|
|
540
|
+
} else {
|
|
541
|
+
carry = 0;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
let out = "";
|
|
546
|
+
for (let i = 0; i < prec; i++) {
|
|
547
|
+
out += arr[i].toString(16);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function padHexRight(s: string, len: number): string {
|
|
554
|
+
if (s.length >= len) return s;
|
|
555
|
+
return s + "0".repeat(len - s.length);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function stripHexTrailingZeros(s: string): string {
|
|
559
|
+
let end = s.length;
|
|
560
|
+
while (end > 0 && s.charCodeAt(end - 1) === 48) { // '0'
|
|
561
|
+
end--;
|
|
562
|
+
}
|
|
563
|
+
if (end === s.length) return s;
|
|
564
|
+
return s.slice(0, end);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function quoteString(s: string): string {
|
|
568
|
+
let out = '"';
|
|
569
|
+
for (let i = 0; i < s.length; i++) {
|
|
570
|
+
const c = s.charCodeAt(i);
|
|
571
|
+
if (c === 34 || c === 92 || c === 10) {
|
|
572
|
+
// '"', '\\', '\n': backslash + literal char
|
|
573
|
+
out += "\\";
|
|
574
|
+
out += String.fromCharCode(c);
|
|
575
|
+
} else if (c < 32) {
|
|
576
|
+
const next = i + 1 < s.length ? s.charCodeAt(i + 1) : -1;
|
|
577
|
+
const isNextDigit = next >= 48 && next <= 57;
|
|
578
|
+
if (isNextDigit) {
|
|
579
|
+
const ds = c.toString();
|
|
580
|
+
out += "\\";
|
|
581
|
+
if (ds.length < 3) out += "0".repeat(3 - ds.length);
|
|
582
|
+
out += ds;
|
|
583
|
+
} else {
|
|
584
|
+
out += "\\" + c.toString();
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
out += String.fromCharCode(c);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
out += '"';
|
|
591
|
+
return out;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Format a float for %q: hex representation preserving full precision
|
|
595
|
+
function quoteFloat(n: number): string {
|
|
596
|
+
if (n !== n) return "(0/0)";
|
|
597
|
+
if (n === Infinity) return "1e9999";
|
|
598
|
+
if (n === -Infinity) return "-1e9999";
|
|
599
|
+
|
|
600
|
+
const spec: FormatSpec = {
|
|
601
|
+
flags: 0,
|
|
602
|
+
width: 0,
|
|
603
|
+
hasPrec: false,
|
|
604
|
+
prec: 0,
|
|
605
|
+
spec: 97, // 'a'
|
|
606
|
+
};
|
|
607
|
+
return formatHexFloat(n, spec);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function formatQ(v: unknown): string {
|
|
611
|
+
if (v === null || v === undefined) return "nil";
|
|
612
|
+
if (v === true) return "true";
|
|
613
|
+
if (v === false) return "false";
|
|
614
|
+
|
|
615
|
+
if (typeof v === "number") {
|
|
616
|
+
if (v === 0 && 1 / v === -Infinity) return quoteFloat(v);
|
|
617
|
+
if (Number.isInteger(v) && Number.isFinite(v)) {
|
|
618
|
+
return v.toString();
|
|
619
|
+
}
|
|
620
|
+
return quoteFloat(v);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return quoteString(String(v));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function formatChar(n: number): string {
|
|
627
|
+
return String.fromCharCode(n & 0x7f);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const objectIds = new WeakMap<WeakKey, number>();
|
|
631
|
+
const stringIds = new Map<string, number>();
|
|
632
|
+
let nextId = 1;
|
|
633
|
+
|
|
634
|
+
function toPointer(v: unknown): string {
|
|
635
|
+
if (v === null || v === undefined) return "(null)";
|
|
636
|
+
if (typeof v === "boolean" || typeof v === "number") return "(null)";
|
|
637
|
+
|
|
638
|
+
// Primitives (strings, symbols, etc.) cannot be `WeakMap` keys
|
|
639
|
+
if (typeof v !== "object" && typeof v !== "function") {
|
|
640
|
+
const key = String(v);
|
|
641
|
+
let id = stringIds.get(key);
|
|
642
|
+
if (id === undefined) {
|
|
643
|
+
id = nextId++;
|
|
644
|
+
stringIds.set(key, id);
|
|
645
|
+
}
|
|
646
|
+
return "0x" + id.toString(16).padStart(14, "0");
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const obj = v as object;
|
|
650
|
+
let id = objectIds.get(obj);
|
|
651
|
+
if (id === undefined) {
|
|
652
|
+
id = nextId++;
|
|
653
|
+
objectIds.set(obj, id);
|
|
654
|
+
}
|
|
655
|
+
return "0x" + id.toString(16).padStart(14, "0");
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function formatPointer(v: unknown, spec: FormatSpec): string {
|
|
659
|
+
const s = toPointer(v);
|
|
660
|
+
// `%p` only supports width and '-' flag, no precision
|
|
661
|
+
return pad(s, spec.width, spec.flags, false);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export function luaFormat(fmt: string, ...args: any[]): string {
|
|
665
|
+
let out = "";
|
|
666
|
+
let ai = 0; // arg index
|
|
667
|
+
const len = fmt.length;
|
|
668
|
+
let i = 0;
|
|
669
|
+
|
|
670
|
+
while (i < len) {
|
|
671
|
+
const c = fmt.charCodeAt(i);
|
|
672
|
+
if (c !== 37) { // not '%'
|
|
673
|
+
// Fast path: scan for next '%' or end
|
|
674
|
+
let j = i + 1;
|
|
675
|
+
while (j < len && fmt.charCodeAt(j) !== 37) j++;
|
|
676
|
+
out += fmt.slice(i, j);
|
|
677
|
+
i = j;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// '%' found
|
|
682
|
+
i++;
|
|
683
|
+
if (i >= len) {
|
|
684
|
+
throw new Error("invalid format (ends with '%')");
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// '%%' into literal '%'
|
|
688
|
+
if (fmt.charCodeAt(i) === 37) {
|
|
689
|
+
out += "%";
|
|
690
|
+
i++;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const { spec, end } = parseSpec(fmt, i);
|
|
695
|
+
i = end + 1;
|
|
696
|
+
|
|
697
|
+
// Resolve `*` width and precision from args
|
|
698
|
+
let width = spec.width;
|
|
699
|
+
if (width === -1) {
|
|
700
|
+
width = Number(args[ai++]) || 0;
|
|
701
|
+
if (width < 0) {
|
|
702
|
+
spec.flags |= FLAG_MINUS;
|
|
703
|
+
width = -width;
|
|
704
|
+
}
|
|
705
|
+
spec.width = width;
|
|
706
|
+
}
|
|
707
|
+
if (spec.prec === -1) {
|
|
708
|
+
let p = Number(args[ai++]) || 0;
|
|
709
|
+
if (p < 0) {
|
|
710
|
+
spec.hasPrec = false;
|
|
711
|
+
p = 0;
|
|
712
|
+
}
|
|
713
|
+
spec.prec = p;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const code = spec.spec;
|
|
717
|
+
switch (code) {
|
|
718
|
+
case 97:
|
|
719
|
+
case 65: // 'a', 'A'
|
|
720
|
+
out += formatHexFloat(Number(args[ai++]), spec);
|
|
721
|
+
break;
|
|
722
|
+
case 100:
|
|
723
|
+
case 105:
|
|
724
|
+
case 117: // 'd', 'i', 'u'
|
|
725
|
+
case 111:
|
|
726
|
+
case 120:
|
|
727
|
+
case 88: // 'o', 'x', 'X'
|
|
728
|
+
out += formatInt(Number(args[ai++]), spec);
|
|
729
|
+
break;
|
|
730
|
+
case 102:
|
|
731
|
+
case 101:
|
|
732
|
+
case 69: // 'f', 'e', 'E'
|
|
733
|
+
case 103:
|
|
734
|
+
case 71:
|
|
735
|
+
case 70: // 'g', 'G', 'F'
|
|
736
|
+
out += formatFloat(Number(args[ai++]), spec);
|
|
737
|
+
break;
|
|
738
|
+
case 99: // 'c'
|
|
739
|
+
out += pad(
|
|
740
|
+
formatChar(Number(args[ai++])),
|
|
741
|
+
spec.width,
|
|
742
|
+
spec.flags,
|
|
743
|
+
false,
|
|
744
|
+
);
|
|
745
|
+
break;
|
|
746
|
+
case 112: { // 'p'
|
|
747
|
+
out += formatPointer(args[ai++], spec);
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
case 113: { // 'q'
|
|
751
|
+
out += formatQ(args[ai++]);
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
case 115: { // 's'
|
|
755
|
+
let s = String(args[ai++]);
|
|
756
|
+
if (spec.hasPrec && s.length > spec.prec) {
|
|
757
|
+
s = s.slice(0, spec.prec);
|
|
758
|
+
}
|
|
759
|
+
out += pad(s, spec.width, spec.flags, false);
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
default:
|
|
763
|
+
throw new Error(
|
|
764
|
+
`invalid format specifier '${String.fromCharCode(code)}'`,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return out;
|
|
770
|
+
}
|