@silverbulletmd/silverbullet 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +18 -0
- package/README.md +98 -0
- package/client/asset_bundle/bundle.ts +95 -0
- package/client/data/datastore.ts +85 -0
- package/client/data/kv_primitives.ts +25 -0
- package/client/markdown_parser/constants.ts +13 -0
- package/client/plugos/event.ts +36 -0
- package/client/plugos/eventhook.ts +8 -0
- package/client/plugos/hooks/code_widget.ts +59 -0
- package/client/plugos/hooks/command.ts +104 -0
- package/client/plugos/hooks/document_editor.ts +77 -0
- package/client/plugos/hooks/event.ts +187 -0
- package/client/plugos/hooks/mq.ts +154 -0
- package/client/plugos/hooks/plug_namespace.ts +85 -0
- package/client/plugos/hooks/slash_command.ts +192 -0
- package/client/plugos/hooks/syscall.ts +66 -0
- package/client/plugos/manifest_cache.ts +67 -0
- package/client/plugos/plug.ts +99 -0
- package/client/plugos/plug_compile.ts +202 -0
- package/client/plugos/protocol.ts +40 -0
- package/client/plugos/proxy_fetch.ts +53 -0
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
- package/client/plugos/sandboxes/sandbox.ts +14 -0
- package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
- package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
- package/client/plugos/syscalls/asset.ts +35 -0
- package/client/plugos/syscalls/clientStore.ts +21 -0
- package/client/plugos/syscalls/client_code_widget.ts +12 -0
- package/client/plugos/syscalls/code_widget.ts +24 -0
- package/client/plugos/syscalls/config.ts +46 -0
- package/client/plugos/syscalls/datastore.ts +89 -0
- package/client/plugos/syscalls/editor.ts +673 -0
- package/client/plugos/syscalls/event.ts +36 -0
- package/client/plugos/syscalls/fetch.ts +128 -0
- package/client/plugos/syscalls/index.ts +102 -0
- package/client/plugos/syscalls/jsonschema.ts +69 -0
- package/client/plugos/syscalls/language.ts +23 -0
- package/client/plugos/syscalls/lua.ts +58 -0
- package/client/plugos/syscalls/markdown.ts +84 -0
- package/client/plugos/syscalls/mq.ts +52 -0
- package/client/plugos/syscalls/service_registry.ts +43 -0
- package/client/plugos/syscalls/shell.ts +39 -0
- package/client/plugos/syscalls/space.ts +139 -0
- package/client/plugos/syscalls/sync.ts +77 -0
- package/client/plugos/syscalls/system.ts +150 -0
- package/client/plugos/system.ts +201 -0
- package/client/plugos/types.ts +60 -0
- package/client/plugos/util.ts +14 -0
- package/client/plugos/worker_runtime.ts +195 -0
- package/client/space_lua/ast.ts +328 -0
- package/client/space_lua/ast_narrow.ts +81 -0
- package/client/space_lua/eval.ts +2478 -0
- package/client/space_lua/labels.ts +416 -0
- package/client/space_lua/numeric.ts +240 -0
- package/client/space_lua/parse.ts +1522 -0
- package/client/space_lua/query_collection.ts +232 -0
- package/client/space_lua/rp.ts +27 -0
- package/client/space_lua/runtime.ts +1702 -0
- package/client/space_lua/stdlib/crypto.ts +10 -0
- package/client/space_lua/stdlib/encoding.ts +19 -0
- package/client/space_lua/stdlib/format.ts +770 -0
- package/client/space_lua/stdlib/js.ts +73 -0
- package/client/space_lua/stdlib/load.ts +52 -0
- package/client/space_lua/stdlib/math.ts +193 -0
- package/client/space_lua/stdlib/net.ts +113 -0
- package/client/space_lua/stdlib/os.ts +368 -0
- package/client/space_lua/stdlib/space_lua.ts +153 -0
- package/client/space_lua/stdlib/string.ts +286 -0
- package/client/space_lua/stdlib/table.ts +401 -0
- package/client/space_lua/stdlib.ts +489 -0
- package/client/space_lua/tonumber.ts +501 -0
- package/client/space_lua/util.ts +96 -0
- package/dist/plug-compile.js +1513 -0
- package/package.json +120 -0
- package/plug-api/constants.ts +42 -0
- package/plug-api/lib/async.ts +162 -0
- package/plug-api/lib/crypto.ts +202 -0
- package/plug-api/lib/dates.ts +13 -0
- package/plug-api/lib/json.ts +136 -0
- package/plug-api/lib/limited_map.ts +72 -0
- package/plug-api/lib/memory_cache.ts +21 -0
- package/plug-api/lib/native_fetch.ts +6 -0
- package/plug-api/lib/ref.ts +275 -0
- package/plug-api/lib/resolve.ts +90 -0
- package/plug-api/lib/tags.ts +15 -0
- package/plug-api/lib/transclusion.ts +122 -0
- package/plug-api/lib/tree.ts +232 -0
- package/plug-api/lib/yaml.ts +284 -0
- package/plug-api/syscall.ts +15 -0
- package/plug-api/syscalls/asset.ts +36 -0
- package/plug-api/syscalls/client_store.ts +33 -0
- package/plug-api/syscalls/code_widget.ts +8 -0
- package/plug-api/syscalls/config.ts +58 -0
- package/plug-api/syscalls/datastore.ts +96 -0
- package/plug-api/syscalls/editor.ts +517 -0
- package/plug-api/syscalls/event.ts +47 -0
- package/plug-api/syscalls/index.ts +77 -0
- package/plug-api/syscalls/jsonschema.ts +25 -0
- package/plug-api/syscalls/language.ts +23 -0
- package/plug-api/syscalls/lua.ts +20 -0
- package/plug-api/syscalls/markdown.ts +38 -0
- package/plug-api/syscalls/mq.ts +79 -0
- package/plug-api/syscalls/shell.ts +14 -0
- package/plug-api/syscalls/space.ts +212 -0
- package/plug-api/syscalls/sync.ts +28 -0
- package/plug-api/syscalls/system.ts +102 -0
- package/plug-api/syscalls/yaml.ts +28 -0
- package/plug-api/syscalls.ts +21 -0
- package/plug-api/system_mock.ts +89 -0
- package/plug-api/types/client.ts +116 -0
- package/plug-api/types/config.ts +22 -0
- package/plug-api/types/datastore.ts +28 -0
- package/plug-api/types/event.ts +27 -0
- package/plug-api/types/index.ts +56 -0
- package/plug-api/types/manifest.ts +98 -0
- package/plug-api/types/namespace.ts +6 -0
- package/plugs/builtin_plugs.ts +14 -0
|
@@ -0,0 +1,2478 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ASTCtx,
|
|
3
|
+
LuaBlock,
|
|
4
|
+
LuaExpression,
|
|
5
|
+
LuaLValue,
|
|
6
|
+
LuaStatement,
|
|
7
|
+
NumericType,
|
|
8
|
+
} from "./ast.ts";
|
|
9
|
+
import { LuaAttribute } from "./ast.ts";
|
|
10
|
+
import { evalPromiseValues } from "./util.ts";
|
|
11
|
+
import {
|
|
12
|
+
getMetatable,
|
|
13
|
+
type ILuaFunction,
|
|
14
|
+
type ILuaGettable,
|
|
15
|
+
isILuaFunction,
|
|
16
|
+
jsToLuaValue,
|
|
17
|
+
luaCall,
|
|
18
|
+
luaCloseFromMark,
|
|
19
|
+
luaEnsureCloseStack,
|
|
20
|
+
LuaEnv,
|
|
21
|
+
luaEquals,
|
|
22
|
+
LuaFunction,
|
|
23
|
+
luaGet,
|
|
24
|
+
luaIndexValue,
|
|
25
|
+
type LuaLValueContainer,
|
|
26
|
+
luaMarkToBeClosed,
|
|
27
|
+
LuaMultiRes,
|
|
28
|
+
LuaRuntimeError,
|
|
29
|
+
luaSet,
|
|
30
|
+
type LuaStackFrame,
|
|
31
|
+
LuaTable,
|
|
32
|
+
luaTruthy,
|
|
33
|
+
type LuaType,
|
|
34
|
+
luaTypeName,
|
|
35
|
+
luaTypeOf,
|
|
36
|
+
type LuaValue,
|
|
37
|
+
luaValueToJS,
|
|
38
|
+
singleResult,
|
|
39
|
+
} from "./runtime.ts";
|
|
40
|
+
import {
|
|
41
|
+
ArrayQueryCollection,
|
|
42
|
+
type LuaCollectionQuery,
|
|
43
|
+
} from "./query_collection.ts";
|
|
44
|
+
import {
|
|
45
|
+
coerceNumericPair,
|
|
46
|
+
coerceToNumber,
|
|
47
|
+
inferNumericType,
|
|
48
|
+
isNegativeZero,
|
|
49
|
+
isTaggedFloat,
|
|
50
|
+
luaStringCoercionError,
|
|
51
|
+
makeLuaFloat,
|
|
52
|
+
makeLuaZero,
|
|
53
|
+
normalizeArithmeticResult,
|
|
54
|
+
toInteger,
|
|
55
|
+
untagNumber,
|
|
56
|
+
} from "./numeric.ts";
|
|
57
|
+
import { isPromise, rpAll, rpThen } from "./rp.ts";
|
|
58
|
+
import {
|
|
59
|
+
asAssignment,
|
|
60
|
+
asBinary,
|
|
61
|
+
asBlock,
|
|
62
|
+
asFor,
|
|
63
|
+
asForIn,
|
|
64
|
+
asFunctionCall,
|
|
65
|
+
asFunctionCallStmt,
|
|
66
|
+
asFunctionDef,
|
|
67
|
+
asFunctionStmt,
|
|
68
|
+
asGoto,
|
|
69
|
+
asIf,
|
|
70
|
+
asLabel,
|
|
71
|
+
asLocal,
|
|
72
|
+
asLocalFunction,
|
|
73
|
+
asLValuePropertyAccess,
|
|
74
|
+
asLValueTableAccess,
|
|
75
|
+
asLValueVariable,
|
|
76
|
+
asParenthesized,
|
|
77
|
+
asPropertyAccess,
|
|
78
|
+
asQueryExpr,
|
|
79
|
+
asRepeat,
|
|
80
|
+
asReturn,
|
|
81
|
+
asTableAccess,
|
|
82
|
+
asTableConstructor,
|
|
83
|
+
asUnary,
|
|
84
|
+
asVariable,
|
|
85
|
+
asWhile,
|
|
86
|
+
} from "./ast_narrow.ts";
|
|
87
|
+
import { getBlockGotoMeta } from "./labels.ts";
|
|
88
|
+
|
|
89
|
+
const astNumberKindCache = new WeakMap<LuaExpression, NumericType>();
|
|
90
|
+
|
|
91
|
+
function astNumberKind(e: LuaExpression | undefined): NumericType | undefined {
|
|
92
|
+
if (!e) return undefined;
|
|
93
|
+
|
|
94
|
+
const cached = astNumberKindCache.get(e);
|
|
95
|
+
if (cached) return cached;
|
|
96
|
+
|
|
97
|
+
let unwrapped = e;
|
|
98
|
+
while (unwrapped.type === "Parenthesized") {
|
|
99
|
+
unwrapped = unwrapped.expression;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let result: NumericType | undefined;
|
|
103
|
+
|
|
104
|
+
if (unwrapped.type === "Unary" && unwrapped.operator === "-") {
|
|
105
|
+
result = astNumberKind(unwrapped.argument);
|
|
106
|
+
} else if (unwrapped.type === "Number") {
|
|
107
|
+
result = unwrapped.numericType === "int" ? "int" : "float";
|
|
108
|
+
} else if (unwrapped.type === "Binary") {
|
|
109
|
+
const op = unwrapped.operator;
|
|
110
|
+
const numericOp = op === "+" || op === "-" || op === "*" || op === "/" ||
|
|
111
|
+
op === "//" || op === "%" || op === "^";
|
|
112
|
+
|
|
113
|
+
if (numericOp) {
|
|
114
|
+
const lk = astNumberKind(unwrapped.left);
|
|
115
|
+
const rk = astNumberKind(unwrapped.right);
|
|
116
|
+
|
|
117
|
+
if (lk === "float" || rk === "float") {
|
|
118
|
+
result = "float";
|
|
119
|
+
} else if (lk === "int" && rk === "int") {
|
|
120
|
+
result = "int";
|
|
121
|
+
} else {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (result !== undefined) {
|
|
132
|
+
astNumberKindCache.set(e, result);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type GotoSignal = { ctrl: "goto"; target: string };
|
|
139
|
+
type ReturnSignal = { ctrl: "return"; values: LuaValue[] };
|
|
140
|
+
type BreakSignal = { ctrl: "break" };
|
|
141
|
+
type ControlSignal = GotoSignal | ReturnSignal | BreakSignal;
|
|
142
|
+
|
|
143
|
+
function isGotoSignal(v: any): v is GotoSignal {
|
|
144
|
+
return !!v && typeof v === "object" && v.ctrl === "goto";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isBreakSignal(v: any): v is BreakSignal {
|
|
148
|
+
return !!v && typeof v === "object" && v.ctrl === "break";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function consumeGotoInBlock(
|
|
152
|
+
res: any,
|
|
153
|
+
labels: Map<string, number>,
|
|
154
|
+
): number | any | undefined {
|
|
155
|
+
if (res === undefined) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
if (isGotoSignal(res)) {
|
|
159
|
+
const labelIdx = labels.get(res.target);
|
|
160
|
+
if (labelIdx !== undefined) {
|
|
161
|
+
return labelIdx + 1; // next statement
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return res;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function blockMetaOrThrow(
|
|
168
|
+
block: LuaBlock,
|
|
169
|
+
sf: LuaStackFrame,
|
|
170
|
+
): ReturnType<typeof getBlockGotoMeta> {
|
|
171
|
+
try {
|
|
172
|
+
return getBlockGotoMeta(block);
|
|
173
|
+
} catch (e: any) {
|
|
174
|
+
if (e && typeof e === "object" && "astCtx" in e) {
|
|
175
|
+
throw new LuaRuntimeError(e.message, sf.withCtx((e as any).astCtx));
|
|
176
|
+
}
|
|
177
|
+
throw e;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Queryable guard to avoid `(collection as any).query` usage
|
|
182
|
+
type Queryable = {
|
|
183
|
+
query: (
|
|
184
|
+
q: LuaCollectionQuery,
|
|
185
|
+
env: LuaEnv,
|
|
186
|
+
sf: LuaStackFrame,
|
|
187
|
+
) => Promise<any>;
|
|
188
|
+
};
|
|
189
|
+
function isQueryable(x: unknown): x is Queryable {
|
|
190
|
+
return !!x && typeof (x as any).query === "function";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function arithVerbFromOperator(op: string): string | null {
|
|
194
|
+
switch (op) {
|
|
195
|
+
case "+":
|
|
196
|
+
return "add";
|
|
197
|
+
case "-":
|
|
198
|
+
return "sub";
|
|
199
|
+
case "*":
|
|
200
|
+
return "mul";
|
|
201
|
+
case "/":
|
|
202
|
+
return "div";
|
|
203
|
+
case "//":
|
|
204
|
+
return "idiv";
|
|
205
|
+
case "%":
|
|
206
|
+
return "mod";
|
|
207
|
+
case "^":
|
|
208
|
+
return "pow";
|
|
209
|
+
default:
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isNumericBinaryOp(op: string): boolean {
|
|
215
|
+
return (
|
|
216
|
+
op === "+" ||
|
|
217
|
+
op === "-" ||
|
|
218
|
+
op === "*" ||
|
|
219
|
+
op === "/" ||
|
|
220
|
+
op === "//" ||
|
|
221
|
+
op === "%" ||
|
|
222
|
+
op === "^"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function arithCoercionErrorOrThrow(
|
|
227
|
+
op: string,
|
|
228
|
+
left: any,
|
|
229
|
+
right: any,
|
|
230
|
+
ctx: ASTCtx,
|
|
231
|
+
sf: LuaStackFrame,
|
|
232
|
+
e: any,
|
|
233
|
+
): never {
|
|
234
|
+
if (e === luaStringCoercionError) {
|
|
235
|
+
const mapped = maybeLuaArithStringError(op, left, right, ctx, sf);
|
|
236
|
+
if (mapped) {
|
|
237
|
+
throw mapped;
|
|
238
|
+
}
|
|
239
|
+
throw new LuaRuntimeError(
|
|
240
|
+
"attempt to perform arithmetic on a string value",
|
|
241
|
+
sf.withCtx(ctx),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const mapped = maybeLuaArithStringError(op, left, right, ctx, sf);
|
|
246
|
+
if (mapped) {
|
|
247
|
+
throw mapped;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
throw e;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function luaOp(
|
|
254
|
+
op: string,
|
|
255
|
+
left: any,
|
|
256
|
+
right: any,
|
|
257
|
+
leftType: NumericType | undefined,
|
|
258
|
+
rightType: NumericType | undefined,
|
|
259
|
+
ctx: ASTCtx,
|
|
260
|
+
sf: LuaStackFrame,
|
|
261
|
+
): any {
|
|
262
|
+
switch (op) {
|
|
263
|
+
case "+":
|
|
264
|
+
case "-":
|
|
265
|
+
case "*":
|
|
266
|
+
case "/":
|
|
267
|
+
case "^": {
|
|
268
|
+
const ar = numericArith[op as NumericArithOp];
|
|
269
|
+
try {
|
|
270
|
+
const { left: l, right: r, resultType } = coerceNumericPair(
|
|
271
|
+
left,
|
|
272
|
+
right,
|
|
273
|
+
leftType,
|
|
274
|
+
rightType,
|
|
275
|
+
op,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
let result = ar.f(l, r);
|
|
279
|
+
|
|
280
|
+
if (
|
|
281
|
+
ar.special === "sub" &&
|
|
282
|
+
result === 0 &&
|
|
283
|
+
isNegativeZero(result) &&
|
|
284
|
+
resultType === "float"
|
|
285
|
+
) {
|
|
286
|
+
const rhsIsIntZero = r === 0 && rightType === "int";
|
|
287
|
+
if (rhsIsIntZero) {
|
|
288
|
+
result = 0;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const normalized = normalizeArithmeticResult(result, resultType);
|
|
293
|
+
|
|
294
|
+
// Operators `/` and `^` produce float, wrap only if needed.
|
|
295
|
+
if (op === "/" || op === "^") {
|
|
296
|
+
if (normalized === 0) {
|
|
297
|
+
return makeLuaZero(normalized, "float");
|
|
298
|
+
}
|
|
299
|
+
if (!Number.isFinite(normalized)) {
|
|
300
|
+
return normalized;
|
|
301
|
+
}
|
|
302
|
+
if (!Number.isInteger(normalized)) {
|
|
303
|
+
return normalized;
|
|
304
|
+
}
|
|
305
|
+
return makeLuaFloat(normalized);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (normalized === 0) {
|
|
309
|
+
return makeLuaZero(normalized, resultType);
|
|
310
|
+
}
|
|
311
|
+
if (resultType === "float" && Number.isInteger(normalized)) {
|
|
312
|
+
return makeLuaFloat(normalized);
|
|
313
|
+
}
|
|
314
|
+
return normalized;
|
|
315
|
+
} catch (e: any) {
|
|
316
|
+
const meta = evalMetamethod(left, right, ar.metaMethod, ctx, sf);
|
|
317
|
+
if (meta !== undefined) {
|
|
318
|
+
return meta;
|
|
319
|
+
}
|
|
320
|
+
return arithCoercionErrorOrThrow(op, left, right, ctx, sf, e);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
case "..": {
|
|
324
|
+
try {
|
|
325
|
+
const coerce = (v: any): string => {
|
|
326
|
+
if (v === null || v === undefined) {
|
|
327
|
+
throw new LuaRuntimeError(
|
|
328
|
+
"attempt to concatenate a nil value",
|
|
329
|
+
sf.withCtx(ctx),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
if (typeof v === "string") {
|
|
333
|
+
return v as string;
|
|
334
|
+
}
|
|
335
|
+
if (typeof v === "number") {
|
|
336
|
+
return String(v);
|
|
337
|
+
}
|
|
338
|
+
if (isTaggedFloat(v)) {
|
|
339
|
+
return String(v.value);
|
|
340
|
+
}
|
|
341
|
+
const t = luaTypeName(v);
|
|
342
|
+
throw new LuaRuntimeError(
|
|
343
|
+
`attempt to concatenate a ${t} value`,
|
|
344
|
+
sf.withCtx(ctx),
|
|
345
|
+
);
|
|
346
|
+
};
|
|
347
|
+
return coerce(left) + coerce(right);
|
|
348
|
+
} catch (e: any) {
|
|
349
|
+
const meta = evalMetamethod(left, right, "__concat", ctx, sf);
|
|
350
|
+
if (meta !== undefined) {
|
|
351
|
+
return meta;
|
|
352
|
+
}
|
|
353
|
+
throw e;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
case "==": {
|
|
357
|
+
if (luaEquals(left, right)) return true;
|
|
358
|
+
return luaEqWithMetamethod(left, right, ctx, sf);
|
|
359
|
+
}
|
|
360
|
+
case "~=":
|
|
361
|
+
case "!=": {
|
|
362
|
+
if (luaEquals(left, right)) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
return !luaEqWithMetamethod(left, right, ctx, sf);
|
|
366
|
+
}
|
|
367
|
+
case "<": {
|
|
368
|
+
return luaRelWithMetamethod("<", left, right, ctx, sf);
|
|
369
|
+
}
|
|
370
|
+
case "<=": {
|
|
371
|
+
return luaRelWithMetamethod("<=", left, right, ctx, sf);
|
|
372
|
+
}
|
|
373
|
+
// Lua: `a>b` is `b<a`, `a>=b` is `b<=a`
|
|
374
|
+
case ">": {
|
|
375
|
+
return luaRelWithMetamethod("<", right, left, ctx, sf);
|
|
376
|
+
}
|
|
377
|
+
case ">=": {
|
|
378
|
+
return luaRelWithMetamethod("<=", right, left, ctx, sf);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Remaining operators: //, %, bitwise
|
|
383
|
+
const handler = operatorsMetaMethods[op];
|
|
384
|
+
if (!handler) {
|
|
385
|
+
throw new LuaRuntimeError(`Unknown operator ${op}`, sf.withCtx(ctx));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
return handler.nativeImplementation(
|
|
390
|
+
left,
|
|
391
|
+
right,
|
|
392
|
+
leftType,
|
|
393
|
+
rightType,
|
|
394
|
+
ctx,
|
|
395
|
+
sf,
|
|
396
|
+
);
|
|
397
|
+
} catch (e: any) {
|
|
398
|
+
if (handler.metaMethod) {
|
|
399
|
+
const meta = evalMetamethod(left, right, handler.metaMethod, ctx, sf);
|
|
400
|
+
if (meta !== undefined) {
|
|
401
|
+
return meta;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return arithCoercionErrorOrThrow(op, left, right, ctx, sf, e);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
type NumericArithOp = "+" | "-" | "*" | "/" | "^";
|
|
409
|
+
|
|
410
|
+
const numericArith: Record<NumericArithOp, {
|
|
411
|
+
metaMethod: "__add" | "__sub" | "__mul" | "__div" | "__pow";
|
|
412
|
+
f: (l: number, r: number) => number;
|
|
413
|
+
special?: "sub";
|
|
414
|
+
}> = {
|
|
415
|
+
"+": { metaMethod: "__add", f: (l, r) => l + r },
|
|
416
|
+
"-": { metaMethod: "__sub", f: (l, r) => l - r, special: "sub" },
|
|
417
|
+
"*": { metaMethod: "__mul", f: (l, r) => l * r },
|
|
418
|
+
"/": { metaMethod: "__div", f: (l, r) => l / r },
|
|
419
|
+
"^": { metaMethod: "__pow", f: (l, r) => l ** r },
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
function maybeLuaArithStringError(
|
|
423
|
+
op: string,
|
|
424
|
+
a: any,
|
|
425
|
+
b: any,
|
|
426
|
+
ctx: ASTCtx,
|
|
427
|
+
sf: LuaStackFrame,
|
|
428
|
+
): LuaRuntimeError | null {
|
|
429
|
+
const verb = arithVerbFromOperator(op);
|
|
430
|
+
if (!verb) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const ta = luaTypeName(a);
|
|
435
|
+
const tb = luaTypeName(b);
|
|
436
|
+
|
|
437
|
+
if (ta === "string" || tb === "string") {
|
|
438
|
+
return new LuaRuntimeError(
|
|
439
|
+
`attempt to ${verb} a '${ta}' with a '${tb}'`,
|
|
440
|
+
sf.withCtx(ctx),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function luaFloorDiv(
|
|
448
|
+
a: unknown,
|
|
449
|
+
b: unknown,
|
|
450
|
+
leftType: NumericType | undefined,
|
|
451
|
+
rightType: NumericType | undefined,
|
|
452
|
+
ctx: ASTCtx,
|
|
453
|
+
sf: LuaStackFrame,
|
|
454
|
+
): any {
|
|
455
|
+
const { left, right, resultType } = coerceNumericPair(
|
|
456
|
+
a,
|
|
457
|
+
b,
|
|
458
|
+
leftType,
|
|
459
|
+
rightType,
|
|
460
|
+
"//",
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
if (resultType === "int" && right === 0) {
|
|
464
|
+
throw new LuaRuntimeError(
|
|
465
|
+
`attempt to divide by zero`,
|
|
466
|
+
sf.withCtx(ctx),
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const result = Math.floor(left / right);
|
|
471
|
+
const normalized = normalizeArithmeticResult(result, resultType);
|
|
472
|
+
if (normalized === 0) {
|
|
473
|
+
return makeLuaZero(normalized, resultType);
|
|
474
|
+
}
|
|
475
|
+
if (resultType === "float" && Number.isInteger(normalized)) {
|
|
476
|
+
return makeLuaFloat(normalized);
|
|
477
|
+
}
|
|
478
|
+
return normalized;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function luaMod(
|
|
482
|
+
a: unknown,
|
|
483
|
+
b: unknown,
|
|
484
|
+
leftType: NumericType | undefined,
|
|
485
|
+
rightType: NumericType | undefined,
|
|
486
|
+
ctx: ASTCtx,
|
|
487
|
+
sf: LuaStackFrame,
|
|
488
|
+
): any {
|
|
489
|
+
const { left, right, resultType } = coerceNumericPair(
|
|
490
|
+
a,
|
|
491
|
+
b,
|
|
492
|
+
leftType,
|
|
493
|
+
rightType,
|
|
494
|
+
"%",
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
if (resultType === "int" && right === 0) {
|
|
498
|
+
throw new LuaRuntimeError(
|
|
499
|
+
`attempt to perform 'n%0'`,
|
|
500
|
+
sf.withCtx(ctx),
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const q = Math.floor(left / right);
|
|
505
|
+
const result = left - q * right;
|
|
506
|
+
|
|
507
|
+
// Preserve -0.0 from left operand in float mode
|
|
508
|
+
if (result === 0 && resultType === "float" && isNegativeZero(left)) {
|
|
509
|
+
return makeLuaZero(-0, "float");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const normalized = normalizeArithmeticResult(result, resultType);
|
|
513
|
+
if (normalized === 0) {
|
|
514
|
+
return makeLuaZero(normalized, resultType);
|
|
515
|
+
}
|
|
516
|
+
if (resultType === "float" && Number.isInteger(normalized)) {
|
|
517
|
+
return makeLuaFloat(normalized);
|
|
518
|
+
}
|
|
519
|
+
return normalized;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function luaUnaryMinus(
|
|
523
|
+
v: number,
|
|
524
|
+
numType: NumericType | undefined,
|
|
525
|
+
): number {
|
|
526
|
+
const vType = numType ?? inferNumericType(v);
|
|
527
|
+
|
|
528
|
+
if (v === 0 && vType === "int") {
|
|
529
|
+
return 0;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (v === 0 && vType === "float") {
|
|
533
|
+
return isNegativeZero(v) ? 0 : -0;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return -v;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const operatorsMetaMethods: Record<string, {
|
|
540
|
+
metaMethod?: string;
|
|
541
|
+
nativeImplementation: (
|
|
542
|
+
a: LuaValue,
|
|
543
|
+
b: LuaValue,
|
|
544
|
+
leftType: NumericType | undefined,
|
|
545
|
+
rightType: NumericType | undefined,
|
|
546
|
+
ctx: ASTCtx,
|
|
547
|
+
sf: LuaStackFrame,
|
|
548
|
+
) => LuaValue;
|
|
549
|
+
}> = {
|
|
550
|
+
"//": {
|
|
551
|
+
metaMethod: "__idiv",
|
|
552
|
+
nativeImplementation: (a, b, lt, rt, ctx, sf) =>
|
|
553
|
+
luaFloorDiv(a, b, lt, rt, ctx, sf),
|
|
554
|
+
},
|
|
555
|
+
"%": {
|
|
556
|
+
metaMethod: "__mod",
|
|
557
|
+
nativeImplementation: (a, b, lt, rt, ctx, sf) =>
|
|
558
|
+
luaMod(a, b, lt, rt, ctx, sf),
|
|
559
|
+
},
|
|
560
|
+
"&": {
|
|
561
|
+
metaMethod: "__band",
|
|
562
|
+
nativeImplementation: (a, b, _lt, _rt, ctx, sf) => {
|
|
563
|
+
const aInt = toInteger(a);
|
|
564
|
+
const bInt = toInteger(b);
|
|
565
|
+
if (aInt === null) throw createBitwiseError(a, ctx, sf);
|
|
566
|
+
if (bInt === null) throw createBitwiseError(b, ctx, sf);
|
|
567
|
+
return aInt & bInt;
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
"|": {
|
|
571
|
+
metaMethod: "__bor",
|
|
572
|
+
nativeImplementation: (a, b, _lt, _rt, ctx, sf) => {
|
|
573
|
+
const aInt = toInteger(a);
|
|
574
|
+
const bInt = toInteger(b);
|
|
575
|
+
if (aInt === null) throw createBitwiseError(a, ctx, sf);
|
|
576
|
+
if (bInt === null) throw createBitwiseError(b, ctx, sf);
|
|
577
|
+
return aInt | bInt;
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
"~": {
|
|
581
|
+
metaMethod: "__bxor",
|
|
582
|
+
nativeImplementation: (a, b, _lt, _rt, ctx, sf) => {
|
|
583
|
+
const aInt = toInteger(a);
|
|
584
|
+
const bInt = toInteger(b);
|
|
585
|
+
if (aInt === null) throw createBitwiseError(a, ctx, sf);
|
|
586
|
+
if (bInt === null) throw createBitwiseError(b, ctx, sf);
|
|
587
|
+
return aInt ^ bInt;
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
"<<": {
|
|
591
|
+
metaMethod: "__shl",
|
|
592
|
+
nativeImplementation: (a, b, _lt, _rt, ctx, sf) => {
|
|
593
|
+
const aInt = toInteger(a);
|
|
594
|
+
const bInt = toInteger(b);
|
|
595
|
+
if (aInt === null) throw createBitwiseError(a, ctx, sf);
|
|
596
|
+
if (bInt === null) throw createBitwiseError(b, ctx, sf);
|
|
597
|
+
return aInt << bInt;
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
">>": {
|
|
601
|
+
metaMethod: "__shr",
|
|
602
|
+
nativeImplementation: (a, b, _lt, _rt, ctx, sf) => {
|
|
603
|
+
const aInt = toInteger(a);
|
|
604
|
+
const bInt = toInteger(b);
|
|
605
|
+
if (aInt === null) throw createBitwiseError(a, ctx, sf);
|
|
606
|
+
if (bInt === null) throw createBitwiseError(b, ctx, sf);
|
|
607
|
+
return aInt >> bInt;
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
export function evalExpression(
|
|
613
|
+
e: LuaExpression,
|
|
614
|
+
env: LuaEnv,
|
|
615
|
+
sf: LuaStackFrame,
|
|
616
|
+
): Promise<LuaValue> | LuaValue {
|
|
617
|
+
try {
|
|
618
|
+
switch (e.type) {
|
|
619
|
+
case "String": {
|
|
620
|
+
return e.value;
|
|
621
|
+
}
|
|
622
|
+
case "Number": {
|
|
623
|
+
if (e.value === 0) {
|
|
624
|
+
return makeLuaZero(e.value, e.numericType);
|
|
625
|
+
}
|
|
626
|
+
if (e.numericType === "float" && Number.isInteger(e.value)) {
|
|
627
|
+
return makeLuaFloat(e.value);
|
|
628
|
+
}
|
|
629
|
+
return e.value;
|
|
630
|
+
}
|
|
631
|
+
case "Boolean": {
|
|
632
|
+
return e.value;
|
|
633
|
+
}
|
|
634
|
+
case "Nil": {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
case "Binary": {
|
|
638
|
+
const b = asBinary(e);
|
|
639
|
+
if (b.operator === "or") {
|
|
640
|
+
return evalLogical("or", b.left, b.right, env, sf);
|
|
641
|
+
}
|
|
642
|
+
if (b.operator === "and") {
|
|
643
|
+
return evalLogical("and", b.left, b.right, env, sf);
|
|
644
|
+
}
|
|
645
|
+
return evalBinaryWithLR(
|
|
646
|
+
b.operator,
|
|
647
|
+
b.left,
|
|
648
|
+
b.right,
|
|
649
|
+
b.ctx,
|
|
650
|
+
env,
|
|
651
|
+
sf,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
case "Unary": {
|
|
655
|
+
const u = asUnary(e);
|
|
656
|
+
|
|
657
|
+
// Fast path: negation of numeric literal
|
|
658
|
+
if (u.operator === "-" && u.argument.type === "Number") {
|
|
659
|
+
const num = u.argument;
|
|
660
|
+
if (num.value === 0) {
|
|
661
|
+
const z = num.numericType === "int" ? 0 : -0;
|
|
662
|
+
return makeLuaZero(z, num.numericType);
|
|
663
|
+
}
|
|
664
|
+
if (num.numericType === "float" && Number.isInteger(num.value)) {
|
|
665
|
+
return makeLuaFloat(-num.value);
|
|
666
|
+
}
|
|
667
|
+
return -num.value;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (u.operator === "-") {
|
|
671
|
+
const tv = evalExprWithNumericType(u.argument, env, sf, true);
|
|
672
|
+
|
|
673
|
+
const applyTyped = (typed: TypedValue) => {
|
|
674
|
+
const arg = singleResult(typed.value);
|
|
675
|
+
|
|
676
|
+
return unaryWithMeta(
|
|
677
|
+
arg,
|
|
678
|
+
"__unm",
|
|
679
|
+
u.ctx,
|
|
680
|
+
sf,
|
|
681
|
+
() => {
|
|
682
|
+
// Numeric-string coercion for unary minus
|
|
683
|
+
if (typeof arg === "string") {
|
|
684
|
+
const n = coerceToNumber(arg);
|
|
685
|
+
if (n === null) {
|
|
686
|
+
throw new LuaRuntimeError(
|
|
687
|
+
"attempt to unm a 'string' with a 'string'",
|
|
688
|
+
sf.withCtx(u.ctx),
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if (n === 0) {
|
|
692
|
+
return 0;
|
|
693
|
+
}
|
|
694
|
+
return -n;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const plain = untagNumber(arg);
|
|
698
|
+
if (typeof plain !== "number") {
|
|
699
|
+
throw new LuaRuntimeError(
|
|
700
|
+
"attempt to perform arithmetic on a table value",
|
|
701
|
+
sf.withCtx(u.ctx),
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const argType = isTaggedFloat(arg)
|
|
706
|
+
? "float"
|
|
707
|
+
: astNumberKind(u.argument);
|
|
708
|
+
|
|
709
|
+
const out = luaUnaryMinus(plain, argType);
|
|
710
|
+
|
|
711
|
+
// If the operand is a float-tagged boxed number, unary
|
|
712
|
+
// minus must keep the result float-typed.
|
|
713
|
+
if (isTaggedFloat(arg)) {
|
|
714
|
+
if (out === 0) {
|
|
715
|
+
return makeLuaZero(out, "float");
|
|
716
|
+
}
|
|
717
|
+
return makeLuaFloat(out);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Preserve numeric kind for zero results
|
|
721
|
+
if (out === 0) {
|
|
722
|
+
const outType = argType ?? inferNumericType(plain);
|
|
723
|
+
return makeLuaZero(out, outType);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return out;
|
|
727
|
+
},
|
|
728
|
+
);
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
return rpThen(tv as any, applyTyped);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const value = evalExpression(u.argument, env, sf);
|
|
735
|
+
|
|
736
|
+
const applyUnary = (value: LuaValue) => {
|
|
737
|
+
switch (u.operator) {
|
|
738
|
+
case "not": {
|
|
739
|
+
return !luaTruthy(value);
|
|
740
|
+
}
|
|
741
|
+
case "~": {
|
|
742
|
+
const arg = singleResult(value);
|
|
743
|
+
return unaryWithMeta(
|
|
744
|
+
arg,
|
|
745
|
+
"__bnot",
|
|
746
|
+
u.ctx,
|
|
747
|
+
sf,
|
|
748
|
+
() => {
|
|
749
|
+
const intVal = toInteger(arg);
|
|
750
|
+
if (intVal === null) {
|
|
751
|
+
if (typeof arg === "string") {
|
|
752
|
+
throw new LuaRuntimeError(
|
|
753
|
+
`attempt to perform bitwise operation on a string value (constant '${arg}')`,
|
|
754
|
+
sf.withCtx(u.ctx),
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
const t = luaTypeName(arg);
|
|
758
|
+
if (t === "number") {
|
|
759
|
+
throw new LuaRuntimeError(
|
|
760
|
+
`number has no integer representation`,
|
|
761
|
+
sf.withCtx(u.ctx),
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
throw new LuaRuntimeError(
|
|
765
|
+
`attempt to perform bitwise operation on a ${t} value`,
|
|
766
|
+
sf.withCtx(u.ctx),
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
return ~intVal;
|
|
770
|
+
},
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
case "#": {
|
|
774
|
+
return luaLengthOp(singleResult(value), u.ctx, sf);
|
|
775
|
+
}
|
|
776
|
+
default: {
|
|
777
|
+
throw new LuaRuntimeError(
|
|
778
|
+
`Unknown unary operator ${u.operator}`,
|
|
779
|
+
sf.withCtx(u.ctx),
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
return rpThen(value, applyUnary);
|
|
786
|
+
}
|
|
787
|
+
case "Variable":
|
|
788
|
+
case "FunctionCall":
|
|
789
|
+
case "TableAccess":
|
|
790
|
+
case "PropertyAccess": {
|
|
791
|
+
return evalPrefixExpression(e, env, sf);
|
|
792
|
+
}
|
|
793
|
+
case "TableConstructor": {
|
|
794
|
+
const tc = asTableConstructor(e);
|
|
795
|
+
return Promise.resolve().then(async () => {
|
|
796
|
+
const table = new LuaTable();
|
|
797
|
+
// Expression fields assign consecutive integer keys starting
|
|
798
|
+
// at 1 and advance even when the value is `nil`.
|
|
799
|
+
let nextArrayIndex = 1;
|
|
800
|
+
for (const field of tc.fields) {
|
|
801
|
+
switch (field.type) {
|
|
802
|
+
case "PropField": {
|
|
803
|
+
const value = await evalExpression(field.value, env, sf);
|
|
804
|
+
table.set(field.key, singleResult(value), sf);
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
case "DynamicField": {
|
|
808
|
+
const key = await evalExpression(field.key, env, sf);
|
|
809
|
+
const value = await evalExpression(field.value, env, sf);
|
|
810
|
+
table.set(singleResult(key), singleResult(value), sf);
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
case "ExpressionField": {
|
|
814
|
+
const value = await evalExpression(field.value, env, sf);
|
|
815
|
+
|
|
816
|
+
if (value instanceof LuaMultiRes) {
|
|
817
|
+
const flat = value.flatten();
|
|
818
|
+
for (let i = 0; i < flat.values.length; i++) {
|
|
819
|
+
table.rawSetArrayIndex(nextArrayIndex, flat.values[i]);
|
|
820
|
+
nextArrayIndex++;
|
|
821
|
+
}
|
|
822
|
+
} else {
|
|
823
|
+
table.rawSetArrayIndex(nextArrayIndex, singleResult(value));
|
|
824
|
+
nextArrayIndex++;
|
|
825
|
+
}
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return table;
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
case "FunctionDefinition": {
|
|
834
|
+
const fd = asFunctionDef(e);
|
|
835
|
+
return new LuaFunction(fd.body, env);
|
|
836
|
+
}
|
|
837
|
+
case "Query": {
|
|
838
|
+
const q = asQueryExpr(e);
|
|
839
|
+
const findFromClause = q.clauses.find((c) => c.type === "From");
|
|
840
|
+
if (!findFromClause) {
|
|
841
|
+
throw new LuaRuntimeError(
|
|
842
|
+
"No from clause found",
|
|
843
|
+
sf.withCtx(q.ctx),
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
const objectVariable = findFromClause.name;
|
|
847
|
+
const objectExpression = findFromClause.expression;
|
|
848
|
+
return Promise.resolve(evalExpression(objectExpression, env, sf)).then(
|
|
849
|
+
async (collection: LuaValue) => {
|
|
850
|
+
if (!collection) {
|
|
851
|
+
throw new LuaRuntimeError(
|
|
852
|
+
"Collection is nil",
|
|
853
|
+
sf.withCtx(q.ctx),
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
if (collection instanceof LuaTable && collection.empty()) {
|
|
857
|
+
// Make sure we're converting an empty result to an array to "query"
|
|
858
|
+
collection = [];
|
|
859
|
+
} else {
|
|
860
|
+
collection = luaValueToJS(collection, sf);
|
|
861
|
+
}
|
|
862
|
+
// Check if collection is a queryable collection
|
|
863
|
+
if (!isQueryable(collection)) {
|
|
864
|
+
if (!Array.isArray(collection)) {
|
|
865
|
+
throw new LuaRuntimeError(
|
|
866
|
+
"Collection does not support query",
|
|
867
|
+
sf.withCtx(q.ctx),
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
collection = new ArrayQueryCollection(collection);
|
|
871
|
+
}
|
|
872
|
+
// Build up query object
|
|
873
|
+
const query: LuaCollectionQuery = {
|
|
874
|
+
objectVariable,
|
|
875
|
+
distinct: true,
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// Map clauses to query parameters
|
|
879
|
+
for (const clause of q.clauses) {
|
|
880
|
+
switch (clause.type) {
|
|
881
|
+
case "Where": {
|
|
882
|
+
query.where = clause.expression;
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
case "OrderBy": {
|
|
886
|
+
query.orderBy = clause.orderBy.map((o) => ({
|
|
887
|
+
expr: o.expression,
|
|
888
|
+
desc: o.direction === "desc",
|
|
889
|
+
}));
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
case "Select": {
|
|
893
|
+
query.select = clause.expression;
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
case "Limit": {
|
|
897
|
+
const limitVal = await evalExpression(clause.limit, env, sf);
|
|
898
|
+
query.limit = Number(limitVal);
|
|
899
|
+
if (clause.offset) {
|
|
900
|
+
const offsetVal = await evalExpression(
|
|
901
|
+
clause.offset,
|
|
902
|
+
env,
|
|
903
|
+
sf,
|
|
904
|
+
);
|
|
905
|
+
query.offset = Number(offsetVal);
|
|
906
|
+
}
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
return (collection as Queryable).query(query, env, sf).then(
|
|
913
|
+
jsToLuaValue,
|
|
914
|
+
);
|
|
915
|
+
},
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
default:
|
|
919
|
+
throw new LuaRuntimeError(
|
|
920
|
+
`Unknown expression type ${e.type}`,
|
|
921
|
+
sf.withCtx(e.ctx),
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
} catch (err: any) {
|
|
925
|
+
// Repackage any non Lua-specific exceptions with some position information
|
|
926
|
+
if (!err.constructor.name.startsWith("Lua")) {
|
|
927
|
+
throw new LuaRuntimeError(err.message, sf.withCtx(e.ctx), err);
|
|
928
|
+
} else {
|
|
929
|
+
throw err;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function evalPrefixExpression(
|
|
935
|
+
e: LuaExpression,
|
|
936
|
+
env: LuaEnv,
|
|
937
|
+
sf: LuaStackFrame,
|
|
938
|
+
): Promise<LuaValue> | LuaValue {
|
|
939
|
+
switch (e.type) {
|
|
940
|
+
case "Variable": {
|
|
941
|
+
const v = asVariable(e);
|
|
942
|
+
const value = env.get(v.name);
|
|
943
|
+
if (value === undefined) {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
return value;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
case "Parenthesized": {
|
|
950
|
+
const p = asParenthesized(e);
|
|
951
|
+
return evalExpression(p.expression, env, sf);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// <<expr>>[<<expr>>]
|
|
955
|
+
case "TableAccess": {
|
|
956
|
+
const ta = asTableAccess(e);
|
|
957
|
+
// Sync-first: evaluate object and key without allocating Promise when both are sync.
|
|
958
|
+
const objV = evalPrefixExpression(ta.object, env, sf);
|
|
959
|
+
const keyV = evalExpression(ta.key, env, sf);
|
|
960
|
+
|
|
961
|
+
if (!isPromise(objV) && !isPromise(keyV)) {
|
|
962
|
+
const table = singleResult(objV);
|
|
963
|
+
const key = singleResult(keyV);
|
|
964
|
+
return luaGet(table, key, ta.ctx, sf);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
return rpThen(
|
|
968
|
+
objV,
|
|
969
|
+
(obj) =>
|
|
970
|
+
rpThen(
|
|
971
|
+
keyV,
|
|
972
|
+
(key) => luaGet(singleResult(obj), singleResult(key), ta.ctx, sf),
|
|
973
|
+
),
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// <expr>.property
|
|
978
|
+
case "PropertyAccess": {
|
|
979
|
+
const pa = asPropertyAccess(e);
|
|
980
|
+
// Sync-first: evaluate object; avoid Promise when object is sync.
|
|
981
|
+
const objV = evalPrefixExpression(pa.object, env, sf);
|
|
982
|
+
if (!isPromise(objV)) {
|
|
983
|
+
return luaGet(objV, pa.property, pa.ctx, sf);
|
|
984
|
+
}
|
|
985
|
+
return rpThen(objV, (obj) => luaGet(obj, pa.property, pa.ctx, sf));
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
case "FunctionCall": {
|
|
989
|
+
const fc = asFunctionCall(e);
|
|
990
|
+
const prefixValue = evalPrefixExpression(fc.prefix, env, sf);
|
|
991
|
+
if (prefixValue === null || prefixValue === undefined) {
|
|
992
|
+
const nilMsg = fc.prefix.type === "Variable"
|
|
993
|
+
? `attempt to call a nil value (global '${
|
|
994
|
+
asVariable(fc.prefix).name
|
|
995
|
+
}')`
|
|
996
|
+
: `attempt to call a nil value`;
|
|
997
|
+
throw new LuaRuntimeError(
|
|
998
|
+
nilMsg,
|
|
999
|
+
sf.withCtx(fc.prefix.ctx),
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
let selfArgs: LuaValue[] = [];
|
|
1004
|
+
|
|
1005
|
+
const handleFunctionCall = (
|
|
1006
|
+
calleeVal: LuaValue,
|
|
1007
|
+
): LuaValue | Promise<LuaValue> => {
|
|
1008
|
+
// Normal argument handling for hello:there(a, b, c) type calls
|
|
1009
|
+
if (fc.name) {
|
|
1010
|
+
selfArgs = [calleeVal];
|
|
1011
|
+
calleeVal = luaIndexValue(calleeVal, fc.name, sf);
|
|
1012
|
+
|
|
1013
|
+
if (isPromise(calleeVal)) {
|
|
1014
|
+
return (calleeVal as Promise<any>).then(handleFunctionCall);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const argsVal = evalExpressions(fc.args, env, sf);
|
|
1019
|
+
|
|
1020
|
+
const thenCall = (args: LuaValue[]) =>
|
|
1021
|
+
luaCall(calleeVal, [...selfArgs, ...args], fc.ctx, sf);
|
|
1022
|
+
|
|
1023
|
+
return rpThen(argsVal, thenCall);
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
return rpThen(prefixValue, handleFunctionCall);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
default: {
|
|
1030
|
+
throw new LuaRuntimeError(
|
|
1031
|
+
`Unknown prefix expression type ${e.type}`,
|
|
1032
|
+
sf.withCtx(e.ctx),
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Helper functions to reduce duplication
|
|
1039
|
+
function evalMetamethod(
|
|
1040
|
+
left: any,
|
|
1041
|
+
right: any,
|
|
1042
|
+
metaMethod: string,
|
|
1043
|
+
ctx: ASTCtx,
|
|
1044
|
+
sf: LuaStackFrame,
|
|
1045
|
+
): LuaValue | undefined {
|
|
1046
|
+
const leftMetatable = getMetatable(left, sf);
|
|
1047
|
+
if (leftMetatable) {
|
|
1048
|
+
const fn = leftMetatable.rawGet(metaMethod);
|
|
1049
|
+
if (!(fn === undefined || fn === null)) {
|
|
1050
|
+
return luaCall(fn, [left, right], ctx, sf);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
const rightMetatable = getMetatable(right, sf);
|
|
1055
|
+
if (rightMetatable) {
|
|
1056
|
+
const fn = rightMetatable.rawGet(metaMethod);
|
|
1057
|
+
if (!(fn === undefined || fn === null)) {
|
|
1058
|
+
return luaCall(fn, [left, right], ctx, sf);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// Unary metamethod lookup and call
|
|
1064
|
+
function evalUnaryMetamethod(
|
|
1065
|
+
value: any,
|
|
1066
|
+
metaMethod: "__unm" | "__bnot",
|
|
1067
|
+
ctx: ASTCtx,
|
|
1068
|
+
sf: LuaStackFrame,
|
|
1069
|
+
): LuaValue | Promise<LuaValue> | undefined {
|
|
1070
|
+
const mt = getMetatable(value, sf);
|
|
1071
|
+
if (!mt) {
|
|
1072
|
+
return undefined;
|
|
1073
|
+
}
|
|
1074
|
+
const fn = mt.rawGet(metaMethod);
|
|
1075
|
+
if (fn === undefined || fn === null) {
|
|
1076
|
+
return undefined;
|
|
1077
|
+
}
|
|
1078
|
+
return luaCall(fn, [value], ctx, sf);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// Unary metamethod handling (with fallback)
|
|
1082
|
+
function unaryWithMeta(
|
|
1083
|
+
arg: any,
|
|
1084
|
+
meta: "__unm" | "__bnot",
|
|
1085
|
+
ctx: ASTCtx,
|
|
1086
|
+
sf: LuaStackFrame,
|
|
1087
|
+
fallback: () => any,
|
|
1088
|
+
): any {
|
|
1089
|
+
const mm = evalUnaryMetamethod(arg, meta, ctx, sf);
|
|
1090
|
+
|
|
1091
|
+
if (mm !== undefined) {
|
|
1092
|
+
return isPromise(mm)
|
|
1093
|
+
? (mm as Promise<any>).then(singleResult)
|
|
1094
|
+
: singleResult(mm);
|
|
1095
|
+
}
|
|
1096
|
+
return fallback();
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Logical short-circuit evaluation
|
|
1100
|
+
function evalLogical(
|
|
1101
|
+
op: "and" | "or",
|
|
1102
|
+
leftExpr: LuaExpression,
|
|
1103
|
+
rightExpr: LuaExpression,
|
|
1104
|
+
env: LuaEnv,
|
|
1105
|
+
sf: LuaStackFrame,
|
|
1106
|
+
): any {
|
|
1107
|
+
const left = evalExpression(leftExpr, env, sf);
|
|
1108
|
+
|
|
1109
|
+
const decide = (lv: any) => {
|
|
1110
|
+
if (op === "or") {
|
|
1111
|
+
if (luaTruthy(lv)) {
|
|
1112
|
+
return singleResult(lv);
|
|
1113
|
+
}
|
|
1114
|
+
const rv = evalExpression(rightExpr, env, sf);
|
|
1115
|
+
return isPromise(rv)
|
|
1116
|
+
? (rv as Promise<any>).then(singleResult)
|
|
1117
|
+
: singleResult(rv);
|
|
1118
|
+
}
|
|
1119
|
+
if (!luaTruthy(lv)) {
|
|
1120
|
+
return singleResult(lv);
|
|
1121
|
+
}
|
|
1122
|
+
const rv = evalExpression(rightExpr, env, sf);
|
|
1123
|
+
return isPromise(rv)
|
|
1124
|
+
? (rv as Promise<any>).then(singleResult)
|
|
1125
|
+
: singleResult(rv);
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
if (isPromise(left)) {
|
|
1129
|
+
return (left as Promise<any>).then(decide);
|
|
1130
|
+
}
|
|
1131
|
+
return decide(left);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
type TypedValue = { value: LuaValue };
|
|
1135
|
+
|
|
1136
|
+
function evalExprWithNumericType(
|
|
1137
|
+
expr: LuaExpression,
|
|
1138
|
+
env: LuaEnv,
|
|
1139
|
+
sf: LuaStackFrame,
|
|
1140
|
+
_wantNumericType: boolean,
|
|
1141
|
+
): TypedValue | Promise<TypedValue> {
|
|
1142
|
+
const v = evalExpression(expr, env, sf);
|
|
1143
|
+
const apply = (vv: any): TypedValue => ({ value: vv });
|
|
1144
|
+
return rpThen(v, apply) as any;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function getSimpleLiteralType(expr: LuaExpression): NumericType | undefined {
|
|
1148
|
+
if (expr.type === "Number") {
|
|
1149
|
+
return expr.numericType === "int" ? "int" : "float";
|
|
1150
|
+
}
|
|
1151
|
+
if (
|
|
1152
|
+
expr.type === "Unary" &&
|
|
1153
|
+
(expr.operator === "+" || expr.operator === "-") &&
|
|
1154
|
+
expr.argument.type === "Number"
|
|
1155
|
+
) {
|
|
1156
|
+
return expr.argument.numericType === "int" ? "int" : "float";
|
|
1157
|
+
}
|
|
1158
|
+
return undefined;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function evalBinaryWithLR(
|
|
1162
|
+
op: string,
|
|
1163
|
+
leftExpr: LuaExpression,
|
|
1164
|
+
rightExpr: LuaExpression,
|
|
1165
|
+
ctx: ASTCtx,
|
|
1166
|
+
env: LuaEnv,
|
|
1167
|
+
sf: LuaStackFrame,
|
|
1168
|
+
): any {
|
|
1169
|
+
const wantNumericType = isNumericBinaryOp(op);
|
|
1170
|
+
const leftType = wantNumericType ? getSimpleLiteralType(leftExpr) : undefined;
|
|
1171
|
+
const rightType = wantNumericType
|
|
1172
|
+
? getSimpleLiteralType(rightExpr)
|
|
1173
|
+
: undefined;
|
|
1174
|
+
const leftVal = evalExpression(leftExpr, env, sf);
|
|
1175
|
+
|
|
1176
|
+
const applyLeft = (lv: any) => {
|
|
1177
|
+
const rightVal = evalExpression(rightExpr, env, sf);
|
|
1178
|
+
const applyRight = (rv: any) => {
|
|
1179
|
+
return luaOp(
|
|
1180
|
+
op,
|
|
1181
|
+
singleResult(lv),
|
|
1182
|
+
singleResult(rv),
|
|
1183
|
+
leftType,
|
|
1184
|
+
rightType,
|
|
1185
|
+
ctx,
|
|
1186
|
+
sf,
|
|
1187
|
+
);
|
|
1188
|
+
};
|
|
1189
|
+
return rpThen(rightVal, applyRight);
|
|
1190
|
+
};
|
|
1191
|
+
return rpThen(leftVal, applyLeft);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function createBitwiseError(
|
|
1195
|
+
val: any,
|
|
1196
|
+
ctx: ASTCtx,
|
|
1197
|
+
sf: LuaStackFrame,
|
|
1198
|
+
): LuaRuntimeError {
|
|
1199
|
+
if (typeof val === "string") {
|
|
1200
|
+
return new LuaRuntimeError(
|
|
1201
|
+
`attempt to perform bitwise operation on a string value (constant '${val}')`,
|
|
1202
|
+
sf.withCtx(ctx),
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
const t = luaTypeName(val);
|
|
1206
|
+
if (t === "number") {
|
|
1207
|
+
return new LuaRuntimeError(
|
|
1208
|
+
`number has no integer representation`,
|
|
1209
|
+
sf.withCtx(ctx),
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
return new LuaRuntimeError(
|
|
1213
|
+
`attempt to perform bitwise operation on a ${t} value`,
|
|
1214
|
+
sf.withCtx(ctx),
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function getBinaryMM(
|
|
1219
|
+
a: any,
|
|
1220
|
+
b: any,
|
|
1221
|
+
mmName: string,
|
|
1222
|
+
sf: LuaStackFrame,
|
|
1223
|
+
): any | null {
|
|
1224
|
+
// Look in a's metatable first; if absent, look in b's.
|
|
1225
|
+
const ma = getMetatable(a, sf);
|
|
1226
|
+
if (ma) {
|
|
1227
|
+
const mmA = ma.rawGet(mmName);
|
|
1228
|
+
if (!(mmA === undefined || mmA === null)) {
|
|
1229
|
+
return mmA;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const mb = getMetatable(b, sf);
|
|
1233
|
+
if (mb) {
|
|
1234
|
+
const mmB = mb.rawGet(mmName);
|
|
1235
|
+
if (!(mmB === undefined || mmB === null)) {
|
|
1236
|
+
return mmB;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function luaEqWithMetamethod(
|
|
1243
|
+
a: any,
|
|
1244
|
+
b: any,
|
|
1245
|
+
ctx: ASTCtx,
|
|
1246
|
+
sf: LuaStackFrame,
|
|
1247
|
+
): boolean | Promise<boolean> {
|
|
1248
|
+
if (luaEquals(a, b)) {
|
|
1249
|
+
return true;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const ta = luaTypeName(a);
|
|
1253
|
+
const tb = luaTypeName(b);
|
|
1254
|
+
|
|
1255
|
+
// __eq only applies to tables/userdata
|
|
1256
|
+
const aOk = ta === "table" || ta === "userdata";
|
|
1257
|
+
const bOk = tb === "table" || tb === "userdata";
|
|
1258
|
+
if (!aOk || !bOk) {
|
|
1259
|
+
return false;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
const getEqMM = (obj: any): any | null => {
|
|
1263
|
+
const mt = getMetatable(obj, sf);
|
|
1264
|
+
if (!mt) return null;
|
|
1265
|
+
|
|
1266
|
+
const mm = mt.rawGet("__eq");
|
|
1267
|
+
if (mm === undefined || mm === null) return null;
|
|
1268
|
+
|
|
1269
|
+
if (typeof mm === "function" || isILuaFunction(mm)) {
|
|
1270
|
+
return mm;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const ty = luaTypeName(mm);
|
|
1274
|
+
throw new LuaRuntimeError(
|
|
1275
|
+
`attempt to call a ${ty} value`,
|
|
1276
|
+
sf.withCtx(ctx),
|
|
1277
|
+
);
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
// Try left __eq first, then right.
|
|
1281
|
+
const mm = getEqMM(a) ?? getEqMM(b);
|
|
1282
|
+
if (!mm) {
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const r = luaCall(mm, [a, b], ctx, sf);
|
|
1287
|
+
return isPromise(r)
|
|
1288
|
+
? (r as Promise<any>).then((v) => !!singleResult(v))
|
|
1289
|
+
: !!singleResult(r);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function luaRelWithMetamethod(
|
|
1293
|
+
op: "<" | "<=",
|
|
1294
|
+
a: any,
|
|
1295
|
+
b: any,
|
|
1296
|
+
ctx: ASTCtx,
|
|
1297
|
+
sf: LuaStackFrame,
|
|
1298
|
+
): boolean | Promise<boolean> {
|
|
1299
|
+
const an = isTaggedFloat(a) ? a.value : a;
|
|
1300
|
+
const bn = isTaggedFloat(b) ? b.value : b;
|
|
1301
|
+
|
|
1302
|
+
if (typeof an === "number" && typeof bn === "number") {
|
|
1303
|
+
return op === "<" ? an < bn : an <= bn;
|
|
1304
|
+
}
|
|
1305
|
+
if (typeof an === "string" && typeof bn === "string") {
|
|
1306
|
+
return op === "<" ? an < bn : an <= bn;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const mmName = op === "<" ? "__lt" : "__le";
|
|
1310
|
+
const mm = getBinaryMM(a, b, mmName, sf);
|
|
1311
|
+
if (mm) {
|
|
1312
|
+
const r = luaCall(mm, [a, b], ctx, sf);
|
|
1313
|
+
if (isPromise(r)) {
|
|
1314
|
+
return (r as Promise<any>).then((v) => !!singleResult(v));
|
|
1315
|
+
}
|
|
1316
|
+
return !!singleResult(r);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
throw new LuaRuntimeError(
|
|
1320
|
+
`attempt to compare ${luaTypeName(a)} with ${luaTypeName(b)}`,
|
|
1321
|
+
sf.withCtx(ctx),
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Length operator:
|
|
1327
|
+
* - for strings return byte length, ignore `__len`,
|
|
1328
|
+
* - for Lua tables if metatable has `__len` metamethod then call it;
|
|
1329
|
+
* use table length otherwise,
|
|
1330
|
+
* - for other values (userdata): honor `__len` if present,
|
|
1331
|
+
* - for JavaScript arrays return length,
|
|
1332
|
+
* - throw error otherwise.
|
|
1333
|
+
*/
|
|
1334
|
+
function luaLengthOp(
|
|
1335
|
+
val: any,
|
|
1336
|
+
ctx: ASTCtx,
|
|
1337
|
+
sf: LuaStackFrame,
|
|
1338
|
+
): LuaValue {
|
|
1339
|
+
// Strings: ignore `__len`
|
|
1340
|
+
if (typeof val === "string") {
|
|
1341
|
+
return val.length;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Tables: prefer metatable `__len` to raw length
|
|
1345
|
+
if (val instanceof LuaTable) {
|
|
1346
|
+
const mt = getMetatable(val, sf);
|
|
1347
|
+
if (mt) {
|
|
1348
|
+
const fn = mt.rawGet("__len");
|
|
1349
|
+
if (!(fn === undefined || fn === null)) {
|
|
1350
|
+
return luaCall(fn, [val], ctx, sf);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return val.length;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// Other values: allow metatable `__len` first
|
|
1357
|
+
{
|
|
1358
|
+
const mt = getMetatable(val, sf);
|
|
1359
|
+
if (mt) {
|
|
1360
|
+
const fn = mt.rawGet("__len");
|
|
1361
|
+
if (!(fn === undefined || fn === null)) {
|
|
1362
|
+
return luaCall(fn, [val], ctx, sf);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// JS arrays (interop): length if no `__len` override
|
|
1368
|
+
if (Array.isArray(val)) {
|
|
1369
|
+
return val.length;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// Otherwise error with type
|
|
1373
|
+
const t = luaTypeOf(val) as LuaType;
|
|
1374
|
+
throw new LuaRuntimeError(
|
|
1375
|
+
`attempt to get length of a ${t} value`,
|
|
1376
|
+
sf.withCtx(ctx),
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
function evalExpressions(
|
|
1381
|
+
es: LuaExpression[],
|
|
1382
|
+
env: LuaEnv,
|
|
1383
|
+
sf: LuaStackFrame,
|
|
1384
|
+
): Promise<LuaValue[]> | LuaValue[] {
|
|
1385
|
+
// Evaluate all arguments first (sync-first); do not allocate a Promise if all are sync.
|
|
1386
|
+
const parts = es.map((arg) => evalExpression(arg, env, sf));
|
|
1387
|
+
const argsVal = rpAll(parts);
|
|
1388
|
+
|
|
1389
|
+
// In Lua multi-returns propagate only in tail position of an expression list.
|
|
1390
|
+
const finalize = (argsResolved: any[]) => {
|
|
1391
|
+
if (argsResolved.length === 0) {
|
|
1392
|
+
return [];
|
|
1393
|
+
}
|
|
1394
|
+
const out: LuaValue[] = [];
|
|
1395
|
+
// All but last expression produce a single value
|
|
1396
|
+
for (let i = 0; i < argsResolved.length - 1; i++) {
|
|
1397
|
+
out.push(singleResult(argsResolved[i]));
|
|
1398
|
+
}
|
|
1399
|
+
// Last expression preserves multiple results
|
|
1400
|
+
const last = argsResolved[argsResolved.length - 1];
|
|
1401
|
+
if (last instanceof LuaMultiRes) {
|
|
1402
|
+
out.push(...last.flatten().values);
|
|
1403
|
+
} else {
|
|
1404
|
+
out.push(singleResult(last));
|
|
1405
|
+
}
|
|
1406
|
+
return out;
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
return rpThen(argsVal, finalize);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
type EvalBlockResult =
|
|
1413
|
+
| void
|
|
1414
|
+
| ControlSignal
|
|
1415
|
+
| Promise<void | ControlSignal>;
|
|
1416
|
+
|
|
1417
|
+
function runStatementsNoGoto(
|
|
1418
|
+
stmts: LuaStatement[],
|
|
1419
|
+
execEnv: LuaEnv,
|
|
1420
|
+
sf: LuaStackFrame,
|
|
1421
|
+
returnOnReturn: boolean,
|
|
1422
|
+
startIdx: number,
|
|
1423
|
+
): void | ControlSignal | Promise<void | ControlSignal> {
|
|
1424
|
+
const processFrom = (
|
|
1425
|
+
idx: number,
|
|
1426
|
+
): void | ControlSignal | Promise<void | ControlSignal> => {
|
|
1427
|
+
for (let i = idx; i < stmts.length; i++) {
|
|
1428
|
+
const result = evalStatement(
|
|
1429
|
+
stmts[i],
|
|
1430
|
+
execEnv,
|
|
1431
|
+
sf,
|
|
1432
|
+
returnOnReturn,
|
|
1433
|
+
);
|
|
1434
|
+
if (isPromise(result)) {
|
|
1435
|
+
return (result as Promise<any>).then((res) => {
|
|
1436
|
+
if (res !== undefined) {
|
|
1437
|
+
if (isGotoSignal(res)) {
|
|
1438
|
+
throw new LuaRuntimeError(
|
|
1439
|
+
"unexpected goto signal",
|
|
1440
|
+
sf.withCtx(stmts[i].ctx),
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
return res;
|
|
1444
|
+
}
|
|
1445
|
+
return processFrom(i + 1);
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
if (result !== undefined) {
|
|
1449
|
+
if (isGotoSignal(result)) {
|
|
1450
|
+
throw new LuaRuntimeError(
|
|
1451
|
+
"unexpected goto signal",
|
|
1452
|
+
sf.withCtx(stmts[i].ctx),
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
return result;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
return;
|
|
1459
|
+
};
|
|
1460
|
+
|
|
1461
|
+
return processFrom(startIdx);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function withCloseBoundary(
|
|
1465
|
+
sf: LuaStackFrame,
|
|
1466
|
+
mark: number,
|
|
1467
|
+
out: EvalBlockResult,
|
|
1468
|
+
): EvalBlockResult {
|
|
1469
|
+
if (!isPromise(out)) {
|
|
1470
|
+
const r = luaCloseFromMark(sf, mark, null);
|
|
1471
|
+
if (isPromise(r)) {
|
|
1472
|
+
return (r as Promise<void>).then(() => out as any);
|
|
1473
|
+
}
|
|
1474
|
+
return out;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
const p = out as Promise<any>;
|
|
1478
|
+
|
|
1479
|
+
const onFulfilled = (res: any) => {
|
|
1480
|
+
const r = luaCloseFromMark(sf, mark, null);
|
|
1481
|
+
return isPromise(r) ? (r as Promise<void>).then(() => res) : res;
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
const onRejected = (e: any) => {
|
|
1485
|
+
const errObj: LuaValue = e instanceof LuaRuntimeError
|
|
1486
|
+
? e.message
|
|
1487
|
+
: (e?.message ?? String(e));
|
|
1488
|
+
const r = luaCloseFromMark(sf, mark, errObj);
|
|
1489
|
+
if (isPromise(r)) {
|
|
1490
|
+
return (r as Promise<void>).then(() => {
|
|
1491
|
+
throw e;
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
throw e;
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
return p.then(onFulfilled, onRejected);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function evalBlockNoClose(
|
|
1501
|
+
b: LuaBlock,
|
|
1502
|
+
env: LuaEnv,
|
|
1503
|
+
sf: LuaStackFrame,
|
|
1504
|
+
returnOnReturn: boolean,
|
|
1505
|
+
): EvalBlockResult {
|
|
1506
|
+
const hasGotoFlag = b.hasGoto === true;
|
|
1507
|
+
const hasLabelFlag = b.hasLabel === true;
|
|
1508
|
+
const hasLabelHere = b.hasLabelHere === true;
|
|
1509
|
+
|
|
1510
|
+
const curFn = sf.currentFunction;
|
|
1511
|
+
const fnHasGotos = curFn?.funcHasGotos;
|
|
1512
|
+
|
|
1513
|
+
if (fnHasGotos === false || (!hasGotoFlag && !hasLabelFlag)) {
|
|
1514
|
+
const dup = b.dupLabelError;
|
|
1515
|
+
if (dup) {
|
|
1516
|
+
// Duplicated labels detected by parser.
|
|
1517
|
+
throw new LuaRuntimeError(
|
|
1518
|
+
`label '${dup.name}' already defined`,
|
|
1519
|
+
sf.withCtx(dup.ctx),
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
const execEnv = b.needsEnv === true ? new LuaEnv(env) : env;
|
|
1524
|
+
return runStatementsNoGoto(b.statements, execEnv, sf, returnOnReturn, 0);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
if (fnHasGotos === true && !hasLabelHere && !hasGotoFlag) {
|
|
1528
|
+
const execEnv = b.needsEnv === true ? new LuaEnv(env) : env;
|
|
1529
|
+
const stmts = b.statements;
|
|
1530
|
+
const runFrom = (
|
|
1531
|
+
i: number,
|
|
1532
|
+
): EvalBlockResult => {
|
|
1533
|
+
for (; i < stmts.length; i++) {
|
|
1534
|
+
const r = evalStatement(stmts[i], execEnv, sf, returnOnReturn);
|
|
1535
|
+
if (isPromise(r)) {
|
|
1536
|
+
return (r as Promise<any>).then((res) => {
|
|
1537
|
+
if (isGotoSignal(res)) return res;
|
|
1538
|
+
if (res !== undefined) return res;
|
|
1539
|
+
return runFrom(i + 1);
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
if (isGotoSignal(r)) return r;
|
|
1543
|
+
if (r !== undefined) return r;
|
|
1544
|
+
}
|
|
1545
|
+
return;
|
|
1546
|
+
};
|
|
1547
|
+
return runFrom(0);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
let meta: ReturnType<typeof getBlockGotoMeta> | undefined;
|
|
1551
|
+
if (fnHasGotos === undefined && (hasGotoFlag || hasLabelFlag)) {
|
|
1552
|
+
meta = blockMetaOrThrow(b, sf);
|
|
1553
|
+
if (curFn) {
|
|
1554
|
+
curFn.funcHasGotos = !!meta?.funcHasGotos;
|
|
1555
|
+
}
|
|
1556
|
+
} else if (fnHasGotos === true) {
|
|
1557
|
+
meta = hasLabelFlag || hasGotoFlag ? blockMetaOrThrow(b, sf) : undefined;
|
|
1558
|
+
} else {
|
|
1559
|
+
meta = undefined;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if (!meta || !meta.funcHasGotos) {
|
|
1563
|
+
const dup = b.dupLabelError;
|
|
1564
|
+
if (dup) {
|
|
1565
|
+
throw new LuaRuntimeError(
|
|
1566
|
+
`label '${dup.name}' already defined`,
|
|
1567
|
+
sf.withCtx(dup.ctx),
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
const execEnv = b.needsEnv === true ? new LuaEnv(env) : env;
|
|
1571
|
+
return runStatementsNoGoto(b.statements, execEnv, sf, returnOnReturn, 0);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
const execEnv = b.needsEnv === true ? new LuaEnv(env) : env;
|
|
1575
|
+
const stmts = b.statements;
|
|
1576
|
+
|
|
1577
|
+
const runFrom = (
|
|
1578
|
+
i: number,
|
|
1579
|
+
): EvalBlockResult => {
|
|
1580
|
+
for (; i < stmts.length; i++) {
|
|
1581
|
+
const r = evalStatement(stmts[i], execEnv, sf, returnOnReturn);
|
|
1582
|
+
if (isPromise(r)) {
|
|
1583
|
+
return (r as Promise<any>).then((res) => {
|
|
1584
|
+
const consumed = consumeGotoInBlock(res, meta!.labels);
|
|
1585
|
+
if (typeof consumed === "number") {
|
|
1586
|
+
return runFrom(consumed);
|
|
1587
|
+
}
|
|
1588
|
+
if (consumed !== undefined) {
|
|
1589
|
+
return consumed;
|
|
1590
|
+
}
|
|
1591
|
+
return runFrom(i + 1);
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
const consumed = consumeGotoInBlock(r, meta.labels);
|
|
1595
|
+
if (typeof consumed === "number") {
|
|
1596
|
+
i = consumed - 1;
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
if (consumed !== undefined) {
|
|
1600
|
+
return consumed;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return;
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
return runFrom(0);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* Evaluates a statement in two possible modes:
|
|
1611
|
+
*
|
|
1612
|
+
* 1. With `returnOnReturn` set to `true` will return the value of
|
|
1613
|
+
* a return statement.
|
|
1614
|
+
* 2. With `returnOnReturn` set to `false` will throw a LuaReturn
|
|
1615
|
+
* exception if a return statement is encountered.
|
|
1616
|
+
*
|
|
1617
|
+
* May also return `{ctrl:"goto", target}` for goto.
|
|
1618
|
+
*/
|
|
1619
|
+
export function evalStatement(
|
|
1620
|
+
s: LuaStatement,
|
|
1621
|
+
env: LuaEnv,
|
|
1622
|
+
sf: LuaStackFrame,
|
|
1623
|
+
returnOnReturn = false,
|
|
1624
|
+
): void | ControlSignal | Promise<void | ControlSignal> {
|
|
1625
|
+
switch (s.type) {
|
|
1626
|
+
case "Assignment": {
|
|
1627
|
+
const a = asAssignment(s);
|
|
1628
|
+
const valuesRP = evalExpressions(a.expressions, env, sf);
|
|
1629
|
+
const lvaluesRP = evalPromiseValues(a.variables
|
|
1630
|
+
.map((lval) => evalLValue(lval, env, sf)));
|
|
1631
|
+
|
|
1632
|
+
const apply = (values: LuaValue[], lvalues: { env: any; key: any }[]) => {
|
|
1633
|
+
const ps: Promise<any>[] = [];
|
|
1634
|
+
for (let i = 0; i < lvalues.length; i++) {
|
|
1635
|
+
const r = luaSet(
|
|
1636
|
+
lvalues[i].env,
|
|
1637
|
+
lvalues[i].key,
|
|
1638
|
+
values[i],
|
|
1639
|
+
sf.withCtx(a.ctx),
|
|
1640
|
+
);
|
|
1641
|
+
|
|
1642
|
+
if (isPromise(r)) {
|
|
1643
|
+
ps.push(r);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (ps.length) {
|
|
1647
|
+
return Promise.all(ps).then(() => undefined);
|
|
1648
|
+
}
|
|
1649
|
+
return;
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
if (!isPromise(valuesRP) && !isPromise(lvaluesRP)) {
|
|
1653
|
+
return apply(
|
|
1654
|
+
valuesRP as LuaValue[],
|
|
1655
|
+
lvaluesRP as LuaLValueContainer[],
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
if (
|
|
1659
|
+
isPromise(valuesRP) && !isPromise(lvaluesRP)
|
|
1660
|
+
) {
|
|
1661
|
+
return (valuesRP as Promise<LuaValue[]>).then((values: LuaValue[]) =>
|
|
1662
|
+
apply(values, lvaluesRP as LuaLValueContainer[])
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
if (
|
|
1666
|
+
!isPromise(valuesRP) && isPromise(lvaluesRP)
|
|
1667
|
+
) {
|
|
1668
|
+
return (lvaluesRP as Promise<any[]>).then((lvalues: any[]) =>
|
|
1669
|
+
apply(valuesRP as LuaValue[], lvalues)
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
return (valuesRP as Promise<LuaValue[]>).then((values: LuaValue[]) =>
|
|
1673
|
+
(lvaluesRP as Promise<any[]>).then((lvalues: any[]) =>
|
|
1674
|
+
apply(values, lvalues)
|
|
1675
|
+
)
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
case "Local": {
|
|
1679
|
+
const l = asLocal(s);
|
|
1680
|
+
|
|
1681
|
+
const hasInit = Array.isArray(l.expressions) && l.expressions.length > 0;
|
|
1682
|
+
|
|
1683
|
+
for (const att of l.names) {
|
|
1684
|
+
const isConst = att.attributes?.includes(LuaAttribute.Const) === true;
|
|
1685
|
+
if (isConst && !hasInit) {
|
|
1686
|
+
throw new LuaRuntimeError(
|
|
1687
|
+
`const variable '${att.name}' must be initialized`,
|
|
1688
|
+
sf.withCtx(att.ctx),
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
const bindOne = (name: any, v: LuaValue) => {
|
|
1694
|
+
const isConst = name.attributes?.includes(LuaAttribute.Const) === true;
|
|
1695
|
+
const isClose = name.attributes?.includes(LuaAttribute.Close) === true;
|
|
1696
|
+
|
|
1697
|
+
if (isConst || isClose) {
|
|
1698
|
+
env.setLocalConst(name.name, v);
|
|
1699
|
+
} else {
|
|
1700
|
+
env.setLocal(name.name, v);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
if (isClose) {
|
|
1704
|
+
luaMarkToBeClosed(sf, v, name.ctx);
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
if (!hasInit) {
|
|
1709
|
+
for (let i = 0; i < l.names.length; i++) {
|
|
1710
|
+
bindOne(l.names[i], null);
|
|
1711
|
+
}
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
// Evaluate initializers left-to-right and bind/mark `<close>`
|
|
1716
|
+
// locals as soon as they receive a value. This ensures earlier
|
|
1717
|
+
// `<close>` locals are closed if a later initializer errors.
|
|
1718
|
+
const exprs = l.expressions!;
|
|
1719
|
+
const out: LuaValue[] = [];
|
|
1720
|
+
let boundCount = 0;
|
|
1721
|
+
|
|
1722
|
+
const bindAvailable = () => {
|
|
1723
|
+
while (boundCount < l.names.length && boundCount < out.length) {
|
|
1724
|
+
bindOne(
|
|
1725
|
+
l.names[boundCount],
|
|
1726
|
+
out[boundCount] ?? null,
|
|
1727
|
+
);
|
|
1728
|
+
boundCount++;
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1732
|
+
const finish = () => {
|
|
1733
|
+
while (out.length < l.names.length) {
|
|
1734
|
+
out.push(null);
|
|
1735
|
+
}
|
|
1736
|
+
bindAvailable();
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
const runFrom = (i: number): void | Promise<void> => {
|
|
1740
|
+
if (i >= exprs.length) {
|
|
1741
|
+
finish();
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
const isLastExpr = i === exprs.length - 1;
|
|
1746
|
+
const rp = evalExpression(exprs[i], env, sf);
|
|
1747
|
+
|
|
1748
|
+
const onValue = (v: LuaValue) => {
|
|
1749
|
+
if (isLastExpr) {
|
|
1750
|
+
if (v instanceof LuaMultiRes) {
|
|
1751
|
+
const flat = v.flatten();
|
|
1752
|
+
for (let k = 0; k < flat.values.length; k++) {
|
|
1753
|
+
out.push(flat.values[k]);
|
|
1754
|
+
}
|
|
1755
|
+
} else {
|
|
1756
|
+
out.push(v);
|
|
1757
|
+
}
|
|
1758
|
+
} else {
|
|
1759
|
+
out.push(singleResult(v));
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
bindAvailable();
|
|
1763
|
+
|
|
1764
|
+
// If we already have enough values for all locals, remaining
|
|
1765
|
+
// expressions will not affect the binding, so we can stop.
|
|
1766
|
+
if (out.length >= l.names.length && !isLastExpr) {
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
return runFrom(i + 1);
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
return rpThen(rp, onValue) as any;
|
|
1774
|
+
};
|
|
1775
|
+
|
|
1776
|
+
return runFrom(0);
|
|
1777
|
+
}
|
|
1778
|
+
case "Semicolon": {
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
case "Label": {
|
|
1782
|
+
const _lab = asLabel(s); // No-op!
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
case "Goto": {
|
|
1786
|
+
const g = asGoto(s);
|
|
1787
|
+
return { ctrl: "goto", target: g.name };
|
|
1788
|
+
}
|
|
1789
|
+
case "Block": {
|
|
1790
|
+
const b = asBlock(s);
|
|
1791
|
+
|
|
1792
|
+
if (!b.hasCloseHere) {
|
|
1793
|
+
return evalBlockNoClose(b, env, sf, returnOnReturn);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// Blocks establish a boundary (mark) and close all entries
|
|
1797
|
+
// created within the block on exit or error, shrinking the stack
|
|
1798
|
+
// back to mark. This is _required_ for correct `pcall` and
|
|
1799
|
+
// `xpcall` boundary semantics.
|
|
1800
|
+
const closeStack = luaEnsureCloseStack(sf);
|
|
1801
|
+
const mark = closeStack.length;
|
|
1802
|
+
|
|
1803
|
+
let out: EvalBlockResult;
|
|
1804
|
+
try {
|
|
1805
|
+
out = evalBlockNoClose(b, env, sf, returnOnReturn);
|
|
1806
|
+
} catch (e: any) {
|
|
1807
|
+
const errObj: LuaValue = e instanceof LuaRuntimeError
|
|
1808
|
+
? e.message
|
|
1809
|
+
: (e?.message ?? String(e));
|
|
1810
|
+
const r = luaCloseFromMark(sf, mark, errObj);
|
|
1811
|
+
if (isPromise(r)) {
|
|
1812
|
+
return (r as Promise<void>).then(() => {
|
|
1813
|
+
throw e;
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
throw e;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
return withCloseBoundary(sf, mark, out);
|
|
1820
|
+
}
|
|
1821
|
+
case "If": {
|
|
1822
|
+
const iff = asIf(s);
|
|
1823
|
+
// Evaluate conditions in order; avoid awaiting when not necessary
|
|
1824
|
+
const conds = iff.conditions;
|
|
1825
|
+
|
|
1826
|
+
const runFrom = (
|
|
1827
|
+
i: number,
|
|
1828
|
+
):
|
|
1829
|
+
| void
|
|
1830
|
+
| ControlSignal
|
|
1831
|
+
| Promise<void | ControlSignal> => {
|
|
1832
|
+
if (i >= conds.length) {
|
|
1833
|
+
if (iff.elseBlock) {
|
|
1834
|
+
return evalStatement(iff.elseBlock, env, sf, returnOnReturn);
|
|
1835
|
+
}
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
const cv = evalExpression(conds[i].condition, env, sf);
|
|
1839
|
+
if (isPromise(cv)) {
|
|
1840
|
+
return (cv as Promise<any>).then((val) => {
|
|
1841
|
+
if (luaTruthy(val)) {
|
|
1842
|
+
return evalStatement(conds[i].block, env, sf, returnOnReturn);
|
|
1843
|
+
}
|
|
1844
|
+
return runFrom(i + 1);
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
if (luaTruthy(cv)) {
|
|
1848
|
+
return evalStatement(conds[i].block, env, sf, returnOnReturn);
|
|
1849
|
+
}
|
|
1850
|
+
return runFrom(i + 1);
|
|
1851
|
+
};
|
|
1852
|
+
|
|
1853
|
+
return runFrom(0);
|
|
1854
|
+
}
|
|
1855
|
+
case "While": {
|
|
1856
|
+
const w = asWhile(s);
|
|
1857
|
+
|
|
1858
|
+
const runAsync = async (): Promise<void | ControlSignal> => {
|
|
1859
|
+
while (true) {
|
|
1860
|
+
const c = await evalExpression(w.condition, env, sf);
|
|
1861
|
+
if (!luaTruthy(c)) {
|
|
1862
|
+
break;
|
|
1863
|
+
}
|
|
1864
|
+
const r = evalStatement(w.block, env, sf, returnOnReturn);
|
|
1865
|
+
const res = isPromise(r) ? await r : r;
|
|
1866
|
+
if (res !== undefined) {
|
|
1867
|
+
if (isBreakSignal(res)) {
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1870
|
+
return res;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return;
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
while (true) {
|
|
1877
|
+
const c = evalExpression(w.condition, env, sf);
|
|
1878
|
+
if (isPromise(c)) {
|
|
1879
|
+
return (c as Promise<any>).then((cv) => {
|
|
1880
|
+
if (!luaTruthy(cv)) {
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
try {
|
|
1884
|
+
const r = evalStatement(w.block, env, sf, returnOnReturn);
|
|
1885
|
+
if (isPromise(r)) {
|
|
1886
|
+
return (r as Promise<any>).then((res) => {
|
|
1887
|
+
if (res !== undefined) {
|
|
1888
|
+
if (isBreakSignal(res)) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
return res;
|
|
1892
|
+
}
|
|
1893
|
+
return runAsync();
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
if (r !== undefined) {
|
|
1897
|
+
if (isBreakSignal(r)) {
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
return r;
|
|
1901
|
+
}
|
|
1902
|
+
return runAsync();
|
|
1903
|
+
} catch (e: any) {
|
|
1904
|
+
throw e;
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
if (!luaTruthy(c)) {
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
const r = evalStatement(w.block, env, sf, returnOnReturn);
|
|
1912
|
+
if (isPromise(r)) {
|
|
1913
|
+
return (r as Promise<any>).then((res) => {
|
|
1914
|
+
if (res !== undefined) {
|
|
1915
|
+
if (isBreakSignal(res)) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
return res;
|
|
1919
|
+
}
|
|
1920
|
+
return runAsync();
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
if (r !== undefined) {
|
|
1924
|
+
if (isBreakSignal(r)) {
|
|
1925
|
+
break;
|
|
1926
|
+
}
|
|
1927
|
+
return r;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
case "Repeat": {
|
|
1933
|
+
const r = asRepeat(s);
|
|
1934
|
+
|
|
1935
|
+
const runAsync = async (): Promise<void | ControlSignal> => {
|
|
1936
|
+
while (true) {
|
|
1937
|
+
const rr = evalStatement(r.block, env, sf, returnOnReturn);
|
|
1938
|
+
const res = isPromise(rr) ? await rr : rr;
|
|
1939
|
+
if (res !== undefined) {
|
|
1940
|
+
if (isBreakSignal(res)) {
|
|
1941
|
+
break;
|
|
1942
|
+
}
|
|
1943
|
+
return res;
|
|
1944
|
+
}
|
|
1945
|
+
const c = await evalExpression(r.condition, env, sf);
|
|
1946
|
+
if (luaTruthy(c)) {
|
|
1947
|
+
break;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
return;
|
|
1951
|
+
};
|
|
1952
|
+
|
|
1953
|
+
while (true) {
|
|
1954
|
+
const rr = evalStatement(r.block, env, sf, returnOnReturn);
|
|
1955
|
+
if (isPromise(rr)) {
|
|
1956
|
+
return (rr as Promise<any>).then((res) => {
|
|
1957
|
+
if (res !== undefined) {
|
|
1958
|
+
if (isBreakSignal(res)) {
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
return res;
|
|
1962
|
+
}
|
|
1963
|
+
return runAsync();
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
if (rr !== undefined) {
|
|
1967
|
+
if (isBreakSignal(rr)) {
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
return rr;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
const c = evalExpression(r.condition, env, sf);
|
|
1974
|
+
if (isPromise(c)) {
|
|
1975
|
+
return (c as Promise<any>).then((cv) =>
|
|
1976
|
+
luaTruthy(cv) ? undefined : runAsync()
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
if (luaTruthy(c)) {
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
case "Break": {
|
|
1986
|
+
return { ctrl: "break" };
|
|
1987
|
+
}
|
|
1988
|
+
case "FunctionCallStatement": {
|
|
1989
|
+
const fcs = asFunctionCallStmt(s);
|
|
1990
|
+
const r = evalExpression(fcs.call, env, sf);
|
|
1991
|
+
if (isPromise(r)) {
|
|
1992
|
+
return (r as Promise<any>).then(() => undefined);
|
|
1993
|
+
}
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
case "Function": {
|
|
1997
|
+
const fn = asFunctionStmt(s);
|
|
1998
|
+
let body = fn.body;
|
|
1999
|
+
let propNames = fn.name.propNames;
|
|
2000
|
+
if (fn.name.colonName) {
|
|
2001
|
+
// function hello:there() -> function hello.there(self) transformation
|
|
2002
|
+
body = {
|
|
2003
|
+
...(fn.body),
|
|
2004
|
+
parameters: ["self", ...fn.body.parameters],
|
|
2005
|
+
};
|
|
2006
|
+
propNames = [...fn.name.propNames, fn.name.colonName];
|
|
2007
|
+
}
|
|
2008
|
+
let settable: ILuaGettable = env;
|
|
2009
|
+
for (let i = 0; i < propNames.length - 1; i++) {
|
|
2010
|
+
settable = (settable as any).get(propNames[i]);
|
|
2011
|
+
if (!settable) {
|
|
2012
|
+
throw new LuaRuntimeError(
|
|
2013
|
+
`Cannot find property ${propNames[i]}`,
|
|
2014
|
+
sf.withCtx(fn.name.ctx),
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
(settable as any).set(
|
|
2019
|
+
propNames[propNames.length - 1],
|
|
2020
|
+
new LuaFunction(body, env),
|
|
2021
|
+
);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
case "LocalFunction": {
|
|
2025
|
+
const lf = asLocalFunction(s);
|
|
2026
|
+
env.setLocal(
|
|
2027
|
+
lf.name,
|
|
2028
|
+
new LuaFunction(lf.body, env),
|
|
2029
|
+
);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
case "Return": {
|
|
2033
|
+
const ret = asReturn(s);
|
|
2034
|
+
|
|
2035
|
+
const parts = ret.expressions.map((value: LuaExpression) =>
|
|
2036
|
+
evalExpression(value, env, sf)
|
|
2037
|
+
);
|
|
2038
|
+
const valuesRP = rpAll(parts);
|
|
2039
|
+
|
|
2040
|
+
const finalize = (vals: any[]): ReturnSignal => {
|
|
2041
|
+
const outVals: LuaValue[] = [];
|
|
2042
|
+
|
|
2043
|
+
if (vals.length === 0) {
|
|
2044
|
+
return { ctrl: "return", values: outVals };
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
for (let i = 0; i < vals.length; i++) {
|
|
2048
|
+
const isLast = i === vals.length - 1;
|
|
2049
|
+
const v = vals[i];
|
|
2050
|
+
|
|
2051
|
+
if (!isLast) {
|
|
2052
|
+
outVals.push(singleResult(v));
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
if (v instanceof LuaMultiRes) {
|
|
2057
|
+
const flat = v.flatten();
|
|
2058
|
+
outVals.push(...flat.values);
|
|
2059
|
+
} else {
|
|
2060
|
+
outVals.push(v);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
return {
|
|
2065
|
+
ctrl: "return" as const,
|
|
2066
|
+
values: outVals,
|
|
2067
|
+
};
|
|
2068
|
+
};
|
|
2069
|
+
|
|
2070
|
+
if (isPromise(valuesRP)) {
|
|
2071
|
+
return (valuesRP as Promise<any[]>).then((vals) => finalize(vals));
|
|
2072
|
+
}
|
|
2073
|
+
return finalize(valuesRP as any[]);
|
|
2074
|
+
}
|
|
2075
|
+
case "For": {
|
|
2076
|
+
const fr = asFor(s);
|
|
2077
|
+
const startV = evalExpression(fr.start, env, sf);
|
|
2078
|
+
const endV = evalExpression(fr.end, env, sf);
|
|
2079
|
+
const stepV = fr.step ? evalExpression(fr.step, env, sf) : 1;
|
|
2080
|
+
|
|
2081
|
+
const determineLoopType = (): NumericType => {
|
|
2082
|
+
const startType = astNumberKind(fr.start);
|
|
2083
|
+
const stepType = fr.step ? astNumberKind(fr.step) : "int";
|
|
2084
|
+
return (startType === "float" || stepType === "float")
|
|
2085
|
+
? "float"
|
|
2086
|
+
: "int";
|
|
2087
|
+
};
|
|
2088
|
+
|
|
2089
|
+
const wrapLoopVar = (i: number, loopType: NumericType) => {
|
|
2090
|
+
if (loopType === "float") {
|
|
2091
|
+
return makeLuaFloat(i);
|
|
2092
|
+
}
|
|
2093
|
+
return i;
|
|
2094
|
+
};
|
|
2095
|
+
|
|
2096
|
+
const canReuseEnv = !fr.block.hasFunctionDef ||
|
|
2097
|
+
fr.capturesLoopVar === false;
|
|
2098
|
+
|
|
2099
|
+
const executeIteration = canReuseEnv
|
|
2100
|
+
? (
|
|
2101
|
+
loopEnv: LuaEnv,
|
|
2102
|
+
i: number,
|
|
2103
|
+
loopType: NumericType,
|
|
2104
|
+
): void | ControlSignal | Promise<void | ControlSignal> => {
|
|
2105
|
+
loopEnv.setLocal(fr.name, wrapLoopVar(i, loopType));
|
|
2106
|
+
return evalStatement(fr.block, loopEnv, sf, returnOnReturn);
|
|
2107
|
+
}
|
|
2108
|
+
: (
|
|
2109
|
+
_loopEnv: LuaEnv,
|
|
2110
|
+
i: number,
|
|
2111
|
+
loopType: NumericType,
|
|
2112
|
+
): void | ControlSignal | Promise<void | ControlSignal> => {
|
|
2113
|
+
const localEnv = new LuaEnv(env);
|
|
2114
|
+
localEnv.setLocal(fr.name, wrapLoopVar(i, loopType));
|
|
2115
|
+
return evalStatement(fr.block, localEnv, sf, returnOnReturn);
|
|
2116
|
+
};
|
|
2117
|
+
|
|
2118
|
+
const runAsync = async (
|
|
2119
|
+
loopEnv: LuaEnv,
|
|
2120
|
+
end: number,
|
|
2121
|
+
step: number,
|
|
2122
|
+
startIndex: number,
|
|
2123
|
+
loopType: NumericType,
|
|
2124
|
+
) => {
|
|
2125
|
+
if (step === 0) {
|
|
2126
|
+
throw new LuaRuntimeError("'for' step is zero", sf.withCtx(fr.ctx));
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
const shouldContinue = step > 0
|
|
2130
|
+
? (i: number) => i <= end
|
|
2131
|
+
: (i: number) => i >= end;
|
|
2132
|
+
|
|
2133
|
+
for (let i = startIndex; shouldContinue(i); i += step) {
|
|
2134
|
+
const r = executeIteration(loopEnv, i, loopType);
|
|
2135
|
+
const res = isPromise(r) ? await r : r;
|
|
2136
|
+
if (res !== undefined) {
|
|
2137
|
+
if (isBreakSignal(res)) {
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
return res;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
};
|
|
2144
|
+
|
|
2145
|
+
const runSyncFirst = (
|
|
2146
|
+
start: number,
|
|
2147
|
+
end: number,
|
|
2148
|
+
step: number,
|
|
2149
|
+
loopType: NumericType,
|
|
2150
|
+
):
|
|
2151
|
+
| void
|
|
2152
|
+
| ControlSignal
|
|
2153
|
+
| Promise<void | ControlSignal> => {
|
|
2154
|
+
if (step === 0) {
|
|
2155
|
+
throw new LuaRuntimeError("'for' step is zero", sf.withCtx(fr.ctx));
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
const shouldContinue = step > 0
|
|
2159
|
+
? (i: number) => i <= end
|
|
2160
|
+
: (i: number) => i >= end;
|
|
2161
|
+
|
|
2162
|
+
const loopEnv = new LuaEnv(env);
|
|
2163
|
+
|
|
2164
|
+
for (let i = start; shouldContinue(i); i += step) {
|
|
2165
|
+
const r = executeIteration(loopEnv, i, loopType);
|
|
2166
|
+
if (isPromise(r)) {
|
|
2167
|
+
return (r as Promise<any>).then((res) => {
|
|
2168
|
+
if (res !== undefined) {
|
|
2169
|
+
if (isBreakSignal(res)) {
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
return res;
|
|
2173
|
+
}
|
|
2174
|
+
return runAsync(loopEnv, end, step, i + step, loopType);
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
if (r !== undefined) {
|
|
2178
|
+
if (isBreakSignal(r)) {
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
return r;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
return;
|
|
2185
|
+
};
|
|
2186
|
+
|
|
2187
|
+
const loopType = determineLoopType();
|
|
2188
|
+
|
|
2189
|
+
if (
|
|
2190
|
+
!isPromise(startV) &&
|
|
2191
|
+
!isPromise(endV) &&
|
|
2192
|
+
!isPromise(stepV)
|
|
2193
|
+
) {
|
|
2194
|
+
return runSyncFirst(
|
|
2195
|
+
untagNumber(startV) as number,
|
|
2196
|
+
untagNumber(endV) as number,
|
|
2197
|
+
untagNumber(stepV ?? 1) as number,
|
|
2198
|
+
loopType,
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
return Promise.all([
|
|
2202
|
+
isPromise(startV) ? startV : Promise.resolve(startV),
|
|
2203
|
+
isPromise(endV) ? endV : Promise.resolve(endV),
|
|
2204
|
+
isPromise(stepV) ? stepV : Promise.resolve(stepV),
|
|
2205
|
+
]).then(([start, end, step]) => {
|
|
2206
|
+
return runSyncFirst(
|
|
2207
|
+
untagNumber(start) as number,
|
|
2208
|
+
untagNumber(end) as number,
|
|
2209
|
+
untagNumber(step ?? 1) as number,
|
|
2210
|
+
loopType,
|
|
2211
|
+
);
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
case "ForIn": {
|
|
2215
|
+
const fi = asForIn(s);
|
|
2216
|
+
const exprVals = rpAll(
|
|
2217
|
+
fi.expressions.map((e: LuaExpression) => evalExpression(e, env, sf)),
|
|
2218
|
+
);
|
|
2219
|
+
|
|
2220
|
+
const canReuseEnv = !fi.block.hasFunctionDef ||
|
|
2221
|
+
fi.capturesLoopVar === false;
|
|
2222
|
+
const setIterVars = (
|
|
2223
|
+
localEnv: LuaEnv,
|
|
2224
|
+
names: string[],
|
|
2225
|
+
values: LuaValue[],
|
|
2226
|
+
) => {
|
|
2227
|
+
for (let i = 0; i < names.length; i++) {
|
|
2228
|
+
localEnv.setLocal(names[i], values[i]);
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
|
|
2232
|
+
const afterExprs = (resolved: any[]) => {
|
|
2233
|
+
const iteratorMultiRes = new LuaMultiRes(resolved).flatten();
|
|
2234
|
+
let iteratorValue: ILuaFunction | any = iteratorMultiRes.values[0];
|
|
2235
|
+
// Handle the case where the iterator is a table and we need
|
|
2236
|
+
// to call the `each` function.
|
|
2237
|
+
if (Array.isArray(iteratorValue) || iteratorValue instanceof LuaTable) {
|
|
2238
|
+
iteratorValue = (env.get("each") as ILuaFunction).call(
|
|
2239
|
+
sf,
|
|
2240
|
+
iteratorValue,
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
if (!iteratorValue?.call) {
|
|
2245
|
+
console.error("Cannot iterate over", iteratorMultiRes.values[0]);
|
|
2246
|
+
throw new LuaRuntimeError(
|
|
2247
|
+
`Cannot iterate over ${iteratorMultiRes.values[0]}`,
|
|
2248
|
+
sf.withCtx(fi.ctx),
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
const state: LuaValue = iteratorMultiRes.values[1] ?? null;
|
|
2253
|
+
let control: LuaValue = iteratorMultiRes.values[2] ?? null;
|
|
2254
|
+
const closing: LuaValue = iteratorMultiRes.values[3] ?? null;
|
|
2255
|
+
|
|
2256
|
+
const closeStack = luaEnsureCloseStack(sf);
|
|
2257
|
+
const mark = closeStack.length;
|
|
2258
|
+
|
|
2259
|
+
if (closing !== null) {
|
|
2260
|
+
luaMarkToBeClosed(sf, closing, fi.ctx);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
const errObjFrom = (e: any): LuaValue =>
|
|
2264
|
+
e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e));
|
|
2265
|
+
|
|
2266
|
+
const finish = (res: any) => {
|
|
2267
|
+
const r = luaCloseFromMark(sf, mark, null);
|
|
2268
|
+
return isPromise(r) ? (r as Promise<void>).then(() => res) : res;
|
|
2269
|
+
};
|
|
2270
|
+
|
|
2271
|
+
const finishErr = (e: any): Promise<never> | never => {
|
|
2272
|
+
const errObj = errObjFrom(e);
|
|
2273
|
+
const r = luaCloseFromMark(sf, mark, errObj);
|
|
2274
|
+
if (isPromise(r)) {
|
|
2275
|
+
return (r as Promise<void>).then(() => {
|
|
2276
|
+
throw e;
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
throw e;
|
|
2280
|
+
};
|
|
2281
|
+
|
|
2282
|
+
// Allocate the reusable env once before the loop
|
|
2283
|
+
const loopEnv = canReuseEnv ? new LuaEnv(env) : null;
|
|
2284
|
+
|
|
2285
|
+
const makeIterEnv = (): LuaEnv => {
|
|
2286
|
+
if (loopEnv) {
|
|
2287
|
+
return loopEnv;
|
|
2288
|
+
}
|
|
2289
|
+
return new LuaEnv(env);
|
|
2290
|
+
};
|
|
2291
|
+
|
|
2292
|
+
try {
|
|
2293
|
+
const runAsync = async () => {
|
|
2294
|
+
while (true) {
|
|
2295
|
+
const callRes = luaCall(
|
|
2296
|
+
iteratorValue,
|
|
2297
|
+
[state, control],
|
|
2298
|
+
fi.ctx,
|
|
2299
|
+
sf,
|
|
2300
|
+
);
|
|
2301
|
+
const iterResult = new LuaMultiRes(
|
|
2302
|
+
isPromise(callRes) ? await callRes : callRes,
|
|
2303
|
+
).flatten();
|
|
2304
|
+
const nextControl = iterResult.values[0];
|
|
2305
|
+
if (nextControl === null || nextControl === undefined) {
|
|
2306
|
+
break;
|
|
2307
|
+
}
|
|
2308
|
+
control = nextControl;
|
|
2309
|
+
|
|
2310
|
+
const localEnv = makeIterEnv();
|
|
2311
|
+
setIterVars(localEnv, fi.names, iterResult.values);
|
|
2312
|
+
|
|
2313
|
+
const r = evalStatement(fi.block, localEnv, sf, returnOnReturn);
|
|
2314
|
+
const res = isPromise(r) ? await r : r;
|
|
2315
|
+
if (res !== undefined) {
|
|
2316
|
+
if (isBreakSignal(res)) {
|
|
2317
|
+
break;
|
|
2318
|
+
}
|
|
2319
|
+
return await finish(res);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return await finish(undefined);
|
|
2323
|
+
};
|
|
2324
|
+
|
|
2325
|
+
while (true) {
|
|
2326
|
+
const iterCall = luaCall(
|
|
2327
|
+
iteratorValue,
|
|
2328
|
+
[state, control],
|
|
2329
|
+
fi.ctx,
|
|
2330
|
+
sf,
|
|
2331
|
+
);
|
|
2332
|
+
if (isPromise(iterCall)) {
|
|
2333
|
+
return (iterCall as Promise<any>).then((itv) => {
|
|
2334
|
+
const iterResult = new LuaMultiRes(itv).flatten();
|
|
2335
|
+
const nextControl = iterResult.values[0];
|
|
2336
|
+
if (nextControl === null || nextControl === undefined) {
|
|
2337
|
+
const r = finish(undefined);
|
|
2338
|
+
if (isPromise(r)) return (r as Promise<void>).then(() => {});
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
control = nextControl;
|
|
2342
|
+
|
|
2343
|
+
const localEnv = makeIterEnv();
|
|
2344
|
+
setIterVars(localEnv, fi.names, iterResult.values);
|
|
2345
|
+
|
|
2346
|
+
const r = evalStatement(
|
|
2347
|
+
fi.block,
|
|
2348
|
+
localEnv,
|
|
2349
|
+
sf,
|
|
2350
|
+
returnOnReturn,
|
|
2351
|
+
);
|
|
2352
|
+
if (isPromise(r)) {
|
|
2353
|
+
return (r as Promise<any>).then((res) => {
|
|
2354
|
+
if (res !== undefined) {
|
|
2355
|
+
if (isBreakSignal(res)) {
|
|
2356
|
+
return finish(undefined);
|
|
2357
|
+
}
|
|
2358
|
+
return rpThen(finish(undefined), () => res);
|
|
2359
|
+
}
|
|
2360
|
+
return runAsync();
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
if (r !== undefined) {
|
|
2364
|
+
if (isBreakSignal(r)) {
|
|
2365
|
+
return finish(undefined);
|
|
2366
|
+
}
|
|
2367
|
+
return rpThen(finish(undefined), () => r);
|
|
2368
|
+
}
|
|
2369
|
+
return runAsync();
|
|
2370
|
+
}).catch((e: any) => finishErr(e));
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
const iterResult = new LuaMultiRes(iterCall).flatten();
|
|
2374
|
+
const nextControl = iterResult.values[0];
|
|
2375
|
+
if (nextControl === null || nextControl === undefined) {
|
|
2376
|
+
const r = finish(undefined);
|
|
2377
|
+
if (isPromise(r)) {
|
|
2378
|
+
return (r as Promise<void>);
|
|
2379
|
+
}
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
control = nextControl;
|
|
2383
|
+
|
|
2384
|
+
const localEnv = makeIterEnv();
|
|
2385
|
+
setIterVars(localEnv, fi.names, iterResult.values);
|
|
2386
|
+
|
|
2387
|
+
const r = evalStatement(fi.block, localEnv, sf, returnOnReturn);
|
|
2388
|
+
if (isPromise(r)) {
|
|
2389
|
+
return (r as Promise<any>).then((res) => {
|
|
2390
|
+
if (res !== undefined) {
|
|
2391
|
+
if (isBreakSignal(res)) {
|
|
2392
|
+
return finish(undefined);
|
|
2393
|
+
}
|
|
2394
|
+
return rpThen(finish(undefined), () => res);
|
|
2395
|
+
}
|
|
2396
|
+
return runAsync();
|
|
2397
|
+
}).catch((e: any) => finishErr(e));
|
|
2398
|
+
}
|
|
2399
|
+
if (r !== undefined) {
|
|
2400
|
+
if (isBreakSignal(r)) {
|
|
2401
|
+
return finish(undefined);
|
|
2402
|
+
}
|
|
2403
|
+
return rpThen(finish(undefined), () => r);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
} catch (e: any) {
|
|
2407
|
+
return finishErr(e);
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
|
|
2411
|
+
if (isPromise(exprVals)) {
|
|
2412
|
+
return (exprVals as Promise<any[]>).then(afterExprs);
|
|
2413
|
+
}
|
|
2414
|
+
return afterExprs(exprVals as any[]);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
function evalLValue(
|
|
2420
|
+
lval: LuaLValue,
|
|
2421
|
+
env: LuaEnv,
|
|
2422
|
+
sf: LuaStackFrame,
|
|
2423
|
+
): LuaLValueContainer | Promise<LuaLValueContainer> {
|
|
2424
|
+
switch (lval.type) {
|
|
2425
|
+
case "Variable": {
|
|
2426
|
+
const v = asLValueVariable(lval);
|
|
2427
|
+
return {
|
|
2428
|
+
env,
|
|
2429
|
+
key: v.name,
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
case "TableAccess": {
|
|
2433
|
+
const ta = asLValueTableAccess(lval);
|
|
2434
|
+
const objValue = evalExpression(
|
|
2435
|
+
ta.object,
|
|
2436
|
+
env,
|
|
2437
|
+
sf,
|
|
2438
|
+
);
|
|
2439
|
+
const keyValue = evalExpression(ta.key, env, sf);
|
|
2440
|
+
if (
|
|
2441
|
+
isPromise(objValue) ||
|
|
2442
|
+
isPromise(keyValue)
|
|
2443
|
+
) {
|
|
2444
|
+
return Promise.all([
|
|
2445
|
+
isPromise(objValue) ? objValue : Promise.resolve(objValue),
|
|
2446
|
+
isPromise(keyValue) ? keyValue : Promise.resolve(keyValue),
|
|
2447
|
+
]).then(([objValue, keyValue]) => ({
|
|
2448
|
+
env: singleResult(objValue),
|
|
2449
|
+
key: singleResult(keyValue),
|
|
2450
|
+
}));
|
|
2451
|
+
}
|
|
2452
|
+
return {
|
|
2453
|
+
env: singleResult(objValue),
|
|
2454
|
+
key: singleResult(keyValue),
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
case "PropertyAccess": {
|
|
2458
|
+
const pa = asLValuePropertyAccess(lval);
|
|
2459
|
+
const objValue = evalExpression(
|
|
2460
|
+
pa.object,
|
|
2461
|
+
env,
|
|
2462
|
+
sf,
|
|
2463
|
+
);
|
|
2464
|
+
if (isPromise(objValue)) {
|
|
2465
|
+
return (objValue as Promise<any>).then((ov) => {
|
|
2466
|
+
return {
|
|
2467
|
+
env: ov,
|
|
2468
|
+
key: pa.property,
|
|
2469
|
+
};
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
return {
|
|
2473
|
+
env: objValue,
|
|
2474
|
+
key: pa.property,
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|