@silverbulletmd/silverbullet 2.10.0 → 2.11.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 +4 -4
- package/client/asset_bundle/bundle.ts +0 -1
- package/client/config.ts +343 -0
- package/client/markdown_parser/constants.ts +3 -1
- package/client/plugos/hooks/command.ts +0 -5
- package/client/plugos/hooks/event.ts +0 -6
- package/client/plugos/hooks/mq.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +1 -7
- package/client/plugos/hooks/syscall.ts +0 -4
- package/client/plugos/manifest_cache.ts +0 -32
- package/client/plugos/plug.ts +0 -3
- package/client/plugos/plug_compile.ts +3 -13
- package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
- package/client/plugos/syscalls/editor.ts +128 -53
- package/client/plugos/syscalls/event.ts +0 -2
- package/client/plugos/syscalls/fetch.ts +0 -4
- package/client/plugos/syscalls/icon.ts +46 -0
- package/client/plugos/syscalls/index.ts +33 -13
- package/client/plugos/syscalls/jsonschema.ts +0 -1
- package/client/plugos/syscalls/mq.ts +0 -1
- package/client/plugos/syscalls/navigator.ts +143 -0
- package/client/plugos/syscalls/search.ts +44 -0
- package/client/plugos/syscalls/shell.ts +0 -1
- package/client/plugos/syscalls/space.ts +250 -7
- package/client/plugos/syscalls/sync.ts +56 -4
- package/client/plugos/syscalls/system.ts +38 -0
- package/client/plugos/system.ts +7 -5
- package/client/plugos/worker_runtime.ts +0 -2
- package/client/space_lua/aggregates.ts +0 -16
- package/client/space_lua/ast.ts +0 -5
- package/client/space_lua/ast_narrow.ts +0 -5
- package/client/space_lua/budget.ts +113 -0
- package/client/space_lua/budget_ui.ts +49 -0
- package/client/space_lua/eval.ts +113 -88
- package/client/space_lua/labels.ts +0 -4
- package/client/space_lua/numeric.ts +0 -5
- package/client/space_lua/parse.ts +0 -17
- package/client/space_lua/quarantine.ts +127 -0
- package/client/space_lua/query_collection.ts +3 -47
- package/client/space_lua/query_env.ts +0 -2
- package/client/space_lua/render_lua_markdown.ts +2 -7
- package/client/space_lua/render_widget.ts +18 -1
- package/client/space_lua/runtime.ts +50 -70
- package/client/space_lua/stdlib/format.ts +0 -12
- package/client/space_lua/stdlib/js.ts +0 -2
- package/client/space_lua/stdlib/load.ts +0 -1
- package/client/space_lua/stdlib/math.ts +0 -7
- package/client/space_lua/stdlib/net.ts +0 -4
- package/client/space_lua/stdlib/os.ts +0 -10
- package/client/space_lua/stdlib/pattern.ts +10 -15
- package/client/space_lua/stdlib/space_lua.ts +1 -2
- package/client/space_lua/stdlib/string_pack.ts +0 -3
- package/client/space_lua/stdlib/table.ts +0 -5
- package/client/space_lua/stdlib.ts +18 -31
- package/client/space_lua/tonumber.ts +0 -9
- package/dist/plug-compile.js +2 -3
- package/package.json +13 -3
- package/plug-api/lib/async.ts +1 -6
- package/plug-api/lib/collation.ts +22 -0
- package/plug-api/lib/crypto.ts +16 -15
- package/plug-api/lib/dates.ts +33 -0
- package/plug-api/lib/fuzzy.ts +282 -0
- package/plug-api/lib/json.ts +0 -6
- package/plug-api/lib/limited_map.ts +0 -2
- package/plug-api/lib/link_write.ts +27 -0
- package/plug-api/lib/ref.ts +28 -5
- package/plug-api/lib/resolve.ts +0 -1
- package/plug-api/lib/resolve_path.ts +290 -0
- package/plug-api/lib/shortcut.ts +15 -0
- package/plug-api/lib/tags.ts +0 -3
- package/plug-api/lib/transclusion.ts +28 -1
- package/plug-api/lib/tree.ts +0 -5
- package/plug-api/lib/yaml.ts +3 -42
- package/plug-api/syscall.ts +1 -5
- package/plug-api/syscalls/config.ts +2 -2
- package/plug-api/syscalls/editor.ts +59 -213
- package/plug-api/syscalls/icon.ts +18 -0
- package/plug-api/syscalls/index.ts +22 -4
- package/plug-api/syscalls/search.ts +11 -0
- package/plug-api/syscalls/space.ts +122 -5
- package/plug-api/syscalls/sync.ts +23 -2
- package/plug-api/syscalls/system.ts +46 -0
- package/plug-api/syscalls.ts +2 -0
- package/plug-api/system_mock.ts +13 -0
- package/plug-api/types/client.ts +4 -3
- package/plug-api/types/datastore.ts +11 -2
- package/plug-api/types/index.ts +10 -0
- package/plug-api/types/profile.ts +13 -0
- package/plug-api/types/revisions.ts +67 -0
- package/plug-api/ui/description.ts +64 -0
- package/plug-api/ui/hover.ts +69 -0
- package/plug-api/ui/index.ts +58 -2
- package/plug-api/ui/scroll.ts +30 -0
- package/plug-api/ui/tree_model.ts +263 -0
- package/plug-api/ui/tree_types.ts +66 -0
- package/plug-api/ui/use_fit_collapse.ts +67 -0
- package/plugs/builtin_plugs.ts +0 -1
- package/plugs/index/types.ts +9 -0
- package/plug-api/lib/memory_cache.ts +0 -21
- /package/plug-api/{ui → lib}/panel_styles.ts +0 -0
package/client/space_lua/eval.ts
CHANGED
|
@@ -7,7 +7,9 @@ import type {
|
|
|
7
7
|
LuaTableField,
|
|
8
8
|
NumericType,
|
|
9
9
|
} from "./ast.ts";
|
|
10
|
+
import type { Config } from "../config.ts";
|
|
10
11
|
import { LuaAttribute } from "./ast.ts";
|
|
12
|
+
import { budgetTick, LuaBudgetStopped } from "./budget.ts";
|
|
11
13
|
import { evalPromiseValues } from "./util.ts";
|
|
12
14
|
import {
|
|
13
15
|
getMetatable,
|
|
@@ -91,6 +93,11 @@ import { getBlockGotoMeta } from "./labels.ts";
|
|
|
91
93
|
|
|
92
94
|
const astNumberKindCache = new WeakMap<LuaExpression, NumericType>();
|
|
93
95
|
|
|
96
|
+
function getClientConfig(): Config | undefined {
|
|
97
|
+
return (globalThis as typeof globalThis & { client?: { config: Config } })
|
|
98
|
+
.client?.config;
|
|
99
|
+
}
|
|
100
|
+
|
|
94
101
|
function astNumberKind(e: LuaExpression | undefined): NumericType | undefined {
|
|
95
102
|
if (!e) return undefined;
|
|
96
103
|
|
|
@@ -258,7 +265,6 @@ export function luaOp(
|
|
|
258
265
|
): any {
|
|
259
266
|
switch (op) {
|
|
260
267
|
case "+": {
|
|
261
|
-
// Ultra-fast path: both plain numbers with no float type annotation (int + int)
|
|
262
268
|
if (
|
|
263
269
|
typeof left === "number" &&
|
|
264
270
|
typeof right === "number" &&
|
|
@@ -308,11 +314,9 @@ export function luaOp(
|
|
|
308
314
|
);
|
|
309
315
|
}
|
|
310
316
|
case "..": {
|
|
311
|
-
// Fast path: string .. string (most common in SilverBullet — key building, templates)
|
|
312
317
|
if (typeof left === "string" && typeof right === "string") {
|
|
313
318
|
return left + right;
|
|
314
319
|
}
|
|
315
|
-
// Fast path: string .. number or number .. string
|
|
316
320
|
if (typeof left === "string" && typeof right === "number") {
|
|
317
321
|
return left + luaFormatNumber(right);
|
|
318
322
|
}
|
|
@@ -352,7 +356,6 @@ export function luaOp(
|
|
|
352
356
|
}
|
|
353
357
|
}
|
|
354
358
|
case "==": {
|
|
355
|
-
// Fast path for same-type primitives
|
|
356
359
|
if (typeof left === typeof right && typeof left !== "object") {
|
|
357
360
|
return left === right;
|
|
358
361
|
}
|
|
@@ -370,11 +373,9 @@ export function luaOp(
|
|
|
370
373
|
return !luaEqWithMetamethod(left, right, ctx, sf);
|
|
371
374
|
}
|
|
372
375
|
case "<": {
|
|
373
|
-
// Fast path: both plain numbers
|
|
374
376
|
if (typeof left === "number" && typeof right === "number") {
|
|
375
377
|
return left < right;
|
|
376
378
|
}
|
|
377
|
-
// Fast path: both strings
|
|
378
379
|
if (typeof left === "string" && typeof right === "string") {
|
|
379
380
|
return left < right;
|
|
380
381
|
}
|
|
@@ -404,7 +405,6 @@ export function luaOp(
|
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
|
|
407
|
-
// Remaining operators: //, %, bitwise
|
|
408
408
|
const handler = operatorsMetaMethods[op];
|
|
409
409
|
if (!handler) {
|
|
410
410
|
throw new LuaRuntimeError(`Unknown operator ${op}`, sf.withCtx(ctx));
|
|
@@ -820,7 +820,6 @@ async function evalCrossJoinSources(
|
|
|
820
820
|
sf: LuaStackFrame,
|
|
821
821
|
ctx: ASTCtx,
|
|
822
822
|
): Promise<LuaTable[]> {
|
|
823
|
-
// Evaluate each source and normalize to arrays
|
|
824
823
|
const arrays: { name: string; items: any[] }[] = [];
|
|
825
824
|
for (const src of sources) {
|
|
826
825
|
const val = await evalExpression(src.expression, env, sf);
|
|
@@ -834,7 +833,6 @@ async function evalCrossJoinSources(
|
|
|
834
833
|
arrays.push({ name: src.name, items });
|
|
835
834
|
}
|
|
836
835
|
|
|
837
|
-
// Cartesian product
|
|
838
836
|
let product: Record<string, any>[] = [{}];
|
|
839
837
|
for (const { name, items } of arrays) {
|
|
840
838
|
const newProduct: Record<string, any>[] = [];
|
|
@@ -846,7 +844,6 @@ async function evalCrossJoinSources(
|
|
|
846
844
|
product = newProduct;
|
|
847
845
|
}
|
|
848
846
|
|
|
849
|
-
// Convert each combination to a `LuaTable` row
|
|
850
847
|
return product.map((combo) => {
|
|
851
848
|
const row = new LuaTable();
|
|
852
849
|
for (const key in combo) {
|
|
@@ -894,7 +891,6 @@ export function evalExpression(
|
|
|
894
891
|
case "Unary": {
|
|
895
892
|
const u = asUnary(e);
|
|
896
893
|
|
|
897
|
-
// Fast path: negation of numeric literal
|
|
898
894
|
if (u.operator === "-" && u.argument.type === "Number") {
|
|
899
895
|
const num = u.argument;
|
|
900
896
|
if (num.value === 0) {
|
|
@@ -914,7 +910,6 @@ export function evalExpression(
|
|
|
914
910
|
const arg = singleResult(typed.value);
|
|
915
911
|
|
|
916
912
|
return unaryWithMeta(arg, "__unm", u.ctx, sf, () => {
|
|
917
|
-
// Numeric-string coercion for unary minus
|
|
918
913
|
if (typeof arg === "string") {
|
|
919
914
|
const n = coerceToNumber(arg);
|
|
920
915
|
if (n === null) {
|
|
@@ -1109,7 +1104,6 @@ export function evalExpression(
|
|
|
1109
1104
|
);
|
|
1110
1105
|
|
|
1111
1106
|
if (fromSource.kind === "cross") {
|
|
1112
|
-
// Materialize Cartesian product, then query
|
|
1113
1107
|
return (async () => {
|
|
1114
1108
|
const rows = await evalCrossJoinSources(
|
|
1115
1109
|
fromSource.sources,
|
|
@@ -1119,13 +1113,11 @@ export function evalExpression(
|
|
|
1119
1113
|
);
|
|
1120
1114
|
const collection: any = toCollection(rows);
|
|
1121
1115
|
|
|
1122
|
-
// Build up query object
|
|
1123
1116
|
const query: LuaCollectionQuery = {
|
|
1124
1117
|
objectVariable: undefined,
|
|
1125
1118
|
distinct: true,
|
|
1126
1119
|
};
|
|
1127
1120
|
|
|
1128
|
-
// Map clauses to query parameters
|
|
1129
1121
|
for (const clause of q.clauses) {
|
|
1130
1122
|
switch (clause.type) {
|
|
1131
1123
|
case "Where": {
|
|
@@ -1179,12 +1171,11 @@ export function evalExpression(
|
|
|
1179
1171
|
}
|
|
1180
1172
|
|
|
1181
1173
|
return (collection as any)
|
|
1182
|
-
.query(query, env, sf,
|
|
1174
|
+
.query(query, env, sf, getClientConfig())
|
|
1183
1175
|
.then(jsToLuaValue);
|
|
1184
1176
|
})();
|
|
1185
1177
|
}
|
|
1186
1178
|
|
|
1187
|
-
// Single-source
|
|
1188
1179
|
const { objectVariable, expression: objectExpression } = fromSource;
|
|
1189
1180
|
return Promise.resolve(evalExpression(objectExpression, env, sf)).then(
|
|
1190
1181
|
async (collection: LuaValue) => {
|
|
@@ -1200,33 +1191,27 @@ export function evalExpression(
|
|
|
1200
1191
|
"query" in collection &&
|
|
1201
1192
|
typeof (collection as any).query === "function"
|
|
1202
1193
|
) {
|
|
1203
|
-
// Already queryable, use as-is
|
|
1204
1194
|
} else if (collection instanceof LuaTable && collection.empty()) {
|
|
1205
|
-
// Empty table → empty array
|
|
1206
1195
|
collection = toCollection([]);
|
|
1207
1196
|
} else if (collection instanceof LuaTable) {
|
|
1208
1197
|
if (collection.length > 0) {
|
|
1209
|
-
// Array-like table: extract array items, keep as LuaTables
|
|
1210
1198
|
const arr: any[] = [];
|
|
1211
1199
|
for (let i = 1; i <= collection.length; i++) {
|
|
1212
1200
|
arr.push(collection.rawGet(i));
|
|
1213
1201
|
}
|
|
1214
1202
|
collection = toCollection(arr);
|
|
1215
1203
|
} else {
|
|
1216
|
-
// Record-like table (no array part): treat as singleton
|
|
1217
1204
|
collection = toCollection([collection]);
|
|
1218
1205
|
}
|
|
1219
1206
|
} else {
|
|
1220
1207
|
collection = toCollection(luaValueToJS(collection, sf));
|
|
1221
1208
|
}
|
|
1222
1209
|
|
|
1223
|
-
// Build up query object
|
|
1224
1210
|
const query: LuaCollectionQuery = {
|
|
1225
1211
|
objectVariable,
|
|
1226
1212
|
distinct: true,
|
|
1227
1213
|
};
|
|
1228
1214
|
|
|
1229
|
-
// Map clauses to query parameters
|
|
1230
1215
|
for (const clause of q.clauses) {
|
|
1231
1216
|
switch (clause.type) {
|
|
1232
1217
|
case "Where": {
|
|
@@ -1279,9 +1264,8 @@ export function evalExpression(
|
|
|
1279
1264
|
}
|
|
1280
1265
|
}
|
|
1281
1266
|
|
|
1282
|
-
// Always use the possibly-wrapped collection
|
|
1283
1267
|
return (collection as any)
|
|
1284
|
-
.query(query, env, sf,
|
|
1268
|
+
.query(query, env, sf, getClientConfig())
|
|
1285
1269
|
.then(jsToLuaValue);
|
|
1286
1270
|
},
|
|
1287
1271
|
);
|
|
@@ -1293,12 +1277,12 @@ export function evalExpression(
|
|
|
1293
1277
|
);
|
|
1294
1278
|
}
|
|
1295
1279
|
} catch (err: any) {
|
|
1296
|
-
//
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
} else {
|
|
1280
|
+
// instanceof, not constructor.name: the production build minifies class
|
|
1281
|
+
// names, so a name-based test silently fails in the shipped client.
|
|
1282
|
+
if (err instanceof LuaRuntimeError || err instanceof LuaBudgetStopped) {
|
|
1300
1283
|
throw err;
|
|
1301
1284
|
}
|
|
1285
|
+
throw new LuaRuntimeError(err.message, sf.withCtx(e.ctx), err);
|
|
1302
1286
|
}
|
|
1303
1287
|
}
|
|
1304
1288
|
|
|
@@ -1322,7 +1306,6 @@ function evalPrefixExpression(
|
|
|
1322
1306
|
return rpThen(evalExpression(p.expression, env, sf), singleResult);
|
|
1323
1307
|
}
|
|
1324
1308
|
|
|
1325
|
-
// <<expr>>[<<expr>>]
|
|
1326
1309
|
case "TableAccess": {
|
|
1327
1310
|
const ta = asTableAccess(e);
|
|
1328
1311
|
// Sync-first: evaluate object and key without allocating Promise when both are sync.
|
|
@@ -1342,7 +1325,6 @@ function evalPrefixExpression(
|
|
|
1342
1325
|
);
|
|
1343
1326
|
}
|
|
1344
1327
|
|
|
1345
|
-
// <expr>.property
|
|
1346
1328
|
case "PropertyAccess": {
|
|
1347
1329
|
const pa = asPropertyAccess(e);
|
|
1348
1330
|
// Sync-first: evaluate object; avoid Promise when object is sync.
|
|
@@ -1378,7 +1360,6 @@ function evalPrefixExpression(
|
|
|
1378
1360
|
throw new LuaRuntimeError(nilMsg, sf.withCtx(fc.prefix.ctx));
|
|
1379
1361
|
}
|
|
1380
1362
|
|
|
1381
|
-
// Fast path: non-method call with sync prefix
|
|
1382
1363
|
if (!fc.name && !isPromise(prefixValue)) {
|
|
1383
1364
|
const argsVal = evalExpressions(fc.args, env, sf);
|
|
1384
1365
|
if (!isPromise(argsVal)) {
|
|
@@ -1393,7 +1374,6 @@ function evalPrefixExpression(
|
|
|
1393
1374
|
calleeVal: LuaValue,
|
|
1394
1375
|
selfArgs: LuaValue[],
|
|
1395
1376
|
): LuaValue | Promise<LuaValue> => {
|
|
1396
|
-
// Normal argument handling for hello:there(a, b, c) type calls
|
|
1397
1377
|
if (fc.name) {
|
|
1398
1378
|
const self = calleeVal;
|
|
1399
1379
|
calleeVal = luaIndexValue(calleeVal, fc.name, sf);
|
|
@@ -1441,7 +1421,6 @@ function evalPrefixExpression(
|
|
|
1441
1421
|
}
|
|
1442
1422
|
}
|
|
1443
1423
|
|
|
1444
|
-
// Helper functions to reduce duplication
|
|
1445
1424
|
function evalMetamethod(
|
|
1446
1425
|
left: any,
|
|
1447
1426
|
right: any,
|
|
@@ -1466,7 +1445,6 @@ function evalMetamethod(
|
|
|
1466
1445
|
}
|
|
1467
1446
|
}
|
|
1468
1447
|
|
|
1469
|
-
// Unary metamethod lookup and call
|
|
1470
1448
|
function evalUnaryMetamethod(
|
|
1471
1449
|
value: any,
|
|
1472
1450
|
metaMethod: "__unm" | "__bnot",
|
|
@@ -1484,7 +1462,6 @@ function evalUnaryMetamethod(
|
|
|
1484
1462
|
return luaCall(fn, [value], ctx, sf);
|
|
1485
1463
|
}
|
|
1486
1464
|
|
|
1487
|
-
// Unary metamethod handling (with fallback)
|
|
1488
1465
|
function unaryWithMeta(
|
|
1489
1466
|
arg: any,
|
|
1490
1467
|
meta: "__unm" | "__bnot",
|
|
@@ -1502,7 +1479,6 @@ function unaryWithMeta(
|
|
|
1502
1479
|
return fallback();
|
|
1503
1480
|
}
|
|
1504
1481
|
|
|
1505
|
-
// Logical short-circuit evaluation
|
|
1506
1482
|
function evalLogical(
|
|
1507
1483
|
op: "and" | "or",
|
|
1508
1484
|
leftExpr: LuaExpression,
|
|
@@ -1771,12 +1747,10 @@ function luaRelWithMetamethod(
|
|
|
1771
1747
|
* - throw error otherwise.
|
|
1772
1748
|
*/
|
|
1773
1749
|
function luaLengthOp(val: any, ctx: ASTCtx, sf: LuaStackFrame): LuaValue {
|
|
1774
|
-
// Strings: ignore `__len`
|
|
1775
1750
|
if (typeof val === "string") {
|
|
1776
1751
|
return val.length;
|
|
1777
1752
|
}
|
|
1778
1753
|
|
|
1779
|
-
// Tables: prefer metatable `__len` to raw length
|
|
1780
1754
|
if (val instanceof LuaTable) {
|
|
1781
1755
|
const mt = getMetatable(val, sf);
|
|
1782
1756
|
if (mt) {
|
|
@@ -1788,7 +1762,6 @@ function luaLengthOp(val: any, ctx: ASTCtx, sf: LuaStackFrame): LuaValue {
|
|
|
1788
1762
|
return val.length;
|
|
1789
1763
|
}
|
|
1790
1764
|
|
|
1791
|
-
// Other values: allow metatable `__len` first
|
|
1792
1765
|
{
|
|
1793
1766
|
const mt = getMetatable(val, sf);
|
|
1794
1767
|
if (mt) {
|
|
@@ -1799,12 +1772,10 @@ function luaLengthOp(val: any, ctx: ASTCtx, sf: LuaStackFrame): LuaValue {
|
|
|
1799
1772
|
}
|
|
1800
1773
|
}
|
|
1801
1774
|
|
|
1802
|
-
// JS arrays (interop): length if no `__len` override
|
|
1803
1775
|
if (Array.isArray(val)) {
|
|
1804
1776
|
return val.length;
|
|
1805
1777
|
}
|
|
1806
1778
|
|
|
1807
|
-
// Otherwise error with type
|
|
1808
1779
|
const t = luaTypeOf(val) as LuaType;
|
|
1809
1780
|
throw new LuaRuntimeError(
|
|
1810
1781
|
`attempt to get length of a ${t} value`,
|
|
@@ -1820,7 +1791,6 @@ function evalExpressions(
|
|
|
1820
1791
|
const len = es.length;
|
|
1821
1792
|
if (len === 0) return [];
|
|
1822
1793
|
|
|
1823
|
-
// Evaluate all arguments (sync-first); avoid .map() closure overhead
|
|
1824
1794
|
const parts = new Array(len);
|
|
1825
1795
|
for (let i = 0; i < len; i++) {
|
|
1826
1796
|
parts[i] = evalExpression(es[i], env, sf);
|
|
@@ -1831,11 +1801,9 @@ function evalExpressions(
|
|
|
1831
1801
|
const finalize = (argsResolved: any[]) => {
|
|
1832
1802
|
const out: LuaValue[] = [];
|
|
1833
1803
|
const lastIdx = argsResolved.length - 1;
|
|
1834
|
-
// All but last expression produce a single value
|
|
1835
1804
|
for (let i = 0; i < lastIdx; i++) {
|
|
1836
1805
|
out.push(singleResult(argsResolved[i]));
|
|
1837
1806
|
}
|
|
1838
|
-
// Last expression preserves multiple results
|
|
1839
1807
|
const last = argsResolved[lastIdx];
|
|
1840
1808
|
if (last instanceof LuaMultiRes) {
|
|
1841
1809
|
out.push(...last.flatten().values);
|
|
@@ -1867,6 +1835,8 @@ function runStatementsNoGoto(
|
|
|
1867
1835
|
const result = evalStatement(stmts[i], execEnv, sf, returnOnReturn);
|
|
1868
1836
|
if (isPromise(result)) {
|
|
1869
1837
|
return (result as Promise<any>).then((res) => {
|
|
1838
|
+
const budget = sf.threadState.budget;
|
|
1839
|
+
if (budget !== undefined) budget.awaited = true;
|
|
1870
1840
|
if (res !== undefined) {
|
|
1871
1841
|
if (isGotoSignal(res)) {
|
|
1872
1842
|
throw new LuaRuntimeError(
|
|
@@ -1895,6 +1865,35 @@ function runStatementsNoGoto(
|
|
|
1895
1865
|
return processFrom(startIdx);
|
|
1896
1866
|
}
|
|
1897
1867
|
|
|
1868
|
+
function closeThenRethrow(
|
|
1869
|
+
sf: LuaStackFrame,
|
|
1870
|
+
mark: number,
|
|
1871
|
+
e: any,
|
|
1872
|
+
): never | Promise<never> {
|
|
1873
|
+
const errObj: LuaValue =
|
|
1874
|
+
e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e));
|
|
1875
|
+
// Lua 5.4 lets an error from __close replace the one being unwound, but a
|
|
1876
|
+
// user-initiated stop is not a Lua error and outranks it.
|
|
1877
|
+
const preferOriginal = (closeErr: any): never => {
|
|
1878
|
+
if (e instanceof LuaBudgetStopped) {
|
|
1879
|
+
throw e;
|
|
1880
|
+
}
|
|
1881
|
+
throw closeErr;
|
|
1882
|
+
};
|
|
1883
|
+
let r: void | Promise<void>;
|
|
1884
|
+
try {
|
|
1885
|
+
r = luaCloseFromMark(sf, mark, errObj);
|
|
1886
|
+
} catch (closeErr: any) {
|
|
1887
|
+
return preferOriginal(closeErr);
|
|
1888
|
+
}
|
|
1889
|
+
if (isPromise(r)) {
|
|
1890
|
+
return (r as Promise<void>).then(() => {
|
|
1891
|
+
throw e;
|
|
1892
|
+
}, preferOriginal);
|
|
1893
|
+
}
|
|
1894
|
+
throw e;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1898
1897
|
function withCloseBoundary(
|
|
1899
1898
|
sf: LuaStackFrame,
|
|
1900
1899
|
mark: number,
|
|
@@ -1915,17 +1914,7 @@ function withCloseBoundary(
|
|
|
1915
1914
|
return isPromise(r) ? (r as Promise<void>).then(() => res) : res;
|
|
1916
1915
|
};
|
|
1917
1916
|
|
|
1918
|
-
const onRejected = (e: any) =>
|
|
1919
|
-
const errObj: LuaValue =
|
|
1920
|
-
e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e));
|
|
1921
|
-
const r = luaCloseFromMark(sf, mark, errObj);
|
|
1922
|
-
if (isPromise(r)) {
|
|
1923
|
-
return (r as Promise<void>).then(() => {
|
|
1924
|
-
throw e;
|
|
1925
|
-
});
|
|
1926
|
-
}
|
|
1927
|
-
throw e;
|
|
1928
|
-
};
|
|
1917
|
+
const onRejected = (e: any) => closeThenRethrow(sf, mark, e);
|
|
1929
1918
|
|
|
1930
1919
|
return p.then(onFulfilled, onRejected);
|
|
1931
1920
|
}
|
|
@@ -1965,6 +1954,8 @@ function evalBlockNoClose(
|
|
|
1965
1954
|
const r = evalStatement(stmts[i], execEnv, sf, returnOnReturn);
|
|
1966
1955
|
if (isPromise(r)) {
|
|
1967
1956
|
return (r as Promise<any>).then((res) => {
|
|
1957
|
+
const budget = sf.threadState.budget;
|
|
1958
|
+
if (budget !== undefined) budget.awaited = true;
|
|
1968
1959
|
if (isGotoSignal(res)) return res;
|
|
1969
1960
|
if (res !== undefined) return res;
|
|
1970
1961
|
return runFrom(i + 1);
|
|
@@ -2005,11 +1996,13 @@ function evalBlockNoClose(
|
|
|
2005
1996
|
const execEnv = b.needsEnv === true ? new LuaEnv(env) : env;
|
|
2006
1997
|
const stmts = b.statements;
|
|
2007
1998
|
|
|
1999
|
+
const budget = sf.threadState.budget;
|
|
2008
2000
|
const runFrom = (i: number): EvalBlockResult => {
|
|
2009
2001
|
for (; i < stmts.length; i++) {
|
|
2010
2002
|
const r = evalStatement(stmts[i], execEnv, sf, returnOnReturn);
|
|
2011
2003
|
if (isPromise(r)) {
|
|
2012
2004
|
return (r as Promise<any>).then((res) => {
|
|
2005
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2013
2006
|
const consumed = consumeGotoInBlock(res, meta!.labels);
|
|
2014
2007
|
if (typeof consumed === "number") {
|
|
2015
2008
|
return runFrom(consumed);
|
|
@@ -2022,6 +2015,13 @@ function evalBlockNoClose(
|
|
|
2022
2015
|
}
|
|
2023
2016
|
const consumed = consumeGotoInBlock(r, meta.labels);
|
|
2024
2017
|
if (typeof consumed === "number") {
|
|
2018
|
+
if (budget !== undefined && --budget.ticks <= 0) {
|
|
2019
|
+
const y = budgetTick(budget);
|
|
2020
|
+
if (y !== undefined) {
|
|
2021
|
+
const resumeAt = consumed;
|
|
2022
|
+
return rpThen(y, () => runFrom(resumeAt));
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
2025
|
i = consumed - 1;
|
|
2026
2026
|
continue;
|
|
2027
2027
|
}
|
|
@@ -2060,7 +2060,6 @@ export function evalStatement(
|
|
|
2060
2060
|
);
|
|
2061
2061
|
|
|
2062
2062
|
const apply = (values: LuaValue[], lvalues: { env: any; key: any }[]) => {
|
|
2063
|
-
// Create the error-reporting frame once, not per-lvalue
|
|
2064
2063
|
let errSf: LuaStackFrame | undefined;
|
|
2065
2064
|
const ps: Promise<any>[] = [];
|
|
2066
2065
|
for (let i = 0; i < lvalues.length; i++) {
|
|
@@ -2233,22 +2232,13 @@ export function evalStatement(
|
|
|
2233
2232
|
try {
|
|
2234
2233
|
out = evalBlockNoClose(b, env, sf, returnOnReturn);
|
|
2235
2234
|
} catch (e: any) {
|
|
2236
|
-
|
|
2237
|
-
e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e));
|
|
2238
|
-
const r = luaCloseFromMark(sf, mark, errObj);
|
|
2239
|
-
if (isPromise(r)) {
|
|
2240
|
-
return (r as Promise<void>).then(() => {
|
|
2241
|
-
throw e;
|
|
2242
|
-
});
|
|
2243
|
-
}
|
|
2244
|
-
throw e;
|
|
2235
|
+
return closeThenRethrow(sf, mark, e);
|
|
2245
2236
|
}
|
|
2246
2237
|
|
|
2247
2238
|
return withCloseBoundary(sf, mark, out);
|
|
2248
2239
|
}
|
|
2249
2240
|
case "If": {
|
|
2250
2241
|
const iff = asIf(s);
|
|
2251
|
-
// Evaluate conditions in order; avoid awaiting when not necessary
|
|
2252
2242
|
const conds = iff.conditions;
|
|
2253
2243
|
|
|
2254
2244
|
const runFrom = (
|
|
@@ -2279,6 +2269,7 @@ export function evalStatement(
|
|
|
2279
2269
|
}
|
|
2280
2270
|
case "While": {
|
|
2281
2271
|
const w = asWhile(s);
|
|
2272
|
+
const budget = sf.threadState.budget;
|
|
2282
2273
|
|
|
2283
2274
|
// Sync-first loop that re-enters sync mode after each async iteration
|
|
2284
2275
|
const runSyncFirst = ():
|
|
@@ -2286,9 +2277,14 @@ export function evalStatement(
|
|
|
2286
2277
|
| ControlSignal
|
|
2287
2278
|
| Promise<undefined | ControlSignal> => {
|
|
2288
2279
|
while (true) {
|
|
2280
|
+
if (budget !== undefined && --budget.ticks <= 0) {
|
|
2281
|
+
const y = budgetTick(budget);
|
|
2282
|
+
if (y !== undefined) return rpThen(y, () => runSyncFirst());
|
|
2283
|
+
}
|
|
2289
2284
|
const c = evalExpression(w.condition, env, sf);
|
|
2290
2285
|
if (isPromise(c)) {
|
|
2291
2286
|
return (c as Promise<any>).then((cv) => {
|
|
2287
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2292
2288
|
if (!luaTruthy(cv)) return;
|
|
2293
2289
|
return rpThen(
|
|
2294
2290
|
evalStatement(w.block, env, sf, returnOnReturn),
|
|
@@ -2305,6 +2301,7 @@ export function evalStatement(
|
|
|
2305
2301
|
const r = evalStatement(w.block, env, sf, returnOnReturn);
|
|
2306
2302
|
if (isPromise(r)) {
|
|
2307
2303
|
return (r as Promise<any>).then((res) => {
|
|
2304
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2308
2305
|
if (res !== undefined) {
|
|
2309
2306
|
return isBreakSignal(res) ? undefined : res;
|
|
2310
2307
|
}
|
|
@@ -2323,6 +2320,7 @@ export function evalStatement(
|
|
|
2323
2320
|
}
|
|
2324
2321
|
case "Repeat": {
|
|
2325
2322
|
const rep = asRepeat(s);
|
|
2323
|
+
const budget = sf.threadState.budget;
|
|
2326
2324
|
|
|
2327
2325
|
// Sync-first loop that re-enters sync mode after each async iteration
|
|
2328
2326
|
const runSyncFirst = ():
|
|
@@ -2330,9 +2328,14 @@ export function evalStatement(
|
|
|
2330
2328
|
| ControlSignal
|
|
2331
2329
|
| Promise<undefined | ControlSignal> => {
|
|
2332
2330
|
while (true) {
|
|
2331
|
+
if (budget !== undefined && --budget.ticks <= 0) {
|
|
2332
|
+
const y = budgetTick(budget);
|
|
2333
|
+
if (y !== undefined) return rpThen(y, () => runSyncFirst());
|
|
2334
|
+
}
|
|
2333
2335
|
const rr = evalStatement(rep.block, env, sf, returnOnReturn);
|
|
2334
2336
|
if (isPromise(rr)) {
|
|
2335
2337
|
return (rr as Promise<any>).then((res) => {
|
|
2338
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2336
2339
|
if (res !== undefined) {
|
|
2337
2340
|
return isBreakSignal(res) ? undefined : res;
|
|
2338
2341
|
}
|
|
@@ -2348,9 +2351,10 @@ export function evalStatement(
|
|
|
2348
2351
|
|
|
2349
2352
|
const c = evalExpression(rep.condition, env, sf);
|
|
2350
2353
|
if (isPromise(c)) {
|
|
2351
|
-
return (c as Promise<any>).then((cv) =>
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
+
return (c as Promise<any>).then((cv) => {
|
|
2355
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2356
|
+
return luaTruthy(cv) ? undefined : runSyncFirst();
|
|
2357
|
+
});
|
|
2354
2358
|
}
|
|
2355
2359
|
if (luaTruthy(c)) break;
|
|
2356
2360
|
}
|
|
@@ -2375,7 +2379,6 @@ export function evalStatement(
|
|
|
2375
2379
|
let body = fn.body;
|
|
2376
2380
|
let propNames = fn.name.propNames;
|
|
2377
2381
|
if (fn.name.colonName) {
|
|
2378
|
-
// function hello:there() -> function hello.there(self) transformation
|
|
2379
2382
|
body = {
|
|
2380
2383
|
...fn.body,
|
|
2381
2384
|
parameters: ["self", ...fn.body.parameters],
|
|
@@ -2502,10 +2505,22 @@ export function evalStatement(
|
|
|
2502
2505
|
const shouldContinue =
|
|
2503
2506
|
step > 0 ? (i: number) => i <= end : (i: number) => i >= end;
|
|
2504
2507
|
|
|
2508
|
+
const budget = sf.threadState.budget;
|
|
2509
|
+
|
|
2505
2510
|
for (let i = startIndex; shouldContinue(i); i += step) {
|
|
2511
|
+
if (budget !== undefined && --budget.ticks <= 0) {
|
|
2512
|
+
const y = budgetTick(budget);
|
|
2513
|
+
if (y !== undefined) {
|
|
2514
|
+
const resumeAt = i;
|
|
2515
|
+
return rpThen(y, () =>
|
|
2516
|
+
runFromIndex(loopEnv, end, step, resumeAt, loopType),
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2506
2520
|
const r = executeIteration(loopEnv, i, loopType);
|
|
2507
2521
|
if (isPromise(r)) {
|
|
2508
2522
|
return (r as Promise<any>).then((res) => {
|
|
2523
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2509
2524
|
if (res !== undefined) {
|
|
2510
2525
|
return isBreakSignal(res) ? undefined : res;
|
|
2511
2526
|
}
|
|
@@ -2534,11 +2549,22 @@ export function evalStatement(
|
|
|
2534
2549
|
step > 0 ? (i: number) => i <= end : (i: number) => i >= end;
|
|
2535
2550
|
|
|
2536
2551
|
const loopEnv = new LuaEnv(env);
|
|
2552
|
+
const budget = sf.threadState.budget;
|
|
2537
2553
|
|
|
2538
2554
|
for (let i = start; shouldContinue(i); i += step) {
|
|
2555
|
+
if (budget !== undefined && --budget.ticks <= 0) {
|
|
2556
|
+
const y = budgetTick(budget);
|
|
2557
|
+
if (y !== undefined) {
|
|
2558
|
+
const resumeAt = i;
|
|
2559
|
+
return rpThen(y, () =>
|
|
2560
|
+
runSyncFirst(resumeAt, end, step, loopType),
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2539
2564
|
const r = executeIteration(loopEnv, i, loopType);
|
|
2540
2565
|
if (isPromise(r)) {
|
|
2541
2566
|
return (r as Promise<any>).then((res) => {
|
|
2567
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2542
2568
|
if (res !== undefined) {
|
|
2543
2569
|
if (isBreakSignal(res)) {
|
|
2544
2570
|
return;
|
|
@@ -2573,6 +2599,8 @@ export function evalStatement(
|
|
|
2573
2599
|
isPromise(endV) ? endV : Promise.resolve(endV),
|
|
2574
2600
|
isPromise(stepV) ? stepV : Promise.resolve(stepV),
|
|
2575
2601
|
]).then(([start, end, step]) => {
|
|
2602
|
+
const budget = sf.threadState.budget;
|
|
2603
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2576
2604
|
return runSyncFirst(
|
|
2577
2605
|
untagNumber(start) as number,
|
|
2578
2606
|
untagNumber(end) as number,
|
|
@@ -2602,8 +2630,6 @@ export function evalStatement(
|
|
|
2602
2630
|
const afterExprs = (resolved: any[]) => {
|
|
2603
2631
|
const iteratorMultiRes = new LuaMultiRes(resolved).flatten();
|
|
2604
2632
|
let iteratorValue: ILuaFunction | any = iteratorMultiRes.values[0];
|
|
2605
|
-
// Handle the case where the iterator is a table and we need
|
|
2606
|
-
// to call the `each` function.
|
|
2607
2633
|
if (Array.isArray(iteratorValue) || iteratorValue instanceof LuaTable) {
|
|
2608
2634
|
iteratorValue = (env.get("each") as ILuaFunction).call(
|
|
2609
2635
|
sf,
|
|
@@ -2630,26 +2656,14 @@ export function evalStatement(
|
|
|
2630
2656
|
luaMarkToBeClosed(sf, closing, fi.ctx);
|
|
2631
2657
|
}
|
|
2632
2658
|
|
|
2633
|
-
const errObjFrom = (e: any): LuaValue =>
|
|
2634
|
-
e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e));
|
|
2635
|
-
|
|
2636
2659
|
const finish = (res: any) => {
|
|
2637
2660
|
const r = luaCloseFromMark(sf, mark, null);
|
|
2638
2661
|
return isPromise(r) ? (r as Promise<void>).then(() => res) : res;
|
|
2639
2662
|
};
|
|
2640
2663
|
|
|
2641
|
-
const finishErr = (e: any): Promise<never> | never =>
|
|
2642
|
-
|
|
2643
|
-
const r = luaCloseFromMark(sf, mark, errObj);
|
|
2644
|
-
if (isPromise(r)) {
|
|
2645
|
-
return (r as Promise<void>).then(() => {
|
|
2646
|
-
throw e;
|
|
2647
|
-
});
|
|
2648
|
-
}
|
|
2649
|
-
throw e;
|
|
2650
|
-
};
|
|
2664
|
+
const finishErr = (e: any): Promise<never> | never =>
|
|
2665
|
+
closeThenRethrow(sf, mark, e);
|
|
2651
2666
|
|
|
2652
|
-
// Allocate the reusable env once before the loop
|
|
2653
2667
|
const loopEnv = canReuseEnv ? new LuaEnv(env) : null;
|
|
2654
2668
|
|
|
2655
2669
|
const makeIterEnv = (): LuaEnv => {
|
|
@@ -2659,9 +2673,14 @@ export function evalStatement(
|
|
|
2659
2673
|
return new LuaEnv(env);
|
|
2660
2674
|
};
|
|
2661
2675
|
|
|
2676
|
+
const budget = sf.threadState.budget;
|
|
2662
2677
|
// Sync-first loop that re-enters sync mode after each async iteration
|
|
2663
2678
|
const runSyncFirst = (): any => {
|
|
2664
2679
|
while (true) {
|
|
2680
|
+
if (budget !== undefined && --budget.ticks <= 0) {
|
|
2681
|
+
const y = budgetTick(budget);
|
|
2682
|
+
if (y !== undefined) return rpThen(y, () => runSyncFirst());
|
|
2683
|
+
}
|
|
2665
2684
|
const iterCall = luaCall(
|
|
2666
2685
|
iteratorValue,
|
|
2667
2686
|
[state, control],
|
|
@@ -2670,6 +2689,7 @@ export function evalStatement(
|
|
|
2670
2689
|
);
|
|
2671
2690
|
|
|
2672
2691
|
const afterIterCall = (itv: any): any => {
|
|
2692
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2673
2693
|
const iterResult = new LuaMultiRes(itv).flatten();
|
|
2674
2694
|
const nextControl = iterResult.values[0];
|
|
2675
2695
|
if (nextControl === null || nextControl === undefined) {
|
|
@@ -2712,6 +2732,7 @@ export function evalStatement(
|
|
|
2712
2732
|
if (isPromise(r)) {
|
|
2713
2733
|
return (r as Promise<any>)
|
|
2714
2734
|
.then((res) => {
|
|
2735
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2715
2736
|
if (res !== undefined) {
|
|
2716
2737
|
if (isBreakSignal(res)) {
|
|
2717
2738
|
return finish(undefined);
|
|
@@ -2739,7 +2760,11 @@ export function evalStatement(
|
|
|
2739
2760
|
};
|
|
2740
2761
|
|
|
2741
2762
|
if (isPromise(exprVals)) {
|
|
2742
|
-
return (exprVals as Promise<any[]>).then(
|
|
2763
|
+
return (exprVals as Promise<any[]>).then((v) => {
|
|
2764
|
+
const budget = sf.threadState.budget;
|
|
2765
|
+
if (budget !== undefined) budget.awaited = true;
|
|
2766
|
+
return afterExprs(v);
|
|
2767
|
+
});
|
|
2743
2768
|
}
|
|
2744
2769
|
return afterExprs(exprVals as any[]);
|
|
2745
2770
|
}
|
|
@@ -35,7 +35,6 @@ class LabelResolveError extends Error {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Cache
|
|
39
38
|
const functionMetaByRoot = new WeakMap<LuaBlock, FunctionMeta>();
|
|
40
39
|
const functionMetaByAnyBlock = new WeakMap<LuaBlock, FunctionMeta>();
|
|
41
40
|
|
|
@@ -107,11 +106,9 @@ function resolveFunction(root: LuaBlock): FunctionMeta {
|
|
|
107
106
|
roleByBlock,
|
|
108
107
|
);
|
|
109
108
|
|
|
110
|
-
// Validate gotos
|
|
111
109
|
for (const g of vctx.gotos) {
|
|
112
110
|
const target = g.node.name;
|
|
113
111
|
|
|
114
|
-
// Search current block for the label, then ancestors
|
|
115
112
|
let searchBlock: LuaBlock | undefined = g.block;
|
|
116
113
|
let labelIndex: number | undefined;
|
|
117
114
|
let labelDefBlock: LuaBlock | undefined;
|
|
@@ -151,7 +148,6 @@ function resolveFunction(root: LuaBlock): FunctionMeta {
|
|
|
151
148
|
);
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
// Local scope forward jump check
|
|
155
151
|
let entersLocalScope = false;
|
|
156
152
|
let entersCloseScope = false;
|
|
157
153
|
|