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.
- package/CHANGELOG.md +14 -75
- package/README.md +42 -88
- package/docs/README.md +25 -21
- package/docs/architecture.md +13 -76
- package/docs/cli.md +12 -64
- package/docs/comparison.md +6 -24
- package/docs/config.md +6 -7
- package/docs/cpp-advanced.md +6 -3
- package/docs/cpp-types.md +2 -0
- package/docs/examples/combat.md +19 -227
- package/docs/examples/hud.md +13 -81
- package/docs/examples/index.md +9 -16
- package/docs/examples/leaderstats.md +44 -115
- package/docs/getting-started.md +20 -135
- package/docs/intellisense.md +5 -42
- package/docs/intro.md +16 -15
- package/docs/libraries/dataservice.md +4 -1
- package/docs/libraries/index.md +2 -1
- package/docs/libraries/more.md +3 -1
- package/docs/libraries/ui.md +76 -0
- package/docs/migration.md +38 -0
- package/docs/oop/file-tags.md +12 -36
- package/docs/oop/index.md +18 -11
- package/docs/roblox-api.md +4 -56
- package/docs/syntax.md +54 -179
- package/editors/vscode/extension.js +1 -1
- package/editors/vscode/package.json +4 -4
- package/examples/README.md +5 -7
- package/examples/game/.vscode/extensions.json +2 -2
- package/examples/game/.vscode/settings.json +6 -23
- package/examples/game/src/client/hud.client.clpp +54 -0
- package/examples/game/src/client/init.client.clpp +6 -0
- package/examples/game/src/server/combat.server.clpp +85 -0
- package/examples/game/src/server/leaderstats.server.clpp +28 -0
- package/examples/game/src/shared/PlayerData.clh +20 -0
- package/examples/game/src/shared/config.clp +6 -0
- package/examples/game/src/shared/features.clp +88 -0
- package/generated/api.js +15 -1
- package/generated/cli.js +40 -24
- package/generated/clpp/contract.js +5 -0
- package/generated/clpp/index.js +27 -0
- package/generated/clpp/paths.js +121 -0
- package/generated/clpp/postprocess.js +78 -0
- package/generated/clpp/runner.js +102 -0
- package/generated/compile.js +26 -138
- package/generated/emit.js +79 -3
- package/generated/intellisense.js +83 -207
- package/generated/lex.js +3 -0
- package/generated/libs.js +1 -1
- package/generated/lsp.js +1 -8
- package/generated/parse.js +119 -24
- package/generated/preprocess.js +8 -179
- package/generated/system-understander.js +1 -1
- package/generated/transpile.js +3 -50
- package/generated/utils/project.js +13 -28
- package/include/{cluaupp/datatypes.hpp → clpp/datatypes.clh} +5 -1
- package/include/{cluaupp/generated/instances.hpp → clpp/generated/instances.clh} +1 -1
- package/include/{cluaupp/libs/chrono.hpp → clpp/libs/chrono.clh} +9 -9
- package/include/{cluaupp/libs/cmdr.hpp → clpp/libs/cmdr.clh} +4 -4
- package/include/{cluaupp/libs/dataservice.hpp → clpp/libs/dataservice.clh} +8 -15
- package/include/{cluaupp/libs/display.hpp → clpp/libs/display.clh} +1 -2
- package/include/{cluaupp/libs/ezvisual.hpp → clpp/libs/ezvisual.clh} +1 -3
- package/include/{cluaupp/libs/formatnumber.hpp → clpp/libs/formatnumber.clh} +2 -3
- package/include/{cluaupp/libs/fusion.hpp → clpp/libs/fusion.clh} +5 -5
- package/include/{cluaupp/libs/iris.hpp → clpp/libs/iris.clh} +2 -2
- package/include/{cluaupp/libs/janitor.hpp → clpp/libs/janitor.clh} +1 -2
- package/include/{cluaupp/libs/module3d.hpp → clpp/libs/module3d.clh} +2 -4
- package/include/{cluaupp/libs/net.hpp → clpp/libs/net.clh} +2 -4
- package/include/{cluaupp/libs/promise.hpp → clpp/libs/promise.clh} +1 -4
- package/include/{cluaupp/libs/spring.hpp → clpp/libs/spring.clh} +1 -2
- package/include/{cluaupp/libs/statemachine.hpp → clpp/libs/statemachine.clh} +2 -3
- package/include/{cluaupp/libs/stickybillboard.hpp → clpp/libs/stickybillboard.clh} +1 -2
- package/include/{cluaupp/libs/topbarplus.hpp → clpp/libs/topbarplus.clh} +3 -4
- package/include/clpp/libs.clh +24 -0
- package/include/clpp/roblox.clh +31 -0
- package/package.json +20 -16
- package/src/api.ts +15 -1
- package/src/cli.ts +39 -25
- package/src/clpp/contract.ts +39 -0
- package/src/clpp/index.ts +18 -0
- package/src/clpp/paths.ts +117 -0
- package/src/clpp/postprocess.ts +93 -0
- package/src/clpp/runner.ts +102 -0
- package/src/compile.ts +26 -149
- package/src/intellisense.ts +82 -211
- package/src/libs.ts +1 -1
- package/src/lsp.ts +1 -5
- package/src/preprocess.ts +8 -180
- package/src/system-understander.ts +1 -1
- package/src/transpile.ts +9 -52
- package/src/utils/project.ts +12 -28
- package/templates/game/.vscode/extensions.json +2 -2
- package/templates/game/.vscode/settings.json +6 -23
- package/templates/game/src/client/init.client.clpp +6 -0
- package/templates/game/src/server/leaderstats.server.clpp +29 -0
- package/templates/game/src/shared/config.clp +6 -0
- package/examples/game/.clangd +0 -25
- package/examples/game/.vscode/c_cpp_properties.json +0 -27
- package/examples/game/compile_flags.txt +0 -6
- package/examples/game/src/client/hud.client.cpp +0 -12
- package/examples/game/src/client/init.client.cpp +0 -6
- package/examples/game/src/server/combat.server.cpp +0 -23
- package/examples/game/src/server/leaderstats.server.cpp +0 -29
- package/examples/game/src/shared/config.cpp +0 -5
- package/examples/game/src/shared/config.h +0 -5
- package/include/cluaupp/libs.hpp +0 -25
- package/include/cluaupp/roblox.hpp +0 -47
- package/src/architecture.ts +0 -500
- package/src/ast.ts +0 -37
- package/src/emit.ts +0 -531
- package/src/emitter/luau-codegen.ts +0 -187
- package/src/emitter/translators.ts +0 -168
- package/src/headers.ts +0 -233
- package/src/layout.ts +0 -114
- package/src/lex.ts +0 -155
- package/src/parse.ts +0 -630
- package/src/parser/collector.ts +0 -148
- package/src/parser/index.ts +0 -27
- package/src/tree-sitter-cpp.d.ts +0 -4
- package/src/understand.ts +0 -322
- package/templates/game/.vscode/c_cpp_properties.json +0 -27
- package/templates/game/src/client/init.client.cpp +0 -6
- package/templates/game/src/server/leaderstats.server.cpp +0 -29
- package/templates/game/src/shared/config.cpp +0 -5
- package/templates/game/src/shared/config.h +0 -5
- /package/include/{cluaupp/generated/enums.hpp → clpp/generated/enums.clh} +0 -0
- /package/include/{cluaupp/libs/math.hpp → clpp/libs/math.clh} +0 -0
- /package/include/{cluaupp/libs/twinkle.hpp → clpp/libs/twinkle.clh} +0 -0
- /package/include/{cluaupp/libs/vfx.hpp → clpp/libs/vfx.clh} +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const SOURCE_EXTS = [".clpp", ".clp", ".clh"];
|
|
5
|
+
|
|
6
|
+
export function isSourceFile(fileName: string): boolean {
|
|
7
|
+
return SOURCE_EXTS.includes(path.extname(fileName).toLowerCase());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isEngineStub(filePath: string): boolean {
|
|
11
|
+
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
12
|
+
return (
|
|
13
|
+
normalized.includes("/include/cluaupp/") ||
|
|
14
|
+
normalized.includes("/include/clpp/") ||
|
|
15
|
+
normalized.includes("/include/cluau/") ||
|
|
16
|
+
normalized.includes("/stdlib/") ||
|
|
17
|
+
normalized.endsWith("/roblox.hpp") ||
|
|
18
|
+
normalized.endsWith("/roblox.h") ||
|
|
19
|
+
normalized.endsWith("/roblox.clh")
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isHeaderFile(fileName: string): boolean {
|
|
24
|
+
return path.extname(fileName).toLowerCase() === ".clh";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isImplFile(fileName: string): boolean {
|
|
28
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
29
|
+
return ext === ".clp" || ext === ".clpp";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function toLuauPath(filePath: string): string {
|
|
33
|
+
return String(filePath).replace(/\.(clpp|clp|clh)$/i, ".luau");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function fileStem(fileName: string): string {
|
|
37
|
+
let name = path.basename(fileName);
|
|
38
|
+
const lower = name.toLowerCase();
|
|
39
|
+
for (const ext of [".clpp", ".clp", ".clh"]) {
|
|
40
|
+
if (lower.endsWith(ext)) {
|
|
41
|
+
name = name.slice(0, -ext.length);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const tagged = name.toLowerCase();
|
|
46
|
+
for (const tag of [".legacy.server", ".legacy.client", ".legacy", ".server", ".client", ".plugin"]) {
|
|
47
|
+
if (tagged.endsWith(tag)) {
|
|
48
|
+
name = name.slice(0, -tag.length);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return name;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function scriptKind(fileName: string): "server" | "client" | "plugin" | "legacy" | "module" | "header" {
|
|
56
|
+
const name = path.basename(fileName).toLowerCase();
|
|
57
|
+
if (name.endsWith(".clh")) {
|
|
58
|
+
return "header";
|
|
59
|
+
}
|
|
60
|
+
if (name.includes(".legacy.")) {
|
|
61
|
+
return "legacy";
|
|
62
|
+
}
|
|
63
|
+
if (name.includes(".server.")) {
|
|
64
|
+
return "server";
|
|
65
|
+
}
|
|
66
|
+
if (name.includes(".client.")) {
|
|
67
|
+
return "client";
|
|
68
|
+
}
|
|
69
|
+
if (name.includes(".plugin.")) {
|
|
70
|
+
return "plugin";
|
|
71
|
+
}
|
|
72
|
+
return "module";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function emitKind(fileName: string): "flat" | "module" | "legacy" {
|
|
76
|
+
const kind = scriptKind(fileName);
|
|
77
|
+
if (kind === "legacy") {
|
|
78
|
+
return "legacy";
|
|
79
|
+
}
|
|
80
|
+
if (kind === "module" || kind === "header") {
|
|
81
|
+
return "module";
|
|
82
|
+
}
|
|
83
|
+
return "flat";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function isTaggedScript(fileName: string): boolean {
|
|
87
|
+
const kind = scriptKind(fileName);
|
|
88
|
+
return kind === "server" || kind === "client" || kind === "plugin" || kind === "legacy";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function siblingHeader(implPath: string | null | undefined): string | null {
|
|
92
|
+
if (!implPath || isHeaderFile(implPath)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const dir = path.dirname(implPath);
|
|
96
|
+
const stem = fileStem(implPath);
|
|
97
|
+
const candidate = path.join(dir, `${stem}.clh`);
|
|
98
|
+
if (fs.existsSync(candidate)) {
|
|
99
|
+
return candidate;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function collectSources(dir: string, files: string[] = []): string[] {
|
|
105
|
+
if (!fs.existsSync(dir)) {
|
|
106
|
+
return files;
|
|
107
|
+
}
|
|
108
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
109
|
+
const full = path.join(dir, entry.name);
|
|
110
|
+
if (entry.isDirectory()) {
|
|
111
|
+
collectSources(full, files);
|
|
112
|
+
} else if (isSourceFile(entry.name)) {
|
|
113
|
+
files.push(full);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return files;
|
|
117
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { insertPreamble, MODULES, requireCluauppLib } from "../libs.js";
|
|
2
|
+
import type { CompileArtifact } from "./contract.js";
|
|
3
|
+
import { isTaggedScript } from "./paths.js";
|
|
4
|
+
|
|
5
|
+
export type PostprocessOptions = {
|
|
6
|
+
relativeName: string;
|
|
7
|
+
outName: string;
|
|
8
|
+
srcDir?: string;
|
|
9
|
+
rootDir?: string;
|
|
10
|
+
strict?: boolean;
|
|
11
|
+
skipInit?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function insertAfterHeader(luau: string, block: string): string {
|
|
15
|
+
const match = String(luau).match(/^(?:--[^\n]*\n)+/);
|
|
16
|
+
if (!match) {
|
|
17
|
+
return `${block}\n${luau}`;
|
|
18
|
+
}
|
|
19
|
+
const insertAt = match[0].length;
|
|
20
|
+
const rest = luau.slice(insertAt).replace(/^\n*/, "\n");
|
|
21
|
+
return `${luau.slice(0, insertAt)}\n${block}\n${rest}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function rewriteClppLibs(luau: string): string {
|
|
25
|
+
return String(luau).replace(/require\(\s*ClppLibs\.([A-Za-z0-9_]+)\s*\)/g, (_all, name: string) => {
|
|
26
|
+
return requireCluauppLib(name);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensureReplicatedStorage(luau: string): string {
|
|
31
|
+
if (!luau.includes("CluauppLibs")) {
|
|
32
|
+
return luau;
|
|
33
|
+
}
|
|
34
|
+
if (/GetService\(\s*"ReplicatedStorage"\s*\)/.test(luau)) {
|
|
35
|
+
return luau;
|
|
36
|
+
}
|
|
37
|
+
return insertAfterHeader(luau, 'const ReplicatedStorage = game:GetService("ReplicatedStorage")');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stripInitCall(luau: string): string {
|
|
41
|
+
return String(luau).replace(/\ninit\(\)\s*\n?$/m, "\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ensureStrict(luau: string, strict?: boolean): string {
|
|
45
|
+
if (!strict || /^\s*--!strict\b/m.test(luau)) {
|
|
46
|
+
return luau;
|
|
47
|
+
}
|
|
48
|
+
if (luau.startsWith("--")) {
|
|
49
|
+
return `--!strict\n${luau}`;
|
|
50
|
+
}
|
|
51
|
+
return `--!strict\n${luau}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function librarySpecs(names: string[] | undefined) {
|
|
55
|
+
const specs: Array<{ file: string; bind: string }> = [];
|
|
56
|
+
const seen = new Set<string>();
|
|
57
|
+
for (const name of names || []) {
|
|
58
|
+
const spec = (MODULES as Record<string, { file: string; bind: string }>)[name];
|
|
59
|
+
if (!spec || seen.has(spec.bind)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
seen.add(spec.bind);
|
|
63
|
+
specs.push(spec);
|
|
64
|
+
}
|
|
65
|
+
return specs;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function missingLibraries(luau: string, names: string[] | undefined) {
|
|
69
|
+
return librarySpecs(names).filter((spec) => {
|
|
70
|
+
return !luau.includes(`CluauppLibs.${spec.file}`) && !new RegExp(`\\bconst ${spec.bind}\\b`).test(luau);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function shouldSkipInit(fileName: string, hasSiblingHeader: boolean): boolean {
|
|
75
|
+
if (!hasSiblingHeader) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return !isTaggedScript(fileName);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function clppLuauToGame(artifact: CompileArtifact, options: PostprocessOptions): string {
|
|
82
|
+
let luau = rewriteClppLibs(artifact.luau || "");
|
|
83
|
+
luau = ensureReplicatedStorage(luau);
|
|
84
|
+
if (options.skipInit) {
|
|
85
|
+
luau = stripInitCall(luau);
|
|
86
|
+
}
|
|
87
|
+
luau = ensureStrict(luau, options.strict);
|
|
88
|
+
const missing = missingLibraries(luau, artifact.libraries);
|
|
89
|
+
if (missing.length > 0) {
|
|
90
|
+
luau = insertPreamble(luau, [], missing, options.outName);
|
|
91
|
+
}
|
|
92
|
+
return luau;
|
|
93
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { CLPP_INSTALL_HINT, type CompileArtifact, type CompileRequest, type LanguageManifest } from "./contract.js";
|
|
3
|
+
|
|
4
|
+
const MISSING_CLPP = `cluaupp: clpp not found. ${CLPP_INSTALL_HINT}`;
|
|
5
|
+
|
|
6
|
+
function whichClpp(): string | null {
|
|
7
|
+
if (process.env.CLPP_PATH) {
|
|
8
|
+
return process.env.CLPP_PATH;
|
|
9
|
+
}
|
|
10
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
11
|
+
const found = spawnSync(finder, ["clpp"], { encoding: "utf8", windowsHide: true });
|
|
12
|
+
if (found.status !== 0) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const line = String(found.stdout || "")
|
|
16
|
+
.split(/\r?\n/)
|
|
17
|
+
.map((item) => item.trim())
|
|
18
|
+
.find((item) => item.length > 0);
|
|
19
|
+
return line || "clpp";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function hasClpp(): boolean {
|
|
23
|
+
return Boolean(whichClpp());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveClppBinary(): string {
|
|
27
|
+
const bin = whichClpp();
|
|
28
|
+
if (!bin) {
|
|
29
|
+
throw new Error(MISSING_CLPP);
|
|
30
|
+
}
|
|
31
|
+
return bin;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseArtifact(stdout: string, fileName: string, fallbackError: string): CompileArtifact {
|
|
35
|
+
const text = String(stdout || "").trim();
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(text) as CompileArtifact;
|
|
38
|
+
if (parsed && typeof parsed === "object") {
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// fall through
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
luau: "",
|
|
47
|
+
fileName,
|
|
48
|
+
outputHint: "",
|
|
49
|
+
scriptKind: null,
|
|
50
|
+
isScript: false,
|
|
51
|
+
isHeader: false,
|
|
52
|
+
rojoClass: "ModuleScript",
|
|
53
|
+
libraries: [],
|
|
54
|
+
error: fallbackError || "clpp returned invalid JSON",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function compileViaClpp(request: CompileRequest): CompileArtifact {
|
|
59
|
+
const bin = resolveClppBinary();
|
|
60
|
+
const payload = JSON.stringify({
|
|
61
|
+
source: request.source,
|
|
62
|
+
fileName: request.fileName,
|
|
63
|
+
strict: request.strict,
|
|
64
|
+
});
|
|
65
|
+
const result = spawnSync(bin, ["api", "compile"], {
|
|
66
|
+
input: payload,
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
cwd: request.cwd,
|
|
69
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
70
|
+
windowsHide: true,
|
|
71
|
+
});
|
|
72
|
+
const stderr = String(result.stderr || "").trim();
|
|
73
|
+
const artifact = parseArtifact(result.stdout, request.fileName, stderr || result.error?.message || "clpp api compile failed");
|
|
74
|
+
if (!artifact.ok) {
|
|
75
|
+
throw new Error(artifact.error || stderr || `clpp failed (${result.status})`);
|
|
76
|
+
}
|
|
77
|
+
return artifact;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function clppManifest(): LanguageManifest {
|
|
81
|
+
const bin = resolveClppBinary();
|
|
82
|
+
const result = spawnSync(bin, ["api", "manifest"], {
|
|
83
|
+
encoding: "utf8",
|
|
84
|
+
windowsHide: true,
|
|
85
|
+
});
|
|
86
|
+
if (result.status !== 0) {
|
|
87
|
+
throw new Error(String(result.stderr || result.error?.message || "clpp api manifest failed"));
|
|
88
|
+
}
|
|
89
|
+
return JSON.parse(result.stdout) as LanguageManifest;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function clppVersion(): string | null {
|
|
93
|
+
const bin = whichClpp();
|
|
94
|
+
if (!bin) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const result = spawnSync(bin, ["--version"], { encoding: "utf8", windowsHide: true });
|
|
98
|
+
if (result.status !== 0) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return String(result.stdout || "").trim() || null;
|
|
102
|
+
}
|
package/src/compile.ts
CHANGED
|
@@ -1,160 +1,37 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
|
-
import { parse } from "./parse.js";
|
|
4
|
-
import { emit } from "./emit.js";
|
|
5
|
-
import { preprocess, isHeaderFile, toLuauPath, siblingImplementation, siblingHeader, implOutName } from "./preprocess.js";
|
|
6
|
-
import { collectLibraries, insertPreamble } from "./libs.js";
|
|
7
|
-
import { planOutput } from "./architecture.js";
|
|
8
|
-
import { emitHeaderModule, primaryHeaderName } from "./headers.js";
|
|
9
|
-
import type { AstNode, AstProgram, ModuleInclude, PreprocessOptions } from "./ast.js";
|
|
10
2
|
import type { CompileOptions, CompileServiceResult } from "./types.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (/\.json$/i.test(file) || /\.meta\.json$/i.test(file)) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
const trimmed = String(contents || "").trim();
|
|
18
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function attachRequires(contents: string, source: string, ast: AstProgram, options: PreprocessOptions, outName: string): string {
|
|
25
|
-
if (!shouldAttachRequires(outName, contents)) {
|
|
26
|
-
return contents;
|
|
27
|
-
}
|
|
28
|
-
const libraries = String(contents).includes("CluauppLibs") ? [] : collectLibraries(source, ast);
|
|
29
|
-
return insertPreamble(contents, options.moduleIncludes || [], libraries, outName);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function posixRel(from: string, file: string): string {
|
|
33
|
-
return path.relative(from, file).replace(/\\/g, "/");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function declKey(decl: AstNode): string {
|
|
37
|
-
return `${decl.owner || ""}::${decl.name}`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function mergeSiblingImpl(ast: AstProgram, implPath: string, options: PreprocessOptions): void {
|
|
41
|
-
const implOptions: PreprocessOptions = {
|
|
42
|
-
...options,
|
|
43
|
-
moduleIncludes: [],
|
|
44
|
-
seen: new Set(),
|
|
45
|
-
pragmaResolved: false,
|
|
46
|
-
};
|
|
47
|
-
const prepared = preprocess(fs.readFileSync(implPath, "utf8"), implPath, implOptions);
|
|
48
|
-
const implAst = parse(prepared, path.basename(implPath)) as AstProgram;
|
|
49
|
-
if (!Array.isArray(ast.body)) {
|
|
50
|
-
ast.body = [];
|
|
51
|
-
}
|
|
52
|
-
const existing = new Set(
|
|
53
|
-
(ast.body || [])
|
|
54
|
-
.filter((decl) => decl && (decl.type === "function" || decl.type === "proto") && decl.name)
|
|
55
|
-
.map(declKey),
|
|
56
|
-
);
|
|
57
|
-
for (const decl of implAst.body || []) {
|
|
58
|
-
if ((decl.type !== "function" && decl.type !== "proto") || !decl.name) {
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const key = declKey(decl);
|
|
62
|
-
if (existing.has(key)) {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
existing.add(key);
|
|
66
|
-
ast.body.push({
|
|
67
|
-
type: "proto",
|
|
68
|
-
name: decl.name,
|
|
69
|
-
owner: decl.owner,
|
|
70
|
-
returnType: decl.returnType,
|
|
71
|
-
params: decl.params,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function wireHeaderImpl(ast: AstProgram, fileName: string, options: PreprocessOptions): void {
|
|
77
|
-
const headerPath = options.filePath || null;
|
|
78
|
-
const impl = headerPath ? siblingImplementation(headerPath) : null;
|
|
79
|
-
if (!impl) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
mergeSiblingImpl(ast, impl, options);
|
|
83
|
-
const typeName = primaryHeaderName(ast, options.relativeName || fileName);
|
|
84
|
-
const implRel = options.srcDir ? posixRel(options.srcDir, impl) : path.basename(impl).replace(/\\/g, "/");
|
|
85
|
-
const implOut = implOutName(implRel);
|
|
86
|
-
options.headerImplName = `${typeName}Module`;
|
|
87
|
-
options.moduleIncludes = options.moduleIncludes || [];
|
|
88
|
-
if (!options.moduleIncludes.some((spec: ModuleInclude) => spec.name === options.headerImplName)) {
|
|
89
|
-
options.moduleIncludes.push({
|
|
90
|
-
name: options.headerImplName,
|
|
91
|
-
outRel: implOut,
|
|
92
|
-
exports: { consts: [], structs: [] },
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function compileHeader(source: string, fileName: string, options: PreprocessOptions, ast: AstProgram): CompileServiceResult {
|
|
98
|
-
wireHeaderImpl(ast, fileName, options);
|
|
99
|
-
const rel = (options.relativeName || fileName || "header.h").replace(/\\/g, "/");
|
|
100
|
-
const outName = toLuauPath(rel);
|
|
101
|
-
const luau = attachRequires(emitHeaderModule(ast, options), source, ast, options, outName);
|
|
102
|
-
return {
|
|
103
|
-
kind: "module",
|
|
104
|
-
plan: { kind: "module", strict: options.strict === true },
|
|
105
|
-
files: [{ name: outName, contents: luau }],
|
|
106
|
-
stale: [],
|
|
107
|
-
};
|
|
108
|
-
}
|
|
3
|
+
import { compileViaClpp } from "./clpp/runner.js";
|
|
4
|
+
import { clppLuauToGame, shouldSkipInit } from "./clpp/postprocess.js";
|
|
5
|
+
import { emitKind, siblingHeader, toLuauPath } from "./clpp/paths.js";
|
|
109
6
|
|
|
110
7
|
export function compileSource(source: string, fileName?: string, options: CompileOptions = {}): string {
|
|
111
|
-
|
|
112
|
-
opts.moduleIncludes = opts.moduleIncludes || [];
|
|
113
|
-
const prepared = preprocess(source, opts.filePath || null, opts);
|
|
114
|
-
const ast = parse(prepared, fileName || "input.cpp");
|
|
115
|
-
if (isHeaderFile(fileName || "") || isHeaderFile(opts.filePath || "")) {
|
|
116
|
-
return compileHeader(source, fileName || "header.h", opts, ast).files[0].contents;
|
|
117
|
-
}
|
|
118
|
-
const luau = emit(ast, opts);
|
|
119
|
-
return attachRequires(luau, source, ast, opts, opts.outName || fileName || "input.cpp");
|
|
8
|
+
return compileService(source, fileName, options).files[0]?.contents ?? "";
|
|
120
9
|
}
|
|
121
10
|
|
|
122
11
|
export function compileService(source: string, fileName?: string, options: CompileOptions = {}): CompileServiceResult {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
12
|
+
const relative = (options.relativeName || fileName || "input.clpp").replace(/\\/g, "/");
|
|
13
|
+
const outName = options.outName || toLuauPath(relative);
|
|
14
|
+
const header = options.filePath ? siblingHeader(options.filePath) : null;
|
|
15
|
+
const skipInit = options.skipInit === true || shouldSkipInit(relative, Boolean(header));
|
|
16
|
+
const diskPath = options.filePath ? path.resolve(options.filePath) : null;
|
|
17
|
+
const artifact = compileViaClpp({
|
|
18
|
+
source,
|
|
19
|
+
fileName: diskPath ? diskPath.replace(/\\/g, "/") : relative,
|
|
20
|
+
strict: options.strict,
|
|
21
|
+
cwd: diskPath ? path.dirname(diskPath) : options.srcDir,
|
|
22
|
+
});
|
|
23
|
+
const luau = clppLuauToGame(artifact, {
|
|
24
|
+
relativeName: relative,
|
|
25
|
+
outName,
|
|
26
|
+
srcDir: options.srcDir,
|
|
27
|
+
rootDir: options.rootDir,
|
|
28
|
+
strict: options.strict,
|
|
29
|
+
skipInit,
|
|
139
30
|
});
|
|
140
|
-
if (planned.files) {
|
|
141
|
-
return {
|
|
142
|
-
...planned,
|
|
143
|
-
files: planned.files.map((file: { name: string; contents: string }) => ({
|
|
144
|
-
...file,
|
|
145
|
-
contents: attachRequires(file.contents, source, ast, opts, file.name),
|
|
146
|
-
})),
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
const luau = attachRequires(emit(ast, opts), source, ast, opts, planned.outName || opts.outName || fileName || "input.cpp");
|
|
150
|
-
const name =
|
|
151
|
-
planned.outName ||
|
|
152
|
-
opts.outName ||
|
|
153
|
-
String(fileName || "input.cpp").replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
|
|
154
31
|
return {
|
|
155
|
-
kind:
|
|
156
|
-
plan:
|
|
157
|
-
files: [{ name, contents: luau }],
|
|
158
|
-
stale:
|
|
32
|
+
kind: emitKind(relative),
|
|
33
|
+
plan: { kind: emitKind(relative), strict: options.strict === true },
|
|
34
|
+
files: [{ name: outName, contents: luau }],
|
|
35
|
+
stale: [],
|
|
159
36
|
};
|
|
160
37
|
}
|