@silverbulletmd/silverbullet 2.5.3 → 2.6.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.
Files changed (90) hide show
  1. package/README.md +4 -5
  2. package/client/asset_bundle/bundle.ts +3 -9
  3. package/client/data/datastore.ts +4 -5
  4. package/client/markdown_parser/constants.ts +3 -2
  5. package/client/plugos/hooks/code_widget.ts +3 -5
  6. package/client/plugos/hooks/command.ts +8 -8
  7. package/client/plugos/hooks/document_editor.ts +10 -12
  8. package/client/plugos/hooks/event.ts +33 -36
  9. package/client/plugos/hooks/mq.ts +17 -17
  10. package/client/plugos/hooks/plug_namespace.ts +3 -5
  11. package/client/plugos/hooks/slash_command.ts +12 -27
  12. package/client/plugos/hooks/syscall.ts +3 -3
  13. package/client/plugos/manifest_cache.ts +22 -15
  14. package/client/plugos/plug.ts +2 -5
  15. package/client/plugos/plug_compile.ts +67 -65
  16. package/client/plugos/protocol.ts +28 -28
  17. package/client/plugos/proxy_fetch.ts +7 -6
  18. package/client/plugos/sandboxes/worker_sandbox.ts +16 -15
  19. package/client/plugos/syscalls/asset.ts +1 -3
  20. package/client/plugos/syscalls/code_widget.ts +1 -3
  21. package/client/plugos/syscalls/config.ts +1 -5
  22. package/client/plugos/syscalls/datastore.ts +1 -1
  23. package/client/plugos/syscalls/editor.ts +63 -60
  24. package/client/plugos/syscalls/event.ts +9 -12
  25. package/client/plugos/syscalls/fetch.ts +30 -22
  26. package/client/plugos/syscalls/index.ts +10 -1
  27. package/client/plugos/syscalls/jsonschema.ts +72 -32
  28. package/client/plugos/syscalls/language.ts +9 -5
  29. package/client/plugos/syscalls/markdown.ts +29 -7
  30. package/client/plugos/syscalls/mq.ts +3 -11
  31. package/client/plugos/syscalls/service_registry.ts +1 -4
  32. package/client/plugos/syscalls/shell.ts +2 -5
  33. package/client/plugos/syscalls/sync.ts +69 -60
  34. package/client/plugos/syscalls/system.ts +2 -3
  35. package/client/plugos/system.ts +4 -10
  36. package/client/plugos/worker_runtime.ts +4 -3
  37. package/client/space_lua/aggregates.ts +632 -59
  38. package/client/space_lua/ast.ts +21 -9
  39. package/client/space_lua/ast_narrow.ts +4 -2
  40. package/client/space_lua/eval.ts +842 -536
  41. package/client/space_lua/labels.ts +6 -11
  42. package/client/space_lua/liq_null.ts +6 -0
  43. package/client/space_lua/numeric.ts +5 -8
  44. package/client/space_lua/parse.ts +290 -169
  45. package/client/space_lua/query_collection.ts +213 -149
  46. package/client/space_lua/render_lua_markdown.ts +369 -0
  47. package/client/space_lua/rp.ts +5 -4
  48. package/client/space_lua/runtime.ts +245 -142
  49. package/client/space_lua/stdlib/format.ts +34 -20
  50. package/client/space_lua/stdlib/js.ts +3 -7
  51. package/client/space_lua/stdlib/load.ts +1 -3
  52. package/client/space_lua/stdlib/math.ts +15 -14
  53. package/client/space_lua/stdlib/net.ts +25 -15
  54. package/client/space_lua/stdlib/os.ts +76 -85
  55. package/client/space_lua/stdlib/pattern.ts +28 -35
  56. package/client/space_lua/stdlib/prng.ts +15 -12
  57. package/client/space_lua/stdlib/space_lua.ts +16 -17
  58. package/client/space_lua/stdlib/string.ts +7 -17
  59. package/client/space_lua/stdlib/string_pack.ts +23 -19
  60. package/client/space_lua/stdlib/table.ts +5 -9
  61. package/client/space_lua/stdlib.ts +20 -30
  62. package/client/space_lua/tonumber.ts +79 -40
  63. package/client/space_lua/util.ts +14 -10
  64. package/dist/plug-compile.js +44 -41
  65. package/package.json +24 -22
  66. package/plug-api/lib/async.ts +19 -6
  67. package/plug-api/lib/crypto.ts +5 -6
  68. package/plug-api/lib/dates.ts +15 -7
  69. package/plug-api/lib/json.ts +10 -4
  70. package/plug-api/lib/ref.ts +18 -18
  71. package/plug-api/lib/resolve.ts +7 -11
  72. package/plug-api/lib/tags.ts +13 -4
  73. package/plug-api/lib/transclusion.ts +6 -17
  74. package/plug-api/lib/tree.ts +115 -43
  75. package/plug-api/lib/yaml.ts +25 -15
  76. package/plug-api/syscalls/asset.ts +1 -1
  77. package/plug-api/syscalls/config.ts +1 -4
  78. package/plug-api/syscalls/editor.ts +14 -14
  79. package/plug-api/syscalls/jsonschema.ts +1 -3
  80. package/plug-api/syscalls/lua.ts +3 -9
  81. package/plug-api/syscalls/mq.ts +1 -4
  82. package/plug-api/syscalls/shell.ts +4 -1
  83. package/plug-api/syscalls/space.ts +3 -10
  84. package/plug-api/syscalls/system.ts +1 -4
  85. package/plug-api/syscalls/yaml.ts +2 -6
  86. package/plug-api/types/client.ts +16 -1
  87. package/plug-api/types/event.ts +6 -4
  88. package/plug-api/types/manifest.ts +8 -9
  89. package/plugs/builtin_plugs.ts +2 -2
  90. package/dist/worker_runtime_bundle.js +0 -233
@@ -4,6 +4,7 @@ import type {
4
4
  LuaExpression,
5
5
  LuaLValue,
6
6
  LuaStatement,
7
+ LuaTableField,
7
8
  NumericType,
8
9
  } from "./ast.ts";
9
10
  import { LuaAttribute } from "./ast.ts";
@@ -38,7 +39,11 @@ import {
38
39
  luaValueToJS,
39
40
  singleResult,
40
41
  } from "./runtime.ts";
41
- import { type LuaCollectionQuery, toCollection } from "./query_collection.ts";
42
+ import {
43
+ type LuaCollectionQuery,
44
+ type LuaGroupByEntry,
45
+ toCollection,
46
+ } from "./query_collection.ts";
42
47
  import {
43
48
  coerceNumericPair,
44
49
  coerceToNumber,
@@ -105,8 +110,14 @@ function astNumberKind(e: LuaExpression | undefined): NumericType | undefined {
105
110
  result = unwrapped.numericType === "int" ? "int" : "float";
106
111
  } else if (unwrapped.type === "Binary") {
107
112
  const op = unwrapped.operator;
108
- const numericOp = op === "+" || op === "-" || op === "*" || op === "/" ||
109
- op === "//" || op === "%" || op === "^";
113
+ const numericOp =
114
+ op === "+" ||
115
+ op === "-" ||
116
+ op === "*" ||
117
+ op === "/" ||
118
+ op === "//" ||
119
+ op === "%" ||
120
+ op === "^";
110
121
 
111
122
  if (numericOp) {
112
123
  const lk = astNumberKind(unwrapped.left);
@@ -176,7 +187,6 @@ function blockMetaOrThrow(
176
187
  }
177
188
  }
178
189
 
179
-
180
190
  function arithVerbFromOperator(op: string): string | null {
181
191
  switch (op) {
182
192
  case "+":
@@ -247,67 +257,68 @@ export function luaOp(
247
257
  sf: LuaStackFrame,
248
258
  ): any {
249
259
  switch (op) {
250
- case "+":
251
- case "-":
252
- case "*":
260
+ case "+": {
261
+ // Ultra-fast path: both plain numbers with no float type annotation (int + int)
262
+ if (
263
+ typeof left === "number" &&
264
+ typeof right === "number" &&
265
+ leftType !== "float" &&
266
+ rightType !== "float"
267
+ ) {
268
+ return left + right;
269
+ }
270
+ return luaArithGeneric("+", left, right, leftType, rightType, ctx, sf);
271
+ }
272
+ case "-": {
273
+ if (
274
+ typeof left === "number" &&
275
+ typeof right === "number" &&
276
+ leftType !== "float" &&
277
+ rightType !== "float"
278
+ ) {
279
+ const r = left - right;
280
+ // Integer subtraction can produce -0 (e.g. 0 - 0), normalize to +0
281
+ return r === 0 ? 0 : r;
282
+ }
283
+ return luaArithGeneric("-", left, right, leftType, rightType, ctx, sf);
284
+ }
285
+ case "*": {
286
+ if (
287
+ typeof left === "number" &&
288
+ typeof right === "number" &&
289
+ leftType !== "float" &&
290
+ rightType !== "float"
291
+ ) {
292
+ const r = left * right;
293
+ // Integer multiplication can produce -0 (e.g. 0 * -1), normalize to +0
294
+ return r === 0 ? 0 : r;
295
+ }
296
+ return luaArithGeneric("*", left, right, leftType, rightType, ctx, sf);
297
+ }
253
298
  case "/":
254
299
  case "^": {
255
- const ar = numericArith[op as NumericArithOp];
256
- try {
257
- const { left: l, right: r, resultType } = coerceNumericPair(
258
- left,
259
- right,
260
- leftType,
261
- rightType,
262
- op,
263
- );
264
-
265
- let result = ar.f(l, r);
266
-
267
- if (
268
- ar.special === "sub" &&
269
- result === 0 &&
270
- isNegativeZero(result) &&
271
- resultType === "float"
272
- ) {
273
- const rhsIsIntZero = r === 0 && rightType === "int";
274
- if (rhsIsIntZero) {
275
- result = 0;
276
- }
277
- }
278
-
279
- const normalized = normalizeArithmeticResult(result, resultType);
280
-
281
- // Operators `/` and `^` produce float, wrap only if needed.
282
- if (op === "/" || op === "^") {
283
- if (normalized === 0) {
284
- return makeLuaZero(normalized, "float");
285
- }
286
- if (!Number.isFinite(normalized)) {
287
- return normalized;
288
- }
289
- if (!Number.isInteger(normalized)) {
290
- return normalized;
291
- }
292
- return makeLuaFloat(normalized);
293
- }
294
-
295
- if (normalized === 0) {
296
- return makeLuaZero(normalized, resultType);
297
- }
298
- if (resultType === "float" && Number.isInteger(normalized)) {
299
- return makeLuaFloat(normalized);
300
- }
301
- return normalized;
302
- } catch (e: any) {
303
- const meta = evalMetamethod(left, right, ar.metaMethod, ctx, sf);
304
- if (meta !== undefined) {
305
- return meta;
306
- }
307
- return arithCoercionErrorOrThrow(op, left, right, ctx, sf, e);
308
- }
300
+ return luaArithGeneric(
301
+ op as NumericArithOp,
302
+ left,
303
+ right,
304
+ leftType,
305
+ rightType,
306
+ ctx,
307
+ sf,
308
+ );
309
309
  }
310
310
  case "..": {
311
+ // Fast path: string .. string (most common in SilverBullet — key building, templates)
312
+ if (typeof left === "string" && typeof right === "string") {
313
+ return left + right;
314
+ }
315
+ // Fast path: string .. number or number .. string
316
+ if (typeof left === "string" && typeof right === "number") {
317
+ return left + luaFormatNumber(right);
318
+ }
319
+ if (typeof left === "number" && typeof right === "string") {
320
+ return luaFormatNumber(left) + right;
321
+ }
311
322
  try {
312
323
  const coerce = (v: any): string => {
313
324
  if (v === null || v === undefined) {
@@ -341,27 +352,54 @@ export function luaOp(
341
352
  }
342
353
  }
343
354
  case "==": {
355
+ // Fast path for same-type primitives
356
+ if (typeof left === typeof right && typeof left !== "object") {
357
+ return left === right;
358
+ }
344
359
  if (luaEquals(left, right)) return true;
345
360
  return luaEqWithMetamethod(left, right, ctx, sf);
346
361
  }
347
362
  case "~=":
348
363
  case "!=": {
364
+ if (typeof left === typeof right && typeof left !== "object") {
365
+ return left !== right;
366
+ }
349
367
  if (luaEquals(left, right)) {
350
368
  return false;
351
369
  }
352
370
  return !luaEqWithMetamethod(left, right, ctx, sf);
353
371
  }
354
372
  case "<": {
373
+ // Fast path: both plain numbers
374
+ if (typeof left === "number" && typeof right === "number") {
375
+ return left < right;
376
+ }
377
+ // Fast path: both strings
378
+ if (typeof left === "string" && typeof right === "string") {
379
+ return left < right;
380
+ }
355
381
  return luaRelWithMetamethod("<", left, right, ctx, sf);
356
382
  }
357
383
  case "<=": {
384
+ if (typeof left === "number" && typeof right === "number")
385
+ return left <= right;
386
+ if (typeof left === "string" && typeof right === "string")
387
+ return left <= right;
358
388
  return luaRelWithMetamethod("<=", left, right, ctx, sf);
359
389
  }
360
390
  // Lua: `a>b` is `b<a`, `a>=b` is `b<=a`
361
391
  case ">": {
392
+ if (typeof left === "number" && typeof right === "number")
393
+ return left > right;
394
+ if (typeof left === "string" && typeof right === "string")
395
+ return left > right;
362
396
  return luaRelWithMetamethod("<", right, left, ctx, sf);
363
397
  }
364
398
  case ">=": {
399
+ if (typeof left === "number" && typeof right === "number")
400
+ return left >= right;
401
+ if (typeof left === "string" && typeof right === "string")
402
+ return left >= right;
365
403
  return luaRelWithMetamethod("<=", right, left, ctx, sf);
366
404
  }
367
405
  }
@@ -392,13 +430,79 @@ export function luaOp(
392
430
  }
393
431
  }
394
432
 
433
+ function luaArithGeneric(
434
+ op: NumericArithOp,
435
+ left: any,
436
+ right: any,
437
+ leftType: NumericType | undefined,
438
+ rightType: NumericType | undefined,
439
+ ctx: ASTCtx,
440
+ sf: LuaStackFrame,
441
+ ): any {
442
+ const ar = numericArith[op];
443
+ try {
444
+ const {
445
+ left: l,
446
+ right: r,
447
+ resultType,
448
+ } = coerceNumericPair(left, right, leftType, rightType, op);
449
+
450
+ let result = ar.f(l, r);
451
+
452
+ if (
453
+ ar.special === "sub" &&
454
+ result === 0 &&
455
+ isNegativeZero(result) &&
456
+ resultType === "float"
457
+ ) {
458
+ const rhsIsIntZero = r === 0 && rightType === "int";
459
+ if (rhsIsIntZero) {
460
+ result = 0;
461
+ }
462
+ }
463
+
464
+ const normalized = normalizeArithmeticResult(result, resultType);
465
+
466
+ // Operators `/` and `^` produce float, wrap only if needed.
467
+ if (op === "/" || op === "^") {
468
+ if (normalized === 0) {
469
+ return makeLuaZero(normalized, "float");
470
+ }
471
+ if (!Number.isFinite(normalized)) {
472
+ return normalized;
473
+ }
474
+ if (!Number.isInteger(normalized)) {
475
+ return normalized;
476
+ }
477
+ return makeLuaFloat(normalized);
478
+ }
479
+
480
+ if (normalized === 0) {
481
+ return makeLuaZero(normalized, resultType);
482
+ }
483
+ if (resultType === "float" && Number.isInteger(normalized)) {
484
+ return makeLuaFloat(normalized);
485
+ }
486
+ return normalized;
487
+ } catch (e: any) {
488
+ const meta = evalMetamethod(left, right, ar.metaMethod, ctx, sf);
489
+ if (meta !== undefined) {
490
+ return meta;
491
+ }
492
+ return arithCoercionErrorOrThrow(op, left, right, ctx, sf, e);
493
+ }
494
+ }
495
+
395
496
  type NumericArithOp = "+" | "-" | "*" | "/" | "^";
396
497
 
397
- const numericArith: Record<NumericArithOp, {
398
- metaMethod: "__add" | "__sub" | "__mul" | "__div" | "__pow";
399
- f: (l: number, r: number) => number;
400
- special?: "sub";
401
- }> = {
498
+ const numericArith: Record<
499
+ NumericArithOp,
500
+ {
501
+ metaMethod: "__add" | "__sub" | "__mul" | "__div" | "__pow";
502
+ f: (l: number, r: number) => number;
503
+ special?: "sub";
504
+ }
505
+ > = {
402
506
  "+": { metaMethod: "__add", f: (l, r) => l + r },
403
507
  "-": { metaMethod: "__sub", f: (l, r) => l - r, special: "sub" },
404
508
  "*": { metaMethod: "__mul", f: (l, r) => l * r },
@@ -448,10 +552,7 @@ function luaFloorDiv(
448
552
  );
449
553
 
450
554
  if (resultType === "int" && right === 0) {
451
- throw new LuaRuntimeError(
452
- `attempt to divide by zero`,
453
- sf.withCtx(ctx),
454
- );
555
+ throw new LuaRuntimeError(`attempt to divide by zero`, sf.withCtx(ctx));
455
556
  }
456
557
 
457
558
  const result = Math.floor(left / right);
@@ -482,10 +583,7 @@ function luaMod(
482
583
  );
483
584
 
484
585
  if (resultType === "int" && right === 0) {
485
- throw new LuaRuntimeError(
486
- `attempt to perform 'n%0'`,
487
- sf.withCtx(ctx),
488
- );
586
+ throw new LuaRuntimeError(`attempt to perform 'n%0'`, sf.withCtx(ctx));
489
587
  }
490
588
 
491
589
  const q = Math.floor(left / right);
@@ -506,10 +604,7 @@ function luaMod(
506
604
  return normalized;
507
605
  }
508
606
 
509
- function luaUnaryMinus(
510
- v: number,
511
- numType: NumericType | undefined,
512
- ): number {
607
+ function luaUnaryMinus(v: number, numType: NumericType | undefined): number {
513
608
  const vType = numType ?? inferNumericType(v);
514
609
 
515
610
  if (v === 0 && vType === "int") {
@@ -523,17 +618,20 @@ function luaUnaryMinus(
523
618
  return -v;
524
619
  }
525
620
 
526
- const operatorsMetaMethods: Record<string, {
527
- metaMethod?: string;
528
- nativeImplementation: (
529
- a: LuaValue,
530
- b: LuaValue,
531
- leftType: NumericType | undefined,
532
- rightType: NumericType | undefined,
533
- ctx: ASTCtx,
534
- sf: LuaStackFrame,
535
- ) => LuaValue;
536
- }> = {
621
+ const operatorsMetaMethods: Record<
622
+ string,
623
+ {
624
+ metaMethod?: string;
625
+ nativeImplementation: (
626
+ a: LuaValue,
627
+ b: LuaValue,
628
+ leftType: NumericType | undefined,
629
+ rightType: NumericType | undefined,
630
+ ctx: ASTCtx,
631
+ sf: LuaStackFrame,
632
+ ) => LuaValue;
633
+ }
634
+ > = {
537
635
  "//": {
538
636
  metaMethod: "__idiv",
539
637
  nativeImplementation: (a, b, lt, rt, ctx, sf) =>
@@ -596,6 +694,168 @@ const operatorsMetaMethods: Record<string, {
596
694
  },
597
695
  };
598
696
 
697
+ function deriveFieldName(e: LuaExpression): string | undefined {
698
+ switch (e.type) {
699
+ case "Variable":
700
+ return e.name;
701
+ case "PropertyAccess":
702
+ return e.property;
703
+ case "FunctionCall":
704
+ if (e.name) return e.name;
705
+ if (e.prefix.type === "Variable") return e.prefix.name;
706
+ if (e.prefix.type === "PropertyAccess") return e.prefix.property;
707
+ return undefined;
708
+ case "FilteredCall":
709
+ return deriveFieldName(e.call);
710
+ case "AggregateCall":
711
+ return deriveFieldName((e as any).call);
712
+ default:
713
+ return undefined;
714
+ }
715
+ }
716
+
717
+ function fieldsToExpression(
718
+ fields: LuaTableField[],
719
+ ctx: ASTCtx,
720
+ ): LuaExpression {
721
+ if (fields.length === 1 && fields[0].type === "ExpressionField") {
722
+ return fields[0].value;
723
+ }
724
+ const promoted: LuaTableField[] = fields.map((f) => {
725
+ if (f.type !== "ExpressionField") return f;
726
+ const key = deriveFieldName(f.value);
727
+ if (key) {
728
+ return {
729
+ type: "PropField",
730
+ key,
731
+ value: f.value,
732
+ ctx: f.ctx,
733
+ } as LuaTableField;
734
+ }
735
+ return f;
736
+ });
737
+ return { type: "TableConstructor", fields: promoted, ctx };
738
+ }
739
+
740
+ function fieldsToGroupByEntries(fields: LuaTableField[]): LuaGroupByEntry[] {
741
+ return fields.map((f) => {
742
+ switch (f.type) {
743
+ case "PropField":
744
+ return { expr: f.value, alias: f.key };
745
+ case "ExpressionField":
746
+ return { expr: f.value };
747
+ case "DynamicField":
748
+ return { expr: f.value };
749
+ }
750
+ });
751
+ }
752
+
753
+ type FromSource =
754
+ | { kind: "single"; objectVariable?: string; expression: LuaExpression }
755
+ | { kind: "cross"; sources: { name: string; expression: LuaExpression }[] };
756
+
757
+ function fromFieldsToSource(fields: LuaTableField[], ctx: ASTCtx): FromSource {
758
+ if (fields.length === 1) {
759
+ const f = fields[0];
760
+ if (f.type === "ExpressionField") {
761
+ return { kind: "single", expression: f.value };
762
+ }
763
+ if (f.type === "PropField") {
764
+ return { kind: "single", objectVariable: f.key, expression: f.value };
765
+ }
766
+ }
767
+
768
+ const sources: { name: string; expression: LuaExpression }[] = [];
769
+ for (const f of fields) {
770
+ if (f.type !== "PropField") {
771
+ throw new LuaRuntimeError("Multi-source 'from' requires named sources", {
772
+ ref: ctx,
773
+ } as any);
774
+ }
775
+ sources.push({ name: f.key, expression: f.value });
776
+ }
777
+ return { kind: "cross", sources };
778
+ }
779
+
780
+ async function normalizeToArray(
781
+ collection: LuaValue,
782
+ sf: LuaStackFrame,
783
+ ): Promise<any[]> {
784
+ if (collection instanceof LuaTable && collection.empty()) {
785
+ return [];
786
+ }
787
+ if (collection instanceof LuaTable) {
788
+ if (collection.length > 0) {
789
+ const arr: any[] = [];
790
+ for (let i = 1; i <= collection.length; i++) {
791
+ arr.push(collection.rawGet(i));
792
+ }
793
+ return arr;
794
+ }
795
+ return [collection];
796
+ }
797
+ if (Array.isArray(collection)) {
798
+ return collection;
799
+ }
800
+ if (
801
+ typeof collection === "object" &&
802
+ collection !== null &&
803
+ "query" in collection &&
804
+ typeof (collection as any).query === "function"
805
+ ) {
806
+ const allItems = await (collection as any).query(
807
+ { distinct: false },
808
+ new LuaEnv(),
809
+ sf,
810
+ );
811
+ return Array.isArray(allItems) ? allItems : [allItems];
812
+ }
813
+ const jsVal = luaValueToJS(collection, sf);
814
+ return Array.isArray(jsVal) ? jsVal : [jsVal];
815
+ }
816
+
817
+ async function evalCrossJoinSources(
818
+ sources: { name: string; expression: LuaExpression }[],
819
+ env: LuaEnv,
820
+ sf: LuaStackFrame,
821
+ ctx: ASTCtx,
822
+ ): Promise<LuaTable[]> {
823
+ // Evaluate each source and normalize to arrays
824
+ const arrays: { name: string; items: any[] }[] = [];
825
+ for (const src of sources) {
826
+ const val = await evalExpression(src.expression, env, sf);
827
+ if (val === null || val === undefined) {
828
+ throw new LuaRuntimeError(
829
+ `Cross-join source '${src.name}' is nil`,
830
+ sf.withCtx(ctx),
831
+ );
832
+ }
833
+ const items = await normalizeToArray(val, sf);
834
+ arrays.push({ name: src.name, items });
835
+ }
836
+
837
+ // Cartesian product
838
+ let product: Record<string, any>[] = [{}];
839
+ for (const { name, items } of arrays) {
840
+ const newProduct: Record<string, any>[] = [];
841
+ for (const combo of product) {
842
+ for (const item of items) {
843
+ newProduct.push({ ...combo, [name]: item });
844
+ }
845
+ }
846
+ product = newProduct;
847
+ }
848
+
849
+ // Convert each combination to a `LuaTable` row
850
+ return product.map((combo) => {
851
+ const row = new LuaTable();
852
+ for (const key in combo) {
853
+ void row.rawSet(key, combo[key]);
854
+ }
855
+ return row;
856
+ });
857
+ }
858
+
599
859
  export function evalExpression(
600
860
  e: LuaExpression,
601
861
  env: LuaEnv,
@@ -629,14 +889,7 @@ export function evalExpression(
629
889
  if (b.operator === "and") {
630
890
  return evalLogical("and", b.left, b.right, env, sf);
631
891
  }
632
- return evalBinaryWithLR(
633
- b.operator,
634
- b.left,
635
- b.right,
636
- b.ctx,
637
- env,
638
- sf,
639
- );
892
+ return evalBinaryWithLR(b.operator, b.left, b.right, b.ctx, env, sf);
640
893
  }
641
894
  case "Unary": {
642
895
  const u = asUnary(e);
@@ -660,59 +913,53 @@ export function evalExpression(
660
913
  const applyTyped = (typed: TypedValue) => {
661
914
  const arg = singleResult(typed.value);
662
915
 
663
- return unaryWithMeta(
664
- arg,
665
- "__unm",
666
- u.ctx,
667
- sf,
668
- () => {
669
- // Numeric-string coercion for unary minus
670
- if (typeof arg === "string") {
671
- const n = coerceToNumber(arg);
672
- if (n === null) {
673
- throw new LuaRuntimeError(
674
- "attempt to unm a 'string' with a 'string'",
675
- sf.withCtx(u.ctx),
676
- );
677
- }
678
- if (n === 0) {
679
- return 0;
680
- }
681
- return -n;
682
- }
683
-
684
- const plain = untagNumber(arg);
685
- if (typeof plain !== "number") {
916
+ return unaryWithMeta(arg, "__unm", u.ctx, sf, () => {
917
+ // Numeric-string coercion for unary minus
918
+ if (typeof arg === "string") {
919
+ const n = coerceToNumber(arg);
920
+ if (n === null) {
686
921
  throw new LuaRuntimeError(
687
- "attempt to perform arithmetic on a table value",
922
+ "attempt to unm a 'string' with a 'string'",
688
923
  sf.withCtx(u.ctx),
689
924
  );
690
925
  }
926
+ if (n === 0) {
927
+ return 0;
928
+ }
929
+ return -n;
930
+ }
691
931
 
692
- const argType = isTaggedFloat(arg)
693
- ? "float"
694
- : astNumberKind(u.argument);
932
+ const plain = untagNumber(arg);
933
+ if (typeof plain !== "number") {
934
+ throw new LuaRuntimeError(
935
+ "attempt to perform arithmetic on a table value",
936
+ sf.withCtx(u.ctx),
937
+ );
938
+ }
695
939
 
696
- const out = luaUnaryMinus(plain, argType);
940
+ const argType = isTaggedFloat(arg)
941
+ ? "float"
942
+ : astNumberKind(u.argument);
697
943
 
698
- // If the operand is a float-tagged boxed number, unary
699
- // minus must keep the result float-typed.
700
- if (isTaggedFloat(arg)) {
701
- if (out === 0) {
702
- return makeLuaZero(out, "float");
703
- }
704
- return makeLuaFloat(out);
705
- }
944
+ const out = luaUnaryMinus(plain, argType);
706
945
 
707
- // Preserve numeric kind for zero results
946
+ // If the operand is a float-tagged boxed number, unary
947
+ // minus must keep the result float-typed.
948
+ if (isTaggedFloat(arg)) {
708
949
  if (out === 0) {
709
- const outType = argType ?? inferNumericType(plain);
710
- return makeLuaZero(out, outType);
950
+ return makeLuaZero(out, "float");
711
951
  }
952
+ return makeLuaFloat(out);
953
+ }
712
954
 
713
- return out;
714
- },
715
- );
955
+ // Preserve numeric kind for zero results
956
+ if (out === 0) {
957
+ const outType = argType ?? inferNumericType(plain);
958
+ return makeLuaZero(out, outType);
959
+ }
960
+
961
+ return out;
962
+ });
716
963
  };
717
964
 
718
965
  return rpThen(tv as any, applyTyped);
@@ -727,35 +974,29 @@ export function evalExpression(
727
974
  }
728
975
  case "~": {
729
976
  const arg = singleResult(value);
730
- return unaryWithMeta(
731
- arg,
732
- "__bnot",
733
- u.ctx,
734
- sf,
735
- () => {
736
- const intVal = toInteger(arg);
737
- if (intVal === null) {
738
- if (typeof arg === "string") {
739
- throw new LuaRuntimeError(
740
- `attempt to perform bitwise operation on a string value (constant '${arg}')`,
741
- sf.withCtx(u.ctx),
742
- );
743
- }
744
- const t = luaTypeName(arg);
745
- if (t === "number") {
746
- throw new LuaRuntimeError(
747
- `number has no integer representation`,
748
- sf.withCtx(u.ctx),
749
- );
750
- }
977
+ return unaryWithMeta(arg, "__bnot", u.ctx, sf, () => {
978
+ const intVal = toInteger(arg);
979
+ if (intVal === null) {
980
+ if (typeof arg === "string") {
751
981
  throw new LuaRuntimeError(
752
- `attempt to perform bitwise operation on a ${t} value`,
982
+ `attempt to perform bitwise operation on a string value (constant '${arg}')`,
753
983
  sf.withCtx(u.ctx),
754
984
  );
755
985
  }
756
- return ~intVal;
757
- },
758
- );
986
+ const t = luaTypeName(arg);
987
+ if (t === "number") {
988
+ throw new LuaRuntimeError(
989
+ `number has no integer representation`,
990
+ sf.withCtx(u.ctx),
991
+ );
992
+ }
993
+ throw new LuaRuntimeError(
994
+ `attempt to perform bitwise operation on a ${t} value`,
995
+ sf.withCtx(u.ctx),
996
+ );
997
+ }
998
+ return ~intVal;
999
+ });
759
1000
  }
760
1001
  case "#": {
761
1002
  return luaLengthOp(singleResult(value), u.ctx, sf);
@@ -779,31 +1020,63 @@ export function evalExpression(
779
1020
  }
780
1021
  case "TableConstructor": {
781
1022
  const tc = asTableConstructor(e);
782
- return Promise.resolve().then(async () => {
783
- const table = new LuaTable();
784
- // Expression fields assign consecutive integer keys starting
785
- // at 1 and advance even when the value is `nil`.
786
- let nextArrayIndex = 1;
787
- for (const field of tc.fields) {
1023
+ const table = new LuaTable();
1024
+ // Expression fields assign consecutive integer keys starting
1025
+ // at 1 and advance even when the value is `nil`.
1026
+ let nextArrayIndex = 1;
1027
+
1028
+ const processField = (
1029
+ fieldIdx: number,
1030
+ ): LuaTable | Promise<LuaTable> => {
1031
+ for (let fi = fieldIdx; fi < tc.fields.length; fi++) {
1032
+ const field = tc.fields[fi];
788
1033
  switch (field.type) {
789
1034
  case "PropField": {
790
- const value = await evalExpression(field.value, env, sf);
791
- table.set(field.key, singleResult(value), sf);
1035
+ const value = evalExpression(field.value, env, sf);
1036
+ if (isPromise(value)) {
1037
+ return (value as Promise<any>).then((v) => {
1038
+ void table.set(field.key, singleResult(v), sf);
1039
+ return processField(fi + 1);
1040
+ });
1041
+ }
1042
+ void table.set(field.key, singleResult(value), sf);
792
1043
  break;
793
1044
  }
794
1045
  case "DynamicField": {
795
- const key = await evalExpression(field.key, env, sf);
796
- const value = await evalExpression(field.value, env, sf);
797
- table.set(singleResult(key), singleResult(value), sf);
1046
+ const key = evalExpression(field.key, env, sf);
1047
+ const val = evalExpression(field.value, env, sf);
1048
+ if (isPromise(key) || isPromise(val)) {
1049
+ return rpThen(key, (k) =>
1050
+ rpThen(val, (v) => {
1051
+ void table.set(singleResult(k), singleResult(v), sf);
1052
+ return processField(fi + 1);
1053
+ }),
1054
+ ) as Promise<LuaTable>;
1055
+ }
1056
+ void table.set(singleResult(key), singleResult(val), sf);
798
1057
  break;
799
1058
  }
800
1059
  case "ExpressionField": {
801
- const value = await evalExpression(field.value, env, sf);
802
-
1060
+ const value = evalExpression(field.value, env, sf);
1061
+ if (isPromise(value)) {
1062
+ return (value as Promise<any>).then((v) => {
1063
+ if (v instanceof LuaMultiRes) {
1064
+ const flat = v.flatten();
1065
+ for (let j = 0; j < flat.values.length; j++) {
1066
+ table.rawSetArrayIndex(nextArrayIndex, flat.values[j]);
1067
+ nextArrayIndex++;
1068
+ }
1069
+ } else {
1070
+ table.rawSetArrayIndex(nextArrayIndex, singleResult(v));
1071
+ nextArrayIndex++;
1072
+ }
1073
+ return processField(fi + 1);
1074
+ });
1075
+ }
803
1076
  if (value instanceof LuaMultiRes) {
804
1077
  const flat = value.flatten();
805
- for (let i = 0; i < flat.values.length; i++) {
806
- table.rawSetArrayIndex(nextArrayIndex, flat.values[i]);
1078
+ for (let j = 0; j < flat.values.length; j++) {
1079
+ table.rawSetArrayIndex(nextArrayIndex, flat.values[j]);
807
1080
  nextArrayIndex++;
808
1081
  }
809
1082
  } else {
@@ -815,7 +1088,9 @@ export function evalExpression(
815
1088
  }
816
1089
  }
817
1090
  return table;
818
- });
1091
+ };
1092
+
1093
+ return processField(0);
819
1094
  }
820
1095
  case "FunctionDefinition": {
821
1096
  const fd = asFunctionDef(e);
@@ -825,20 +1100,95 @@ export function evalExpression(
825
1100
  const q = asQueryExpr(e);
826
1101
  const findFromClause = q.clauses.find((c) => c.type === "From");
827
1102
  if (!findFromClause) {
828
- throw new LuaRuntimeError(
829
- "No from clause found",
830
- sf.withCtx(q.ctx),
831
- );
1103
+ throw new LuaRuntimeError("No from clause found", sf.withCtx(q.ctx));
1104
+ }
1105
+ const fromSource = fromFieldsToSource(
1106
+ findFromClause.fields,
1107
+ findFromClause.ctx,
1108
+ );
1109
+
1110
+ if (fromSource.kind === "cross") {
1111
+ // Materialize Cartesian product, then query
1112
+ return (async () => {
1113
+ const rows = await evalCrossJoinSources(
1114
+ fromSource.sources,
1115
+ env,
1116
+ sf,
1117
+ findFromClause.ctx,
1118
+ );
1119
+ const collection: any = toCollection(rows);
1120
+
1121
+ // Build up query object
1122
+ const query: LuaCollectionQuery = {
1123
+ objectVariable: undefined,
1124
+ distinct: true,
1125
+ };
1126
+
1127
+ // Map clauses to query parameters
1128
+ for (const clause of q.clauses) {
1129
+ switch (clause.type) {
1130
+ case "Where": {
1131
+ query.where = clause.expression;
1132
+ break;
1133
+ }
1134
+ case "OrderBy": {
1135
+ query.orderBy = clause.orderBy.map((o) => ({
1136
+ expr: o.expression,
1137
+ desc: o.direction === "desc",
1138
+ nulls: o.nulls,
1139
+ using: o.using,
1140
+ }));
1141
+ break;
1142
+ }
1143
+ case "Select": {
1144
+ query.select = fieldsToExpression(clause.fields, clause.ctx);
1145
+ break;
1146
+ }
1147
+ case "Limit": {
1148
+ const limitVal = await evalExpression(clause.limit, env, sf);
1149
+ query.limit = Number(limitVal);
1150
+ if (clause.offset) {
1151
+ const offsetVal = await evalExpression(
1152
+ clause.offset,
1153
+ env,
1154
+ sf,
1155
+ );
1156
+ query.offset = Number(offsetVal);
1157
+ }
1158
+ break;
1159
+ }
1160
+ case "Offset": {
1161
+ const offsetVal = await evalExpression(
1162
+ clause.offset,
1163
+ env,
1164
+ sf,
1165
+ );
1166
+ query.offset = Number(offsetVal);
1167
+ break;
1168
+ }
1169
+ case "GroupBy": {
1170
+ query.groupBy = fieldsToGroupByEntries(clause.fields);
1171
+ break;
1172
+ }
1173
+ case "Having": {
1174
+ query.having = clause.expression;
1175
+ break;
1176
+ }
1177
+ }
1178
+ }
1179
+
1180
+ return (collection as any)
1181
+ .query(query, env, sf, globalThis.client?.config)
1182
+ .then(jsToLuaValue);
1183
+ })();
832
1184
  }
833
- const objectVariable = findFromClause.name;
834
- const objectExpression = findFromClause.expression;
1185
+
1186
+ // Single-source
1187
+ const { objectVariable, expression: objectExpression } = fromSource;
835
1188
  return Promise.resolve(evalExpression(objectExpression, env, sf)).then(
836
1189
  async (collection: LuaValue) => {
837
1190
  if (!collection) {
838
- throw new LuaRuntimeError(
839
- "Collection is nil",
840
- sf.withCtx(q.ctx),
841
- );
1191
+ throw new LuaRuntimeError("Collection is nil", sf.withCtx(q.ctx));
842
1192
  }
843
1193
 
844
1194
  // If already a queryable collection (e.g. DataStoreQueryCollection),
@@ -892,7 +1242,7 @@ export function evalExpression(
892
1242
  break;
893
1243
  }
894
1244
  case "Select": {
895
- query.select = clause.expression;
1245
+ query.select = fieldsToExpression(clause.fields, clause.ctx);
896
1246
  break;
897
1247
  }
898
1248
  case "Limit": {
@@ -908,8 +1258,17 @@ export function evalExpression(
908
1258
  }
909
1259
  break;
910
1260
  }
1261
+ case "Offset": {
1262
+ const offsetVal = await evalExpression(
1263
+ clause.offset,
1264
+ env,
1265
+ sf,
1266
+ );
1267
+ query.offset = Number(offsetVal);
1268
+ break;
1269
+ }
911
1270
  case "GroupBy": {
912
- query.groupBy = clause.expressions;
1271
+ query.groupBy = fieldsToGroupByEntries(clause.fields);
913
1272
  break;
914
1273
  }
915
1274
  case "Having": {
@@ -920,7 +1279,9 @@ export function evalExpression(
920
1279
  }
921
1280
 
922
1281
  // Always use the possibly-wrapped collection
923
- return (collection as any).query(query, env, sf).then(jsToLuaValue);
1282
+ return (collection as any)
1283
+ .query(query, env, sf, globalThis.client?.config)
1284
+ .then(jsToLuaValue);
924
1285
  },
925
1286
  );
926
1287
  }
@@ -973,13 +1334,10 @@ function evalPrefixExpression(
973
1334
  return luaGet(table, key, ta.ctx, sf);
974
1335
  }
975
1336
 
976
- return rpThen(
977
- objV,
978
- (obj) =>
979
- rpThen(
980
- keyV,
981
- (key) => luaGet(singleResult(obj), singleResult(key), ta.ctx, sf),
982
- ),
1337
+ return rpThen(objV, (obj) =>
1338
+ rpThen(keyV, (key) =>
1339
+ luaGet(singleResult(obj), singleResult(key), ta.ctx, sf),
1340
+ ),
983
1341
  );
984
1342
  }
985
1343
 
@@ -989,50 +1347,88 @@ function evalPrefixExpression(
989
1347
  // Sync-first: evaluate object; avoid Promise when object is sync.
990
1348
  const objV = evalPrefixExpression(pa.object, env, sf);
991
1349
  if (!isPromise(objV)) {
992
- return luaGet(objV, pa.property, pa.ctx, sf);
1350
+ return luaGet(singleResult(objV), pa.property, pa.ctx, sf);
993
1351
  }
994
- return rpThen(objV, (obj) => luaGet(obj, pa.property, pa.ctx, sf));
1352
+ return rpThen(objV, (obj) =>
1353
+ luaGet(singleResult(obj), pa.property, pa.ctx, sf),
1354
+ );
995
1355
  }
996
1356
 
997
1357
  case "FunctionCall": {
998
1358
  const fc = asFunctionCall(e);
999
- const prefixValue = evalPrefixExpression(fc.prefix, env, sf);
1000
- if (prefixValue === null || prefixValue === undefined) {
1001
- const nilMsg = fc.prefix.type === "Variable"
1002
- ? `attempt to call a nil value (global '${
1003
- asVariable(fc.prefix).name
1004
- }')`
1005
- : `attempt to call a nil value`;
1359
+
1360
+ // `order by` inside function arguments is only valid for aggregate
1361
+ // calls evaluated by the query engine
1362
+ if (fc.orderBy && fc.orderBy.length > 0) {
1006
1363
  throw new LuaRuntimeError(
1007
- nilMsg,
1008
- sf.withCtx(fc.prefix.ctx),
1364
+ `'order by' is not allowed in non-aggregate function calls`,
1365
+ sf.withCtx(fc.ctx),
1009
1366
  );
1010
1367
  }
1011
1368
 
1012
- let selfArgs: LuaValue[] = [];
1369
+ const prefixValue = evalPrefixExpression(fc.prefix, env, sf);
1370
+ if (prefixValue === null || prefixValue === undefined) {
1371
+ const nilMsg =
1372
+ fc.prefix.type === "Variable"
1373
+ ? `attempt to call a nil value (global '${
1374
+ asVariable(fc.prefix).name
1375
+ }')`
1376
+ : `attempt to call a nil value`;
1377
+ throw new LuaRuntimeError(nilMsg, sf.withCtx(fc.prefix.ctx));
1378
+ }
1379
+
1380
+ // Fast path: non-method call with sync prefix
1381
+ if (!fc.name && !isPromise(prefixValue)) {
1382
+ const argsVal = evalExpressions(fc.args, env, sf);
1383
+ if (!isPromise(argsVal)) {
1384
+ return luaCall(prefixValue, argsVal as LuaValue[], fc.ctx, sf);
1385
+ }
1386
+ return (argsVal as Promise<LuaValue[]>).then((args) =>
1387
+ luaCall(prefixValue, args, fc.ctx, sf),
1388
+ );
1389
+ }
1013
1390
 
1014
1391
  const handleFunctionCall = (
1015
1392
  calleeVal: LuaValue,
1393
+ selfArgs: LuaValue[],
1016
1394
  ): LuaValue | Promise<LuaValue> => {
1017
1395
  // Normal argument handling for hello:there(a, b, c) type calls
1018
1396
  if (fc.name) {
1019
- selfArgs = [calleeVal];
1397
+ const self = calleeVal;
1020
1398
  calleeVal = luaIndexValue(calleeVal, fc.name, sf);
1021
1399
 
1022
1400
  if (isPromise(calleeVal)) {
1023
- return (calleeVal as Promise<any>).then(handleFunctionCall);
1401
+ return (calleeVal as Promise<any>).then((cv) =>
1402
+ handleFunctionCall(cv, [self]),
1403
+ );
1024
1404
  }
1405
+ selfArgs = [self];
1025
1406
  }
1026
1407
 
1027
1408
  const argsVal = evalExpressions(fc.args, env, sf);
1028
-
1029
- const thenCall = (args: LuaValue[]) =>
1030
- luaCall(calleeVal, [...selfArgs, ...args], fc.ctx, sf);
1031
-
1032
- return rpThen(argsVal, thenCall);
1409
+ if (!isPromise(argsVal)) {
1410
+ const allArgs =
1411
+ selfArgs.length > 0
1412
+ ? [...selfArgs, ...(argsVal as LuaValue[])]
1413
+ : (argsVal as LuaValue[]);
1414
+ return luaCall(calleeVal, allArgs, fc.ctx, sf);
1415
+ }
1416
+ return (argsVal as Promise<LuaValue[]>).then((args) =>
1417
+ luaCall(
1418
+ calleeVal,
1419
+ selfArgs.length > 0 ? [...selfArgs, ...args] : args,
1420
+ fc.ctx,
1421
+ sf,
1422
+ ),
1423
+ );
1033
1424
  };
1034
1425
 
1035
- return rpThen(prefixValue, handleFunctionCall);
1426
+ if (isPromise(prefixValue)) {
1427
+ return (prefixValue as Promise<any>).then((pv) =>
1428
+ handleFunctionCall(pv, []),
1429
+ );
1430
+ }
1431
+ return handleFunctionCall(prefixValue, []);
1036
1432
  }
1037
1433
 
1038
1434
  default: {
@@ -1182,22 +1578,58 @@ function evalBinaryWithLR(
1182
1578
  : undefined;
1183
1579
  const leftVal = evalExpression(leftExpr, env, sf);
1184
1580
 
1185
- const applyLeft = (lv: any) => {
1581
+ // Sync-first fast path: avoid closure allocation when both operands are sync
1582
+ if (!isPromise(leftVal)) {
1186
1583
  const rightVal = evalExpression(rightExpr, env, sf);
1187
- const applyRight = (rv: any) => {
1584
+ if (!isPromise(rightVal)) {
1188
1585
  return luaOp(
1189
1586
  op,
1190
- singleResult(lv),
1587
+ singleResult(leftVal),
1588
+ singleResult(rightVal),
1589
+ leftType,
1590
+ rightType,
1591
+ ctx,
1592
+ sf,
1593
+ );
1594
+ }
1595
+ return (rightVal as Promise<any>).then((rv) =>
1596
+ luaOp(
1597
+ op,
1598
+ singleResult(leftVal),
1191
1599
  singleResult(rv),
1192
1600
  leftType,
1193
1601
  rightType,
1194
1602
  ctx,
1195
1603
  sf,
1604
+ ),
1605
+ );
1606
+ }
1607
+
1608
+ return (leftVal as Promise<any>).then((lv) => {
1609
+ const rightVal = evalExpression(rightExpr, env, sf);
1610
+ if (!isPromise(rightVal)) {
1611
+ return luaOp(
1612
+ op,
1613
+ singleResult(lv),
1614
+ singleResult(rightVal),
1615
+ leftType,
1616
+ rightType,
1617
+ ctx,
1618
+ sf,
1196
1619
  );
1197
- };
1198
- return rpThen(rightVal, applyRight);
1199
- };
1200
- return rpThen(leftVal, applyLeft);
1620
+ }
1621
+ return (rightVal as Promise<any>).then((rv) =>
1622
+ luaOp(
1623
+ op,
1624
+ singleResult(lv),
1625
+ singleResult(rv),
1626
+ leftType,
1627
+ rightType,
1628
+ ctx,
1629
+ sf,
1630
+ ),
1631
+ );
1632
+ });
1201
1633
  }
1202
1634
 
1203
1635
  function createBitwiseError(
@@ -1280,10 +1712,7 @@ function luaEqWithMetamethod(
1280
1712
  }
1281
1713
 
1282
1714
  const ty = luaTypeName(mm);
1283
- throw new LuaRuntimeError(
1284
- `attempt to call a ${ty} value`,
1285
- sf.withCtx(ctx),
1286
- );
1715
+ throw new LuaRuntimeError(`attempt to call a ${ty} value`, sf.withCtx(ctx));
1287
1716
  };
1288
1717
 
1289
1718
  // Try left __eq first, then right.
@@ -1340,11 +1769,7 @@ function luaRelWithMetamethod(
1340
1769
  * - for JavaScript arrays return length,
1341
1770
  * - throw error otherwise.
1342
1771
  */
1343
- function luaLengthOp(
1344
- val: any,
1345
- ctx: ASTCtx,
1346
- sf: LuaStackFrame,
1347
- ): LuaValue {
1772
+ function luaLengthOp(val: any, ctx: ASTCtx, sf: LuaStackFrame): LuaValue {
1348
1773
  // Strings: ignore `__len`
1349
1774
  if (typeof val === "string") {
1350
1775
  return val.length;
@@ -1391,22 +1816,26 @@ function evalExpressions(
1391
1816
  env: LuaEnv,
1392
1817
  sf: LuaStackFrame,
1393
1818
  ): Promise<LuaValue[]> | LuaValue[] {
1394
- // Evaluate all arguments first (sync-first); do not allocate a Promise if all are sync.
1395
- const parts = es.map((arg) => evalExpression(arg, env, sf));
1819
+ const len = es.length;
1820
+ if (len === 0) return [];
1821
+
1822
+ // Evaluate all arguments (sync-first); avoid .map() closure overhead
1823
+ const parts = new Array(len);
1824
+ for (let i = 0; i < len; i++) {
1825
+ parts[i] = evalExpression(es[i], env, sf);
1826
+ }
1396
1827
  const argsVal = rpAll(parts);
1397
1828
 
1398
1829
  // In Lua multi-returns propagate only in tail position of an expression list.
1399
1830
  const finalize = (argsResolved: any[]) => {
1400
- if (argsResolved.length === 0) {
1401
- return [];
1402
- }
1403
1831
  const out: LuaValue[] = [];
1832
+ const lastIdx = argsResolved.length - 1;
1404
1833
  // All but last expression produce a single value
1405
- for (let i = 0; i < argsResolved.length - 1; i++) {
1834
+ for (let i = 0; i < lastIdx; i++) {
1406
1835
  out.push(singleResult(argsResolved[i]));
1407
1836
  }
1408
1837
  // Last expression preserves multiple results
1409
- const last = argsResolved[argsResolved.length - 1];
1838
+ const last = argsResolved[lastIdx];
1410
1839
  if (last instanceof LuaMultiRes) {
1411
1840
  out.push(...last.flatten().values);
1412
1841
  } else {
@@ -1434,12 +1863,7 @@ function runStatementsNoGoto(
1434
1863
  idx: number,
1435
1864
  ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
1436
1865
  for (let i = idx; i < stmts.length; i++) {
1437
- const result = evalStatement(
1438
- stmts[i],
1439
- execEnv,
1440
- sf,
1441
- returnOnReturn,
1442
- );
1866
+ const result = evalStatement(stmts[i], execEnv, sf, returnOnReturn);
1443
1867
  if (isPromise(result)) {
1444
1868
  return (result as Promise<any>).then((res) => {
1445
1869
  if (res !== undefined) {
@@ -1491,9 +1915,8 @@ function withCloseBoundary(
1491
1915
  };
1492
1916
 
1493
1917
  const onRejected = (e: any) => {
1494
- const errObj: LuaValue = e instanceof LuaRuntimeError
1495
- ? e.message
1496
- : (e?.message ?? String(e));
1918
+ const errObj: LuaValue =
1919
+ e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e));
1497
1920
  const r = luaCloseFromMark(sf, mark, errObj);
1498
1921
  if (isPromise(r)) {
1499
1922
  return (r as Promise<void>).then(() => {
@@ -1536,9 +1959,7 @@ function evalBlockNoClose(
1536
1959
  if (fnHasGotos === true && !hasLabelHere && !hasGotoFlag) {
1537
1960
  const execEnv = b.needsEnv === true ? new LuaEnv(env) : env;
1538
1961
  const stmts = b.statements;
1539
- const runFrom = (
1540
- i: number,
1541
- ): EvalBlockResult => {
1962
+ const runFrom = (i: number): EvalBlockResult => {
1542
1963
  for (; i < stmts.length; i++) {
1543
1964
  const r = evalStatement(stmts[i], execEnv, sf, returnOnReturn);
1544
1965
  if (isPromise(r)) {
@@ -1583,9 +2004,7 @@ function evalBlockNoClose(
1583
2004
  const execEnv = b.needsEnv === true ? new LuaEnv(env) : env;
1584
2005
  const stmts = b.statements;
1585
2006
 
1586
- const runFrom = (
1587
- i: number,
1588
- ): EvalBlockResult => {
2007
+ const runFrom = (i: number): EvalBlockResult => {
1589
2008
  for (; i < stmts.length; i++) {
1590
2009
  const r = evalStatement(stmts[i], execEnv, sf, returnOnReturn);
1591
2010
  if (isPromise(r)) {
@@ -1635,17 +2054,20 @@ export function evalStatement(
1635
2054
  case "Assignment": {
1636
2055
  const a = asAssignment(s);
1637
2056
  const valuesRP = evalExpressions(a.expressions, env, sf);
1638
- const lvaluesRP = evalPromiseValues(a.variables
1639
- .map((lval) => evalLValue(lval, env, sf)));
2057
+ const lvaluesRP = evalPromiseValues(
2058
+ a.variables.map((lval) => evalLValue(lval, env, sf)),
2059
+ );
1640
2060
 
1641
2061
  const apply = (values: LuaValue[], lvalues: { env: any; key: any }[]) => {
2062
+ // Create the error-reporting frame once, not per-lvalue
2063
+ let errSf: LuaStackFrame | undefined;
1642
2064
  const ps: Promise<any>[] = [];
1643
2065
  for (let i = 0; i < lvalues.length; i++) {
1644
2066
  const r = luaSet(
1645
2067
  lvalues[i].env,
1646
2068
  lvalues[i].key,
1647
2069
  values[i],
1648
- sf.withCtx(a.ctx),
2070
+ errSf || (errSf = sf.withCtx(a.ctx)),
1649
2071
  );
1650
2072
 
1651
2073
  if (isPromise(r)) {
@@ -1659,29 +2081,22 @@ export function evalStatement(
1659
2081
  };
1660
2082
 
1661
2083
  if (!isPromise(valuesRP) && !isPromise(lvaluesRP)) {
1662
- return apply(
1663
- valuesRP as LuaValue[],
1664
- lvaluesRP as LuaLValueContainer[],
1665
- );
2084
+ return apply(valuesRP as LuaValue[], lvaluesRP as LuaLValueContainer[]);
1666
2085
  }
1667
- if (
1668
- isPromise(valuesRP) && !isPromise(lvaluesRP)
1669
- ) {
2086
+ if (isPromise(valuesRP) && !isPromise(lvaluesRP)) {
1670
2087
  return (valuesRP as Promise<LuaValue[]>).then((values: LuaValue[]) =>
1671
- apply(values, lvaluesRP as LuaLValueContainer[])
2088
+ apply(values, lvaluesRP as LuaLValueContainer[]),
1672
2089
  );
1673
2090
  }
1674
- if (
1675
- !isPromise(valuesRP) && isPromise(lvaluesRP)
1676
- ) {
2091
+ if (!isPromise(valuesRP) && isPromise(lvaluesRP)) {
1677
2092
  return (lvaluesRP as Promise<any[]>).then((lvalues: any[]) =>
1678
- apply(valuesRP as LuaValue[], lvalues)
2093
+ apply(valuesRP as LuaValue[], lvalues),
1679
2094
  );
1680
2095
  }
1681
2096
  return (valuesRP as Promise<LuaValue[]>).then((values: LuaValue[]) =>
1682
2097
  (lvaluesRP as Promise<any[]>).then((lvalues: any[]) =>
1683
- apply(values, lvalues)
1684
- )
2098
+ apply(values, lvalues),
2099
+ ),
1685
2100
  );
1686
2101
  }
1687
2102
  case "Local": {
@@ -1730,10 +2145,7 @@ export function evalStatement(
1730
2145
 
1731
2146
  const bindAvailable = () => {
1732
2147
  while (boundCount < l.names.length && boundCount < out.length) {
1733
- bindOne(
1734
- l.names[boundCount],
1735
- out[boundCount] ?? null,
1736
- );
2148
+ bindOne(l.names[boundCount], out[boundCount] ?? null);
1737
2149
  boundCount++;
1738
2150
  }
1739
2151
  };
@@ -1813,9 +2225,8 @@ export function evalStatement(
1813
2225
  try {
1814
2226
  out = evalBlockNoClose(b, env, sf, returnOnReturn);
1815
2227
  } catch (e: any) {
1816
- const errObj: LuaValue = e instanceof LuaRuntimeError
1817
- ? e.message
1818
- : (e?.message ?? String(e));
2228
+ const errObj: LuaValue =
2229
+ e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e));
1819
2230
  const r = luaCloseFromMark(sf, mark, errObj);
1820
2231
  if (isPromise(r)) {
1821
2232
  return (r as Promise<void>).then(() => {
@@ -1834,10 +2245,7 @@ export function evalStatement(
1834
2245
 
1835
2246
  const runFrom = (
1836
2247
  i: number,
1837
- ):
1838
- | undefined
1839
- | ControlSignal
1840
- | Promise<undefined | ControlSignal> => {
2248
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
1841
2249
  if (i >= conds.length) {
1842
2250
  if (iff.elseBlock) {
1843
2251
  return evalStatement(iff.elseBlock, env, sf, returnOnReturn);
@@ -1864,128 +2272,84 @@ export function evalStatement(
1864
2272
  case "While": {
1865
2273
  const w = asWhile(s);
1866
2274
 
1867
- const runAsync = async (): Promise<undefined | ControlSignal> => {
2275
+ // Sync-first loop that re-enters sync mode after each async iteration
2276
+ const runSyncFirst = ():
2277
+ | undefined
2278
+ | ControlSignal
2279
+ | Promise<undefined | ControlSignal> => {
1868
2280
  while (true) {
1869
- const c = await evalExpression(w.condition, env, sf);
1870
- if (!luaTruthy(c)) {
1871
- break;
2281
+ const c = evalExpression(w.condition, env, sf);
2282
+ if (isPromise(c)) {
2283
+ return (c as Promise<any>).then((cv) => {
2284
+ if (!luaTruthy(cv)) return;
2285
+ return rpThen(
2286
+ evalStatement(w.block, env, sf, returnOnReturn),
2287
+ (res) => {
2288
+ if (res !== undefined) {
2289
+ return isBreakSignal(res) ? undefined : res;
2290
+ }
2291
+ return runSyncFirst();
2292
+ },
2293
+ );
2294
+ });
1872
2295
  }
2296
+ if (!luaTruthy(c)) break;
1873
2297
  const r = evalStatement(w.block, env, sf, returnOnReturn);
1874
- const res = isPromise(r) ? await r : r;
1875
- if (res !== undefined) {
1876
- if (isBreakSignal(res)) {
1877
- break;
1878
- }
1879
- return res;
2298
+ if (isPromise(r)) {
2299
+ return (r as Promise<any>).then((res) => {
2300
+ if (res !== undefined) {
2301
+ return isBreakSignal(res) ? undefined : res;
2302
+ }
2303
+ return runSyncFirst();
2304
+ });
2305
+ }
2306
+ if (r !== undefined) {
2307
+ if (isBreakSignal(r)) break;
2308
+ return r;
1880
2309
  }
1881
2310
  }
1882
2311
  return;
1883
2312
  };
1884
2313
 
1885
- while (true) {
1886
- const c = evalExpression(w.condition, env, sf);
1887
- if (isPromise(c)) {
1888
- return (c as Promise<any>).then((cv) => {
1889
- if (!luaTruthy(cv)) {
1890
- return;
1891
- }
1892
- const r = evalStatement(w.block, env, sf, returnOnReturn);
1893
- if (isPromise(r)) {
1894
- return (r as Promise<any>).then((res) => {
1895
- if (res !== undefined) {
1896
- if (isBreakSignal(res)) {
1897
- return;
1898
- }
1899
- return res;
1900
- }
1901
- return runAsync();
1902
- });
1903
- }
1904
- if (r !== undefined) {
1905
- if (isBreakSignal(r)) {
1906
- return;
1907
- }
1908
- return r;
1909
- }
1910
- return runAsync();
1911
- });
1912
- }
1913
- if (!luaTruthy(c)) {
1914
- break;
1915
- }
1916
- const r = evalStatement(w.block, env, sf, returnOnReturn);
1917
- if (isPromise(r)) {
1918
- return (r as Promise<any>).then((res) => {
1919
- if (res !== undefined) {
1920
- if (isBreakSignal(res)) {
1921
- return;
1922
- }
1923
- return res;
1924
- }
1925
- return runAsync();
1926
- });
1927
- }
1928
- if (r !== undefined) {
1929
- if (isBreakSignal(r)) {
1930
- break;
1931
- }
1932
- return r;
1933
- }
1934
- }
1935
- return;
2314
+ return runSyncFirst();
1936
2315
  }
1937
2316
  case "Repeat": {
1938
- const r = asRepeat(s);
2317
+ const rep = asRepeat(s);
1939
2318
 
1940
- const runAsync = async (): Promise<undefined | ControlSignal> => {
2319
+ // Sync-first loop that re-enters sync mode after each async iteration
2320
+ const runSyncFirst = ():
2321
+ | undefined
2322
+ | ControlSignal
2323
+ | Promise<undefined | ControlSignal> => {
1941
2324
  while (true) {
1942
- const rr = evalStatement(r.block, env, sf, returnOnReturn);
1943
- const res = isPromise(rr) ? await rr : rr;
1944
- if (res !== undefined) {
1945
- if (isBreakSignal(res)) {
1946
- break;
1947
- }
1948
- return res;
2325
+ const rr = evalStatement(rep.block, env, sf, returnOnReturn);
2326
+ if (isPromise(rr)) {
2327
+ return (rr as Promise<any>).then((res) => {
2328
+ if (res !== undefined) {
2329
+ return isBreakSignal(res) ? undefined : res;
2330
+ }
2331
+ return rpThen(evalExpression(rep.condition, env, sf), (cv) =>
2332
+ luaTruthy(cv) ? undefined : runSyncFirst(),
2333
+ );
2334
+ });
1949
2335
  }
1950
- const c = await evalExpression(r.condition, env, sf);
1951
- if (luaTruthy(c)) {
1952
- break;
2336
+ if (rr !== undefined) {
2337
+ if (isBreakSignal(rr)) return;
2338
+ return rr;
1953
2339
  }
1954
- }
1955
- return;
1956
- };
1957
2340
 
1958
- while (true) {
1959
- const rr = evalStatement(r.block, env, sf, returnOnReturn);
1960
- if (isPromise(rr)) {
1961
- return (rr as Promise<any>).then((res) => {
1962
- if (res !== undefined) {
1963
- if (isBreakSignal(res)) {
1964
- return;
1965
- }
1966
- return res;
1967
- }
1968
- return runAsync();
1969
- });
1970
- }
1971
- if (rr !== undefined) {
1972
- if (isBreakSignal(rr)) {
1973
- return;
2341
+ const c = evalExpression(rep.condition, env, sf);
2342
+ if (isPromise(c)) {
2343
+ return (c as Promise<any>).then((cv) =>
2344
+ luaTruthy(cv) ? undefined : runSyncFirst(),
2345
+ );
1974
2346
  }
1975
- return rr;
2347
+ if (luaTruthy(c)) break;
1976
2348
  }
2349
+ return;
2350
+ };
1977
2351
 
1978
- const c = evalExpression(r.condition, env, sf);
1979
- if (isPromise(c)) {
1980
- return (c as Promise<any>).then((cv) =>
1981
- luaTruthy(cv) ? undefined : runAsync()
1982
- );
1983
- }
1984
- if (luaTruthy(c)) {
1985
- break;
1986
- }
1987
- }
1988
- return;
2352
+ return runSyncFirst();
1989
2353
  }
1990
2354
  case "Break": {
1991
2355
  return { ctrl: "break" };
@@ -2005,7 +2369,7 @@ export function evalStatement(
2005
2369
  if (fn.name.colonName) {
2006
2370
  // function hello:there() -> function hello.there(self) transformation
2007
2371
  body = {
2008
- ...(fn.body),
2372
+ ...fn.body,
2009
2373
  parameters: ["self", ...fn.body.parameters],
2010
2374
  };
2011
2375
  propNames = [...fn.name.propNames, fn.name.colonName];
@@ -2028,17 +2392,14 @@ export function evalStatement(
2028
2392
  }
2029
2393
  case "LocalFunction": {
2030
2394
  const lf = asLocalFunction(s);
2031
- env.setLocal(
2032
- lf.name,
2033
- new LuaFunction(lf.body, env),
2034
- );
2395
+ env.setLocal(lf.name, new LuaFunction(lf.body, env));
2035
2396
  return;
2036
2397
  }
2037
2398
  case "Return": {
2038
2399
  const ret = asReturn(s);
2039
2400
 
2040
2401
  const parts = ret.expressions.map((value: LuaExpression) =>
2041
- evalExpression(value, env, sf)
2402
+ evalExpression(value, env, sf),
2042
2403
  );
2043
2404
  const valuesRP = rpAll(parts);
2044
2405
 
@@ -2086,9 +2447,7 @@ export function evalStatement(
2086
2447
  const determineLoopType = (): NumericType => {
2087
2448
  const startType = astNumberKind(fr.start);
2088
2449
  const stepType = fr.step ? astNumberKind(fr.step) : "int";
2089
- return (startType === "float" || stepType === "float")
2090
- ? "float"
2091
- : "int";
2450
+ return startType === "float" || stepType === "float" ? "float" : "int";
2092
2451
  };
2093
2452
 
2094
2453
  const wrapLoopVar = (i: number, loopType: NumericType) => {
@@ -2098,53 +2457,59 @@ export function evalStatement(
2098
2457
  return i;
2099
2458
  };
2100
2459
 
2101
- const canReuseEnv = !fr.block.hasFunctionDef ||
2102
- fr.capturesLoopVar === false;
2460
+ const canReuseEnv =
2461
+ !fr.block.hasFunctionDef || fr.capturesLoopVar === false;
2103
2462
 
2104
2463
  const executeIteration = canReuseEnv
2105
2464
  ? (
2106
- loopEnv: LuaEnv,
2107
- i: number,
2108
- loopType: NumericType,
2109
- ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
2110
- loopEnv.setLocal(fr.name, wrapLoopVar(i, loopType));
2111
- return evalStatement(fr.block, loopEnv, sf, returnOnReturn);
2112
- }
2465
+ loopEnv: LuaEnv,
2466
+ i: number,
2467
+ loopType: NumericType,
2468
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
2469
+ loopEnv.setLocal(fr.name, wrapLoopVar(i, loopType));
2470
+ return evalStatement(fr.block, loopEnv, sf, returnOnReturn);
2471
+ }
2113
2472
  : (
2114
- _loopEnv: LuaEnv,
2115
- i: number,
2116
- loopType: NumericType,
2117
- ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
2118
- const localEnv = new LuaEnv(env);
2119
- localEnv.setLocal(fr.name, wrapLoopVar(i, loopType));
2120
- return evalStatement(fr.block, localEnv, sf, returnOnReturn);
2121
- };
2473
+ _loopEnv: LuaEnv,
2474
+ i: number,
2475
+ loopType: NumericType,
2476
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
2477
+ const localEnv = new LuaEnv(env);
2478
+ localEnv.setLocal(fr.name, wrapLoopVar(i, loopType));
2479
+ return evalStatement(fr.block, localEnv, sf, returnOnReturn);
2480
+ };
2122
2481
 
2123
- const runAsync = async (
2482
+ // Continuation that re-enters sync mode after each async iteration
2483
+ const runFromIndex = (
2124
2484
  loopEnv: LuaEnv,
2125
2485
  end: number,
2126
2486
  step: number,
2127
2487
  startIndex: number,
2128
2488
  loopType: NumericType,
2129
- ) => {
2489
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
2130
2490
  if (step === 0) {
2131
2491
  throw new LuaRuntimeError("'for' step is zero", sf.withCtx(fr.ctx));
2132
2492
  }
2133
2493
 
2134
- const shouldContinue = step > 0
2135
- ? (i: number) => i <= end
2136
- : (i: number) => i >= end;
2494
+ const shouldContinue =
2495
+ step > 0 ? (i: number) => i <= end : (i: number) => i >= end;
2137
2496
 
2138
2497
  for (let i = startIndex; shouldContinue(i); i += step) {
2139
2498
  const r = executeIteration(loopEnv, i, loopType);
2140
- const res = isPromise(r) ? await r : r;
2141
- if (res !== undefined) {
2142
- if (isBreakSignal(res)) {
2143
- return;
2144
- }
2145
- return res;
2499
+ if (isPromise(r)) {
2500
+ return (r as Promise<any>).then((res) => {
2501
+ if (res !== undefined) {
2502
+ return isBreakSignal(res) ? undefined : res;
2503
+ }
2504
+ return runFromIndex(loopEnv, end, step, i + step, loopType);
2505
+ });
2506
+ }
2507
+ if (r !== undefined) {
2508
+ if (isBreakSignal(r)) return;
2509
+ return r;
2146
2510
  }
2147
2511
  }
2512
+ return;
2148
2513
  };
2149
2514
 
2150
2515
  const runSyncFirst = (
@@ -2152,17 +2517,13 @@ export function evalStatement(
2152
2517
  end: number,
2153
2518
  step: number,
2154
2519
  loopType: NumericType,
2155
- ):
2156
- | undefined
2157
- | ControlSignal
2158
- | Promise<undefined | ControlSignal> => {
2520
+ ): undefined | ControlSignal | Promise<undefined | ControlSignal> => {
2159
2521
  if (step === 0) {
2160
2522
  throw new LuaRuntimeError("'for' step is zero", sf.withCtx(fr.ctx));
2161
2523
  }
2162
2524
 
2163
- const shouldContinue = step > 0
2164
- ? (i: number) => i <= end
2165
- : (i: number) => i >= end;
2525
+ const shouldContinue =
2526
+ step > 0 ? (i: number) => i <= end : (i: number) => i >= end;
2166
2527
 
2167
2528
  const loopEnv = new LuaEnv(env);
2168
2529
 
@@ -2176,7 +2537,7 @@ export function evalStatement(
2176
2537
  }
2177
2538
  return res;
2178
2539
  }
2179
- return runAsync(loopEnv, end, step, i + step, loopType);
2540
+ return runFromIndex(loopEnv, end, step, i + step, loopType);
2180
2541
  });
2181
2542
  }
2182
2543
  if (r !== undefined) {
@@ -2191,11 +2552,7 @@ export function evalStatement(
2191
2552
 
2192
2553
  const loopType = determineLoopType();
2193
2554
 
2194
- if (
2195
- !isPromise(startV) &&
2196
- !isPromise(endV) &&
2197
- !isPromise(stepV)
2198
- ) {
2555
+ if (!isPromise(startV) && !isPromise(endV) && !isPromise(stepV)) {
2199
2556
  return runSyncFirst(
2200
2557
  untagNumber(startV) as number,
2201
2558
  untagNumber(endV) as number,
@@ -2222,8 +2579,8 @@ export function evalStatement(
2222
2579
  fi.expressions.map((e: LuaExpression) => evalExpression(e, env, sf)),
2223
2580
  );
2224
2581
 
2225
- const canReuseEnv = !fi.block.hasFunctionDef ||
2226
- fi.capturesLoopVar === false;
2582
+ const canReuseEnv =
2583
+ !fi.block.hasFunctionDef || fi.capturesLoopVar === false;
2227
2584
  const setIterVars = (
2228
2585
  localEnv: LuaEnv,
2229
2586
  names: string[],
@@ -2294,39 +2651,8 @@ export function evalStatement(
2294
2651
  return new LuaEnv(env);
2295
2652
  };
2296
2653
 
2297
- try {
2298
- const runAsync = async () => {
2299
- while (true) {
2300
- const callRes = luaCall(
2301
- iteratorValue,
2302
- [state, control],
2303
- fi.ctx,
2304
- sf,
2305
- );
2306
- const iterResult = new LuaMultiRes(
2307
- isPromise(callRes) ? await callRes : callRes,
2308
- ).flatten();
2309
- const nextControl = iterResult.values[0];
2310
- if (nextControl === null || nextControl === undefined) {
2311
- break;
2312
- }
2313
- control = nextControl;
2314
-
2315
- const localEnv = makeIterEnv();
2316
- setIterVars(localEnv, fi.names, iterResult.values);
2317
-
2318
- const r = evalStatement(fi.block, localEnv, sf, returnOnReturn);
2319
- const res = isPromise(r) ? await r : r;
2320
- if (res !== undefined) {
2321
- if (isBreakSignal(res)) {
2322
- break;
2323
- }
2324
- return await finish(res);
2325
- }
2326
- }
2327
- return await finish(undefined);
2328
- };
2329
-
2654
+ // Sync-first loop that re-enters sync mode after each async iteration
2655
+ const runSyncFirst = (): any => {
2330
2656
  while (true) {
2331
2657
  const iterCall = luaCall(
2332
2658
  iteratorValue,
@@ -2334,55 +2660,40 @@ export function evalStatement(
2334
2660
  fi.ctx,
2335
2661
  sf,
2336
2662
  );
2337
- if (isPromise(iterCall)) {
2338
- return (iterCall as Promise<any>).then((itv) => {
2339
- const iterResult = new LuaMultiRes(itv).flatten();
2340
- const nextControl = iterResult.values[0];
2341
- if (nextControl === null || nextControl === undefined) {
2342
- const r = finish(undefined);
2343
- if (isPromise(r)) return (r as Promise<void>).then(() => {});
2344
- return;
2345
- }
2346
- control = nextControl;
2347
2663
 
2348
- const localEnv = makeIterEnv();
2349
- setIterVars(localEnv, fi.names, iterResult.values);
2664
+ const afterIterCall = (itv: any): any => {
2665
+ const iterResult = new LuaMultiRes(itv).flatten();
2666
+ const nextControl = iterResult.values[0];
2667
+ if (nextControl === null || nextControl === undefined) {
2668
+ return finish(undefined);
2669
+ }
2670
+ control = nextControl;
2350
2671
 
2351
- const r = evalStatement(
2352
- fi.block,
2353
- localEnv,
2354
- sf,
2355
- returnOnReturn,
2356
- );
2357
- if (isPromise(r)) {
2358
- return (r as Promise<any>).then((res) => {
2359
- if (res !== undefined) {
2360
- if (isBreakSignal(res)) {
2361
- return finish(undefined);
2362
- }
2363
- return rpThen(finish(undefined), () => res);
2364
- }
2365
- return runAsync();
2366
- });
2367
- }
2368
- if (r !== undefined) {
2369
- if (isBreakSignal(r)) {
2672
+ const localEnv = makeIterEnv();
2673
+ setIterVars(localEnv, fi.names, iterResult.values);
2674
+
2675
+ const r = evalStatement(fi.block, localEnv, sf, returnOnReturn);
2676
+ return rpThen(r, (res) => {
2677
+ if (res !== undefined) {
2678
+ if (isBreakSignal(res)) {
2370
2679
  return finish(undefined);
2371
2680
  }
2372
- return rpThen(finish(undefined), () => r);
2681
+ return rpThen(finish(undefined), () => res);
2373
2682
  }
2374
- return runAsync();
2375
- }).catch((e: any) => finishErr(e));
2683
+ return runSyncFirst();
2684
+ });
2685
+ };
2686
+
2687
+ if (isPromise(iterCall)) {
2688
+ return (iterCall as Promise<any>)
2689
+ .then(afterIterCall)
2690
+ .catch((e: any) => finishErr(e));
2376
2691
  }
2377
2692
 
2378
2693
  const iterResult = new LuaMultiRes(iterCall).flatten();
2379
2694
  const nextControl = iterResult.values[0];
2380
2695
  if (nextControl === null || nextControl === undefined) {
2381
- const r = finish(undefined);
2382
- if (isPromise(r)) {
2383
- return (r as Promise<void>);
2384
- }
2385
- return;
2696
+ return finish(undefined);
2386
2697
  }
2387
2698
  control = nextControl;
2388
2699
 
@@ -2391,15 +2702,17 @@ export function evalStatement(
2391
2702
 
2392
2703
  const r = evalStatement(fi.block, localEnv, sf, returnOnReturn);
2393
2704
  if (isPromise(r)) {
2394
- return (r as Promise<any>).then((res) => {
2395
- if (res !== undefined) {
2396
- if (isBreakSignal(res)) {
2397
- return finish(undefined);
2705
+ return (r as Promise<any>)
2706
+ .then((res) => {
2707
+ if (res !== undefined) {
2708
+ if (isBreakSignal(res)) {
2709
+ return finish(undefined);
2710
+ }
2711
+ return rpThen(finish(undefined), () => res);
2398
2712
  }
2399
- return rpThen(finish(undefined), () => res);
2400
- }
2401
- return runAsync();
2402
- }).catch((e: any) => finishErr(e));
2713
+ return runSyncFirst();
2714
+ })
2715
+ .catch((e: any) => finishErr(e));
2403
2716
  }
2404
2717
  if (r !== undefined) {
2405
2718
  if (isBreakSignal(r)) {
@@ -2408,6 +2721,10 @@ export function evalStatement(
2408
2721
  return rpThen(finish(undefined), () => r);
2409
2722
  }
2410
2723
  }
2724
+ };
2725
+
2726
+ try {
2727
+ return runSyncFirst();
2411
2728
  } catch (e: any) {
2412
2729
  return finishErr(e);
2413
2730
  }
@@ -2436,16 +2753,9 @@ function evalLValue(
2436
2753
  }
2437
2754
  case "TableAccess": {
2438
2755
  const ta = asLValueTableAccess(lval);
2439
- const objValue = evalExpression(
2440
- ta.object,
2441
- env,
2442
- sf,
2443
- );
2756
+ const objValue = evalExpression(ta.object, env, sf);
2444
2757
  const keyValue = evalExpression(ta.key, env, sf);
2445
- if (
2446
- isPromise(objValue) ||
2447
- isPromise(keyValue)
2448
- ) {
2758
+ if (isPromise(objValue) || isPromise(keyValue)) {
2449
2759
  return Promise.all([
2450
2760
  isPromise(objValue) ? objValue : Promise.resolve(objValue),
2451
2761
  isPromise(keyValue) ? keyValue : Promise.resolve(keyValue),
@@ -2461,11 +2771,7 @@ function evalLValue(
2461
2771
  }
2462
2772
  case "PropertyAccess": {
2463
2773
  const pa = asLValuePropertyAccess(lval);
2464
- const objValue = evalExpression(
2465
- pa.object,
2466
- env,
2467
- sf,
2468
- );
2774
+ const objValue = evalExpression(pa.object, env, sf);
2469
2775
  if (isPromise(objValue)) {
2470
2776
  return (objValue as Promise<any>).then((ov) => {
2471
2777
  return {