cluaupp 0.1.4 → 0.2.0

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 (113) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +12 -7
  3. package/bin/cluau.js +1 -1
  4. package/bin/cluaupp.js +1 -1
  5. package/docs/README.md +1 -1
  6. package/docs/architecture.md +52 -77
  7. package/docs/cli.md +29 -9
  8. package/docs/comparison.md +3 -3
  9. package/docs/config.md +6 -6
  10. package/docs/cpp-organization.md +2 -2
  11. package/docs/examples/index.md +2 -0
  12. package/docs/getting-started.md +7 -5
  13. package/docs/intellisense.md +49 -31
  14. package/docs/intro.md +2 -2
  15. package/docs/oop/file-tags.md +16 -17
  16. package/docs/oop/index.md +2 -2
  17. package/docs/oop/modules.md +3 -1
  18. package/docs/oop/services.md +7 -14
  19. package/docs/syntax.md +6 -5
  20. package/editors/vscode/extension.js +97 -146
  21. package/editors/vscode/package.json +6 -6
  22. package/examples/README.md +18 -0
  23. package/examples/game/.clangd +25 -0
  24. package/examples/game/.vscode/c_cpp_properties.json +27 -0
  25. package/examples/game/.vscode/extensions.json +5 -0
  26. package/examples/game/.vscode/settings.json +27 -0
  27. package/examples/game/Packages/.gitkeep +0 -0
  28. package/examples/game/cluaupp.config.json +6 -0
  29. package/examples/game/compile_flags.txt +6 -0
  30. package/examples/game/default.project.json +39 -0
  31. package/examples/game/package.json +9 -0
  32. package/examples/game/rokit.toml +5 -0
  33. package/examples/game/src/client/hud.client.cpp +12 -0
  34. package/{src → examples/game/src}/client/init.client.cpp +1 -0
  35. package/examples/game/src/server/combat.server.cpp +23 -0
  36. package/{src → examples/game/src}/server/leaderstats.server.cpp +1 -0
  37. package/examples/game/wally.toml +11 -0
  38. package/generated/api.js +51 -0
  39. package/generated/architecture.js +473 -0
  40. package/generated/ast.js +2 -0
  41. package/generated/cli.js +191 -0
  42. package/generated/compile.js +115 -0
  43. package/generated/editor-install.js +170 -0
  44. package/generated/emit.js +503 -0
  45. package/generated/emitter/luau-codegen.js +167 -0
  46. package/generated/emitter/translators.js +164 -0
  47. package/generated/headers.js +220 -0
  48. package/generated/index.html +49 -0
  49. package/generated/intellisense.js +277 -0
  50. package/generated/layout.js +112 -0
  51. package/generated/lex.js +146 -0
  52. package/generated/libs.js +484 -0
  53. package/generated/lsp.js +55 -0
  54. package/generated/package-info.js +11 -0
  55. package/generated/parse.js +607 -0
  56. package/generated/parser/collector.js +100 -0
  57. package/generated/parser/index.js +30 -0
  58. package/generated/preprocess.js +181 -0
  59. package/generated/system-understander.js +443 -0
  60. package/generated/transpile.js +66 -0
  61. package/generated/types.js +2 -0
  62. package/generated/understand.js +298 -0
  63. package/generated/utils/process-orchestrator.js +63 -0
  64. package/generated/utils/project.js +491 -0
  65. package/generated/utils/rojo-mapper.js +181 -0
  66. package/generated/utils/safe-paths.js +104 -0
  67. package/package.json +25 -8
  68. package/src/api.ts +53 -0
  69. package/src/{architecture.js → architecture.ts} +80 -33
  70. package/src/ast.ts +37 -0
  71. package/src/cli.ts +214 -0
  72. package/src/compile.ts +118 -0
  73. package/src/editor-install.ts +182 -0
  74. package/src/{emit.js → emit.ts} +5 -6
  75. package/src/emitter/luau-codegen.ts +187 -0
  76. package/src/emitter/translators.ts +168 -0
  77. package/src/headers.ts +233 -0
  78. package/src/intellisense.ts +286 -0
  79. package/src/{layout.js → layout.ts} +114 -129
  80. package/src/{lex.js → lex.ts} +4 -6
  81. package/src/{libs.js → libs.ts} +119 -46
  82. package/src/lsp.ts +60 -0
  83. package/src/package-info.ts +7 -0
  84. package/src/{parse.js → parse.ts} +3 -4
  85. package/src/parser/collector.ts +115 -0
  86. package/src/parser/index.ts +27 -0
  87. package/src/{preprocess.js → preprocess.ts} +64 -32
  88. package/src/system-understander.ts +501 -0
  89. package/src/transpile.ts +64 -0
  90. package/src/tree-sitter-cpp.d.ts +4 -0
  91. package/src/types.ts +81 -0
  92. package/src/{understand.js → understand.ts} +24 -213
  93. package/src/utils/process-orchestrator.ts +72 -0
  94. package/src/utils/project.ts +506 -0
  95. package/src/utils/rojo-mapper.ts +190 -0
  96. package/src/utils/safe-paths.ts +98 -0
  97. package/templates/game/.clangd +9 -0
  98. package/templates/game/.vscode/c_cpp_properties.json +1 -1
  99. package/templates/game/.vscode/extensions.json +5 -6
  100. package/templates/game/.vscode/settings.json +7 -6
  101. package/templates/game/cluaupp.config.json +2 -2
  102. package/templates/game/compile_flags.txt +1 -0
  103. package/templates/game/rokit.toml +5 -0
  104. package/templates/game/src/client/init.client.cpp +1 -0
  105. package/templates/game/src/server/leaderstats.server.cpp +1 -0
  106. package/src/api.js +0 -57
  107. package/src/cli.js +0 -481
  108. package/src/compile.js +0 -56
  109. package/src/editor-install.js +0 -218
  110. package/src/intellisense.js +0 -1368
  111. package/src/lsp.js +0 -227
  112. /package/{src → examples/game/src}/shared/config.cpp +0 -0
  113. /package/{src → examples/game/src}/shared/config.h +0 -0
@@ -0,0 +1,503 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emit = emit;
4
+ // @ts-nocheck
5
+ const api_js_1 = require("./api.js");
6
+ const libs_js_1 = require("./libs.js");
7
+ function emit(ast, options = {}) {
8
+ const strict = options.strict === true;
9
+ const lines = [];
10
+ if (!options.skipHeader) {
11
+ if (strict) {
12
+ lines.push("--!strict");
13
+ }
14
+ lines.push("-- Compiled by Cluaupp — C++ × Luau");
15
+ lines.push("");
16
+ }
17
+ const indentOf = (n) => "\t".repeat(n);
18
+ let switchId = 0;
19
+ const classOwners = new Set();
20
+ const classFields = new Set();
21
+ const classMethods = new Set();
22
+ for (const decl of ast.body || []) {
23
+ if (decl.owner) {
24
+ classOwners.add(decl.owner);
25
+ }
26
+ if (decl.type === "decl" && decl.owner) {
27
+ classFields.add(decl.name);
28
+ }
29
+ if ((decl.type === "function" || decl.type === "proto") && decl.owner && decl.name) {
30
+ classMethods.add(decl.name);
31
+ }
32
+ }
33
+ const ownedFns = (ast.body || []).filter((decl) => decl.type === "function");
34
+ const classModule = ownedFns.length > 0 && ownedFns.every((decl) => Boolean(decl.owner));
35
+ const nestedTypes = new Set();
36
+ for (const decl of ast.body || []) {
37
+ if (decl.type === "decl" && decl.owner && decl.valueType && classOwners.has(decl.valueType) && decl.valueType !== decl.owner) {
38
+ nestedTypes.add(decl.valueType);
39
+ }
40
+ }
41
+ const structRoots = [...classOwners].filter((name) => !nestedTypes.has(name));
42
+ const structModule = !classModule && ownedFns.length === 0 && structRoots.length > 0;
43
+ const localNames = new Set();
44
+ let selfOwner = null;
45
+ const isBareGlobal = (name) => {
46
+ return (name === "print" ||
47
+ name === "warn" ||
48
+ name === "error" ||
49
+ name === "game" ||
50
+ name === "workspace" ||
51
+ name === "script" ||
52
+ name === "cout" ||
53
+ name === "cerr" ||
54
+ name === "endl" ||
55
+ (0, libs_js_1.isLibraryType)(name) ||
56
+ (0, api_js_1.isInstanceType)(name) ||
57
+ (0, api_js_1.isDatatype)(name));
58
+ };
59
+ const emitSelfIdent = (name) => {
60
+ if (name === "this") {
61
+ return "self";
62
+ }
63
+ if (!selfOwner || localNames.has(name) || isBareGlobal(name)) {
64
+ return name;
65
+ }
66
+ if (classFields.has(name)) {
67
+ return `self.${name}`;
68
+ }
69
+ return name;
70
+ };
71
+ const emitMethodArg = (arg) => {
72
+ if (arg && arg.type === "ident" && selfOwner && classMethods.has(arg.name) && !localNames.has(arg.name)) {
73
+ return `function(...) self:${arg.name}(...) end`;
74
+ }
75
+ return emitExpr(arg);
76
+ };
77
+ const flattenShift = (node) => {
78
+ const parts = [];
79
+ let current = node;
80
+ while (current && current.type === "binary" && current.op === "<<") {
81
+ parts.unshift(current.right);
82
+ current = current.left;
83
+ }
84
+ return { stream: current, args: parts };
85
+ };
86
+ const isEndl = (node) => node && node.type === "ident" && node.name === "endl";
87
+ const streamPrint = (stream) => {
88
+ if (!stream) {
89
+ return null;
90
+ }
91
+ if (stream.type === "ident") {
92
+ if (stream.name === "cout") {
93
+ return "print";
94
+ }
95
+ if (stream.name === "cerr") {
96
+ return "warn";
97
+ }
98
+ return null;
99
+ }
100
+ if (stream.type === "member" && stream.object && stream.object.type === "ident" && stream.object.name === "cout") {
101
+ const mapped = { print: "print", warn: "warn", error: "error", ping: "print", endl: "print" };
102
+ return mapped[stream.name] || "print";
103
+ }
104
+ return null;
105
+ };
106
+ const emitCout = (node) => {
107
+ const { stream, args } = flattenShift(node);
108
+ const fn = streamPrint(stream);
109
+ if (!fn) {
110
+ return null;
111
+ }
112
+ const linesOut = [];
113
+ let current = [];
114
+ const flush = () => {
115
+ if (current.length === 0) {
116
+ return;
117
+ }
118
+ linesOut.push(`${fn}(${current.map(emitExpr).join(", ")})`);
119
+ current = [];
120
+ };
121
+ for (const arg of args) {
122
+ if (isEndl(arg)) {
123
+ flush();
124
+ continue;
125
+ }
126
+ current.push(arg);
127
+ }
128
+ flush();
129
+ if (linesOut.length === 0) {
130
+ linesOut.push(`${fn}()`);
131
+ }
132
+ return linesOut;
133
+ };
134
+ const emitExpr = (node) => {
135
+ if (!node) {
136
+ return "nil";
137
+ }
138
+ switch (node.type) {
139
+ case "null":
140
+ return "nil";
141
+ case "bool":
142
+ return node.value ? "true" : "false";
143
+ case "number":
144
+ return node.value;
145
+ case "string":
146
+ return `"${node.value}"`;
147
+ case "ident":
148
+ return emitSelfIdent(node.name);
149
+ case "initlist": {
150
+ const entries = (node.fields || []).map((field) => `${field.name} = ${emitExpr(field.value)}`);
151
+ if (entries.length === 0) {
152
+ return "{}";
153
+ }
154
+ return `{ ${entries.join(", ")} }`;
155
+ }
156
+ case "unary": {
157
+ const inner = emitExpr(node.argument);
158
+ if (node.op === "!") {
159
+ return `not ${inner}`;
160
+ }
161
+ return `-${inner}`;
162
+ }
163
+ case "getService":
164
+ return `game:GetService("${node.service}")`;
165
+ case "new": {
166
+ if ((0, libs_js_1.isLibraryType)(node.className) || !(0, api_js_1.isInstanceType)(node.className)) {
167
+ const args = node.args.map(emitExpr).join(", ");
168
+ return `${node.className}.new(${args})`;
169
+ }
170
+ return `Instance.new("${node.className}")`;
171
+ }
172
+ case "member":
173
+ return `${emitExpr(node.object)}.${node.name}`;
174
+ case "call": {
175
+ const args = node.args.map(emitMethodArg).join(", ");
176
+ if (!node.object) {
177
+ if ((0, api_js_1.isDatatype)(node.name)) {
178
+ return `${node.name}.new(${args})`;
179
+ }
180
+ if (selfOwner && classMethods.has(node.name) && !localNames.has(node.name)) {
181
+ return `self:${node.name}(${args})`;
182
+ }
183
+ return `${node.name}(${args})`;
184
+ }
185
+ const obj = emitExpr(node.object);
186
+ if (node.access === "::" && node.object.type === "ident" && node.object.name === "cout") {
187
+ if (node.name === "endl") {
188
+ return "print()";
189
+ }
190
+ const mapped = { print: "print", warn: "warn", error: "error", ping: "print" };
191
+ return `${mapped[node.name] || "print"}(${args})`;
192
+ }
193
+ if (node.access === "::") {
194
+ if (libs_js_1.MODULE_COLON.has(node.name)) {
195
+ return `${obj}:${node.name}(${args})`;
196
+ }
197
+ const staticNs = node.object.type === "ident" &&
198
+ ((0, libs_js_1.isLibraryType)(node.object.name) ||
199
+ (0, api_js_1.isDatatype)(node.object.name) ||
200
+ node.object.name === "DataService" ||
201
+ node.object.name === "FormatNumber" ||
202
+ node.object.name === "Enum");
203
+ if (staticNs) {
204
+ return `${obj}.${node.name}(${args})`;
205
+ }
206
+ return `${obj}:${node.name}(${args})`;
207
+ }
208
+ if ((0, api_js_1.isDatatype)(obj) || (node.object.type === "ident" && (0, api_js_1.isDatatype)(node.object.name))) {
209
+ return `${obj}.${node.name}(${args})`;
210
+ }
211
+ const colon = (0, api_js_1.isMethod)(node.name) || (0, libs_js_1.isLibraryMethod)(node.name);
212
+ return `${obj}${colon ? ":" : "."}${node.name}(${args})`;
213
+ }
214
+ case "assign":
215
+ return `${emitExpr(node.left)} = ${emitExpr(node.right)}`;
216
+ case "binary": {
217
+ if (node.op === "<<") {
218
+ const printed = emitCout(node);
219
+ if (printed) {
220
+ return printed.join("; ");
221
+ }
222
+ }
223
+ const ops = { "!=": "~=", "&&": "and", "||": "or" };
224
+ const op = ops[node.op] || node.op;
225
+ return `${emitExpr(node.left)} ${op} ${emitExpr(node.right)}`;
226
+ }
227
+ default:
228
+ return "nil";
229
+ }
230
+ };
231
+ const inferredType = (node) => {
232
+ const created = node.value;
233
+ if (created && created.type === "new" && ((0, api_js_1.isInstanceType)(created.className) || (0, libs_js_1.isLibraryType)(created.className) || (0, api_js_1.isDatatype)(created.className))) {
234
+ return created.className;
235
+ }
236
+ if (created && created.type === "call" && !created.object && (0, api_js_1.isDatatype)(created.name)) {
237
+ return created.name;
238
+ }
239
+ if (created && created.type === "getService") {
240
+ return created.service;
241
+ }
242
+ const fromCpp = (0, api_js_1.luauType)(node.valueType);
243
+ if (fromCpp && fromCpp !== "auto") {
244
+ return fromCpp;
245
+ }
246
+ return null;
247
+ };
248
+ const emitNewDecl = (node, indent) => {
249
+ const prefix = indentOf(indent);
250
+ const typeAnn = inferredType(node);
251
+ const created = node.value;
252
+ if (node.owner && indent === 0 && classModule) {
253
+ const value = node.value ? emitExpr(node.value) : "nil";
254
+ if (created && created.type === "new" && (0, api_js_1.isInstanceType)(created.className) && !(0, libs_js_1.isLibraryType)(created.className)) {
255
+ const linesOut = [`${prefix}${node.owner}.${node.name} = Instance.new("${created.className}")`];
256
+ if (created.args[0]) {
257
+ linesOut.push(`${prefix}${node.owner}.${node.name}.Parent = ${emitExpr(created.args[0])}`);
258
+ }
259
+ return linesOut;
260
+ }
261
+ if (created && created.type === "new" && ((0, api_js_1.isDatatype)(created.className) || (0, libs_js_1.isLibraryType)(created.className))) {
262
+ const args = created.args.map(emitExpr).join(", ");
263
+ return [`${prefix}${node.owner}.${node.name} = ${created.className}.new(${args})`];
264
+ }
265
+ return [`${prefix}${node.owner}.${node.name} = ${value}`];
266
+ }
267
+ const kind = node.isConst ? "const" : "local";
268
+ const typed = typeAnn ? `: ${typeAnn}` : "";
269
+ if (created && created.type === "new" && (0, api_js_1.isInstanceType)(created.className) && !(0, libs_js_1.isLibraryType)(created.className)) {
270
+ const linesOut = [`${prefix}${kind} ${node.name}${typed} = Instance.new("${created.className}")`];
271
+ if (created.args[0]) {
272
+ linesOut.push(`${prefix}${node.name}.Parent = ${emitExpr(created.args[0])}`);
273
+ }
274
+ return linesOut;
275
+ }
276
+ if (created && created.type === "new" && ((0, api_js_1.isDatatype)(created.className) || (0, libs_js_1.isLibraryType)(created.className))) {
277
+ const args = created.args.map(emitExpr).join(", ");
278
+ return [`${prefix}${kind} ${node.name}${typed} = ${created.className}.new(${args})`];
279
+ }
280
+ const value = node.value ? emitExpr(node.value) : "nil";
281
+ return [`${prefix}${kind} ${node.name}${typed} = ${value}`];
282
+ };
283
+ const emitStmt = (node, indent) => {
284
+ const prefix = indentOf(indent);
285
+ switch (node.type) {
286
+ case "decl":
287
+ if (node.name) {
288
+ localNames.add(node.name);
289
+ }
290
+ return emitNewDecl(node, indent);
291
+ case "expr": {
292
+ const expr = node.expr;
293
+ if (expr && expr.type === "binary" && expr.op === "<<") {
294
+ const printed = emitCout(expr);
295
+ if (printed) {
296
+ return printed.map((line) => `${prefix}${line}`);
297
+ }
298
+ }
299
+ if (expr &&
300
+ expr.type === "assign" &&
301
+ expr.right &&
302
+ expr.right.type === "new" &&
303
+ (0, api_js_1.isInstanceType)(expr.right.className) &&
304
+ !(0, libs_js_1.isLibraryType)(expr.right.className)) {
305
+ const left = emitExpr(expr.left);
306
+ const linesOut = [`${prefix}${left} = Instance.new("${expr.right.className}")`];
307
+ if (expr.right.args[0]) {
308
+ linesOut.push(`${prefix}${left}.Parent = ${emitExpr(expr.right.args[0])}`);
309
+ }
310
+ return linesOut;
311
+ }
312
+ return [`${prefix}${emitExpr(expr)}`];
313
+ }
314
+ case "return":
315
+ return node.value ? [`${prefix}return ${emitExpr(node.value)}`] : [`${prefix}return`];
316
+ case "if": {
317
+ const out = [`${prefix}if ${emitExpr(node.test)} then`];
318
+ for (const stmt of node.consequent) {
319
+ out.push(...emitStmt(stmt, indent + 1));
320
+ }
321
+ if (node.alternate) {
322
+ out.push(`${prefix}else`);
323
+ for (const stmt of node.alternate) {
324
+ out.push(...emitStmt(stmt, indent + 1));
325
+ }
326
+ }
327
+ out.push(`${prefix}end`);
328
+ return out;
329
+ }
330
+ case "while": {
331
+ const out = [`${prefix}while ${emitExpr(node.test)} do`];
332
+ for (const stmt of node.body) {
333
+ out.push(...emitStmt(stmt, indent + 1));
334
+ }
335
+ out.push(`${prefix}end`);
336
+ return out;
337
+ }
338
+ case "foreach": {
339
+ const out = [`${prefix}for _, ${node.name} in ${emitExpr(node.iter)} do`];
340
+ for (const stmt of node.body) {
341
+ out.push(...emitStmt(stmt, indent + 1));
342
+ }
343
+ out.push(`${prefix}end`);
344
+ return out;
345
+ }
346
+ case "break":
347
+ return [`${prefix}break`];
348
+ case "switch": {
349
+ switchId += 1;
350
+ const id = `__switch${switchId}`;
351
+ const disc = node.discriminant;
352
+ const simple = disc &&
353
+ (disc.type === "ident" || disc.type === "number" || disc.type === "string" || disc.type === "bool");
354
+ const subject = simple ? emitExpr(disc) : id;
355
+ const out = [`${prefix}repeat`];
356
+ const inner = indent + 1;
357
+ const innerP = indentOf(inner);
358
+ if (!simple) {
359
+ out.push(`${innerP}local ${id} = ${emitExpr(disc)}`);
360
+ }
361
+ const regular = (node.cases || []).filter((item) => !item.isDefault);
362
+ const fallback = (node.cases || []).find((item) => item.isDefault);
363
+ const testOf = (item) => (item.values || []).map((value) => `${subject} == ${emitExpr(value)}`).join(" or ");
364
+ for (let index = 0; index < regular.length; index += 1) {
365
+ const item = regular[index];
366
+ const keyword = index === 0 ? "if" : "elseif";
367
+ out.push(`${innerP}${keyword} ${testOf(item)} then`);
368
+ for (const stmt of item.body || []) {
369
+ out.push(...emitStmt(stmt, inner + 1));
370
+ }
371
+ }
372
+ if (fallback) {
373
+ if (regular.length === 0) {
374
+ for (const stmt of fallback.body || []) {
375
+ out.push(...emitStmt(stmt, inner));
376
+ }
377
+ }
378
+ else {
379
+ out.push(`${innerP}else`);
380
+ for (const stmt of fallback.body || []) {
381
+ out.push(...emitStmt(stmt, inner + 1));
382
+ }
383
+ }
384
+ }
385
+ if (regular.length > 0) {
386
+ out.push(`${innerP}end`);
387
+ }
388
+ out.push(`${prefix}until true`);
389
+ return out;
390
+ }
391
+ default:
392
+ return [];
393
+ }
394
+ };
395
+ const paramList = (fn) => fn.params
396
+ .map((param) => {
397
+ if (typeof param === "string") {
398
+ return param;
399
+ }
400
+ const typeAnn = (0, api_js_1.luauType)(param.valueType);
401
+ return typeAnn ? `${param.name}: ${typeAnn}` : param.name;
402
+ })
403
+ .join(", ");
404
+ const returnAnn = (fn) => {
405
+ const typeAnn = (0, api_js_1.luauType)(fn.returnType);
406
+ if (!typeAnn || typeAnn === "()") {
407
+ return "";
408
+ }
409
+ return `: ${typeAnn}`;
410
+ };
411
+ const emitFieldLiteral = (decl) => {
412
+ if (decl.value && decl.value.type === "initlist") {
413
+ const inner = (decl.value.fields || [])
414
+ .map((field) => `${field.name} = ${emitExpr(field.value)}`)
415
+ .join(", ");
416
+ return `{ ${inner} }`;
417
+ }
418
+ if (decl.valueType && nestedTypes.has(decl.valueType)) {
419
+ const nested = (ast.body || []).filter((item) => item.type === "decl" && item.owner === decl.valueType);
420
+ const inner = nested.map((item) => `${item.name} = ${emitFieldLiteral(item)}`).join(", ");
421
+ return `{ ${inner} }`;
422
+ }
423
+ return decl.value ? emitExpr(decl.value) : "nil";
424
+ };
425
+ const emitStructConstructor = (root) => {
426
+ const fields = (ast.body || []).filter((decl) => decl.type === "decl" && decl.owner === root);
427
+ const inner = fields.map((field) => `\t\t${field.name} = ${emitFieldLiteral(field)},`).join("\n");
428
+ lines.push(`const function ${root}()`);
429
+ lines.push(" return {");
430
+ if (inner) {
431
+ lines.push(inner);
432
+ }
433
+ lines.push(" }");
434
+ lines.push("end");
435
+ lines.push("");
436
+ lines.push(`return ${root}`);
437
+ lines.push("");
438
+ };
439
+ if (classModule) {
440
+ for (const owner of classOwners) {
441
+ lines.push(`local ${owner} = {}`);
442
+ lines.push("");
443
+ }
444
+ }
445
+ if (structModule) {
446
+ for (const root of structRoots) {
447
+ emitStructConstructor(root);
448
+ }
449
+ return lines.join("\n");
450
+ }
451
+ for (const decl of ast.body) {
452
+ if (decl.type === "decl") {
453
+ lines.push(...emitNewDecl(decl, 0));
454
+ lines.push("");
455
+ continue;
456
+ }
457
+ if (decl.type === "expr") {
458
+ lines.push(emitExpr(decl.expr));
459
+ lines.push("");
460
+ continue;
461
+ }
462
+ if (decl.type !== "function") {
463
+ continue;
464
+ }
465
+ selfOwner = decl.owner || null;
466
+ localNames.clear();
467
+ for (const param of decl.params || []) {
468
+ const paramName = typeof param === "string" ? param : param.name;
469
+ if (paramName) {
470
+ localNames.add(paramName);
471
+ }
472
+ }
473
+ if (decl.owner) {
474
+ lines.push(`function ${decl.owner}:${decl.name}(${paramList(decl)})${returnAnn(decl)}`);
475
+ }
476
+ else {
477
+ lines.push(`const function ${decl.name}(${paramList(decl)})${returnAnn(decl)}`);
478
+ }
479
+ for (const stmt of decl.body) {
480
+ lines.push(...emitStmt(stmt, 1));
481
+ }
482
+ lines.push("end");
483
+ lines.push("");
484
+ selfOwner = null;
485
+ localNames.clear();
486
+ }
487
+ const hasInit = ast.body.some((decl) => decl.type === "function" && decl.name === "init");
488
+ if (classModule) {
489
+ for (const owner of classOwners) {
490
+ if (hasInit) {
491
+ lines.push(`${owner}.Start = ${owner}.init`);
492
+ lines.push(`${owner}.Init = ${owner}.init`);
493
+ }
494
+ lines.push(`return ${owner}`);
495
+ lines.push("");
496
+ }
497
+ }
498
+ else if (hasInit && !options.skipInit) {
499
+ lines.push("init()");
500
+ lines.push("");
501
+ }
502
+ return lines.join("\n");
503
+ }
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LuauCodeEmitter = void 0;
4
+ const GET_SERVICE = /^const\s+([A-Za-z_][\w]*)\s*=\s*game:GetService\("([A-Za-z_][\w]*)"\)\s*$/;
5
+ const REQUIRE_LINE = /^const\s+([A-Za-z_][\w]*)\s*=\s*require\((.+)\)\s*$/;
6
+ class LuauCodeEmitter {
7
+ static emptyState(strict = false) {
8
+ return {
9
+ robloxServices: new Set(),
10
+ moduleRequires: new Map(),
11
+ customTypes: [],
12
+ constants: [],
13
+ functions: [],
14
+ headerLines: [],
15
+ strict,
16
+ };
17
+ }
18
+ static emit(state) {
19
+ const output = [];
20
+ if (state.strict) {
21
+ output.push("--!strict");
22
+ }
23
+ if (state.headerLines.length > 0) {
24
+ output.push(...state.headerLines);
25
+ }
26
+ else {
27
+ output.push("-- Compiled by Cluaupp — C++ × Luau");
28
+ }
29
+ output.push("");
30
+ if (state.robloxServices.size > 0) {
31
+ for (const service of Array.from(state.robloxServices).sort()) {
32
+ output.push(`const ${service} = game:GetService("${service}")`);
33
+ }
34
+ output.push("");
35
+ }
36
+ if (state.moduleRequires.size > 0) {
37
+ for (const [alias, requirePath] of Array.from(state.moduleRequires.entries()).sort(([a], [b]) => a.localeCompare(b))) {
38
+ output.push(`const ${alias} = require(${requirePath})`);
39
+ }
40
+ output.push("");
41
+ }
42
+ if (state.customTypes.length > 0) {
43
+ output.push(...state.customTypes);
44
+ output.push("");
45
+ }
46
+ if (state.constants.length > 0) {
47
+ output.push(...state.constants);
48
+ output.push("");
49
+ }
50
+ if (state.functions.length > 0) {
51
+ output.push(...state.functions);
52
+ }
53
+ else {
54
+ output.push("-- Nenhum método ou lógica detectado para transcompilação.");
55
+ }
56
+ return output.join("\n").replace(/\n{3,}/g, "\n\n") + "\n";
57
+ }
58
+ static merge(state, compiledLuau) {
59
+ const trimmed = String(compiledLuau || "").trim();
60
+ if (!trimmed || trimmed.startsWith("{") || trimmed.startsWith("[")) {
61
+ return compiledLuau;
62
+ }
63
+ const harvested = harvestPreamble(compiledLuau);
64
+ for (const service of harvested.services) {
65
+ state.robloxServices.add(service);
66
+ }
67
+ for (const [alias, requirePath] of harvested.requires) {
68
+ if (!state.moduleRequires.has(alias)) {
69
+ state.moduleRequires.set(alias, requirePath);
70
+ }
71
+ }
72
+ const output = [];
73
+ output.push(...(harvested.header.length > 0 ? harvested.header : ["-- Compiled by Cluaupp — C++ × Luau"]));
74
+ for (const line of state.headerLines) {
75
+ if (line && !output.includes(line)) {
76
+ output.push(line);
77
+ }
78
+ }
79
+ if (state.strict && !output.some((line) => line.startsWith("--!strict"))) {
80
+ output.unshift("--!strict");
81
+ }
82
+ output.push("");
83
+ if (state.robloxServices.size > 0) {
84
+ for (const service of Array.from(state.robloxServices).sort()) {
85
+ output.push(`const ${service} = game:GetService("${service}")`);
86
+ }
87
+ output.push("");
88
+ }
89
+ if (state.moduleRequires.size > 0) {
90
+ for (const [alias, requirePath] of Array.from(state.moduleRequires.entries()).sort(([a], [b]) => a.localeCompare(b))) {
91
+ output.push(`const ${alias} = require(${requirePath})`);
92
+ }
93
+ output.push("");
94
+ }
95
+ const rest = harvested.body.trim();
96
+ const extraTypes = state.customTypes.filter((line) => !bodyHasDecl(rest, line, /^type\s+(\w+)/));
97
+ const extraConsts = state.constants.filter((line) => !bodyHasDecl(rest, line, /^const\s+(\w+)/));
98
+ if (extraTypes.length > 0) {
99
+ output.push(...extraTypes, "");
100
+ }
101
+ if (extraConsts.length > 0) {
102
+ output.push(...extraConsts, "");
103
+ }
104
+ if (rest) {
105
+ output.push(rest);
106
+ }
107
+ else if (state.customTypes.length || state.constants.length || state.functions.length) {
108
+ if (state.customTypes.length) {
109
+ output.push(...state.customTypes, "");
110
+ }
111
+ if (state.constants.length) {
112
+ output.push(...state.constants, "");
113
+ }
114
+ if (state.functions.length) {
115
+ output.push(...state.functions);
116
+ }
117
+ }
118
+ return `${output.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
119
+ }
120
+ }
121
+ exports.LuauCodeEmitter = LuauCodeEmitter;
122
+ function harvestPreamble(luau) {
123
+ const lines = luau.split(/\r?\n/);
124
+ const header = [];
125
+ const services = new Set();
126
+ const requires = new Map();
127
+ let i = 0;
128
+ while (i < lines.length && (lines[i].startsWith("--") || lines[i].trim() === "")) {
129
+ if (lines[i].startsWith("--")) {
130
+ header.push(lines[i]);
131
+ }
132
+ i += 1;
133
+ }
134
+ while (i < lines.length) {
135
+ const line = lines[i];
136
+ if (line.trim() === "") {
137
+ i += 1;
138
+ continue;
139
+ }
140
+ const service = line.match(GET_SERVICE);
141
+ if (service && service[1] === service[2]) {
142
+ services.add(service[1]);
143
+ i += 1;
144
+ continue;
145
+ }
146
+ const required = line.match(REQUIRE_LINE);
147
+ if (required) {
148
+ requires.set(required[1], required[2]);
149
+ i += 1;
150
+ continue;
151
+ }
152
+ break;
153
+ }
154
+ return {
155
+ header,
156
+ services,
157
+ requires,
158
+ body: lines.slice(i).join("\n"),
159
+ };
160
+ }
161
+ function bodyHasDecl(body, line, pattern) {
162
+ const match = line.match(pattern);
163
+ if (!match) {
164
+ return body.includes(line);
165
+ }
166
+ return new RegExp(`^(?:export\\s+)?(?:type|const)\\s+${match[1]}\\b`, "m").test(body);
167
+ }