cluaupp 0.1.2 → 0.1.5
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/CHANGELOG.md +29 -1
- package/README.md +10 -13
- package/docs/README.md +13 -10
- package/docs/architecture.md +30 -73
- package/docs/cli.md +5 -6
- package/docs/comparison.md +3 -3
- package/docs/config.md +6 -6
- package/docs/cpp-advanced.md +10 -6
- package/docs/cpp-organization.md +4 -2
- package/docs/cpp-safety.md +11 -7
- package/docs/cpp-types.md +1 -1
- package/docs/examples/_category_.json +5 -0
- package/docs/examples/combat.md +239 -0
- package/docs/examples/data-boot.md +98 -0
- package/docs/examples/hud.md +96 -0
- package/docs/examples/index.md +43 -0
- package/docs/examples/leaderstats.md +140 -0
- package/docs/examples/shop.md +122 -0
- package/docs/examples/sword.md +108 -0
- package/docs/getting-started.md +14 -7
- package/docs/intellisense.md +31 -0
- package/docs/intro.md +3 -3
- package/docs/libraries/_category_.json +5 -0
- package/docs/libraries/dataservice.md +104 -0
- package/docs/libraries/index.md +22 -0
- package/docs/libraries/janitor.md +65 -0
- package/docs/libraries/more.md +79 -0
- package/docs/libraries/net.md +35 -0
- package/docs/libraries/promise.md +27 -0
- package/docs/oop/_category_.json +5 -0
- package/docs/oop/file-tags.md +48 -0
- package/docs/oop/index.md +22 -0
- package/docs/oop/modules.md +88 -0
- package/docs/oop/services.md +76 -0
- package/docs/print-cout.md +91 -0
- package/docs/syntax.md +63 -8
- package/editors/vscode/extension.js +146 -0
- package/editors/vscode/package.json +25 -0
- package/include/cluaupp/libs/janitor.hpp +7 -3
- package/include/cluaupp/roblox.hpp +19 -0
- package/package.json +66 -65
- package/runtime/Janitor/init.luau +4 -34
- package/src/architecture.js +174 -66
- package/src/cli.js +88 -21
- package/src/client/init.client.cpp +5 -0
- package/src/compile.js +77 -7
- package/src/editor-install.js +218 -0
- package/src/emit.js +321 -9
- package/src/headers.js +234 -0
- package/src/intellisense.js +1368 -0
- package/src/layout.js +129 -0
- package/src/lex.js +17 -9
- package/src/libs.js +165 -18
- package/src/lsp.js +227 -0
- package/src/parse.js +187 -6
- package/src/preprocess.js +118 -3
- package/src/server/leaderstats.server.cpp +28 -0
- package/src/shared/config.cpp +5 -0
- package/src/shared/config.h +3 -0
- package/src/understand.js +66 -6
- package/templates/game/.clangd +11 -0
- package/templates/game/.vscode/c_cpp_properties.json +6 -2
- package/templates/game/.vscode/extensions.json +6 -0
- package/templates/game/.vscode/settings.json +16 -2
- package/templates/game/cluaupp.config.json +2 -2
- package/templates/game/compile_flags.txt +2 -0
- package/templates/game/src/client/init.client.cpp +1 -0
- package/templates/game/src/server/leaderstats.server.cpp +1 -0
- package/docs/libraries.md +0 -80
package/src/emit.js
CHANGED
|
@@ -4,7 +4,7 @@ const { isInstanceType, isMethod, isDatatype, luauType } = require("./api");
|
|
|
4
4
|
const { isLibraryType, isLibraryMethod, MODULE_COLON } = require("./libs");
|
|
5
5
|
|
|
6
6
|
function emit(ast, options = {}) {
|
|
7
|
-
const strict = options.strict
|
|
7
|
+
const strict = options.strict === true;
|
|
8
8
|
const lines = [];
|
|
9
9
|
if (!options.skipHeader) {
|
|
10
10
|
if (strict) {
|
|
@@ -15,6 +15,133 @@ function emit(ast, options = {}) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const indentOf = (n) => "\t".repeat(n);
|
|
18
|
+
let switchId = 0;
|
|
19
|
+
|
|
20
|
+
const classOwners = new Set();
|
|
21
|
+
const classFields = new Set();
|
|
22
|
+
const classMethods = new Set();
|
|
23
|
+
for (const decl of ast.body || []) {
|
|
24
|
+
if (decl.owner) {
|
|
25
|
+
classOwners.add(decl.owner);
|
|
26
|
+
}
|
|
27
|
+
if (decl.type === "decl" && decl.owner) {
|
|
28
|
+
classFields.add(decl.name);
|
|
29
|
+
}
|
|
30
|
+
if ((decl.type === "function" || decl.type === "proto") && decl.owner && decl.name) {
|
|
31
|
+
classMethods.add(decl.name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const ownedFns = (ast.body || []).filter((decl) => decl.type === "function");
|
|
35
|
+
const classModule = ownedFns.length > 0 && ownedFns.every((decl) => Boolean(decl.owner));
|
|
36
|
+
const nestedTypes = new Set();
|
|
37
|
+
for (const decl of ast.body || []) {
|
|
38
|
+
if (decl.type === "decl" && decl.owner && decl.valueType && classOwners.has(decl.valueType) && decl.valueType !== decl.owner) {
|
|
39
|
+
nestedTypes.add(decl.valueType);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const structRoots = [...classOwners].filter((name) => !nestedTypes.has(name));
|
|
43
|
+
const structModule = !classModule && ownedFns.length === 0 && structRoots.length > 0;
|
|
44
|
+
|
|
45
|
+
const localNames = new Set();
|
|
46
|
+
let selfOwner = null;
|
|
47
|
+
|
|
48
|
+
const isBareGlobal = (name) => {
|
|
49
|
+
return (
|
|
50
|
+
name === "print" ||
|
|
51
|
+
name === "warn" ||
|
|
52
|
+
name === "error" ||
|
|
53
|
+
name === "game" ||
|
|
54
|
+
name === "workspace" ||
|
|
55
|
+
name === "script" ||
|
|
56
|
+
name === "cout" ||
|
|
57
|
+
name === "cerr" ||
|
|
58
|
+
name === "endl" ||
|
|
59
|
+
isLibraryType(name) ||
|
|
60
|
+
isInstanceType(name) ||
|
|
61
|
+
isDatatype(name)
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const emitSelfIdent = (name) => {
|
|
66
|
+
if (name === "this") {
|
|
67
|
+
return "self";
|
|
68
|
+
}
|
|
69
|
+
if (!selfOwner || localNames.has(name) || isBareGlobal(name)) {
|
|
70
|
+
return name;
|
|
71
|
+
}
|
|
72
|
+
if (classFields.has(name)) {
|
|
73
|
+
return `self.${name}`;
|
|
74
|
+
}
|
|
75
|
+
return name;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const emitMethodArg = (arg) => {
|
|
79
|
+
if (arg && arg.type === "ident" && selfOwner && classMethods.has(arg.name) && !localNames.has(arg.name)) {
|
|
80
|
+
return `function(...) self:${arg.name}(...) end`;
|
|
81
|
+
}
|
|
82
|
+
return emitExpr(arg);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const flattenShift = (node) => {
|
|
86
|
+
const parts = [];
|
|
87
|
+
let current = node;
|
|
88
|
+
while (current && current.type === "binary" && current.op === "<<") {
|
|
89
|
+
parts.unshift(current.right);
|
|
90
|
+
current = current.left;
|
|
91
|
+
}
|
|
92
|
+
return { stream: current, args: parts };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const isEndl = (node) => node && node.type === "ident" && node.name === "endl";
|
|
96
|
+
|
|
97
|
+
const streamPrint = (stream) => {
|
|
98
|
+
if (!stream) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
if (stream.type === "ident") {
|
|
102
|
+
if (stream.name === "cout") {
|
|
103
|
+
return "print";
|
|
104
|
+
}
|
|
105
|
+
if (stream.name === "cerr") {
|
|
106
|
+
return "warn";
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (stream.type === "member" && stream.object && stream.object.type === "ident" && stream.object.name === "cout") {
|
|
111
|
+
const mapped = { print: "print", warn: "warn", error: "error", ping: "print", endl: "print" };
|
|
112
|
+
return mapped[stream.name] || "print";
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const emitCout = (node) => {
|
|
118
|
+
const { stream, args } = flattenShift(node);
|
|
119
|
+
const fn = streamPrint(stream);
|
|
120
|
+
if (!fn) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const linesOut = [];
|
|
124
|
+
let current = [];
|
|
125
|
+
const flush = () => {
|
|
126
|
+
if (current.length === 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
linesOut.push(`${fn}(${current.map(emitExpr).join(", ")})`);
|
|
130
|
+
current = [];
|
|
131
|
+
};
|
|
132
|
+
for (const arg of args) {
|
|
133
|
+
if (isEndl(arg)) {
|
|
134
|
+
flush();
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
current.push(arg);
|
|
138
|
+
}
|
|
139
|
+
flush();
|
|
140
|
+
if (linesOut.length === 0) {
|
|
141
|
+
linesOut.push(`${fn}()`);
|
|
142
|
+
}
|
|
143
|
+
return linesOut;
|
|
144
|
+
};
|
|
18
145
|
|
|
19
146
|
const emitExpr = (node) => {
|
|
20
147
|
if (!node) {
|
|
@@ -30,7 +157,7 @@ function emit(ast, options = {}) {
|
|
|
30
157
|
case "string":
|
|
31
158
|
return `"${node.value}"`;
|
|
32
159
|
case "ident":
|
|
33
|
-
return node.name;
|
|
160
|
+
return emitSelfIdent(node.name);
|
|
34
161
|
case "initlist": {
|
|
35
162
|
const entries = (node.fields || []).map((field) => `${field.name} = ${emitExpr(field.value)}`);
|
|
36
163
|
if (entries.length === 0) {
|
|
@@ -57,19 +184,39 @@ function emit(ast, options = {}) {
|
|
|
57
184
|
case "member":
|
|
58
185
|
return `${emitExpr(node.object)}.${node.name}`;
|
|
59
186
|
case "call": {
|
|
60
|
-
const args = node.args.map(
|
|
187
|
+
const args = node.args.map(emitMethodArg).join(", ");
|
|
61
188
|
if (!node.object) {
|
|
62
189
|
if (isDatatype(node.name)) {
|
|
63
190
|
return `${node.name}.new(${args})`;
|
|
64
191
|
}
|
|
192
|
+
if (selfOwner && classMethods.has(node.name) && !localNames.has(node.name)) {
|
|
193
|
+
return `self:${node.name}(${args})`;
|
|
194
|
+
}
|
|
65
195
|
return `${node.name}(${args})`;
|
|
66
196
|
}
|
|
67
197
|
const obj = emitExpr(node.object);
|
|
198
|
+
if (node.access === "::" && node.object.type === "ident" && node.object.name === "cout") {
|
|
199
|
+
if (node.name === "endl") {
|
|
200
|
+
return "print()";
|
|
201
|
+
}
|
|
202
|
+
const mapped = { print: "print", warn: "warn", error: "error", ping: "print" };
|
|
203
|
+
return `${mapped[node.name] || "print"}(${args})`;
|
|
204
|
+
}
|
|
68
205
|
if (node.access === "::") {
|
|
69
206
|
if (MODULE_COLON.has(node.name)) {
|
|
70
207
|
return `${obj}:${node.name}(${args})`;
|
|
71
208
|
}
|
|
72
|
-
|
|
209
|
+
const staticNs =
|
|
210
|
+
node.object.type === "ident" &&
|
|
211
|
+
(isLibraryType(node.object.name) ||
|
|
212
|
+
isDatatype(node.object.name) ||
|
|
213
|
+
node.object.name === "DataService" ||
|
|
214
|
+
node.object.name === "FormatNumber" ||
|
|
215
|
+
node.object.name === "Enum");
|
|
216
|
+
if (staticNs) {
|
|
217
|
+
return `${obj}.${node.name}(${args})`;
|
|
218
|
+
}
|
|
219
|
+
return `${obj}:${node.name}(${args})`;
|
|
73
220
|
}
|
|
74
221
|
if (isDatatype(obj) || (node.object.type === "ident" && isDatatype(node.object.name))) {
|
|
75
222
|
return `${obj}.${node.name}(${args})`;
|
|
@@ -80,6 +227,12 @@ function emit(ast, options = {}) {
|
|
|
80
227
|
case "assign":
|
|
81
228
|
return `${emitExpr(node.left)} = ${emitExpr(node.right)}`;
|
|
82
229
|
case "binary": {
|
|
230
|
+
if (node.op === "<<") {
|
|
231
|
+
const printed = emitCout(node);
|
|
232
|
+
if (printed) {
|
|
233
|
+
return printed.join("; ");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
83
236
|
const ops = { "!=": "~=", "&&": "and", "||": "or" };
|
|
84
237
|
const op = ops[node.op] || node.op;
|
|
85
238
|
return `${emitExpr(node.left)} ${op} ${emitExpr(node.right)}`;
|
|
@@ -110,9 +263,24 @@ function emit(ast, options = {}) {
|
|
|
110
263
|
const emitNewDecl = (node, indent) => {
|
|
111
264
|
const prefix = indentOf(indent);
|
|
112
265
|
const typeAnn = inferredType(node);
|
|
266
|
+
const created = node.value;
|
|
267
|
+
if (node.owner && indent === 0 && classModule) {
|
|
268
|
+
const value = node.value ? emitExpr(node.value) : "nil";
|
|
269
|
+
if (created && created.type === "new" && isInstanceType(created.className) && !isLibraryType(created.className)) {
|
|
270
|
+
const linesOut = [`${prefix}${node.owner}.${node.name} = Instance.new("${created.className}")`];
|
|
271
|
+
if (created.args[0]) {
|
|
272
|
+
linesOut.push(`${prefix}${node.owner}.${node.name}.Parent = ${emitExpr(created.args[0])}`);
|
|
273
|
+
}
|
|
274
|
+
return linesOut;
|
|
275
|
+
}
|
|
276
|
+
if (created && created.type === "new" && (isDatatype(created.className) || isLibraryType(created.className))) {
|
|
277
|
+
const args = created.args.map(emitExpr).join(", ");
|
|
278
|
+
return [`${prefix}${node.owner}.${node.name} = ${created.className}.new(${args})`];
|
|
279
|
+
}
|
|
280
|
+
return [`${prefix}${node.owner}.${node.name} = ${value}`];
|
|
281
|
+
}
|
|
113
282
|
const kind = node.isConst ? "const" : "local";
|
|
114
283
|
const typed = typeAnn ? `: ${typeAnn}` : "";
|
|
115
|
-
const created = node.value;
|
|
116
284
|
if (created && created.type === "new" && isInstanceType(created.className) && !isLibraryType(created.className)) {
|
|
117
285
|
const linesOut = [`${prefix}${kind} ${node.name}${typed} = Instance.new("${created.className}")`];
|
|
118
286
|
if (created.args[0]) {
|
|
@@ -132,9 +300,35 @@ function emit(ast, options = {}) {
|
|
|
132
300
|
const prefix = indentOf(indent);
|
|
133
301
|
switch (node.type) {
|
|
134
302
|
case "decl":
|
|
303
|
+
if (node.name) {
|
|
304
|
+
localNames.add(node.name);
|
|
305
|
+
}
|
|
135
306
|
return emitNewDecl(node, indent);
|
|
136
|
-
case "expr":
|
|
137
|
-
|
|
307
|
+
case "expr": {
|
|
308
|
+
const expr = node.expr;
|
|
309
|
+
if (expr && expr.type === "binary" && expr.op === "<<") {
|
|
310
|
+
const printed = emitCout(expr);
|
|
311
|
+
if (printed) {
|
|
312
|
+
return printed.map((line) => `${prefix}${line}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (
|
|
316
|
+
expr &&
|
|
317
|
+
expr.type === "assign" &&
|
|
318
|
+
expr.right &&
|
|
319
|
+
expr.right.type === "new" &&
|
|
320
|
+
isInstanceType(expr.right.className) &&
|
|
321
|
+
!isLibraryType(expr.right.className)
|
|
322
|
+
) {
|
|
323
|
+
const left = emitExpr(expr.left);
|
|
324
|
+
const linesOut = [`${prefix}${left} = Instance.new("${expr.right.className}")`];
|
|
325
|
+
if (expr.right.args[0]) {
|
|
326
|
+
linesOut.push(`${prefix}${left}.Parent = ${emitExpr(expr.right.args[0])}`);
|
|
327
|
+
}
|
|
328
|
+
return linesOut;
|
|
329
|
+
}
|
|
330
|
+
return [`${prefix}${emitExpr(expr)}`];
|
|
331
|
+
}
|
|
138
332
|
case "return":
|
|
139
333
|
return node.value ? [`${prefix}return ${emitExpr(node.value)}`] : [`${prefix}return`];
|
|
140
334
|
case "if": {
|
|
@@ -167,6 +361,52 @@ function emit(ast, options = {}) {
|
|
|
167
361
|
out.push(`${prefix}end`);
|
|
168
362
|
return out;
|
|
169
363
|
}
|
|
364
|
+
case "break":
|
|
365
|
+
return [`${prefix}break`];
|
|
366
|
+
case "switch": {
|
|
367
|
+
switchId += 1;
|
|
368
|
+
const id = `__switch${switchId}`;
|
|
369
|
+
const disc = node.discriminant;
|
|
370
|
+
const simple =
|
|
371
|
+
disc &&
|
|
372
|
+
(disc.type === "ident" || disc.type === "number" || disc.type === "string" || disc.type === "bool");
|
|
373
|
+
const subject = simple ? emitExpr(disc) : id;
|
|
374
|
+
const out = [`${prefix}repeat`];
|
|
375
|
+
const inner = indent + 1;
|
|
376
|
+
const innerP = indentOf(inner);
|
|
377
|
+
if (!simple) {
|
|
378
|
+
out.push(`${innerP}local ${id} = ${emitExpr(disc)}`);
|
|
379
|
+
}
|
|
380
|
+
const regular = (node.cases || []).filter((item) => !item.isDefault);
|
|
381
|
+
const fallback = (node.cases || []).find((item) => item.isDefault);
|
|
382
|
+
const testOf = (item) =>
|
|
383
|
+
(item.values || []).map((value) => `${subject} == ${emitExpr(value)}`).join(" or ");
|
|
384
|
+
for (let index = 0; index < regular.length; index += 1) {
|
|
385
|
+
const item = regular[index];
|
|
386
|
+
const keyword = index === 0 ? "if" : "elseif";
|
|
387
|
+
out.push(`${innerP}${keyword} ${testOf(item)} then`);
|
|
388
|
+
for (const stmt of item.body || []) {
|
|
389
|
+
out.push(...emitStmt(stmt, inner + 1));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (fallback) {
|
|
393
|
+
if (regular.length === 0) {
|
|
394
|
+
for (const stmt of fallback.body || []) {
|
|
395
|
+
out.push(...emitStmt(stmt, inner));
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
out.push(`${innerP}else`);
|
|
399
|
+
for (const stmt of fallback.body || []) {
|
|
400
|
+
out.push(...emitStmt(stmt, inner + 1));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (regular.length > 0) {
|
|
405
|
+
out.push(`${innerP}end`);
|
|
406
|
+
}
|
|
407
|
+
out.push(`${prefix}until true`);
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
170
410
|
default:
|
|
171
411
|
return [];
|
|
172
412
|
}
|
|
@@ -191,25 +431,97 @@ function emit(ast, options = {}) {
|
|
|
191
431
|
return `: ${typeAnn}`;
|
|
192
432
|
};
|
|
193
433
|
|
|
434
|
+
const emitFieldLiteral = (decl) => {
|
|
435
|
+
if (decl.value && decl.value.type === "initlist") {
|
|
436
|
+
const inner = (decl.value.fields || [])
|
|
437
|
+
.map((field) => `${field.name} = ${emitExpr(field.value)}`)
|
|
438
|
+
.join(", ");
|
|
439
|
+
return `{ ${inner} }`;
|
|
440
|
+
}
|
|
441
|
+
if (decl.valueType && nestedTypes.has(decl.valueType)) {
|
|
442
|
+
const nested = (ast.body || []).filter((item) => item.type === "decl" && item.owner === decl.valueType);
|
|
443
|
+
const inner = nested.map((item) => `${item.name} = ${emitFieldLiteral(item)}`).join(", ");
|
|
444
|
+
return `{ ${inner} }`;
|
|
445
|
+
}
|
|
446
|
+
return decl.value ? emitExpr(decl.value) : "nil";
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const emitStructConstructor = (root) => {
|
|
450
|
+
const fields = (ast.body || []).filter((decl) => decl.type === "decl" && decl.owner === root);
|
|
451
|
+
const inner = fields.map((field) => `\t\t${field.name} = ${emitFieldLiteral(field)},`).join("\n");
|
|
452
|
+
lines.push(`const function ${root}()`);
|
|
453
|
+
lines.push(" return {");
|
|
454
|
+
if (inner) {
|
|
455
|
+
lines.push(inner);
|
|
456
|
+
}
|
|
457
|
+
lines.push(" }");
|
|
458
|
+
lines.push("end");
|
|
459
|
+
lines.push("");
|
|
460
|
+
lines.push(`return ${root}`);
|
|
461
|
+
lines.push("");
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
if (classModule) {
|
|
465
|
+
for (const owner of classOwners) {
|
|
466
|
+
lines.push(`local ${owner} = {}`);
|
|
467
|
+
lines.push("");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (structModule) {
|
|
472
|
+
for (const root of structRoots) {
|
|
473
|
+
emitStructConstructor(root);
|
|
474
|
+
}
|
|
475
|
+
return lines.join("\n");
|
|
476
|
+
}
|
|
477
|
+
|
|
194
478
|
for (const decl of ast.body) {
|
|
195
479
|
if (decl.type === "decl") {
|
|
196
480
|
lines.push(...emitNewDecl(decl, 0));
|
|
197
481
|
lines.push("");
|
|
198
482
|
continue;
|
|
199
483
|
}
|
|
484
|
+
if (decl.type === "expr") {
|
|
485
|
+
lines.push(emitExpr(decl.expr));
|
|
486
|
+
lines.push("");
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
200
489
|
if (decl.type !== "function") {
|
|
201
490
|
continue;
|
|
202
491
|
}
|
|
203
|
-
|
|
492
|
+
selfOwner = decl.owner || null;
|
|
493
|
+
localNames.clear();
|
|
494
|
+
for (const param of decl.params || []) {
|
|
495
|
+
const paramName = typeof param === "string" ? param : param.name;
|
|
496
|
+
if (paramName) {
|
|
497
|
+
localNames.add(paramName);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (decl.owner) {
|
|
501
|
+
lines.push(`function ${decl.owner}:${decl.name}(${paramList(decl)})${returnAnn(decl)}`);
|
|
502
|
+
} else {
|
|
503
|
+
lines.push(`const function ${decl.name}(${paramList(decl)})${returnAnn(decl)}`);
|
|
504
|
+
}
|
|
204
505
|
for (const stmt of decl.body) {
|
|
205
506
|
lines.push(...emitStmt(stmt, 1));
|
|
206
507
|
}
|
|
207
508
|
lines.push("end");
|
|
208
509
|
lines.push("");
|
|
510
|
+
selfOwner = null;
|
|
511
|
+
localNames.clear();
|
|
209
512
|
}
|
|
210
513
|
|
|
211
514
|
const hasInit = ast.body.some((decl) => decl.type === "function" && decl.name === "init");
|
|
212
|
-
if (
|
|
515
|
+
if (classModule) {
|
|
516
|
+
for (const owner of classOwners) {
|
|
517
|
+
if (hasInit) {
|
|
518
|
+
lines.push(`${owner}.Start = ${owner}.init`);
|
|
519
|
+
lines.push(`${owner}.Init = ${owner}.init`);
|
|
520
|
+
}
|
|
521
|
+
lines.push(`return ${owner}`);
|
|
522
|
+
lines.push("");
|
|
523
|
+
}
|
|
524
|
+
} else if (hasInit && !options.skipInit) {
|
|
213
525
|
lines.push("init()");
|
|
214
526
|
lines.push("");
|
|
215
527
|
}
|
package/src/headers.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { luauType } = require("./api");
|
|
5
|
+
|
|
6
|
+
function fileStem(fileName) {
|
|
7
|
+
return path.basename(String(fileName || "module")).replace(/\.(h|hpp|hh|cpp|cc|cxx|c)$/i, "");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function emitValue(node) {
|
|
11
|
+
if (!node) {
|
|
12
|
+
return "nil";
|
|
13
|
+
}
|
|
14
|
+
if (node.type === "number") {
|
|
15
|
+
return node.value;
|
|
16
|
+
}
|
|
17
|
+
if (node.type === "string") {
|
|
18
|
+
return `"${node.value}"`;
|
|
19
|
+
}
|
|
20
|
+
if (node.type === "bool") {
|
|
21
|
+
return node.value ? "true" : "false";
|
|
22
|
+
}
|
|
23
|
+
if (node.type === "null") {
|
|
24
|
+
return "nil";
|
|
25
|
+
}
|
|
26
|
+
if (node.type === "initlist") {
|
|
27
|
+
const inner = (node.fields || []).map((field) => `${field.name} = ${emitValue(field.value)}`).join(", ");
|
|
28
|
+
return `{ ${inner} }`;
|
|
29
|
+
}
|
|
30
|
+
return "nil";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectHeaderShape(ast, fileName) {
|
|
34
|
+
const owners = new Map();
|
|
35
|
+
const ensure = (name) => {
|
|
36
|
+
if (!owners.has(name)) {
|
|
37
|
+
owners.set(name, { fields: [], methods: [] });
|
|
38
|
+
}
|
|
39
|
+
return owners.get(name);
|
|
40
|
+
};
|
|
41
|
+
const freeProtos = [];
|
|
42
|
+
const consts = [];
|
|
43
|
+
for (const decl of ast.body || []) {
|
|
44
|
+
if (decl.type === "decl" && decl.owner) {
|
|
45
|
+
ensure(decl.owner).fields.push(decl);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (decl.type === "decl") {
|
|
49
|
+
consts.push(decl);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if ((decl.type === "proto" || decl.type === "function") && decl.owner) {
|
|
53
|
+
ensure(decl.owner).methods.push(decl);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (decl.type === "proto") {
|
|
57
|
+
freeProtos.push(decl);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const nested = new Set();
|
|
61
|
+
for (const [ownerName, spec] of owners) {
|
|
62
|
+
for (const field of spec.fields) {
|
|
63
|
+
if (field.valueType && owners.has(field.valueType) && field.valueType !== ownerName) {
|
|
64
|
+
nested.add(field.valueType);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const roots = [...owners.keys()].filter((name) => !nested.has(name));
|
|
69
|
+
const withMethods = roots.filter((name) => ensure(name).methods.length > 0);
|
|
70
|
+
const stem = fileStem(fileName);
|
|
71
|
+
let primary = withMethods[0] || (freeProtos.length ? stem : roots[0] || stem);
|
|
72
|
+
return { owners, nested, roots, freeProtos, consts, primary, stem };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function primaryHeaderName(ast, fileName) {
|
|
76
|
+
return collectHeaderShape(ast, fileName).primary;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function methodType(fn, owner) {
|
|
80
|
+
const params = [];
|
|
81
|
+
if (owner) {
|
|
82
|
+
params.push(`self: ${owner}`);
|
|
83
|
+
}
|
|
84
|
+
for (const param of fn.params || []) {
|
|
85
|
+
if (typeof param === "string") {
|
|
86
|
+
params.push(param);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const typeAnn = luauType(param.valueType);
|
|
90
|
+
params.push(typeAnn ? `${param.name}: ${typeAnn}` : param.name);
|
|
91
|
+
}
|
|
92
|
+
const ret = luauType(fn.returnType);
|
|
93
|
+
const retAnn = !ret || ret === "()" ? "()" : ret;
|
|
94
|
+
return `(${params.join(", ")}) -> ${retAnn}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function fieldType(field, owners) {
|
|
98
|
+
if (field.valueType && owners.has(field.valueType)) {
|
|
99
|
+
return field.valueType;
|
|
100
|
+
}
|
|
101
|
+
return luauType(field.valueType) || "any";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function emitExportType(lines, name, spec, owners, extraMethods, extraFields) {
|
|
105
|
+
lines.push(`export type ${name} = {`);
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const field of [...((spec && spec.fields) || []), ...(extraFields || [])]) {
|
|
108
|
+
if (!field.name || seen.has(field.name)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
seen.add(field.name);
|
|
112
|
+
lines.push(` ${field.name}: ${fieldType(field, owners)},`);
|
|
113
|
+
}
|
|
114
|
+
for (const method of (spec && spec.methods) || []) {
|
|
115
|
+
if (!method.name || seen.has(method.name)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
seen.add(method.name);
|
|
119
|
+
lines.push(` ${method.name}: ${methodType(method, name)},`);
|
|
120
|
+
}
|
|
121
|
+
for (const method of extraMethods || []) {
|
|
122
|
+
if (!method.name || seen.has(method.name)) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
seen.add(method.name);
|
|
126
|
+
lines.push(` ${method.name}: ${methodType(method, null)},`);
|
|
127
|
+
}
|
|
128
|
+
lines.push("}");
|
|
129
|
+
lines.push("");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function emitDataConstructor(lines, name, spec, owners) {
|
|
133
|
+
const emitField = (field) => {
|
|
134
|
+
if (field.valueType && owners.has(field.valueType)) {
|
|
135
|
+
const nested = owners.get(field.valueType);
|
|
136
|
+
const inner = nested.fields.map((item) => `${item.name} = ${emitValue(item.value)}`).join(", ");
|
|
137
|
+
return `{ ${inner} }`;
|
|
138
|
+
}
|
|
139
|
+
if (field.value && field.value.type === "initlist") {
|
|
140
|
+
return emitValue(field.value);
|
|
141
|
+
}
|
|
142
|
+
return emitValue(field.value);
|
|
143
|
+
};
|
|
144
|
+
lines.push(`const function ${name}(): ${name}`);
|
|
145
|
+
lines.push(" return {");
|
|
146
|
+
for (const field of spec.fields) {
|
|
147
|
+
lines.push(` ${field.name} = ${emitField(field)},`);
|
|
148
|
+
}
|
|
149
|
+
lines.push(" }");
|
|
150
|
+
lines.push("end");
|
|
151
|
+
lines.push("");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function emitHeaderModule(ast, options = {}) {
|
|
155
|
+
const strict = options.strict === true;
|
|
156
|
+
const fileName = options.relativeName || options.filePath || "header.h";
|
|
157
|
+
const shape = collectHeaderShape(ast, fileName);
|
|
158
|
+
const lines = [];
|
|
159
|
+
if (strict) {
|
|
160
|
+
lines.push("--!strict");
|
|
161
|
+
}
|
|
162
|
+
lines.push("-- Compiled by Cluaupp — C++ × Luau");
|
|
163
|
+
lines.push("");
|
|
164
|
+
|
|
165
|
+
const typeOrder = [...shape.nested, ...shape.roots];
|
|
166
|
+
for (const name of typeOrder) {
|
|
167
|
+
const extra = name === shape.primary ? shape.freeProtos : [];
|
|
168
|
+
emitExportType(lines, name, shape.owners.get(name), shape.owners, extra);
|
|
169
|
+
}
|
|
170
|
+
if (shape.freeProtos.length && !shape.owners.has(shape.primary)) {
|
|
171
|
+
emitExportType(lines, shape.primary, { fields: [], methods: [] }, shape.owners, shape.freeProtos, shape.consts);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const decl of shape.consts) {
|
|
175
|
+
const typeAnn = luauType(decl.valueType);
|
|
176
|
+
const typed = typeAnn ? `: ${typeAnn}` : "";
|
|
177
|
+
lines.push(`const ${decl.name}${typed} = ${emitValue(decl.value)}`);
|
|
178
|
+
}
|
|
179
|
+
if (shape.consts.length) {
|
|
180
|
+
lines.push("");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const hasMethods = shape.roots.some((name) => shape.owners.get(name).methods.length > 0) || shape.freeProtos.length > 0;
|
|
184
|
+
const implName = hasMethods ? options.headerImplName : null;
|
|
185
|
+
|
|
186
|
+
if (!hasMethods && shape.roots.length > 0) {
|
|
187
|
+
for (const name of shape.roots) {
|
|
188
|
+
emitDataConstructor(lines, name, shape.owners.get(name), shape.owners);
|
|
189
|
+
}
|
|
190
|
+
lines.push(`return ${shape.primary}`);
|
|
191
|
+
lines.push("");
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!hasMethods && shape.consts.length > 0) {
|
|
196
|
+
lines.push("return {");
|
|
197
|
+
for (const decl of shape.consts) {
|
|
198
|
+
lines.push(` ${decl.name} = ${decl.name},`);
|
|
199
|
+
}
|
|
200
|
+
lines.push("}");
|
|
201
|
+
lines.push("");
|
|
202
|
+
return lines.join("\n");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const tableName = shape.primary;
|
|
206
|
+
const spec = shape.owners.get(tableName) || { fields: [], methods: [] };
|
|
207
|
+
const methods = [...spec.methods, ...shape.freeProtos];
|
|
208
|
+
lines.push(`const ${tableName}: ${tableName} = {`);
|
|
209
|
+
if (implName) {
|
|
210
|
+
for (const field of spec.fields) {
|
|
211
|
+
if (field.valueType && shape.owners.has(field.valueType) && field.valueType !== tableName) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
lines.push(` ${field.name} = ${implName}.${field.name},`);
|
|
215
|
+
}
|
|
216
|
+
for (const method of methods) {
|
|
217
|
+
lines.push(` ${method.name} = ${implName}.${method.name},`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const decl of shape.consts) {
|
|
221
|
+
lines.push(` ${decl.name} = ${decl.name},`);
|
|
222
|
+
}
|
|
223
|
+
lines.push("}");
|
|
224
|
+
lines.push("");
|
|
225
|
+
lines.push(`return ${tableName}`);
|
|
226
|
+
lines.push("");
|
|
227
|
+
return lines.join("\n");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
emitHeaderModule,
|
|
232
|
+
primaryHeaderName,
|
|
233
|
+
collectHeaderShape,
|
|
234
|
+
};
|