cluaupp 0.2.2 → 1.0.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +14 -75
  2. package/README.md +42 -88
  3. package/docs/README.md +25 -21
  4. package/docs/architecture.md +13 -76
  5. package/docs/cli.md +12 -64
  6. package/docs/comparison.md +6 -24
  7. package/docs/config.md +6 -7
  8. package/docs/cpp-advanced.md +6 -3
  9. package/docs/cpp-types.md +2 -0
  10. package/docs/examples/combat.md +19 -227
  11. package/docs/examples/hud.md +13 -81
  12. package/docs/examples/index.md +9 -16
  13. package/docs/examples/leaderstats.md +44 -115
  14. package/docs/getting-started.md +20 -135
  15. package/docs/intellisense.md +5 -42
  16. package/docs/intro.md +16 -15
  17. package/docs/libraries/dataservice.md +4 -1
  18. package/docs/libraries/index.md +2 -1
  19. package/docs/libraries/more.md +3 -1
  20. package/docs/libraries/ui.md +76 -0
  21. package/docs/migration.md +38 -0
  22. package/docs/oop/file-tags.md +12 -36
  23. package/docs/oop/index.md +18 -11
  24. package/docs/roblox-api.md +4 -56
  25. package/docs/syntax.md +54 -179
  26. package/editors/vscode/extension.js +1 -1
  27. package/editors/vscode/package.json +4 -4
  28. package/examples/README.md +5 -7
  29. package/examples/game/.vscode/extensions.json +2 -2
  30. package/examples/game/.vscode/settings.json +6 -23
  31. package/examples/game/src/client/hud.client.clpp +54 -0
  32. package/examples/game/src/client/init.client.clpp +6 -0
  33. package/examples/game/src/server/combat.server.clpp +85 -0
  34. package/examples/game/src/server/leaderstats.server.clpp +28 -0
  35. package/examples/game/src/shared/PlayerData.clh +20 -0
  36. package/examples/game/src/shared/config.clp +6 -0
  37. package/examples/game/src/shared/features.clp +88 -0
  38. package/generated/api.js +15 -1
  39. package/generated/cli.js +40 -24
  40. package/generated/clpp/contract.js +5 -0
  41. package/generated/clpp/index.js +27 -0
  42. package/generated/clpp/paths.js +121 -0
  43. package/generated/clpp/postprocess.js +78 -0
  44. package/generated/clpp/runner.js +102 -0
  45. package/generated/compile.js +26 -138
  46. package/generated/emit.js +79 -3
  47. package/generated/intellisense.js +83 -207
  48. package/generated/lex.js +3 -0
  49. package/generated/libs.js +1 -1
  50. package/generated/lsp.js +1 -8
  51. package/generated/parse.js +119 -24
  52. package/generated/preprocess.js +8 -179
  53. package/generated/system-understander.js +1 -1
  54. package/generated/transpile.js +3 -50
  55. package/generated/utils/project.js +13 -28
  56. package/include/{cluaupp/datatypes.hpp → clpp/datatypes.clh} +5 -1
  57. package/include/{cluaupp/generated/instances.hpp → clpp/generated/instances.clh} +1 -1
  58. package/include/{cluaupp/libs/chrono.hpp → clpp/libs/chrono.clh} +9 -9
  59. package/include/{cluaupp/libs/cmdr.hpp → clpp/libs/cmdr.clh} +4 -4
  60. package/include/{cluaupp/libs/dataservice.hpp → clpp/libs/dataservice.clh} +8 -15
  61. package/include/{cluaupp/libs/display.hpp → clpp/libs/display.clh} +1 -2
  62. package/include/{cluaupp/libs/ezvisual.hpp → clpp/libs/ezvisual.clh} +1 -3
  63. package/include/{cluaupp/libs/formatnumber.hpp → clpp/libs/formatnumber.clh} +2 -3
  64. package/include/{cluaupp/libs/fusion.hpp → clpp/libs/fusion.clh} +5 -5
  65. package/include/{cluaupp/libs/iris.hpp → clpp/libs/iris.clh} +2 -2
  66. package/include/{cluaupp/libs/janitor.hpp → clpp/libs/janitor.clh} +1 -2
  67. package/include/{cluaupp/libs/module3d.hpp → clpp/libs/module3d.clh} +2 -4
  68. package/include/{cluaupp/libs/net.hpp → clpp/libs/net.clh} +2 -4
  69. package/include/{cluaupp/libs/promise.hpp → clpp/libs/promise.clh} +1 -4
  70. package/include/{cluaupp/libs/spring.hpp → clpp/libs/spring.clh} +1 -2
  71. package/include/{cluaupp/libs/statemachine.hpp → clpp/libs/statemachine.clh} +2 -3
  72. package/include/{cluaupp/libs/stickybillboard.hpp → clpp/libs/stickybillboard.clh} +1 -2
  73. package/include/{cluaupp/libs/topbarplus.hpp → clpp/libs/topbarplus.clh} +3 -4
  74. package/include/clpp/libs.clh +24 -0
  75. package/include/clpp/roblox.clh +31 -0
  76. package/package.json +20 -16
  77. package/src/api.ts +15 -1
  78. package/src/cli.ts +39 -25
  79. package/src/clpp/contract.ts +39 -0
  80. package/src/clpp/index.ts +18 -0
  81. package/src/clpp/paths.ts +117 -0
  82. package/src/clpp/postprocess.ts +93 -0
  83. package/src/clpp/runner.ts +102 -0
  84. package/src/compile.ts +26 -149
  85. package/src/intellisense.ts +82 -211
  86. package/src/libs.ts +1 -1
  87. package/src/lsp.ts +1 -5
  88. package/src/preprocess.ts +8 -180
  89. package/src/system-understander.ts +1 -1
  90. package/src/transpile.ts +9 -52
  91. package/src/utils/project.ts +12 -28
  92. package/templates/game/.vscode/extensions.json +2 -2
  93. package/templates/game/.vscode/settings.json +6 -23
  94. package/templates/game/src/client/init.client.clpp +6 -0
  95. package/templates/game/src/server/leaderstats.server.clpp +29 -0
  96. package/templates/game/src/shared/config.clp +6 -0
  97. package/examples/game/.clangd +0 -25
  98. package/examples/game/.vscode/c_cpp_properties.json +0 -27
  99. package/examples/game/compile_flags.txt +0 -6
  100. package/examples/game/src/client/hud.client.cpp +0 -12
  101. package/examples/game/src/client/init.client.cpp +0 -6
  102. package/examples/game/src/server/combat.server.cpp +0 -23
  103. package/examples/game/src/server/leaderstats.server.cpp +0 -29
  104. package/examples/game/src/shared/config.cpp +0 -5
  105. package/examples/game/src/shared/config.h +0 -5
  106. package/include/cluaupp/libs.hpp +0 -25
  107. package/include/cluaupp/roblox.hpp +0 -47
  108. package/src/architecture.ts +0 -500
  109. package/src/ast.ts +0 -37
  110. package/src/emit.ts +0 -531
  111. package/src/emitter/luau-codegen.ts +0 -187
  112. package/src/emitter/translators.ts +0 -168
  113. package/src/headers.ts +0 -233
  114. package/src/layout.ts +0 -114
  115. package/src/lex.ts +0 -155
  116. package/src/parse.ts +0 -630
  117. package/src/parser/collector.ts +0 -148
  118. package/src/parser/index.ts +0 -27
  119. package/src/tree-sitter-cpp.d.ts +0 -4
  120. package/src/understand.ts +0 -322
  121. package/templates/game/.vscode/c_cpp_properties.json +0 -27
  122. package/templates/game/src/client/init.client.cpp +0 -6
  123. package/templates/game/src/server/leaderstats.server.cpp +0 -29
  124. package/templates/game/src/shared/config.cpp +0 -5
  125. package/templates/game/src/shared/config.h +0 -5
  126. /package/include/{cluaupp/generated/enums.hpp → clpp/generated/enums.clh} +0 -0
  127. /package/include/{cluaupp/libs/math.hpp → clpp/libs/math.clh} +0 -0
  128. /package/include/{cluaupp/libs/twinkle.hpp → clpp/libs/twinkle.clh} +0 -0
  129. /package/include/{cluaupp/libs/vfx.hpp → clpp/libs/vfx.clh} +0 -0
@@ -1,12 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { parse } from "./parse.js";
5
- import { preprocess } from "./preprocess.js";
6
- import { findClangPlusPlus, installMarketplaceExtension, ensureLlvm, CLANGD_ID, reportInstall } from "./editor-install.js";
4
+ import { spawnSync } from "node:child_process";
7
5
  import { pkg, projectRoot as PACKAGE_ROOT } from "./package-info.js";
8
6
  import type { ProjectConfig } from "./types.js";
9
- import type { PreprocessOptions } from "./ast.js";
7
+ import { CLPP_INSTALL_HINT } from "./clpp/contract.js";
8
+ import { compileViaClpp, hasClpp } from "./clpp/runner.js";
9
+ import { isSourceFile } from "./clpp/paths.js";
10
10
 
11
11
  export type CluauppDiagnostic = {
12
12
  line: number;
@@ -15,83 +15,6 @@ export type CluauppDiagnostic = {
15
15
  severity: "error";
16
16
  };
17
17
 
18
- function posix(file: string): string {
19
- return String(file).replace(/\\/g, "/");
20
- }
21
-
22
- function collectFiles(dir: string, files: string[] = []): string[] {
23
- if (!fs.existsSync(dir)) {
24
- return files;
25
- }
26
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
27
- const full = path.join(dir, entry.name);
28
- if (entry.isDirectory()) {
29
- collectFiles(full, files);
30
- } else {
31
- files.push(full);
32
- }
33
- }
34
- return files;
35
- }
36
-
37
- function isCppFile(file: string): boolean {
38
- return /\.(cpp|cc|cxx|c|h|hpp|hh)$/i.test(file);
39
- }
40
-
41
- function clangCompileArgs(root: string): string[] {
42
- const absRoot = posix(path.resolve(root));
43
- const absInclude = posix(path.join(absRoot, "include"));
44
- const absSrc = posix(path.join(absRoot, "src"));
45
- const roblox = posix(path.join(absInclude, "cluaupp", "roblox.hpp"));
46
- return [
47
- "-xc++",
48
- "-std=c++20",
49
- "-ferror-limit=0",
50
- "-I" + absInclude,
51
- "-I" + absSrc,
52
- "-Iinclude",
53
- "-Isrc",
54
- "-include",
55
- roblox,
56
- ];
57
- }
58
-
59
- function compileCommandEntry(root: string, file: string) {
60
- const absRoot = posix(path.resolve(root));
61
- const absFile = posix(path.resolve(file));
62
- return {
63
- directory: absRoot,
64
- file: absFile,
65
- arguments: [clangPath(), ...clangCompileArgs(root), "-c", absFile],
66
- };
67
- }
68
-
69
- function mergeJson(file: string, patch: Record<string, unknown>): boolean {
70
- let current: Record<string, unknown> = {};
71
- if (fs.existsSync(file)) {
72
- try {
73
- current = JSON.parse(fs.readFileSync(file, "utf8")) as Record<string, unknown>;
74
- } catch {
75
- current = {};
76
- }
77
- }
78
- const next: Record<string, unknown> = { ...current, ...patch };
79
- for (const key of Object.keys(patch)) {
80
- const value = patch[key];
81
- const existing = current[key];
82
- if (value && typeof value === "object" && !Array.isArray(value) && existing && typeof existing === "object" && !Array.isArray(existing)) {
83
- next[key] = { ...(existing as Record<string, unknown>), ...(value as Record<string, unknown>) };
84
- }
85
- }
86
- const json = `${JSON.stringify(next, null, "\t")}\n`;
87
- if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === json) {
88
- return false;
89
- }
90
- fs.mkdirSync(path.dirname(file), { recursive: true });
91
- fs.writeFileSync(file, json, "utf8");
92
- return true;
93
- }
94
-
95
18
  function copyDir(from: string, to: string): void {
96
19
  fs.mkdirSync(to, { recursive: true });
97
20
  for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
@@ -105,153 +28,94 @@ function copyDir(from: string, to: string): void {
105
28
  }
106
29
  }
107
30
 
108
- function clangdEditorArgs(): string[] {
109
- return [
110
- "--compile-commands-dir=${workspaceFolder}",
111
- "--header-insertion=never",
112
- "--query-driver=**/clang++*,**/clang++.exe,**/g++*,**/g++.exe",
113
- ];
114
- }
115
-
116
- export function intelliSenseMode(): string {
117
- const arch = process.arch === "arm64" ? "arm64" : "x64";
118
- if (process.platform === "win32") {
119
- return `windows-clang-${arch}`;
120
- }
121
- if (process.platform === "darwin") {
122
- return `macos-clang-${arch}`;
31
+ function uniqueStrings(...lists: unknown[]): string[] {
32
+ const seen = new Set<string>();
33
+ const out: string[] = [];
34
+ for (const list of lists) {
35
+ if (!Array.isArray(list)) {
36
+ continue;
37
+ }
38
+ for (const item of list) {
39
+ if (typeof item === "string" && item && !seen.has(item)) {
40
+ seen.add(item);
41
+ out.push(item);
42
+ }
43
+ }
123
44
  }
124
- return `linux-clang-${arch}`;
45
+ return out;
125
46
  }
126
47
 
127
- export function clangPath(): string {
128
- return findClangPlusPlus() || "clang++";
129
- }
130
-
131
- export function writeCompileCommands(root: string, srcDir: string): boolean {
132
- const files = collectFiles(srcDir).filter((file) => isCppFile(file)).sort();
133
- const json = `${JSON.stringify(files.map((file) => compileCommandEntry(root, file)), null, "\t")}\n`;
134
- const dest = path.join(root, "compile_commands.json");
135
- if (fs.existsSync(dest) && fs.readFileSync(dest, "utf8") === json) {
136
- return false;
48
+ function readJsonObject(file: string): Record<string, unknown> {
49
+ if (!fs.existsSync(file)) {
50
+ return {};
51
+ }
52
+ try {
53
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
54
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
55
+ return parsed as Record<string, unknown>;
56
+ }
57
+ } catch {
58
+ // ignore
137
59
  }
138
- fs.writeFileSync(dest, json, "utf8");
139
- return true;
60
+ return {};
140
61
  }
141
62
 
142
- export function writeClangd(root: string): boolean {
143
- const flags = clangCompileArgs(root)
144
- .map((flag) => ` - ${flag}`)
145
- .join("\n");
146
- const contents = `CompileFlags:
147
- CompilationDatabase: .
148
- Add:
149
- ${flags}
150
-
151
- Diagnostics:
152
- Suppress: '*'
153
-
154
- ---
155
- If:
156
- PathMatch: .*\\.(cpp|cc|cxx|c|h|hpp|hh)$
157
- CompileFlags:
158
- Add:
159
- - -xc++
160
-
161
- ---
162
- If:
163
- PathMatch: (out|libs)/.*
164
- Index:
165
- Background: Skip
166
- `;
167
- const dest = path.join(root, ".clangd");
168
- if (fs.existsSync(dest) && fs.readFileSync(dest, "utf8") === contents) {
169
- return false;
63
+ function writeJson(file: string, value: Record<string, unknown>): void {
64
+ const json = `${JSON.stringify(value, null, "\t")}\n`;
65
+ fs.mkdirSync(path.dirname(file), { recursive: true });
66
+ if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === json) {
67
+ return;
170
68
  }
171
- fs.writeFileSync(dest, contents, "utf8");
172
- return true;
69
+ fs.writeFileSync(file, json, "utf8");
173
70
  }
174
71
 
175
72
  function writeVscode(root: string): void {
176
- const settings = path.join(root, ".vscode", "settings.json");
177
- mergeJson(settings, {
178
- "C_Cpp.intelliSenseEngine": "Disabled",
179
- "C_Cpp.default.cppStandard": "c++20",
180
- "C_Cpp.default.cStandard": "c17",
181
- "C_Cpp.default.compilerPath": clangPath(),
182
- "C_Cpp.default.intelliSenseMode": intelliSenseMode(),
183
- "C_Cpp.default.includePath": ["${workspaceFolder}/include", "${workspaceFolder}/src"],
184
- "clangd.enable": true,
185
- "clangd.arguments": clangdEditorArgs(),
186
- "files.associations": {
187
- "*.hpp": "cpp",
188
- "*.h": "cpp",
189
- "*.server.cpp": "cpp",
190
- "*.client.cpp": "cpp",
191
- "*.plugin.cpp": "cpp",
192
- "*.legacy.cpp": "cpp",
193
- "*.legacy.server.cpp": "cpp",
194
- "*.legacy.client.cpp": "cpp",
195
- },
73
+ const settingsPath = path.join(root, ".vscode", "settings.json");
74
+ const current = readJsonObject(settingsPath);
75
+ const associations = {
76
+ ...((current["files.associations"] && typeof current["files.associations"] === "object" && !Array.isArray(current["files.associations"]))
77
+ ? (current["files.associations"] as Record<string, unknown>)
78
+ : {}),
79
+ "*.clpp": "clpp",
80
+ "*.clp": "clpp",
81
+ "*.clh": "clpp",
82
+ "*.server.clpp": "clpp",
83
+ "*.client.clpp": "clpp",
84
+ "*.plugin.clpp": "clpp",
85
+ };
86
+ writeJson(settingsPath, {
87
+ ...current,
88
+ "files.associations": associations,
196
89
  });
197
- mergeJson(path.join(root, ".vscode", "extensions.json"), {
198
- recommendations: ["llvm-vs-code-extensions.vscode-clangd"],
90
+
91
+ const extensionsPath = path.join(root, ".vscode", "extensions.json");
92
+ const extensions = readJsonObject(extensionsPath);
93
+ writeJson(extensionsPath, {
94
+ ...extensions,
95
+ recommendations: uniqueStrings(extensions.recommendations),
96
+ unwantedRecommendations: uniqueStrings(extensions.unwantedRecommendations, ["ms-vscode.cpptools"]),
199
97
  });
200
- const props = path.join(root, ".vscode", "c_cpp_properties.json");
201
- const nextProps = {
202
- configurations: [
203
- {
204
- name: "Cluaupp",
205
- compilerPath: clangPath(),
206
- cStandard: "c17",
207
- cppStandard: "c++20",
208
- intelliSenseMode: intelliSenseMode(),
209
- includePath: ["${workspaceFolder}/include", "${workspaceFolder}/src"],
210
- forcedInclude: ["${workspaceFolder}/include/cluaupp/roblox.hpp"],
211
- compileCommands: "${workspaceFolder}/compile_commands.json",
212
- browse: {
213
- path: ["${workspaceFolder}/include", "${workspaceFolder}/src"],
214
- limitSymbolsToIncludedHeaders: true,
215
- },
216
- },
217
- ],
218
- version: 4,
219
- };
220
- const json = `${JSON.stringify(nextProps, null, "\t")}\n`;
221
- fs.mkdirSync(path.dirname(props), { recursive: true });
222
- if (!fs.existsSync(props) || fs.readFileSync(props, "utf8") !== json) {
223
- fs.writeFileSync(props, json, "utf8");
224
- }
225
98
  }
226
99
 
227
- export function diagnosticsFor(source: string, fileName?: string, options: PreprocessOptions = {}): CluauppDiagnostic[] {
100
+ export function diagnosticsFor(source: string, fileName?: string): CluauppDiagnostic[] {
101
+ if (!hasClpp()) {
102
+ return [];
103
+ }
228
104
  try {
229
- const prepared = options.filePath ? preprocess(source, options.filePath, options) : source;
230
- parse(prepared, fileName || "input.cpp");
105
+ compileViaClpp({ source, fileName: fileName || "input.clpp" });
231
106
  return [];
232
107
  } catch (err) {
233
- const error = err as { message?: string; line?: number; col?: number };
234
- const message = error.message ? String(error.message) : "parse error";
108
+ const message = err instanceof Error ? err.message : String(err);
235
109
  const match = message.match(/:(\d+):(\d+):\s*(.*)$/);
236
110
  if (match) {
237
111
  return [{ line: Number(match[1]), col: Number(match[2]), message: match[3], severity: "error" }];
238
112
  }
239
- return [{ line: error.line || 1, col: error.col || 1, message, severity: "error" }];
113
+ return [{ line: 1, col: 1, message, severity: "error" }];
240
114
  }
241
115
  }
242
116
 
243
- export function syncEditorSupport(root: string, config: Partial<ProjectConfig> = {}): void {
244
- const srcDir = path.join(root, config.rootDir || "src");
245
- if (fs.existsSync(srcDir)) {
246
- writeCompileCommands(root, srcDir);
247
- }
248
- writeClangd(root);
117
+ export function syncEditorSupport(root: string, _config: Partial<ProjectConfig> = {}): void {
249
118
  writeVscode(root);
250
- const flags = ["-xc++", "-std=c++20", "-ferror-limit=0", "-Iinclude", "-Isrc", "-include", "include/cluaupp/roblox.hpp"].join("\n") + "\n";
251
- const flagsFile = path.join(root, "compile_flags.txt");
252
- if (!fs.existsSync(flagsFile) || fs.readFileSync(flagsFile, "utf8") !== flags) {
253
- fs.writeFileSync(flagsFile, flags, "utf8");
254
- }
255
119
  }
256
120
 
257
121
  export function installEditorExtension(cluauppRoot = PACKAGE_ROOT): string[] {
@@ -275,12 +139,19 @@ export function installEditorExtension(cluauppRoot = PACKAGE_ROOT): string[] {
275
139
  return installed;
276
140
  }
277
141
 
278
- export async function installEditorSupport(cluauppRoot = PACKAGE_ROOT) {
279
- const local = installEditorExtension(cluauppRoot);
280
- const llvm = ensureLlvm();
281
- reportInstall(llvm, "LLVM clang++");
282
- const clangd = await installMarketplaceExtension(CLANGD_ID, "clangd (LLVM)");
283
- reportInstall(clangd, "clangd");
284
- console.log("cluaupp: C++ completion is clangd — reload the editor (Ctrl+Shift+P → Developer: Reload Window)");
285
- return { local, llvm, clangd };
142
+ export async function installEditorSupport() {
143
+ const local = installEditorExtension();
144
+ if (hasClpp()) {
145
+ const result = spawnSync("clpp", ["install"], { encoding: "utf8", windowsHide: true });
146
+ if (result.status === 0) {
147
+ console.log(String(result.stderr || result.stdout || "clpp install ok").trim());
148
+ } else {
149
+ console.log("cluaupp:", CLPP_INSTALL_HINT);
150
+ }
151
+ } else {
152
+ console.log("cluaupp:", CLPP_INSTALL_HINT);
153
+ }
154
+ return { local };
286
155
  }
156
+
157
+ export const isClppSource = isSourceFile;
package/src/libs.ts CHANGED
@@ -248,7 +248,7 @@ function walk(node, visit) {
248
248
  function modulesFromIncludes(source) {
249
249
  const found = new Set();
250
250
  for (const line of String(source).split(/\r?\n/)) {
251
- const match = line.match(/^\s*#\s*include\s+<cluaupp\/(?:libs\/)?([A-Za-z0-9]+)\.hpp>/);
251
+ const match = line.match(/^\s*#\s*include\s+<(?:clpp|cluaupp)\/(?:libs\/)?([A-Za-z0-9]+)\.(?:clh|hpp)>/);
252
252
  if (!match) {
253
253
  continue;
254
254
  }
package/src/lsp.ts CHANGED
@@ -1,4 +1,3 @@
1
- import path from "node:path";
2
1
  import {
3
2
  createConnection,
4
3
  ProposedFeatures,
@@ -31,10 +30,7 @@ export function start(options: { projectRoot?: string } = {}): void {
31
30
 
32
31
  const publish = (document: TextDocument) => {
33
32
  const file = uriPath(document.uri);
34
- const items = diagnosticsFor(document.getText(), file, {
35
- filePath: file,
36
- includeDirs: [path.dirname(file), path.join(projectRoot, "src"), path.join(projectRoot, "include")],
37
- });
33
+ const items = diagnosticsFor(document.getText(), file);
38
34
  connection.sendDiagnostics({
39
35
  uri: document.uri,
40
36
  diagnostics: items.map((item) => ({
package/src/preprocess.ts CHANGED
@@ -1,180 +1,8 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import type { ModuleInclude, PreprocessOptions } from "./ast.js";
4
-
5
- export const SOURCE_EXTS = [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hh"];
6
-
7
- export function isSourceFile(fileName: string): boolean {
8
- return SOURCE_EXTS.includes(path.extname(fileName).toLowerCase());
9
- }
10
-
11
- export function isHeaderFile(fileName: string): boolean {
12
- return [".h", ".hpp", ".hh"].includes(path.extname(fileName).toLowerCase());
13
- }
14
-
15
- export function toLuauPath(filePath: string): string {
16
- return filePath.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
17
- }
18
-
19
- export function isEngineStub(filePath: string): boolean {
20
- const normalized = filePath.replace(/\\/g, "/").toLowerCase();
21
- return (
22
- normalized.includes("/include/cluaupp/") ||
23
- normalized.includes("/include/cluau/") ||
24
- normalized.endsWith("/roblox.hpp") ||
25
- normalized.endsWith("/roblox.h")
26
- );
27
- }
28
-
29
- function includeVariants(name: string): string[] {
30
- const variants = [name];
31
- if (/\.h$/i.test(name)) {
32
- variants.push(name.replace(/\.h$/i, ".hpp"), name.replace(/\.h$/i, ".hh"));
33
- } else if (/\.hpp$/i.test(name)) {
34
- variants.push(name.replace(/\.hpp$/i, ".h"), name.replace(/\.hpp$/i, ".hh"));
35
- } else if (/\.hh$/i.test(name)) {
36
- variants.push(name.replace(/\.hh$/i, ".h"), name.replace(/\.hh$/i, ".hpp"));
37
- }
38
- return [...new Set(variants)];
39
- }
40
-
41
- export function siblingImplementation(headerPath: string | null | undefined): string | null {
42
- if (!headerPath || !isHeaderFile(headerPath)) {
43
- return null;
44
- }
45
- const base = headerPath.replace(/\.(h|hpp|hh)$/i, "");
46
- for (const ext of [".cpp", ".cc", ".cxx", ".c"]) {
47
- if (fs.existsSync(base + ext)) {
48
- return base + ext;
49
- }
50
- }
51
- return null;
52
- }
53
-
54
- export function siblingHeader(implPath: string | null | undefined): string | null {
55
- if (!implPath || isHeaderFile(implPath)) {
56
- return null;
57
- }
58
- const base = String(implPath).replace(/\.(cpp|cc|cxx|c)$/i, "");
59
- if (base === String(implPath)) {
60
- return null;
61
- }
62
- for (const ext of [".h", ".hpp", ".hh"]) {
63
- if (fs.existsSync(base + ext)) {
64
- return base + ext;
65
- }
66
- }
67
- return null;
68
- }
69
-
70
- export function implOutName(rel: string): string {
71
- return String(rel)
72
- .replace(/\\/g, "/")
73
- .replace(/\.(cpp|cc|cxx|c)$/i, "Impl.luau");
74
- }
75
-
76
- export function resolveInclude(name: string, fromFile: string, includeDirs?: string[]): string | null {
77
- const bases = [path.dirname(fromFile), ...(includeDirs || [])];
78
- for (const base of bases) {
79
- for (const variant of includeVariants(name)) {
80
- const candidate = path.resolve(base, variant);
81
- if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
82
- return candidate;
83
- }
84
- }
85
- }
86
- return null;
87
- }
88
-
89
- export function scanHeaderExports(source: string): { consts: string[]; structs: string[]; hasProtos: boolean } {
90
- const consts: string[] = [];
91
- const structs: string[] = [];
92
- for (const match of String(source).matchAll(/\bconst\s+(?:int|bool|float|double|auto|string)\s+(\w+)/g)) {
93
- if (!consts.includes(match[1])) {
94
- consts.push(match[1]);
95
- }
96
- }
97
- for (const match of String(source).matchAll(/\bstruct\s+(\w+)/g)) {
98
- if (!structs.includes(match[1])) {
99
- structs.push(match[1]);
100
- }
101
- }
102
- const hasProtos = /\b(?:void|int|bool|float|double|auto|string|[\w:]+)\s+\w+\s*\([^;]*\)\s*;/.test(String(source));
103
- return { consts, structs, hasProtos };
104
- }
105
-
106
- function pushModuleInclude(options: PreprocessOptions, resolved: string, impl: string | null): void {
107
- options.moduleIncludes = options.moduleIncludes || [];
108
- const srcDir = options.srcDir || null;
109
- const moduleFile = impl || resolved;
110
- const rel = srcDir ? path.relative(srcDir, moduleFile).replace(/\\/g, "/") : path.basename(moduleFile);
111
- const headerText = fs.readFileSync(resolved, "utf8");
112
- options.moduleIncludes.push({
113
- name: path.basename(resolved).replace(/\.(h|hpp|hh)$/i, ""),
114
- outRel: rel.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau"),
115
- header: resolved,
116
- impl: impl || null,
117
- exports: scanHeaderExports(headerText),
118
- });
119
- }
120
-
121
- export function applyPragmas(source: string, options: PreprocessOptions): void {
122
- let nstrict = false;
123
- let strict = false;
124
- for (const line of String(source).split(/\r?\n/)) {
125
- if (/^\s*#\s*pragma\s+nstrict\b/i.test(line)) {
126
- nstrict = true;
127
- } else if (/^\s*#\s*pragma\s+strict\b/i.test(line)) {
128
- strict = true;
129
- }
130
- }
131
- if (nstrict) {
132
- options.strict = false;
133
- } else if (strict) {
134
- options.strict = true;
135
- }
136
- }
137
-
138
- export function preprocess(source: string, filePath: string | null, options: PreprocessOptions = {}): string {
139
- if (!options.pragmaResolved) {
140
- options.pragmaResolved = true;
141
- applyPragmas(source, options);
142
- }
143
- const seen = options.seen || new Set<string>();
144
- options.seen = seen;
145
- const includeDirs = options.includeDirs || [];
146
- const resolvedSelf = filePath ? path.resolve(filePath) : null;
147
- if (resolvedSelf) {
148
- seen.add(resolvedSelf);
149
- }
150
-
151
- const lines = String(source).split(/\r?\n/);
152
- const out: string[] = [];
153
-
154
- for (const line of lines) {
155
- const quoted = line.match(/^\s*#\s*include\s+"([^"]+)"/);
156
- if (quoted) {
157
- const resolved = resolveInclude(quoted[1], filePath || process.cwd(), includeDirs);
158
- if (!resolved || isEngineStub(resolved) || seen.has(resolved)) {
159
- continue;
160
- }
161
- const impl = siblingImplementation(resolved);
162
- const including = filePath ? path.resolve(filePath) : null;
163
- const ownHeader = Boolean(impl && including && path.resolve(impl) === including);
164
- if (!ownHeader) {
165
- pushModuleInclude(options, resolved, impl);
166
- continue;
167
- }
168
- seen.add(resolved);
169
- const inner = fs.readFileSync(resolved, "utf8");
170
- out.push(preprocess(inner, resolved, { ...options, seen }));
171
- continue;
172
- }
173
- if (/^\s*#\s*include\s+</.test(line)) {
174
- continue;
175
- }
176
- out.push(line);
177
- }
178
-
179
- return out.join("\n");
180
- }
1
+ export {
2
+ SOURCE_EXTS,
3
+ isSourceFile,
4
+ isHeaderFile,
5
+ isEngineStub,
6
+ toLuauPath,
7
+ siblingHeader,
8
+ } from "./clpp/paths.js";
@@ -40,7 +40,7 @@ export interface ArchitectureReport {
40
40
  reasoning: string[];
41
41
  }
42
42
 
43
- const EXT = "(cpp|cc|cxx|c|h|hpp|hh)";
43
+ const EXT = "(clpp|clp|clh)";
44
44
  const EXT_REGEX = new RegExp(`\\.${EXT}$`, "i");
45
45
  const SCORE_THRESHOLD = 2;
46
46
 
package/src/transpile.ts CHANGED
@@ -1,64 +1,21 @@
1
1
  import path from "node:path";
2
- import type { CompileOptions, CompileServiceResult, TranspilerState } from "./types.js";
3
- import { ASTCollector } from "./parser/collector.js";
4
- import { parseCpp } from "./parser/index.js";
5
- import { LuauCodeEmitter } from "./emitter/luau-codegen.js";
2
+ import type { CompileOptions, CompileServiceResult } from "./types.js";
6
3
  import { compileService } from "./compile.js";
7
- import { determineArchitecture } from "./system-understander.js";
8
4
  import type { RojoMapper } from "./utils/rojo-mapper.js";
9
5
 
10
- function shouldMerge(name: string, contents: string): boolean {
11
- const file = String(name || "").replace(/\\/g, "/");
12
- if (/\.json$/i.test(file) || /\.meta\.json$/i.test(file)) {
13
- return false;
14
- }
15
- const trimmed = String(contents || "").trim();
16
- return !(trimmed.startsWith("{") || trimmed.startsWith("["));
17
- }
18
-
19
- export function collectState(source: string, mapper: RojoMapper, options: CompileOptions): TranspilerState {
20
- const tree = parseCpp(source);
21
- if (!tree) {
22
- const state = LuauCodeEmitter.emptyState(options.strict === true);
23
- return state;
24
- }
25
- const collector = new ASTCollector(mapper, {
26
- fileName: options.relativeName || options.filePath || "input.cpp",
27
- sourcePath: options.filePath,
28
- srcDir: options.srcDir,
29
- outDir: options.outDir,
30
- strict: options.strict,
31
- });
32
- const state = collector.collect(tree.rootNode);
33
- const report = determineArchitecture(options.relativeName || options.filePath || "input.cpp", null, tree.rootNode);
34
- for (const service of report.injectedServices) {
35
- state.robloxServices.add(service);
36
- }
37
- for (const line of report.reasoning) {
38
- state.headerLines.push(`-- ${line}`);
39
- }
40
- return state;
41
- }
42
-
43
- export function transpileSource(source: string, fileName: string, options: CompileOptions, mapper: RojoMapper): CompileServiceResult {
44
- const state = collectState(source, mapper, options);
45
- if (state.strict) {
46
- options.strict = true;
47
- }
48
- const compiled = compileService(source, fileName, options);
49
- return {
50
- ...compiled,
51
- files: compiled.files.map((file) => ({
52
- ...file,
53
- contents: shouldMerge(file.name, file.contents) ? LuauCodeEmitter.merge(state, file.contents) : file.contents,
54
- })),
55
- };
6
+ export function transpileSource(
7
+ source: string,
8
+ fileName: string,
9
+ options: CompileOptions,
10
+ _mapper?: RojoMapper,
11
+ ): CompileServiceResult {
12
+ return compileService(source, fileName, options);
56
13
  }
57
14
 
58
15
  export function outputNameFor(inputFile: string, outputPath: string): string {
59
16
  if (/\.luau$/i.test(outputPath)) {
60
17
  return outputPath;
61
18
  }
62
- const base = path.basename(inputFile).replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
19
+ const base = path.basename(inputFile).replace(/\.(clpp|clp|clh)$/i, ".luau");
63
20
  return path.join(outputPath, base);
64
21
  }