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.
Files changed (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/docs/README.md +32 -20
  4. package/docs/cli.md +1 -1
  5. package/docs/cpp-advanced.md +6 -3
  6. package/docs/cpp-types.md +2 -0
  7. package/docs/examples/hud.md +1 -1
  8. package/docs/examples/index.md +1 -1
  9. package/docs/examples/leaderstats.md +44 -115
  10. package/docs/getting-started.md +4 -2
  11. package/docs/intellisense.md +4 -5
  12. package/docs/libraries/dataservice.md +4 -1
  13. package/docs/libraries/index.md +2 -1
  14. package/docs/libraries/more.md +3 -1
  15. package/docs/libraries/ui.md +76 -0
  16. package/docs/oop/index.md +20 -7
  17. package/docs/roblox-api.md +4 -56
  18. package/docs/syntax.md +112 -55
  19. package/editors/vscode/package.json +25 -25
  20. package/examples/game/.vscode/extensions.json +3 -0
  21. package/examples/game/.vscode/settings.json +0 -9
  22. package/examples/game/src/shared/config.h +2 -0
  23. package/generated/api.js +15 -1
  24. package/generated/compile.js +38 -0
  25. package/generated/emit.js +79 -3
  26. package/generated/intellisense.js +75 -80
  27. package/generated/lex.js +3 -0
  28. package/generated/parse.js +119 -24
  29. package/generated/parser/collector.js +37 -1
  30. package/generated/utils/rojo-mapper.js +23 -3
  31. package/include/cluaupp/datatypes.hpp +4 -0
  32. package/package.json +98 -87
  33. package/src/api.ts +15 -1
  34. package/src/compile.ts +43 -1
  35. package/src/emit.ts +81 -3
  36. package/src/intellisense.ts +80 -80
  37. package/src/lex.ts +3 -0
  38. package/src/parse.ts +124 -25
  39. package/src/parser/collector.ts +35 -2
  40. package/src/utils/rojo-mapper.ts +26 -3
  41. package/templates/game/.vscode/extensions.json +3 -0
  42. package/templates/game/.vscode/settings.json +0 -9
  43. package/templates/game/src/shared/config.h +2 -0
  44. package/examples/game/.vscode/c_cpp_properties.json +0 -27
  45. 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.1",
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": "node scripts/check-highlight.js",
40
- "vendor-libs": "node scripts/vendor-libs.js",
41
- "docs": "moonwave dev",
42
- "docs:build": "moonwave build",
43
- "site": "node scripts/generate-api.js && node scripts/check-highlight.js",
44
- "dev": "node scripts/dev.js",
45
- "stop": "node scripts/stop.js",
46
- "prepublishOnly": "tsc && npm test"
47
- },
48
- "engines": {
49
- "node": ">=18"
50
- },
51
- "keywords": [
52
- "luau",
53
- "roblox",
54
- "cpp",
55
- "c++",
56
- "transpiler",
57
- "compiler",
58
- "rojo",
59
- "cluaupp",
60
- "cluau"
61
- ],
62
- "repository": {
63
- "type": "git",
64
- "url": "git+https://github.com/KartzRbx/Cluaupp.git"
65
- },
66
- "bugs": {
67
- "url": "https://github.com/KartzRbx/Cluaupp/issues"
68
- },
69
- "homepage": "https://github.com/KartzRbx/Cluaupp#readme",
70
- "dependencies": {
71
- "commander": "^15.0.0",
72
- "comment-json": "^5.0.0",
73
- "tree-sitter": "0.21.1",
74
- "tree-sitter-c": "0.23.2",
75
- "tree-sitter-cpp": "0.23.4",
76
- "vscode-languageserver": "^10.1.1",
77
- "vscode-languageserver-textdocument": "^1.0.14"
78
- },
79
- "overrides": {
80
- "tree-sitter-c": "0.23.2"
81
- },
82
- "devDependencies": {
83
- "@types/node": "^22.20.3",
84
- "tsx": "^4.23.13",
85
- "typescript": "^7.0.2"
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).replace(/\*+$/, "").replace(/^const\s+/, "").replace(/^Enum::/, "Enum.");
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 (classModule) {
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
 
@@ -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 settings = path.join(root, ".vscode", "settings.json");
177
- mergeJson(settings, {
178
- "C_Cpp.intelliSenseEngine": "Disabled",
179
- "C_Cpp.default.cppStandard": "c++20",
180
- "C_Cpp.default.cStandard": "c17",
181
- "C_Cpp.default.compilerPath": clangPath(),
182
- "C_Cpp.default.intelliSenseMode": intelliSenseMode(),
183
- "C_Cpp.default.includePath": ["${workspaceFolder}/include", "${workspaceFolder}/src"],
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
- mergeJson(path.join(root, ".vscode", "extensions.json"), {
198
- recommendations: ["llvm-vs-code-extensions.vscode-clangd"],
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
- const nextProps = {
202
- configurations: [
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
 
package/src/lex.ts CHANGED
@@ -32,6 +32,9 @@ const KEYWORDS = new Set([
32
32
  "case",
33
33
  "default",
34
34
  "break",
35
+ "static",
36
+ "constexpr",
37
+ "inline",
35
38
  ]);
36
39
 
37
40
  export function tokenize(source: string): Token[] {