cluaupp 0.1.4 → 0.1.5
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 +9 -0
- package/README.md +6 -6
- package/docs/README.md +1 -1
- package/docs/architecture.md +28 -78
- package/docs/cli.md +4 -5
- package/docs/comparison.md +3 -3
- package/docs/config.md +6 -6
- package/docs/cpp-organization.md +2 -2
- package/docs/getting-started.md +2 -1
- package/docs/intro.md +1 -1
- package/docs/oop/file-tags.md +16 -17
- package/docs/oop/index.md +2 -2
- package/docs/oop/modules.md +3 -1
- package/docs/oop/services.md +7 -14
- package/docs/syntax.md +6 -5
- package/package.json +1 -1
- package/src/architecture.js +73 -25
- package/src/cli.js +5 -13
- package/src/compile.js +62 -8
- package/src/emit.js +1 -1
- package/src/headers.js +234 -0
- package/src/libs.js +110 -42
- package/src/preprocess.js +48 -1
- package/src/understand.js +5 -3
- package/templates/game/cluaupp.config.json +2 -2
- package/templates/game/src/client/init.client.cpp +1 -0
- package/templates/game/src/server/leaderstats.server.cpp +1 -0
package/src/libs.js
CHANGED
|
@@ -299,17 +299,64 @@ function collectLibraries(source, ast) {
|
|
|
299
299
|
return unique;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
const ROJO_ROOTS = {
|
|
303
|
+
shared: { service: "ReplicatedStorage", expr: "ReplicatedStorage.Cluaupp" },
|
|
304
|
+
server: { service: "ServerScriptService", expr: "ServerScriptService.Cluaupp" },
|
|
305
|
+
client: { service: "StarterPlayer", expr: "StarterPlayer.StarterPlayerScripts.Cluaupp" },
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const SERVICE_ORDER = ["ReplicatedStorage", "ServerScriptService", "StarterPlayer"];
|
|
309
|
+
|
|
310
|
+
const SERVICE_GET = {
|
|
311
|
+
ReplicatedStorage: 'const ReplicatedStorage = game:GetService("ReplicatedStorage")',
|
|
312
|
+
ServerScriptService: 'const ServerScriptService = game:GetService("ServerScriptService")',
|
|
313
|
+
StarterPlayer: 'const StarterPlayer = game:GetService("StarterPlayer")',
|
|
314
|
+
};
|
|
315
|
+
|
|
302
316
|
function requireCluauppLib(name) {
|
|
303
317
|
return `require(ReplicatedStorage.CluauppLibs.${name})`;
|
|
304
318
|
}
|
|
305
319
|
|
|
306
|
-
function
|
|
320
|
+
function parseOutRel(toOutRel) {
|
|
321
|
+
const posix = String(toOutRel || "module.luau")
|
|
322
|
+
.replace(/\\/g, "/")
|
|
323
|
+
.replace(/\.luau$/i, "");
|
|
324
|
+
const parts = posix.split("/").filter(Boolean);
|
|
325
|
+
const tree = parts[0];
|
|
326
|
+
const root = ROJO_ROOTS[tree] || null;
|
|
327
|
+
const rest = root ? parts.slice(1) : parts;
|
|
328
|
+
return { tree, root, rest, posix };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function robloxRequireFrom(_fromOutRel, toOutRel) {
|
|
332
|
+
const { root, rest } = parseOutRel(toOutRel);
|
|
333
|
+
if (root) {
|
|
334
|
+
const tail = rest.join(".");
|
|
335
|
+
return tail ? `require(${root.expr}.${tail})` : `require(${root.expr})`;
|
|
336
|
+
}
|
|
337
|
+
const fromDir = path.posix.dirname(String(_fromOutRel || "module.luau").replace(/\\/g, "/"));
|
|
338
|
+
const toMod = String(toOutRel || "module.luau")
|
|
339
|
+
.replace(/\\/g, "/")
|
|
340
|
+
.replace(/\.luau$/i, "");
|
|
341
|
+
let rel = path.posix.relative(fromDir, toMod);
|
|
342
|
+
if (!rel || rel === ".") {
|
|
343
|
+
rel = path.posix.basename(toMod);
|
|
344
|
+
}
|
|
345
|
+
const parts = rel.split("/");
|
|
346
|
+
let expr = "script.Parent";
|
|
347
|
+
for (const part of parts) {
|
|
348
|
+
if (part === "..") {
|
|
349
|
+
expr += ".Parent";
|
|
350
|
+
} else if (part && part !== ".") {
|
|
351
|
+
expr += `.${part}`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return `require(${expr})`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function emitLibraryLines(libraries) {
|
|
307
358
|
const api = [];
|
|
308
359
|
const types = [];
|
|
309
|
-
if (!libraries.length) {
|
|
310
|
-
return { api: "", types: "" };
|
|
311
|
-
}
|
|
312
|
-
api.push('const ReplicatedStorage = game:GetService("ReplicatedStorage")');
|
|
313
360
|
for (const spec of libraries) {
|
|
314
361
|
api.push(`const ${spec.bind} = ${requireCluauppLib(spec.file)}`);
|
|
315
362
|
const exported = TYPE_EXPORTS[spec.bind];
|
|
@@ -323,6 +370,15 @@ function emitRequireParts(libraries) {
|
|
|
323
370
|
}
|
|
324
371
|
}
|
|
325
372
|
}
|
|
373
|
+
return { api, types };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function emitRequireParts(libraries) {
|
|
377
|
+
if (!libraries.length) {
|
|
378
|
+
return { api: "", types: "" };
|
|
379
|
+
}
|
|
380
|
+
const { api, types } = emitLibraryLines(libraries);
|
|
381
|
+
api.unshift(SERVICE_GET.ReplicatedStorage);
|
|
326
382
|
return {
|
|
327
383
|
api: `${api.join("\n")}\n`,
|
|
328
384
|
types: types.length ? `${types.join("\n")}\n` : "",
|
|
@@ -339,34 +395,7 @@ function insertRequires(luau, libraries) {
|
|
|
339
395
|
if (!block) {
|
|
340
396
|
return luau;
|
|
341
397
|
}
|
|
342
|
-
|
|
343
|
-
if (!match) {
|
|
344
|
-
return `${block}\n${luau}`;
|
|
345
|
-
}
|
|
346
|
-
const insertAt = match[0].length;
|
|
347
|
-
const rest = luau.slice(insertAt).replace(/^\n*/, "\n");
|
|
348
|
-
return `${luau.slice(0, insertAt)}\n${block}\n${rest}`;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function robloxRequireFrom(fromOutRel, toOutRel) {
|
|
352
|
-
const fromDir = path.posix.dirname(String(fromOutRel || "module.luau").replace(/\\/g, "/"));
|
|
353
|
-
const toMod = String(toOutRel || "module.luau")
|
|
354
|
-
.replace(/\\/g, "/")
|
|
355
|
-
.replace(/\.luau$/i, "");
|
|
356
|
-
let rel = path.posix.relative(fromDir, toMod);
|
|
357
|
-
if (!rel || rel === ".") {
|
|
358
|
-
rel = path.posix.basename(toMod);
|
|
359
|
-
}
|
|
360
|
-
const parts = rel.split("/");
|
|
361
|
-
let expr = "script.Parent";
|
|
362
|
-
for (const part of parts) {
|
|
363
|
-
if (part === "..") {
|
|
364
|
-
expr += ".Parent";
|
|
365
|
-
} else if (part && part !== ".") {
|
|
366
|
-
expr += `.${part}`;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return `require(${expr})`;
|
|
398
|
+
return insertBlock(luau, block);
|
|
370
399
|
}
|
|
371
400
|
|
|
372
401
|
function moduleIsUsed(luau, spec) {
|
|
@@ -377,13 +406,11 @@ function moduleIsUsed(luau, spec) {
|
|
|
377
406
|
return exported.some((name) => String(luau).includes(name));
|
|
378
407
|
}
|
|
379
408
|
|
|
380
|
-
function
|
|
381
|
-
if (!modules || modules.length === 0) {
|
|
382
|
-
return luau;
|
|
383
|
-
}
|
|
409
|
+
function moduleRequireLines(modules, fromOutRel, luau) {
|
|
384
410
|
const lines = [];
|
|
411
|
+
const services = new Set();
|
|
385
412
|
const seen = new Set();
|
|
386
|
-
for (const spec of modules) {
|
|
413
|
+
for (const spec of modules || []) {
|
|
387
414
|
if (!spec || !spec.name || seen.has(spec.name)) {
|
|
388
415
|
continue;
|
|
389
416
|
}
|
|
@@ -391,20 +418,60 @@ function insertModuleRequires(luau, modules, fromOutRel) {
|
|
|
391
418
|
continue;
|
|
392
419
|
}
|
|
393
420
|
seen.add(spec.name);
|
|
421
|
+
const { root } = parseOutRel(spec.outRel);
|
|
422
|
+
if (root) {
|
|
423
|
+
services.add(root.service);
|
|
424
|
+
}
|
|
394
425
|
lines.push(`const ${spec.name} = ${robloxRequireFrom(fromOutRel, spec.outRel)}`);
|
|
395
426
|
for (const name of (spec.exports && spec.exports.consts) || []) {
|
|
396
427
|
if (name !== spec.name) {
|
|
397
428
|
lines.push(`const ${name} = ${spec.name}.${name}`);
|
|
398
429
|
}
|
|
399
430
|
}
|
|
400
|
-
if ((spec.exports && spec.exports.structs || []).includes(spec.name) &&
|
|
401
|
-
lines.push(`type ${spec.name} =
|
|
431
|
+
if (((spec.exports && spec.exports.structs) || []).includes(spec.name) || (spec.exports && spec.exports.hasProtos)) {
|
|
432
|
+
lines.push(`type ${spec.name} = ${spec.name}.${spec.name}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return { lines, services };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function insertModuleRequires(luau, modules, fromOutRel) {
|
|
439
|
+
return insertPreamble(luau, modules, [], fromOutRel);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function stripServiceGets(luau, services) {
|
|
443
|
+
let next = String(luau);
|
|
444
|
+
for (const name of services) {
|
|
445
|
+
const line = SERVICE_GET[name];
|
|
446
|
+
if (!line) {
|
|
447
|
+
continue;
|
|
402
448
|
}
|
|
449
|
+
next = next.replace(new RegExp(`^${line.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "m"), "");
|
|
403
450
|
}
|
|
404
|
-
|
|
451
|
+
return next;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function insertPreamble(luau, modules, libraries, fromOutRel) {
|
|
455
|
+
const { lines: moduleLines, services } = moduleRequireLines(modules, fromOutRel, luau);
|
|
456
|
+
const libList = libraries || [];
|
|
457
|
+
if (libList.length) {
|
|
458
|
+
services.add("ReplicatedStorage");
|
|
459
|
+
}
|
|
460
|
+
if (moduleLines.length === 0 && libList.length === 0) {
|
|
405
461
|
return luau;
|
|
406
462
|
}
|
|
407
|
-
|
|
463
|
+
const { api: libApi, types: libTypes } = emitLibraryLines(libList);
|
|
464
|
+
const blockLines = [];
|
|
465
|
+
for (const name of SERVICE_ORDER) {
|
|
466
|
+
if (services.has(name)) {
|
|
467
|
+
blockLines.push(SERVICE_GET[name]);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
blockLines.push(...moduleLines);
|
|
471
|
+
blockLines.push(...libApi);
|
|
472
|
+
blockLines.push(...libTypes);
|
|
473
|
+
const body = stripServiceGets(luau, services);
|
|
474
|
+
return insertBlock(body, blockLines.join("\n"));
|
|
408
475
|
}
|
|
409
476
|
|
|
410
477
|
function insertBlock(luau, block) {
|
|
@@ -431,4 +498,5 @@ module.exports = {
|
|
|
431
498
|
emitRequires,
|
|
432
499
|
insertRequires,
|
|
433
500
|
insertModuleRequires,
|
|
501
|
+
insertPreamble,
|
|
434
502
|
};
|
package/src/preprocess.js
CHANGED
|
@@ -52,6 +52,28 @@ function siblingImplementation(headerPath) {
|
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function siblingHeader(implPath) {
|
|
56
|
+
if (!implPath || isHeaderFile(implPath)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const base = String(implPath).replace(/\.(cpp|cc|cxx|c)$/i, "");
|
|
60
|
+
if (base === String(implPath)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
for (const ext of [".h", ".hpp", ".hh"]) {
|
|
64
|
+
if (fs.existsSync(base + ext)) {
|
|
65
|
+
return base + ext;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function implOutName(rel) {
|
|
72
|
+
return String(rel)
|
|
73
|
+
.replace(/\\/g, "/")
|
|
74
|
+
.replace(/\.(cpp|cc|cxx|c)$/i, "Impl.luau");
|
|
75
|
+
}
|
|
76
|
+
|
|
55
77
|
function resolveInclude(name, fromFile, includeDirs) {
|
|
56
78
|
const bases = [path.dirname(fromFile), ...(includeDirs || [])];
|
|
57
79
|
for (const base of bases) {
|
|
@@ -78,7 +100,8 @@ function scanHeaderExports(source) {
|
|
|
78
100
|
structs.push(match[1]);
|
|
79
101
|
}
|
|
80
102
|
}
|
|
81
|
-
|
|
103
|
+
const hasProtos = /\b(?:void|int|bool|float|double|auto|string|[\w:]+)\s+\w+\s*\([^;]*\)\s*;/.test(String(source));
|
|
104
|
+
return { consts, structs, hasProtos };
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
function pushModuleInclude(options, resolved, impl) {
|
|
@@ -96,7 +119,28 @@ function pushModuleInclude(options, resolved, impl) {
|
|
|
96
119
|
});
|
|
97
120
|
}
|
|
98
121
|
|
|
122
|
+
function applyPragmas(source, options) {
|
|
123
|
+
let nstrict = false;
|
|
124
|
+
let strict = false;
|
|
125
|
+
for (const line of String(source).split(/\r?\n/)) {
|
|
126
|
+
if (/^\s*#\s*pragma\s+nstrict\b/i.test(line)) {
|
|
127
|
+
nstrict = true;
|
|
128
|
+
} else if (/^\s*#\s*pragma\s+strict\b/i.test(line)) {
|
|
129
|
+
strict = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (nstrict) {
|
|
133
|
+
options.strict = false;
|
|
134
|
+
} else if (strict) {
|
|
135
|
+
options.strict = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
99
139
|
function preprocess(source, filePath, options = {}) {
|
|
140
|
+
if (!options.pragmaResolved) {
|
|
141
|
+
options.pragmaResolved = true;
|
|
142
|
+
applyPragmas(source, options);
|
|
143
|
+
}
|
|
100
144
|
const seen = options.seen || new Set();
|
|
101
145
|
const includeDirs = options.includeDirs || [];
|
|
102
146
|
const resolvedSelf = filePath ? path.resolve(filePath) : null;
|
|
@@ -140,9 +184,12 @@ module.exports = {
|
|
|
140
184
|
isSourceFile,
|
|
141
185
|
isHeaderFile,
|
|
142
186
|
toLuauPath,
|
|
187
|
+
applyPragmas,
|
|
143
188
|
preprocess,
|
|
144
189
|
scanHeaderExports,
|
|
145
190
|
isEngineStub,
|
|
146
191
|
siblingImplementation,
|
|
192
|
+
siblingHeader,
|
|
193
|
+
implOutName,
|
|
147
194
|
resolveInclude,
|
|
148
195
|
};
|
package/src/understand.js
CHANGED
|
@@ -482,9 +482,11 @@ function analyze(ast, fileName) {
|
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
function modernScriptOutName(relativeName) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
.replace(/\.
|
|
485
|
+
const rel = String(relativeName).replace(/\\/g, "/");
|
|
486
|
+
if (/\.plugin\.(cpp|cc|cxx|c|h|hpp|hh)$/i.test(rel)) {
|
|
487
|
+
return rel.replace(/\.plugin\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
|
|
488
|
+
}
|
|
489
|
+
return rel.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
|
|
488
490
|
}
|
|
489
491
|
|
|
490
492
|
function legacyOutName(relativeName) {
|