@silverbulletmd/silverbullet 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +18 -0
- package/README.md +98 -0
- package/client/asset_bundle/bundle.ts +95 -0
- package/client/data/datastore.ts +85 -0
- package/client/data/kv_primitives.ts +25 -0
- package/client/markdown_parser/constants.ts +13 -0
- package/client/plugos/event.ts +36 -0
- package/client/plugos/eventhook.ts +8 -0
- package/client/plugos/hooks/code_widget.ts +59 -0
- package/client/plugos/hooks/command.ts +104 -0
- package/client/plugos/hooks/document_editor.ts +77 -0
- package/client/plugos/hooks/event.ts +187 -0
- package/client/plugos/hooks/mq.ts +154 -0
- package/client/plugos/hooks/plug_namespace.ts +85 -0
- package/client/plugos/hooks/slash_command.ts +192 -0
- package/client/plugos/hooks/syscall.ts +66 -0
- package/client/plugos/manifest_cache.ts +67 -0
- package/client/plugos/plug.ts +99 -0
- package/client/plugos/plug_compile.ts +202 -0
- package/client/plugos/protocol.ts +40 -0
- package/client/plugos/proxy_fetch.ts +53 -0
- package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
- package/client/plugos/sandboxes/sandbox.ts +14 -0
- package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
- package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
- package/client/plugos/syscalls/asset.ts +35 -0
- package/client/plugos/syscalls/clientStore.ts +21 -0
- package/client/plugos/syscalls/client_code_widget.ts +12 -0
- package/client/plugos/syscalls/code_widget.ts +24 -0
- package/client/plugos/syscalls/config.ts +46 -0
- package/client/plugos/syscalls/datastore.ts +89 -0
- package/client/plugos/syscalls/editor.ts +673 -0
- package/client/plugos/syscalls/event.ts +36 -0
- package/client/plugos/syscalls/fetch.ts +128 -0
- package/client/plugos/syscalls/index.ts +102 -0
- package/client/plugos/syscalls/jsonschema.ts +69 -0
- package/client/plugos/syscalls/language.ts +23 -0
- package/client/plugos/syscalls/lua.ts +58 -0
- package/client/plugos/syscalls/markdown.ts +84 -0
- package/client/plugos/syscalls/mq.ts +52 -0
- package/client/plugos/syscalls/service_registry.ts +43 -0
- package/client/plugos/syscalls/shell.ts +39 -0
- package/client/plugos/syscalls/space.ts +139 -0
- package/client/plugos/syscalls/sync.ts +77 -0
- package/client/plugos/syscalls/system.ts +150 -0
- package/client/plugos/system.ts +201 -0
- package/client/plugos/types.ts +60 -0
- package/client/plugos/util.ts +14 -0
- package/client/plugos/worker_runtime.ts +195 -0
- package/client/space_lua/ast.ts +328 -0
- package/client/space_lua/ast_narrow.ts +81 -0
- package/client/space_lua/eval.ts +2478 -0
- package/client/space_lua/labels.ts +416 -0
- package/client/space_lua/numeric.ts +240 -0
- package/client/space_lua/parse.ts +1522 -0
- package/client/space_lua/query_collection.ts +232 -0
- package/client/space_lua/rp.ts +27 -0
- package/client/space_lua/runtime.ts +1702 -0
- package/client/space_lua/stdlib/crypto.ts +10 -0
- package/client/space_lua/stdlib/encoding.ts +19 -0
- package/client/space_lua/stdlib/format.ts +770 -0
- package/client/space_lua/stdlib/js.ts +73 -0
- package/client/space_lua/stdlib/load.ts +52 -0
- package/client/space_lua/stdlib/math.ts +193 -0
- package/client/space_lua/stdlib/net.ts +113 -0
- package/client/space_lua/stdlib/os.ts +368 -0
- package/client/space_lua/stdlib/space_lua.ts +153 -0
- package/client/space_lua/stdlib/string.ts +286 -0
- package/client/space_lua/stdlib/table.ts +401 -0
- package/client/space_lua/stdlib.ts +489 -0
- package/client/space_lua/tonumber.ts +501 -0
- package/client/space_lua/util.ts +96 -0
- package/dist/plug-compile.js +1513 -0
- package/package.json +120 -0
- package/plug-api/constants.ts +42 -0
- package/plug-api/lib/async.ts +162 -0
- package/plug-api/lib/crypto.ts +202 -0
- package/plug-api/lib/dates.ts +13 -0
- package/plug-api/lib/json.ts +136 -0
- package/plug-api/lib/limited_map.ts +72 -0
- package/plug-api/lib/memory_cache.ts +21 -0
- package/plug-api/lib/native_fetch.ts +6 -0
- package/plug-api/lib/ref.ts +275 -0
- package/plug-api/lib/resolve.ts +90 -0
- package/plug-api/lib/tags.ts +15 -0
- package/plug-api/lib/transclusion.ts +122 -0
- package/plug-api/lib/tree.ts +232 -0
- package/plug-api/lib/yaml.ts +284 -0
- package/plug-api/syscall.ts +15 -0
- package/plug-api/syscalls/asset.ts +36 -0
- package/plug-api/syscalls/client_store.ts +33 -0
- package/plug-api/syscalls/code_widget.ts +8 -0
- package/plug-api/syscalls/config.ts +58 -0
- package/plug-api/syscalls/datastore.ts +96 -0
- package/plug-api/syscalls/editor.ts +517 -0
- package/plug-api/syscalls/event.ts +47 -0
- package/plug-api/syscalls/index.ts +77 -0
- package/plug-api/syscalls/jsonschema.ts +25 -0
- package/plug-api/syscalls/language.ts +23 -0
- package/plug-api/syscalls/lua.ts +20 -0
- package/plug-api/syscalls/markdown.ts +38 -0
- package/plug-api/syscalls/mq.ts +79 -0
- package/plug-api/syscalls/shell.ts +14 -0
- package/plug-api/syscalls/space.ts +212 -0
- package/plug-api/syscalls/sync.ts +28 -0
- package/plug-api/syscalls/system.ts +102 -0
- package/plug-api/syscalls/yaml.ts +28 -0
- package/plug-api/syscalls.ts +21 -0
- package/plug-api/system_mock.ts +89 -0
- package/plug-api/types/client.ts +116 -0
- package/plug-api/types/config.ts +22 -0
- package/plug-api/types/datastore.ts +28 -0
- package/plug-api/types/event.ts +27 -0
- package/plug-api/types/index.ts +56 -0
- package/plug-api/types/manifest.ts +98 -0
- package/plug-api/types/namespace.ts +6 -0
- package/plugs/builtin_plugs.ts +14 -0
|
@@ -0,0 +1,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
|
+
}
|