cluaupp 0.2.3 → 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 +10 -79
- package/README.md +42 -88
- package/docs/README.md +13 -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/examples/combat.md +19 -227
- package/docs/examples/hud.md +13 -81
- package/docs/examples/index.md +9 -16
- package/docs/getting-started.md +20 -137
- package/docs/intellisense.md +5 -41
- package/docs/intro.md +16 -15
- package/docs/migration.md +38 -0
- package/docs/oop/file-tags.md +12 -36
- package/docs/oop/index.md +8 -14
- package/docs/syntax.md +54 -236
- 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 +0 -3
- package/examples/game/.vscode/settings.json +6 -14
- 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/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/intellisense.js +38 -157
- package/generated/libs.js +1 -1
- package/generated/lsp.js +1 -8
- 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} +1 -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 +7 -14
- 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 +36 -165
- 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 +0 -3
- package/templates/game/.vscode/settings.json +6 -14
- 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/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 -609
- 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 -158
- package/src/parse.ts +0 -729
- 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/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
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
// 1ForeverHD/TopbarPlus — vendored into CluauppLibs.TopbarPlus (
|
|
4
|
-
|
|
3
|
+
// 1ForeverHD/TopbarPlus — vendored into CluauppLibs.TopbarPlus (struct Icon)
|
|
4
|
+
struct IconTheme {};
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
public:
|
|
6
|
+
struct Icon {
|
|
8
7
|
static Icon* new_();
|
|
9
8
|
Icon* setName(string name);
|
|
10
9
|
Icon* setLabel(string text);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
// Cluaupp libraries — CL++ IntelliSense. clpp emits
|
|
3
|
+
// require(ReplicatedStorage.CluauppLibs.*). Runtime lives in runtime/.
|
|
4
|
+
|
|
5
|
+
#include <clpp/generated/instances.clh>
|
|
6
|
+
#include <clpp/libs/janitor.clh>
|
|
7
|
+
#include <clpp/libs/promise.clh>
|
|
8
|
+
#include <clpp/libs/net.clh>
|
|
9
|
+
#include <clpp/libs/math.clh>
|
|
10
|
+
#include <clpp/libs/formatnumber.clh>
|
|
11
|
+
#include <clpp/libs/module3d.clh>
|
|
12
|
+
#include <clpp/libs/twinkle.clh>
|
|
13
|
+
#include <clpp/libs/dataservice.clh>
|
|
14
|
+
#include <clpp/libs/ezvisual.clh>
|
|
15
|
+
#include <clpp/libs/spring.clh>
|
|
16
|
+
#include <clpp/libs/display.clh>
|
|
17
|
+
#include <clpp/libs/stickybillboard.clh>
|
|
18
|
+
#include <clpp/libs/topbarplus.clh>
|
|
19
|
+
#include <clpp/libs/cmdr.clh>
|
|
20
|
+
#include <clpp/libs/chrono.clh>
|
|
21
|
+
#include <clpp/libs/iris.clh>
|
|
22
|
+
#include <clpp/libs/fusion.clh>
|
|
23
|
+
#include <clpp/libs/statemachine.clh>
|
|
24
|
+
#include <clpp/libs/vfx.clh>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
// Cluaupp — Roblox API stubs for CL++ IntelliSense.
|
|
3
|
+
// Source: official client dump + datatypes from create.roblox.com
|
|
4
|
+
// clpp emits Luau (Vector3.new, Instance.new, :GetPlayers, ...).
|
|
5
|
+
|
|
6
|
+
#include <clpp/generated/enums.clh>
|
|
7
|
+
#include <clpp/datatypes.clh>
|
|
8
|
+
#include <clpp/generated/instances.clh>
|
|
9
|
+
#include <clpp/libs.clh>
|
|
10
|
+
|
|
11
|
+
template <typename T>
|
|
12
|
+
T* GetService();
|
|
13
|
+
|
|
14
|
+
template <typename T>
|
|
15
|
+
T* GetService(Instance* game_);
|
|
16
|
+
|
|
17
|
+
extern DataModel* game;
|
|
18
|
+
extern Workspace* workspace;
|
|
19
|
+
extern LuaSourceContainer* script;
|
|
20
|
+
|
|
21
|
+
void post(string message);
|
|
22
|
+
void warn(string message);
|
|
23
|
+
void report(string message);
|
|
24
|
+
void print(string message);
|
|
25
|
+
void error(string message);
|
|
26
|
+
|
|
27
|
+
double tick();
|
|
28
|
+
double time();
|
|
29
|
+
void wait(double seconds = 0);
|
|
30
|
+
void spawn(void (*callback)());
|
|
31
|
+
void delay(double seconds, void (*callback)());
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cluaupp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Cluaupp —
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Cluaupp toolchain — compile CL++ to Luau with Rojo and CluauppLibs.",
|
|
5
5
|
"author": "KartzDev",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"build:cli": "tsc",
|
|
35
35
|
"dev:cli": "tsx src/cli.ts",
|
|
36
36
|
"prepare": "tsc",
|
|
37
|
-
"test": "node test/
|
|
37
|
+
"test": "node test/security.test.js && node test/understander.test.js && node test/out.test.js && node test/types.test.js && node test/modules.test.js && node test/clpp.test.js",
|
|
38
|
+
"test:clpp-contract": "node test/clpp.test.js",
|
|
38
39
|
"generate-api": "node scripts/generate-api.js",
|
|
39
40
|
"check-highlight": "tsx web/src/check-content.ts",
|
|
40
41
|
"vendor-libs": "node scripts/vendor-libs.js",
|
|
@@ -52,13 +53,12 @@
|
|
|
52
53
|
"keywords": [
|
|
53
54
|
"luau",
|
|
54
55
|
"roblox",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
56
|
+
"clpp",
|
|
57
|
+
"cl++",
|
|
57
58
|
"transpiler",
|
|
58
59
|
"compiler",
|
|
59
60
|
"rojo",
|
|
60
|
-
"cluaupp"
|
|
61
|
-
"cluau"
|
|
61
|
+
"cluaupp"
|
|
62
62
|
],
|
|
63
63
|
"repository": {
|
|
64
64
|
"type": "git",
|
|
@@ -69,18 +69,11 @@
|
|
|
69
69
|
},
|
|
70
70
|
"homepage": "https://github.com/KartzRbx/Cluaupp#readme",
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"cluaupp": "^0.2.2",
|
|
73
72
|
"commander": "^15.0.0",
|
|
74
73
|
"comment-json": "^5.0.0",
|
|
75
|
-
"tree-sitter": "0.21.1",
|
|
76
|
-
"tree-sitter-c": "0.23.2",
|
|
77
|
-
"tree-sitter-cpp": "0.23.4",
|
|
78
74
|
"vscode-languageserver": "^10.1.1",
|
|
79
75
|
"vscode-languageserver-textdocument": "^1.0.14"
|
|
80
76
|
},
|
|
81
|
-
"overrides": {
|
|
82
|
-
"tree-sitter-c": "0.23.2"
|
|
83
|
-
},
|
|
84
77
|
"devDependencies": {
|
|
85
78
|
"@types/node": "^22.20.3",
|
|
86
79
|
"@types/react": "^19.3.0",
|
package/src/cli.ts
CHANGED
|
@@ -3,11 +3,13 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pkg } from "./package-info.js";
|
|
5
5
|
import { start as startLsp } from "./lsp.js";
|
|
6
|
-
import { toLuauPath } from "./
|
|
6
|
+
import { collectSources, toLuauPath } from "./clpp/paths.js";
|
|
7
|
+
import { CLPP_INSTALL_HINT } from "./clpp/contract.js";
|
|
8
|
+
import { clppManifest, clppVersion, hasClpp, resolveClppBinary } from "./clpp/runner.js";
|
|
7
9
|
import { installEditorSupport, syncEditorSupport } from "./intellisense.js";
|
|
8
10
|
import { RojoMapper } from "./utils/rojo-mapper.js";
|
|
9
11
|
import { ProcessOrchestrator } from "./utils/process-orchestrator.js";
|
|
10
|
-
import { build as buildProject, init, loadConfig, watch
|
|
12
|
+
import { build as buildProject, init, loadConfig, watch } from "./utils/project.js";
|
|
11
13
|
import { transpileSource } from "./transpile.js";
|
|
12
14
|
import type { BuildOptions, BuildResult } from "./types.js";
|
|
13
15
|
|
|
@@ -15,7 +17,7 @@ const program = new Command();
|
|
|
15
17
|
|
|
16
18
|
program
|
|
17
19
|
.name("cluaupp")
|
|
18
|
-
.description("
|
|
20
|
+
.description("Cluaupp toolchain — compile CL++ to Luau, Rojo, and CluauppLibs")
|
|
19
21
|
.version(pkg.version, "-v, --version", "print version")
|
|
20
22
|
.showHelpAfterError()
|
|
21
23
|
.action(() => {
|
|
@@ -37,14 +39,14 @@ program
|
|
|
37
39
|
|
|
38
40
|
program
|
|
39
41
|
.command("build")
|
|
40
|
-
.description("
|
|
42
|
+
.description("Compile a CL++ file, directory, or project to Luau")
|
|
41
43
|
.argument("[folder]", "project folder", ".")
|
|
42
|
-
.option("-i, --input <path>", "
|
|
43
|
-
.option("-o, --output <path>", "
|
|
44
|
-
.option("-r, --rojo <path>", "
|
|
45
|
-
.option("--strict", "
|
|
46
|
-
.option("--format", "
|
|
47
|
-
.option("--analyze", "
|
|
44
|
+
.option("-i, --input <path>", "CL++ file or directory")
|
|
45
|
+
.option("-o, --output <path>", "Luau file or directory")
|
|
46
|
+
.option("-r, --rojo <path>", "Path to Rojo default.project.json", "./default.project.json")
|
|
47
|
+
.option("--strict", "Emit --!strict")
|
|
48
|
+
.option("--format", "Run StyLua on output")
|
|
49
|
+
.option("--analyze", "Run luau-analyze on output")
|
|
48
50
|
.action(async (folder: string, options: {
|
|
49
51
|
input?: string;
|
|
50
52
|
output?: string;
|
|
@@ -73,8 +75,8 @@ program
|
|
|
73
75
|
.command("watch")
|
|
74
76
|
.description("rebuild on save")
|
|
75
77
|
.argument("[folder]", "project folder", ".")
|
|
76
|
-
.option("-r, --rojo <path>", "
|
|
77
|
-
.option("--format", "
|
|
78
|
+
.option("-r, --rojo <path>", "Path to Rojo default.project.json", "./default.project.json")
|
|
79
|
+
.option("--format", "Run StyLua on output")
|
|
78
80
|
.action((folder: string, options: { rojo?: string; format?: boolean }) => {
|
|
79
81
|
try {
|
|
80
82
|
watch(path.resolve(process.cwd(), folder), {
|
|
@@ -89,7 +91,7 @@ program
|
|
|
89
91
|
|
|
90
92
|
program
|
|
91
93
|
.command("lsp")
|
|
92
|
-
.description("
|
|
94
|
+
.description("Language server stdio (JSON-RPC). Use `clpp install` for CL++ IntelliSense.")
|
|
93
95
|
.argument("[folder]", "project folder", ".")
|
|
94
96
|
.action((folder: string) => {
|
|
95
97
|
startLsp({ projectRoot: path.resolve(process.cwd(), folder) });
|
|
@@ -98,18 +100,32 @@ program
|
|
|
98
100
|
program
|
|
99
101
|
.command("intellisense")
|
|
100
102
|
.alias("intelisense")
|
|
101
|
-
.description("
|
|
103
|
+
.description("Point the editor at CL++ (`clpp install`)")
|
|
102
104
|
.argument("[folder]", "project folder", ".")
|
|
103
105
|
.action(async (folder: string) => {
|
|
104
106
|
try {
|
|
105
107
|
const root = path.resolve(process.cwd(), folder);
|
|
106
108
|
syncEditorSupport(root, loadConfig(root));
|
|
107
|
-
|
|
108
|
-
console.log("cluaupp:
|
|
109
|
-
console.log("cluaupp:
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
await installEditorSupport();
|
|
110
|
+
console.log("cluaupp: editor associations written in", root);
|
|
111
|
+
console.log("cluaupp:", CLPP_INSTALL_HINT);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error(err instanceof Error ? err.message : err);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
program
|
|
119
|
+
.command("language")
|
|
120
|
+
.description("CL++ manifest (`clpp api manifest`)")
|
|
121
|
+
.action(() => {
|
|
122
|
+
try {
|
|
123
|
+
resolveClppBinary();
|
|
124
|
+
const version = clppVersion();
|
|
125
|
+
if (version) {
|
|
126
|
+
console.log("clpp", version);
|
|
112
127
|
}
|
|
128
|
+
console.log(JSON.stringify(clppManifest(), null, "\t"));
|
|
113
129
|
} catch (err) {
|
|
114
130
|
console.error(err instanceof Error ? err.message : err);
|
|
115
131
|
process.exit(1);
|
|
@@ -139,17 +155,15 @@ async function buildInput(options: {
|
|
|
139
155
|
process.exit(1);
|
|
140
156
|
}
|
|
141
157
|
|
|
142
|
-
console.log("🏁 Inicializando Pipeline do Compilador Cluaupp V2...");
|
|
143
|
-
|
|
144
158
|
const mapper = new RojoMapper(path.resolve(options.rojo), path.dirname(inputPath));
|
|
145
159
|
await mapper.load();
|
|
146
160
|
|
|
147
161
|
const inputs = (await fs.stat(inputPath)).isDirectory()
|
|
148
|
-
?
|
|
162
|
+
? collectSources(inputPath)
|
|
149
163
|
: [inputPath];
|
|
150
164
|
|
|
151
165
|
if (inputs.length === 0) {
|
|
152
|
-
console.error("no .
|
|
166
|
+
console.error("no .clpp/.clp/.clh files in", inputPath);
|
|
153
167
|
process.exit(1);
|
|
154
168
|
}
|
|
155
169
|
|
|
@@ -184,7 +198,7 @@ async function buildInput(options: {
|
|
|
184
198
|
console.log("\nRelatório de Análise Estática do Luau:\n", analysisReport);
|
|
185
199
|
}
|
|
186
200
|
}
|
|
187
|
-
console.log(`
|
|
201
|
+
console.log(`Compiled ${file} → ${dest}`);
|
|
188
202
|
}
|
|
189
203
|
}
|
|
190
204
|
|
|
@@ -192,7 +206,7 @@ export function build(root: string, options: BuildOptions = {}): BuildResult {
|
|
|
192
206
|
return buildProject(root, options);
|
|
193
207
|
}
|
|
194
208
|
|
|
195
|
-
export { watch, init, program };
|
|
209
|
+
export { watch, init, program, hasClpp };
|
|
196
210
|
|
|
197
211
|
export function dispatch(args: string[]): Promise<Command> {
|
|
198
212
|
return program.parseAsync(["node", "cluaupp", ...args]);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Stable CL++ ↔ Cluaupp contract. Cluaupp invokes the `clpp` binary. */
|
|
2
|
+
|
|
3
|
+
export interface CompileRequest {
|
|
4
|
+
source: string;
|
|
5
|
+
fileName: string;
|
|
6
|
+
strict?: boolean;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CompileArtifact {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
luau: string;
|
|
13
|
+
fileName: string;
|
|
14
|
+
outputHint: string;
|
|
15
|
+
scriptKind: "server" | "client" | "plugin" | null;
|
|
16
|
+
isScript: boolean;
|
|
17
|
+
isHeader: boolean;
|
|
18
|
+
rojoClass: "Script" | "LocalScript" | "ModuleScript";
|
|
19
|
+
libraries: string[];
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LanguageManifest {
|
|
24
|
+
name: "CL++";
|
|
25
|
+
id: "clpp";
|
|
26
|
+
version: string;
|
|
27
|
+
extensions: string[];
|
|
28
|
+
tags: Array<{
|
|
29
|
+
pattern: string;
|
|
30
|
+
scriptKind: string;
|
|
31
|
+
rojoClass: string;
|
|
32
|
+
}>;
|
|
33
|
+
builtins: string[];
|
|
34
|
+
operators: Array<{ clpp: string; luau: string; meaning: string }>;
|
|
35
|
+
io: Array<{ clpp: string; luau: string }>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const CLPP_INSTALL_HINT =
|
|
39
|
+
"Install CL++ and put `clpp` on PATH: https://github.com/KartzRbx/CLPP (`cargo install --path .` then `clpp install`). Override with CLPP_PATH.";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type { CompileArtifact, CompileRequest, LanguageManifest } from "./contract.js";
|
|
2
|
+
export { CLPP_INSTALL_HINT } from "./contract.js";
|
|
3
|
+
export { compileViaClpp, clppManifest, clppVersion, hasClpp, resolveClppBinary } from "./runner.js";
|
|
4
|
+
export { clppLuauToGame, shouldSkipInit } from "./postprocess.js";
|
|
5
|
+
export {
|
|
6
|
+
SOURCE_EXTS,
|
|
7
|
+
collectSources,
|
|
8
|
+
emitKind,
|
|
9
|
+
fileStem,
|
|
10
|
+
isEngineStub,
|
|
11
|
+
isHeaderFile,
|
|
12
|
+
isImplFile,
|
|
13
|
+
isSourceFile,
|
|
14
|
+
isTaggedScript,
|
|
15
|
+
scriptKind,
|
|
16
|
+
siblingHeader,
|
|
17
|
+
toLuauPath,
|
|
18
|
+
} from "./paths.js";
|
|
@@ -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
|
+
}
|