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