cluaupp 0.2.2 → 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.
Files changed (129) hide show
  1. package/CHANGELOG.md +14 -75
  2. package/README.md +42 -88
  3. package/docs/README.md +25 -21
  4. package/docs/architecture.md +13 -76
  5. package/docs/cli.md +12 -64
  6. package/docs/comparison.md +6 -24
  7. package/docs/config.md +6 -7
  8. package/docs/cpp-advanced.md +6 -3
  9. package/docs/cpp-types.md +2 -0
  10. package/docs/examples/combat.md +19 -227
  11. package/docs/examples/hud.md +13 -81
  12. package/docs/examples/index.md +9 -16
  13. package/docs/examples/leaderstats.md +44 -115
  14. package/docs/getting-started.md +20 -135
  15. package/docs/intellisense.md +5 -42
  16. package/docs/intro.md +16 -15
  17. package/docs/libraries/dataservice.md +4 -1
  18. package/docs/libraries/index.md +2 -1
  19. package/docs/libraries/more.md +3 -1
  20. package/docs/libraries/ui.md +76 -0
  21. package/docs/migration.md +38 -0
  22. package/docs/oop/file-tags.md +12 -36
  23. package/docs/oop/index.md +18 -11
  24. package/docs/roblox-api.md +4 -56
  25. package/docs/syntax.md +54 -179
  26. package/editors/vscode/extension.js +1 -1
  27. package/editors/vscode/package.json +4 -4
  28. package/examples/README.md +5 -7
  29. package/examples/game/.vscode/extensions.json +2 -2
  30. package/examples/game/.vscode/settings.json +6 -23
  31. package/examples/game/src/client/hud.client.clpp +54 -0
  32. package/examples/game/src/client/init.client.clpp +6 -0
  33. package/examples/game/src/server/combat.server.clpp +85 -0
  34. package/examples/game/src/server/leaderstats.server.clpp +28 -0
  35. package/examples/game/src/shared/PlayerData.clh +20 -0
  36. package/examples/game/src/shared/config.clp +6 -0
  37. package/examples/game/src/shared/features.clp +88 -0
  38. package/generated/api.js +15 -1
  39. package/generated/cli.js +40 -24
  40. package/generated/clpp/contract.js +5 -0
  41. package/generated/clpp/index.js +27 -0
  42. package/generated/clpp/paths.js +121 -0
  43. package/generated/clpp/postprocess.js +78 -0
  44. package/generated/clpp/runner.js +102 -0
  45. package/generated/compile.js +26 -138
  46. package/generated/emit.js +79 -3
  47. package/generated/intellisense.js +83 -207
  48. package/generated/lex.js +3 -0
  49. package/generated/libs.js +1 -1
  50. package/generated/lsp.js +1 -8
  51. package/generated/parse.js +119 -24
  52. package/generated/preprocess.js +8 -179
  53. package/generated/system-understander.js +1 -1
  54. package/generated/transpile.js +3 -50
  55. package/generated/utils/project.js +13 -28
  56. package/include/{cluaupp/datatypes.hpp → clpp/datatypes.clh} +5 -1
  57. package/include/{cluaupp/generated/instances.hpp → clpp/generated/instances.clh} +1 -1
  58. package/include/{cluaupp/libs/chrono.hpp → clpp/libs/chrono.clh} +9 -9
  59. package/include/{cluaupp/libs/cmdr.hpp → clpp/libs/cmdr.clh} +4 -4
  60. package/include/{cluaupp/libs/dataservice.hpp → clpp/libs/dataservice.clh} +8 -15
  61. package/include/{cluaupp/libs/display.hpp → clpp/libs/display.clh} +1 -2
  62. package/include/{cluaupp/libs/ezvisual.hpp → clpp/libs/ezvisual.clh} +1 -3
  63. package/include/{cluaupp/libs/formatnumber.hpp → clpp/libs/formatnumber.clh} +2 -3
  64. package/include/{cluaupp/libs/fusion.hpp → clpp/libs/fusion.clh} +5 -5
  65. package/include/{cluaupp/libs/iris.hpp → clpp/libs/iris.clh} +2 -2
  66. package/include/{cluaupp/libs/janitor.hpp → clpp/libs/janitor.clh} +1 -2
  67. package/include/{cluaupp/libs/module3d.hpp → clpp/libs/module3d.clh} +2 -4
  68. package/include/{cluaupp/libs/net.hpp → clpp/libs/net.clh} +2 -4
  69. package/include/{cluaupp/libs/promise.hpp → clpp/libs/promise.clh} +1 -4
  70. package/include/{cluaupp/libs/spring.hpp → clpp/libs/spring.clh} +1 -2
  71. package/include/{cluaupp/libs/statemachine.hpp → clpp/libs/statemachine.clh} +2 -3
  72. package/include/{cluaupp/libs/stickybillboard.hpp → clpp/libs/stickybillboard.clh} +1 -2
  73. package/include/{cluaupp/libs/topbarplus.hpp → clpp/libs/topbarplus.clh} +3 -4
  74. package/include/clpp/libs.clh +24 -0
  75. package/include/clpp/roblox.clh +31 -0
  76. package/package.json +20 -16
  77. package/src/api.ts +15 -1
  78. package/src/cli.ts +39 -25
  79. package/src/clpp/contract.ts +39 -0
  80. package/src/clpp/index.ts +18 -0
  81. package/src/clpp/paths.ts +117 -0
  82. package/src/clpp/postprocess.ts +93 -0
  83. package/src/clpp/runner.ts +102 -0
  84. package/src/compile.ts +26 -149
  85. package/src/intellisense.ts +82 -211
  86. package/src/libs.ts +1 -1
  87. package/src/lsp.ts +1 -5
  88. package/src/preprocess.ts +8 -180
  89. package/src/system-understander.ts +1 -1
  90. package/src/transpile.ts +9 -52
  91. package/src/utils/project.ts +12 -28
  92. package/templates/game/.vscode/extensions.json +2 -2
  93. package/templates/game/.vscode/settings.json +6 -23
  94. package/templates/game/src/client/init.client.clpp +6 -0
  95. package/templates/game/src/server/leaderstats.server.clpp +29 -0
  96. package/templates/game/src/shared/config.clp +6 -0
  97. package/examples/game/.clangd +0 -25
  98. package/examples/game/.vscode/c_cpp_properties.json +0 -27
  99. package/examples/game/compile_flags.txt +0 -6
  100. package/examples/game/src/client/hud.client.cpp +0 -12
  101. package/examples/game/src/client/init.client.cpp +0 -6
  102. package/examples/game/src/server/combat.server.cpp +0 -23
  103. package/examples/game/src/server/leaderstats.server.cpp +0 -29
  104. package/examples/game/src/shared/config.cpp +0 -5
  105. package/examples/game/src/shared/config.h +0 -5
  106. package/include/cluaupp/libs.hpp +0 -25
  107. package/include/cluaupp/roblox.hpp +0 -47
  108. package/src/architecture.ts +0 -500
  109. package/src/ast.ts +0 -37
  110. package/src/emit.ts +0 -531
  111. package/src/emitter/luau-codegen.ts +0 -187
  112. package/src/emitter/translators.ts +0 -168
  113. package/src/headers.ts +0 -233
  114. package/src/layout.ts +0 -114
  115. package/src/lex.ts +0 -155
  116. package/src/parse.ts +0 -630
  117. package/src/parser/collector.ts +0 -148
  118. package/src/parser/index.ts +0 -27
  119. package/src/tree-sitter-cpp.d.ts +0 -4
  120. package/src/understand.ts +0 -322
  121. package/templates/game/.vscode/c_cpp_properties.json +0 -27
  122. package/templates/game/src/client/init.client.cpp +0 -6
  123. package/templates/game/src/server/leaderstats.server.cpp +0 -29
  124. package/templates/game/src/shared/config.cpp +0 -5
  125. package/templates/game/src/shared/config.h +0 -5
  126. /package/include/{cluaupp/generated/enums.hpp → clpp/generated/enums.clh} +0 -0
  127. /package/include/{cluaupp/libs/math.hpp → clpp/libs/math.clh} +0 -0
  128. /package/include/{cluaupp/libs/twinkle.hpp → clpp/libs/twinkle.clh} +0 -0
  129. /package/include/{cluaupp/libs/vfx.hpp → clpp/libs/vfx.clh} +0 -0
@@ -5,149 +5,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.compileSource = compileSource;
7
7
  exports.compileService = compileService;
8
- const node_fs_1 = __importDefault(require("node:fs"));
9
8
  const node_path_1 = __importDefault(require("node:path"));
10
- const parse_js_1 = require("./parse.js");
11
- const emit_js_1 = require("./emit.js");
12
- const preprocess_js_1 = require("./preprocess.js");
13
- const libs_js_1 = require("./libs.js");
14
- const architecture_js_1 = require("./architecture.js");
15
- const headers_js_1 = require("./headers.js");
16
- function shouldAttachRequires(name, contents) {
17
- const file = String(name || "").replace(/\\/g, "/");
18
- if (/\.json$/i.test(file) || /\.meta\.json$/i.test(file)) {
19
- return false;
20
- }
21
- const trimmed = String(contents || "").trim();
22
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
23
- return false;
24
- }
25
- return true;
26
- }
27
- function attachRequires(contents, source, ast, options, outName) {
28
- if (!shouldAttachRequires(outName, contents)) {
29
- return contents;
30
- }
31
- const libraries = String(contents).includes("CluauppLibs") ? [] : (0, libs_js_1.collectLibraries)(source, ast);
32
- return (0, libs_js_1.insertPreamble)(contents, options.moduleIncludes || [], libraries, outName);
33
- }
34
- function posixRel(from, file) {
35
- return node_path_1.default.relative(from, file).replace(/\\/g, "/");
36
- }
37
- function declKey(decl) {
38
- return `${decl.owner || ""}::${decl.name}`;
39
- }
40
- function mergeSiblingImpl(ast, implPath, options) {
41
- const implOptions = {
42
- ...options,
43
- moduleIncludes: [],
44
- seen: new Set(),
45
- pragmaResolved: false,
46
- };
47
- const prepared = (0, preprocess_js_1.preprocess)(node_fs_1.default.readFileSync(implPath, "utf8"), implPath, implOptions);
48
- const implAst = (0, parse_js_1.parse)(prepared, node_path_1.default.basename(implPath));
49
- if (!Array.isArray(ast.body)) {
50
- ast.body = [];
51
- }
52
- const existing = new Set((ast.body || [])
53
- .filter((decl) => decl && (decl.type === "function" || decl.type === "proto") && decl.name)
54
- .map(declKey));
55
- for (const decl of implAst.body || []) {
56
- if ((decl.type !== "function" && decl.type !== "proto") || !decl.name) {
57
- continue;
58
- }
59
- const key = declKey(decl);
60
- if (existing.has(key)) {
61
- continue;
62
- }
63
- existing.add(key);
64
- ast.body.push({
65
- type: "proto",
66
- name: decl.name,
67
- owner: decl.owner,
68
- returnType: decl.returnType,
69
- params: decl.params,
70
- });
71
- }
72
- }
73
- function wireHeaderImpl(ast, fileName, options) {
74
- const headerPath = options.filePath || null;
75
- const impl = headerPath ? (0, preprocess_js_1.siblingImplementation)(headerPath) : null;
76
- if (!impl) {
77
- return;
78
- }
79
- mergeSiblingImpl(ast, impl, options);
80
- const typeName = (0, headers_js_1.primaryHeaderName)(ast, options.relativeName || fileName);
81
- const implRel = options.srcDir ? posixRel(options.srcDir, impl) : node_path_1.default.basename(impl).replace(/\\/g, "/");
82
- const implOut = (0, preprocess_js_1.implOutName)(implRel);
83
- options.headerImplName = `${typeName}Module`;
84
- options.moduleIncludes = options.moduleIncludes || [];
85
- if (!options.moduleIncludes.some((spec) => spec.name === options.headerImplName)) {
86
- options.moduleIncludes.push({
87
- name: options.headerImplName,
88
- outRel: implOut,
89
- exports: { consts: [], structs: [] },
90
- });
91
- }
92
- }
93
- function compileHeader(source, fileName, options, ast) {
94
- wireHeaderImpl(ast, fileName, options);
95
- const rel = (options.relativeName || fileName || "header.h").replace(/\\/g, "/");
96
- const outName = (0, preprocess_js_1.toLuauPath)(rel);
97
- const luau = attachRequires((0, headers_js_1.emitHeaderModule)(ast, options), source, ast, options, outName);
98
- return {
99
- kind: "module",
100
- plan: { kind: "module", strict: options.strict === true },
101
- files: [{ name: outName, contents: luau }],
102
- stale: [],
103
- };
104
- }
9
+ const runner_js_1 = require("./clpp/runner.js");
10
+ const postprocess_js_1 = require("./clpp/postprocess.js");
11
+ const paths_js_1 = require("./clpp/paths.js");
105
12
  function compileSource(source, fileName, options = {}) {
106
- const opts = options;
107
- opts.moduleIncludes = opts.moduleIncludes || [];
108
- const prepared = (0, preprocess_js_1.preprocess)(source, opts.filePath || null, opts);
109
- const ast = (0, parse_js_1.parse)(prepared, fileName || "input.cpp");
110
- if ((0, preprocess_js_1.isHeaderFile)(fileName || "") || (0, preprocess_js_1.isHeaderFile)(opts.filePath || "")) {
111
- return compileHeader(source, fileName || "header.h", opts, ast).files[0].contents;
112
- }
113
- const luau = (0, emit_js_1.emit)(ast, opts);
114
- return attachRequires(luau, source, ast, opts, opts.outName || fileName || "input.cpp");
13
+ return compileService(source, fileName, options).files[0]?.contents ?? "";
115
14
  }
116
15
  function compileService(source, fileName, options = {}) {
117
- const opts = options;
118
- opts.moduleIncludes = opts.moduleIncludes || [];
119
- const prepared = (0, preprocess_js_1.preprocess)(source, opts.filePath || null, opts);
120
- const ast = (0, parse_js_1.parse)(prepared, fileName || "input.cpp");
121
- if ((0, preprocess_js_1.isHeaderFile)(fileName || "") || (0, preprocess_js_1.isHeaderFile)(opts.filePath || "") || (0, preprocess_js_1.isHeaderFile)(opts.relativeName || "")) {
122
- return compileHeader(source, fileName || "header.h", opts, ast);
123
- }
124
- const implHeader = opts.filePath ? (0, preprocess_js_1.siblingHeader)(opts.filePath) : null;
125
- if (implHeader) {
126
- opts.skipInit = true;
127
- }
128
- const planned = (0, architecture_js_1.planOutput)(ast, fileName || "input.cpp", {
129
- ...opts,
130
- relativeName: opts.relativeName || fileName,
131
- source: prepared,
132
- siblingHeader: implHeader,
16
+ const relative = (options.relativeName || fileName || "input.clpp").replace(/\\/g, "/");
17
+ const outName = options.outName || (0, paths_js_1.toLuauPath)(relative);
18
+ const header = options.filePath ? (0, paths_js_1.siblingHeader)(options.filePath) : null;
19
+ const skipInit = options.skipInit === true || (0, postprocess_js_1.shouldSkipInit)(relative, Boolean(header));
20
+ const diskPath = options.filePath ? node_path_1.default.resolve(options.filePath) : null;
21
+ const artifact = (0, runner_js_1.compileViaClpp)({
22
+ source,
23
+ fileName: diskPath ? diskPath.replace(/\\/g, "/") : relative,
24
+ strict: options.strict,
25
+ cwd: diskPath ? node_path_1.default.dirname(diskPath) : options.srcDir,
26
+ });
27
+ const luau = (0, postprocess_js_1.clppLuauToGame)(artifact, {
28
+ relativeName: relative,
29
+ outName,
30
+ srcDir: options.srcDir,
31
+ rootDir: options.rootDir,
32
+ strict: options.strict,
33
+ skipInit,
133
34
  });
134
- if (planned.files) {
135
- return {
136
- ...planned,
137
- files: planned.files.map((file) => ({
138
- ...file,
139
- contents: attachRequires(file.contents, source, ast, opts, file.name),
140
- })),
141
- };
142
- }
143
- const luau = attachRequires((0, emit_js_1.emit)(ast, opts), source, ast, opts, planned.outName || opts.outName || fileName || "input.cpp");
144
- const name = planned.outName ||
145
- opts.outName ||
146
- String(fileName || "input.cpp").replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
147
35
  return {
148
- kind: planned.kind || "flat",
149
- plan: planned.plan,
150
- files: [{ name, contents: luau }],
151
- stale: planned.stale || [],
36
+ kind: (0, paths_js_1.emitKind)(relative),
37
+ plan: { kind: (0, paths_js_1.emitKind)(relative), strict: options.strict === true },
38
+ files: [{ name: outName, contents: luau }],
39
+ stale: [],
152
40
  };
153
41
  }
package/generated/emit.js 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 || []) {
@@ -131,6 +132,35 @@ function emit(ast, options = {}) {
131
132
  }
132
133
  return linesOut;
133
134
  };
135
+ const isStringConcatCall = (node) => node && node.type === "call" && !node.object && node.name === "string_concat";
136
+ const emitStringConcat = (args) => {
137
+ const parts = (args || []).map(emitExpr);
138
+ if (parts.length === 0) {
139
+ return `""`;
140
+ }
141
+ if (parts.length === 1) {
142
+ return parts[0];
143
+ }
144
+ return `(${parts.join(" .. ")})`;
145
+ };
146
+ const isStringExpr = (node) => {
147
+ if (!node) {
148
+ return false;
149
+ }
150
+ if (node.type === "string") {
151
+ return true;
152
+ }
153
+ if (isStringConcatCall(node)) {
154
+ return true;
155
+ }
156
+ if (node.type === "binary" && node.op === "+" && (isStringExpr(node.left) || isStringExpr(node.right))) {
157
+ return true;
158
+ }
159
+ if (node.type === "member" && (node.name === "Name" || node.name === "Text" || node.name === "DisplayName")) {
160
+ return true;
161
+ }
162
+ return false;
163
+ };
134
164
  const emitExpr = (node) => {
135
165
  if (!node) {
136
166
  return "nil";
@@ -153,6 +183,28 @@ function emit(ast, options = {}) {
153
183
  }
154
184
  return `{ ${entries.join(", ")} }`;
155
185
  }
186
+ case "cast":
187
+ if (node.valueType === "void") {
188
+ return emitExpr(node.argument);
189
+ }
190
+ return emitExpr(node.argument);
191
+ case "lambda": {
192
+ const params = (node.params || [])
193
+ .map((param) => {
194
+ if (typeof param === "string") {
195
+ return param;
196
+ }
197
+ const typeAnn = (0, api_js_1.luauType)(param.valueType);
198
+ return typeAnn ? `${param.name}: ${typeAnn}` : param.name;
199
+ })
200
+ .join(", ");
201
+ const inner = [];
202
+ for (const stmt of node.body || []) {
203
+ inner.push(...emitStmt(stmt, 1));
204
+ }
205
+ const body = inner.length > 0 ? `\n${inner.join("\n")}\n` : "\n";
206
+ return `function(${params})${body}end`;
207
+ }
156
208
  case "unary": {
157
209
  const inner = emitExpr(node.argument);
158
210
  if (node.op === "!") {
@@ -172,6 +224,9 @@ function emit(ast, options = {}) {
172
224
  case "member":
173
225
  return `${emitExpr(node.object)}.${node.name}`;
174
226
  case "call": {
227
+ if (isStringConcatCall(node)) {
228
+ return emitStringConcat(node.args);
229
+ }
175
230
  const args = node.args.map(emitMethodArg).join(", ");
176
231
  if (!node.object) {
177
232
  if ((0, api_js_1.isDatatype)(node.name)) {
@@ -183,6 +238,9 @@ function emit(ast, options = {}) {
183
238
  return `${node.name}(${args})`;
184
239
  }
185
240
  const obj = emitExpr(node.object);
241
+ if (classMethods.has(node.name) && !localNames.has(node.name)) {
242
+ return `${obj}:${node.name}(${args})`;
243
+ }
186
244
  if (node.access === "::" && node.object.type === "ident" && node.object.name === "cout") {
187
245
  if (node.name === "endl") {
188
246
  return "print()";
@@ -220,6 +278,9 @@ function emit(ast, options = {}) {
220
278
  return printed.join("; ");
221
279
  }
222
280
  }
281
+ if (node.op === "+" && (isStringExpr(node.left) || isStringExpr(node.right))) {
282
+ return `${emitExpr(node.left)} .. ${emitExpr(node.right)}`;
283
+ }
223
284
  const ops = { "!=": "~=", "&&": "and", "||": "or" };
224
285
  const op = ops[node.op] || node.op;
225
286
  return `${emitExpr(node.left)} ${op} ${emitExpr(node.right)}`;
@@ -278,6 +339,9 @@ function emit(ast, options = {}) {
278
339
  return [`${prefix}${kind} ${node.name}${typed} = ${created.className}.new(${args})`];
279
340
  }
280
341
  const value = node.value ? emitExpr(node.value) : "nil";
342
+ if (!node.value && node.valueType && classOwners.has(node.valueType)) {
343
+ return [`${prefix}${kind} ${node.name}${typed} = ${node.valueType}`];
344
+ }
281
345
  return [`${prefix}${kind} ${node.name}${typed} = ${value}`];
282
346
  };
283
347
  const emitStmt = (node, indent) => {
@@ -290,6 +354,9 @@ function emit(ast, options = {}) {
290
354
  return emitNewDecl(node, indent);
291
355
  case "expr": {
292
356
  const expr = node.expr;
357
+ if (expr && expr.type === "cast" && expr.valueType === "void") {
358
+ return [];
359
+ }
293
360
  if (expr && expr.type === "binary" && expr.op === "<<") {
294
361
  const printed = emitCout(expr);
295
362
  if (printed) {
@@ -433,10 +500,8 @@ function emit(ast, options = {}) {
433
500
  lines.push(" }");
434
501
  lines.push("end");
435
502
  lines.push("");
436
- lines.push(`return ${root}`);
437
- lines.push("");
438
503
  };
439
- if (classModule) {
504
+ if (hasOwnedMethods) {
440
505
  for (const owner of classOwners) {
441
506
  lines.push(`local ${owner} = {}`);
442
507
  lines.push("");
@@ -446,6 +511,17 @@ function emit(ast, options = {}) {
446
511
  for (const root of structRoots) {
447
512
  emitStructConstructor(root);
448
513
  }
514
+ if (structRoots.length === 1) {
515
+ lines.push(`return ${structRoots[0]}`);
516
+ }
517
+ else {
518
+ lines.push("return {");
519
+ for (const root of structRoots) {
520
+ lines.push(` ${root} = ${root},`);
521
+ }
522
+ lines.push("}");
523
+ }
524
+ lines.push("");
449
525
  return lines.join("\n");
450
526
  }
451
527
  for (const decl of ast.body) {
@@ -3,10 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.intelliSenseMode = intelliSenseMode;
7
- exports.clangPath = clangPath;
8
- exports.writeCompileCommands = writeCompileCommands;
9
- exports.writeClangd = writeClangd;
6
+ exports.isClppSource = void 0;
10
7
  exports.diagnosticsFor = diagnosticsFor;
11
8
  exports.syncEditorSupport = syncEditorSupport;
12
9
  exports.installEditorExtension = installEditorExtension;
@@ -14,83 +11,11 @@ exports.installEditorSupport = installEditorSupport;
14
11
  const node_fs_1 = __importDefault(require("node:fs"));
15
12
  const node_os_1 = __importDefault(require("node:os"));
16
13
  const node_path_1 = __importDefault(require("node:path"));
17
- const parse_js_1 = require("./parse.js");
18
- const preprocess_js_1 = require("./preprocess.js");
19
- const editor_install_js_1 = require("./editor-install.js");
14
+ const node_child_process_1 = require("node:child_process");
20
15
  const package_info_js_1 = require("./package-info.js");
21
- function posix(file) {
22
- return String(file).replace(/\\/g, "/");
23
- }
24
- function collectFiles(dir, files = []) {
25
- if (!node_fs_1.default.existsSync(dir)) {
26
- return files;
27
- }
28
- for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
29
- const full = node_path_1.default.join(dir, entry.name);
30
- if (entry.isDirectory()) {
31
- collectFiles(full, files);
32
- }
33
- else {
34
- files.push(full);
35
- }
36
- }
37
- return files;
38
- }
39
- function isCppFile(file) {
40
- return /\.(cpp|cc|cxx|c|h|hpp|hh)$/i.test(file);
41
- }
42
- function clangCompileArgs(root) {
43
- const absRoot = posix(node_path_1.default.resolve(root));
44
- const absInclude = posix(node_path_1.default.join(absRoot, "include"));
45
- const absSrc = posix(node_path_1.default.join(absRoot, "src"));
46
- const roblox = posix(node_path_1.default.join(absInclude, "cluaupp", "roblox.hpp"));
47
- return [
48
- "-xc++",
49
- "-std=c++20",
50
- "-ferror-limit=0",
51
- "-I" + absInclude,
52
- "-I" + absSrc,
53
- "-Iinclude",
54
- "-Isrc",
55
- "-include",
56
- roblox,
57
- ];
58
- }
59
- function compileCommandEntry(root, file) {
60
- const absRoot = posix(node_path_1.default.resolve(root));
61
- const absFile = posix(node_path_1.default.resolve(file));
62
- return {
63
- directory: absRoot,
64
- file: absFile,
65
- arguments: [clangPath(), ...clangCompileArgs(root), "-c", absFile],
66
- };
67
- }
68
- function mergeJson(file, patch) {
69
- let current = {};
70
- if (node_fs_1.default.existsSync(file)) {
71
- try {
72
- current = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
73
- }
74
- catch {
75
- current = {};
76
- }
77
- }
78
- const next = { ...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, ...value };
84
- }
85
- }
86
- const json = `${JSON.stringify(next, null, "\t")}\n`;
87
- if (node_fs_1.default.existsSync(file) && node_fs_1.default.readFileSync(file, "utf8") === json) {
88
- return false;
89
- }
90
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
91
- node_fs_1.default.writeFileSync(file, json, "utf8");
92
- return true;
93
- }
16
+ const contract_js_1 = require("./clpp/contract.js");
17
+ const runner_js_1 = require("./clpp/runner.js");
18
+ const paths_js_1 = require("./clpp/paths.js");
94
19
  function copyDir(from, to) {
95
20
  node_fs_1.default.mkdirSync(to, { recursive: true });
96
21
  for (const entry of node_fs_1.default.readdirSync(from, { withFileTypes: true })) {
@@ -104,147 +29,90 @@ function copyDir(from, to) {
104
29
  }
105
30
  }
106
31
  }
107
- function clangdEditorArgs() {
108
- return [
109
- "--compile-commands-dir=${workspaceFolder}",
110
- "--header-insertion=never",
111
- "--query-driver=**/clang++*,**/clang++.exe,**/g++*,**/g++.exe",
112
- ];
32
+ function uniqueStrings(...lists) {
33
+ const seen = new Set();
34
+ const out = [];
35
+ for (const list of lists) {
36
+ if (!Array.isArray(list)) {
37
+ continue;
38
+ }
39
+ for (const item of list) {
40
+ if (typeof item === "string" && item && !seen.has(item)) {
41
+ seen.add(item);
42
+ out.push(item);
43
+ }
44
+ }
45
+ }
46
+ return out;
113
47
  }
114
- function intelliSenseMode() {
115
- const arch = process.arch === "arm64" ? "arm64" : "x64";
116
- if (process.platform === "win32") {
117
- return `windows-clang-${arch}`;
48
+ function readJsonObject(file) {
49
+ if (!node_fs_1.default.existsSync(file)) {
50
+ return {};
118
51
  }
119
- if (process.platform === "darwin") {
120
- return `macos-clang-${arch}`;
52
+ try {
53
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
54
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
55
+ return parsed;
56
+ }
121
57
  }
122
- return `linux-clang-${arch}`;
123
- }
124
- function clangPath() {
125
- return (0, editor_install_js_1.findClangPlusPlus)() || "clang++";
126
- }
127
- function writeCompileCommands(root, srcDir) {
128
- const files = collectFiles(srcDir).filter((file) => isCppFile(file)).sort();
129
- const json = `${JSON.stringify(files.map((file) => compileCommandEntry(root, file)), null, "\t")}\n`;
130
- const dest = node_path_1.default.join(root, "compile_commands.json");
131
- if (node_fs_1.default.existsSync(dest) && node_fs_1.default.readFileSync(dest, "utf8") === json) {
132
- return false;
58
+ catch {
59
+ // ignore
133
60
  }
134
- node_fs_1.default.writeFileSync(dest, json, "utf8");
135
- return true;
61
+ return {};
136
62
  }
137
- function writeClangd(root) {
138
- const flags = clangCompileArgs(root)
139
- .map((flag) => ` - ${flag}`)
140
- .join("\n");
141
- const contents = `CompileFlags:
142
- CompilationDatabase: .
143
- Add:
144
- ${flags}
145
-
146
- Diagnostics:
147
- Suppress: '*'
148
-
149
- ---
150
- If:
151
- PathMatch: .*\\.(cpp|cc|cxx|c|h|hpp|hh)$
152
- CompileFlags:
153
- Add:
154
- - -xc++
155
-
156
- ---
157
- If:
158
- PathMatch: (out|libs)/.*
159
- Index:
160
- Background: Skip
161
- `;
162
- const dest = node_path_1.default.join(root, ".clangd");
163
- if (node_fs_1.default.existsSync(dest) && node_fs_1.default.readFileSync(dest, "utf8") === contents) {
164
- return false;
63
+ function writeJson(file, value) {
64
+ const json = `${JSON.stringify(value, null, "\t")}\n`;
65
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
66
+ if (node_fs_1.default.existsSync(file) && node_fs_1.default.readFileSync(file, "utf8") === json) {
67
+ return;
165
68
  }
166
- node_fs_1.default.writeFileSync(dest, contents, "utf8");
167
- return true;
69
+ node_fs_1.default.writeFileSync(file, json, "utf8");
168
70
  }
169
71
  function writeVscode(root) {
170
- const settings = node_path_1.default.join(root, ".vscode", "settings.json");
171
- mergeJson(settings, {
172
- "C_Cpp.intelliSenseEngine": "Disabled",
173
- "C_Cpp.default.cppStandard": "c++20",
174
- "C_Cpp.default.cStandard": "c17",
175
- "C_Cpp.default.compilerPath": clangPath(),
176
- "C_Cpp.default.intelliSenseMode": intelliSenseMode(),
177
- "C_Cpp.default.includePath": ["${workspaceFolder}/include", "${workspaceFolder}/src"],
178
- "clangd.enable": true,
179
- "clangd.arguments": clangdEditorArgs(),
180
- "files.associations": {
181
- "*.hpp": "cpp",
182
- "*.h": "cpp",
183
- "*.server.cpp": "cpp",
184
- "*.client.cpp": "cpp",
185
- "*.plugin.cpp": "cpp",
186
- "*.legacy.cpp": "cpp",
187
- "*.legacy.server.cpp": "cpp",
188
- "*.legacy.client.cpp": "cpp",
189
- },
72
+ const settingsPath = node_path_1.default.join(root, ".vscode", "settings.json");
73
+ const current = readJsonObject(settingsPath);
74
+ const associations = {
75
+ ...((current["files.associations"] && typeof current["files.associations"] === "object" && !Array.isArray(current["files.associations"]))
76
+ ? current["files.associations"]
77
+ : {}),
78
+ "*.clpp": "clpp",
79
+ "*.clp": "clpp",
80
+ "*.clh": "clpp",
81
+ "*.server.clpp": "clpp",
82
+ "*.client.clpp": "clpp",
83
+ "*.plugin.clpp": "clpp",
84
+ };
85
+ writeJson(settingsPath, {
86
+ ...current,
87
+ "files.associations": associations,
190
88
  });
191
- mergeJson(node_path_1.default.join(root, ".vscode", "extensions.json"), {
192
- recommendations: ["llvm-vs-code-extensions.vscode-clangd"],
89
+ const extensionsPath = node_path_1.default.join(root, ".vscode", "extensions.json");
90
+ const extensions = readJsonObject(extensionsPath);
91
+ writeJson(extensionsPath, {
92
+ ...extensions,
93
+ recommendations: uniqueStrings(extensions.recommendations),
94
+ unwantedRecommendations: uniqueStrings(extensions.unwantedRecommendations, ["ms-vscode.cpptools"]),
193
95
  });
194
- const props = node_path_1.default.join(root, ".vscode", "c_cpp_properties.json");
195
- const nextProps = {
196
- configurations: [
197
- {
198
- name: "Cluaupp",
199
- compilerPath: clangPath(),
200
- cStandard: "c17",
201
- cppStandard: "c++20",
202
- intelliSenseMode: intelliSenseMode(),
203
- includePath: ["${workspaceFolder}/include", "${workspaceFolder}/src"],
204
- forcedInclude: ["${workspaceFolder}/include/cluaupp/roblox.hpp"],
205
- compileCommands: "${workspaceFolder}/compile_commands.json",
206
- browse: {
207
- path: ["${workspaceFolder}/include", "${workspaceFolder}/src"],
208
- limitSymbolsToIncludedHeaders: true,
209
- },
210
- },
211
- ],
212
- version: 4,
213
- };
214
- const json = `${JSON.stringify(nextProps, null, "\t")}\n`;
215
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(props), { recursive: true });
216
- if (!node_fs_1.default.existsSync(props) || node_fs_1.default.readFileSync(props, "utf8") !== json) {
217
- node_fs_1.default.writeFileSync(props, json, "utf8");
218
- }
219
96
  }
220
- function diagnosticsFor(source, fileName, options = {}) {
97
+ function diagnosticsFor(source, fileName) {
98
+ if (!(0, runner_js_1.hasClpp)()) {
99
+ return [];
100
+ }
221
101
  try {
222
- const prepared = options.filePath ? (0, preprocess_js_1.preprocess)(source, options.filePath, options) : source;
223
- (0, parse_js_1.parse)(prepared, fileName || "input.cpp");
102
+ (0, runner_js_1.compileViaClpp)({ source, fileName: fileName || "input.clpp" });
224
103
  return [];
225
104
  }
226
105
  catch (err) {
227
- const error = err;
228
- const message = error.message ? String(error.message) : "parse error";
106
+ const message = err instanceof Error ? err.message : String(err);
229
107
  const match = message.match(/:(\d+):(\d+):\s*(.*)$/);
230
108
  if (match) {
231
109
  return [{ line: Number(match[1]), col: Number(match[2]), message: match[3], severity: "error" }];
232
110
  }
233
- return [{ line: error.line || 1, col: error.col || 1, message, severity: "error" }];
111
+ return [{ line: 1, col: 1, message, severity: "error" }];
234
112
  }
235
113
  }
236
- function syncEditorSupport(root, config = {}) {
237
- const srcDir = node_path_1.default.join(root, config.rootDir || "src");
238
- if (node_fs_1.default.existsSync(srcDir)) {
239
- writeCompileCommands(root, srcDir);
240
- }
241
- writeClangd(root);
114
+ function syncEditorSupport(root, _config = {}) {
242
115
  writeVscode(root);
243
- const flags = ["-xc++", "-std=c++20", "-ferror-limit=0", "-Iinclude", "-Isrc", "-include", "include/cluaupp/roblox.hpp"].join("\n") + "\n";
244
- const flagsFile = node_path_1.default.join(root, "compile_flags.txt");
245
- if (!node_fs_1.default.existsSync(flagsFile) || node_fs_1.default.readFileSync(flagsFile, "utf8") !== flags) {
246
- node_fs_1.default.writeFileSync(flagsFile, flags, "utf8");
247
- }
248
116
  }
249
117
  function installEditorExtension(cluauppRoot = package_info_js_1.projectRoot) {
250
118
  const from = node_path_1.default.join(cluauppRoot, "editors", "vscode");
@@ -266,12 +134,20 @@ function installEditorExtension(cluauppRoot = package_info_js_1.projectRoot) {
266
134
  }
267
135
  return installed;
268
136
  }
269
- async function installEditorSupport(cluauppRoot = package_info_js_1.projectRoot) {
270
- const local = installEditorExtension(cluauppRoot);
271
- const llvm = (0, editor_install_js_1.ensureLlvm)();
272
- (0, editor_install_js_1.reportInstall)(llvm, "LLVM clang++");
273
- const clangd = await (0, editor_install_js_1.installMarketplaceExtension)(editor_install_js_1.CLANGD_ID, "clangd (LLVM)");
274
- (0, editor_install_js_1.reportInstall)(clangd, "clangd");
275
- console.log("cluaupp: C++ completion is clangd — reload the editor (Ctrl+Shift+P → Developer: Reload Window)");
276
- return { local, llvm, clangd };
137
+ async function installEditorSupport() {
138
+ const local = installEditorExtension();
139
+ if ((0, runner_js_1.hasClpp)()) {
140
+ const result = (0, node_child_process_1.spawnSync)("clpp", ["install"], { encoding: "utf8", windowsHide: true });
141
+ if (result.status === 0) {
142
+ console.log(String(result.stderr || result.stdout || "clpp install ok").trim());
143
+ }
144
+ else {
145
+ console.log("cluaupp:", contract_js_1.CLPP_INSTALL_HINT);
146
+ }
147
+ }
148
+ else {
149
+ console.log("cluaupp:", contract_js_1.CLPP_INSTALL_HINT);
150
+ }
151
+ return { local };
277
152
  }
153
+ exports.isClppSource = paths_js_1.isSourceFile;