@silverbulletmd/silverbullet 2.9.0 → 2.10.0
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 +36 -23
- package/client/plugos/hooks/syscall.ts +24 -10
- package/client/plugos/syscalls/asset.ts +61 -23
- package/client/plugos/syscalls/clientStore.ts +32 -6
- package/client/plugos/syscalls/client_code_widget.ts +9 -4
- package/client/plugos/syscalls/code_widget.ts +26 -11
- package/client/plugos/syscalls/config.ts +146 -30
- package/client/plugos/syscalls/datastore.ts +146 -40
- package/client/plugos/syscalls/editor.ts +1432 -711
- package/client/plugos/syscalls/event.ts +46 -13
- package/client/plugos/syscalls/fetch.ts +123 -82
- package/client/plugos/syscalls/index.ts +242 -116
- package/client/plugos/syscalls/jsonschema.ts +107 -8
- package/client/plugos/syscalls/language.ts +38 -12
- package/client/plugos/syscalls/markdown.ts +166 -50
- package/client/plugos/syscalls/mq.ts +93 -20
- package/client/plugos/syscalls/schema_introspection.ts +33 -0
- package/client/plugos/syscalls/service_registry.ts +74 -20
- package/client/plugos/syscalls/shell.ts +51 -20
- package/client/plugos/syscalls/space.ts +169 -113
- package/client/plugos/syscalls/sync.ts +50 -19
- package/client/plugos/syscalls/system.ts +236 -139
- package/client/plugos/system.ts +21 -5
- package/client/space_lua/api_documentation.ts +107 -0
- package/client/space_lua/ast.ts +13 -0
- package/client/space_lua/eval.ts +12 -4
- package/client/space_lua/parse.ts +243 -108
- package/client/space_lua/pretty_print.ts +128 -54
- package/client/space_lua/query_collection.ts +4 -4
- package/client/space_lua/render_widget.ts +156 -0
- package/client/space_lua/runtime.ts +83 -22
- package/client/space_lua/stdlib/crypto.ts +8 -3
- package/client/space_lua/stdlib/encoding.ts +27 -9
- package/client/space_lua/stdlib/js.ts +137 -22
- package/client/space_lua/stdlib/load.ts +24 -15
- package/client/space_lua/stdlib/math.ts +323 -118
- package/client/space_lua/stdlib/net.ts +57 -9
- package/client/space_lua/stdlib/os.ts +111 -44
- package/client/space_lua/stdlib/space_lua.ts +344 -20
- package/client/space_lua/stdlib/string.ts +282 -104
- package/client/space_lua/stdlib/string_pack.ts +58 -19
- package/client/space_lua/stdlib/table.ts +195 -40
- package/client/space_lua/stdlib.ts +335 -93
- package/client/space_lua/syscalls.ts +270 -0
- package/dist/plug-compile.js +1 -1
- package/package.json +3 -3
- package/plug-api/lib/ref.ts +96 -0
- package/plug-api/lib/shortcut.ts +2 -1
- package/plug-api/syscalls/config.ts +5 -3
- package/plug-api/syscalls/editor.ts +16 -0
- package/plug-api/syscalls/index.ts +17 -1
- package/plug-api/syscalls/jsonschema.ts +9 -0
- package/plug-api/syscalls/lua.ts +7 -0
- package/plug-api/syscalls/shell.ts +0 -1
- package/plug-api/syscalls/system.ts +7 -0
- package/plug-api/system_mock.ts +2 -2
- package/plug-api/types/index.ts +52 -1
- package/plug-api/types/manifest.ts +22 -4
- package/plug-api/ui/cx.ts +1 -3
- package/plug-api/ui/index.ts +3 -0
- package/plug-api/ui/panel_styles.ts +4 -3
- package/plug-api/ui/slugify.ts +44 -0
- package/client/plugos/syscalls/lua.ts +0 -81
|
@@ -29,9 +29,28 @@ function resolveOptions(opts?: PrintOptions): ResolvedOptions {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const RESERVED = new Set([
|
|
32
|
-
"and",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
32
|
+
"and",
|
|
33
|
+
"break",
|
|
34
|
+
"do",
|
|
35
|
+
"else",
|
|
36
|
+
"elseif",
|
|
37
|
+
"end",
|
|
38
|
+
"false",
|
|
39
|
+
"for",
|
|
40
|
+
"function",
|
|
41
|
+
"goto",
|
|
42
|
+
"if",
|
|
43
|
+
"in",
|
|
44
|
+
"local",
|
|
45
|
+
"nil",
|
|
46
|
+
"not",
|
|
47
|
+
"or",
|
|
48
|
+
"repeat",
|
|
49
|
+
"return",
|
|
50
|
+
"then",
|
|
51
|
+
"true",
|
|
52
|
+
"until",
|
|
53
|
+
"while",
|
|
35
54
|
]);
|
|
36
55
|
|
|
37
56
|
function isLuaIdentifier(s: string): boolean {
|
|
@@ -39,16 +58,26 @@ function isLuaIdentifier(s: string): boolean {
|
|
|
39
58
|
}
|
|
40
59
|
|
|
41
60
|
const BINARY_PREC: Record<string, number> = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"<": 3,
|
|
61
|
+
or: 1,
|
|
62
|
+
and: 2,
|
|
63
|
+
"<": 3,
|
|
64
|
+
">": 3,
|
|
65
|
+
"<=": 3,
|
|
66
|
+
">=": 3,
|
|
67
|
+
"~=": 3,
|
|
68
|
+
"==": 3,
|
|
45
69
|
"|": 4,
|
|
46
70
|
"~": 5, // binary bitwise xor
|
|
47
71
|
"&": 6,
|
|
48
|
-
"<<": 7,
|
|
72
|
+
"<<": 7,
|
|
73
|
+
">>": 7,
|
|
49
74
|
"..": 8,
|
|
50
|
-
"+": 9,
|
|
51
|
-
"
|
|
75
|
+
"+": 9,
|
|
76
|
+
"-": 9,
|
|
77
|
+
"*": 10,
|
|
78
|
+
"/": 10,
|
|
79
|
+
"//": 10,
|
|
80
|
+
"%": 10,
|
|
52
81
|
"^": 12,
|
|
53
82
|
};
|
|
54
83
|
const UNARY_PREC = 11;
|
|
@@ -62,7 +91,7 @@ function exprPrec(e: LuaExpression): number {
|
|
|
62
91
|
}
|
|
63
92
|
|
|
64
93
|
class Printer {
|
|
65
|
-
constructor(private opts: ResolvedOptions) {
|
|
94
|
+
constructor(private opts: ResolvedOptions) {}
|
|
66
95
|
|
|
67
96
|
private indent(depth: number): string {
|
|
68
97
|
return " ".repeat(depth * this.opts.indentWidth);
|
|
@@ -129,22 +158,29 @@ class Printer {
|
|
|
129
158
|
case "TableConstructor":
|
|
130
159
|
return this.tableConstructor(e.fields, depth);
|
|
131
160
|
case "FunctionDefinition":
|
|
132
|
-
return
|
|
133
|
-
|
|
161
|
+
return `function${this.functionRest(
|
|
162
|
+
e.body.parameters,
|
|
163
|
+
e.body.block,
|
|
164
|
+
depth,
|
|
165
|
+
)}`;
|
|
134
166
|
case "FunctionCall":
|
|
135
167
|
return this.functionCall(e, depth);
|
|
136
168
|
case "Query": {
|
|
137
|
-
const lines = e.clauses.map(
|
|
138
|
-
this.indent(depth + 1) + this.queryClause(c, depth + 1)
|
|
169
|
+
const lines = e.clauses.map(
|
|
170
|
+
(c) => this.indent(depth + 1) + this.queryClause(c, depth + 1),
|
|
139
171
|
);
|
|
140
172
|
return `query[[\n${lines.join("\n")}\n${this.indent(depth)}]]`;
|
|
141
173
|
}
|
|
142
174
|
case "FilteredCall":
|
|
143
|
-
return `${this.expression(e.call, depth)} filter ${this.expression(
|
|
144
|
-
|
|
175
|
+
return `${this.expression(e.call, depth)} filter ${this.expression(
|
|
176
|
+
e.filter,
|
|
177
|
+
depth,
|
|
178
|
+
)}`;
|
|
145
179
|
case "AggregateCall":
|
|
146
|
-
return `${this.expression(e.call, depth)} order by ${this.orderByList(
|
|
147
|
-
|
|
180
|
+
return `${this.expression(e.call, depth)} order by ${this.orderByList(
|
|
181
|
+
e.orderBy,
|
|
182
|
+
depth,
|
|
183
|
+
)}`;
|
|
148
184
|
default:
|
|
149
185
|
throw new Error(
|
|
150
186
|
`pretty_print: unsupported expression type: ${(e as any).type}`,
|
|
@@ -193,8 +229,8 @@ class Printer {
|
|
|
193
229
|
private tableConstructor(fields: LuaTableField[], depth: number): string {
|
|
194
230
|
if (fields.length === 0) return "{}";
|
|
195
231
|
if (fields.length === 1) return `{${this.field(fields[0], depth)}}`;
|
|
196
|
-
const lines = fields.map(
|
|
197
|
-
this.indent(depth + 1) + this.field(f, depth + 1)
|
|
232
|
+
const lines = fields.map(
|
|
233
|
+
(f) => this.indent(depth + 1) + this.field(f, depth + 1),
|
|
198
234
|
);
|
|
199
235
|
const tail = this.opts.trailingComma ? "," : "";
|
|
200
236
|
return `{\n${lines.join(",\n")}${tail}\n${this.indent(depth)}}`;
|
|
@@ -224,7 +260,8 @@ class Printer {
|
|
|
224
260
|
? ` order by ${this.orderByList(e.orderBy, depth)}`
|
|
225
261
|
: "";
|
|
226
262
|
if (
|
|
227
|
-
!e.orderBy &&
|
|
263
|
+
!e.orderBy &&
|
|
264
|
+
args.length === 1 &&
|
|
228
265
|
(args[0].type === "TableConstructor" || args[0].type === "String")
|
|
229
266
|
) {
|
|
230
267
|
return `${base} ${this.expression(args[0], depth)}`;
|
|
@@ -254,8 +291,10 @@ class Printer {
|
|
|
254
291
|
case "Where":
|
|
255
292
|
return `where ${this.expression(c.expression, depth)}`;
|
|
256
293
|
case "Limit":
|
|
257
|
-
return
|
|
258
|
-
|
|
294
|
+
return (
|
|
295
|
+
`limit ${this.expression(c.limit, depth)}` +
|
|
296
|
+
(c.offset ? ` offset ${this.expression(c.offset, depth)}` : "")
|
|
297
|
+
);
|
|
259
298
|
case "Offset":
|
|
260
299
|
return `offset ${this.expression(c.offset, depth)}`;
|
|
261
300
|
case "OrderBy":
|
|
@@ -271,16 +310,32 @@ class Printer {
|
|
|
271
310
|
|
|
272
311
|
block(b: LuaBlock, depth: number): string {
|
|
273
312
|
const stmts = b.statements.filter((s) => s.type !== "Semicolon");
|
|
274
|
-
const
|
|
313
|
+
const items: (LuaStatement | NonNullable<LuaBlock["comments"]>[number])[] =
|
|
314
|
+
[...stmts, ...(b.comments ?? [])].sort(
|
|
315
|
+
(a, z) => (a.ctx.from ?? 0) - (z.ctx.from ?? 0),
|
|
316
|
+
);
|
|
317
|
+
const isDef = (s: (typeof items)[number]) =>
|
|
275
318
|
s.type === "Function" || s.type === "LocalFunction";
|
|
276
319
|
let out = "";
|
|
277
|
-
for (let i = 0; i <
|
|
320
|
+
for (let i = 0; i < items.length; i++) {
|
|
278
321
|
if (i > 0) {
|
|
279
322
|
out += "\n";
|
|
280
323
|
// blank line separating function definitions from neighbours
|
|
281
|
-
|
|
324
|
+
const previous = items[i - 1];
|
|
325
|
+
const isDocumentationPair =
|
|
326
|
+
previous.type === "Comment" &&
|
|
327
|
+
previous.text.startsWith("---") &&
|
|
328
|
+
isDef(items[i]);
|
|
329
|
+
if (!isDocumentationPair && (isDef(previous) || isDef(items[i]))) {
|
|
330
|
+
out += "\n";
|
|
331
|
+
}
|
|
282
332
|
}
|
|
283
|
-
|
|
333
|
+
const item = items[i];
|
|
334
|
+
out +=
|
|
335
|
+
item.type === "Comment"
|
|
336
|
+
? this.indent(depth) +
|
|
337
|
+
item.text.replaceAll("\n", `\n${this.indent(depth)}`)
|
|
338
|
+
: this.statement(item, depth);
|
|
284
339
|
}
|
|
285
340
|
return out;
|
|
286
341
|
}
|
|
@@ -290,11 +345,14 @@ class Printer {
|
|
|
290
345
|
switch (s.type) {
|
|
291
346
|
case "Local": {
|
|
292
347
|
const names = s.names
|
|
293
|
-
.map(
|
|
294
|
-
n
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
348
|
+
.map(
|
|
349
|
+
(n) =>
|
|
350
|
+
n.name +
|
|
351
|
+
(n.attribute
|
|
352
|
+
? ` <${n.attribute}>`
|
|
353
|
+
: n.attributes?.length
|
|
354
|
+
? ` <${n.attributes[0]}>`
|
|
355
|
+
: ""),
|
|
298
356
|
)
|
|
299
357
|
.join(", ");
|
|
300
358
|
const exprs = s.expressions?.length
|
|
@@ -303,12 +361,16 @@ class Printer {
|
|
|
303
361
|
return `${ind}local ${names}${exprs}`;
|
|
304
362
|
}
|
|
305
363
|
case "Assignment":
|
|
306
|
-
return `${ind}${s.variables
|
|
307
|
-
|
|
364
|
+
return `${ind}${s.variables
|
|
365
|
+
.map((v) => this.expression(v, depth))
|
|
366
|
+
.join(
|
|
367
|
+
", ",
|
|
368
|
+
)} = ${s.expressions.map((e) => this.expression(e, depth)).join(", ")}`;
|
|
308
369
|
case "Return":
|
|
309
370
|
return s.expressions.length
|
|
310
|
-
? `${ind}return ${s.expressions
|
|
311
|
-
|
|
371
|
+
? `${ind}return ${s.expressions
|
|
372
|
+
.map((e) => this.expression(e, depth))
|
|
373
|
+
.join(", ")}`
|
|
312
374
|
: `${ind}return`;
|
|
313
375
|
case "FunctionCallStatement":
|
|
314
376
|
return ind + this.expression(s.call, depth);
|
|
@@ -328,7 +390,8 @@ class Printer {
|
|
|
328
390
|
const kw = i === 0 ? "if" : "elseif";
|
|
329
391
|
const head = i === 0 ? `${ind}${kw} ` : `\n${ind}${kw} `;
|
|
330
392
|
const body = this.block(c.block, depth + 1);
|
|
331
|
-
out +=
|
|
393
|
+
out +=
|
|
394
|
+
`${head}${this.expression(c.condition, depth)} then` +
|
|
332
395
|
(body ? `\n${body}` : "");
|
|
333
396
|
});
|
|
334
397
|
if (s.elseBlock) {
|
|
@@ -340,34 +403,48 @@ class Printer {
|
|
|
340
403
|
}
|
|
341
404
|
case "While": {
|
|
342
405
|
const body = this.block(s.block, depth + 1);
|
|
343
|
-
return
|
|
344
|
-
|
|
406
|
+
return (
|
|
407
|
+
`${ind}while ${this.expression(s.condition, depth)} do` +
|
|
408
|
+
(body ? `\n${body}` : "") +
|
|
409
|
+
`\n${ind}end`
|
|
410
|
+
);
|
|
345
411
|
}
|
|
346
412
|
case "Repeat": {
|
|
347
413
|
const body = this.block(s.block, depth + 1);
|
|
348
|
-
return
|
|
349
|
-
|
|
414
|
+
return (
|
|
415
|
+
`${ind}repeat` +
|
|
416
|
+
(body ? `\n${body}` : "") +
|
|
417
|
+
`\n${ind}until ${this.expression(s.condition, depth)}`
|
|
418
|
+
);
|
|
350
419
|
}
|
|
351
420
|
case "For": {
|
|
352
421
|
const step = s.step ? `, ${this.expression(s.step, depth)}` : "";
|
|
353
422
|
const body = this.block(s.block, depth + 1);
|
|
354
|
-
return `${ind}for ${s.name} = ${this.expression(s.start, depth)}, ${this.expression(
|
|
355
|
-
|
|
423
|
+
return `${ind}for ${s.name} = ${this.expression(s.start, depth)}, ${this.expression(
|
|
424
|
+
s.end,
|
|
425
|
+
depth,
|
|
426
|
+
)}${step} do${body ? `\n${body}` : ""}\n${ind}end`;
|
|
356
427
|
}
|
|
357
428
|
case "ForIn": {
|
|
358
429
|
const body = this.block(s.block, depth + 1);
|
|
359
|
-
return `${ind}for ${s.names.join(", ")} in ${s.expressions
|
|
360
|
-
|
|
430
|
+
return `${ind}for ${s.names.join(", ")} in ${s.expressions
|
|
431
|
+
.map((e) => this.expression(e, depth))
|
|
432
|
+
.join(", ")} do${body ? `\n${body}` : ""}\n${ind}end`;
|
|
361
433
|
}
|
|
362
434
|
case "Function": {
|
|
363
|
-
const name =
|
|
435
|
+
const name =
|
|
436
|
+
s.name.propNames.join(".") +
|
|
364
437
|
(s.name.colonName ? `:${s.name.colonName}` : "");
|
|
365
|
-
return
|
|
366
|
-
|
|
438
|
+
return (
|
|
439
|
+
`${ind}function ${name}` +
|
|
440
|
+
this.functionRest(s.body.parameters, s.body.block, depth)
|
|
441
|
+
);
|
|
367
442
|
}
|
|
368
443
|
case "LocalFunction":
|
|
369
|
-
return
|
|
370
|
-
|
|
444
|
+
return (
|
|
445
|
+
`${ind}local function ${s.name}` +
|
|
446
|
+
this.functionRest(s.body.parameters, s.body.block, depth)
|
|
447
|
+
);
|
|
371
448
|
default:
|
|
372
449
|
throw new Error(
|
|
373
450
|
`pretty_print: unsupported statement type: ${(s as any).type}`,
|
|
@@ -383,9 +460,6 @@ export function prettyPrintExpression(
|
|
|
383
460
|
return new Printer(resolveOptions(opts)).expression(expr, 0);
|
|
384
461
|
}
|
|
385
462
|
|
|
386
|
-
export function prettyPrintBlock(
|
|
387
|
-
block: LuaBlock,
|
|
388
|
-
opts?: PrintOptions,
|
|
389
|
-
): string {
|
|
463
|
+
export function prettyPrintBlock(block: LuaBlock, opts?: PrintOptions): string {
|
|
390
464
|
return new Printer(resolveOptions(opts)).block(block, 0);
|
|
391
465
|
}
|
|
@@ -193,7 +193,7 @@ export interface LuaQueryCollection {
|
|
|
193
193
|
* Implements a query collection for a regular JavaScript array
|
|
194
194
|
*/
|
|
195
195
|
export class ArrayQueryCollection<T> implements LuaQueryCollection {
|
|
196
|
-
constructor(private readonly array: T[]) {
|
|
196
|
+
constructor(private readonly array: T[]) {}
|
|
197
197
|
query(
|
|
198
198
|
query: LuaCollectionQuery,
|
|
199
199
|
env: LuaEnv,
|
|
@@ -842,7 +842,7 @@ export async function applyQuery(
|
|
|
842
842
|
|
|
843
843
|
const mkEnv = grouped
|
|
844
844
|
? (ov: string | undefined, item: any, e: LuaEnv, s: LuaStackFrame) =>
|
|
845
|
-
|
|
845
|
+
buildGroupItemEnv(ov, groupByNames, item, e, s)
|
|
846
846
|
: buildItemEnvLocal;
|
|
847
847
|
|
|
848
848
|
let selectResults: any[] | undefined;
|
|
@@ -1002,7 +1002,7 @@ export async function queryLua<T = any>(
|
|
|
1002
1002
|
for await (let { key, value } of kv.query({ prefix })) {
|
|
1003
1003
|
if (enricher) {
|
|
1004
1004
|
value = enricher(key, value);
|
|
1005
|
-
// Enrichers may return undefined to filter a row out
|
|
1005
|
+
// Enrichers may return undefined to filter a row out
|
|
1006
1006
|
if (value === undefined) continue;
|
|
1007
1007
|
}
|
|
1008
1008
|
results.push(value);
|
|
@@ -1056,7 +1056,7 @@ export class DataStoreQueryCollection implements LuaQueryCollection {
|
|
|
1056
1056
|
constructor(
|
|
1057
1057
|
private readonly dataStore: DataStore,
|
|
1058
1058
|
readonly prefix: string[],
|
|
1059
|
-
) {
|
|
1059
|
+
) {}
|
|
1060
1060
|
query(
|
|
1061
1061
|
query: LuaCollectionQuery,
|
|
1062
1062
|
env: LuaEnv,
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { ASTCtx } from "./ast.ts";
|
|
2
|
+
import { evalExpression } from "./eval.ts";
|
|
3
|
+
import { parseExpressionString } from "./parse.ts";
|
|
4
|
+
import {
|
|
5
|
+
type ILuaFunction,
|
|
6
|
+
LuaEnv,
|
|
7
|
+
LuaRuntimeError,
|
|
8
|
+
LuaStackFrame,
|
|
9
|
+
LuaTable,
|
|
10
|
+
type LuaValue,
|
|
11
|
+
luaValueToJS,
|
|
12
|
+
singleResult,
|
|
13
|
+
} from "./runtime.ts";
|
|
14
|
+
import { isTaggedFloat } from "./numeric.ts";
|
|
15
|
+
import {
|
|
16
|
+
encodeRef,
|
|
17
|
+
getNameFromPath,
|
|
18
|
+
} from "@silverbulletmd/silverbullet/lib/ref";
|
|
19
|
+
import { resolveASTReference } from "../space_lua.ts";
|
|
20
|
+
import type { Client } from "../client.ts";
|
|
21
|
+
import {
|
|
22
|
+
classifyResult,
|
|
23
|
+
renderResultToCleanMarkdown,
|
|
24
|
+
} from "./render_lua_markdown.ts";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run a Space Lua computation and convert its result into something the
|
|
28
|
+
* LuaWidget renderer understands (a widget table, markdown string, or number).
|
|
29
|
+
* Shared by the `${...}` directive and Lua code widgets so the conversion +
|
|
30
|
+
* error formatting live in one place.
|
|
31
|
+
*/
|
|
32
|
+
export async function renderLuaWidgetResult(
|
|
33
|
+
client: Client,
|
|
34
|
+
compute: (env: LuaEnv, sf: LuaStackFrame) => Promise<LuaValue> | LuaValue,
|
|
35
|
+
ctx: ASTCtx,
|
|
36
|
+
currentPageMeta?: { name: string } | undefined,
|
|
37
|
+
): Promise<any> {
|
|
38
|
+
try {
|
|
39
|
+
const tl = new LuaEnv();
|
|
40
|
+
tl.setLocal(
|
|
41
|
+
"currentPage",
|
|
42
|
+
currentPageMeta ||
|
|
43
|
+
(client.ui.viewState.current
|
|
44
|
+
? { name: getNameFromPath(client.ui.viewState.current.path) }
|
|
45
|
+
: undefined),
|
|
46
|
+
);
|
|
47
|
+
const sf = LuaStackFrame.createWithGlobalEnv(
|
|
48
|
+
client.clientSystem.spaceLuaEnv.env,
|
|
49
|
+
ctx,
|
|
50
|
+
);
|
|
51
|
+
const env = new LuaEnv(client.clientSystem.spaceLuaEnv.env);
|
|
52
|
+
env.setLocal("_CTX", tl);
|
|
53
|
+
const rawResult = singleResult(await compute(env, sf));
|
|
54
|
+
if (isTaggedFloat(rawResult) || typeof rawResult === "number") {
|
|
55
|
+
return rawResult;
|
|
56
|
+
}
|
|
57
|
+
if (rawResult instanceof LuaTable) {
|
|
58
|
+
if (rawResult.rawGet("_isWidget")) {
|
|
59
|
+
return luaValueToJS(rawResult, sf);
|
|
60
|
+
}
|
|
61
|
+
return rawResult;
|
|
62
|
+
}
|
|
63
|
+
return luaValueToJS(rawResult, sf);
|
|
64
|
+
} catch (e: any) {
|
|
65
|
+
if (e instanceof LuaRuntimeError && e.sf?.astCtx) {
|
|
66
|
+
const source = resolveASTReference(e.sf.astCtx);
|
|
67
|
+
if (source) {
|
|
68
|
+
return `**Lua error:** ${e.message} (Origin: [[${encodeRef(source)}]])`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return `**Lua error:** ${e.message}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Evaluate a `${...}`-style expression string and render it as a widget. */
|
|
76
|
+
export async function renderLuaExpression(
|
|
77
|
+
client: Client,
|
|
78
|
+
expressionText: string,
|
|
79
|
+
currentPageMeta?: { name: string } | undefined,
|
|
80
|
+
): Promise<any> {
|
|
81
|
+
if (expressionText.trim().length === 0) {
|
|
82
|
+
return "**Error:** Empty Lua expression";
|
|
83
|
+
}
|
|
84
|
+
const expr = parseExpressionString(expressionText);
|
|
85
|
+
return renderLuaWidgetResult(
|
|
86
|
+
client,
|
|
87
|
+
(env, sf) => evalExpression(expr, env, sf),
|
|
88
|
+
expr.ctx,
|
|
89
|
+
currentPageMeta,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Call a stored Lua render function (e.g. a code widget) and render it. */
|
|
94
|
+
export async function renderLuaCallback(
|
|
95
|
+
client: Client,
|
|
96
|
+
fn: ILuaFunction,
|
|
97
|
+
args: LuaValue[],
|
|
98
|
+
currentPageMeta?: { name: string } | undefined,
|
|
99
|
+
): Promise<any> {
|
|
100
|
+
return renderLuaWidgetResult(
|
|
101
|
+
client,
|
|
102
|
+
(_env, sf) => fn.call(sf, ...args),
|
|
103
|
+
{} as ASTCtx,
|
|
104
|
+
currentPageMeta,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type PortableMarkdownResult =
|
|
109
|
+
| { ok: true; markdown: string }
|
|
110
|
+
| { ok: false; reason: string };
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Evaluate a `${...}`-style expression and produce portable GFM markdown — the
|
|
114
|
+
* same clean rendering the Copy button uses — or report why it can't be baked.
|
|
115
|
+
* - nil/empty → ok with empty markdown
|
|
116
|
+
* - Lua error → not ok (the eval error string)
|
|
117
|
+
* - widget object: portable only if it exposes a `markdown` rendering;
|
|
118
|
+
* html-only widgets are not bakeable
|
|
119
|
+
* - scalars / tables / arrays / strings → clean GFM
|
|
120
|
+
*/
|
|
121
|
+
export async function expressionToPortableMarkdown(
|
|
122
|
+
client: Client,
|
|
123
|
+
expressionText: string,
|
|
124
|
+
currentPageMeta?: { name: string } | undefined,
|
|
125
|
+
): Promise<PortableMarkdownResult> {
|
|
126
|
+
const rawResult = await renderLuaExpression(
|
|
127
|
+
client,
|
|
128
|
+
expressionText,
|
|
129
|
+
currentPageMeta,
|
|
130
|
+
);
|
|
131
|
+
if (rawResult === null || rawResult === undefined) {
|
|
132
|
+
return { ok: true, markdown: "" };
|
|
133
|
+
}
|
|
134
|
+
// renderLuaExpression returns eval failures as markdown error strings.
|
|
135
|
+
if (
|
|
136
|
+
typeof rawResult === "string" &&
|
|
137
|
+
(rawResult.startsWith("**Lua error:**") ||
|
|
138
|
+
rawResult.startsWith("**Error:**"))
|
|
139
|
+
) {
|
|
140
|
+
return { ok: false, reason: rawResult };
|
|
141
|
+
}
|
|
142
|
+
// Widget objects: portable only when they expose a `markdown` rendering.
|
|
143
|
+
if (typeof rawResult === "object" && (rawResult as any)._isWidget) {
|
|
144
|
+
const md = (rawResult as any).markdown;
|
|
145
|
+
if (typeof md === "string") {
|
|
146
|
+
return { ok: true, markdown: md.trim() };
|
|
147
|
+
}
|
|
148
|
+
return { ok: false, reason: "html-only widget (no markdown rendering)" };
|
|
149
|
+
}
|
|
150
|
+
// Scalars, tables, arrays, plain strings → clean GFM markdown.
|
|
151
|
+
const markdown = await renderResultToCleanMarkdown(
|
|
152
|
+
rawResult,
|
|
153
|
+
classifyResult(rawResult),
|
|
154
|
+
);
|
|
155
|
+
return { ok: true, markdown };
|
|
156
|
+
}
|
|
@@ -5,6 +5,10 @@ import { isPromise, rpAll } from "./rp.ts";
|
|
|
5
5
|
import { isNegativeZero, isTaggedFloat } from "./numeric.ts";
|
|
6
6
|
import { isSqlNull } from "./sliq_null.ts";
|
|
7
7
|
import { luaFormat } from "./stdlib/format.ts";
|
|
8
|
+
import type {
|
|
9
|
+
LuaFunctionDocumentation,
|
|
10
|
+
LuaFunctionInfo,
|
|
11
|
+
} from "../../plug-api/types/index.ts";
|
|
8
12
|
|
|
9
13
|
export type LuaType =
|
|
10
14
|
| "nil"
|
|
@@ -20,10 +24,19 @@ export type LuaType =
|
|
|
20
24
|
export type LuaValue = any;
|
|
21
25
|
export type JSValue = any;
|
|
22
26
|
|
|
27
|
+
type LuaFunctionDefinitionMetadata = LuaFunctionDocumentation &
|
|
28
|
+
Partial<Pick<LuaFunctionInfo, "kind" | "name" | "source">>;
|
|
29
|
+
|
|
30
|
+
export type LuaFunctionDefinition<Callback> = {
|
|
31
|
+
callback: Callback;
|
|
32
|
+
} & LuaFunctionDefinitionMetadata;
|
|
33
|
+
|
|
23
34
|
export interface ILuaFunction {
|
|
24
35
|
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue;
|
|
25
36
|
|
|
26
37
|
asString(): string;
|
|
38
|
+
|
|
39
|
+
info?: LuaFunctionInfo;
|
|
27
40
|
}
|
|
28
41
|
|
|
29
42
|
export interface ILuaSettable {
|
|
@@ -453,12 +466,25 @@ export function singleResult(value: any): any {
|
|
|
453
466
|
export class LuaFunction implements ILuaFunction {
|
|
454
467
|
private capturedEnv: LuaEnv;
|
|
455
468
|
funcHasGotos?: boolean;
|
|
469
|
+
info: LuaFunctionInfo;
|
|
456
470
|
|
|
457
471
|
constructor(
|
|
458
472
|
readonly body: LuaFunctionBody,
|
|
459
473
|
closure: LuaEnv,
|
|
474
|
+
info: Partial<LuaFunctionInfo> = {},
|
|
460
475
|
) {
|
|
461
476
|
this.capturedEnv = closure;
|
|
477
|
+
const documentation = body.documentation ?? {};
|
|
478
|
+
this.info = {
|
|
479
|
+
kind: "lua",
|
|
480
|
+
source: { ...body.ctx },
|
|
481
|
+
...documentation,
|
|
482
|
+
...info,
|
|
483
|
+
parameters:
|
|
484
|
+
info.parameters ??
|
|
485
|
+
documentation.parameters ??
|
|
486
|
+
body.parameters.map((name) => ({ name })),
|
|
487
|
+
};
|
|
462
488
|
}
|
|
463
489
|
|
|
464
490
|
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
|
@@ -482,10 +508,7 @@ export class LuaFunction implements ILuaFunction {
|
|
|
482
508
|
for (let i = 0; i < varargStart; i++) {
|
|
483
509
|
env.setLocal(params[i], resolvedArgs[i] ?? null);
|
|
484
510
|
}
|
|
485
|
-
env.setLocal(
|
|
486
|
-
"...",
|
|
487
|
-
new LuaMultiRes(resolvedArgs.slice(varargStart)),
|
|
488
|
-
);
|
|
511
|
+
env.setLocal("...", new LuaMultiRes(resolvedArgs.slice(varargStart)));
|
|
489
512
|
} else {
|
|
490
513
|
// Non-variadic: bind all named params directly
|
|
491
514
|
for (let i = 0; i < params.length; i++) {
|
|
@@ -565,7 +588,26 @@ function mapFunctionReturnValue(values: any[]): any {
|
|
|
565
588
|
}
|
|
566
589
|
|
|
567
590
|
export class LuaNativeJSFunction implements ILuaFunction {
|
|
568
|
-
|
|
591
|
+
readonly fn: (...args: JSValue[]) => JSValue;
|
|
592
|
+
public info: LuaFunctionInfo;
|
|
593
|
+
|
|
594
|
+
constructor(
|
|
595
|
+
definition:
|
|
596
|
+
| ((...args: JSValue[]) => JSValue)
|
|
597
|
+
| LuaFunctionDefinition<(...args: JSValue[]) => JSValue>,
|
|
598
|
+
) {
|
|
599
|
+
if (typeof definition === "function") {
|
|
600
|
+
this.fn = definition;
|
|
601
|
+
this.info = { kind: "builtin" };
|
|
602
|
+
} else {
|
|
603
|
+
const { callback, ...info } = definition;
|
|
604
|
+
this.fn = callback;
|
|
605
|
+
this.info = {
|
|
606
|
+
kind: "builtin",
|
|
607
|
+
...info,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
569
611
|
|
|
570
612
|
// Performs automatic conversion between Lua and JS values for arguments, but not for return values
|
|
571
613
|
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
|
@@ -595,9 +637,28 @@ export class LuaNativeJSFunction implements ILuaFunction {
|
|
|
595
637
|
}
|
|
596
638
|
|
|
597
639
|
export class LuaBuiltinFunction implements ILuaFunction {
|
|
640
|
+
readonly fn: (sf: LuaStackFrame, ...args: LuaValue[]) => LuaValue;
|
|
641
|
+
public info: LuaFunctionInfo;
|
|
642
|
+
|
|
598
643
|
constructor(
|
|
599
|
-
|
|
600
|
-
|
|
644
|
+
definition:
|
|
645
|
+
| ((sf: LuaStackFrame, ...args: LuaValue[]) => LuaValue)
|
|
646
|
+
| LuaFunctionDefinition<
|
|
647
|
+
(sf: LuaStackFrame, ...args: LuaValue[]) => LuaValue
|
|
648
|
+
>,
|
|
649
|
+
) {
|
|
650
|
+
if (typeof definition === "function") {
|
|
651
|
+
this.fn = definition;
|
|
652
|
+
this.info = { kind: "builtin" };
|
|
653
|
+
} else {
|
|
654
|
+
const { callback, ...info } = definition;
|
|
655
|
+
this.fn = callback;
|
|
656
|
+
this.info = {
|
|
657
|
+
kind: "builtin",
|
|
658
|
+
...info,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
}
|
|
601
662
|
|
|
602
663
|
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
|
603
664
|
// _CTX is already available via the stack frame
|
|
@@ -1313,11 +1374,9 @@ export function luaSet(
|
|
|
1313
1374
|
const k = toNumKey(normKey);
|
|
1314
1375
|
const jsVal = luaValueToJS(value, sf);
|
|
1315
1376
|
if (isPromise(jsVal)) {
|
|
1316
|
-
return (jsVal as Promise<any>).then(
|
|
1317
|
-
(
|
|
1318
|
-
|
|
1319
|
-
},
|
|
1320
|
-
);
|
|
1377
|
+
return (jsVal as Promise<any>).then((v) => {
|
|
1378
|
+
(obj as Record<string | number, any>)[k] = v;
|
|
1379
|
+
});
|
|
1321
1380
|
}
|
|
1322
1381
|
(obj as Record<string | number, any>)[k] = jsVal;
|
|
1323
1382
|
}
|
|
@@ -1492,16 +1551,18 @@ export function luaCall(
|
|
|
1492
1551
|
|
|
1493
1552
|
export function luaEquals(a: any, b: any): boolean {
|
|
1494
1553
|
// Normalize nil variants (null, undefined, SQL NULL) to null
|
|
1495
|
-
const an =
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1554
|
+
const an =
|
|
1555
|
+
a === null || a === undefined || isSqlNull(a)
|
|
1556
|
+
? null
|
|
1557
|
+
: isTaggedFloat(a)
|
|
1558
|
+
? a.value
|
|
1559
|
+
: a;
|
|
1560
|
+
const bn =
|
|
1561
|
+
b === null || b === undefined || isSqlNull(b)
|
|
1562
|
+
? null
|
|
1563
|
+
: isTaggedFloat(b)
|
|
1564
|
+
? b.value
|
|
1565
|
+
: b;
|
|
1505
1566
|
return an === bn;
|
|
1506
1567
|
}
|
|
1507
1568
|
|
|
@@ -2,9 +2,14 @@ import { LuaBuiltinFunction, LuaTable } from "../runtime.ts";
|
|
|
2
2
|
import { hashSHA256 } from "../../../plug-api/lib/crypto.ts";
|
|
3
3
|
|
|
4
4
|
export const cryptoApi = new LuaTable({
|
|
5
|
-
sha256: new LuaBuiltinFunction(
|
|
6
|
-
(_sf, s: string | Uint8Array): Promise<string> => {
|
|
5
|
+
sha256: new LuaBuiltinFunction({
|
|
6
|
+
callback: (_sf, s: string | Uint8Array): Promise<string> => {
|
|
7
7
|
return hashSHA256(s);
|
|
8
8
|
},
|
|
9
|
-
|
|
9
|
+
description: "Computes the SHA-256 digest of a string or byte buffer.",
|
|
10
|
+
parameters: [
|
|
11
|
+
{ name: "data", type: "string|bytes", description: "Data to hash." },
|
|
12
|
+
],
|
|
13
|
+
returns: [{ type: "string", description: "Hexadecimal SHA-256 digest." }],
|
|
14
|
+
}),
|
|
10
15
|
});
|