@silverbulletmd/silverbullet 2.4.2 → 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 (97) hide show
  1. package/README.md +19 -4
  2. package/client/asset_bundle/bundle.ts +3 -9
  3. package/client/data/datastore.ts +4 -5
  4. package/client/markdown_parser/constants.ts +5 -4
  5. package/client/plugos/hooks/code_widget.ts +3 -8
  6. package/client/plugos/hooks/command.ts +8 -8
  7. package/client/plugos/hooks/document_editor.ts +10 -15
  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 -8
  11. package/client/plugos/hooks/slash_command.ts +13 -28
  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 -6
  15. package/client/plugos/plug_compile.ts +79 -78
  16. package/client/plugos/protocol.ts +28 -28
  17. package/client/plugos/proxy_fetch.ts +7 -6
  18. package/client/plugos/sandboxes/web_worker_sandbox.ts +1 -1
  19. package/client/plugos/sandboxes/worker_sandbox.ts +18 -18
  20. package/client/plugos/syscalls/asset.ts +1 -3
  21. package/client/plugos/syscalls/code_widget.ts +1 -3
  22. package/client/plugos/syscalls/config.ts +1 -5
  23. package/client/plugos/syscalls/datastore.ts +1 -1
  24. package/client/plugos/syscalls/editor.ts +72 -69
  25. package/client/plugos/syscalls/event.ts +9 -12
  26. package/client/plugos/syscalls/fetch.ts +31 -23
  27. package/client/plugos/syscalls/index.ts +10 -1
  28. package/client/plugos/syscalls/jsonschema.ts +72 -32
  29. package/client/plugos/syscalls/language.ts +9 -5
  30. package/client/plugos/syscalls/markdown.ts +29 -7
  31. package/client/plugos/syscalls/mq.ts +4 -12
  32. package/client/plugos/syscalls/service_registry.ts +1 -4
  33. package/client/plugos/syscalls/shell.ts +2 -5
  34. package/client/plugos/syscalls/space.ts +1 -1
  35. package/client/plugos/syscalls/sync.ts +69 -60
  36. package/client/plugos/syscalls/system.ts +2 -3
  37. package/client/plugos/system.ts +6 -12
  38. package/client/plugos/worker_runtime.ts +12 -33
  39. package/client/space_lua/aggregates.ts +782 -0
  40. package/client/space_lua/ast.ts +42 -8
  41. package/client/space_lua/ast_narrow.ts +4 -2
  42. package/client/space_lua/eval.ts +886 -575
  43. package/client/space_lua/labels.ts +7 -12
  44. package/client/space_lua/liq_null.ts +6 -0
  45. package/client/space_lua/numeric.ts +5 -8
  46. package/client/space_lua/parse.ts +346 -120
  47. package/client/space_lua/query_collection.ts +926 -82
  48. package/client/space_lua/query_env.ts +26 -0
  49. package/client/space_lua/render_lua_markdown.ts +369 -0
  50. package/client/space_lua/rp.ts +5 -4
  51. package/client/space_lua/runtime.ts +288 -155
  52. package/client/space_lua/stdlib/format.ts +53 -39
  53. package/client/space_lua/stdlib/js.ts +3 -7
  54. package/client/space_lua/stdlib/load.ts +1 -3
  55. package/client/space_lua/stdlib/math.ts +84 -58
  56. package/client/space_lua/stdlib/net.ts +27 -17
  57. package/client/space_lua/stdlib/os.ts +81 -85
  58. package/client/space_lua/stdlib/pattern.ts +695 -0
  59. package/client/space_lua/stdlib/prng.ts +148 -0
  60. package/client/space_lua/stdlib/space_lua.ts +17 -23
  61. package/client/space_lua/stdlib/string.ts +102 -190
  62. package/client/space_lua/stdlib/string_pack.ts +490 -0
  63. package/client/space_lua/stdlib/table.ts +76 -16
  64. package/client/space_lua/stdlib.ts +53 -39
  65. package/client/space_lua/tonumber.ts +82 -42
  66. package/client/space_lua/util.ts +53 -15
  67. package/dist/plug-compile.js +55 -98
  68. package/package.json +27 -20
  69. package/plug-api/constants.ts +0 -32
  70. package/plug-api/lib/async.ts +20 -7
  71. package/plug-api/lib/crypto.ts +16 -17
  72. package/plug-api/lib/dates.ts +15 -7
  73. package/plug-api/lib/json.ts +11 -5
  74. package/plug-api/lib/limited_map.ts +1 -1
  75. package/plug-api/lib/native_fetch.ts +2 -0
  76. package/plug-api/lib/ref.ts +23 -23
  77. package/plug-api/lib/resolve.ts +7 -11
  78. package/plug-api/lib/tags.ts +13 -4
  79. package/plug-api/lib/transclusion.ts +10 -21
  80. package/plug-api/lib/tree.ts +165 -45
  81. package/plug-api/lib/yaml.ts +35 -25
  82. package/plug-api/syscalls/asset.ts +1 -1
  83. package/plug-api/syscalls/config.ts +1 -4
  84. package/plug-api/syscalls/editor.ts +15 -15
  85. package/plug-api/syscalls/jsonschema.ts +1 -3
  86. package/plug-api/syscalls/lua.ts +3 -9
  87. package/plug-api/syscalls/mq.ts +1 -4
  88. package/plug-api/syscalls/shell.ts +4 -1
  89. package/plug-api/syscalls/space.ts +3 -10
  90. package/plug-api/syscalls/system.ts +1 -4
  91. package/plug-api/syscalls/yaml.ts +2 -6
  92. package/plug-api/system_mock.ts +0 -1
  93. package/plug-api/types/client.ts +16 -1
  94. package/plug-api/types/event.ts +6 -4
  95. package/plug-api/types/manifest.ts +8 -9
  96. package/plugs/builtin_plugs.ts +2 -2
  97. package/client/plugos/sandboxes/deno_worker_sandbox.ts +0 -6
@@ -4,6 +4,7 @@ import {
4
4
  cleanTree,
5
5
  type ParseTree,
6
6
  } from "@silverbulletmd/silverbullet/lib/tree";
7
+ // @ts-expect-error - Local generated JavaScript file without type definitions
7
8
  import { parser } from "./parse-lua.js";
8
9
  import { styleTags, tags as t } from "@lezer/highlight";
9
10
  import { indentNodeProp, LRLanguage } from "@codemirror/language";
@@ -35,7 +36,7 @@ const luaStyleTags = styleTags({
35
36
  CompareOp: t.operator,
36
37
  "true false": t.bool,
37
38
  Comment: t.lineComment,
38
- "return break goto do end while repeat until function local if then else elseif in for nil or and not query from where limit select order by desc":
39
+ "return break goto do end while repeat until function local if then else elseif in for nil or and not query from where limit offset select order by desc asc nulls first last group having filter using":
39
40
  t.keyword,
40
41
  });
41
42
 
@@ -51,10 +52,7 @@ const customIndent = indentNodeProp.add({
51
52
  export const luaLanguage = LRLanguage.define({
52
53
  name: "space-lua",
53
54
  parser: parser.configure({
54
- props: [
55
- luaStyleTags,
56
- customIndent,
57
- ],
55
+ props: [luaStyleTags, customIndent],
58
56
  }),
59
57
  languageData: {
60
58
  commentTokens: { line: "--", block: { open: "--[[", close: "--]]" } },
@@ -96,8 +94,9 @@ function expressionHasFunctionDef(e: LuaExpression): boolean {
96
94
  }
97
95
  return false;
98
96
  case "Binary":
99
- return expressionHasFunctionDef(e.left) ||
100
- expressionHasFunctionDef(e.right);
97
+ return (
98
+ expressionHasFunctionDef(e.left) || expressionHasFunctionDef(e.right)
99
+ );
101
100
  case "Unary":
102
101
  return expressionHasFunctionDef(e.argument);
103
102
  case "Parenthesized":
@@ -118,8 +117,9 @@ function expressionHasFunctionDef(e: LuaExpression): boolean {
118
117
  }
119
118
  return false;
120
119
  case "TableAccess":
121
- return expressionHasFunctionDef(e.object) ||
122
- expressionHasFunctionDef(e.key);
120
+ return (
121
+ expressionHasFunctionDef(e.object) || expressionHasFunctionDef(e.key)
122
+ );
123
123
  case "PropertyAccess":
124
124
  return expressionHasFunctionDef(e.object);
125
125
  case "Query":
@@ -127,10 +127,23 @@ function expressionHasFunctionDef(e: LuaExpression): boolean {
127
127
  const c = e.clauses[i];
128
128
  switch (c.type) {
129
129
  case "From":
130
- if (expressionHasFunctionDef(c.expression)) return true;
130
+ case "Select":
131
+ case "GroupBy":
132
+ for (const f of c.fields) {
133
+ switch (f.type) {
134
+ case "DynamicField":
135
+ if (expressionHasFunctionDef(f.key)) return true;
136
+ if (expressionHasFunctionDef(f.value)) return true;
137
+ break;
138
+ case "PropField":
139
+ case "ExpressionField":
140
+ if (expressionHasFunctionDef(f.value)) return true;
141
+ break;
142
+ }
143
+ }
131
144
  break;
132
145
  case "Where":
133
- case "Select":
146
+ case "Having":
134
147
  if (expressionHasFunctionDef(c.expression)) return true;
135
148
  break;
136
149
  case "Limit":
@@ -142,11 +155,30 @@ function expressionHasFunctionDef(e: LuaExpression): boolean {
142
155
  if (expressionHasFunctionDef(c.orderBy[j].expression)) {
143
156
  return true;
144
157
  }
158
+ if (
159
+ c.orderBy[j].using &&
160
+ typeof c.orderBy[j].using !== "string"
161
+ ) {
162
+ return true;
163
+ }
145
164
  }
146
165
  break;
147
166
  }
148
167
  }
149
168
  return false;
169
+ case "FilteredCall":
170
+ return (
171
+ expressionHasFunctionDef(e.call) || expressionHasFunctionDef(e.filter)
172
+ );
173
+ case "AggregateCall":
174
+ return (
175
+ expressionHasFunctionDef((e as any).call) ||
176
+ ((e as any).orderBy as LuaOrderBy[]).some(
177
+ (ob) =>
178
+ expressionHasFunctionDef(ob.expression) ||
179
+ (ob.using && typeof ob.using !== "string"),
180
+ )
181
+ );
150
182
  default:
151
183
  return false;
152
184
  }
@@ -170,8 +202,10 @@ function exprReferencesNames(e: LuaExpression, names: Set<string>): boolean {
170
202
  case "Variable":
171
203
  return names.has(e.name);
172
204
  case "Binary":
173
- return exprReferencesNames(e.left, names) ||
174
- exprReferencesNames(e.right, names);
205
+ return (
206
+ exprReferencesNames(e.left, names) ||
207
+ exprReferencesNames(e.right, names)
208
+ );
175
209
  case "Unary":
176
210
  return exprReferencesNames(e.argument, names);
177
211
  case "Parenthesized":
@@ -183,8 +217,10 @@ function exprReferencesNames(e: LuaExpression, names: Set<string>): boolean {
183
217
  }
184
218
  return false;
185
219
  case "TableAccess":
186
- return exprReferencesNames(e.object, names) ||
187
- exprReferencesNames(e.key, names);
220
+ return (
221
+ exprReferencesNames(e.object, names) ||
222
+ exprReferencesNames(e.key, names)
223
+ );
188
224
  case "PropertyAccess":
189
225
  return exprReferencesNames(e.object, names);
190
226
  case "TableConstructor":
@@ -209,10 +245,23 @@ function exprReferencesNames(e: LuaExpression, names: Set<string>): boolean {
209
245
  const c = e.clauses[i];
210
246
  switch (c.type) {
211
247
  case "From":
212
- if (exprReferencesNames(c.expression, names)) return true;
248
+ case "Select":
249
+ case "GroupBy":
250
+ for (const f of c.fields) {
251
+ switch (f.type) {
252
+ case "DynamicField":
253
+ if (exprReferencesNames(f.key, names)) return true;
254
+ if (exprReferencesNames(f.value, names)) return true;
255
+ break;
256
+ case "PropField":
257
+ case "ExpressionField":
258
+ if (exprReferencesNames(f.value, names)) return true;
259
+ break;
260
+ }
261
+ }
213
262
  break;
214
263
  case "Where":
215
- case "Select":
264
+ case "Having":
216
265
  if (exprReferencesNames(c.expression, names)) return true;
217
266
  break;
218
267
  case "Limit":
@@ -224,11 +273,31 @@ function exprReferencesNames(e: LuaExpression, names: Set<string>): boolean {
224
273
  if (exprReferencesNames(c.orderBy[j].expression, names)) {
225
274
  return true;
226
275
  }
276
+ if (
277
+ typeof c.orderBy[j].using === "string" &&
278
+ names.has(c.orderBy[j].using as string)
279
+ ) {
280
+ return true;
281
+ }
227
282
  }
228
283
  break;
229
284
  }
230
285
  }
231
286
  return false;
287
+ case "FilteredCall":
288
+ return (
289
+ exprReferencesNames(e.call, names) ||
290
+ exprReferencesNames(e.filter, names)
291
+ );
292
+ case "AggregateCall": {
293
+ const ac = e as any;
294
+ if (exprReferencesNames(ac.call, names)) return true;
295
+ for (const ob of ac.orderBy as LuaOrderBy[]) {
296
+ if (exprReferencesNames(ob.expression, names)) return true;
297
+ if (typeof ob.using === "string" && names.has(ob.using)) return true;
298
+ }
299
+ return false;
300
+ }
232
301
  default:
233
302
  return false;
234
303
  }
@@ -241,8 +310,10 @@ function lvalueReferencesNames(lv: LuaLValue, names: Set<string>): boolean {
241
310
  case "PropertyAccess":
242
311
  return exprReferencesNames(lv.object as LuaExpression, names);
243
312
  case "TableAccess":
244
- return exprReferencesNames(lv.object as LuaExpression, names) ||
245
- exprReferencesNames(lv.key, names);
313
+ return (
314
+ exprReferencesNames(lv.object as LuaExpression, names) ||
315
+ exprReferencesNames(lv.key, names)
316
+ );
246
317
  }
247
318
  }
248
319
 
@@ -375,10 +446,7 @@ function blockCapturesNames(block: LuaBlock, names: Set<string>): boolean {
375
446
  return false;
376
447
  }
377
448
 
378
- function statementCapturesNames(
379
- s: LuaStatement,
380
- names: Set<string>,
381
- ): boolean {
449
+ function statementCapturesNames(s: LuaStatement, names: Set<string>): boolean {
382
450
  switch (s.type) {
383
451
  case "Local": {
384
452
  const exprs = (s as any).expressions as LuaExpression[] | undefined;
@@ -471,8 +539,9 @@ function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
471
539
  case "FunctionDefinition":
472
540
  return functionBodyCapturesNames(e.body, names);
473
541
  case "Binary":
474
- return exprCapturesNames(e.left, names) ||
475
- exprCapturesNames(e.right, names);
542
+ return (
543
+ exprCapturesNames(e.left, names) || exprCapturesNames(e.right, names)
544
+ );
476
545
  case "Unary":
477
546
  return exprCapturesNames(e.argument, names);
478
547
  case "Parenthesized":
@@ -484,8 +553,9 @@ function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
484
553
  }
485
554
  return false;
486
555
  case "TableAccess":
487
- return exprCapturesNames(e.object, names) ||
488
- exprCapturesNames(e.key, names);
556
+ return (
557
+ exprCapturesNames(e.object, names) || exprCapturesNames(e.key, names)
558
+ );
489
559
  case "PropertyAccess":
490
560
  return exprCapturesNames(e.object, names);
491
561
  case "TableConstructor":
@@ -508,10 +578,23 @@ function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
508
578
  const c = e.clauses[i];
509
579
  switch (c.type) {
510
580
  case "From":
511
- if (exprCapturesNames(c.expression, names)) return true;
581
+ case "Select":
582
+ case "GroupBy":
583
+ for (const f of c.fields) {
584
+ switch (f.type) {
585
+ case "DynamicField":
586
+ if (exprCapturesNames(f.key, names)) return true;
587
+ if (exprCapturesNames(f.value, names)) return true;
588
+ break;
589
+ case "PropField":
590
+ case "ExpressionField":
591
+ if (exprCapturesNames(f.value, names)) return true;
592
+ break;
593
+ }
594
+ }
512
595
  break;
513
596
  case "Where":
514
- case "Select":
597
+ case "Having":
515
598
  if (exprCapturesNames(c.expression, names)) return true;
516
599
  break;
517
600
  case "Limit":
@@ -523,11 +606,31 @@ function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
523
606
  if (exprCapturesNames(c.orderBy[j].expression, names)) {
524
607
  return true;
525
608
  }
609
+ const u = c.orderBy[j].using;
610
+ if (u && typeof u !== "string") {
611
+ if (functionBodyCapturesNames(u, names)) return true;
612
+ }
526
613
  }
527
614
  break;
528
615
  }
529
616
  }
530
617
  return false;
618
+ case "FilteredCall":
619
+ return (
620
+ exprCapturesNames(e.call, names) || exprCapturesNames(e.filter, names)
621
+ );
622
+ case "AggregateCall": {
623
+ const ac = e as any;
624
+ if (exprCapturesNames(ac.call, names)) return true;
625
+ for (const ob of ac.orderBy as LuaOrderBy[]) {
626
+ if (exprCapturesNames(ob.expression, names)) return true;
627
+ const u = ob.using;
628
+ if (u && typeof u !== "string") {
629
+ if (functionBodyCapturesNames(u, names)) return true;
630
+ }
631
+ }
632
+ return false;
633
+ }
531
634
  default:
532
635
  return false;
533
636
  }
@@ -537,7 +640,7 @@ function parseBlock(t: ParseTree, ctx: ASTCtx): LuaBlock {
537
640
  if (t.type !== "Block") {
538
641
  throw new Error(`Expected Block, got ${t.type}`);
539
642
  }
540
- const stmtNodes = t.children!.filter((c) => c && c.type);
643
+ const stmtNodes = t.children!.filter((c) => c?.type);
541
644
  const statements = stmtNodes.map((s) => parseStatement(s, ctx));
542
645
  const block: LuaBlock = { type: "Block", statements, ctx: context(t, ctx) };
543
646
 
@@ -595,7 +698,8 @@ function parseBlock(t: ParseTree, ctx: ASTCtx): LuaBlock {
595
698
  case "FunctionCallStatement": {
596
699
  if (!hasFunctionDef) {
597
700
  const call = (s as any).call as LuaFunctionCallExpression;
598
- hasFunctionDef = expressionHasFunctionDef(call.prefix) ||
701
+ hasFunctionDef =
702
+ expressionHasFunctionDef(call.prefix) ||
599
703
  expressionsHaveFunctionDef(call.args);
600
704
  }
601
705
  break;
@@ -662,7 +766,8 @@ function parseBlock(t: ParseTree, ctx: ASTCtx): LuaBlock {
662
766
  hasCloseHere = hasCloseHere || !!child.hasCloseHere;
663
767
  hasFunctionDef = hasFunctionDef || !!child.hasFunctionDef;
664
768
  if (!hasFunctionDef) {
665
- hasFunctionDef = expressionHasFunctionDef((s as any).start) ||
769
+ hasFunctionDef =
770
+ expressionHasFunctionDef((s as any).start) ||
666
771
  expressionHasFunctionDef((s as any).end) ||
667
772
  ((s as any).step
668
773
  ? expressionHasFunctionDef((s as any).step)
@@ -765,7 +870,7 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
765
870
  from?: number;
766
871
  to?: number;
767
872
  }[] = [];
768
- let elseBlock: LuaBlock | undefined = undefined;
873
+ let elseBlock: LuaBlock | undefined;
769
874
  for (let i = 0; i < t.children!.length; i += 4) {
770
875
  const child = t.children![i];
771
876
  if (!child || !child.children || !child.children[0]) {
@@ -855,8 +960,8 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
855
960
  case "Assign":
856
961
  return {
857
962
  type: "Assignment",
858
- variables: t.children![0].children!
859
- .filter((c) => c.type && c.type !== ",")
963
+ variables: t
964
+ .children![0].children!.filter((c) => c.type && c.type !== ",")
860
965
  .map((lvalue) => parseLValue(lvalue, ctx)),
861
966
  expressions: parseExpList(t.children![2], ctx),
862
967
  ctx: context(t, ctx),
@@ -900,9 +1005,7 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
900
1005
  console.error(t);
901
1006
  throw new Error(
902
1007
  `Unknown statement type: ${
903
- t.children![0] && t.children![0].text
904
- ? t.children![0].text
905
- : String(t.type)
1008
+ t.children![0]?.text ? t.children![0].text : String(t.type)
906
1009
  }`,
907
1010
  );
908
1011
  }
@@ -913,28 +1016,44 @@ function parseFunctionCall(
913
1016
  ctx: ASTCtx,
914
1017
  ): LuaFunctionCallExpression {
915
1018
  if (t.children![1] && t.children![1].type === ":") {
916
- return {
1019
+ const { args, aggOrderBy } = parseFunctionArgsWithOrderBy(
1020
+ t.children!.slice(3),
1021
+ ctx,
1022
+ );
1023
+ const result: LuaFunctionCallExpression = {
917
1024
  type: "FunctionCall",
918
1025
  prefix: parsePrefixExpression(t.children![0], ctx),
919
1026
  name: t.children![2].children![0].text!,
920
- args: parseFunctionArgs(t.children!.slice(3), ctx),
1027
+ args,
921
1028
  ctx: context(t, ctx),
922
1029
  };
1030
+ if (aggOrderBy) {
1031
+ (result as any).orderBy = aggOrderBy;
1032
+ }
1033
+ return result;
923
1034
  }
924
- return {
1035
+ const { args, aggOrderBy } = parseFunctionArgsWithOrderBy(
1036
+ t.children!.slice(1),
1037
+ ctx,
1038
+ );
1039
+ const result: LuaFunctionCallExpression = {
925
1040
  type: "FunctionCall",
926
1041
  prefix: parsePrefixExpression(t.children![0], ctx),
927
- args: parseFunctionArgs(t.children!.slice(1), ctx),
1042
+ args,
928
1043
  ctx: context(t, ctx),
929
1044
  };
1045
+ if (aggOrderBy) {
1046
+ (result as any).orderBy = aggOrderBy;
1047
+ }
1048
+ return result;
930
1049
  }
931
1050
 
932
1051
  function parseAttNames(t: ParseTree, ctx: ASTCtx): LuaAttName[] {
933
1052
  if (t.type !== "AttNameList") {
934
1053
  throw new Error(`Expected AttNameList, got ${t.type}`);
935
1054
  }
936
- return t.children!
937
- .filter((c) => c.type && c.type !== ",")
1055
+ return t
1056
+ .children!.filter((c) => c.type && c.type !== ",")
938
1057
  .map((att) => parseAttName(att, ctx));
939
1058
  }
940
1059
 
@@ -995,7 +1114,7 @@ function parseFunctionName(t: ParseTree, ctx: ASTCtx): LuaFunctionName {
995
1114
  throw new Error(`Expected FunctionName, got ${t.type}`);
996
1115
  }
997
1116
  const propNames: string[] = [];
998
- let colonName: string | undefined = undefined;
1117
+ let colonName: string | undefined;
999
1118
  for (let i = 0; i < t.children!.length; i += 2) {
1000
1119
  const prop = t.children![i];
1001
1120
  propNames.push(prop.children![0].text!);
@@ -1016,8 +1135,8 @@ function parseNameList(t: ParseTree): string[] {
1016
1135
  if (t.type !== "NameList") {
1017
1136
  throw new Error(`Expected NameList, got ${t.type}`);
1018
1137
  }
1019
- return t.children!
1020
- .filter((c) => c.type === "Name")
1138
+ return t
1139
+ .children!.filter((c) => c.type === "Name")
1021
1140
  .map((c) => c.children![0].text!);
1022
1141
  }
1023
1142
 
@@ -1025,8 +1144,8 @@ function parseExpList(t: ParseTree, ctx: ASTCtx): LuaExpression[] {
1025
1144
  if (t.type !== "ExpList") {
1026
1145
  throw new Error(`Expected ExpList, got ${t.type}`);
1027
1146
  }
1028
- return t.children!
1029
- .filter((c) => c.type && c.type !== ",")
1147
+ return t
1148
+ .children!.filter((c) => c.type && c.type !== ",")
1030
1149
  .map((e) => parseExpression(e, ctx));
1031
1150
  }
1032
1151
 
@@ -1045,44 +1164,46 @@ function parseString(s: string): string {
1045
1164
  }
1046
1165
  return text;
1047
1166
  }
1048
- return s.slice(1, -1).replace(
1049
- /\\(x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\}|[abfnrtv\\'"n])/g,
1050
- (match, capture) => {
1051
- switch (capture) {
1052
- case "a":
1053
- return "\x07"; // Bell
1054
- case "b":
1055
- return "\b"; // Backspace
1056
- case "f":
1057
- return "\f"; // Form feed
1058
- case "n":
1059
- return "\n"; // Newline
1060
- case "r":
1061
- return "\r"; // Carriage return
1062
- case "t":
1063
- return "\t"; // Horizontal tab
1064
- case "v":
1065
- return "\v"; // Vertical tab
1066
- case "\\":
1067
- return "\\"; // Backslash
1068
- case '"':
1069
- return '"'; // Double quote
1070
- case "'":
1071
- return "'"; // Single quote
1072
- default:
1073
- // Handle hexadecimal \x00
1074
- if (capture.startsWith("x")) {
1075
- return String.fromCharCode(parseInt(capture.slice(1), 16));
1076
- }
1077
- // Handle unicode \u{XXXX}
1078
- if (capture.startsWith("u{")) {
1079
- const codePoint = parseInt(capture.slice(2, -1), 16);
1080
- return String.fromCodePoint(codePoint);
1081
- }
1082
- return match; // return the original match if nothing fits
1083
- }
1084
- },
1085
- );
1167
+ return s
1168
+ .slice(1, -1)
1169
+ .replace(
1170
+ /\\(x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\}|[abfnrtv\\'"n])/g,
1171
+ (match, capture) => {
1172
+ switch (capture) {
1173
+ case "a":
1174
+ return "\x07"; // Bell
1175
+ case "b":
1176
+ return "\b"; // Backspace
1177
+ case "f":
1178
+ return "\f"; // Form feed
1179
+ case "n":
1180
+ return "\n"; // Newline
1181
+ case "r":
1182
+ return "\r"; // Carriage return
1183
+ case "t":
1184
+ return "\t"; // Horizontal tab
1185
+ case "v":
1186
+ return "\v"; // Vertical tab
1187
+ case "\\":
1188
+ return "\\"; // Backslash
1189
+ case '"':
1190
+ return '"'; // Double quote
1191
+ case "'":
1192
+ return "'"; // Single quote
1193
+ default:
1194
+ // Handle hexadecimal \x00
1195
+ if (capture.startsWith("x")) {
1196
+ return String.fromCharCode(parseInt(capture.slice(1), 16));
1197
+ }
1198
+ // Handle unicode \u{XXXX}
1199
+ if (capture.startsWith("u{")) {
1200
+ const codePoint = parseInt(capture.slice(2, -1), 16);
1201
+ return String.fromCodePoint(codePoint);
1202
+ }
1203
+ return match; // return the original match if nothing fits
1204
+ }
1205
+ },
1206
+ );
1086
1207
  }
1087
1208
 
1088
1209
  function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
@@ -1103,8 +1224,9 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
1103
1224
  return {
1104
1225
  type: "Number",
1105
1226
  // Use the integer parser fox 0x literals
1227
+ // biome-ignore lint/correctness/useParseIntRadix: hex strings need auto-detect radix
1106
1228
  value: text.includes("x") ? parseInt(text) : parseFloat(text),
1107
- numericType: /[\.eEpP]/.test(text) ? "float" : "int",
1229
+ numericType: /[.eEpP]/.test(text) ? "float" : "int",
1108
1230
  ctx: context(t, ctx),
1109
1231
  };
1110
1232
  }
@@ -1174,10 +1296,10 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
1174
1296
  case "TableConstructor":
1175
1297
  return {
1176
1298
  type: "TableConstructor",
1177
- fields: t.children!
1178
- .slice(1, -1)
1299
+ fields: t
1300
+ .children!.slice(1, -1)
1179
1301
  .filter((c) =>
1180
- ["FieldExp", "FieldProp", "FieldDynamic"].includes(c.type!)
1302
+ ["FieldExp", "FieldProp", "FieldDynamic"].includes(c.type!),
1181
1303
  )
1182
1304
  .map((tf) => parseTableField(tf, ctx)),
1183
1305
  ctx: context(t, ctx),
@@ -1190,12 +1312,36 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
1190
1312
  clauses: t.children!.slice(2, -1).map((c) => parseQueryClause(c, ctx)),
1191
1313
  ctx: context(t, ctx),
1192
1314
  };
1315
+ case "FilteredCall": {
1316
+ const call = parseFunctionCall(t.children![0], ctx);
1317
+ const filterExpr = parseExpression(t.children![4], ctx);
1318
+ return {
1319
+ type: "FilteredCall",
1320
+ call,
1321
+ filter: filterExpr,
1322
+ ctx: context(t, ctx),
1323
+ };
1324
+ }
1193
1325
  default:
1194
1326
  console.error(t);
1195
1327
  throw new Error(`Unknown expression type: ${t.type}`);
1196
1328
  }
1197
1329
  }
1198
1330
 
1331
+ function parseFieldList(t: ParseTree, ctx: ASTCtx): LuaTableField[] {
1332
+ if (t.type !== "FieldList") {
1333
+ throw new Error(`Expected FieldList, got ${t.type}`);
1334
+ }
1335
+ return t
1336
+ .children!.filter(
1337
+ (c) =>
1338
+ c.type === "FieldExp" ||
1339
+ c.type === "FieldProp" ||
1340
+ c.type === "FieldDynamic",
1341
+ )
1342
+ .map((c) => parseTableField(c, ctx));
1343
+ }
1344
+
1199
1345
  function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause {
1200
1346
  if (t.type !== "QueryClause") {
1201
1347
  throw new Error(`Expected QueryClause, got ${t.type}`);
@@ -1203,18 +1349,14 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause {
1203
1349
  t = t.children![0];
1204
1350
  switch (t.type) {
1205
1351
  case "FromClause": {
1206
- if (t.children!.length === 4) {
1207
- // From clause with a name
1208
- return {
1209
- type: "From",
1210
- name: t.children![1].children![0].text!,
1211
- expression: parseExpression(t.children![3], ctx),
1212
- ctx: context(t, ctx),
1213
- };
1352
+ // children: ckw<"from">, FieldList
1353
+ const fieldListNode = t.children!.find((c) => c.type === "FieldList");
1354
+ if (!fieldListNode) {
1355
+ throw new Error("FromClause missing FieldList");
1214
1356
  }
1215
1357
  return {
1216
1358
  type: "From",
1217
- expression: parseExpression(t.children![1], ctx),
1359
+ fields: parseFieldList(fieldListNode, ctx),
1218
1360
  ctx: context(t, ctx),
1219
1361
  };
1220
1362
  }
@@ -1236,16 +1378,18 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause {
1236
1378
  ctx: context(t, ctx),
1237
1379
  };
1238
1380
  }
1381
+ case "OffsetClause": {
1382
+ return {
1383
+ type: "Offset",
1384
+ offset: parseExpression(t.children![1], ctx),
1385
+ ctx: context(t, ctx),
1386
+ };
1387
+ }
1239
1388
  case "OrderByClause": {
1240
1389
  const orderBy: LuaOrderBy[] = [];
1241
1390
  for (const child of t.children!) {
1242
1391
  if (child.type === "OrderBy") {
1243
- orderBy.push({
1244
- type: "Order",
1245
- expression: parseExpression(child.children![0], ctx),
1246
- direction: child.children![1]?.type === "desc" ? "desc" : "asc",
1247
- ctx: context(child, ctx),
1248
- });
1392
+ orderBy.push(parseOrderByNode(child, ctx));
1249
1393
  }
1250
1394
  }
1251
1395
  return {
@@ -1255,8 +1399,32 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause {
1255
1399
  };
1256
1400
  }
1257
1401
  case "SelectClause": {
1402
+ // children: ckw<"select">, FieldList
1403
+ const fieldListNode = t.children!.find((c) => c.type === "FieldList");
1404
+ if (!fieldListNode) {
1405
+ throw new Error("SelectClause missing FieldList");
1406
+ }
1258
1407
  return {
1259
1408
  type: "Select",
1409
+ fields: parseFieldList(fieldListNode, ctx),
1410
+ ctx: context(t, ctx),
1411
+ };
1412
+ }
1413
+ case "GroupByClause": {
1414
+ // children: ckw<"group">, ckw<"by">, FieldList
1415
+ const fieldListNode = t.children!.find((c) => c.type === "FieldList");
1416
+ if (!fieldListNode) {
1417
+ throw new Error("GroupByClause missing FieldList");
1418
+ }
1419
+ return {
1420
+ type: "GroupBy",
1421
+ fields: parseFieldList(fieldListNode, ctx),
1422
+ ctx: context(t, ctx),
1423
+ };
1424
+ }
1425
+ case "HavingClause": {
1426
+ return {
1427
+ type: "Having",
1260
1428
  expression: parseExpression(t.children![1], ctx),
1261
1429
  ctx: context(t, ctx),
1262
1430
  };
@@ -1267,10 +1435,70 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause {
1267
1435
  }
1268
1436
  }
1269
1437
 
1270
- function parseFunctionArgs(ts: ParseTree[], ctx: ASTCtx): LuaExpression[] {
1271
- return ts
1272
- .filter((t) => t.type && ![",", "(", ")"].includes(t.type))
1273
- .map((e) => parseExpression(e, ctx));
1438
+ // Parse a single OrderBy node (shared by query OrderByClause and AggOrderBy)
1439
+ function parseOrderByNode(child: ParseTree, ctx: ASTCtx): LuaOrderBy {
1440
+ const kids = child.children!;
1441
+ let direction: "asc" | "desc" = "asc";
1442
+ let nulls: "first" | "last" | undefined;
1443
+ let usingVal: string | LuaFunctionBody | undefined;
1444
+ for (let i = 1; i < kids.length; i++) {
1445
+ const typ = kids[i].type;
1446
+ if (typ === "desc") direction = "desc";
1447
+ else if (typ === "asc") direction = "asc";
1448
+ else if (typ === "first") nulls = "first";
1449
+ else if (typ === "last") nulls = "last";
1450
+ else if (typ === "using") {
1451
+ const next = kids[i + 1];
1452
+ if (next.type === "function") {
1453
+ usingVal = parseFunctionBody(kids[i + 2], ctx);
1454
+ i += 2;
1455
+ } else {
1456
+ usingVal = next.children![0].text!;
1457
+ i++;
1458
+ }
1459
+ }
1460
+ }
1461
+ const ob: LuaOrderBy = {
1462
+ type: "Order",
1463
+ expression: parseExpression(kids[0], ctx),
1464
+ direction,
1465
+ ctx: context(child, ctx),
1466
+ };
1467
+ if (nulls) ob.nulls = nulls;
1468
+ if (usingVal !== undefined) ob.using = usingVal;
1469
+ return ob;
1470
+ }
1471
+
1472
+ // Parse an AggOrderBy node into LuaOrderBy[]
1473
+ function parseAggOrderBy(t: ParseTree, ctx: ASTCtx): LuaOrderBy[] {
1474
+ if (t.type !== "AggOrderBy") {
1475
+ throw new Error(`Expected AggOrderBy, got ${t.type}`);
1476
+ }
1477
+ const orderBy: LuaOrderBy[] = [];
1478
+ for (const child of t.children!) {
1479
+ if (child.type === "OrderBy") {
1480
+ orderBy.push(parseOrderByNode(child, ctx));
1481
+ }
1482
+ }
1483
+ return orderBy;
1484
+ }
1485
+
1486
+ // Parse function args, extracting AggOrderBy if present inside funcParams
1487
+ function parseFunctionArgsWithOrderBy(
1488
+ ts: ParseTree[],
1489
+ ctx: ASTCtx,
1490
+ ): { args: LuaExpression[]; aggOrderBy?: LuaOrderBy[] } {
1491
+ let aggOrderBy: LuaOrderBy[] | undefined;
1492
+ const args: LuaExpression[] = [];
1493
+ for (const t of ts) {
1494
+ if (!t.type || [",", "(", ")"].includes(t.type)) continue;
1495
+ if (t.type === "AggOrderBy") {
1496
+ aggOrderBy = parseAggOrderBy(t, ctx);
1497
+ } else {
1498
+ args.push(parseExpression(t, ctx));
1499
+ }
1500
+ }
1501
+ return { args, aggOrderBy };
1274
1502
  }
1275
1503
 
1276
1504
  function parseFunctionBody(t: ParseTree, ctx: ASTCtx): LuaFunctionBody {
@@ -1279,8 +1507,10 @@ function parseFunctionBody(t: ParseTree, ctx: ASTCtx): LuaFunctionBody {
1279
1507
  }
1280
1508
  return {
1281
1509
  type: "FunctionBody",
1282
- parameters: t.children![1].children!
1283
- .filter((c) => c.type && ["Name", "Ellipsis"].includes(c.type))
1510
+ parameters: t
1511
+ .children![1].children!.filter(
1512
+ (c) => c.type && ["Name", "Ellipsis"].includes(c.type),
1513
+ )
1284
1514
  .map((c) => c.children![0].text!),
1285
1515
  block: parseBlock(t.children![3], ctx),
1286
1516
  ctx: context(t, ctx),
@@ -1371,7 +1601,7 @@ export function stripLuaComments(s: string): string {
1371
1601
  if (s[j] === "[") {
1372
1602
  // Found long string start
1373
1603
  const openBracket = s.substring(i, j + 1);
1374
- const closeBracket = "]" + "=".repeat(equalsCount) + "]";
1604
+ const closeBracket = `]${"=".repeat(equalsCount)}]`;
1375
1605
  result += openBracket;
1376
1606
  i = j + 1;
1377
1607
 
@@ -1424,7 +1654,7 @@ export function stripLuaComments(s: string): string {
1424
1654
  }
1425
1655
  if (s[j] === "[") {
1426
1656
  // Found long comment start
1427
- const closeBracket = "]" + "=".repeat(equalsCount) + "]";
1657
+ const closeBracket = `]${"=".repeat(equalsCount)}]`;
1428
1658
  // Replace opening bracket with spaces
1429
1659
  result += " ".repeat(j - i + 1);
1430
1660
  i = j + 1;
@@ -1468,9 +1698,7 @@ export function parse(s: string, ctx: ASTCtx = {}): LuaBlock {
1468
1698
  if (e && typeof e === "object" && "astCtx" in e) {
1469
1699
  throw new LuaRuntimeError(
1470
1700
  e.message,
1471
- LuaStackFrame.lostFrame.withCtx(
1472
- (e as any).astCtx as ASTCtx,
1473
- ),
1701
+ LuaStackFrame.lostFrame.withCtx((e as any).astCtx as ASTCtx),
1474
1702
  );
1475
1703
  }
1476
1704
  throw e;
@@ -1514,9 +1742,7 @@ function luaUnexpectedSymbolMessage(src: string, from: number): string {
1514
1742
  /**
1515
1743
  * Helper function to parse a Lua expression string
1516
1744
  */
1517
- export function parseExpressionString(
1518
- expr: string,
1519
- ): LuaExpression {
1745
+ export function parseExpressionString(expr: string): LuaExpression {
1520
1746
  const parsedLua = parse(`_(${expr})`) as LuaBlock;
1521
1747
  return (parsedLua.statements[0] as LuaFunctionCallStatement).call.args[0];
1522
1748
  }