@silverbulletmd/silverbullet 2.10.0 → 2.12.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 +6 -6
- package/client/asset_bundle/bundle.ts +0 -1
- package/client/config.ts +343 -0
- package/client/data/kv_primitives.ts +2 -2
- package/client/markdown_parser/constants.ts +5 -1
- package/client/plugos/hooks/command.ts +8 -6
- package/client/plugos/hooks/event.ts +60 -24
- package/client/plugos/hooks/mq.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +3 -20
- package/client/plugos/hooks/syscall.ts +0 -4
- package/client/plugos/manifest_cache.ts +2 -34
- package/client/plugos/plug.ts +0 -3
- package/client/plugos/plug_compile.ts +3 -13
- package/client/plugos/sandboxes/sandbox.ts +2 -2
- package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
- package/client/plugos/syscalls/capture.ts +104 -0
- package/client/plugos/syscalls/editor.ts +197 -56
- package/client/plugos/syscalls/event.ts +12 -9
- 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 +138 -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 +70 -4
- package/client/plugos/syscalls/system.ts +39 -0
- package/client/plugos/system.ts +9 -7
- package/client/plugos/types.ts +6 -6
- package/client/plugos/worker_runtime.ts +0 -2
- package/client/space_lua/aggregates.ts +6 -22
- 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 +2 -7
- package/client/space_lua/parse.ts +0 -17
- package/client/space_lua/quarantine.ts +127 -0
- package/client/space_lua/query_collection.ts +18 -51
- 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 +64 -77
- 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 +18 -23
- package/client/space_lua/stdlib/space_lua.ts +1 -2
- package/client/space_lua/stdlib/string_pack.ts +4 -7
- 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 +3 -4
- package/package.json +52 -41
- 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 +1 -9
- 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 +5 -44
- package/plug-api/syscall.ts +1 -5
- package/plug-api/syscalls/config.ts +2 -2
- package/plug-api/syscalls/editor.ts +75 -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 +31 -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 +20 -4
- package/plug-api/types/datastore.ts +11 -2
- package/plug-api/types/index.ts +10 -0
- package/plug-api/types/manifest.ts +7 -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 +285 -0
- package/plug-api/ui/tree_types.ts +67 -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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ASTCtx, LuaFunctionBody, NumericType } from "./ast.ts";
|
|
2
|
+
import type { LuaBudget } from "./budget.ts";
|
|
2
3
|
import { evalStatement } from "./eval.ts";
|
|
3
4
|
import { asyncQuickSort } from "./util.ts";
|
|
4
5
|
import { isPromise, rpAll } from "./rp.ts";
|
|
@@ -10,6 +11,15 @@ import type {
|
|
|
10
11
|
LuaFunctionInfo,
|
|
11
12
|
} from "../../plug-api/types/index.ts";
|
|
12
13
|
|
|
14
|
+
let boundaryBudgetFactory: (() => LuaBudget) | undefined;
|
|
15
|
+
export const luaFunctionInfo = Symbol("luaFunctionInfo");
|
|
16
|
+
|
|
17
|
+
export function setBoundaryBudgetFactory(
|
|
18
|
+
f: (() => LuaBudget) | undefined,
|
|
19
|
+
): void {
|
|
20
|
+
boundaryBudgetFactory = f;
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
export type LuaType =
|
|
14
24
|
| "nil"
|
|
15
25
|
| "boolean"
|
|
@@ -20,7 +30,6 @@ export type LuaType =
|
|
|
20
30
|
| "userdata"
|
|
21
31
|
| "thread";
|
|
22
32
|
|
|
23
|
-
// These types are for documentation only
|
|
24
33
|
export type LuaValue = any;
|
|
25
34
|
export type JSValue = any;
|
|
26
35
|
|
|
@@ -31,29 +40,28 @@ export type LuaFunctionDefinition<Callback> = {
|
|
|
31
40
|
callback: Callback;
|
|
32
41
|
} & LuaFunctionDefinitionMetadata;
|
|
33
42
|
|
|
34
|
-
export
|
|
43
|
+
export type ILuaFunction = {
|
|
35
44
|
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue;
|
|
36
45
|
|
|
37
46
|
asString(): string;
|
|
38
47
|
|
|
39
48
|
info?: LuaFunctionInfo;
|
|
40
|
-
}
|
|
49
|
+
};
|
|
41
50
|
|
|
42
|
-
export
|
|
51
|
+
export type ILuaSettable = {
|
|
43
52
|
set(
|
|
44
53
|
key: LuaValue,
|
|
45
54
|
value: LuaValue,
|
|
46
55
|
sf?: LuaStackFrame,
|
|
47
56
|
numType?: NumericType,
|
|
48
57
|
): void | Promise<void>;
|
|
49
|
-
}
|
|
58
|
+
};
|
|
50
59
|
|
|
51
|
-
export
|
|
60
|
+
export type ILuaGettable = {
|
|
52
61
|
get(key: LuaValue, sf?: LuaStackFrame): LuaValue | Promise<LuaValue> | null;
|
|
53
62
|
getNumericType?(key: LuaValue): NumericType | undefined;
|
|
54
|
-
}
|
|
63
|
+
};
|
|
55
64
|
|
|
56
|
-
// Small helpers for type safety/readability
|
|
57
65
|
export function isILuaFunction(v: unknown): v is ILuaFunction {
|
|
58
66
|
return !!v && typeof (v as any).call === "function";
|
|
59
67
|
}
|
|
@@ -81,11 +89,11 @@ const EMPTY_CTX = {} as ASTCtx;
|
|
|
81
89
|
|
|
82
90
|
const MAX_TAG_LOOP = 200;
|
|
83
91
|
|
|
84
|
-
// Close-stack support
|
|
85
92
|
export type LuaCloseEntry = { value: LuaValue; ctx: ASTCtx };
|
|
86
93
|
|
|
87
94
|
type LuaThreadState = {
|
|
88
95
|
closeStack?: LuaCloseEntry[];
|
|
96
|
+
budget?: LuaBudget;
|
|
89
97
|
};
|
|
90
98
|
|
|
91
99
|
function isLuaNumber(v: any): boolean {
|
|
@@ -336,14 +344,10 @@ export class LuaEnv implements ILuaSettable, ILuaGettable {
|
|
|
336
344
|
}
|
|
337
345
|
|
|
338
346
|
get(name: string, _sf?: LuaStackFrame): Promise<LuaValue> | LuaValue | null {
|
|
339
|
-
// Fast path: single Map.get() instead of has() + get() for the common case
|
|
340
|
-
// where the variable exists and has a non-undefined value (null = Lua nil is fine).
|
|
341
347
|
const v = this.variables.get(name);
|
|
342
348
|
if (v !== undefined) {
|
|
343
349
|
return v;
|
|
344
350
|
}
|
|
345
|
-
// Variable set to null (Lua nil) returns null from Map.get() which is !== undefined,
|
|
346
|
-
// so it's handled above. Only fall through when the key is truly absent.
|
|
347
351
|
if (this.parent) {
|
|
348
352
|
return this.parent.get(name, _sf);
|
|
349
353
|
}
|
|
@@ -488,39 +492,31 @@ export class LuaFunction implements ILuaFunction {
|
|
|
488
492
|
}
|
|
489
493
|
|
|
490
494
|
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
|
491
|
-
// Create a new environment that chains to the captured environment
|
|
492
495
|
const env = new LuaEnv(this.capturedEnv);
|
|
493
|
-
// Set _CTX to the thread local environment from the stack frame
|
|
494
496
|
env.setLocal("_CTX", sf.threadLocal);
|
|
495
497
|
|
|
496
|
-
// Eval using a stack frame that knows the current function
|
|
497
498
|
const sfWithFn = sf.currentFunction === this ? sf : sf.withFunction(this);
|
|
498
499
|
|
|
499
500
|
const params = this.body.parameters;
|
|
500
501
|
const hasVarargs = params.length > 0 && params[params.length - 1] === "...";
|
|
501
502
|
|
|
502
|
-
// Resolve args (sync-first)
|
|
503
503
|
const argsRP = rpAll(args as any[]);
|
|
504
504
|
const resolveArgs = (resolvedArgs: any[]) => {
|
|
505
505
|
if (hasVarargs) {
|
|
506
|
-
// Variadic function: bind named params, collect rest into varargs
|
|
507
506
|
const varargStart = params.length - 1;
|
|
508
507
|
for (let i = 0; i < varargStart; i++) {
|
|
509
508
|
env.setLocal(params[i], resolvedArgs[i] ?? null);
|
|
510
509
|
}
|
|
511
510
|
env.setLocal("...", new LuaMultiRes(resolvedArgs.slice(varargStart)));
|
|
512
511
|
} else {
|
|
513
|
-
// Non-variadic: bind all named params directly
|
|
514
512
|
for (let i = 0; i < params.length; i++) {
|
|
515
513
|
env.setLocal(params[i], resolvedArgs[i] ?? null);
|
|
516
514
|
}
|
|
517
515
|
}
|
|
518
516
|
|
|
519
|
-
// Evaluate the function body with returnOnReturn set to true
|
|
520
517
|
const r = evalStatement(this.body.block, env, sfWithFn, true);
|
|
521
518
|
|
|
522
519
|
if (!isPromise(r)) {
|
|
523
|
-
// Fast path: synchronous result
|
|
524
520
|
if (r === undefined) return;
|
|
525
521
|
if (r && typeof r === "object" && r.ctrl === "return") {
|
|
526
522
|
return mapFunctionReturnValue(r.values);
|
|
@@ -675,14 +671,10 @@ export class LuaBuiltinFunction implements ILuaFunction {
|
|
|
675
671
|
}
|
|
676
672
|
|
|
677
673
|
export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
678
|
-
// To optimize the table implementation we use a combination of different data structures
|
|
679
674
|
public metatable: LuaTable | null;
|
|
680
675
|
|
|
681
|
-
// When tables are used as maps, the common case is that they are string keys, so we use a simple object for that
|
|
682
676
|
private stringKeys: Record<string, any>;
|
|
683
|
-
// Other keys we can support using a Map as a fallback
|
|
684
677
|
private otherKeys: Map<any, any> | null;
|
|
685
|
-
// When tables are used as arrays, we use a native JavaScript array for that
|
|
686
678
|
private arrayPart: any[];
|
|
687
679
|
|
|
688
680
|
// Numeric type metadata at storage boundaries (lazily allocated)
|
|
@@ -719,15 +711,12 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
719
711
|
|
|
720
712
|
const numVal = LuaTable.numKeyValue(key);
|
|
721
713
|
if (numVal !== null) {
|
|
722
|
-
// Normalize -0 to +0
|
|
723
714
|
if (isNegativeZero(numVal)) {
|
|
724
715
|
return 0;
|
|
725
716
|
}
|
|
726
|
-
// Integer-valued numbers normalize to integers
|
|
727
717
|
if (Number.isInteger(numVal)) {
|
|
728
718
|
return numVal;
|
|
729
719
|
}
|
|
730
|
-
// Non-integer floats
|
|
731
720
|
return numVal;
|
|
732
721
|
}
|
|
733
722
|
return key;
|
|
@@ -799,7 +788,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
799
788
|
return this.stringKeys[key] !== undefined;
|
|
800
789
|
}
|
|
801
790
|
|
|
802
|
-
// Fast path for plain integer keys (common in for loops)
|
|
803
791
|
if (typeof key === "number") {
|
|
804
792
|
if (key >= 1 && (key | 0) === key) {
|
|
805
793
|
if (this.arrayPart[key - 1] !== undefined) return true;
|
|
@@ -892,7 +880,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
892
880
|
return value.then((v) => this.rawSet(key, v, numType));
|
|
893
881
|
}
|
|
894
882
|
|
|
895
|
-
// Fast path: string keys (the dominant case)
|
|
896
883
|
if (typeof key === "string") {
|
|
897
884
|
if (value === null || value === undefined) {
|
|
898
885
|
delete this.stringKeys[key];
|
|
@@ -951,7 +938,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
951
938
|
this.promoteIntegerKeysFromHash();
|
|
952
939
|
}
|
|
953
940
|
|
|
954
|
-
// Trailing nil shrink
|
|
955
941
|
if (value === null || value === undefined) {
|
|
956
942
|
let n = this.arrayPart.length;
|
|
957
943
|
while (n > 0) {
|
|
@@ -971,7 +957,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
971
957
|
return;
|
|
972
958
|
}
|
|
973
959
|
|
|
974
|
-
// Sparse numeric key
|
|
975
960
|
if (!this.otherKeys) {
|
|
976
961
|
this.otherKeys = new Map();
|
|
977
962
|
}
|
|
@@ -1033,12 +1018,10 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
1033
1018
|
);
|
|
1034
1019
|
}
|
|
1035
1020
|
|
|
1036
|
-
// Fast path: no metatable — skip has() check and metamethod machinery
|
|
1037
1021
|
if (this.metatable === null) {
|
|
1038
1022
|
return this.rawSet(key, value, numType);
|
|
1039
1023
|
}
|
|
1040
1024
|
|
|
1041
|
-
// Key exists — rawSet directly, no metamethod needed
|
|
1042
1025
|
if (this.has(key)) {
|
|
1043
1026
|
return this.rawSet(key, value, numType);
|
|
1044
1027
|
}
|
|
@@ -1049,7 +1032,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
1049
1032
|
return this.rawSet(key, value, numType);
|
|
1050
1033
|
}
|
|
1051
1034
|
|
|
1052
|
-
// Slow path: __newindex metamethod chain (rare)
|
|
1053
1035
|
const errSf = sf || LuaStackFrame.lostFrame;
|
|
1054
1036
|
const ctx = sf?.astCtx ?? EMPTY_CTX;
|
|
1055
1037
|
const k: LuaValue = key;
|
|
@@ -1078,12 +1060,10 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
1078
1060
|
return t.rawSet(k, v, nt);
|
|
1079
1061
|
}
|
|
1080
1062
|
|
|
1081
|
-
// Function metamethod: call and stop
|
|
1082
1063
|
if (typeof mm === "function" || isILuaFunction(mm)) {
|
|
1083
1064
|
return luaCall(mm, [t, k, v], ctx, errSf);
|
|
1084
1065
|
}
|
|
1085
1066
|
|
|
1086
|
-
// Table/env metamethod: forward assignment
|
|
1087
1067
|
if (mm instanceof LuaTable || mm instanceof LuaEnv) {
|
|
1088
1068
|
target = mm;
|
|
1089
1069
|
continue;
|
|
@@ -1113,7 +1093,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
1113
1093
|
if (typeof key === "string") {
|
|
1114
1094
|
return this.stringKeyTypes ? this.stringKeyTypes.get(key) : undefined;
|
|
1115
1095
|
}
|
|
1116
|
-
// Fast path for plain integer keys
|
|
1117
1096
|
if (typeof key === "number") {
|
|
1118
1097
|
if (key >= 1 && (key | 0) === key) {
|
|
1119
1098
|
return this.arrayTypes ? this.arrayTypes[key - 1] : undefined;
|
|
@@ -1136,7 +1115,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
1136
1115
|
return this.stringKeys[key];
|
|
1137
1116
|
}
|
|
1138
1117
|
|
|
1139
|
-
// Fast path for plain integer keys (common in for loops)
|
|
1140
1118
|
if (typeof key === "number") {
|
|
1141
1119
|
if (key >= 1 && (key | 0) === key) {
|
|
1142
1120
|
const v = this.arrayPart[key - 1];
|
|
@@ -1144,7 +1122,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
1144
1122
|
if (this.otherKeys) return this.otherKeys.get(key);
|
|
1145
1123
|
return undefined;
|
|
1146
1124
|
}
|
|
1147
|
-
// Non-integer or zero/negative number keys
|
|
1148
1125
|
if (this.otherKeys) return this.otherKeys.get(key);
|
|
1149
1126
|
return undefined;
|
|
1150
1127
|
}
|
|
@@ -1178,7 +1155,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
1178
1155
|
}
|
|
1179
1156
|
|
|
1180
1157
|
get(key: LuaValue, sf?: LuaStackFrame): LuaValue | Promise<LuaValue> | null {
|
|
1181
|
-
// Fast path: no metatable means rawGet is the final answer (most tables in SilverBullet)
|
|
1182
1158
|
if (this.metatable === null) {
|
|
1183
1159
|
const raw = this.rawGet(key);
|
|
1184
1160
|
return raw !== undefined ? raw : null;
|
|
@@ -1212,11 +1188,9 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|
|
1212
1188
|
result[k] = luaValueToJS(this.stringKeys[k], sf);
|
|
1213
1189
|
}
|
|
1214
1190
|
}
|
|
1215
|
-
// Include array part with 1-based keys
|
|
1216
1191
|
for (let i = 0; i < this.arrayPart.length; i++) {
|
|
1217
1192
|
result[i + 1] = luaValueToJS(this.arrayPart[i], sf);
|
|
1218
1193
|
}
|
|
1219
|
-
// Include other keys
|
|
1220
1194
|
if (this.otherKeys) {
|
|
1221
1195
|
for (const [key, val] of this.otherKeys) {
|
|
1222
1196
|
result[key] = luaValueToJS(val, sf);
|
|
@@ -1299,14 +1273,12 @@ export function luaIndexValue(
|
|
|
1299
1273
|
let t: LuaValue = value;
|
|
1300
1274
|
|
|
1301
1275
|
for (let loop = 0; loop < MAX_TAG_LOOP; loop++) {
|
|
1302
|
-
// Primitive get when table
|
|
1303
1276
|
if (t instanceof LuaTable) {
|
|
1304
1277
|
const raw = t.rawGet(key);
|
|
1305
1278
|
if (raw !== undefined) {
|
|
1306
1279
|
if (isSqlNull(raw)) return null;
|
|
1307
1280
|
return raw;
|
|
1308
1281
|
}
|
|
1309
|
-
// If no metatable, raw miss => nil
|
|
1310
1282
|
if (t.metatable === null) {
|
|
1311
1283
|
return null;
|
|
1312
1284
|
}
|
|
@@ -1328,18 +1300,15 @@ export function luaIndexValue(
|
|
|
1328
1300
|
);
|
|
1329
1301
|
}
|
|
1330
1302
|
|
|
1331
|
-
// Function metamethod
|
|
1332
1303
|
if (typeof mm === "function" || isILuaFunction(mm)) {
|
|
1333
1304
|
return luaCall(mm, [t, key], ctx, errSf);
|
|
1334
1305
|
}
|
|
1335
1306
|
|
|
1336
|
-
// Table/metatable delegation: repeat with mm as new "t"
|
|
1337
1307
|
if (mm instanceof LuaTable || mm instanceof LuaEnv) {
|
|
1338
1308
|
t = mm;
|
|
1339
1309
|
continue;
|
|
1340
1310
|
}
|
|
1341
1311
|
|
|
1342
|
-
// Bad metamethod type: make it a Lua-like type error
|
|
1343
1312
|
const ty = luaTypeOf(mm) as any as string;
|
|
1344
1313
|
throw new LuaRuntimeError(
|
|
1345
1314
|
`attempt to index a ${ty} value`,
|
|
@@ -1413,11 +1382,9 @@ export function luaGet(
|
|
|
1413
1382
|
if (isTaggedFloat(key)) {
|
|
1414
1383
|
return (obj as any[])[key.value - 1] ?? null;
|
|
1415
1384
|
}
|
|
1416
|
-
// Native JS object
|
|
1417
1385
|
const k = toNumKey(key);
|
|
1418
1386
|
const val = (obj as Record<string | number, any>)[k];
|
|
1419
1387
|
if (typeof val === "function") {
|
|
1420
|
-
// Automatically bind the function to the object
|
|
1421
1388
|
return val.bind(obj);
|
|
1422
1389
|
}
|
|
1423
1390
|
if (val === undefined) {
|
|
@@ -1438,7 +1405,6 @@ export function luaLen(
|
|
|
1438
1405
|
return obj.length;
|
|
1439
1406
|
}
|
|
1440
1407
|
if (obj instanceof LuaTable) {
|
|
1441
|
-
// Check __len metamethod unless raw access is requested
|
|
1442
1408
|
if (!raw) {
|
|
1443
1409
|
const mt = getMetatable(obj, sf || LuaStackFrame.lostFrame);
|
|
1444
1410
|
const mm = mt ? mt.rawGet("__len") : null;
|
|
@@ -1473,7 +1439,6 @@ export function luaCall(
|
|
|
1473
1439
|
);
|
|
1474
1440
|
}
|
|
1475
1441
|
|
|
1476
|
-
// Fast path: native JS function
|
|
1477
1442
|
if (typeof callee === "function") {
|
|
1478
1443
|
const baseSf = sf || LuaStackFrame.lostFrame;
|
|
1479
1444
|
const len = args.length;
|
|
@@ -1494,7 +1459,6 @@ export function luaCall(
|
|
|
1494
1459
|
return (callee as (...a: any[]) => any)(...args);
|
|
1495
1460
|
}
|
|
1496
1461
|
|
|
1497
|
-
// Lua table: may be callable via __call metamethod
|
|
1498
1462
|
if (callee instanceof LuaTable) {
|
|
1499
1463
|
const metatable = getMetatable(callee, sf);
|
|
1500
1464
|
|
|
@@ -1525,7 +1489,6 @@ export function luaCall(
|
|
|
1525
1489
|
}
|
|
1526
1490
|
}
|
|
1527
1491
|
|
|
1528
|
-
// ILuaFunction (LuaFunction/LuaBuiltinFunction/LuaNativeJSFunction/etc.)
|
|
1529
1492
|
if (isILuaFunction(callee)) {
|
|
1530
1493
|
// Fast path for builtins: skip withFunction, defer withCtx
|
|
1531
1494
|
if (callee instanceof LuaBuiltinFunction) {
|
|
@@ -1550,7 +1513,6 @@ export function luaCall(
|
|
|
1550
1513
|
}
|
|
1551
1514
|
|
|
1552
1515
|
export function luaEquals(a: any, b: any): boolean {
|
|
1553
|
-
// Normalize nil variants (null, undefined, SQL NULL) to null
|
|
1554
1516
|
const an =
|
|
1555
1517
|
a === null || a === undefined || isSqlNull(a)
|
|
1556
1518
|
? null
|
|
@@ -1630,7 +1592,6 @@ export class LuaRuntimeError extends Error {
|
|
|
1630
1592
|
if (!ctx || !ctx.from || !ctx.to) {
|
|
1631
1593
|
break;
|
|
1632
1594
|
}
|
|
1633
|
-
// Find the line and column
|
|
1634
1595
|
let line = 1;
|
|
1635
1596
|
let column = 0;
|
|
1636
1597
|
let lastNewline = -1;
|
|
@@ -1644,7 +1605,6 @@ export class LuaRuntimeError extends Error {
|
|
|
1644
1605
|
}
|
|
1645
1606
|
}
|
|
1646
1607
|
|
|
1647
|
-
// Get the full line of code for context
|
|
1648
1608
|
const lineStart = lastNewline + 1;
|
|
1649
1609
|
const lineEnd = code.indexOf("\n", ctx.from);
|
|
1650
1610
|
const codeLine = code.substring(
|
|
@@ -1652,7 +1612,6 @@ export class LuaRuntimeError extends Error {
|
|
|
1652
1612
|
lineEnd === -1 ? undefined : lineEnd,
|
|
1653
1613
|
);
|
|
1654
1614
|
|
|
1655
|
-
// Add position indicator
|
|
1656
1615
|
const pointer = `${" ".repeat(column)}^`;
|
|
1657
1616
|
|
|
1658
1617
|
traceStr +=
|
|
@@ -1704,12 +1663,10 @@ export function luaToString(
|
|
|
1704
1663
|
return luaFormatNumber(value);
|
|
1705
1664
|
}
|
|
1706
1665
|
|
|
1707
|
-
// Check for circular references
|
|
1708
1666
|
if (typeof value === "object" && visited.has(value)) {
|
|
1709
1667
|
return "<circular reference>";
|
|
1710
1668
|
}
|
|
1711
1669
|
if ((value as any).toStringAsync) {
|
|
1712
|
-
// Add to visited before recursing
|
|
1713
1670
|
visited.add(value);
|
|
1714
1671
|
return (value as any).toStringAsync();
|
|
1715
1672
|
}
|
|
@@ -1721,15 +1678,12 @@ export function luaToString(
|
|
|
1721
1678
|
// Don't recurse into the function body, just show the function signature
|
|
1722
1679
|
return `<lua-function (${value.body.parameters.join(", ")})>`;
|
|
1723
1680
|
}
|
|
1724
|
-
// Handle plain JavaScript objects in a Lua-like format
|
|
1725
1681
|
if (typeof value === "object") {
|
|
1726
|
-
// Add to visited before recursing
|
|
1727
1682
|
visited.add(value);
|
|
1728
1683
|
return (async () => {
|
|
1729
1684
|
let result = "{";
|
|
1730
1685
|
let first = true;
|
|
1731
1686
|
|
|
1732
|
-
// Handle arrays
|
|
1733
1687
|
if (Array.isArray(value)) {
|
|
1734
1688
|
for (const val of value) {
|
|
1735
1689
|
if (first) {
|
|
@@ -1737,14 +1691,12 @@ export function luaToString(
|
|
|
1737
1691
|
} else {
|
|
1738
1692
|
result += ", ";
|
|
1739
1693
|
}
|
|
1740
|
-
// Recursively stringify the value, passing the visited set
|
|
1741
1694
|
const strVal = await luaToString(val, visited);
|
|
1742
1695
|
result += strVal;
|
|
1743
1696
|
}
|
|
1744
1697
|
return `${result}}`;
|
|
1745
1698
|
}
|
|
1746
1699
|
|
|
1747
|
-
// Handle objects
|
|
1748
1700
|
for (const [key, val] of Object.entries(value)) {
|
|
1749
1701
|
if (first) {
|
|
1750
1702
|
first = false;
|
|
@@ -1756,7 +1708,6 @@ export function luaToString(
|
|
|
1756
1708
|
} else {
|
|
1757
1709
|
result += `["${key}"] = `;
|
|
1758
1710
|
}
|
|
1759
|
-
// Recursively stringify the value, passing the visited set
|
|
1760
1711
|
const strVal = await luaToString(val, visited);
|
|
1761
1712
|
result += strVal;
|
|
1762
1713
|
}
|
|
@@ -1777,7 +1728,6 @@ export function luaFormatNumber(n: number, kind?: "int" | "float"): string {
|
|
|
1777
1728
|
if (n === 0) {
|
|
1778
1729
|
return 1 / n === -Infinity ? "-0.0" : "0.0";
|
|
1779
1730
|
}
|
|
1780
|
-
// Delegate to luaFormat for `%.14g`
|
|
1781
1731
|
const s = luaFormat("%.14g", n);
|
|
1782
1732
|
// Guarantee `.01 suffix for integer-valued floats
|
|
1783
1733
|
if (s.indexOf(".") === -1 && s.indexOf("e") === -1) {
|
|
@@ -1834,7 +1784,6 @@ export function jsToLuaValue(value: any): any {
|
|
|
1834
1784
|
return value;
|
|
1835
1785
|
}
|
|
1836
1786
|
if (Array.isArray(value) && "index" in value && "input" in value) {
|
|
1837
|
-
// This is a RegExpMatchArray
|
|
1838
1787
|
const regexMatch = value as RegExpMatchArray;
|
|
1839
1788
|
const regexMatchTable = new LuaTable();
|
|
1840
1789
|
for (let i = 0; i < regexMatch.length; i++) {
|
|
@@ -1865,7 +1814,6 @@ export function jsToLuaValue(value: any): any {
|
|
|
1865
1814
|
return value;
|
|
1866
1815
|
}
|
|
1867
1816
|
|
|
1868
|
-
// Inverse of jsToLuaValue
|
|
1869
1817
|
export function luaValueToJS(value: any, sf: LuaStackFrame): any {
|
|
1870
1818
|
if (isPromise(value)) {
|
|
1871
1819
|
return (value as Promise<any>).then((v) => luaValueToJS(v, sf));
|
|
@@ -1878,16 +1826,55 @@ export function luaValueToJS(value: any, sf: LuaStackFrame): any {
|
|
|
1878
1826
|
value instanceof LuaFunction ||
|
|
1879
1827
|
value instanceof LuaBuiltinFunction
|
|
1880
1828
|
) {
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1829
|
+
const fn = (...args: any[]) => {
|
|
1830
|
+
// A converted closure captures one frame forever; without a fresh
|
|
1831
|
+
// thread state per call, every invocation would share one budget.
|
|
1832
|
+
const boundaryBudget = boundaryBudgetFactory?.();
|
|
1833
|
+
const callSf =
|
|
1834
|
+
boundaryBudget === undefined
|
|
1835
|
+
? sf
|
|
1836
|
+
: new LuaStackFrame(sf.threadLocal, sf.astCtx, undefined, undefined, {
|
|
1837
|
+
closeStack: undefined,
|
|
1838
|
+
budget: boundaryBudget,
|
|
1839
|
+
});
|
|
1840
|
+
// Marks this call's own budget (if any) done, so a Stop click that
|
|
1841
|
+
// arrives after the call has already returned is a no-op rather than
|
|
1842
|
+
// quarantining/disabling something that isn't running anymore.
|
|
1843
|
+
const markFinished = () => {
|
|
1844
|
+
if (boundaryBudget !== undefined) {
|
|
1845
|
+
boundaryBudget.finished = true;
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
try {
|
|
1849
|
+
const jsArgs = rpAll(args.map((v) => luaValueToJS(v, callSf)));
|
|
1850
|
+
if (isPromise(jsArgs)) {
|
|
1851
|
+
return luaValueToJS(
|
|
1852
|
+
jsArgs
|
|
1853
|
+
.then((jsArgs) => (value as ILuaFunction).call(callSf, ...jsArgs))
|
|
1854
|
+
.finally(markFinished),
|
|
1855
|
+
callSf,
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
const result = (value as ILuaFunction).call(callSf, ...jsArgs);
|
|
1859
|
+
if (isPromise(result)) {
|
|
1860
|
+
return luaValueToJS(
|
|
1861
|
+
(result as Promise<any>).finally(markFinished),
|
|
1862
|
+
callSf,
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
markFinished();
|
|
1866
|
+
return luaValueToJS(result, callSf);
|
|
1867
|
+
} catch (e) {
|
|
1868
|
+
markFinished();
|
|
1869
|
+
throw e;
|
|
1888
1870
|
}
|
|
1889
|
-
return luaValueToJS((value as ILuaFunction).call(sf, ...jsArgs), sf);
|
|
1890
1871
|
};
|
|
1872
|
+
if (value instanceof LuaFunction) {
|
|
1873
|
+
(fn as typeof fn & { [luaFunctionInfo]: LuaFunctionInfo })[
|
|
1874
|
+
luaFunctionInfo
|
|
1875
|
+
] = value.info;
|
|
1876
|
+
}
|
|
1877
|
+
return fn;
|
|
1891
1878
|
}
|
|
1892
1879
|
if (isTaggedFloat(value)) {
|
|
1893
1880
|
return value.value;
|
|
@@ -56,7 +56,6 @@ function parseSpec(
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Parse width
|
|
60
59
|
let width = 0;
|
|
61
60
|
if (i < len && fmt.charCodeAt(i) === 42) {
|
|
62
61
|
// '*'
|
|
@@ -69,7 +68,6 @@ function parseSpec(
|
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
// Parse precision
|
|
73
71
|
let hasPrec = false;
|
|
74
72
|
let prec = 0;
|
|
75
73
|
if (i < len && fmt.charCodeAt(i) === 46) {
|
|
@@ -183,7 +181,6 @@ function formatInt(n: number, spec: FormatSpec): string {
|
|
|
183
181
|
|
|
184
182
|
if (upper) digits = digits.toUpperCase();
|
|
185
183
|
|
|
186
|
-
// Precision
|
|
187
184
|
if (spec.hasPrec) {
|
|
188
185
|
if (spec.prec === 0 && v === 0) {
|
|
189
186
|
digits = "";
|
|
@@ -192,7 +189,6 @@ function formatInt(n: number, spec: FormatSpec): string {
|
|
|
192
189
|
}
|
|
193
190
|
}
|
|
194
191
|
|
|
195
|
-
// Alt flag
|
|
196
192
|
let prefix = "";
|
|
197
193
|
if (spec.flags & FLAG_HASH) {
|
|
198
194
|
if (base === 8 && (digits.length === 0 || digits.charCodeAt(0) !== 48)) {
|
|
@@ -277,7 +273,6 @@ function formatFloat(n: number, spec: FormatSpec): string {
|
|
|
277
273
|
// Alt flag for 'f'/'e': ensure decimal point exists
|
|
278
274
|
if (spec.flags & FLAG_HASH && lower !== 103) {
|
|
279
275
|
if (body.indexOf(".") === -1) {
|
|
280
|
-
// Insert dot before 'e' if present, else append
|
|
281
276
|
const eIdx = body.indexOf("e");
|
|
282
277
|
const EIdx = body.indexOf("E");
|
|
283
278
|
const expIdx = eIdx !== -1 ? eIdx : EIdx;
|
|
@@ -348,7 +343,6 @@ function stripTrailingZerosG(s: string): string {
|
|
|
348
343
|
// '0'
|
|
349
344
|
end--;
|
|
350
345
|
}
|
|
351
|
-
// Remove dot if nothing after it
|
|
352
346
|
if (end === dotIdx + 1) {
|
|
353
347
|
end = dotIdx;
|
|
354
348
|
}
|
|
@@ -452,13 +446,11 @@ function hexFloatBody(abs: number, spec: FormatSpec): string {
|
|
|
452
446
|
let mantBits: bigint;
|
|
453
447
|
|
|
454
448
|
if (biasedExp === 0) {
|
|
455
|
-
// Subnormal
|
|
456
449
|
if (frac === 0n) return "0x0p+0";
|
|
457
450
|
const shift = 52 - bitLength(frac) + 1;
|
|
458
451
|
mantBits = frac << BigInt(shift);
|
|
459
452
|
exponent = -1022 - shift;
|
|
460
453
|
} else {
|
|
461
|
-
// Normal
|
|
462
454
|
exponent = biasedExp - 1023;
|
|
463
455
|
mantBits = frac | (1n << 52n);
|
|
464
456
|
}
|
|
@@ -680,8 +672,6 @@ export function luaFormat(fmt: string, ...args: any[]): string {
|
|
|
680
672
|
while (i < len) {
|
|
681
673
|
const c = fmt.charCodeAt(i);
|
|
682
674
|
if (c !== 37) {
|
|
683
|
-
// not '%'
|
|
684
|
-
// Fast path: scan for next '%' or end
|
|
685
675
|
let j = i + 1;
|
|
686
676
|
while (j < len && fmt.charCodeAt(j) !== 37) j++;
|
|
687
677
|
out += fmt.slice(i, j);
|
|
@@ -689,7 +679,6 @@ export function luaFormat(fmt: string, ...args: any[]): string {
|
|
|
689
679
|
continue;
|
|
690
680
|
}
|
|
691
681
|
|
|
692
|
-
// '%' found
|
|
693
682
|
i++;
|
|
694
683
|
if (i >= len) {
|
|
695
684
|
throw new Error("invalid format (ends with '%')");
|
|
@@ -705,7 +694,6 @@ export function luaFormat(fmt: string, ...args: any[]): string {
|
|
|
705
694
|
const { spec, end } = parseSpec(fmt, i);
|
|
706
695
|
i = end + 1;
|
|
707
696
|
|
|
708
|
-
// Resolve `*` width and precision from args
|
|
709
697
|
let width = spec.width;
|
|
710
698
|
if (width === -1) {
|
|
711
699
|
width = Number(args[ai++]) || 0;
|
|
@@ -41,7 +41,6 @@ export const jsApi = new LuaTable({
|
|
|
41
41
|
import: new LuaBuiltinFunction({
|
|
42
42
|
callback: async (_sf, url) => {
|
|
43
43
|
let m = await import(url);
|
|
44
|
-
// Unwrap default if it exists
|
|
45
44
|
if (Object.keys(m).length === 1 && m.default) {
|
|
46
45
|
m = m.default;
|
|
47
46
|
}
|
|
@@ -179,6 +178,5 @@ export const jsApi = new LuaTable({
|
|
|
179
178
|
examples: [{ code: "print(js.stringify({1, 2, 3})) -- [1,2,3]" }],
|
|
180
179
|
}),
|
|
181
180
|
|
|
182
|
-
// Expose the global window object
|
|
183
181
|
window: globalThis,
|
|
184
182
|
});
|
|
@@ -16,7 +16,6 @@ export function luaLoad(code: LuaValue, sf: LuaStackFrame): LuaValue {
|
|
|
16
16
|
const block = parseBlock(s, sf.astCtx || {});
|
|
17
17
|
const globalEnvMaybe = sf.threadLocal.get("_GLOBAL");
|
|
18
18
|
|
|
19
|
-
// Be vocal when no _GLOBAL is set
|
|
20
19
|
if (!globalEnvMaybe) {
|
|
21
20
|
console.warn("load() called without _GLOBAL in thread-local environment");
|
|
22
21
|
return new LuaMultiRes([null, "Global environment not set"]);
|
|
@@ -18,7 +18,6 @@ function untagNumber(x: any): number {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const mathApi = new LuaTable({
|
|
21
|
-
// math constants
|
|
22
21
|
huge: 1 / 0,
|
|
23
22
|
pi: Math.PI,
|
|
24
23
|
|
|
@@ -145,7 +144,6 @@ export const mathApi = new LuaTable({
|
|
|
145
144
|
],
|
|
146
145
|
}),
|
|
147
146
|
|
|
148
|
-
// Basic functions
|
|
149
147
|
abs: new LuaBuiltinFunction({
|
|
150
148
|
callback: (_sf, x: number) => Math.abs(untagNumber(x)),
|
|
151
149
|
description: "Returns the absolute value of `x`.",
|
|
@@ -177,7 +175,6 @@ export const mathApi = new LuaTable({
|
|
|
177
175
|
returns: [{ type: "number" }],
|
|
178
176
|
}),
|
|
179
177
|
|
|
180
|
-
// Rounding and remainder
|
|
181
178
|
fmod: new LuaBuiltinFunction({
|
|
182
179
|
callback: (_sf, x: number, y: number) => untagNumber(x) % untagNumber(y),
|
|
183
180
|
description:
|
|
@@ -248,7 +245,6 @@ export const mathApi = new LuaTable({
|
|
|
248
245
|
returns: [{ type: "number" }],
|
|
249
246
|
}),
|
|
250
247
|
|
|
251
|
-
// Power and logarithms
|
|
252
248
|
exp: new LuaBuiltinFunction({
|
|
253
249
|
callback: (_sf, x: number) => Math.exp(untagNumber(x)),
|
|
254
250
|
description: "Returns `e` raised to `x`.",
|
|
@@ -289,7 +285,6 @@ export const mathApi = new LuaTable({
|
|
|
289
285
|
returns: [{ type: "number" }],
|
|
290
286
|
}),
|
|
291
287
|
|
|
292
|
-
// Trigonometric functions
|
|
293
288
|
cos: new LuaBuiltinFunction({
|
|
294
289
|
callback: (_sf, x: number) => Math.cos(untagNumber(x)),
|
|
295
290
|
description: "Returns the cosine of `x` radians.",
|
|
@@ -359,7 +354,6 @@ export const mathApi = new LuaTable({
|
|
|
359
354
|
deprecated: "Retained for compatibility with older Lua versions.",
|
|
360
355
|
}),
|
|
361
356
|
|
|
362
|
-
// Additional utility
|
|
363
357
|
deg: new LuaBuiltinFunction({
|
|
364
358
|
callback: (_sf, x: number) => (untagNumber(x) * 180) / Math.PI,
|
|
365
359
|
description: "Converts an angle from radians to degrees.",
|
|
@@ -386,7 +380,6 @@ export const mathApi = new LuaTable({
|
|
|
386
380
|
examples: [{ code: "print(math.ult(2, 3)) -- true" }],
|
|
387
381
|
}),
|
|
388
382
|
|
|
389
|
-
// Keep the cosineSimilarity utility function
|
|
390
383
|
cosineSimilarity: new LuaBuiltinFunction({
|
|
391
384
|
callback: (sf, vecA: LuaTable | number[], vecB: LuaTable | number[]) => {
|
|
392
385
|
if (vecA instanceof LuaTable) {
|