cluaupp 0.2.1 → 0.2.3
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 +12 -0
- package/README.md +1 -1
- package/docs/README.md +32 -20
- package/docs/cli.md +1 -1
- package/docs/cpp-advanced.md +6 -3
- package/docs/cpp-types.md +2 -0
- package/docs/examples/hud.md +1 -1
- package/docs/examples/index.md +1 -1
- package/docs/examples/leaderstats.md +44 -115
- package/docs/getting-started.md +4 -2
- package/docs/intellisense.md +4 -5
- 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/oop/index.md +20 -7
- package/docs/roblox-api.md +4 -56
- package/docs/syntax.md +112 -55
- package/editors/vscode/package.json +25 -25
- package/examples/game/.vscode/extensions.json +3 -0
- package/examples/game/.vscode/settings.json +0 -9
- package/examples/game/src/shared/config.h +2 -0
- package/generated/api.js +15 -1
- package/generated/compile.js +38 -0
- package/generated/emit.js +79 -3
- package/generated/intellisense.js +75 -80
- package/generated/lex.js +3 -0
- package/generated/parse.js +119 -24
- package/generated/parser/collector.js +37 -1
- package/generated/utils/rojo-mapper.js +23 -3
- package/include/cluaupp/datatypes.hpp +4 -0
- package/package.json +98 -87
- package/src/api.ts +15 -1
- package/src/compile.ts +43 -1
- package/src/emit.ts +81 -3
- package/src/intellisense.ts +80 -80
- package/src/lex.ts +3 -0
- package/src/parse.ts +124 -25
- package/src/parser/collector.ts +35 -2
- package/src/utils/rojo-mapper.ts +26 -3
- package/templates/game/.vscode/extensions.json +3 -0
- package/templates/game/.vscode/settings.json +0 -9
- package/templates/game/src/shared/config.h +2 -0
- package/examples/game/.vscode/c_cpp_properties.json +0 -27
- package/templates/game/.vscode/c_cpp_properties.json +0 -27
package/package.json
CHANGED
|
@@ -1,87 +1,98 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cluaupp",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Cluaupp — the definitive merge of C++ and modern Luau. Source-to-source transpiler with first-class Roblox APIs.",
|
|
5
|
-
"author": "KartzDev",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"bin": {
|
|
8
|
-
"cluaupp": "bin/cluaupp.js",
|
|
9
|
-
"cluau": "bin/cluaupp.js"
|
|
10
|
-
},
|
|
11
|
-
"main": "./generated/compile.js",
|
|
12
|
-
"exports": {
|
|
13
|
-
".": "./generated/compile.js",
|
|
14
|
-
"./package.json": "./package.json"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"bin",
|
|
18
|
-
"src",
|
|
19
|
-
"generated",
|
|
20
|
-
"include",
|
|
21
|
-
"templates",
|
|
22
|
-
"examples",
|
|
23
|
-
"editors",
|
|
24
|
-
"runtime",
|
|
25
|
-
"README.md",
|
|
26
|
-
"LICENSE",
|
|
27
|
-
"CHANGELOG.md",
|
|
28
|
-
"docs"
|
|
29
|
-
],
|
|
30
|
-
"scripts": {
|
|
31
|
-
"cluaupp": "node bin/cluaupp.js",
|
|
32
|
-
"cluau": "node bin/cluaupp.js",
|
|
33
|
-
"build": "tsc",
|
|
34
|
-
"build:cli": "tsc",
|
|
35
|
-
"dev:cli": "tsx src/cli.ts",
|
|
36
|
-
"prepare": "tsc",
|
|
37
|
-
"test": "node test/out.test.js && node test/types.test.js && node test/modules.test.js && node test/security.test.js && node test/understander.test.js",
|
|
38
|
-
"generate-api": "node scripts/generate-api.js",
|
|
39
|
-
"check-highlight": "
|
|
40
|
-
"vendor-libs": "node scripts/vendor-libs.js",
|
|
41
|
-
"docs": "moonwave dev",
|
|
42
|
-
"docs:build": "moonwave build",
|
|
43
|
-
"site": "
|
|
44
|
-
"dev": "
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"tree-sitter
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "cluaupp",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Cluaupp — the definitive merge of C++ and modern Luau. Source-to-source transpiler with first-class Roblox APIs.",
|
|
5
|
+
"author": "KartzDev",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"cluaupp": "bin/cluaupp.js",
|
|
9
|
+
"cluau": "bin/cluaupp.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./generated/compile.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./generated/compile.js",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"src",
|
|
19
|
+
"generated",
|
|
20
|
+
"include",
|
|
21
|
+
"templates",
|
|
22
|
+
"examples",
|
|
23
|
+
"editors",
|
|
24
|
+
"runtime",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"CHANGELOG.md",
|
|
28
|
+
"docs"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"cluaupp": "node bin/cluaupp.js",
|
|
32
|
+
"cluau": "node bin/cluaupp.js",
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"build:cli": "tsc",
|
|
35
|
+
"dev:cli": "tsx src/cli.ts",
|
|
36
|
+
"prepare": "tsc",
|
|
37
|
+
"test": "node test/out.test.js && node test/types.test.js && node test/modules.test.js && node test/security.test.js && node test/understander.test.js",
|
|
38
|
+
"generate-api": "node scripts/generate-api.js",
|
|
39
|
+
"check-highlight": "tsx web/src/check-content.ts",
|
|
40
|
+
"vendor-libs": "node scripts/vendor-libs.js",
|
|
41
|
+
"docs": "moonwave dev",
|
|
42
|
+
"docs:build": "moonwave build",
|
|
43
|
+
"site": "vite build --config web/vite.config.mts && npm run check-highlight",
|
|
44
|
+
"site:dev": "vite --config web/vite.config.mts",
|
|
45
|
+
"dev": "node scripts/dev.js",
|
|
46
|
+
"stop": "node scripts/stop.js",
|
|
47
|
+
"prepublishOnly": "tsc && npm test"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"luau",
|
|
54
|
+
"roblox",
|
|
55
|
+
"cpp",
|
|
56
|
+
"c++",
|
|
57
|
+
"transpiler",
|
|
58
|
+
"compiler",
|
|
59
|
+
"rojo",
|
|
60
|
+
"cluaupp",
|
|
61
|
+
"cluau"
|
|
62
|
+
],
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "git+https://github.com/KartzRbx/Cluaupp.git"
|
|
66
|
+
},
|
|
67
|
+
"bugs": {
|
|
68
|
+
"url": "https://github.com/KartzRbx/Cluaupp/issues"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/KartzRbx/Cluaupp#readme",
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"cluaupp": "^0.2.2",
|
|
73
|
+
"commander": "^15.0.0",
|
|
74
|
+
"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
|
+
"vscode-languageserver": "^10.1.1",
|
|
79
|
+
"vscode-languageserver-textdocument": "^1.0.14"
|
|
80
|
+
},
|
|
81
|
+
"overrides": {
|
|
82
|
+
"tree-sitter-c": "0.23.2"
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@types/node": "^22.20.3",
|
|
86
|
+
"@types/react": "^19.3.0",
|
|
87
|
+
"@types/react-dom": "^19.3.0",
|
|
88
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
89
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
90
|
+
"react": "^19.1.1",
|
|
91
|
+
"react-dom": "^19.1.1",
|
|
92
|
+
"react-router-dom": "^7.8.2",
|
|
93
|
+
"tsx": "^4.23.13",
|
|
94
|
+
"typescript": "^7.0.2",
|
|
95
|
+
"vite": "^8.0.0",
|
|
96
|
+
"vue": "^3.5.21"
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/api.ts
CHANGED
|
@@ -45,9 +45,23 @@ export function luauType(name: string | null | undefined): string | null {
|
|
|
45
45
|
if (!name) {
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
|
-
const cleaned = String(name)
|
|
48
|
+
const cleaned = String(name)
|
|
49
|
+
.replace(/\*+$/, "")
|
|
50
|
+
.replace(/^const\s+/, "")
|
|
51
|
+
.replace(/^Enum::/, "Enum.")
|
|
52
|
+
.replace(/\s+/g, " ")
|
|
53
|
+
.trim();
|
|
49
54
|
if (cleaned in LUAU_TYPES) {
|
|
50
55
|
return LUAU_TYPES[cleaned];
|
|
51
56
|
}
|
|
57
|
+
const generic = cleaned.match(/^(?:std::)?(?:vector|array|span|LuaArray)\s*<\s*([^>]+)\s*>$/);
|
|
58
|
+
if (generic) {
|
|
59
|
+
const inner = luauType(generic[1]) || generic[1];
|
|
60
|
+
return `{${inner}}`;
|
|
61
|
+
}
|
|
62
|
+
const optional = cleaned.match(/^(?:std::)?optional\s*<\s*([^>]+)\s*>$/);
|
|
63
|
+
if (optional) {
|
|
64
|
+
return `${luauType(optional[1]) || optional[1]}?`;
|
|
65
|
+
}
|
|
52
66
|
return cleaned;
|
|
53
67
|
}
|
package/src/compile.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { parse } from "./parse.js";
|
|
3
4
|
import { emit } from "./emit.js";
|
|
@@ -5,7 +6,7 @@ import { preprocess, isHeaderFile, toLuauPath, siblingImplementation, siblingHea
|
|
|
5
6
|
import { collectLibraries, insertPreamble } from "./libs.js";
|
|
6
7
|
import { planOutput } from "./architecture.js";
|
|
7
8
|
import { emitHeaderModule, primaryHeaderName } from "./headers.js";
|
|
8
|
-
import type { AstProgram, ModuleInclude, PreprocessOptions } from "./ast.js";
|
|
9
|
+
import type { AstNode, AstProgram, ModuleInclude, PreprocessOptions } from "./ast.js";
|
|
9
10
|
import type { CompileOptions, CompileServiceResult } from "./types.js";
|
|
10
11
|
|
|
11
12
|
function shouldAttachRequires(name: string, contents: string): boolean {
|
|
@@ -32,12 +33,53 @@ function posixRel(from: string, file: string): string {
|
|
|
32
33
|
return path.relative(from, file).replace(/\\/g, "/");
|
|
33
34
|
}
|
|
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
|
+
|
|
35
76
|
function wireHeaderImpl(ast: AstProgram, fileName: string, options: PreprocessOptions): void {
|
|
36
77
|
const headerPath = options.filePath || null;
|
|
37
78
|
const impl = headerPath ? siblingImplementation(headerPath) : null;
|
|
38
79
|
if (!impl) {
|
|
39
80
|
return;
|
|
40
81
|
}
|
|
82
|
+
mergeSiblingImpl(ast, impl, options);
|
|
41
83
|
const typeName = primaryHeaderName(ast, options.relativeName || fileName);
|
|
42
84
|
const implRel = options.srcDir ? posixRel(options.srcDir, impl) : path.basename(impl).replace(/\\/g, "/");
|
|
43
85
|
const implOut = implOutName(implRel);
|
package/src/emit.ts
CHANGED
|
@@ -31,6 +31,7 @@ function emit(ast, options = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
const ownedFns = (ast.body || []).filter((decl) => decl.type === "function");
|
|
34
|
+
const hasOwnedMethods = ownedFns.some((decl) => Boolean(decl.owner));
|
|
34
35
|
const classModule = ownedFns.length > 0 && ownedFns.every((decl) => Boolean(decl.owner));
|
|
35
36
|
const nestedTypes = new Set();
|
|
36
37
|
for (const decl of ast.body || []) {
|
|
@@ -142,6 +143,38 @@ function emit(ast, options = {}) {
|
|
|
142
143
|
return linesOut;
|
|
143
144
|
};
|
|
144
145
|
|
|
146
|
+
const isStringConcatCall = (node) => node && node.type === "call" && !node.object && node.name === "string_concat";
|
|
147
|
+
|
|
148
|
+
const emitStringConcat = (args) => {
|
|
149
|
+
const parts = (args || []).map(emitExpr);
|
|
150
|
+
if (parts.length === 0) {
|
|
151
|
+
return `""`;
|
|
152
|
+
}
|
|
153
|
+
if (parts.length === 1) {
|
|
154
|
+
return parts[0];
|
|
155
|
+
}
|
|
156
|
+
return `(${parts.join(" .. ")})`;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const isStringExpr = (node) => {
|
|
160
|
+
if (!node) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if (node.type === "string") {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (isStringConcatCall(node)) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
if (node.type === "binary" && node.op === "+" && (isStringExpr(node.left) || isStringExpr(node.right))) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
if (node.type === "member" && (node.name === "Name" || node.name === "Text" || node.name === "DisplayName")) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
};
|
|
177
|
+
|
|
145
178
|
const emitExpr = (node) => {
|
|
146
179
|
if (!node) {
|
|
147
180
|
return "nil";
|
|
@@ -164,6 +197,28 @@ function emit(ast, options = {}) {
|
|
|
164
197
|
}
|
|
165
198
|
return `{ ${entries.join(", ")} }`;
|
|
166
199
|
}
|
|
200
|
+
case "cast":
|
|
201
|
+
if (node.valueType === "void") {
|
|
202
|
+
return emitExpr(node.argument);
|
|
203
|
+
}
|
|
204
|
+
return emitExpr(node.argument);
|
|
205
|
+
case "lambda": {
|
|
206
|
+
const params = (node.params || [])
|
|
207
|
+
.map((param) => {
|
|
208
|
+
if (typeof param === "string") {
|
|
209
|
+
return param;
|
|
210
|
+
}
|
|
211
|
+
const typeAnn = luauType(param.valueType);
|
|
212
|
+
return typeAnn ? `${param.name}: ${typeAnn}` : param.name;
|
|
213
|
+
})
|
|
214
|
+
.join(", ");
|
|
215
|
+
const inner = [];
|
|
216
|
+
for (const stmt of node.body || []) {
|
|
217
|
+
inner.push(...emitStmt(stmt, 1));
|
|
218
|
+
}
|
|
219
|
+
const body = inner.length > 0 ? `\n${inner.join("\n")}\n` : "\n";
|
|
220
|
+
return `function(${params})${body}end`;
|
|
221
|
+
}
|
|
167
222
|
case "unary": {
|
|
168
223
|
const inner = emitExpr(node.argument);
|
|
169
224
|
if (node.op === "!") {
|
|
@@ -183,6 +238,9 @@ function emit(ast, options = {}) {
|
|
|
183
238
|
case "member":
|
|
184
239
|
return `${emitExpr(node.object)}.${node.name}`;
|
|
185
240
|
case "call": {
|
|
241
|
+
if (isStringConcatCall(node)) {
|
|
242
|
+
return emitStringConcat(node.args);
|
|
243
|
+
}
|
|
186
244
|
const args = node.args.map(emitMethodArg).join(", ");
|
|
187
245
|
if (!node.object) {
|
|
188
246
|
if (isDatatype(node.name)) {
|
|
@@ -194,6 +252,9 @@ function emit(ast, options = {}) {
|
|
|
194
252
|
return `${node.name}(${args})`;
|
|
195
253
|
}
|
|
196
254
|
const obj = emitExpr(node.object);
|
|
255
|
+
if (classMethods.has(node.name) && !localNames.has(node.name)) {
|
|
256
|
+
return `${obj}:${node.name}(${args})`;
|
|
257
|
+
}
|
|
197
258
|
if (node.access === "::" && node.object.type === "ident" && node.object.name === "cout") {
|
|
198
259
|
if (node.name === "endl") {
|
|
199
260
|
return "print()";
|
|
@@ -232,6 +293,9 @@ function emit(ast, options = {}) {
|
|
|
232
293
|
return printed.join("; ");
|
|
233
294
|
}
|
|
234
295
|
}
|
|
296
|
+
if (node.op === "+" && (isStringExpr(node.left) || isStringExpr(node.right))) {
|
|
297
|
+
return `${emitExpr(node.left)} .. ${emitExpr(node.right)}`;
|
|
298
|
+
}
|
|
235
299
|
const ops = { "!=": "~=", "&&": "and", "||": "or" };
|
|
236
300
|
const op = ops[node.op] || node.op;
|
|
237
301
|
return `${emitExpr(node.left)} ${op} ${emitExpr(node.right)}`;
|
|
@@ -292,6 +356,9 @@ function emit(ast, options = {}) {
|
|
|
292
356
|
return [`${prefix}${kind} ${node.name}${typed} = ${created.className}.new(${args})`];
|
|
293
357
|
}
|
|
294
358
|
const value = node.value ? emitExpr(node.value) : "nil";
|
|
359
|
+
if (!node.value && node.valueType && classOwners.has(node.valueType)) {
|
|
360
|
+
return [`${prefix}${kind} ${node.name}${typed} = ${node.valueType}`];
|
|
361
|
+
}
|
|
295
362
|
return [`${prefix}${kind} ${node.name}${typed} = ${value}`];
|
|
296
363
|
};
|
|
297
364
|
|
|
@@ -305,6 +372,9 @@ function emit(ast, options = {}) {
|
|
|
305
372
|
return emitNewDecl(node, indent);
|
|
306
373
|
case "expr": {
|
|
307
374
|
const expr = node.expr;
|
|
375
|
+
if (expr && expr.type === "cast" && expr.valueType === "void") {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
308
378
|
if (expr && expr.type === "binary" && expr.op === "<<") {
|
|
309
379
|
const printed = emitCout(expr);
|
|
310
380
|
if (printed) {
|
|
@@ -456,11 +526,9 @@ function emit(ast, options = {}) {
|
|
|
456
526
|
lines.push(" }");
|
|
457
527
|
lines.push("end");
|
|
458
528
|
lines.push("");
|
|
459
|
-
lines.push(`return ${root}`);
|
|
460
|
-
lines.push("");
|
|
461
529
|
};
|
|
462
530
|
|
|
463
|
-
if (
|
|
531
|
+
if (hasOwnedMethods) {
|
|
464
532
|
for (const owner of classOwners) {
|
|
465
533
|
lines.push(`local ${owner} = {}`);
|
|
466
534
|
lines.push("");
|
|
@@ -471,6 +539,16 @@ function emit(ast, options = {}) {
|
|
|
471
539
|
for (const root of structRoots) {
|
|
472
540
|
emitStructConstructor(root);
|
|
473
541
|
}
|
|
542
|
+
if (structRoots.length === 1) {
|
|
543
|
+
lines.push(`return ${structRoots[0]}`);
|
|
544
|
+
} else {
|
|
545
|
+
lines.push("return {");
|
|
546
|
+
for (const root of structRoots) {
|
|
547
|
+
lines.push(` ${root} = ${root},`);
|
|
548
|
+
}
|
|
549
|
+
lines.push("}");
|
|
550
|
+
}
|
|
551
|
+
lines.push("");
|
|
474
552
|
return lines.join("\n");
|
|
475
553
|
}
|
|
476
554
|
|
package/src/intellisense.ts
CHANGED
|
@@ -66,32 +66,6 @@ function compileCommandEntry(root: string, file: string) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
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
69
|
function copyDir(from: string, to: string): void {
|
|
96
70
|
fs.mkdirSync(to, { recursive: true });
|
|
97
71
|
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
@@ -105,6 +79,58 @@ function copyDir(from: string, to: string): void {
|
|
|
105
79
|
}
|
|
106
80
|
}
|
|
107
81
|
|
|
82
|
+
function uniqueStrings(...lists: unknown[]): string[] {
|
|
83
|
+
const seen = new Set<string>();
|
|
84
|
+
const out: string[] = [];
|
|
85
|
+
for (const list of lists) {
|
|
86
|
+
if (!Array.isArray(list)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const item of list) {
|
|
90
|
+
if (typeof item === "string" && item && !seen.has(item)) {
|
|
91
|
+
seen.add(item);
|
|
92
|
+
out.push(item);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
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
|
+
function readJsonObject(file: string): Record<string, unknown> {
|
|
111
|
+
if (!fs.existsSync(file)) {
|
|
112
|
+
return {};
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
116
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
117
|
+
return parsed as Record<string, unknown>;
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// ignore corrupt editor json
|
|
121
|
+
}
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function writeJson(file: string, value: Record<string, unknown>): void {
|
|
126
|
+
const json = `${JSON.stringify(value, null, "\t")}\n`;
|
|
127
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
128
|
+
if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === json) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
fs.writeFileSync(file, json, "utf8");
|
|
132
|
+
}
|
|
133
|
+
|
|
108
134
|
function clangdEditorArgs(): string[] {
|
|
109
135
|
return [
|
|
110
136
|
"--compile-commands-dir=${workspaceFolder}",
|
|
@@ -113,17 +139,6 @@ function clangdEditorArgs(): string[] {
|
|
|
113
139
|
];
|
|
114
140
|
}
|
|
115
141
|
|
|
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}`;
|
|
123
|
-
}
|
|
124
|
-
return `linux-clang-${arch}`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
142
|
export function clangPath(): string {
|
|
128
143
|
return findClangPlusPlus() || "clang++";
|
|
129
144
|
}
|
|
@@ -173,54 +188,39 @@ Index:
|
|
|
173
188
|
}
|
|
174
189
|
|
|
175
190
|
function writeVscode(root: string): void {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"
|
|
183
|
-
"
|
|
191
|
+
const settingsPath = path.join(root, ".vscode", "settings.json");
|
|
192
|
+
const current = readJsonObject(settingsPath);
|
|
193
|
+
const associations = {
|
|
194
|
+
...((current["files.associations"] && typeof current["files.associations"] === "object" && !Array.isArray(current["files.associations"]))
|
|
195
|
+
? (current["files.associations"] as Record<string, unknown>)
|
|
196
|
+
: {}),
|
|
197
|
+
"*.hpp": "cpp",
|
|
198
|
+
"*.h": "cpp",
|
|
199
|
+
"*.server.cpp": "cpp",
|
|
200
|
+
"*.client.cpp": "cpp",
|
|
201
|
+
"*.plugin.cpp": "cpp",
|
|
202
|
+
"*.legacy.cpp": "cpp",
|
|
203
|
+
"*.legacy.server.cpp": "cpp",
|
|
204
|
+
"*.legacy.client.cpp": "cpp",
|
|
205
|
+
};
|
|
206
|
+
writeJson(settingsPath, {
|
|
207
|
+
...stripCppToolsSettings(current),
|
|
184
208
|
"clangd.enable": true,
|
|
185
209
|
"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
|
-
},
|
|
210
|
+
"files.associations": associations,
|
|
196
211
|
});
|
|
197
|
-
|
|
198
|
-
|
|
212
|
+
|
|
213
|
+
const extensionsPath = path.join(root, ".vscode", "extensions.json");
|
|
214
|
+
const extensions = readJsonObject(extensionsPath);
|
|
215
|
+
writeJson(extensionsPath, {
|
|
216
|
+
...extensions,
|
|
217
|
+
recommendations: uniqueStrings(extensions.recommendations, ["llvm-vs-code-extensions.vscode-clangd"]),
|
|
218
|
+
unwantedRecommendations: uniqueStrings(extensions.unwantedRecommendations, ["ms-vscode.cpptools"]),
|
|
199
219
|
});
|
|
220
|
+
|
|
200
221
|
const props = path.join(root, ".vscode", "c_cpp_properties.json");
|
|
201
|
-
|
|
202
|
-
|
|
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");
|
|
222
|
+
if (fs.existsSync(props)) {
|
|
223
|
+
fs.unlinkSync(props);
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|