cluaupp 0.2.2 → 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 +8 -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/examples/game/.vscode/extensions.json +3 -0
- package/examples/game/.vscode/settings.json +0 -9
- package/generated/api.js +15 -1
- 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/include/cluaupp/datatypes.hpp +4 -0
- package/package.json +15 -4
- package/src/api.ts +15 -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/templates/game/.vscode/extensions.json +3 -0
- package/templates/game/.vscode/settings.json +0 -9
- package/examples/game/.vscode/c_cpp_properties.json +0 -27
- package/templates/game/.vscode/c_cpp_properties.json +0 -27
package/generated/parse.js
CHANGED
|
@@ -46,21 +46,124 @@ function parse(source, fileName) {
|
|
|
46
46
|
eat("op", ">");
|
|
47
47
|
return name;
|
|
48
48
|
};
|
|
49
|
+
const CAST_NAMES = new Set(["static_cast", "const_cast", "reinterpret_cast", "dynamic_cast"]);
|
|
50
|
+
function skipSpecifiers() {
|
|
51
|
+
let isConst = false;
|
|
52
|
+
while (at("kw", "static") || at("kw", "inline") || at("kw", "constexpr") || at("kw", "const")) {
|
|
53
|
+
if (at("kw", "constexpr") || at("kw", "const")) {
|
|
54
|
+
isConst = true;
|
|
55
|
+
}
|
|
56
|
+
i += 1;
|
|
57
|
+
}
|
|
58
|
+
return isConst;
|
|
59
|
+
}
|
|
60
|
+
function looksLikePrimaryStart() {
|
|
61
|
+
if (at("ident") || at("number") || at("string")) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (at("kw", "true") || at("kw", "false") || at("kw", "nullptr") || at("kw", "new")) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return at("op", "(") || at("op", "{") || at("op", "[") || at("op", "-") || at("op", "!");
|
|
68
|
+
}
|
|
69
|
+
function parseLambda() {
|
|
70
|
+
eat("op", "[");
|
|
71
|
+
while (!at("eof") && !at("op", "]")) {
|
|
72
|
+
i += 1;
|
|
73
|
+
}
|
|
74
|
+
eat("op", "]");
|
|
75
|
+
const params = [];
|
|
76
|
+
if (at("op", "(")) {
|
|
77
|
+
i += 1;
|
|
78
|
+
if (!at("op", ")")) {
|
|
79
|
+
params.push(parseParam());
|
|
80
|
+
while (at("op", ",")) {
|
|
81
|
+
i += 1;
|
|
82
|
+
params.push(parseParam());
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
eat("op", ")");
|
|
86
|
+
}
|
|
87
|
+
const body = parseBlock();
|
|
88
|
+
return { type: "lambda", params, body };
|
|
89
|
+
}
|
|
90
|
+
function parseNamedCast() {
|
|
91
|
+
i += 1;
|
|
92
|
+
eat("op", "<");
|
|
93
|
+
const valueType = parseType() || "any";
|
|
94
|
+
eat("op", ">");
|
|
95
|
+
const args = parseArgs();
|
|
96
|
+
return { type: "cast", valueType, argument: args[0] || { type: "null" } };
|
|
97
|
+
}
|
|
98
|
+
function parseParenPrimary() {
|
|
99
|
+
eat("op", "(");
|
|
100
|
+
const saved = i;
|
|
101
|
+
skipSpecifiers();
|
|
102
|
+
const valueType = parseType();
|
|
103
|
+
if (valueType && at("op", ")")) {
|
|
104
|
+
i += 1;
|
|
105
|
+
if (looksLikePrimaryStart()) {
|
|
106
|
+
return { type: "cast", valueType, argument: parseUnary() };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
i = saved;
|
|
110
|
+
const expr = parseExpr();
|
|
111
|
+
eat("op", ")");
|
|
112
|
+
return expr;
|
|
113
|
+
}
|
|
114
|
+
function eatGeneric() {
|
|
115
|
+
let text = "";
|
|
116
|
+
let depth = 0;
|
|
117
|
+
while (!at("eof")) {
|
|
118
|
+
const token = peek();
|
|
119
|
+
if (at("op", "<")) {
|
|
120
|
+
depth += 1;
|
|
121
|
+
text += "<";
|
|
122
|
+
i += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (at("op", ">")) {
|
|
126
|
+
depth -= 1;
|
|
127
|
+
text += ">";
|
|
128
|
+
i += 1;
|
|
129
|
+
if (depth === 0) {
|
|
130
|
+
return text;
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (token.type === "string") {
|
|
135
|
+
text += `"${token.value}"`;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
text += token.value;
|
|
139
|
+
}
|
|
140
|
+
i += 1;
|
|
141
|
+
}
|
|
142
|
+
return text;
|
|
143
|
+
}
|
|
49
144
|
function parseType() {
|
|
50
145
|
if (at("kw", "const")) {
|
|
51
146
|
i += 1;
|
|
52
147
|
}
|
|
148
|
+
let name = null;
|
|
53
149
|
if (at("kw", "auto") || at("kw", "void") || at("kw", "int") || at("kw", "bool") || at("kw", "float") || at("kw", "double")) {
|
|
54
|
-
|
|
55
|
-
|
|
150
|
+
name = eat("kw").value;
|
|
151
|
+
}
|
|
152
|
+
else if (at("ident")) {
|
|
153
|
+
name = eat("ident").value;
|
|
154
|
+
while (at("op", "::")) {
|
|
56
155
|
i += 1;
|
|
156
|
+
if (at("ident")) {
|
|
157
|
+
name += `::${eat("ident").value}`;
|
|
158
|
+
}
|
|
57
159
|
}
|
|
58
|
-
return name;
|
|
59
160
|
}
|
|
60
|
-
|
|
161
|
+
else {
|
|
61
162
|
return null;
|
|
62
163
|
}
|
|
63
|
-
|
|
164
|
+
if (at("op", "<")) {
|
|
165
|
+
name += eatGeneric();
|
|
166
|
+
}
|
|
64
167
|
while (at("op", "*")) {
|
|
65
168
|
i += 1;
|
|
66
169
|
}
|
|
@@ -129,14 +232,17 @@ function parse(source, fileName) {
|
|
|
129
232
|
eat("op", ")");
|
|
130
233
|
return { type: "getService", service };
|
|
131
234
|
}
|
|
235
|
+
if (at("ident") && CAST_NAMES.has(peek().value) && peek(1).value === "<") {
|
|
236
|
+
return parseNamedCast();
|
|
237
|
+
}
|
|
132
238
|
if (at("ident")) {
|
|
133
239
|
return { type: "ident", name: eat("ident").value };
|
|
134
240
|
}
|
|
241
|
+
if (at("op", "[")) {
|
|
242
|
+
return parseLambda();
|
|
243
|
+
}
|
|
135
244
|
if (at("op", "(")) {
|
|
136
|
-
|
|
137
|
-
const expr = parseExpr();
|
|
138
|
-
eat("op", ")");
|
|
139
|
-
return expr;
|
|
245
|
+
return parseParenPrimary();
|
|
140
246
|
}
|
|
141
247
|
if (at("op", "{")) {
|
|
142
248
|
return parseInitList();
|
|
@@ -309,11 +415,8 @@ function parse(source, fileName) {
|
|
|
309
415
|
throw error("only range-for is supported: for (auto* x : list)");
|
|
310
416
|
}
|
|
311
417
|
function parseDeclOrExpr() {
|
|
312
|
-
const isConst = at("kw", "const");
|
|
313
|
-
if (isConst) {
|
|
314
|
-
i += 1;
|
|
315
|
-
}
|
|
316
418
|
const saved = i;
|
|
419
|
+
const isConst = skipSpecifiers();
|
|
317
420
|
const maybeType = parseType();
|
|
318
421
|
if (maybeType && at("ident") && (peek(1).value === "=" || peek(1).value === ";")) {
|
|
319
422
|
const name = eat("ident").value;
|
|
@@ -325,9 +428,6 @@ function parse(source, fileName) {
|
|
|
325
428
|
return { type: "decl", name, valueType: maybeType, value, isConst: isConst || maybeType === "const" };
|
|
326
429
|
}
|
|
327
430
|
i = saved;
|
|
328
|
-
if (isConst) {
|
|
329
|
-
i = saved - 1;
|
|
330
|
-
}
|
|
331
431
|
return { type: "expr", expr: parseExpr() };
|
|
332
432
|
}
|
|
333
433
|
function parseStmt() {
|
|
@@ -380,6 +480,7 @@ function parse(source, fileName) {
|
|
|
380
480
|
};
|
|
381
481
|
}
|
|
382
482
|
function parseFunction() {
|
|
483
|
+
skipSpecifiers();
|
|
383
484
|
const returnType = parseType();
|
|
384
485
|
if (!returnType || !at("ident")) {
|
|
385
486
|
throw error("invalid function declaration");
|
|
@@ -449,10 +550,7 @@ function parse(source, fileName) {
|
|
|
449
550
|
continue;
|
|
450
551
|
}
|
|
451
552
|
const saved = i;
|
|
452
|
-
const isConst =
|
|
453
|
-
if (isConst) {
|
|
454
|
-
i += 1;
|
|
455
|
-
}
|
|
553
|
+
const isConst = skipSpecifiers();
|
|
456
554
|
const valueType = parseType();
|
|
457
555
|
if (valueType && at("ident")) {
|
|
458
556
|
if (peek(1).value === "(") {
|
|
@@ -573,10 +671,7 @@ function parse(source, fileName) {
|
|
|
573
671
|
continue;
|
|
574
672
|
}
|
|
575
673
|
const saved = i;
|
|
576
|
-
const isConst =
|
|
577
|
-
if (isConst) {
|
|
578
|
-
i += 1;
|
|
579
|
-
}
|
|
674
|
+
const isConst = skipSpecifiers();
|
|
580
675
|
const maybeType = parseType();
|
|
581
676
|
if (maybeType && at("ident")) {
|
|
582
677
|
const next = peek(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cluaupp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Cluaupp — the definitive merge of C++ and modern Luau. Source-to-source transpiler with first-class Roblox APIs.",
|
|
5
5
|
"author": "KartzDev",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,11 +36,12 @@
|
|
|
36
36
|
"prepare": "tsc",
|
|
37
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
38
|
"generate-api": "node scripts/generate-api.js",
|
|
39
|
-
"check-highlight": "
|
|
39
|
+
"check-highlight": "tsx web/src/check-content.ts",
|
|
40
40
|
"vendor-libs": "node scripts/vendor-libs.js",
|
|
41
41
|
"docs": "moonwave dev",
|
|
42
42
|
"docs:build": "moonwave build",
|
|
43
|
-
"site": "
|
|
43
|
+
"site": "vite build --config web/vite.config.mts && npm run check-highlight",
|
|
44
|
+
"site:dev": "vite --config web/vite.config.mts",
|
|
44
45
|
"dev": "node scripts/dev.js",
|
|
45
46
|
"stop": "node scripts/stop.js",
|
|
46
47
|
"prepublishOnly": "tsc && npm test"
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
},
|
|
69
70
|
"homepage": "https://github.com/KartzRbx/Cluaupp#readme",
|
|
70
71
|
"dependencies": {
|
|
72
|
+
"cluaupp": "^0.2.2",
|
|
71
73
|
"commander": "^15.0.0",
|
|
72
74
|
"comment-json": "^5.0.0",
|
|
73
75
|
"tree-sitter": "0.21.1",
|
|
@@ -81,7 +83,16 @@
|
|
|
81
83
|
},
|
|
82
84
|
"devDependencies": {
|
|
83
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",
|
|
84
93
|
"tsx": "^4.23.13",
|
|
85
|
-
"typescript": "^7.0.2"
|
|
94
|
+
"typescript": "^7.0.2",
|
|
95
|
+
"vite": "^8.0.0",
|
|
96
|
+
"vue": "^3.5.21"
|
|
86
97
|
}
|
|
87
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/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
|
|