cluaupp 0.1.5 → 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.
- package/CHANGELOG.md +12 -0
- package/README.md +6 -1
- package/bin/cluau.js +1 -1
- package/bin/cluaupp.js +1 -1
- package/docs/architecture.md +26 -1
- package/docs/cli.md +27 -6
- package/docs/examples/index.md +2 -0
- package/docs/getting-started.md +5 -4
- package/docs/intellisense.md +49 -31
- package/docs/intro.md +1 -1
- package/editors/vscode/extension.js +97 -146
- package/editors/vscode/package.json +6 -6
- package/examples/README.md +18 -0
- package/examples/game/.clangd +25 -0
- package/examples/game/.vscode/c_cpp_properties.json +27 -0
- package/examples/game/.vscode/extensions.json +5 -0
- package/examples/game/.vscode/settings.json +27 -0
- package/examples/game/Packages/.gitkeep +0 -0
- package/examples/game/cluaupp.config.json +6 -0
- package/examples/game/compile_flags.txt +6 -0
- package/examples/game/default.project.json +39 -0
- package/examples/game/package.json +9 -0
- package/examples/game/rokit.toml +5 -0
- package/examples/game/src/client/hud.client.cpp +12 -0
- package/{src → examples/game/src}/client/init.client.cpp +1 -0
- package/examples/game/src/server/combat.server.cpp +23 -0
- package/{src → examples/game/src}/server/leaderstats.server.cpp +1 -0
- package/examples/game/wally.toml +11 -0
- package/generated/api.js +51 -0
- package/generated/architecture.js +473 -0
- package/generated/ast.js +2 -0
- package/generated/cli.js +191 -0
- package/generated/compile.js +115 -0
- package/generated/editor-install.js +170 -0
- package/generated/emit.js +503 -0
- package/generated/emitter/luau-codegen.js +167 -0
- package/generated/emitter/translators.js +164 -0
- package/generated/headers.js +220 -0
- package/generated/index.html +49 -0
- package/generated/intellisense.js +277 -0
- package/generated/layout.js +112 -0
- package/generated/lex.js +146 -0
- package/generated/libs.js +484 -0
- package/generated/lsp.js +55 -0
- package/generated/package-info.js +11 -0
- package/generated/parse.js +607 -0
- package/generated/parser/collector.js +100 -0
- package/generated/parser/index.js +30 -0
- package/generated/preprocess.js +181 -0
- package/generated/system-understander.js +443 -0
- package/generated/transpile.js +66 -0
- package/generated/types.js +2 -0
- package/generated/understand.js +298 -0
- package/generated/utils/process-orchestrator.js +63 -0
- package/generated/utils/project.js +491 -0
- package/generated/utils/rojo-mapper.js +181 -0
- package/generated/utils/safe-paths.js +104 -0
- package/package.json +25 -8
- package/src/api.ts +53 -0
- package/src/{architecture.js → architecture.ts} +9 -10
- package/src/ast.ts +37 -0
- package/src/cli.ts +214 -0
- package/src/compile.ts +118 -0
- package/src/editor-install.ts +182 -0
- package/src/{emit.js → emit.ts} +4 -5
- package/src/emitter/luau-codegen.ts +187 -0
- package/src/emitter/translators.ts +168 -0
- package/src/{headers.js → headers.ts} +4 -5
- package/src/intellisense.ts +286 -0
- package/src/{layout.js → layout.ts} +114 -129
- package/src/{lex.js → lex.ts} +4 -6
- package/src/{libs.js → libs.ts} +9 -4
- package/src/lsp.ts +60 -0
- package/src/package-info.ts +7 -0
- package/src/{parse.js → parse.ts} +3 -4
- package/src/parser/collector.ts +115 -0
- package/src/parser/index.ts +27 -0
- package/src/{preprocess.js → preprocess.ts} +22 -37
- package/src/system-understander.ts +501 -0
- package/src/transpile.ts +64 -0
- package/src/tree-sitter-cpp.d.ts +4 -0
- package/src/types.ts +81 -0
- package/src/{understand.js → understand.ts} +19 -210
- package/src/utils/process-orchestrator.ts +72 -0
- package/src/utils/project.ts +506 -0
- package/src/utils/rojo-mapper.ts +190 -0
- package/src/utils/safe-paths.ts +98 -0
- package/templates/game/.clangd +9 -0
- package/templates/game/.vscode/c_cpp_properties.json +1 -1
- package/templates/game/.vscode/extensions.json +5 -6
- package/templates/game/.vscode/settings.json +7 -6
- package/templates/game/compile_flags.txt +1 -0
- package/templates/game/rokit.toml +5 -0
- package/src/api.js +0 -57
- package/src/cli.js +0 -473
- package/src/compile.js +0 -110
- package/src/editor-install.js +0 -218
- package/src/intellisense.js +0 -1368
- package/src/lsp.js +0 -227
- /package/{src → examples/game/src}/shared/config.cpp +0 -0
- /package/{src → examples/game/src}/shared/config.h +0 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VALUE_CLASSES = exports.toPascalServiceName = exports.analyze = void 0;
|
|
7
|
+
exports.planOutput = planOutput;
|
|
8
|
+
// @ts-nocheck
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const emit_js_1 = require("./emit.js");
|
|
11
|
+
const libs_js_1 = require("./libs.js");
|
|
12
|
+
const layout_js_1 = require("./layout.js");
|
|
13
|
+
const understand_js_1 = require("./understand.js");
|
|
14
|
+
Object.defineProperty(exports, "VALUE_CLASSES", { enumerable: true, get: function () { return understand_js_1.VALUE_CLASSES; } });
|
|
15
|
+
Object.defineProperty(exports, "analyze", { enumerable: true, get: function () { return understand_js_1.analyze; } });
|
|
16
|
+
Object.defineProperty(exports, "toPascalServiceName", { enumerable: true, get: function () { return understand_js_1.toPascalServiceName; } });
|
|
17
|
+
const preprocess_js_1 = require("./preprocess.js");
|
|
18
|
+
function header(plan) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
if (plan && plan.strict) {
|
|
21
|
+
lines.push("--!strict");
|
|
22
|
+
}
|
|
23
|
+
lines.push("-- Compiled by Cluaupp — C++ × Luau");
|
|
24
|
+
if (plan && plan.reasoning) {
|
|
25
|
+
for (const line of plan.reasoning) {
|
|
26
|
+
lines.push(`-- ${line}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
lines.push("");
|
|
30
|
+
return lines.join("\n");
|
|
31
|
+
}
|
|
32
|
+
function cluauppLib(name) {
|
|
33
|
+
return (0, libs_js_1.requireCluauppLib)(name);
|
|
34
|
+
}
|
|
35
|
+
function emitTypes(plan) {
|
|
36
|
+
const lines = [header(plan).trimEnd(), ""];
|
|
37
|
+
lines.push('const ReplicatedStorage = game:GetService("ReplicatedStorage")');
|
|
38
|
+
lines.push(`const Occlude = ${cluauppLib("Occlude")}`);
|
|
39
|
+
lines.push(`const ArrayIndexer = ${cluauppLib("ArrayIndexer")}`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
if (plan.stats.length > 0) {
|
|
42
|
+
const union = plan.stats.map((stat) => `"${stat.name}"`).join(" | ");
|
|
43
|
+
lines.push(`export type StatName = ${union}`);
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push(`export type ${plan.serviceName}Data = {`);
|
|
46
|
+
for (const stat of plan.stats) {
|
|
47
|
+
lines.push(` ${stat.name}: ${stat.luau},`);
|
|
48
|
+
}
|
|
49
|
+
lines.push("}");
|
|
50
|
+
lines.push("");
|
|
51
|
+
}
|
|
52
|
+
if (plan.roles.cache) {
|
|
53
|
+
lines.push("export type PlayerCache = {");
|
|
54
|
+
lines.push(" Folder: Folder,");
|
|
55
|
+
for (const stat of plan.stats) {
|
|
56
|
+
lines.push(` ${stat.name}: ${stat.instance},`);
|
|
57
|
+
}
|
|
58
|
+
lines.push("}");
|
|
59
|
+
lines.push("");
|
|
60
|
+
}
|
|
61
|
+
const manifest = [];
|
|
62
|
+
if (plan.roles.cache) {
|
|
63
|
+
manifest.push(" CacheController: {");
|
|
64
|
+
manifest.push(" Get: (player: Player) -> PlayerCache?,");
|
|
65
|
+
manifest.push(" Ensure: (player: Player) -> PlayerCache,");
|
|
66
|
+
manifest.push(" Clear: (player: Player) -> (),");
|
|
67
|
+
manifest.push(" ClearAll: () -> (),");
|
|
68
|
+
manifest.push(" },");
|
|
69
|
+
}
|
|
70
|
+
if (plan.roles.players) {
|
|
71
|
+
manifest.push(" PlayersManager: {");
|
|
72
|
+
manifest.push(" Start: (onPlayer: (player: Player) -> (), onLeave: ((player: Player) -> ())?) -> (),");
|
|
73
|
+
manifest.push(" Stop: () -> (),");
|
|
74
|
+
manifest.push(" },");
|
|
75
|
+
}
|
|
76
|
+
if (plan.roles.domain) {
|
|
77
|
+
manifest.push(` ${plan.roles.domain}: {`);
|
|
78
|
+
manifest.push(" Start: () -> (),");
|
|
79
|
+
manifest.push(" Stop: () -> (),");
|
|
80
|
+
manifest.push(" },");
|
|
81
|
+
}
|
|
82
|
+
lines.push("export type ServiceManifest = {");
|
|
83
|
+
if (manifest.length === 0) {
|
|
84
|
+
lines.push(" Main: { Start: () -> (), Stop: () -> () },");
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
lines.push(...manifest);
|
|
88
|
+
}
|
|
89
|
+
lines.push("}");
|
|
90
|
+
lines.push("");
|
|
91
|
+
if (plan.roles.cache) {
|
|
92
|
+
lines.push('export type CacheService = ArrayIndexer.Table<ServiceManifest, "CacheController">');
|
|
93
|
+
}
|
|
94
|
+
else if (plan.roles.domain) {
|
|
95
|
+
lines.push(`export type DomainService = ArrayIndexer.Table<ServiceManifest, "${plan.roles.domain}">`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
lines.push('export type MainService = ArrayIndexer.Table<ServiceManifest, "Main">');
|
|
99
|
+
}
|
|
100
|
+
if (plan.stats.length > 1) {
|
|
101
|
+
const hide = plan.stats[plan.stats.length - 1].name;
|
|
102
|
+
lines.push(`export type HudData = Occlude.Keys<${plan.serviceName}Data, "${hide}">`);
|
|
103
|
+
}
|
|
104
|
+
lines.push("");
|
|
105
|
+
lines.push("return {}");
|
|
106
|
+
lines.push("");
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
function emitPlayersManager(plan) {
|
|
110
|
+
return `${header(plan).trimEnd()}
|
|
111
|
+
|
|
112
|
+
const Players = game:GetService("Players")
|
|
113
|
+
const ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
114
|
+
const Janitor = ${cluauppLib("Janitor")}
|
|
115
|
+
|
|
116
|
+
type Janitor = Janitor.Janitor
|
|
117
|
+
export type PlayerHandler = (player: Player) -> ()
|
|
118
|
+
|
|
119
|
+
local PlayersManager = {}
|
|
120
|
+
local lifetime: Janitor? = nil
|
|
121
|
+
local leaveHandler: PlayerHandler? = nil
|
|
122
|
+
|
|
123
|
+
function PlayersManager.Start(onPlayer: PlayerHandler, onLeave: PlayerHandler?)
|
|
124
|
+
PlayersManager.Stop()
|
|
125
|
+
local janitor = Janitor.new()
|
|
126
|
+
lifetime = janitor
|
|
127
|
+
leaveHandler = onLeave
|
|
128
|
+
|
|
129
|
+
const function accept(player: Player)
|
|
130
|
+
onPlayer(player)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
for _, player in Players:GetPlayers() do
|
|
134
|
+
accept(player)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
janitor:Add(Players.PlayerAdded:Connect(accept), "Disconnect", "PlayerAdded")
|
|
138
|
+
if onLeave then
|
|
139
|
+
janitor:Add(Players.PlayerRemoving:Connect(onLeave), "Disconnect", "PlayerRemoving")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
function PlayersManager.Stop()
|
|
144
|
+
if lifetime == nil then
|
|
145
|
+
return
|
|
146
|
+
end
|
|
147
|
+
local onLeave = leaveHandler
|
|
148
|
+
leaveHandler = nil
|
|
149
|
+
if onLeave then
|
|
150
|
+
for _, player in Players:GetPlayers() do
|
|
151
|
+
onLeave(player)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
lifetime:Cleanup()
|
|
155
|
+
lifetime = nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
return PlayersManager
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
function emitCacheController(plan) {
|
|
162
|
+
const types = plan.typesName;
|
|
163
|
+
const folder = plan.folderName || "leaderstats";
|
|
164
|
+
const lines = [header(plan).trimEnd(), ""];
|
|
165
|
+
lines.push(`const Types = require(script.Parent.${types})`);
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push(`const FOLDER_NAME = "${folder}"`);
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("local cache: { [Player]: Types.PlayerCache } = {}");
|
|
170
|
+
lines.push("local CacheController = {}");
|
|
171
|
+
lines.push("");
|
|
172
|
+
lines.push("function CacheController.Get(player: Player): Types.PlayerCache?");
|
|
173
|
+
lines.push(" return cache[player]");
|
|
174
|
+
lines.push("end");
|
|
175
|
+
lines.push("");
|
|
176
|
+
lines.push("function CacheController.Ensure(player: Player): Types.PlayerCache");
|
|
177
|
+
lines.push(" local existing = cache[player]");
|
|
178
|
+
lines.push(" if existing then");
|
|
179
|
+
lines.push(" return existing");
|
|
180
|
+
lines.push(" end");
|
|
181
|
+
lines.push("");
|
|
182
|
+
lines.push(" local folderInstance = player:FindFirstChild(FOLDER_NAME)");
|
|
183
|
+
lines.push(" local folder: Folder");
|
|
184
|
+
lines.push(' if folderInstance and folderInstance:IsA("Folder") then');
|
|
185
|
+
lines.push(" folder = folderInstance");
|
|
186
|
+
lines.push(" else");
|
|
187
|
+
lines.push(' folder = Instance.new("Folder")');
|
|
188
|
+
lines.push(" folder.Name = FOLDER_NAME");
|
|
189
|
+
lines.push(" folder.Parent = player");
|
|
190
|
+
lines.push(" end");
|
|
191
|
+
lines.push("");
|
|
192
|
+
for (const stat of plan.stats) {
|
|
193
|
+
const ident = stat.name;
|
|
194
|
+
lines.push(` local existing${ident} = folder:FindFirstChild("${stat.name}")`);
|
|
195
|
+
lines.push(` local ${ident}: ${stat.instance}`);
|
|
196
|
+
lines.push(` if existing${ident} and existing${ident}:IsA("${stat.instance}") then`);
|
|
197
|
+
lines.push(` ${ident} = existing${ident}`);
|
|
198
|
+
lines.push(" else");
|
|
199
|
+
lines.push(` ${ident} = Instance.new("${stat.instance}")`);
|
|
200
|
+
lines.push(` ${ident}.Name = "${stat.name}"`);
|
|
201
|
+
lines.push(` ${ident}.Value = ${stat.defaultValue}`);
|
|
202
|
+
lines.push(` ${ident}.Parent = folder`);
|
|
203
|
+
lines.push(" end");
|
|
204
|
+
lines.push("");
|
|
205
|
+
}
|
|
206
|
+
lines.push(" local record: Types.PlayerCache = {");
|
|
207
|
+
lines.push(" Folder = folder,");
|
|
208
|
+
for (const stat of plan.stats) {
|
|
209
|
+
lines.push(` ${stat.name} = ${stat.name},`);
|
|
210
|
+
}
|
|
211
|
+
lines.push(" }");
|
|
212
|
+
lines.push(" cache[player] = record");
|
|
213
|
+
lines.push(" return record");
|
|
214
|
+
lines.push("end");
|
|
215
|
+
lines.push("");
|
|
216
|
+
lines.push("function CacheController.Clear(player: Player)");
|
|
217
|
+
lines.push(" cache[player] = nil");
|
|
218
|
+
lines.push("end");
|
|
219
|
+
lines.push("");
|
|
220
|
+
lines.push("function CacheController.ClearAll()");
|
|
221
|
+
lines.push(" table.clear(cache)");
|
|
222
|
+
lines.push("end");
|
|
223
|
+
lines.push("");
|
|
224
|
+
lines.push("return CacheController");
|
|
225
|
+
lines.push("");
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
function emitMain(plan) {
|
|
229
|
+
const lines = [header(plan).trimEnd(), ""];
|
|
230
|
+
if (plan.roles.players) {
|
|
231
|
+
lines.push("const PlayersManager = require(script.Parent.PlayersManager)");
|
|
232
|
+
}
|
|
233
|
+
if (plan.roles.cache) {
|
|
234
|
+
lines.push("const CacheController = require(script.Parent.CacheController)");
|
|
235
|
+
}
|
|
236
|
+
if (plan.roles.domain) {
|
|
237
|
+
lines.push(`const ${plan.roles.domain} = require(script.Parent.${plan.roles.domain})`);
|
|
238
|
+
}
|
|
239
|
+
lines.push("");
|
|
240
|
+
lines.push("local Main = {}");
|
|
241
|
+
lines.push("");
|
|
242
|
+
lines.push("function Main.Start()");
|
|
243
|
+
if (plan.roles.players && plan.roles.cache) {
|
|
244
|
+
lines.push(" PlayersManager.Start(function(player: Player)");
|
|
245
|
+
lines.push(" CacheController.Ensure(player)");
|
|
246
|
+
lines.push(" end, function(player: Player)");
|
|
247
|
+
lines.push(" CacheController.Clear(player)");
|
|
248
|
+
lines.push(" end)");
|
|
249
|
+
}
|
|
250
|
+
else if (plan.roles.players) {
|
|
251
|
+
lines.push(" PlayersManager.Start(function(_player: Player) end)");
|
|
252
|
+
}
|
|
253
|
+
if (plan.roles.domain) {
|
|
254
|
+
lines.push(` ${plan.roles.domain}.Start()`);
|
|
255
|
+
}
|
|
256
|
+
if (!plan.roles.players && !plan.roles.domain) {
|
|
257
|
+
lines.push(" return");
|
|
258
|
+
}
|
|
259
|
+
lines.push("end");
|
|
260
|
+
lines.push("");
|
|
261
|
+
lines.push("function Main.Stop()");
|
|
262
|
+
if (plan.roles.domain) {
|
|
263
|
+
lines.push(` ${plan.roles.domain}.Stop()`);
|
|
264
|
+
}
|
|
265
|
+
if (plan.roles.cache) {
|
|
266
|
+
lines.push(" CacheController.ClearAll()");
|
|
267
|
+
}
|
|
268
|
+
if (plan.roles.players) {
|
|
269
|
+
lines.push(" PlayersManager.Stop()");
|
|
270
|
+
}
|
|
271
|
+
if (!plan.roles.domain && !plan.roles.cache && !plan.roles.players) {
|
|
272
|
+
lines.push(" return");
|
|
273
|
+
}
|
|
274
|
+
lines.push("end");
|
|
275
|
+
lines.push("");
|
|
276
|
+
lines.push("return Main");
|
|
277
|
+
lines.push("");
|
|
278
|
+
return lines.join("\n");
|
|
279
|
+
}
|
|
280
|
+
function emitBootstrap(plan) {
|
|
281
|
+
return `${header(plan)}require(script.Main):Start()
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
function emitConfig(plan, ast, options) {
|
|
285
|
+
const owned = (ast.body || []).some((decl) => decl.owner);
|
|
286
|
+
if (owned) {
|
|
287
|
+
return `${header(plan).trimEnd()}\n\n${(0, emit_js_1.emit)(ast, { ...options, skipHeader: true, skipInit: true }).trimEnd()}\n`;
|
|
288
|
+
}
|
|
289
|
+
const lines = [];
|
|
290
|
+
if (plan.onlyConsts) {
|
|
291
|
+
lines.push(header(plan).trimEnd());
|
|
292
|
+
lines.push("");
|
|
293
|
+
for (const item of plan.consts) {
|
|
294
|
+
lines.push(`const ${item.name}: ${item.luau} = ${item.value}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
lines.push(header(plan).trimEnd());
|
|
299
|
+
lines.push("");
|
|
300
|
+
lines.push((0, emit_js_1.emit)(ast, { ...options, skipHeader: true, skipInit: true }).trimEnd());
|
|
301
|
+
}
|
|
302
|
+
lines.push("");
|
|
303
|
+
lines.push("return {");
|
|
304
|
+
for (const item of plan.consts) {
|
|
305
|
+
lines.push(` ${item.name} = ${item.name},`);
|
|
306
|
+
}
|
|
307
|
+
for (const decl of ast.body || []) {
|
|
308
|
+
if (decl.type === "function" && decl.name !== "init") {
|
|
309
|
+
lines.push(` ${decl.name} = ${decl.name},`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if ((ast.body || []).some((decl) => decl.type === "function" && decl.name === "init")) {
|
|
313
|
+
lines.push(" Start = init,");
|
|
314
|
+
lines.push(" Init = init,");
|
|
315
|
+
lines.push(" init = init,");
|
|
316
|
+
}
|
|
317
|
+
lines.push("}");
|
|
318
|
+
lines.push("");
|
|
319
|
+
return lines.join("\n");
|
|
320
|
+
}
|
|
321
|
+
function emitDecls(decls, options) {
|
|
322
|
+
if (!decls || decls.length === 0) {
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
return (0, emit_js_1.emit)({ type: "program", body: decls }, { ...options, skipHeader: true, skipInit: true }).trimEnd();
|
|
326
|
+
}
|
|
327
|
+
function emitDomainController(plan, ast, options) {
|
|
328
|
+
const keep = (ast.body || []).filter((decl) => {
|
|
329
|
+
return decl.type === "decl" || decl.type === "proto" || decl.type === "function" || decl.type === "expr";
|
|
330
|
+
});
|
|
331
|
+
const groups = (0, layout_js_1.organizeDecls)(keep);
|
|
332
|
+
const name = plan.roles.domain;
|
|
333
|
+
const entry = keep.find((decl) => decl.type === "function" && layout_js_1.ENTRY_FN.test(decl.name || ""));
|
|
334
|
+
const bootExprs = keep.filter((decl) => decl.type === "expr");
|
|
335
|
+
const janitors = (0, layout_js_1.janitorNames)(keep);
|
|
336
|
+
const closer = keep.find((decl) => decl.type === "function" && /^(OnClose|Cleanup|Shutdown)$/i.test(decl.name));
|
|
337
|
+
const stopLines = [];
|
|
338
|
+
if (janitors.length > 0) {
|
|
339
|
+
for (const janitor of janitors) {
|
|
340
|
+
stopLines.push(` ${janitor}:Cleanup()`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else if (closer) {
|
|
344
|
+
stopLines.push(` ${closer.name}()`);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
stopLines.push(" return");
|
|
348
|
+
}
|
|
349
|
+
const boot = entry
|
|
350
|
+
? ` ${entry.name}()`
|
|
351
|
+
: bootExprs.length
|
|
352
|
+
? emitDecls(bootExprs, options)
|
|
353
|
+
.split("\n")
|
|
354
|
+
.map((line) => (line ? `\t${line}` : line))
|
|
355
|
+
.join("\n")
|
|
356
|
+
: " return";
|
|
357
|
+
const startFn = `function ${name}.Start()\n ${name}.Stop()\n${boot}\nend`;
|
|
358
|
+
const stopFn = `function ${name}.Stop()\n${stopLines.join("\n")}\nend`;
|
|
359
|
+
const body = `${header(plan).trimEnd()}\n\n${(0, layout_js_1.emitSection)(layout_js_1.SECTION.api, emitDecls(groups.api, options))}${(0, layout_js_1.emitSection)(layout_js_1.SECTION.constants, emitDecls(groups.constants, options))}${(0, layout_js_1.emitSection)(layout_js_1.SECTION.variables, (0, layout_js_1.joinBlocks)([emitDecls(groups.variables, options), `local ${name} = {}`]))}${(0, layout_js_1.emitSection)(layout_js_1.SECTION.support, emitDecls(groups.support, options))}${(0, layout_js_1.emitSection)(layout_js_1.SECTION.principal, (0, layout_js_1.joinBlocks)([emitDecls(groups.principal, options), startFn]))}${(0, layout_js_1.emitSection)(layout_js_1.SECTION.cleanup, (0, layout_js_1.joinBlocks)([emitDecls(groups.cleanup, options), stopFn]))}${(0, layout_js_1.emitSection)(layout_js_1.SECTION.returns, `return ${name}`)}`;
|
|
360
|
+
return body;
|
|
361
|
+
}
|
|
362
|
+
function emitModule(plan, ast, options) {
|
|
363
|
+
return emitConfig(plan, ast, options);
|
|
364
|
+
}
|
|
365
|
+
function emitScriptMeta(runContext) {
|
|
366
|
+
return `${JSON.stringify({
|
|
367
|
+
properties: {
|
|
368
|
+
RunContext: `Enum.RunContext.${runContext}`,
|
|
369
|
+
},
|
|
370
|
+
}, null, "\t")}\n`;
|
|
371
|
+
}
|
|
372
|
+
function serviceBootName(plan) {
|
|
373
|
+
if (plan.isClient) {
|
|
374
|
+
return "init.client.luau";
|
|
375
|
+
}
|
|
376
|
+
return "init.server.luau";
|
|
377
|
+
}
|
|
378
|
+
function serviceFiles(plan, ast, options, dir) {
|
|
379
|
+
const folder = `${dir}/${plan.serviceName}`;
|
|
380
|
+
const boot = serviceBootName(plan);
|
|
381
|
+
const files = [{ name: `${folder}/${boot}`, contents: emitBootstrap(plan) }];
|
|
382
|
+
if (!plan.isClient && plan.runContext) {
|
|
383
|
+
files.push({ name: `${folder}/init.meta.json`, contents: emitScriptMeta(plan.runContext) });
|
|
384
|
+
}
|
|
385
|
+
files.push({ name: `${folder}/Main.luau`, contents: emitMain(plan) });
|
|
386
|
+
if (plan.roles.players) {
|
|
387
|
+
files.push({ name: `${folder}/PlayersManager.luau`, contents: emitPlayersManager(plan) });
|
|
388
|
+
}
|
|
389
|
+
if (plan.roles.cache) {
|
|
390
|
+
files.push({ name: `${folder}/CacheController.luau`, contents: emitCacheController(plan) });
|
|
391
|
+
}
|
|
392
|
+
if (plan.roles.domain) {
|
|
393
|
+
files.push({ name: `${folder}/${plan.roles.domain}.luau`, contents: emitDomainController(plan, ast, options) });
|
|
394
|
+
}
|
|
395
|
+
files.push({ name: `${folder}/${plan.typesName}.luau`, contents: emitTypes(plan) });
|
|
396
|
+
return files;
|
|
397
|
+
}
|
|
398
|
+
function serviceFolderStale(plan, rel) {
|
|
399
|
+
const dir = node_path_1.default.posix.dirname(rel).replace(/^\.$/, "");
|
|
400
|
+
const serviceDir = dir && dir !== "." ? dir : plan.isClient ? "client" : "server";
|
|
401
|
+
const folder = `${serviceDir}/${plan.serviceName}`;
|
|
402
|
+
return [
|
|
403
|
+
folder,
|
|
404
|
+
`${folder}/init.luau`,
|
|
405
|
+
`${folder}/init.server.luau`,
|
|
406
|
+
`${folder}/init.client.luau`,
|
|
407
|
+
`${folder}/init.meta.json`,
|
|
408
|
+
`${folder}/Main.luau`,
|
|
409
|
+
`${folder}/PlayersManager.luau`,
|
|
410
|
+
`${folder}/CacheController.luau`,
|
|
411
|
+
`${folder}/${plan.typesName}.luau`,
|
|
412
|
+
plan.roles && plan.roles.domain ? `${folder}/${plan.roles.domain}.luau` : null,
|
|
413
|
+
].filter(Boolean);
|
|
414
|
+
}
|
|
415
|
+
function planOutput(ast, fileName, options = {}) {
|
|
416
|
+
const plan = (0, understand_js_1.analyze)(ast, fileName);
|
|
417
|
+
plan.strict = options.strict === true;
|
|
418
|
+
const rel = (options.relativeName || fileName).replace(/\\/g, "/");
|
|
419
|
+
const dir = node_path_1.default.dirname(rel).replace(/^\.$/, "");
|
|
420
|
+
const outDir = dir && dir !== "." ? dir : "";
|
|
421
|
+
const prefix = outDir ? `${outDir}/` : "";
|
|
422
|
+
const stale = serviceFolderStale(plan, rel);
|
|
423
|
+
if (options.siblingHeader) {
|
|
424
|
+
return {
|
|
425
|
+
kind: "module",
|
|
426
|
+
plan,
|
|
427
|
+
files: [{ name: (0, preprocess_js_1.implOutName)(rel), contents: emitModule(plan, ast, { ...options, skipInit: true }) }],
|
|
428
|
+
stale: [],
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
if (options.architecture === true) {
|
|
432
|
+
if (plan.kind === "legacy") {
|
|
433
|
+
return { kind: "legacy", plan, files: null, stale: [], outName: (0, understand_js_1.legacyOutName)(rel) };
|
|
434
|
+
}
|
|
435
|
+
if (plan.kind === "service") {
|
|
436
|
+
const serviceDir = outDir || (plan.isClient ? "client" : "server");
|
|
437
|
+
const folder = `${serviceDir}/${plan.serviceName}`;
|
|
438
|
+
const serviceStale = plan.isClient
|
|
439
|
+
? [`${folder}/init.luau`, `${folder}/init.server.luau`, `${folder}/init.meta.json`]
|
|
440
|
+
: [`${folder}/init.luau`, `${folder}/init.client.luau`];
|
|
441
|
+
return {
|
|
442
|
+
kind: "service",
|
|
443
|
+
plan,
|
|
444
|
+
files: serviceFiles(plan, ast, options, serviceDir),
|
|
445
|
+
stale: serviceStale,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (plan.kind === "config" || plan.kind === "module") {
|
|
449
|
+
return {
|
|
450
|
+
kind: plan.kind,
|
|
451
|
+
plan,
|
|
452
|
+
files: [{ name: `${prefix}${plan.serviceName}.luau`, contents: emitModule(plan, ast, options) }],
|
|
453
|
+
stale: [rel.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau")],
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return { kind: "flat", plan, files: null, stale: [], outName: (0, understand_js_1.modernScriptOutName)(rel) };
|
|
457
|
+
}
|
|
458
|
+
if (plan.kind === "legacy") {
|
|
459
|
+
return { kind: "legacy", plan, files: null, stale, outName: (0, understand_js_1.legacyOutName)(rel) };
|
|
460
|
+
}
|
|
461
|
+
if (plan.kind === "config" || plan.kind === "module") {
|
|
462
|
+
return {
|
|
463
|
+
kind: plan.kind,
|
|
464
|
+
plan,
|
|
465
|
+
files: [{ name: `${prefix}${plan.serviceName}.luau`, contents: emitModule(plan, ast, options) }],
|
|
466
|
+
stale: [rel.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau"), ...stale],
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const outName = (0, understand_js_1.modernScriptOutName)(rel);
|
|
470
|
+
const stripped = rel.replace(/\.(server|client|plugin)\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
|
|
471
|
+
const extraStale = stripped !== outName ? [stripped] : [];
|
|
472
|
+
return { kind: "flat", plan, files: null, stale: [...stale, ...extraStale], outName };
|
|
473
|
+
}
|
package/generated/ast.js
ADDED
package/generated/cli.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.program = exports.init = exports.watch = void 0;
|
|
7
|
+
exports.build = build;
|
|
8
|
+
exports.dispatch = dispatch;
|
|
9
|
+
const commander_1 = require("commander");
|
|
10
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const package_info_js_1 = require("./package-info.js");
|
|
13
|
+
const lsp_js_1 = require("./lsp.js");
|
|
14
|
+
const preprocess_js_1 = require("./preprocess.js");
|
|
15
|
+
const intellisense_js_1 = require("./intellisense.js");
|
|
16
|
+
const rojo_mapper_js_1 = require("./utils/rojo-mapper.js");
|
|
17
|
+
const process_orchestrator_js_1 = require("./utils/process-orchestrator.js");
|
|
18
|
+
const project_js_1 = require("./utils/project.js");
|
|
19
|
+
Object.defineProperty(exports, "init", { enumerable: true, get: function () { return project_js_1.init; } });
|
|
20
|
+
Object.defineProperty(exports, "watch", { enumerable: true, get: function () { return project_js_1.watch; } });
|
|
21
|
+
const transpile_js_1 = require("./transpile.js");
|
|
22
|
+
const program = new commander_1.Command();
|
|
23
|
+
exports.program = program;
|
|
24
|
+
program
|
|
25
|
+
.name("cluaupp")
|
|
26
|
+
.description("Definitive transpiler from C++ subset to structured Luau")
|
|
27
|
+
.version(package_info_js_1.pkg.version, "-v, --version", "print version")
|
|
28
|
+
.showHelpAfterError()
|
|
29
|
+
.action(() => {
|
|
30
|
+
program.outputHelp();
|
|
31
|
+
});
|
|
32
|
+
program
|
|
33
|
+
.command("init")
|
|
34
|
+
.description("create a game (src/server, src/client, src/shared)")
|
|
35
|
+
.argument("[folder]", "destination folder", ".")
|
|
36
|
+
.action(async (folder) => {
|
|
37
|
+
try {
|
|
38
|
+
await (0, project_js_1.init)(node_path_1.default.resolve(process.cwd(), folder));
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(err instanceof Error ? err.message : err);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
program
|
|
46
|
+
.command("build")
|
|
47
|
+
.description("Transpila um arquivo, diretório ou projeto C++ para Luau")
|
|
48
|
+
.argument("[folder]", "project folder", ".")
|
|
49
|
+
.option("-i, --input <path>", "Arquivo ou diretório C++ de entrada")
|
|
50
|
+
.option("-o, --output <path>", "Arquivo ou diretório Luau de saída")
|
|
51
|
+
.option("-r, --rojo <path>", "Caminho para o default.project.json do Rojo", "./default.project.json")
|
|
52
|
+
.option("--strict", "Emitir --!strict")
|
|
53
|
+
.option("--format", "Rodar StyLua no output")
|
|
54
|
+
.option("--analyze", "Rodar luau-analyze no output")
|
|
55
|
+
.action(async (folder, options) => {
|
|
56
|
+
if (options.input) {
|
|
57
|
+
await buildInput(options);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
(0, project_js_1.build)(node_path_1.default.resolve(process.cwd(), folder), {
|
|
62
|
+
format: options.format === true,
|
|
63
|
+
analyze: options.analyze === true,
|
|
64
|
+
rojo: options.rojo,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.error(err instanceof Error ? err.message : err);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
program
|
|
73
|
+
.command("watch")
|
|
74
|
+
.description("rebuild on save")
|
|
75
|
+
.argument("[folder]", "project folder", ".")
|
|
76
|
+
.option("-r, --rojo <path>", "Caminho para o default.project.json do Rojo", "./default.project.json")
|
|
77
|
+
.option("--format", "Rodar StyLua no output")
|
|
78
|
+
.action((folder, options) => {
|
|
79
|
+
try {
|
|
80
|
+
(0, project_js_1.watch)(node_path_1.default.resolve(process.cwd(), folder), {
|
|
81
|
+
format: options.format === true,
|
|
82
|
+
rojo: options.rojo,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error(err instanceof Error ? err.message : err);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
program
|
|
91
|
+
.command("lsp")
|
|
92
|
+
.description("Cluaupp subset diagnostics over stdio (JSON-RPC). C++ completion is clangd.")
|
|
93
|
+
.argument("[folder]", "project folder", ".")
|
|
94
|
+
.action((folder) => {
|
|
95
|
+
(0, lsp_js_1.start)({ projectRoot: node_path_1.default.resolve(process.cwd(), folder) });
|
|
96
|
+
});
|
|
97
|
+
program
|
|
98
|
+
.command("intellisense")
|
|
99
|
+
.alias("intelisense")
|
|
100
|
+
.description("install LLVM clangd and write compile_commands.json")
|
|
101
|
+
.argument("[folder]", "project folder", ".")
|
|
102
|
+
.action(async (folder) => {
|
|
103
|
+
try {
|
|
104
|
+
const root = node_path_1.default.resolve(process.cwd(), folder);
|
|
105
|
+
(0, intellisense_js_1.syncEditorSupport)(root, (0, project_js_1.loadConfig)(root));
|
|
106
|
+
const installed = await (0, intellisense_js_1.installEditorSupport)();
|
|
107
|
+
console.log("cluaupp: compile_commands.json, .clangd, and .vscode updated in", root);
|
|
108
|
+
console.log("cluaupp: C++ completion is clangd (include/cluaupp/roblox.hpp)");
|
|
109
|
+
if (installed.clangd || installed.llvm) {
|
|
110
|
+
console.log("cluaupp: reload Cursor (Ctrl+Shift+P → Developer: Reload Window)");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error(err instanceof Error ? err.message : err);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
async function buildInput(options) {
|
|
119
|
+
if (!options.input || !options.output) {
|
|
120
|
+
console.error("[Erro] --input e --output são obrigatórios neste modo.");
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const inputPath = node_path_1.default.resolve(options.input);
|
|
124
|
+
const outputPath = node_path_1.default.resolve(options.output);
|
|
125
|
+
try {
|
|
126
|
+
await promises_1.default.stat(inputPath);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
console.error(`[Erro] O caminho de entrada especificado não existe: ${inputPath}`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
console.log("🏁 Inicializando Pipeline do Compilador Cluaupp V2...");
|
|
133
|
+
const mapper = new rojo_mapper_js_1.RojoMapper(node_path_1.default.resolve(options.rojo), node_path_1.default.dirname(inputPath));
|
|
134
|
+
await mapper.load();
|
|
135
|
+
const inputs = (await promises_1.default.stat(inputPath)).isDirectory()
|
|
136
|
+
? (0, project_js_1.collectCpp)(inputPath)
|
|
137
|
+
: [inputPath];
|
|
138
|
+
if (inputs.length === 0) {
|
|
139
|
+
console.error("no .cpp/.h/.hpp files in", inputPath);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
const outputIsFile = /\.luau$/i.test(outputPath);
|
|
143
|
+
if (outputIsFile && inputs.length > 1) {
|
|
144
|
+
console.error("[Erro] --output deve ser um diretório quando a entrada contém vários arquivos.");
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
for (const file of inputs) {
|
|
148
|
+
const sourceCode = await promises_1.default.readFile(file, "utf8");
|
|
149
|
+
const rel = node_path_1.default.basename(file);
|
|
150
|
+
const compiled = (0, transpile_js_1.transpileSource)(sourceCode, rel, {
|
|
151
|
+
strict: options.strict === true,
|
|
152
|
+
filePath: file,
|
|
153
|
+
relativeName: rel,
|
|
154
|
+
srcDir: node_path_1.default.dirname(file),
|
|
155
|
+
includeDirs: [node_path_1.default.dirname(file)],
|
|
156
|
+
}, mapper);
|
|
157
|
+
const dest = outputIsFile ? outputPath : node_path_1.default.join(outputPath, node_path_1.default.basename((0, preprocess_js_1.toLuauPath)(rel)));
|
|
158
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(dest), { recursive: true });
|
|
159
|
+
const luau = compiled.files[0]?.contents ?? "";
|
|
160
|
+
await promises_1.default.writeFile(dest, luau, "utf8");
|
|
161
|
+
if (options.format === true) {
|
|
162
|
+
await process_orchestrator_js_1.ProcessOrchestrator.formatWithStyLua(dest);
|
|
163
|
+
}
|
|
164
|
+
if (options.analyze === true) {
|
|
165
|
+
const analysisReport = await process_orchestrator_js_1.ProcessOrchestrator.analyzeWithLuau(dest);
|
|
166
|
+
if (analysisReport) {
|
|
167
|
+
console.log("\nRelatório de Análise Estática do Luau:\n", analysisReport);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
console.log(`Transpilação concluída! ${file} → ${dest}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function build(root, options = {}) {
|
|
174
|
+
return (0, project_js_1.build)(root, options);
|
|
175
|
+
}
|
|
176
|
+
function dispatch(args) {
|
|
177
|
+
return program.parseAsync(["node", "cluaupp", ...args]);
|
|
178
|
+
}
|
|
179
|
+
function isMain() {
|
|
180
|
+
const entry = process.argv[1];
|
|
181
|
+
if (!entry) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return ["cli.js", "cli.ts", "cluaupp.js", "cluau.js"].includes(node_path_1.default.basename(entry));
|
|
185
|
+
}
|
|
186
|
+
if (isMain()) {
|
|
187
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
188
|
+
console.error(err instanceof Error ? err.message : err);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
});
|
|
191
|
+
}
|