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/parse.js
CHANGED
|
@@ -199,8 +199,18 @@ function parse(source, fileName) {
|
|
|
199
199
|
return left;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
function parseShift() {
|
|
203
|
+
let left = parseBinary();
|
|
204
|
+
while (at("op", "<<")) {
|
|
205
|
+
eat("op", "<<");
|
|
206
|
+
const right = parseBinary();
|
|
207
|
+
left = { type: "binary", op: "<<", left, right };
|
|
208
|
+
}
|
|
209
|
+
return left;
|
|
210
|
+
}
|
|
211
|
+
|
|
202
212
|
function parseExpr() {
|
|
203
|
-
const left =
|
|
213
|
+
const left = parseShift();
|
|
204
214
|
if (at("op", "=")) {
|
|
205
215
|
i += 1;
|
|
206
216
|
return { type: "assign", left, right: parseExpr() };
|
|
@@ -223,6 +233,58 @@ function parse(source, fileName) {
|
|
|
223
233
|
return body;
|
|
224
234
|
}
|
|
225
235
|
|
|
236
|
+
function parseSwitch() {
|
|
237
|
+
eat("kw", "switch");
|
|
238
|
+
eat("op", "(");
|
|
239
|
+
const discriminant = parseExpr();
|
|
240
|
+
eat("op", ")");
|
|
241
|
+
eat("op", "{");
|
|
242
|
+
const cases = [];
|
|
243
|
+
let current = null;
|
|
244
|
+
const flush = () => {
|
|
245
|
+
if (current) {
|
|
246
|
+
cases.push(current);
|
|
247
|
+
current = null;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
while (!at("eof") && !at("op", "}")) {
|
|
251
|
+
skipSemicolons();
|
|
252
|
+
if (at("op", "}")) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
if (at("kw", "case")) {
|
|
256
|
+
i += 1;
|
|
257
|
+
const value = parseExpr();
|
|
258
|
+
eat("op", ":");
|
|
259
|
+
if (current && current.body.length === 0 && !current.isDefault) {
|
|
260
|
+
current.values.push(value);
|
|
261
|
+
} else {
|
|
262
|
+
flush();
|
|
263
|
+
current = { values: [value], body: [], isDefault: false };
|
|
264
|
+
}
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (at("kw", "default")) {
|
|
268
|
+
i += 1;
|
|
269
|
+
eat("op", ":");
|
|
270
|
+
flush();
|
|
271
|
+
current = { values: [], body: [], isDefault: true };
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (!current) {
|
|
275
|
+
throw error("switch body needs case or default before statements");
|
|
276
|
+
}
|
|
277
|
+
if (at("op", "{")) {
|
|
278
|
+
current.body.push(...parseBlock());
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
current.body.push(parseStmt());
|
|
282
|
+
}
|
|
283
|
+
flush();
|
|
284
|
+
eat("op", "}");
|
|
285
|
+
return { type: "switch", discriminant, cases };
|
|
286
|
+
}
|
|
287
|
+
|
|
226
288
|
function parseIf() {
|
|
227
289
|
eat("kw", "if");
|
|
228
290
|
eat("op", "(");
|
|
@@ -285,6 +347,9 @@ function parse(source, fileName) {
|
|
|
285
347
|
if (at("kw", "if")) {
|
|
286
348
|
return parseIf();
|
|
287
349
|
}
|
|
350
|
+
if (at("kw", "switch")) {
|
|
351
|
+
return parseSwitch();
|
|
352
|
+
}
|
|
288
353
|
if (at("kw", "for")) {
|
|
289
354
|
return parseFor();
|
|
290
355
|
}
|
|
@@ -293,6 +358,11 @@ function parse(source, fileName) {
|
|
|
293
358
|
const value = at("op", ";") || at("op", "}") ? null : parseExpr();
|
|
294
359
|
return { type: "return", value };
|
|
295
360
|
}
|
|
361
|
+
if (at("kw", "break")) {
|
|
362
|
+
i += 1;
|
|
363
|
+
skipSemicolons();
|
|
364
|
+
return { type: "break" };
|
|
365
|
+
}
|
|
296
366
|
if (at("kw", "while")) {
|
|
297
367
|
i += 1;
|
|
298
368
|
eat("op", "(");
|
|
@@ -312,12 +382,25 @@ function parse(source, fileName) {
|
|
|
312
382
|
return { name: "arg", valueType };
|
|
313
383
|
}
|
|
314
384
|
|
|
385
|
+
function parseQualifiedName() {
|
|
386
|
+
const parts = [eat("ident").value];
|
|
387
|
+
while (at("op", "::")) {
|
|
388
|
+
i += 1;
|
|
389
|
+
parts.push(eat("ident").value);
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
parts,
|
|
393
|
+
name: parts[parts.length - 1],
|
|
394
|
+
owner: parts.length > 1 ? parts.slice(0, -1).join("::") : null,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
315
398
|
function parseFunction() {
|
|
316
399
|
const returnType = parseType();
|
|
317
400
|
if (!returnType || !at("ident")) {
|
|
318
401
|
throw error("invalid function declaration");
|
|
319
402
|
}
|
|
320
|
-
const
|
|
403
|
+
const qualified = parseQualifiedName();
|
|
321
404
|
eat("op", "(");
|
|
322
405
|
const params = [];
|
|
323
406
|
if (!at("op", ")")) {
|
|
@@ -330,10 +413,96 @@ function parse(source, fileName) {
|
|
|
330
413
|
eat("op", ")");
|
|
331
414
|
if (at("op", ";")) {
|
|
332
415
|
i += 1;
|
|
333
|
-
return { type: "proto", name, returnType, params };
|
|
416
|
+
return { type: "proto", name: qualified.name, owner: qualified.owner, returnType, params };
|
|
334
417
|
}
|
|
335
418
|
const body = parseBlock();
|
|
336
|
-
return { type: "function", name, returnType, params, body };
|
|
419
|
+
return { type: "function", name: qualified.name, owner: qualified.owner, returnType, params, body };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function parseStruct() {
|
|
423
|
+
eat("kw");
|
|
424
|
+
const name = at("ident") ? eat("ident").value : "_anon";
|
|
425
|
+
if (at("op", ";")) {
|
|
426
|
+
i += 1;
|
|
427
|
+
return { type: "struct", name, fields: [], methods: [] };
|
|
428
|
+
}
|
|
429
|
+
eat("op", "{");
|
|
430
|
+
const fields = [];
|
|
431
|
+
const methods = [];
|
|
432
|
+
while (!at("eof") && !at("op", "}")) {
|
|
433
|
+
skipSemicolons();
|
|
434
|
+
if (at("op", "}")) {
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
if (at("kw", "public") || at("kw", "private") || at("kw", "protected")) {
|
|
438
|
+
i += 1;
|
|
439
|
+
if (at("op", ":")) {
|
|
440
|
+
i += 1;
|
|
441
|
+
}
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (at("kw", "struct") || at("kw", "class")) {
|
|
445
|
+
const nested = parseStruct();
|
|
446
|
+
if (nested.instance) {
|
|
447
|
+
fields.push({
|
|
448
|
+
type: "decl",
|
|
449
|
+
name: nested.instance.name,
|
|
450
|
+
valueType: nested.name,
|
|
451
|
+
value:
|
|
452
|
+
nested.instance.value || {
|
|
453
|
+
type: "initlist",
|
|
454
|
+
fields: nested.fields.map((field) => ({
|
|
455
|
+
name: field.name,
|
|
456
|
+
value: field.value || { type: "null" },
|
|
457
|
+
})),
|
|
458
|
+
},
|
|
459
|
+
isConst: false,
|
|
460
|
+
owner: name,
|
|
461
|
+
});
|
|
462
|
+
} else {
|
|
463
|
+
fields.push(...nested.fields);
|
|
464
|
+
methods.push(...nested.methods);
|
|
465
|
+
}
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const saved = i;
|
|
469
|
+
const isConst = at("kw", "const");
|
|
470
|
+
if (isConst) {
|
|
471
|
+
i += 1;
|
|
472
|
+
}
|
|
473
|
+
const valueType = parseType();
|
|
474
|
+
if (valueType && at("ident")) {
|
|
475
|
+
if (peek(1).value === "(") {
|
|
476
|
+
i = saved;
|
|
477
|
+
const method = parseFunction();
|
|
478
|
+
method.owner = method.owner || name;
|
|
479
|
+
methods.push(method);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
const fieldName = eat("ident").value;
|
|
483
|
+
let value = null;
|
|
484
|
+
if (at("op", "=")) {
|
|
485
|
+
i += 1;
|
|
486
|
+
value = parseExpr();
|
|
487
|
+
}
|
|
488
|
+
skipSemicolons();
|
|
489
|
+
fields.push({ type: "decl", name: fieldName, valueType, value, isConst, owner: name });
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
i = saved;
|
|
493
|
+
skipBalanced();
|
|
494
|
+
}
|
|
495
|
+
eat("op", "}");
|
|
496
|
+
let instance = null;
|
|
497
|
+
if (at("ident")) {
|
|
498
|
+
instance = { name: eat("ident").value, value: null };
|
|
499
|
+
if (at("op", "=")) {
|
|
500
|
+
i += 1;
|
|
501
|
+
instance.value = parseExpr();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
skipSemicolons();
|
|
505
|
+
return { type: "struct", name, fields, methods, instance };
|
|
337
506
|
}
|
|
338
507
|
|
|
339
508
|
function parseTopLevelDecl(isConst, valueType) {
|
|
@@ -414,7 +583,13 @@ function parse(source, fileName) {
|
|
|
414
583
|
skipSemicolons();
|
|
415
584
|
continue;
|
|
416
585
|
}
|
|
417
|
-
if (at("kw", "struct") || at("kw", "class")
|
|
586
|
+
if (at("kw", "struct") || at("kw", "class")) {
|
|
587
|
+
const parsed = parseStruct();
|
|
588
|
+
decls.push(...parsed.fields);
|
|
589
|
+
decls.push(...parsed.methods);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (at("kw", "enum") || at("kw", "template") || at("kw", "typedef") || at("kw", "extern")) {
|
|
418
593
|
skipTypeDecl();
|
|
419
594
|
continue;
|
|
420
595
|
}
|
|
@@ -426,7 +601,7 @@ function parse(source, fileName) {
|
|
|
426
601
|
const maybeType = parseType();
|
|
427
602
|
if (maybeType && at("ident")) {
|
|
428
603
|
const next = peek(1);
|
|
429
|
-
if (next.value === "(") {
|
|
604
|
+
if (next.value === "(" || next.value === "::") {
|
|
430
605
|
i = saved;
|
|
431
606
|
decls.push(parseFunction());
|
|
432
607
|
skipSemicolons();
|
|
@@ -438,6 +613,12 @@ function parse(source, fileName) {
|
|
|
438
613
|
}
|
|
439
614
|
}
|
|
440
615
|
i = saved;
|
|
616
|
+
if (at("ident") && (peek(1).value === "::" || peek(1).value === "(" || peek(1).value === "." || peek(1).value === "->")) {
|
|
617
|
+
const expr = parseExpr();
|
|
618
|
+
skipSemicolons();
|
|
619
|
+
decls.push({ type: "expr", expr });
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
441
622
|
decls.push(parseFunction());
|
|
442
623
|
skipSemicolons();
|
|
443
624
|
}
|
package/src/preprocess.js
CHANGED
|
@@ -27,17 +27,75 @@ function isEngineStub(filePath) {
|
|
|
27
27
|
);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function includeVariants(name) {
|
|
31
|
+
const variants = [name];
|
|
32
|
+
if (/\.h$/i.test(name)) {
|
|
33
|
+
variants.push(name.replace(/\.h$/i, ".hpp"), name.replace(/\.h$/i, ".hh"));
|
|
34
|
+
} else if (/\.hpp$/i.test(name)) {
|
|
35
|
+
variants.push(name.replace(/\.hpp$/i, ".h"), name.replace(/\.hpp$/i, ".hh"));
|
|
36
|
+
} else if (/\.hh$/i.test(name)) {
|
|
37
|
+
variants.push(name.replace(/\.hh$/i, ".h"), name.replace(/\.hh$/i, ".hpp"));
|
|
38
|
+
}
|
|
39
|
+
return [...new Set(variants)];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function siblingImplementation(headerPath) {
|
|
43
|
+
if (!headerPath || !isHeaderFile(headerPath)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const base = headerPath.replace(/\.(h|hpp|hh)$/i, "");
|
|
47
|
+
for (const ext of [".cpp", ".cc", ".cxx", ".c"]) {
|
|
48
|
+
if (fs.existsSync(base + ext)) {
|
|
49
|
+
return base + ext;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
function resolveInclude(name, fromFile, includeDirs) {
|
|
31
56
|
const bases = [path.dirname(fromFile), ...(includeDirs || [])];
|
|
32
57
|
for (const base of bases) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
for (const variant of includeVariants(name)) {
|
|
59
|
+
const candidate = path.resolve(base, variant);
|
|
60
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
61
|
+
return candidate;
|
|
62
|
+
}
|
|
36
63
|
}
|
|
37
64
|
}
|
|
38
65
|
return null;
|
|
39
66
|
}
|
|
40
67
|
|
|
68
|
+
function scanHeaderExports(source) {
|
|
69
|
+
const consts = [];
|
|
70
|
+
const structs = [];
|
|
71
|
+
for (const match of String(source).matchAll(/\bconst\s+(?:int|bool|float|double|auto|string)\s+(\w+)/g)) {
|
|
72
|
+
if (!consts.includes(match[1])) {
|
|
73
|
+
consts.push(match[1]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const match of String(source).matchAll(/\bstruct\s+(\w+)/g)) {
|
|
77
|
+
if (!structs.includes(match[1])) {
|
|
78
|
+
structs.push(match[1]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { consts, structs };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function pushModuleInclude(options, resolved, impl) {
|
|
85
|
+
options.moduleIncludes = options.moduleIncludes || [];
|
|
86
|
+
const srcDir = options.srcDir || null;
|
|
87
|
+
const moduleFile = impl || resolved;
|
|
88
|
+
const rel = srcDir ? path.relative(srcDir, moduleFile).replace(/\\/g, "/") : path.basename(moduleFile);
|
|
89
|
+
const headerText = fs.readFileSync(resolved, "utf8");
|
|
90
|
+
options.moduleIncludes.push({
|
|
91
|
+
name: path.basename(resolved).replace(/\.(h|hpp|hh)$/i, ""),
|
|
92
|
+
outRel: rel.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau"),
|
|
93
|
+
header: resolved,
|
|
94
|
+
impl: impl || null,
|
|
95
|
+
exports: scanHeaderExports(headerText),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
41
99
|
function preprocess(source, filePath, options = {}) {
|
|
42
100
|
const seen = options.seen || new Set();
|
|
43
101
|
const includeDirs = options.includeDirs || [];
|
|
@@ -56,6 +114,13 @@ function preprocess(source, filePath, options = {}) {
|
|
|
56
114
|
if (!resolved || isEngineStub(resolved) || seen.has(resolved)) {
|
|
57
115
|
continue;
|
|
58
116
|
}
|
|
117
|
+
const impl = siblingImplementation(resolved);
|
|
118
|
+
const including = filePath ? path.resolve(filePath) : null;
|
|
119
|
+
const ownHeader = Boolean(impl && including && path.resolve(impl) === including);
|
|
120
|
+
if (!ownHeader) {
|
|
121
|
+
pushModuleInclude(options, resolved, impl);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
59
124
|
seen.add(resolved);
|
|
60
125
|
const inner = fs.readFileSync(resolved, "utf8");
|
|
61
126
|
out.push(preprocess(inner, resolved, { ...options, seen }));
|
|
@@ -76,5 +141,8 @@ module.exports = {
|
|
|
76
141
|
isHeaderFile,
|
|
77
142
|
toLuauPath,
|
|
78
143
|
preprocess,
|
|
144
|
+
scanHeaderExports,
|
|
79
145
|
isEngineStub,
|
|
146
|
+
siblingImplementation,
|
|
147
|
+
resolveInclude,
|
|
80
148
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#include <cluaupp/roblox.hpp>
|
|
2
|
+
|
|
3
|
+
void CreateLeaderstats(Player* player) {
|
|
4
|
+
if (player->FindFirstChild("leaderstats") != nullptr) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
auto* leaderstats = new Folder(player);
|
|
9
|
+
leaderstats->Name = "leaderstats";
|
|
10
|
+
|
|
11
|
+
auto* coins = new IntValue(leaderstats);
|
|
12
|
+
coins->Name = "Coins";
|
|
13
|
+
coins->Value = 0;
|
|
14
|
+
|
|
15
|
+
auto* level = new IntValue(leaderstats);
|
|
16
|
+
level->Name = "Level";
|
|
17
|
+
level->Value = 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
void init() {
|
|
21
|
+
auto* players = GetService<Players>();
|
|
22
|
+
|
|
23
|
+
for (auto* player : players->GetPlayers()) {
|
|
24
|
+
CreateLeaderstats(player);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
players->PlayerAdded.Connect(CreateLeaderstats);
|
|
28
|
+
}
|
package/src/understand.js
CHANGED
|
@@ -94,7 +94,18 @@ const INTENTS = {
|
|
|
94
94
|
data: {
|
|
95
95
|
role: "controller",
|
|
96
96
|
module: "DataController",
|
|
97
|
-
tokens: [
|
|
97
|
+
tokens: [
|
|
98
|
+
"DataStore",
|
|
99
|
+
"DataStoreService",
|
|
100
|
+
"DataService",
|
|
101
|
+
"SetAsync",
|
|
102
|
+
"GetAsync",
|
|
103
|
+
"UpdateAsync",
|
|
104
|
+
"ProfileStore",
|
|
105
|
+
"GetChangedSignal",
|
|
106
|
+
"WaitFor",
|
|
107
|
+
"Paths",
|
|
108
|
+
],
|
|
98
109
|
},
|
|
99
110
|
character: {
|
|
100
111
|
role: "controller",
|
|
@@ -187,19 +198,28 @@ function toPascalServiceName(fileName) {
|
|
|
187
198
|
|
|
188
199
|
function parseFileTag(fileName) {
|
|
189
200
|
const base = path.basename(fileName);
|
|
201
|
+
if (new RegExp(`\\.legacy\\.plugin\\.${EXT}$`, "i").test(base)) {
|
|
202
|
+
return { key: "legacy.plugin", emit: "legacy", runtime: "plugin", rojo: "Script", runContext: "Plugin" };
|
|
203
|
+
}
|
|
190
204
|
if (new RegExp(`\\.legacy\\.server\\.${EXT}$`, "i").test(base)) {
|
|
191
|
-
return { key: "legacy.server", emit: "legacy", runtime: "server", rojo: "Script" };
|
|
205
|
+
return { key: "legacy.server", emit: "legacy", runtime: "server", rojo: "Script", runContext: null };
|
|
192
206
|
}
|
|
193
207
|
if (new RegExp(`\\.legacy\\.client\\.${EXT}$`, "i").test(base)) {
|
|
194
|
-
return { key: "legacy.client", emit: "legacy", runtime: "client", rojo: "LocalScript" };
|
|
208
|
+
return { key: "legacy.client", emit: "legacy", runtime: "client", rojo: "LocalScript", runContext: null };
|
|
209
|
+
}
|
|
210
|
+
if (new RegExp(`\\.legacy\\.${EXT}$`, "i").test(base)) {
|
|
211
|
+
return { key: "legacy", emit: "legacy", runtime: "server", rojo: "Script", runContext: null };
|
|
212
|
+
}
|
|
213
|
+
if (new RegExp(`\\.plugin\\.${EXT}$`, "i").test(base)) {
|
|
214
|
+
return { key: "plugin", emit: "service", runtime: "plugin", rojo: "Script", runContext: "Plugin" };
|
|
195
215
|
}
|
|
196
216
|
if (new RegExp(`\\.server\\.${EXT}$`, "i").test(base)) {
|
|
197
|
-
return { key: "server", emit: "service", runtime: "server", rojo: "Script" };
|
|
217
|
+
return { key: "server", emit: "service", runtime: "server", rojo: "Script", runContext: "Server" };
|
|
198
218
|
}
|
|
199
219
|
if (new RegExp(`\\.client\\.${EXT}$`, "i").test(base)) {
|
|
200
|
-
return { key: "client", emit: "service", runtime: "client", rojo: "LocalScript" };
|
|
220
|
+
return { key: "client", emit: "service", runtime: "client", rojo: "LocalScript", runContext: "Client" };
|
|
201
221
|
}
|
|
202
|
-
return { key: "module", emit: "module", runtime: "shared", rojo: "ModuleScript" };
|
|
222
|
+
return { key: "module", emit: "module", runtime: "shared", rojo: "ModuleScript", runContext: null };
|
|
203
223
|
}
|
|
204
224
|
|
|
205
225
|
function scoreIntents(named) {
|
|
@@ -362,9 +382,37 @@ function classifyFunctions(ast, intents) {
|
|
|
362
382
|
absorb,
|
|
363
383
|
});
|
|
364
384
|
}
|
|
385
|
+
keepReferencedFunctions(ast, classified);
|
|
365
386
|
return classified;
|
|
366
387
|
}
|
|
367
388
|
|
|
389
|
+
function keepReferencedFunctions(ast, classified) {
|
|
390
|
+
const kept = new Set(["init"]);
|
|
391
|
+
for (const item of classified) {
|
|
392
|
+
if (item.absorb !== "cache") {
|
|
393
|
+
kept.add(item.name);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let changed = true;
|
|
398
|
+
while (changed) {
|
|
399
|
+
changed = false;
|
|
400
|
+
for (const decl of ast.body || []) {
|
|
401
|
+
if (decl.type !== "function" || !kept.has(decl.name)) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
const named = namesIn(decl);
|
|
405
|
+
for (const item of classified) {
|
|
406
|
+
if (item.absorb === "cache" && named.has(item.name)) {
|
|
407
|
+
item.absorb = "none";
|
|
408
|
+
kept.add(item.name);
|
|
409
|
+
changed = true;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
368
416
|
function analyze(ast, fileName) {
|
|
369
417
|
const tag = parseFileTag(fileName);
|
|
370
418
|
const named = namesIn(ast);
|
|
@@ -420,6 +468,7 @@ function analyze(ast, fileName) {
|
|
|
420
468
|
serviceName,
|
|
421
469
|
typesName: `${serviceName}Types`,
|
|
422
470
|
isClient: tag.runtime === "client",
|
|
471
|
+
runContext: tag.runContext || null,
|
|
423
472
|
intents,
|
|
424
473
|
primaryDomain,
|
|
425
474
|
roles,
|
|
@@ -432,10 +481,18 @@ function analyze(ast, fileName) {
|
|
|
432
481
|
};
|
|
433
482
|
}
|
|
434
483
|
|
|
484
|
+
function modernScriptOutName(relativeName) {
|
|
485
|
+
return String(relativeName)
|
|
486
|
+
.replace(/\\/g, "/")
|
|
487
|
+
.replace(/\.(server|client|plugin)\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
|
|
488
|
+
}
|
|
489
|
+
|
|
435
490
|
function legacyOutName(relativeName) {
|
|
436
491
|
return String(relativeName)
|
|
437
492
|
.replace(/\\/g, "/")
|
|
493
|
+
.replace(/\.legacy\.plugin\./i, ".")
|
|
438
494
|
.replace(/\.legacy\.(server|client)\./i, ".$1.")
|
|
495
|
+
.replace(/\.legacy\./i, ".server.")
|
|
439
496
|
.replace(new RegExp(`\\.${EXT}$`, "i"), ".luau");
|
|
440
497
|
}
|
|
441
498
|
|
|
@@ -449,5 +506,6 @@ module.exports = {
|
|
|
449
506
|
scoreIntents,
|
|
450
507
|
analyze,
|
|
451
508
|
legacyOutName,
|
|
509
|
+
modernScriptOutName,
|
|
452
510
|
looksLikeCacheSetup,
|
|
453
511
|
};
|
package/templates/game/.clangd
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
"configurations": [
|
|
3
3
|
{
|
|
4
4
|
"name": "Cluaupp",
|
|
5
|
+
"compilerPath": "C:/Program Files/LLVM/bin/clang++.exe",
|
|
5
6
|
"cStandard": "c17",
|
|
6
7
|
"cppStandard": "c++20",
|
|
7
|
-
"intelliSenseMode": "windows-
|
|
8
|
+
"intelliSenseMode": "windows-clang-x64",
|
|
8
9
|
"includePath": [
|
|
9
10
|
"${workspaceFolder}/include",
|
|
10
11
|
"${workspaceFolder}/src"
|
|
11
12
|
],
|
|
12
|
-
"
|
|
13
|
+
"forcedInclude": [
|
|
14
|
+
"${workspaceFolder}/include/cluaupp/roblox.hpp"
|
|
15
|
+
],
|
|
16
|
+
"compileCommands": "${workspaceFolder}/compile_commands.json",
|
|
13
17
|
"browse": {
|
|
14
18
|
"path": [
|
|
15
19
|
"${workspaceFolder}/include",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"C_Cpp.intelliSenseEngine": "default",
|
|
2
3
|
"C_Cpp.default.cppStandard": "c++20",
|
|
3
|
-
"C_Cpp.default.
|
|
4
|
+
"C_Cpp.default.cStandard": "c17",
|
|
5
|
+
"C_Cpp.default.compilerPath": "C:/Program Files/LLVM/bin/clang++.exe",
|
|
6
|
+
"C_Cpp.default.intelliSenseMode": "windows-clang-x64",
|
|
4
7
|
"C_Cpp.default.includePath": [
|
|
5
8
|
"${workspaceFolder}/include",
|
|
6
9
|
"${workspaceFolder}/src"
|
|
@@ -8,5 +11,16 @@
|
|
|
8
11
|
"C_Cpp.default.browse.path": [
|
|
9
12
|
"${workspaceFolder}/include",
|
|
10
13
|
"${workspaceFolder}/src"
|
|
11
|
-
]
|
|
14
|
+
],
|
|
15
|
+
"clangd.enable": false,
|
|
16
|
+
"files.associations": {
|
|
17
|
+
"*.hpp": "cpp",
|
|
18
|
+
"*.h": "cpp",
|
|
19
|
+
"*.server.cpp": "cpp",
|
|
20
|
+
"*.client.cpp": "cpp",
|
|
21
|
+
"*.plugin.cpp": "cpp",
|
|
22
|
+
"*.legacy.cpp": "cpp",
|
|
23
|
+
"*.legacy.server.cpp": "cpp",
|
|
24
|
+
"*.legacy.client.cpp": "cpp"
|
|
25
|
+
}
|
|
12
26
|
}
|
package/docs/libraries.md
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Libraries
|
|
3
|
-
sidebar_position: 20
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Libraries
|
|
7
|
-
|
|
8
|
-
Cluaupp ships **full library systems** in `CluauppLibs` (`runtime/` → `libs/` on `cluaupp init` / `build`). These are not typings and not `require(Packages.*)` stubs. `cluaupp build` copies the Luau source. `#include <cluaupp/libs/...>` injects `require(ReplicatedStorage.CluauppLibs.<Name>)`.
|
|
9
|
-
|
|
10
|
-
Re-vendor from GitHub:
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
cd cluau
|
|
14
|
-
# clones live in vendor/ (gitignored)
|
|
15
|
-
node scripts/vendor-libs.js
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Sources: [runtime/SOURCES.md](https://github.com/kartzDev/cluaupp/blob/main/runtime/SOURCES.md).
|
|
19
|
-
|
|
20
|
-
## Vendored from GitHub
|
|
21
|
-
|
|
22
|
-
| Library | Header | GitHub | What you get |
|
|
23
|
-
| --- | --- | --- | --- |
|
|
24
|
-
| **Janitor** | `<cluaupp/libs/janitor.hpp>` | [howmanysmall/Janitor](https://github.com/howmanysmall/Janitor) | Add / AddPromise / LinkToInstance / Cleanup |
|
|
25
|
-
| **Promise** | `<cluaupp/libs/promise.hpp>` | [evaera/roblox-lua-promise](https://github.com/evaera/roblox-lua-promise) | andThen + Cluaupp aliases Then / Catch / Await |
|
|
26
|
-
| **Fusion** | `<cluaupp/libs/fusion.hpp>` | [dphfox/Fusion](https://github.com/dphfox/Fusion) | scoped, New, Value, Computed, Spring, Tween |
|
|
27
|
-
| **Iris** | `<cluaupp/libs/iris.hpp>` | [SirMallard/Iris](https://github.com/SirMallard/Iris) | Immediate-mode debug UI |
|
|
28
|
-
| **Cmdr** | `<cluaupp/libs/cmdr.hpp>` | [evaera/Cmdr](https://github.com/evaera/Cmdr) | Command console (server module + CmdrClient) |
|
|
29
|
-
| **TopbarPlus** | `<cluaupp/libs/topbarplus.hpp>` | [1ForeverHD/TopbarPlus](https://github.com/1ForeverHD/TopbarPlus) | `Icon.new`, setLabel, dropdowns |
|
|
30
|
-
| **Chrono** | `<cluaupp/libs/chrono.hpp>` | [Parihsz/Chrono](https://github.com/Parihsz/Chrono) | Custom character replication (`Chrono.Start()`) |
|
|
31
|
-
| **DataService** | `<cluaupp/libs/dataservice.hpp>` | [KartzRbx/dataservicev2](https://github.com/KartzRbx/dataservicev2) | ProfileStore, QuickNet, Paths, Get vs GetPersisted |
|
|
32
|
-
| **EzVisualz** | `<cluaupp/libs/ezvisual.hpp>` | [arxkdev/ezVisualz](https://github.com/arxkdev/ezVisualz) | UIGradient presets (Rainbow, Gold, …) |
|
|
33
|
-
| **StateMachine** | `<cluaupp/libs/statemachine.hpp>` | [Prooheckcp/RobloxStateMachine](https://github.com/Prooheckcp/RobloxStateMachine) | States, transitions, LoadDirectory |
|
|
34
|
-
| **Spring** | `<cluaupp/libs/spring.hpp>` | [nightcycle/spring](https://github.com/nightcycle/spring) | Damped spring (`new(damping, frequency, position)`) |
|
|
35
|
-
| **Display** | `<cluaupp/libs/display.hpp>` | [nightcycle/display](https://github.com/nightcycle/display) | Pretty-print any value (`option` bundled) |
|
|
36
|
-
| **Module3D** | `<cluaupp/libs/module3d.hpp>` | [TheNexusAvenger/Module3D](https://github.com/TheNexusAvenger/Module3D) | Model in a ViewportFrame |
|
|
37
|
-
| **FormatNumber** | `<cluaupp/libs/formatnumber.hpp>` | [Blockzez/RobloxFormatNumber](https://github.com/Blockzez/RobloxFormatNumber) | ICU-style formatters + Abbreviate / Comma |
|
|
38
|
-
|
|
39
|
-
## Cluaupp originals
|
|
40
|
-
|
|
41
|
-
| Library | Header | Role |
|
|
42
|
-
| --- | --- | --- |
|
|
43
|
-
| **Net** | `<cluaupp/libs/net.hpp>` | Buffer-packed RemoteEvent / RemoteFunction |
|
|
44
|
-
| **MathUtils** | `<cluaupp/libs/math.hpp>` | Lerp, Map, Clamp, angles |
|
|
45
|
-
| **Twinkle** | `<cluaupp/libs/twinkle.hpp>` | UI fade / slide / zoom / bounce |
|
|
46
|
-
| **StickyBillboard** | `<cluaupp/libs/stickybillboard.hpp>` | BillboardGui on an adornee (no public GitHub) |
|
|
47
|
-
| **VfxUtil** | `<cluaupp/libs/vfx.hpp>` | Emit / Play / CloneOnto ParticleEmitters |
|
|
48
|
-
| **ArrayIndexer** | (types) | `Table<Manifest, "Name">` |
|
|
49
|
-
| **Occlude** | (types) | `Keys<Data, "field">` |
|
|
50
|
-
|
|
51
|
-
```cpp
|
|
52
|
-
#include <cluaupp/roblox.hpp>
|
|
53
|
-
#include <cluaupp/libs/janitor.hpp>
|
|
54
|
-
#include <cluaupp/libs/net.hpp>
|
|
55
|
-
#include <cluaupp/libs/dataservice.hpp>
|
|
56
|
-
#include <cluaupp/libs/formatnumber.hpp>
|
|
57
|
-
|
|
58
|
-
void OnCoins(Player* player, int amount) {
|
|
59
|
-
auto* stats = player->FindFirstChild("leaderstats");
|
|
60
|
-
if (stats == nullptr) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
void init() {
|
|
66
|
-
auto* janitor = new Janitor();
|
|
67
|
-
auto* coins = Net::Event("Coins");
|
|
68
|
-
coins->On(OnCoins);
|
|
69
|
-
print(FormatNumber::Abbreviate(1500));
|
|
70
|
-
DataService::Server.Init(DataServiceOptions {
|
|
71
|
-
.Template = { .Coins = 0 },
|
|
72
|
-
.StoreName = "PlayerData",
|
|
73
|
-
});
|
|
74
|
-
janitor->Add(coins);
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Wally is **optional**. The game template no longer depends on Wally for these packages. Add Wally only for extra community packages you want beside CluauppLibs.
|
|
79
|
-
|
|
80
|
-
Typed Luau borders live in `runtime/<Lib>/init.luau` (Janitor, Promise, Net, MathUtils, FormatNumber, Module3D, Twinkle). Engine classes: [API](https://kartzrbx.github.io/Cluaupp/api/classes/).
|