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
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
|
}
|
package/src/intellisense.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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,57 +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
18
|
function copyDir(from: string, to: string): void {
|
|
70
19
|
fs.mkdirSync(to, { recursive: true });
|
|
71
20
|
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
@@ -96,17 +45,6 @@ function uniqueStrings(...lists: unknown[]): string[] {
|
|
|
96
45
|
return out;
|
|
97
46
|
}
|
|
98
47
|
|
|
99
|
-
function stripCppToolsSettings(current: Record<string, unknown>): Record<string, unknown> {
|
|
100
|
-
const next: Record<string, unknown> = {};
|
|
101
|
-
for (const [key, value] of Object.entries(current)) {
|
|
102
|
-
if (key === "C_Cpp" || key.startsWith("C_Cpp.")) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
next[key] = value;
|
|
106
|
-
}
|
|
107
|
-
return next;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
48
|
function readJsonObject(file: string): Record<string, unknown> {
|
|
111
49
|
if (!fs.existsSync(file)) {
|
|
112
50
|
return {};
|
|
@@ -117,7 +55,7 @@ function readJsonObject(file: string): Record<string, unknown> {
|
|
|
117
55
|
return parsed as Record<string, unknown>;
|
|
118
56
|
}
|
|
119
57
|
} catch {
|
|
120
|
-
// ignore
|
|
58
|
+
// ignore
|
|
121
59
|
}
|
|
122
60
|
return {};
|
|
123
61
|
}
|
|
@@ -131,62 +69,6 @@ function writeJson(file: string, value: Record<string, unknown>): void {
|
|
|
131
69
|
fs.writeFileSync(file, json, "utf8");
|
|
132
70
|
}
|
|
133
71
|
|
|
134
|
-
function clangdEditorArgs(): string[] {
|
|
135
|
-
return [
|
|
136
|
-
"--compile-commands-dir=${workspaceFolder}",
|
|
137
|
-
"--header-insertion=never",
|
|
138
|
-
"--query-driver=**/clang++*,**/clang++.exe,**/g++*,**/g++.exe",
|
|
139
|
-
];
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function clangPath(): string {
|
|
143
|
-
return findClangPlusPlus() || "clang++";
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function writeCompileCommands(root: string, srcDir: string): boolean {
|
|
147
|
-
const files = collectFiles(srcDir).filter((file) => isCppFile(file)).sort();
|
|
148
|
-
const json = `${JSON.stringify(files.map((file) => compileCommandEntry(root, file)), null, "\t")}\n`;
|
|
149
|
-
const dest = path.join(root, "compile_commands.json");
|
|
150
|
-
if (fs.existsSync(dest) && fs.readFileSync(dest, "utf8") === json) {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
fs.writeFileSync(dest, json, "utf8");
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function writeClangd(root: string): boolean {
|
|
158
|
-
const flags = clangCompileArgs(root)
|
|
159
|
-
.map((flag) => ` - ${flag}`)
|
|
160
|
-
.join("\n");
|
|
161
|
-
const contents = `CompileFlags:
|
|
162
|
-
CompilationDatabase: .
|
|
163
|
-
Add:
|
|
164
|
-
${flags}
|
|
165
|
-
|
|
166
|
-
Diagnostics:
|
|
167
|
-
Suppress: '*'
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
If:
|
|
171
|
-
PathMatch: .*\\.(cpp|cc|cxx|c|h|hpp|hh)$
|
|
172
|
-
CompileFlags:
|
|
173
|
-
Add:
|
|
174
|
-
- -xc++
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
If:
|
|
178
|
-
PathMatch: (out|libs)/.*
|
|
179
|
-
Index:
|
|
180
|
-
Background: Skip
|
|
181
|
-
`;
|
|
182
|
-
const dest = path.join(root, ".clangd");
|
|
183
|
-
if (fs.existsSync(dest) && fs.readFileSync(dest, "utf8") === contents) {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
fs.writeFileSync(dest, contents, "utf8");
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
72
|
function writeVscode(root: string): void {
|
|
191
73
|
const settingsPath = path.join(root, ".vscode", "settings.json");
|
|
192
74
|
const current = readJsonObject(settingsPath);
|
|
@@ -194,19 +76,15 @@ function writeVscode(root: string): void {
|
|
|
194
76
|
...((current["files.associations"] && typeof current["files.associations"] === "object" && !Array.isArray(current["files.associations"]))
|
|
195
77
|
? (current["files.associations"] as Record<string, unknown>)
|
|
196
78
|
: {}),
|
|
197
|
-
"*.
|
|
198
|
-
"*.
|
|
199
|
-
"*.
|
|
200
|
-
"*.
|
|
201
|
-
"*.
|
|
202
|
-
"*.
|
|
203
|
-
"*.legacy.server.cpp": "cpp",
|
|
204
|
-
"*.legacy.client.cpp": "cpp",
|
|
79
|
+
"*.clpp": "clpp",
|
|
80
|
+
"*.clp": "clpp",
|
|
81
|
+
"*.clh": "clpp",
|
|
82
|
+
"*.server.clpp": "clpp",
|
|
83
|
+
"*.client.clpp": "clpp",
|
|
84
|
+
"*.plugin.clpp": "clpp",
|
|
205
85
|
};
|
|
206
86
|
writeJson(settingsPath, {
|
|
207
|
-
...
|
|
208
|
-
"clangd.enable": true,
|
|
209
|
-
"clangd.arguments": clangdEditorArgs(),
|
|
87
|
+
...current,
|
|
210
88
|
"files.associations": associations,
|
|
211
89
|
});
|
|
212
90
|
|
|
@@ -214,44 +92,30 @@ function writeVscode(root: string): void {
|
|
|
214
92
|
const extensions = readJsonObject(extensionsPath);
|
|
215
93
|
writeJson(extensionsPath, {
|
|
216
94
|
...extensions,
|
|
217
|
-
recommendations: uniqueStrings(extensions.recommendations
|
|
95
|
+
recommendations: uniqueStrings(extensions.recommendations),
|
|
218
96
|
unwantedRecommendations: uniqueStrings(extensions.unwantedRecommendations, ["ms-vscode.cpptools"]),
|
|
219
97
|
});
|
|
220
|
-
|
|
221
|
-
const props = path.join(root, ".vscode", "c_cpp_properties.json");
|
|
222
|
-
if (fs.existsSync(props)) {
|
|
223
|
-
fs.unlinkSync(props);
|
|
224
|
-
}
|
|
225
98
|
}
|
|
226
99
|
|
|
227
|
-
export function diagnosticsFor(source: string, fileName?: string
|
|
100
|
+
export function diagnosticsFor(source: string, fileName?: string): CluauppDiagnostic[] {
|
|
101
|
+
if (!hasClpp()) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
228
104
|
try {
|
|
229
|
-
|
|
230
|
-
parse(prepared, fileName || "input.cpp");
|
|
105
|
+
compileViaClpp({ source, fileName: fileName || "input.clpp" });
|
|
231
106
|
return [];
|
|
232
107
|
} catch (err) {
|
|
233
|
-
const
|
|
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:
|
|
113
|
+
return [{ line: 1, col: 1, message, severity: "error" }];
|
|
240
114
|
}
|
|
241
115
|
}
|
|
242
116
|
|
|
243
|
-
export function syncEditorSupport(root: string,
|
|
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(
|
|
279
|
-
const local = installEditorExtension(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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";
|