cluaupp 0.1.2 → 0.1.4
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 +20 -1
- package/README.md +5 -8
- package/docs/README.md +13 -10
- package/docs/architecture.md +18 -11
- package/docs/cli.md +2 -2
- package/docs/config.md +1 -1
- package/docs/cpp-advanced.md +10 -6
- package/docs/cpp-organization.md +3 -1
- package/docs/cpp-safety.md +11 -7
- package/docs/cpp-types.md +1 -1
- package/docs/examples/_category_.json +5 -0
- package/docs/examples/combat.md +239 -0
- package/docs/examples/data-boot.md +98 -0
- package/docs/examples/hud.md +96 -0
- package/docs/examples/index.md +43 -0
- package/docs/examples/leaderstats.md +140 -0
- package/docs/examples/shop.md +122 -0
- package/docs/examples/sword.md +108 -0
- package/docs/getting-started.md +12 -6
- package/docs/intellisense.md +31 -0
- package/docs/intro.md +2 -2
- package/docs/libraries/_category_.json +5 -0
- package/docs/libraries/dataservice.md +104 -0
- package/docs/libraries/index.md +22 -0
- package/docs/libraries/janitor.md +65 -0
- package/docs/libraries/more.md +79 -0
- package/docs/libraries/net.md +35 -0
- package/docs/libraries/promise.md +27 -0
- package/docs/oop/_category_.json +5 -0
- package/docs/oop/file-tags.md +49 -0
- package/docs/oop/index.md +22 -0
- package/docs/oop/modules.md +86 -0
- package/docs/oop/services.md +83 -0
- package/docs/print-cout.md +91 -0
- package/docs/syntax.md +59 -5
- package/editors/vscode/extension.js +146 -0
- package/editors/vscode/package.json +25 -0
- package/include/cluaupp/libs/janitor.hpp +7 -3
- package/include/cluaupp/roblox.hpp +19 -0
- package/package.json +66 -65
- package/runtime/Janitor/init.luau +4 -34
- package/src/architecture.js +108 -48
- package/src/cli.js +85 -10
- package/src/client/init.client.cpp +5 -0
- package/src/compile.js +20 -4
- package/src/editor-install.js +218 -0
- package/src/emit.js +320 -8
- package/src/intellisense.js +1368 -0
- package/src/layout.js +129 -0
- package/src/lex.js +17 -9
- package/src/libs.js +95 -16
- package/src/lsp.js +227 -0
- package/src/parse.js +187 -6
- package/src/preprocess.js +71 -3
- package/src/server/leaderstats.server.cpp +28 -0
- package/src/shared/config.cpp +5 -0
- package/src/shared/config.h +3 -0
- package/src/understand.js +64 -6
- package/templates/game/.clangd +11 -0
- package/templates/game/.vscode/c_cpp_properties.json +6 -2
- package/templates/game/.vscode/extensions.json +6 -0
- package/templates/game/.vscode/settings.json +16 -2
- package/templates/game/compile_flags.txt +2 -0
- package/docs/libraries.md +0 -80
package/src/architecture.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const { emit } = require("./emit");
|
|
5
5
|
const { collectLibraries, insertRequires, requireCluauppLib } = require("./libs");
|
|
6
|
+
const { organizeDecls, janitorNames, emitSection, joinBlocks, SECTION, ENTRY_FN } = require("./layout");
|
|
6
7
|
const {
|
|
7
8
|
VALUE_CLASSES,
|
|
8
9
|
analyze,
|
|
@@ -27,9 +28,9 @@ function cluauppLib(name) {
|
|
|
27
28
|
|
|
28
29
|
function emitTypes(plan) {
|
|
29
30
|
const lines = [header(plan).trimEnd(), ""];
|
|
30
|
-
lines.push('
|
|
31
|
-
lines.push(`
|
|
32
|
-
lines.push(`
|
|
31
|
+
lines.push('const ReplicatedStorage = game:GetService("ReplicatedStorage")');
|
|
32
|
+
lines.push(`const Occlude = ${cluauppLib("Occlude")}`);
|
|
33
|
+
lines.push(`const ArrayIndexer = ${cluauppLib("ArrayIndexer")}`);
|
|
33
34
|
lines.push("");
|
|
34
35
|
|
|
35
36
|
if (plan.stats.length > 0) {
|
|
@@ -60,6 +61,7 @@ function emitTypes(plan) {
|
|
|
60
61
|
manifest.push(" Get: (player: Player) -> PlayerCache?,");
|
|
61
62
|
manifest.push(" Ensure: (player: Player) -> PlayerCache,");
|
|
62
63
|
manifest.push(" Clear: (player: Player) -> (),");
|
|
64
|
+
manifest.push(" ClearAll: () -> (),");
|
|
63
65
|
manifest.push(" },");
|
|
64
66
|
}
|
|
65
67
|
if (plan.roles.players) {
|
|
@@ -100,23 +102,26 @@ function emitTypes(plan) {
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
function emitPlayersManager(plan) {
|
|
103
|
-
return `${header(plan)}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
return `${header(plan).trimEnd()}
|
|
106
|
+
|
|
107
|
+
const Players = game:GetService("Players")
|
|
108
|
+
const ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
109
|
+
const Janitor = ${cluauppLib("Janitor")}
|
|
108
110
|
|
|
111
|
+
type Janitor = Janitor.Janitor
|
|
109
112
|
export type PlayerHandler = (player: Player) -> ()
|
|
110
113
|
|
|
111
114
|
local PlayersManager = {}
|
|
112
115
|
local lifetime: Janitor? = nil
|
|
116
|
+
local leaveHandler: PlayerHandler? = nil
|
|
113
117
|
|
|
114
118
|
function PlayersManager.Start(onPlayer: PlayerHandler, onLeave: PlayerHandler?)
|
|
115
119
|
PlayersManager.Stop()
|
|
116
120
|
local janitor = Janitor.new()
|
|
117
121
|
lifetime = janitor
|
|
122
|
+
leaveHandler = onLeave
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
const function accept(player: Player)
|
|
120
125
|
onPlayer(player)
|
|
121
126
|
end
|
|
122
127
|
|
|
@@ -134,6 +139,13 @@ function PlayersManager.Stop()
|
|
|
134
139
|
if lifetime == nil then
|
|
135
140
|
return
|
|
136
141
|
end
|
|
142
|
+
local onLeave = leaveHandler
|
|
143
|
+
leaveHandler = nil
|
|
144
|
+
if onLeave then
|
|
145
|
+
for _, player in Players:GetPlayers() do
|
|
146
|
+
onLeave(player)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
137
149
|
lifetime:Cleanup()
|
|
138
150
|
lifetime = nil
|
|
139
151
|
end
|
|
@@ -146,11 +158,11 @@ function emitCacheController(plan) {
|
|
|
146
158
|
const types = plan.typesName;
|
|
147
159
|
const folder = plan.folderName || "leaderstats";
|
|
148
160
|
const lines = [header(plan).trimEnd(), ""];
|
|
149
|
-
lines.push(`
|
|
161
|
+
lines.push(`const Types = require(script.Parent.${types})`);
|
|
150
162
|
lines.push("");
|
|
151
|
-
lines.push(`
|
|
152
|
-
lines.push("local cache: { [Player]: Types.PlayerCache } = {}");
|
|
163
|
+
lines.push(`const FOLDER_NAME = "${folder}"`);
|
|
153
164
|
lines.push("");
|
|
165
|
+
lines.push("local cache: { [Player]: Types.PlayerCache } = {}");
|
|
154
166
|
lines.push("local CacheController = {}");
|
|
155
167
|
lines.push("");
|
|
156
168
|
lines.push("function CacheController.Get(player: Player): Types.PlayerCache?");
|
|
@@ -201,6 +213,10 @@ function emitCacheController(plan) {
|
|
|
201
213
|
lines.push(" cache[player] = nil");
|
|
202
214
|
lines.push("end");
|
|
203
215
|
lines.push("");
|
|
216
|
+
lines.push("function CacheController.ClearAll()");
|
|
217
|
+
lines.push(" table.clear(cache)");
|
|
218
|
+
lines.push("end");
|
|
219
|
+
lines.push("");
|
|
204
220
|
lines.push("return CacheController");
|
|
205
221
|
lines.push("");
|
|
206
222
|
return lines.join("\n");
|
|
@@ -209,13 +225,13 @@ function emitCacheController(plan) {
|
|
|
209
225
|
function emitMain(plan) {
|
|
210
226
|
const lines = [header(plan).trimEnd(), ""];
|
|
211
227
|
if (plan.roles.players) {
|
|
212
|
-
lines.push("
|
|
228
|
+
lines.push("const PlayersManager = require(script.Parent.PlayersManager)");
|
|
213
229
|
}
|
|
214
230
|
if (plan.roles.cache) {
|
|
215
|
-
lines.push("
|
|
231
|
+
lines.push("const CacheController = require(script.Parent.CacheController)");
|
|
216
232
|
}
|
|
217
233
|
if (plan.roles.domain) {
|
|
218
|
-
lines.push(`
|
|
234
|
+
lines.push(`const ${plan.roles.domain} = require(script.Parent.${plan.roles.domain})`);
|
|
219
235
|
}
|
|
220
236
|
lines.push("");
|
|
221
237
|
lines.push("local Main = {}");
|
|
@@ -242,9 +258,15 @@ function emitMain(plan) {
|
|
|
242
258
|
if (plan.roles.domain) {
|
|
243
259
|
lines.push(` ${plan.roles.domain}.Stop()`);
|
|
244
260
|
}
|
|
261
|
+
if (plan.roles.cache) {
|
|
262
|
+
lines.push(" CacheController.ClearAll()");
|
|
263
|
+
}
|
|
245
264
|
if (plan.roles.players) {
|
|
246
265
|
lines.push(" PlayersManager.Stop()");
|
|
247
266
|
}
|
|
267
|
+
if (!plan.roles.domain && !plan.roles.cache && !plan.roles.players) {
|
|
268
|
+
lines.push(" return");
|
|
269
|
+
}
|
|
248
270
|
lines.push("end");
|
|
249
271
|
lines.push("");
|
|
250
272
|
lines.push("return Main");
|
|
@@ -258,6 +280,10 @@ function emitBootstrap(plan) {
|
|
|
258
280
|
}
|
|
259
281
|
|
|
260
282
|
function emitConfig(plan, ast, options) {
|
|
283
|
+
const owned = (ast.body || []).some((decl) => decl.owner);
|
|
284
|
+
if (owned) {
|
|
285
|
+
return `${header(plan).trimEnd()}\n\n${emit(ast, { ...options, skipHeader: true, skipInit: true }).trimEnd()}\n`;
|
|
286
|
+
}
|
|
261
287
|
const lines = [];
|
|
262
288
|
if (plan.onlyConsts) {
|
|
263
289
|
lines.push(header(plan).trimEnd());
|
|
@@ -282,48 +308,54 @@ function emitConfig(plan, ast, options) {
|
|
|
282
308
|
}
|
|
283
309
|
if ((ast.body || []).some((decl) => decl.type === "function" && decl.name === "init")) {
|
|
284
310
|
lines.push(" Start = init,");
|
|
311
|
+
lines.push(" Init = init,");
|
|
312
|
+
lines.push(" init = init,");
|
|
285
313
|
}
|
|
286
314
|
lines.push("}");
|
|
287
315
|
lines.push("");
|
|
288
316
|
return lines.join("\n");
|
|
289
317
|
}
|
|
290
318
|
|
|
319
|
+
function emitDecls(decls, options) {
|
|
320
|
+
if (!decls || decls.length === 0) {
|
|
321
|
+
return "";
|
|
322
|
+
}
|
|
323
|
+
return emit({ type: "program", body: decls }, { ...options, skipHeader: true, skipInit: true }).trimEnd();
|
|
324
|
+
}
|
|
325
|
+
|
|
291
326
|
function emitDomainController(plan, ast, options) {
|
|
292
|
-
const absorbCache = plan.roles.cache;
|
|
293
327
|
const keep = (ast.body || []).filter((decl) => {
|
|
294
|
-
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
if (decl.type !== "function") {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
if (decl.name === "init") {
|
|
301
|
-
return true;
|
|
302
|
-
}
|
|
303
|
-
const classified = plan.functions.find((item) => item.name === decl.name);
|
|
304
|
-
if (absorbCache && classified && classified.absorb === "cache") {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
return true;
|
|
328
|
+
return decl.type === "decl" || decl.type === "proto" || decl.type === "function" || decl.type === "expr";
|
|
308
329
|
});
|
|
330
|
+
const groups = organizeDecls(keep);
|
|
309
331
|
const subset = { type: "program", body: keep };
|
|
310
|
-
const code = emit(subset, { ...options, skipHeader: true, skipInit: true }).trimEnd();
|
|
311
332
|
const name = plan.roles.domain;
|
|
312
333
|
const libs = collectLibraries(options.source || "", subset);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return
|
|
326
|
-
|
|
334
|
+
const entry = keep.find((decl) => decl.type === "function" && ENTRY_FN.test(decl.name || ""));
|
|
335
|
+
const bootExprs = keep.filter((decl) => decl.type === "expr");
|
|
336
|
+
const janitors = janitorNames(keep);
|
|
337
|
+
const closer = keep.find((decl) => decl.type === "function" && /^(OnClose|Cleanup|Shutdown)$/i.test(decl.name));
|
|
338
|
+
const stopLines = [];
|
|
339
|
+
if (janitors.length > 0) {
|
|
340
|
+
for (const janitor of janitors) {
|
|
341
|
+
stopLines.push(` ${janitor}:Cleanup()`);
|
|
342
|
+
}
|
|
343
|
+
} else if (closer) {
|
|
344
|
+
stopLines.push(` ${closer.name}()`);
|
|
345
|
+
} else {
|
|
346
|
+
stopLines.push(" return");
|
|
347
|
+
}
|
|
348
|
+
const boot = entry
|
|
349
|
+
? ` ${entry.name}()`
|
|
350
|
+
: bootExprs.length
|
|
351
|
+
? emitDecls(bootExprs, options)
|
|
352
|
+
.split("\n")
|
|
353
|
+
.map((line) => (line ? `\t${line}` : line))
|
|
354
|
+
.join("\n")
|
|
355
|
+
: " return";
|
|
356
|
+
const startFn = `function ${name}.Start()\n ${name}.Stop()\n${boot}\nend`;
|
|
357
|
+
const stopFn = `function ${name}.Stop()\n${stopLines.join("\n")}\nend`;
|
|
358
|
+
const body = `${header(plan).trimEnd()}\n\n${emitSection(SECTION.api, emitDecls(groups.api, options))}${emitSection(SECTION.constants, emitDecls(groups.constants, options))}${emitSection(SECTION.variables, joinBlocks([emitDecls(groups.variables, options), `local ${name} = {}`]))}${emitSection(SECTION.support, emitDecls(groups.support, options))}${emitSection(SECTION.principal, joinBlocks([emitDecls(groups.principal, options), startFn]))}${emitSection(SECTION.cleanup, joinBlocks([emitDecls(groups.cleanup, options), stopFn]))}${emitSection(SECTION.returns, `return ${name}`)}`;
|
|
327
359
|
return insertRequires(body, libs);
|
|
328
360
|
}
|
|
329
361
|
|
|
@@ -331,10 +363,34 @@ function emitModule(plan, ast, options) {
|
|
|
331
363
|
return emitConfig(plan, ast, options);
|
|
332
364
|
}
|
|
333
365
|
|
|
366
|
+
function emitScriptMeta(runContext) {
|
|
367
|
+
return `${JSON.stringify(
|
|
368
|
+
{
|
|
369
|
+
className: "Script",
|
|
370
|
+
properties: {
|
|
371
|
+
RunContext: `Enum.RunContext.${runContext}`,
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
null,
|
|
375
|
+
"\t",
|
|
376
|
+
)}\n`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function serviceBootName(plan) {
|
|
380
|
+
if (plan.isClient) {
|
|
381
|
+
return "init.client.luau";
|
|
382
|
+
}
|
|
383
|
+
return "init.luau";
|
|
384
|
+
}
|
|
385
|
+
|
|
334
386
|
function serviceFiles(plan, ast, options, dir) {
|
|
335
387
|
const folder = `${dir}/${plan.serviceName}`;
|
|
336
|
-
const boot = plan
|
|
337
|
-
const files = [{ name: `${folder}/${boot}`, contents: emitBootstrap(plan) }
|
|
388
|
+
const boot = serviceBootName(plan);
|
|
389
|
+
const files = [{ name: `${folder}/${boot}`, contents: emitBootstrap(plan) }];
|
|
390
|
+
if (!plan.isClient && plan.runContext) {
|
|
391
|
+
files.push({ name: `${folder}/init.meta.json`, contents: emitScriptMeta(plan.runContext) });
|
|
392
|
+
}
|
|
393
|
+
files.push({ name: `${folder}/Main.luau`, contents: emitMain(plan) });
|
|
338
394
|
if (plan.roles.players) {
|
|
339
395
|
files.push({ name: `${folder}/PlayersManager.luau`, contents: emitPlayersManager(plan) });
|
|
340
396
|
}
|
|
@@ -365,11 +421,15 @@ function planOutput(ast, fileName, options = {}) {
|
|
|
365
421
|
|
|
366
422
|
if (plan.kind === "service") {
|
|
367
423
|
const serviceDir = outDir || (plan.isClient ? "client" : "server");
|
|
424
|
+
const folder = `${serviceDir}/${plan.serviceName}`;
|
|
425
|
+
const stale = plan.isClient
|
|
426
|
+
? [`${folder}/init.luau`, `${folder}/init.server.luau`, `${folder}/init.meta.json`]
|
|
427
|
+
: [`${folder}/init.server.luau`, `${folder}/init.client.luau`];
|
|
368
428
|
return {
|
|
369
429
|
kind: "service",
|
|
370
430
|
plan,
|
|
371
431
|
files: serviceFiles(plan, ast, options, serviceDir),
|
|
372
|
-
stale
|
|
432
|
+
stale,
|
|
373
433
|
};
|
|
374
434
|
}
|
|
375
435
|
|
package/src/cli.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const { compileService } = require("./compile");
|
|
6
|
-
const { isSourceFile, isHeaderFile, toLuauPath } = require("./preprocess");
|
|
6
|
+
const { isSourceFile, isHeaderFile, toLuauPath, siblingImplementation } = require("./preprocess");
|
|
7
|
+
const { syncEditorSupport, installEditorSupport } = require("./intellisense");
|
|
7
8
|
const pkg = require("../package.json");
|
|
8
9
|
|
|
9
10
|
function printHelp() {
|
|
@@ -13,6 +14,8 @@ Usage:
|
|
|
13
14
|
cluaupp init [folder] create a game (src/server, src/client, src/shared)
|
|
14
15
|
cluaupp build [folder] transpile src → out (PascalCase services)
|
|
15
16
|
cluaupp watch [folder] rebuild on save
|
|
17
|
+
cluaupp lsp [folder] language server (stdio JSON-RPC)
|
|
18
|
+
cluaupp intellisense install Cursor/VS Code IntelliSense
|
|
16
19
|
cluaupp --version print version
|
|
17
20
|
cluaupp --help this help
|
|
18
21
|
|
|
@@ -144,7 +147,7 @@ function collectFiles(dir, files = []) {
|
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
function siblingCpp(file) {
|
|
147
|
-
return file.replace(/\.(h|hpp|hh)$/i, ".cpp");
|
|
150
|
+
return siblingImplementation(file) || file.replace(/\.(h|hpp|hh)$/i, ".cpp");
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
function resolveKey(file) {
|
|
@@ -238,6 +241,7 @@ function compileProject(root, config) {
|
|
|
238
241
|
outName: toLuauPath(rel),
|
|
239
242
|
architecture: config.architecture !== false,
|
|
240
243
|
includeDirs: [path.dirname(file), srcDir, path.join(root, "include")],
|
|
244
|
+
srcDir,
|
|
241
245
|
});
|
|
242
246
|
jobs.push({ rel, files: result.files, stale: result.stale || [] });
|
|
243
247
|
} catch (err) {
|
|
@@ -293,12 +297,8 @@ function ensureVendor(root) {
|
|
|
293
297
|
function build(root, options = {}) {
|
|
294
298
|
const exitOnError = options.exitOnError !== false;
|
|
295
299
|
const holdOnError = options.holdOnError === true;
|
|
296
|
-
const syncVendor = options.syncVendor === true;
|
|
297
300
|
const config = loadConfig(root);
|
|
298
|
-
if (syncVendor) {
|
|
299
|
-
copyRuntime(root);
|
|
300
|
-
copyHeaders(root);
|
|
301
|
-
} else {
|
|
301
|
+
if (options.syncVendor !== false) {
|
|
302
302
|
ensureVendor(root);
|
|
303
303
|
}
|
|
304
304
|
const srcDir = path.join(root, config.rootDir);
|
|
@@ -320,6 +320,11 @@ function build(root, options = {}) {
|
|
|
320
320
|
}
|
|
321
321
|
if (holdOnError) {
|
|
322
322
|
console.error(`cluaupp: out not updated (${compiled.errors.length} compile error${compiled.errors.length === 1 ? "" : "s"})`);
|
|
323
|
+
try {
|
|
324
|
+
syncEditorSupport(root, config);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error("cluaupp: intellisense sync failed", err.message);
|
|
327
|
+
}
|
|
323
328
|
return { failed: compiled.errors.length, written: new Set() };
|
|
324
329
|
}
|
|
325
330
|
}
|
|
@@ -327,24 +332,74 @@ function build(root, options = {}) {
|
|
|
327
332
|
const written = writeJobs(root, config, compiled.jobs);
|
|
328
333
|
const failedPrefixes = compiled.errors.flatMap((err) => err.prefixes);
|
|
329
334
|
pruneOut(root, config, written, failedPrefixes);
|
|
335
|
+
try {
|
|
336
|
+
syncEditorSupport(root, config);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.error("cluaupp: intellisense sync failed", err.message);
|
|
339
|
+
}
|
|
330
340
|
if (compiled.errors.length > 0 && exitOnError) {
|
|
331
341
|
process.exit(1);
|
|
332
342
|
}
|
|
333
343
|
return { failed: compiled.errors.length, written };
|
|
334
344
|
}
|
|
335
345
|
|
|
336
|
-
function init(dest) {
|
|
346
|
+
async function init(dest) {
|
|
337
347
|
const template = path.join(__dirname, "..", "templates", "game");
|
|
338
348
|
const include = path.join(__dirname, "..", "include");
|
|
339
349
|
copyDir(template, dest);
|
|
340
350
|
copyDir(include, path.join(dest, "include"));
|
|
341
351
|
copyRuntime(dest);
|
|
352
|
+
syncEditorSupport(dest);
|
|
353
|
+
const installed = await installEditorSupport();
|
|
342
354
|
console.log("Cluaupp ready in", dest);
|
|
343
355
|
console.log(" cluaupp build");
|
|
344
356
|
console.log(" rojo serve");
|
|
357
|
+
if (installed.local.length || (installed.cpp && installed.cpp.status === "installed")) {
|
|
358
|
+
console.log(" reload Cursor (Ctrl+Shift+P → Developer: Reload Window)");
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function pidAlive(pid) {
|
|
363
|
+
try {
|
|
364
|
+
process.kill(pid, 0);
|
|
365
|
+
return true;
|
|
366
|
+
} catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function acquireWatchLock(root) {
|
|
372
|
+
const file = path.join(root, ".cluaupp-watch.lock");
|
|
373
|
+
if (fs.existsSync(file)) {
|
|
374
|
+
const pid = Number(fs.readFileSync(file, "utf8").trim());
|
|
375
|
+
if (Number.isFinite(pid) && pid !== process.pid && pidAlive(pid)) {
|
|
376
|
+
console.error(`cluaupp: watch already running (pid ${pid}). Stop it first — two watchers make Rojo crash on libs/.`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
fs.writeFileSync(file, String(process.pid), "utf8");
|
|
381
|
+
const release = () => {
|
|
382
|
+
try {
|
|
383
|
+
if (fs.existsSync(file) && fs.readFileSync(file, "utf8").trim() === String(process.pid)) {
|
|
384
|
+
fs.unlinkSync(file);
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
// ignore
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
process.on("exit", release);
|
|
391
|
+
process.on("SIGINT", () => {
|
|
392
|
+
release();
|
|
393
|
+
process.exit(0);
|
|
394
|
+
});
|
|
395
|
+
process.on("SIGTERM", () => {
|
|
396
|
+
release();
|
|
397
|
+
process.exit(0);
|
|
398
|
+
});
|
|
345
399
|
}
|
|
346
400
|
|
|
347
401
|
function watch(root) {
|
|
402
|
+
acquireWatchLock(root);
|
|
348
403
|
build(root, { exitOnError: false, syncVendor: false, holdOnError: true });
|
|
349
404
|
const config = loadConfig(root);
|
|
350
405
|
const dir = path.join(root, config.rootDir);
|
|
@@ -371,7 +426,7 @@ function watch(root) {
|
|
|
371
426
|
}
|
|
372
427
|
};
|
|
373
428
|
|
|
374
|
-
console.log("watching", dir);
|
|
429
|
+
console.log("cluaupp", pkg.version, "watching", dir, "(libs untouched)");
|
|
375
430
|
if (!fs.existsSync(dir)) {
|
|
376
431
|
fs.mkdirSync(dir, { recursive: true });
|
|
377
432
|
}
|
|
@@ -385,11 +440,31 @@ function dispatch(args) {
|
|
|
385
440
|
const cmd = args[0] || "help";
|
|
386
441
|
const cwd = process.cwd();
|
|
387
442
|
if (cmd === "init") {
|
|
388
|
-
init(path.resolve(cwd, args[1] || "."))
|
|
443
|
+
return init(path.resolve(cwd, args[1] || ".")).catch((err) => {
|
|
444
|
+
console.error(err.message || err);
|
|
445
|
+
process.exit(1);
|
|
446
|
+
});
|
|
389
447
|
} else if (cmd === "build") {
|
|
390
448
|
build(path.resolve(cwd, args[1] || "."));
|
|
391
449
|
} else if (cmd === "watch") {
|
|
392
450
|
watch(path.resolve(cwd, args[1] || "."));
|
|
451
|
+
} else if (cmd === "lsp") {
|
|
452
|
+
const { start } = require("./lsp");
|
|
453
|
+
start({ projectRoot: path.resolve(cwd, args[1] || ".") });
|
|
454
|
+
} else if (cmd === "intellisense" || cmd === "intelisense") {
|
|
455
|
+
const root = path.resolve(cwd, args[1] || ".");
|
|
456
|
+
syncEditorSupport(root, loadConfig(root));
|
|
457
|
+
return installEditorSupport().then((installed) => {
|
|
458
|
+
console.log("cluaupp: compile_commands.json, .clangd, and .vscode updated in", root);
|
|
459
|
+
if (installed.local.length) {
|
|
460
|
+
for (const dest of installed.local) {
|
|
461
|
+
console.log("cluaupp: Cluaupp IntelliSense", dest);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}).catch((err) => {
|
|
465
|
+
console.error(err.message || err);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
});
|
|
393
468
|
} else if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
394
469
|
console.log(pkg.version);
|
|
395
470
|
} else {
|
package/src/compile.js
CHANGED
|
@@ -3,17 +3,27 @@
|
|
|
3
3
|
const { parse } = require("./parse");
|
|
4
4
|
const { emit } = require("./emit");
|
|
5
5
|
const { preprocess } = require("./preprocess");
|
|
6
|
-
const { collectLibraries, insertRequires } = require("./libs");
|
|
6
|
+
const { collectLibraries, insertRequires, insertModuleRequires } = require("./libs");
|
|
7
7
|
const { planOutput } = require("./architecture");
|
|
8
8
|
|
|
9
|
+
function attachRequires(contents, source, ast, options, outName) {
|
|
10
|
+
let next = contents;
|
|
11
|
+
if (!next.includes("CluauppLibs")) {
|
|
12
|
+
next = insertRequires(next, collectLibraries(source, ast));
|
|
13
|
+
}
|
|
14
|
+
return insertModuleRequires(next, options.moduleIncludes || [], outName);
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
function compileSource(source, fileName, options = {}) {
|
|
18
|
+
options.moduleIncludes = options.moduleIncludes || [];
|
|
10
19
|
const prepared = options.filePath ? preprocess(source, options.filePath, options) : source;
|
|
11
20
|
const ast = parse(prepared, fileName || "input.cpp");
|
|
12
21
|
const luau = emit(ast, options);
|
|
13
|
-
return
|
|
22
|
+
return attachRequires(luau, source, ast, options, options.outName || fileName);
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
function compileService(source, fileName, options = {}) {
|
|
26
|
+
options.moduleIncludes = options.moduleIncludes || [];
|
|
17
27
|
const prepared = options.filePath ? preprocess(source, options.filePath, options) : source;
|
|
18
28
|
const ast = parse(prepared, fileName || "input.cpp");
|
|
19
29
|
const planned = planOutput(ast, fileName || "input.cpp", {
|
|
@@ -22,9 +32,15 @@ function compileService(source, fileName, options = {}) {
|
|
|
22
32
|
source: prepared,
|
|
23
33
|
});
|
|
24
34
|
if (planned.files) {
|
|
25
|
-
return
|
|
35
|
+
return {
|
|
36
|
+
...planned,
|
|
37
|
+
files: planned.files.map((file) => ({
|
|
38
|
+
...file,
|
|
39
|
+
contents: attachRequires(file.contents, source, ast, options, file.name),
|
|
40
|
+
})),
|
|
41
|
+
};
|
|
26
42
|
}
|
|
27
|
-
const luau =
|
|
43
|
+
const luau = attachRequires(emit(ast, options), source, ast, options, planned.outName || options.outName || fileName);
|
|
28
44
|
const name =
|
|
29
45
|
planned.outName ||
|
|
30
46
|
options.outName ||
|