@silverbulletmd/silverbullet 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/LICENSE.md +18 -0
  2. package/README.md +98 -0
  3. package/client/asset_bundle/bundle.ts +95 -0
  4. package/client/data/datastore.ts +85 -0
  5. package/client/data/kv_primitives.ts +25 -0
  6. package/client/markdown_parser/constants.ts +13 -0
  7. package/client/plugos/event.ts +36 -0
  8. package/client/plugos/eventhook.ts +8 -0
  9. package/client/plugos/hooks/code_widget.ts +59 -0
  10. package/client/plugos/hooks/command.ts +104 -0
  11. package/client/plugos/hooks/document_editor.ts +77 -0
  12. package/client/plugos/hooks/event.ts +187 -0
  13. package/client/plugos/hooks/mq.ts +154 -0
  14. package/client/plugos/hooks/plug_namespace.ts +85 -0
  15. package/client/plugos/hooks/slash_command.ts +192 -0
  16. package/client/plugos/hooks/syscall.ts +66 -0
  17. package/client/plugos/manifest_cache.ts +67 -0
  18. package/client/plugos/plug.ts +99 -0
  19. package/client/plugos/plug_compile.ts +202 -0
  20. package/client/plugos/protocol.ts +40 -0
  21. package/client/plugos/proxy_fetch.ts +53 -0
  22. package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
  23. package/client/plugos/sandboxes/sandbox.ts +14 -0
  24. package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
  25. package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
  26. package/client/plugos/syscalls/asset.ts +35 -0
  27. package/client/plugos/syscalls/clientStore.ts +21 -0
  28. package/client/plugos/syscalls/client_code_widget.ts +12 -0
  29. package/client/plugos/syscalls/code_widget.ts +24 -0
  30. package/client/plugos/syscalls/config.ts +46 -0
  31. package/client/plugos/syscalls/datastore.ts +89 -0
  32. package/client/plugos/syscalls/editor.ts +673 -0
  33. package/client/plugos/syscalls/event.ts +36 -0
  34. package/client/plugos/syscalls/fetch.ts +128 -0
  35. package/client/plugos/syscalls/index.ts +102 -0
  36. package/client/plugos/syscalls/jsonschema.ts +69 -0
  37. package/client/plugos/syscalls/language.ts +23 -0
  38. package/client/plugos/syscalls/lua.ts +58 -0
  39. package/client/plugos/syscalls/markdown.ts +84 -0
  40. package/client/plugos/syscalls/mq.ts +52 -0
  41. package/client/plugos/syscalls/service_registry.ts +43 -0
  42. package/client/plugos/syscalls/shell.ts +39 -0
  43. package/client/plugos/syscalls/space.ts +139 -0
  44. package/client/plugos/syscalls/sync.ts +77 -0
  45. package/client/plugos/syscalls/system.ts +150 -0
  46. package/client/plugos/system.ts +201 -0
  47. package/client/plugos/types.ts +60 -0
  48. package/client/plugos/util.ts +14 -0
  49. package/client/plugos/worker_runtime.ts +195 -0
  50. package/client/space_lua/ast.ts +328 -0
  51. package/client/space_lua/ast_narrow.ts +81 -0
  52. package/client/space_lua/eval.ts +2478 -0
  53. package/client/space_lua/labels.ts +416 -0
  54. package/client/space_lua/numeric.ts +240 -0
  55. package/client/space_lua/parse.ts +1522 -0
  56. package/client/space_lua/query_collection.ts +232 -0
  57. package/client/space_lua/rp.ts +27 -0
  58. package/client/space_lua/runtime.ts +1702 -0
  59. package/client/space_lua/stdlib/crypto.ts +10 -0
  60. package/client/space_lua/stdlib/encoding.ts +19 -0
  61. package/client/space_lua/stdlib/format.ts +770 -0
  62. package/client/space_lua/stdlib/js.ts +73 -0
  63. package/client/space_lua/stdlib/load.ts +52 -0
  64. package/client/space_lua/stdlib/math.ts +193 -0
  65. package/client/space_lua/stdlib/net.ts +113 -0
  66. package/client/space_lua/stdlib/os.ts +368 -0
  67. package/client/space_lua/stdlib/space_lua.ts +153 -0
  68. package/client/space_lua/stdlib/string.ts +286 -0
  69. package/client/space_lua/stdlib/table.ts +401 -0
  70. package/client/space_lua/stdlib.ts +489 -0
  71. package/client/space_lua/tonumber.ts +501 -0
  72. package/client/space_lua/util.ts +96 -0
  73. package/dist/plug-compile.js +1513 -0
  74. package/package.json +120 -0
  75. package/plug-api/constants.ts +42 -0
  76. package/plug-api/lib/async.ts +162 -0
  77. package/plug-api/lib/crypto.ts +202 -0
  78. package/plug-api/lib/dates.ts +13 -0
  79. package/plug-api/lib/json.ts +136 -0
  80. package/plug-api/lib/limited_map.ts +72 -0
  81. package/plug-api/lib/memory_cache.ts +21 -0
  82. package/plug-api/lib/native_fetch.ts +6 -0
  83. package/plug-api/lib/ref.ts +275 -0
  84. package/plug-api/lib/resolve.ts +90 -0
  85. package/plug-api/lib/tags.ts +15 -0
  86. package/plug-api/lib/transclusion.ts +122 -0
  87. package/plug-api/lib/tree.ts +232 -0
  88. package/plug-api/lib/yaml.ts +284 -0
  89. package/plug-api/syscall.ts +15 -0
  90. package/plug-api/syscalls/asset.ts +36 -0
  91. package/plug-api/syscalls/client_store.ts +33 -0
  92. package/plug-api/syscalls/code_widget.ts +8 -0
  93. package/plug-api/syscalls/config.ts +58 -0
  94. package/plug-api/syscalls/datastore.ts +96 -0
  95. package/plug-api/syscalls/editor.ts +517 -0
  96. package/plug-api/syscalls/event.ts +47 -0
  97. package/plug-api/syscalls/index.ts +77 -0
  98. package/plug-api/syscalls/jsonschema.ts +25 -0
  99. package/plug-api/syscalls/language.ts +23 -0
  100. package/plug-api/syscalls/lua.ts +20 -0
  101. package/plug-api/syscalls/markdown.ts +38 -0
  102. package/plug-api/syscalls/mq.ts +79 -0
  103. package/plug-api/syscalls/shell.ts +14 -0
  104. package/plug-api/syscalls/space.ts +212 -0
  105. package/plug-api/syscalls/sync.ts +28 -0
  106. package/plug-api/syscalls/system.ts +102 -0
  107. package/plug-api/syscalls/yaml.ts +28 -0
  108. package/plug-api/syscalls.ts +21 -0
  109. package/plug-api/system_mock.ts +89 -0
  110. package/plug-api/types/client.ts +116 -0
  111. package/plug-api/types/config.ts +22 -0
  112. package/plug-api/types/datastore.ts +28 -0
  113. package/plug-api/types/event.ts +27 -0
  114. package/plug-api/types/index.ts +56 -0
  115. package/plug-api/types/manifest.ts +98 -0
  116. package/plug-api/types/namespace.ts +6 -0
  117. package/plugs/builtin_plugs.ts +14 -0
@@ -0,0 +1,1522 @@
1
+ import { lezerToParseTree } from "../../client/markdown_parser/parse_tree.ts";
2
+ import type { SyntaxNode } from "@lezer/common";
3
+ import {
4
+ cleanTree,
5
+ type ParseTree,
6
+ } from "@silverbulletmd/silverbullet/lib/tree";
7
+ import { parser } from "./parse-lua.js";
8
+ import { styleTags, tags as t } from "@lezer/highlight";
9
+ import { indentNodeProp, LRLanguage } from "@codemirror/language";
10
+ import type {
11
+ ASTCtx,
12
+ LuaAttName,
13
+ LuaBlock,
14
+ LuaExpression,
15
+ LuaFunctionBody,
16
+ LuaFunctionCallExpression,
17
+ LuaFunctionCallStatement,
18
+ LuaFunctionName,
19
+ LuaIfStatement,
20
+ LuaLValue,
21
+ LuaOrderBy,
22
+ LuaPrefixExpression,
23
+ LuaQueryClause,
24
+ LuaStatement,
25
+ LuaTableField,
26
+ } from "./ast.ts";
27
+ import { LuaAttribute } from "./ast.ts";
28
+ import { getBlockGotoMeta } from "./labels.ts";
29
+ import { LuaRuntimeError, LuaStackFrame } from "./runtime.ts";
30
+
31
+ const luaStyleTags = styleTags({
32
+ Name: t.variableName,
33
+ LiteralString: t.string,
34
+ Number: t.number,
35
+ CompareOp: t.operator,
36
+ "true false": t.bool,
37
+ 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
+ t.keyword,
40
+ });
41
+
42
+ const customIndent = indentNodeProp.add({
43
+ "IfStatement FuncBody WhileStatement ForStatement TableConstructor": (
44
+ context,
45
+ ) => {
46
+ return context.lineIndent(context.node.from) + context.unit;
47
+ },
48
+ });
49
+
50
+ // Use the customIndent in your language support
51
+ export const luaLanguage = LRLanguage.define({
52
+ name: "space-lua",
53
+ parser: parser.configure({
54
+ props: [
55
+ luaStyleTags,
56
+ customIndent,
57
+ ],
58
+ }),
59
+ languageData: {
60
+ commentTokens: { line: "--", block: { open: "--[[", close: "--]]" } },
61
+ },
62
+ });
63
+
64
+ function context(t: ParseTree, ctx: Record<string, any>): ASTCtx {
65
+ return { ...ctx, from: t.from, to: t.to };
66
+ }
67
+
68
+ function parseChunk(t: ParseTree, ctx: ASTCtx): LuaBlock {
69
+ if (t.type !== "Chunk") {
70
+ throw new Error(`Expected Chunk, got ${t.type}`);
71
+ }
72
+ return parseBlock(t.children![0], ctx);
73
+ }
74
+
75
+ function hasCloseLocal(names: LuaAttName[] | undefined): boolean {
76
+ if (!names) {
77
+ return false;
78
+ }
79
+ for (const n of names) {
80
+ if (n.attributes?.includes(LuaAttribute.Close) === true) {
81
+ return true;
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+
87
+ function expressionHasFunctionDef(e: LuaExpression): boolean {
88
+ if (!e) return false;
89
+ switch (e.type) {
90
+ case "FunctionDefinition":
91
+ return true;
92
+ case "FunctionCall":
93
+ if (expressionHasFunctionDef(e.prefix)) return true;
94
+ for (let i = 0; i < e.args.length; i++) {
95
+ if (expressionHasFunctionDef(e.args[i])) return true;
96
+ }
97
+ return false;
98
+ case "Binary":
99
+ return expressionHasFunctionDef(e.left) ||
100
+ expressionHasFunctionDef(e.right);
101
+ case "Unary":
102
+ return expressionHasFunctionDef(e.argument);
103
+ case "Parenthesized":
104
+ return expressionHasFunctionDef(e.expression);
105
+ case "TableConstructor":
106
+ for (let i = 0; i < e.fields.length; i++) {
107
+ const f = e.fields[i];
108
+ switch (f.type) {
109
+ case "DynamicField":
110
+ if (expressionHasFunctionDef(f.key)) return true;
111
+ if (expressionHasFunctionDef(f.value)) return true;
112
+ break;
113
+ case "PropField":
114
+ case "ExpressionField":
115
+ if (expressionHasFunctionDef(f.value)) return true;
116
+ break;
117
+ }
118
+ }
119
+ return false;
120
+ case "TableAccess":
121
+ return expressionHasFunctionDef(e.object) ||
122
+ expressionHasFunctionDef(e.key);
123
+ case "PropertyAccess":
124
+ return expressionHasFunctionDef(e.object);
125
+ case "Query":
126
+ for (let i = 0; i < e.clauses.length; i++) {
127
+ const c = e.clauses[i];
128
+ switch (c.type) {
129
+ case "From":
130
+ if (expressionHasFunctionDef(c.expression)) return true;
131
+ break;
132
+ case "Where":
133
+ case "Select":
134
+ if (expressionHasFunctionDef(c.expression)) return true;
135
+ break;
136
+ case "Limit":
137
+ if (expressionHasFunctionDef(c.limit)) return true;
138
+ if (c.offset && expressionHasFunctionDef(c.offset)) return true;
139
+ break;
140
+ case "OrderBy":
141
+ for (let j = 0; j < c.orderBy.length; j++) {
142
+ if (expressionHasFunctionDef(c.orderBy[j].expression)) {
143
+ return true;
144
+ }
145
+ }
146
+ break;
147
+ }
148
+ }
149
+ return false;
150
+ default:
151
+ return false;
152
+ }
153
+ }
154
+
155
+ function expressionsHaveFunctionDef(
156
+ exprs: LuaExpression[] | undefined,
157
+ ): boolean {
158
+ if (!exprs) return false;
159
+ for (let i = 0; i < exprs.length; i++) {
160
+ if (expressionHasFunctionDef(exprs[i])) return true;
161
+ }
162
+ return false;
163
+ }
164
+
165
+ // Does the expression reference any of `names`?
166
+ // Note: It DOES NOT descend into `FunctionDefinition`.
167
+ function exprReferencesNames(e: LuaExpression, names: Set<string>): boolean {
168
+ if (!e) return false;
169
+ switch (e.type) {
170
+ case "Variable":
171
+ return names.has(e.name);
172
+ case "Binary":
173
+ return exprReferencesNames(e.left, names) ||
174
+ exprReferencesNames(e.right, names);
175
+ case "Unary":
176
+ return exprReferencesNames(e.argument, names);
177
+ case "Parenthesized":
178
+ return exprReferencesNames(e.expression, names);
179
+ case "FunctionCall":
180
+ if (exprReferencesNames(e.prefix, names)) return true;
181
+ for (let i = 0; i < e.args.length; i++) {
182
+ if (exprReferencesNames(e.args[i], names)) return true;
183
+ }
184
+ return false;
185
+ case "TableAccess":
186
+ return exprReferencesNames(e.object, names) ||
187
+ exprReferencesNames(e.key, names);
188
+ case "PropertyAccess":
189
+ return exprReferencesNames(e.object, names);
190
+ case "TableConstructor":
191
+ for (let i = 0; i < e.fields.length; i++) {
192
+ const f = e.fields[i];
193
+ switch (f.type) {
194
+ case "DynamicField":
195
+ if (exprReferencesNames(f.key, names)) return true;
196
+ if (exprReferencesNames(f.value, names)) return true;
197
+ break;
198
+ case "PropField":
199
+ case "ExpressionField":
200
+ if (exprReferencesNames(f.value, names)) return true;
201
+ break;
202
+ }
203
+ }
204
+ return false;
205
+ case "FunctionDefinition":
206
+ return false;
207
+ case "Query":
208
+ for (let i = 0; i < e.clauses.length; i++) {
209
+ const c = e.clauses[i];
210
+ switch (c.type) {
211
+ case "From":
212
+ if (exprReferencesNames(c.expression, names)) return true;
213
+ break;
214
+ case "Where":
215
+ case "Select":
216
+ if (exprReferencesNames(c.expression, names)) return true;
217
+ break;
218
+ case "Limit":
219
+ if (exprReferencesNames(c.limit, names)) return true;
220
+ if (c.offset && exprReferencesNames(c.offset, names)) return true;
221
+ break;
222
+ case "OrderBy":
223
+ for (let j = 0; j < c.orderBy.length; j++) {
224
+ if (exprReferencesNames(c.orderBy[j].expression, names)) {
225
+ return true;
226
+ }
227
+ }
228
+ break;
229
+ }
230
+ }
231
+ return false;
232
+ default:
233
+ return false;
234
+ }
235
+ }
236
+
237
+ function lvalueReferencesNames(lv: LuaLValue, names: Set<string>): boolean {
238
+ switch (lv.type) {
239
+ case "Variable":
240
+ return names.has(lv.name);
241
+ case "PropertyAccess":
242
+ return exprReferencesNames(lv.object as LuaExpression, names);
243
+ case "TableAccess":
244
+ return exprReferencesNames(lv.object as LuaExpression, names) ||
245
+ exprReferencesNames(lv.key, names);
246
+ }
247
+ }
248
+
249
+ // Does a function body reference any of `names` NOT shadowed by its
250
+ // parameters?
251
+ function functionBodyCapturesNames(
252
+ body: LuaFunctionBody,
253
+ names: Set<string>,
254
+ ): boolean {
255
+ let unshadowed: Set<string> | null = null;
256
+ for (let i = 0; i < body.parameters.length; i++) {
257
+ if (names.has(body.parameters[i])) {
258
+ if (!unshadowed) unshadowed = new Set(names);
259
+ unshadowed.delete(body.parameters[i]);
260
+ }
261
+ }
262
+ const check = unshadowed ?? names;
263
+ if (check.size === 0) return false;
264
+ return blockReferencesNames(body.block, check);
265
+ }
266
+
267
+ // Walk block using `exprReferencesNames` (inside a function body).
268
+ function blockReferencesNames(block: LuaBlock, names: Set<string>): boolean {
269
+ for (let i = 0; i < block.statements.length; i++) {
270
+ if (statementReferencesNames(block.statements[i], names)) return true;
271
+ }
272
+ return false;
273
+ }
274
+
275
+ function statementReferencesNames(
276
+ s: LuaStatement,
277
+ names: Set<string>,
278
+ ): boolean {
279
+ switch (s.type) {
280
+ case "Local": {
281
+ const exprs = (s as any).expressions as LuaExpression[] | undefined;
282
+ if (exprs) {
283
+ for (let i = 0; i < exprs.length; i++) {
284
+ if (exprReferencesNames(exprs[i], names)) return true;
285
+ }
286
+ }
287
+ return false;
288
+ }
289
+ case "LocalFunction": {
290
+ const lf = s as any;
291
+ return functionBodyCapturesNames(lf.body as LuaFunctionBody, names);
292
+ }
293
+ case "Function": {
294
+ const fn = s as any;
295
+ return functionBodyCapturesNames(fn.body as LuaFunctionBody, names);
296
+ }
297
+ case "FunctionCallStatement": {
298
+ const call = (s as any).call as LuaFunctionCallExpression;
299
+ if (exprReferencesNames(call.prefix, names)) return true;
300
+ for (let i = 0; i < call.args.length; i++) {
301
+ if (exprReferencesNames(call.args[i], names)) return true;
302
+ }
303
+ return false;
304
+ }
305
+ case "Assignment": {
306
+ const a = s as any;
307
+ const vars = a.variables as LuaLValue[];
308
+ if (vars) {
309
+ for (let i = 0; i < vars.length; i++) {
310
+ if (lvalueReferencesNames(vars[i], names)) return true;
311
+ }
312
+ }
313
+ const exprs = a.expressions as LuaExpression[];
314
+ for (let i = 0; i < exprs.length; i++) {
315
+ if (exprReferencesNames(exprs[i], names)) return true;
316
+ }
317
+ return false;
318
+ }
319
+ case "Return": {
320
+ const exprs = (s as any).expressions as LuaExpression[];
321
+ for (let i = 0; i < exprs.length; i++) {
322
+ if (exprReferencesNames(exprs[i], names)) return true;
323
+ }
324
+ return false;
325
+ }
326
+ case "Block":
327
+ return blockReferencesNames(s as LuaBlock, names);
328
+ case "If": {
329
+ const iff = s as LuaIfStatement;
330
+ for (const c of iff.conditions) {
331
+ if (exprReferencesNames(c.condition, names)) return true;
332
+ if (blockReferencesNames(c.block, names)) return true;
333
+ }
334
+ if (iff.elseBlock && blockReferencesNames(iff.elseBlock, names)) {
335
+ return true;
336
+ }
337
+ return false;
338
+ }
339
+ case "While": {
340
+ const w = s as any;
341
+ if (exprReferencesNames(w.condition, names)) return true;
342
+ return blockReferencesNames(w.block as LuaBlock, names);
343
+ }
344
+ case "Repeat": {
345
+ const r = s as any;
346
+ if (blockReferencesNames(r.block as LuaBlock, names)) return true;
347
+ if (exprReferencesNames(r.condition, names)) return true;
348
+ return false;
349
+ }
350
+ case "For": {
351
+ const fr = s as any;
352
+ if (exprReferencesNames(fr.start, names)) return true;
353
+ if (exprReferencesNames(fr.end, names)) return true;
354
+ if (fr.step && exprReferencesNames(fr.step, names)) return true;
355
+ return blockReferencesNames(fr.block as LuaBlock, names);
356
+ }
357
+ case "ForIn": {
358
+ const fi = s as any;
359
+ const exprs = fi.expressions as LuaExpression[];
360
+ for (let i = 0; i < exprs.length; i++) {
361
+ if (exprReferencesNames(exprs[i], names)) return true;
362
+ }
363
+ return blockReferencesNames(fi.block as LuaBlock, names);
364
+ }
365
+ default:
366
+ return false;
367
+ }
368
+ }
369
+
370
+ // Walk block looking for `FunctionDefinition` nodes that capture `names`.
371
+ function blockCapturesNames(block: LuaBlock, names: Set<string>): boolean {
372
+ for (let i = 0; i < block.statements.length; i++) {
373
+ if (statementCapturesNames(block.statements[i], names)) return true;
374
+ }
375
+ return false;
376
+ }
377
+
378
+ function statementCapturesNames(
379
+ s: LuaStatement,
380
+ names: Set<string>,
381
+ ): boolean {
382
+ switch (s.type) {
383
+ case "Local": {
384
+ const exprs = (s as any).expressions as LuaExpression[] | undefined;
385
+ if (exprs) {
386
+ for (let i = 0; i < exprs.length; i++) {
387
+ if (exprCapturesNames(exprs[i], names)) return true;
388
+ }
389
+ }
390
+ return false;
391
+ }
392
+ case "LocalFunction": {
393
+ const lf = s as any;
394
+ return functionBodyCapturesNames(lf.body as LuaFunctionBody, names);
395
+ }
396
+ case "Function": {
397
+ const fn = s as any;
398
+ return functionBodyCapturesNames(fn.body as LuaFunctionBody, names);
399
+ }
400
+ case "FunctionCallStatement": {
401
+ const call = (s as any).call as LuaFunctionCallExpression;
402
+ if (exprCapturesNames(call.prefix, names)) return true;
403
+ for (let i = 0; i < call.args.length; i++) {
404
+ if (exprCapturesNames(call.args[i], names)) return true;
405
+ }
406
+ return false;
407
+ }
408
+ case "Assignment": {
409
+ const exprs = (s as any).expressions as LuaExpression[];
410
+ for (let i = 0; i < exprs.length; i++) {
411
+ if (exprCapturesNames(exprs[i], names)) return true;
412
+ }
413
+ return false;
414
+ }
415
+ case "Return": {
416
+ const exprs = (s as any).expressions as LuaExpression[];
417
+ for (let i = 0; i < exprs.length; i++) {
418
+ if (exprCapturesNames(exprs[i], names)) return true;
419
+ }
420
+ return false;
421
+ }
422
+ case "Block":
423
+ return blockCapturesNames(s as LuaBlock, names);
424
+ case "If": {
425
+ const iff = s as LuaIfStatement;
426
+ for (const c of iff.conditions) {
427
+ if (exprCapturesNames(c.condition, names)) return true;
428
+ if (blockCapturesNames(c.block, names)) return true;
429
+ }
430
+ if (iff.elseBlock && blockCapturesNames(iff.elseBlock, names)) {
431
+ return true;
432
+ }
433
+ return false;
434
+ }
435
+ case "While": {
436
+ const w = s as any;
437
+ if (exprCapturesNames(w.condition, names)) return true;
438
+ return blockCapturesNames(w.block as LuaBlock, names);
439
+ }
440
+ case "Repeat": {
441
+ const r = s as any;
442
+ if (blockCapturesNames(r.block as LuaBlock, names)) return true;
443
+ if (exprCapturesNames(r.condition, names)) return true;
444
+ return false;
445
+ }
446
+ case "For": {
447
+ const fr = s as any;
448
+ if (exprCapturesNames(fr.start, names)) return true;
449
+ if (exprCapturesNames(fr.end, names)) return true;
450
+ if (fr.step && exprCapturesNames(fr.step, names)) return true;
451
+ return blockCapturesNames(fr.block as LuaBlock, names);
452
+ }
453
+ case "ForIn": {
454
+ const fi = s as any;
455
+ const exprs = fi.expressions as LuaExpression[];
456
+ for (let i = 0; i < exprs.length; i++) {
457
+ if (exprCapturesNames(exprs[i], names)) return true;
458
+ }
459
+ return blockCapturesNames(fi.block as LuaBlock, names);
460
+ }
461
+ default:
462
+ return false;
463
+ }
464
+ }
465
+
466
+ // At loop block level find `FunctionDefinition` and check if it
467
+ // captures `names`.
468
+ function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
469
+ if (!e) return false;
470
+ switch (e.type) {
471
+ case "FunctionDefinition":
472
+ return functionBodyCapturesNames(e.body, names);
473
+ case "Binary":
474
+ return exprCapturesNames(e.left, names) ||
475
+ exprCapturesNames(e.right, names);
476
+ case "Unary":
477
+ return exprCapturesNames(e.argument, names);
478
+ case "Parenthesized":
479
+ return exprCapturesNames(e.expression, names);
480
+ case "FunctionCall":
481
+ if (exprCapturesNames(e.prefix, names)) return true;
482
+ for (let i = 0; i < e.args.length; i++) {
483
+ if (exprCapturesNames(e.args[i], names)) return true;
484
+ }
485
+ return false;
486
+ case "TableAccess":
487
+ return exprCapturesNames(e.object, names) ||
488
+ exprCapturesNames(e.key, names);
489
+ case "PropertyAccess":
490
+ return exprCapturesNames(e.object, names);
491
+ case "TableConstructor":
492
+ for (let i = 0; i < e.fields.length; i++) {
493
+ const f = e.fields[i];
494
+ switch (f.type) {
495
+ case "DynamicField":
496
+ if (exprCapturesNames(f.key, names)) return true;
497
+ if (exprCapturesNames(f.value, names)) return true;
498
+ break;
499
+ case "PropField":
500
+ case "ExpressionField":
501
+ if (exprCapturesNames(f.value, names)) return true;
502
+ break;
503
+ }
504
+ }
505
+ return false;
506
+ case "Query":
507
+ for (let i = 0; i < e.clauses.length; i++) {
508
+ const c = e.clauses[i];
509
+ switch (c.type) {
510
+ case "From":
511
+ if (exprCapturesNames(c.expression, names)) return true;
512
+ break;
513
+ case "Where":
514
+ case "Select":
515
+ if (exprCapturesNames(c.expression, names)) return true;
516
+ break;
517
+ case "Limit":
518
+ if (exprCapturesNames(c.limit, names)) return true;
519
+ if (c.offset && exprCapturesNames(c.offset, names)) return true;
520
+ break;
521
+ case "OrderBy":
522
+ for (let j = 0; j < c.orderBy.length; j++) {
523
+ if (exprCapturesNames(c.orderBy[j].expression, names)) {
524
+ return true;
525
+ }
526
+ }
527
+ break;
528
+ }
529
+ }
530
+ return false;
531
+ default:
532
+ return false;
533
+ }
534
+ }
535
+
536
+ function parseBlock(t: ParseTree, ctx: ASTCtx): LuaBlock {
537
+ if (t.type !== "Block") {
538
+ throw new Error(`Expected Block, got ${t.type}`);
539
+ }
540
+ const stmtNodes = t.children!.filter((c) => c && c.type);
541
+ const statements = stmtNodes.map((s) => parseStatement(s, ctx));
542
+ const block: LuaBlock = { type: "Block", statements, ctx: context(t, ctx) };
543
+
544
+ let hasLabel = false;
545
+ let hasGoto = false;
546
+ let hasLocalDecl = false;
547
+ let dup: { name: string; ctx: ASTCtx } | undefined;
548
+ let hasLabelHere = false;
549
+ let hasCloseHere = false;
550
+ let hasFunctionDef = false;
551
+
552
+ const seen = new Set<string>();
553
+
554
+ for (const s of statements) {
555
+ switch (s.type) {
556
+ case "Label": {
557
+ hasLabel = true;
558
+ hasLabelHere = true;
559
+ // Duplicate labels in the same block are illegal
560
+ const name = (s as any).name as string;
561
+ if (!dup) {
562
+ if (seen.has(name)) {
563
+ dup = { name, ctx: (s as any).ctx as ASTCtx };
564
+ } else {
565
+ seen.add(name);
566
+ }
567
+ }
568
+ break;
569
+ }
570
+ case "Goto": {
571
+ hasGoto = true;
572
+ break;
573
+ }
574
+ case "Local": {
575
+ hasLocalDecl = true;
576
+ if (!hasCloseHere) {
577
+ hasCloseHere = hasCloseLocal((s as any).names as LuaAttName[]);
578
+ }
579
+ if (!hasFunctionDef) {
580
+ hasFunctionDef = expressionsHaveFunctionDef(
581
+ (s as any).expressions as LuaExpression[] | undefined,
582
+ );
583
+ }
584
+ break;
585
+ }
586
+ case "LocalFunction": {
587
+ hasLocalDecl = true;
588
+ hasFunctionDef = true;
589
+ break;
590
+ }
591
+ case "Function": {
592
+ hasFunctionDef = true;
593
+ break;
594
+ }
595
+ case "FunctionCallStatement": {
596
+ if (!hasFunctionDef) {
597
+ const call = (s as any).call as LuaFunctionCallExpression;
598
+ hasFunctionDef = expressionHasFunctionDef(call.prefix) ||
599
+ expressionsHaveFunctionDef(call.args);
600
+ }
601
+ break;
602
+ }
603
+ case "Assignment": {
604
+ if (!hasFunctionDef) {
605
+ hasFunctionDef = expressionsHaveFunctionDef(
606
+ (s as any).expressions as LuaExpression[],
607
+ );
608
+ }
609
+ break;
610
+ }
611
+ case "Return": {
612
+ if (!hasFunctionDef) {
613
+ hasFunctionDef = expressionsHaveFunctionDef(
614
+ (s as any).expressions as LuaExpression[],
615
+ );
616
+ }
617
+ break;
618
+ }
619
+ case "Block": {
620
+ const child = s as LuaBlock;
621
+ hasLabel = hasLabel || !!child.hasLabel;
622
+ hasGoto = hasGoto || !!child.hasGoto;
623
+ hasCloseHere = hasCloseHere || !!child.hasCloseHere;
624
+ hasFunctionDef = hasFunctionDef || !!child.hasFunctionDef;
625
+ break;
626
+ }
627
+ case "If": {
628
+ const iff = s as LuaIfStatement;
629
+ for (const c of iff.conditions) {
630
+ hasLabel = hasLabel || !!c.block.hasLabel;
631
+ hasGoto = hasGoto || !!c.block.hasGoto;
632
+ hasCloseHere = hasCloseHere || !!c.block.hasCloseHere;
633
+ hasFunctionDef = hasFunctionDef || !!c.block.hasFunctionDef;
634
+ if (!hasFunctionDef) {
635
+ hasFunctionDef = expressionHasFunctionDef(c.condition);
636
+ }
637
+ }
638
+ if (iff.elseBlock) {
639
+ hasLabel = hasLabel || !!iff.elseBlock.hasLabel;
640
+ hasGoto = hasGoto || !!iff.elseBlock.hasGoto;
641
+ hasCloseHere = hasCloseHere || !!iff.elseBlock.hasCloseHere;
642
+ hasFunctionDef = hasFunctionDef || !!iff.elseBlock.hasFunctionDef;
643
+ }
644
+ break;
645
+ }
646
+ case "While":
647
+ case "Repeat": {
648
+ const child = (s as any).block as LuaBlock;
649
+ hasLabel = hasLabel || !!child.hasLabel;
650
+ hasGoto = hasGoto || !!child.hasGoto;
651
+ hasCloseHere = hasCloseHere || !!child.hasCloseHere;
652
+ hasFunctionDef = hasFunctionDef || !!child.hasFunctionDef;
653
+ if (!hasFunctionDef) {
654
+ hasFunctionDef = expressionHasFunctionDef((s as any).condition);
655
+ }
656
+ break;
657
+ }
658
+ case "For": {
659
+ const child = (s as any).block as LuaBlock;
660
+ hasLabel = hasLabel || !!child.hasLabel;
661
+ hasGoto = hasGoto || !!child.hasGoto;
662
+ hasCloseHere = hasCloseHere || !!child.hasCloseHere;
663
+ hasFunctionDef = hasFunctionDef || !!child.hasFunctionDef;
664
+ if (!hasFunctionDef) {
665
+ hasFunctionDef = expressionHasFunctionDef((s as any).start) ||
666
+ expressionHasFunctionDef((s as any).end) ||
667
+ ((s as any).step
668
+ ? expressionHasFunctionDef((s as any).step)
669
+ : false);
670
+ }
671
+ break;
672
+ }
673
+ case "ForIn": {
674
+ const child = (s as any).block as LuaBlock;
675
+ hasLabel = hasLabel || !!child.hasLabel;
676
+ hasGoto = hasGoto || !!child.hasGoto;
677
+ hasCloseHere = true;
678
+ hasFunctionDef = hasFunctionDef || !!child.hasFunctionDef;
679
+ if (!hasFunctionDef) {
680
+ hasFunctionDef = expressionsHaveFunctionDef(
681
+ (s as any).expressions as LuaExpression[],
682
+ );
683
+ }
684
+ break;
685
+ }
686
+ default: {
687
+ break;
688
+ }
689
+ }
690
+ }
691
+
692
+ if (hasLabel) {
693
+ block.hasLabel = true;
694
+ }
695
+ if (hasGoto) {
696
+ block.hasGoto = true;
697
+ }
698
+ if (dup) {
699
+ block.dupLabelError = dup;
700
+ }
701
+ if (hasLocalDecl) {
702
+ block.needsEnv = true;
703
+ }
704
+ if (hasLabelHere) {
705
+ block.hasLabelHere = true;
706
+ }
707
+ if (hasCloseHere) {
708
+ block.hasCloseHere = true;
709
+ }
710
+ if (hasFunctionDef) {
711
+ block.hasFunctionDef = true;
712
+ }
713
+
714
+ return block;
715
+ }
716
+
717
+ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
718
+ if (!t || !t.type) {
719
+ return {
720
+ type: "Semicolon",
721
+ ctx: context(t, ctx),
722
+ };
723
+ }
724
+ switch (t.type) {
725
+ case "Block":
726
+ return parseChunk(t.children![0], ctx);
727
+ case "Semicolon":
728
+ return { type: "Semicolon", ctx: context(t, ctx) };
729
+ case "Label":
730
+ return {
731
+ type: "Label",
732
+ name: t.children![1].children![0].text!,
733
+ ctx: context(t, ctx),
734
+ };
735
+ case "Break":
736
+ return { type: "Break", ctx: context(t, ctx) };
737
+ case "Goto":
738
+ return {
739
+ type: "Goto",
740
+ name: t.children![1].children![0].text!,
741
+ ctx: context(t, ctx),
742
+ };
743
+ case "Scope":
744
+ return parseBlock(t.children![1], ctx);
745
+ case ";":
746
+ return { type: "Semicolon", ctx: context(t, ctx) };
747
+ case "WhileStatement":
748
+ return {
749
+ type: "While",
750
+ condition: parseExpression(t.children![1], ctx),
751
+ block: parseBlock(t.children![3], ctx),
752
+ ctx: context(t, ctx),
753
+ };
754
+ case "RepeatStatement":
755
+ return {
756
+ type: "Repeat",
757
+ block: parseBlock(t.children![1], ctx),
758
+ condition: parseExpression(t.children![3], ctx),
759
+ ctx: context(t, ctx),
760
+ };
761
+ case "IfStatement": {
762
+ const conditions: {
763
+ condition: LuaExpression;
764
+ block: LuaBlock;
765
+ from?: number;
766
+ to?: number;
767
+ }[] = [];
768
+ let elseBlock: LuaBlock | undefined = undefined;
769
+ for (let i = 0; i < t.children!.length; i += 4) {
770
+ const child = t.children![i];
771
+ if (!child || !child.children || !child.children[0]) {
772
+ continue;
773
+ }
774
+ const token = child.children![0].text;
775
+ if (token === "if" || token === "elseif") {
776
+ conditions.push({
777
+ condition: parseExpression(t.children![i + 1], ctx),
778
+ block: parseBlock(t.children![i + 3], ctx),
779
+ from: child.from,
780
+ to: child.to,
781
+ });
782
+ } else if (token === "else") {
783
+ elseBlock = parseBlock(t.children![i + 1], ctx);
784
+ } else if (token === "end") {
785
+ break;
786
+ } else {
787
+ throw new Error(`Unknown if clause type: ${token}`);
788
+ }
789
+ }
790
+ return {
791
+ type: "If",
792
+ conditions,
793
+ elseBlock,
794
+ ctx: context(t, ctx),
795
+ };
796
+ }
797
+ case "ForStatement": {
798
+ if (t.children![1].type === "ForNumeric") {
799
+ const forNumeric = t.children![1];
800
+ const name = forNumeric.children![0].children![0].text!;
801
+ const block = parseBlock(t.children![3], ctx);
802
+ const node: LuaStatement = {
803
+ type: "For",
804
+ name,
805
+ start: parseExpression(forNumeric.children![2], ctx),
806
+ end: parseExpression(forNumeric.children![4], ctx),
807
+ step: forNumeric.children![5]
808
+ ? parseExpression(forNumeric.children![6], ctx)
809
+ : undefined,
810
+ block,
811
+ ctx: context(t, ctx),
812
+ };
813
+ if (block.hasFunctionDef) {
814
+ const names = new Set([name]);
815
+ (node as any).capturesLoopVar = blockCapturesNames(block, names);
816
+ }
817
+ return node;
818
+ }
819
+ const forGeneric = t.children![1];
820
+ const names = parseNameList(forGeneric.children![0]);
821
+ const block = parseBlock(t.children![3], ctx);
822
+ const node: LuaStatement = {
823
+ type: "ForIn",
824
+ names,
825
+ expressions: parseExpList(forGeneric.children![2], ctx),
826
+ block,
827
+ ctx: context(t, ctx),
828
+ };
829
+ if (block.hasFunctionDef) {
830
+ const nameSet = new Set(names);
831
+ (node as any).capturesLoopVar = blockCapturesNames(block, nameSet);
832
+ }
833
+ return node;
834
+ }
835
+ case "Function":
836
+ return {
837
+ type: "Function",
838
+ name: parseFunctionName(t.children![1], ctx),
839
+ body: parseFunctionBody(t.children![2], ctx),
840
+ ctx: context(t, ctx),
841
+ };
842
+ case "LocalFunction":
843
+ return {
844
+ type: "LocalFunction",
845
+ name: t.children![2].children![0].text!,
846
+ body: parseFunctionBody(t.children![3], ctx),
847
+ ctx: context(t, ctx),
848
+ };
849
+ case "FunctionCall":
850
+ return {
851
+ type: "FunctionCallStatement",
852
+ call: parseFunctionCall(t, ctx),
853
+ ctx: context(t, ctx),
854
+ };
855
+ case "Assign":
856
+ return {
857
+ type: "Assignment",
858
+ variables: t.children![0].children!
859
+ .filter((c) => c.type && c.type !== ",")
860
+ .map((lvalue) => parseLValue(lvalue, ctx)),
861
+ expressions: parseExpList(t.children![2], ctx),
862
+ ctx: context(t, ctx),
863
+ };
864
+ case "Local": {
865
+ const names = parseAttNames(t.children![1], ctx);
866
+
867
+ let closeCount = 0;
868
+ for (const n of names) {
869
+ if (n.attributes?.includes(LuaAttribute.Close) === true) {
870
+ closeCount++;
871
+ if (closeCount > 1) {
872
+ throw new Error("multiple <close> variables in local list");
873
+ }
874
+ }
875
+ }
876
+
877
+ return {
878
+ type: "Local",
879
+ names,
880
+ expressions: t.children![3] ? parseExpList(t.children![3], ctx) : [],
881
+ ctx: context(t, ctx),
882
+ };
883
+ }
884
+ case "ReturnStatement": {
885
+ const expressions = t.children![1]
886
+ ? parseExpList(t.children![1], ctx)
887
+ : [];
888
+ return { type: "Return", expressions, ctx: context(t, ctx) };
889
+ }
890
+ case "break":
891
+ return { type: "Break", ctx: context(t, ctx) };
892
+ default:
893
+ // Gracefully ignore unknown empty nodes
894
+ if (!t.children || t.children.length === 0) {
895
+ return {
896
+ type: "Semicolon",
897
+ ctx: context(t, ctx),
898
+ };
899
+ }
900
+ console.error(t);
901
+ throw new Error(
902
+ `Unknown statement type: ${
903
+ t.children![0] && t.children![0].text
904
+ ? t.children![0].text
905
+ : String(t.type)
906
+ }`,
907
+ );
908
+ }
909
+ }
910
+
911
+ function parseFunctionCall(
912
+ t: ParseTree,
913
+ ctx: ASTCtx,
914
+ ): LuaFunctionCallExpression {
915
+ if (t.children![1] && t.children![1].type === ":") {
916
+ return {
917
+ type: "FunctionCall",
918
+ prefix: parsePrefixExpression(t.children![0], ctx),
919
+ name: t.children![2].children![0].text!,
920
+ args: parseFunctionArgs(t.children!.slice(3), ctx),
921
+ ctx: context(t, ctx),
922
+ };
923
+ }
924
+ return {
925
+ type: "FunctionCall",
926
+ prefix: parsePrefixExpression(t.children![0], ctx),
927
+ args: parseFunctionArgs(t.children!.slice(1), ctx),
928
+ ctx: context(t, ctx),
929
+ };
930
+ }
931
+
932
+ function parseAttNames(t: ParseTree, ctx: ASTCtx): LuaAttName[] {
933
+ if (t.type !== "AttNameList") {
934
+ throw new Error(`Expected AttNameList, got ${t.type}`);
935
+ }
936
+ return t.children!
937
+ .filter((c) => c.type && c.type !== ",")
938
+ .map((att) => parseAttName(att, ctx));
939
+ }
940
+
941
+ function parseAttName(t: ParseTree, ctx: ASTCtx): LuaAttName {
942
+ if (t.type !== "AttName") {
943
+ throw new Error(`Expected AttName, got ${t.type}`);
944
+ }
945
+ const attribute = t.children![1].children![1]
946
+ ? t.children![1].children![1].children![0].text!
947
+ : undefined;
948
+ if (
949
+ attribute &&
950
+ attribute !== LuaAttribute.Const &&
951
+ attribute !== LuaAttribute.Close
952
+ ) {
953
+ throw new Error(`unknown attribute '${attribute}'`);
954
+ }
955
+ const attributes = attribute ? [attribute as LuaAttribute] : undefined;
956
+ return {
957
+ type: "AttName",
958
+ name: t.children![0].children![0].text!,
959
+ attribute,
960
+ attributes,
961
+ ctx: context(t, ctx),
962
+ };
963
+ }
964
+
965
+ function parseLValue(t: ParseTree, ctx: ASTCtx): LuaLValue {
966
+ switch (t.type) {
967
+ case "Name":
968
+ return {
969
+ type: "Variable",
970
+ name: t.children![0].text!,
971
+ ctx: context(t, ctx),
972
+ };
973
+ case "Property":
974
+ return {
975
+ type: "PropertyAccess",
976
+ object: parsePrefixExpression(t.children![0], ctx),
977
+ property: t.children![2].children![0].text!,
978
+ ctx: context(t, ctx),
979
+ };
980
+ case "MemberExpression":
981
+ return {
982
+ type: "TableAccess",
983
+ object: parsePrefixExpression(t.children![0], ctx),
984
+ key: parseExpression(t.children![2], ctx),
985
+ ctx: context(t, ctx),
986
+ };
987
+ default:
988
+ console.error(t);
989
+ throw new Error(`Unknown lvalue type: ${t.type}`);
990
+ }
991
+ }
992
+
993
+ function parseFunctionName(t: ParseTree, ctx: ASTCtx): LuaFunctionName {
994
+ if (t.type !== "FuncName") {
995
+ throw new Error(`Expected FunctionName, got ${t.type}`);
996
+ }
997
+ const propNames: string[] = [];
998
+ let colonName: string | undefined = undefined;
999
+ for (let i = 0; i < t.children!.length; i += 2) {
1000
+ const prop = t.children![i];
1001
+ propNames.push(prop.children![0].text!);
1002
+ if (t.children![i + 1] && t.children![i + 1].type === ":") {
1003
+ colonName = t.children![i + 2].children![0].text!;
1004
+ break;
1005
+ }
1006
+ }
1007
+ return {
1008
+ type: "FunctionName",
1009
+ propNames,
1010
+ colonName,
1011
+ ctx: context(t, ctx),
1012
+ };
1013
+ }
1014
+
1015
+ function parseNameList(t: ParseTree): string[] {
1016
+ if (t.type !== "NameList") {
1017
+ throw new Error(`Expected NameList, got ${t.type}`);
1018
+ }
1019
+ return t.children!
1020
+ .filter((c) => c.type === "Name")
1021
+ .map((c) => c.children![0].text!);
1022
+ }
1023
+
1024
+ function parseExpList(t: ParseTree, ctx: ASTCtx): LuaExpression[] {
1025
+ if (t.type !== "ExpList") {
1026
+ throw new Error(`Expected ExpList, got ${t.type}`);
1027
+ }
1028
+ return t.children!
1029
+ .filter((c) => c.type && c.type !== ",")
1030
+ .map((e) => parseExpression(e, ctx));
1031
+ }
1032
+
1033
+ const delimiterRegex = /^(\[=*\[)([\s\S]*)(\]=*\])$/;
1034
+
1035
+ // In case of quoted strings, remove the quotes and unescape the string
1036
+ // In case of a [[ type ]] literal string, remove the brackets
1037
+ function parseString(s: string): string {
1038
+ // Handle long strings with delimiters
1039
+ const delimiterMatch = s.match(delimiterRegex);
1040
+ if (delimiterMatch) {
1041
+ let text = delimiterMatch[2];
1042
+ // According to Lua semantics, whenever a [[ formatted string starts with a newline, that newline should be skipped
1043
+ if (text[0] === "\n") {
1044
+ text = text.slice(1);
1045
+ }
1046
+ return text;
1047
+ }
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
+ );
1086
+ }
1087
+
1088
+ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
1089
+ if (!t || !t.type) {
1090
+ throw new Error("Undefined expression node");
1091
+ }
1092
+ switch (t.type) {
1093
+ case "LiteralString": {
1094
+ const cleanString = parseString(t.children![0].text!);
1095
+ return {
1096
+ type: "String",
1097
+ value: cleanString,
1098
+ ctx: context(t, ctx),
1099
+ };
1100
+ }
1101
+ case "Number": {
1102
+ const text = t.children![0].text!.toLowerCase();
1103
+ return {
1104
+ type: "Number",
1105
+ // Use the integer parser fox 0x literals
1106
+ value: text.includes("x") ? parseInt(text) : parseFloat(text),
1107
+ numericType: /[\.eEpP]/.test(text) ? "float" : "int",
1108
+ ctx: context(t, ctx),
1109
+ };
1110
+ }
1111
+ case "BinaryExpression":
1112
+ return {
1113
+ type: "Binary",
1114
+ operator: t.children![1].children![0].text!,
1115
+ left: parseExpression(t.children![0], ctx),
1116
+ right: parseExpression(t.children![2], ctx),
1117
+ ctx: context(t, ctx),
1118
+ };
1119
+ case "UnaryExpression": {
1120
+ const op = t.children![0].children![0].text!;
1121
+ if (op === "+") {
1122
+ const err = new Error("unexpected symbol near '+'");
1123
+ (err as any).astCtx = context(t.children![0], ctx);
1124
+ throw err;
1125
+ }
1126
+ return {
1127
+ type: "Unary",
1128
+ operator: op,
1129
+ argument: parseExpression(t.children![1], ctx),
1130
+ ctx: context(t, ctx),
1131
+ };
1132
+ }
1133
+ case "Property":
1134
+ return {
1135
+ type: "PropertyAccess",
1136
+ object: parsePrefixExpression(t.children![0], ctx),
1137
+ property: t.children![2].children![0].text!,
1138
+ ctx: context(t, ctx),
1139
+ };
1140
+
1141
+ case "MemberExpression":
1142
+ return {
1143
+ type: "TableAccess",
1144
+ object: parsePrefixExpression(t.children![0], ctx),
1145
+ key: parseExpression(t.children![2], ctx),
1146
+ ctx: context(t, ctx),
1147
+ };
1148
+
1149
+ case "Parens":
1150
+ return parseExpression(t.children![1], ctx);
1151
+ case "FunctionCall": {
1152
+ return parseFunctionCall(t, ctx);
1153
+ }
1154
+ case "FunctionDef": {
1155
+ const body = parseFunctionBody(t.children![1], ctx);
1156
+ return {
1157
+ type: "FunctionDefinition",
1158
+ body,
1159
+ ctx: context(t, ctx),
1160
+ };
1161
+ }
1162
+ case "Name":
1163
+ return {
1164
+ type: "Variable",
1165
+ name: t.children![0].text!,
1166
+ ctx: context(t, ctx),
1167
+ };
1168
+ case "Ellipsis":
1169
+ return { type: "Variable", name: "...", ctx: context(t, ctx) };
1170
+ case "true":
1171
+ return { type: "Boolean", value: true, ctx: context(t, ctx) };
1172
+ case "false":
1173
+ return { type: "Boolean", value: false, ctx: context(t, ctx) };
1174
+ case "TableConstructor":
1175
+ return {
1176
+ type: "TableConstructor",
1177
+ fields: t.children!
1178
+ .slice(1, -1)
1179
+ .filter((c) =>
1180
+ ["FieldExp", "FieldProp", "FieldDynamic"].includes(c.type!)
1181
+ )
1182
+ .map((tf) => parseTableField(tf, ctx)),
1183
+ ctx: context(t, ctx),
1184
+ };
1185
+ case "nil":
1186
+ return { type: "Nil", ctx: context(t, ctx) };
1187
+ case "Query":
1188
+ return {
1189
+ type: "Query",
1190
+ clauses: t.children!.slice(2, -1).map((c) => parseQueryClause(c, ctx)),
1191
+ ctx: context(t, ctx),
1192
+ };
1193
+ default:
1194
+ console.error(t);
1195
+ throw new Error(`Unknown expression type: ${t.type}`);
1196
+ }
1197
+ }
1198
+
1199
+ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause {
1200
+ if (t.type !== "QueryClause") {
1201
+ throw new Error(`Expected QueryClause, got ${t.type}`);
1202
+ }
1203
+ t = t.children![0];
1204
+ switch (t.type) {
1205
+ 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
+ };
1214
+ }
1215
+ return {
1216
+ type: "From",
1217
+ expression: parseExpression(t.children![1], ctx),
1218
+ ctx: context(t, ctx),
1219
+ };
1220
+ }
1221
+ case "WhereClause":
1222
+ return {
1223
+ type: "Where",
1224
+ expression: parseExpression(t.children![1], ctx),
1225
+ ctx: context(t, ctx),
1226
+ };
1227
+ case "LimitClause": {
1228
+ const limit = parseExpression(t.children![1], ctx);
1229
+ const offset = t.children![2]
1230
+ ? parseExpression(t.children![3], ctx)
1231
+ : undefined;
1232
+ return {
1233
+ type: "Limit",
1234
+ limit,
1235
+ offset,
1236
+ ctx: context(t, ctx),
1237
+ };
1238
+ }
1239
+ case "OrderByClause": {
1240
+ const orderBy: LuaOrderBy[] = [];
1241
+ for (const child of t.children!) {
1242
+ 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
+ });
1249
+ }
1250
+ }
1251
+ return {
1252
+ type: "OrderBy",
1253
+ orderBy,
1254
+ ctx: context(t, ctx),
1255
+ };
1256
+ }
1257
+ case "SelectClause": {
1258
+ return {
1259
+ type: "Select",
1260
+ expression: parseExpression(t.children![1], ctx),
1261
+ ctx: context(t, ctx),
1262
+ };
1263
+ }
1264
+ default:
1265
+ console.error(t);
1266
+ throw new Error(`Unknown query clause type: ${t.type}`);
1267
+ }
1268
+ }
1269
+
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));
1274
+ }
1275
+
1276
+ function parseFunctionBody(t: ParseTree, ctx: ASTCtx): LuaFunctionBody {
1277
+ if (t.type !== "FuncBody") {
1278
+ throw new Error(`Expected FunctionBody, got ${t.type}`);
1279
+ }
1280
+ return {
1281
+ type: "FunctionBody",
1282
+ parameters: t.children![1].children!
1283
+ .filter((c) => c.type && ["Name", "Ellipsis"].includes(c.type))
1284
+ .map((c) => c.children![0].text!),
1285
+ block: parseBlock(t.children![3], ctx),
1286
+ ctx: context(t, ctx),
1287
+ };
1288
+ }
1289
+
1290
+ function parsePrefixExpression(t: ParseTree, ctx: ASTCtx): LuaPrefixExpression {
1291
+ if (!t || !t.type) {
1292
+ throw new Error("Undefined prefix expression node");
1293
+ }
1294
+ switch (t.type) {
1295
+ case "Name":
1296
+ return {
1297
+ type: "Variable",
1298
+ name: t.children![0].text!,
1299
+ ctx: context(t, ctx),
1300
+ };
1301
+ case "Property":
1302
+ return {
1303
+ type: "PropertyAccess",
1304
+ object: parsePrefixExpression(t.children![0], ctx),
1305
+ property: t.children![2].children![0].text!,
1306
+ ctx: context(t, ctx),
1307
+ };
1308
+ case "MemberExpression":
1309
+ return {
1310
+ type: "TableAccess",
1311
+ object: parsePrefixExpression(t.children![0], ctx),
1312
+ key: parseExpression(t.children![2], ctx),
1313
+ ctx: context(t, ctx),
1314
+ };
1315
+ case "Parens":
1316
+ return {
1317
+ type: "Parenthesized",
1318
+ expression: parseExpression(t.children![1], ctx),
1319
+ ctx: context(t, ctx),
1320
+ };
1321
+ case "FunctionCall": {
1322
+ return parseFunctionCall(t, ctx);
1323
+ }
1324
+ default:
1325
+ console.error(t);
1326
+ throw new Error(`Unknown prefix expression type: ${t.type}`);
1327
+ }
1328
+ }
1329
+
1330
+ function parseTableField(t: ParseTree, ctx: ASTCtx): LuaTableField {
1331
+ switch (t.type) {
1332
+ case "FieldExp":
1333
+ return {
1334
+ type: "ExpressionField",
1335
+ value: parseExpression(t.children![0], ctx),
1336
+ ctx: context(t, ctx),
1337
+ };
1338
+ case "FieldProp":
1339
+ return {
1340
+ type: "PropField",
1341
+ key: t.children![0].children![0].text!,
1342
+ value: parseExpression(t.children![2], ctx),
1343
+ ctx: context(t, ctx),
1344
+ };
1345
+ case "FieldDynamic":
1346
+ return {
1347
+ type: "DynamicField",
1348
+ key: parseExpression(t.children![1], ctx),
1349
+ value: parseExpression(t.children![4], ctx),
1350
+ ctx: context(t, ctx),
1351
+ };
1352
+ default:
1353
+ console.error(t);
1354
+ throw new Error(`Unknown table field type: ${t.type}`);
1355
+ }
1356
+ }
1357
+
1358
+ export function stripLuaComments(s: string): string {
1359
+ let result = "";
1360
+ let i = 0;
1361
+
1362
+ while (i < s.length) {
1363
+ // Check for long string
1364
+ if (s[i] === "[") {
1365
+ let j = i + 1;
1366
+ let equalsCount = 0;
1367
+ while (s[j] === "=") {
1368
+ equalsCount++;
1369
+ j++;
1370
+ }
1371
+ if (s[j] === "[") {
1372
+ // Found long string start
1373
+ const openBracket = s.substring(i, j + 1);
1374
+ const closeBracket = "]" + "=".repeat(equalsCount) + "]";
1375
+ result += openBracket;
1376
+ i = j + 1;
1377
+
1378
+ // Find matching closing bracket
1379
+ const content = s.substring(i);
1380
+ const closeIndex = content.indexOf(closeBracket);
1381
+ if (closeIndex !== -1) {
1382
+ // Copy string content verbatim, including any comment-like sequences
1383
+ result += content.substring(0, closeIndex) + closeBracket;
1384
+ i += closeIndex + closeBracket.length;
1385
+ continue;
1386
+ }
1387
+ }
1388
+ }
1389
+
1390
+ // Check for single quoted string
1391
+ if (s[i] === '"' || s[i] === "'") {
1392
+ const quote = s[i];
1393
+ result += quote;
1394
+ i++;
1395
+ while (i < s.length && s[i] !== quote) {
1396
+ if (s[i] === "\\") {
1397
+ result += s[i] + s[i + 1];
1398
+ i += 2;
1399
+ } else {
1400
+ result += s[i];
1401
+ i++;
1402
+ }
1403
+ }
1404
+ if (i < s.length) {
1405
+ result += s[i]; // closing quote
1406
+ i++;
1407
+ }
1408
+ continue;
1409
+ }
1410
+
1411
+ // Check for comments
1412
+ if (s[i] === "-" && s[i + 1] === "-") {
1413
+ // Replace the -- with spaces
1414
+ result += " ";
1415
+ i += 2;
1416
+
1417
+ // Check for long comment
1418
+ if (s[i] === "[") {
1419
+ let j = i + 1;
1420
+ let equalsCount = 0;
1421
+ while (s[j] === "=") {
1422
+ equalsCount++;
1423
+ j++;
1424
+ }
1425
+ if (s[j] === "[") {
1426
+ // Found long comment start
1427
+ const closeBracket = "]" + "=".repeat(equalsCount) + "]";
1428
+ // Replace opening bracket with spaces
1429
+ result += " ".repeat(j - i + 1);
1430
+ i = j + 1;
1431
+
1432
+ // Find matching closing bracket
1433
+ const content = s.substring(i);
1434
+ const closeIndex = content.indexOf(closeBracket);
1435
+ if (closeIndex !== -1) {
1436
+ // Replace comment content and closing bracket with spaces
1437
+ result += " ".repeat(closeIndex) + " ".repeat(closeBracket.length);
1438
+ i += closeIndex + closeBracket.length;
1439
+ continue;
1440
+ }
1441
+ }
1442
+ }
1443
+
1444
+ // Single line comment - replace rest of line with spaces
1445
+ while (i < s.length && s[i] !== "\n") {
1446
+ result += " ";
1447
+ i++;
1448
+ }
1449
+ continue;
1450
+ }
1451
+
1452
+ result += s[i];
1453
+ i++;
1454
+ }
1455
+
1456
+ return result;
1457
+ }
1458
+
1459
+ export function parse(s: string, ctx: ASTCtx = {}): LuaBlock {
1460
+ try {
1461
+ const t = parseToAST(stripLuaComments(s));
1462
+ // console.log("Clean tree", JSON.stringify(t, null, 2));
1463
+ const result = parseChunk(t, ctx);
1464
+ // console.log("Parsed AST", JSON.stringify(result, null, 2));
1465
+ getBlockGotoMeta(result);
1466
+ return result;
1467
+ } catch (e: any) {
1468
+ if (e && typeof e === "object" && "astCtx" in e) {
1469
+ throw new LuaRuntimeError(
1470
+ e.message,
1471
+ LuaStackFrame.lostFrame.withCtx(
1472
+ (e as any).astCtx as ASTCtx,
1473
+ ),
1474
+ );
1475
+ }
1476
+ throw e;
1477
+ }
1478
+ }
1479
+
1480
+ export function parseToAST(t: string): ParseTree {
1481
+ const tree = parser.parse(t);
1482
+
1483
+ const errNode = findFirstParseError(tree.topNode);
1484
+ if (errNode) {
1485
+ const err = new Error(luaUnexpectedSymbolMessage(t, errNode.from));
1486
+ (err as any).astCtx = { from: errNode.from, to: errNode.to };
1487
+ throw err;
1488
+ }
1489
+
1490
+ const n = lezerToParseTree(t, tree.topNode);
1491
+ return cleanTree(n, true);
1492
+ }
1493
+
1494
+ function findFirstParseError(node: SyntaxNode): SyntaxNode | null {
1495
+ if (node.type.isError) {
1496
+ return node;
1497
+ }
1498
+ for (let ch = node.firstChild; ch; ch = ch.nextSibling) {
1499
+ const hit = findFirstParseError(ch);
1500
+ if (hit) {
1501
+ return hit;
1502
+ }
1503
+ }
1504
+ return null;
1505
+ }
1506
+
1507
+ function luaUnexpectedSymbolMessage(src: string, from: number): string {
1508
+ let i = from;
1509
+ while (i < src.length && /\s/.test(src[i])) i++;
1510
+ const sym = i < src.length ? src[i] : "?";
1511
+ return `unexpected symbol near '${sym}'`;
1512
+ }
1513
+
1514
+ /**
1515
+ * Helper function to parse a Lua expression string
1516
+ */
1517
+ export function parseExpressionString(
1518
+ expr: string,
1519
+ ): LuaExpression {
1520
+ const parsedLua = parse(`_(${expr})`) as LuaBlock;
1521
+ return (parsedLua.statements[0] as LuaFunctionCallStatement).call.args[0];
1522
+ }