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/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 emitRequireParts(libraries) {
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
- const match = String(luau).match(/^(?:--[^\n]*\n)+/);
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 insertModuleRequires(luau, modules, fromOutRel) {
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) && !spec.impl) {
401
- lines.push(`type ${spec.name} = typeof(${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
- if (lines.length === 0) {
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
- return insertBlock(luau, lines.join("\n"));
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
- return { consts, structs };
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
- return String(relativeName)
486
- .replace(/\\/g, "/")
487
- .replace(/\.(server|client|plugin)\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
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) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "rootDir": "src",
3
3
  "outDir": "out",
4
- "strict": true,
5
- "architecture": true
4
+ "strict": false,
5
+ "architecture": false
6
6
  }
@@ -1,3 +1,4 @@
1
+ #pragma strict
1
2
  #include <cluaupp/roblox.hpp>
2
3
 
3
4
  void init() {
@@ -1,3 +1,4 @@
1
+ #pragma strict
1
2
  #include <cluaupp/roblox.hpp>
2
3
 
3
4
  void CreateLeaderstats(Player* player) {