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/docs/syntax.md CHANGED
@@ -2,16 +2,39 @@
2
2
 
3
3
  Cluaupp is not a full C++ compiler. It is a **subset** aimed at Roblox scripts, in the same spirit as roblox-ts (restricted TypeScript → Luau).
4
4
 
5
- Generated Luau follows the current language: [`local`](https://luau.org/getting-started), `const`, and `const function`. Injected `require` / `GetService` lines are `const`. `--!strict` is emitted only when the file has `#pragma strict` (or `"strict": true` in config).
5
+ Live grid of every construct: **[Language reference](https://kartzrbx.github.io/Cluaupp/docs/reference.html)**.
6
6
 
7
- ## Files
7
+ Generated Luau follows the current language: [`local`](https://luau.org/getting-started), `const`, and `const function`. Injected `require` / `GetService` lines are `const`. `--!strict` is emitted only when the file has `#pragma strict` (or `"strict": true` in config). `#pragma nstrict` never emits it.
8
8
 
9
- - Extensions: `.cpp`, `.h`, `.hpp` (also `.cc`, `.hh`)
10
- - Quoted `#include "file.h"` becomes a Rojo `require` (or is inlined for a header that belongs to the same `.cpp`)
11
- - `#include <cluaupp/roblox.hpp>` and `<cluaupp/libs/*.hpp>` are IntelliSense only; library headers also inject `require(CluauppLibs.*)`
12
- - `#pragma once` is ignored
13
- - `#pragma strict` / `#pragma nstrict` control `--!strict` for that compilation unit
14
- - Function prototypes in a `.h` / `.hpp` become `export type` fields (`init: (self: Name) -> ()`). Only `.cpp` files emit function bodies.
9
+ ## Files and includes
10
+
11
+ - Extensions: `.cpp`, `.cc`, `.cxx`, `.c`, `.h`, `.hpp`, `.hh`
12
+ - Tags: `*.server.cpp` → Script, `*.client.cpp` → LocalScript, `*.plugin.cpp` → Plugin, `*.legacy*` → Legacy, untagged → ModuleScript
13
+ - Quoted `#include "Own.h"` (same stem as the `.cpp`) is **inlined**. Other quoted includes become `require` (Rojo path)
14
+ - `#include <cluaupp/roblox.hpp>` is IntelliSense only. `#include <cluaupp/libs/janitor.hpp>` also injects `require(CluauppLibs.Janitor)`
15
+ - `#pragma once` is ignored. Other macros are not expanded. `//` and `/* */` comments are stripped
16
+ - `using …;` is skipped. `namespace { }` is flattened. `enum` / `template` / `typedef` / `extern` declarations are skipped
17
+
18
+ ## Types
19
+
20
+ | C++ | Luau |
21
+ | --- | --- |
22
+ | `int` / `float` / `double` | `number` |
23
+ | `bool` | `boolean` |
24
+ | `string` | `string` (not `std::string`) |
25
+ | `void` | no return annotation |
26
+ | `auto` / `auto*` | inferred from `new` / `GetService` / datatype ctor |
27
+ | `Player*` / `Folder*` | `Player` / `Folder` (Instance handle, not a heap address) |
28
+ | `LuaArray<T>` / `vector<T>` / `array<T>` / `span<T>` | `{T}` |
29
+ | `optional<T>` | `T?` |
30
+ | `const` / `static constexpr` | Luau `const` |
31
+ | `nullptr` | `nil` |
32
+ | `Vector3` `CFrame` `UDim2` `Color3` | same names (copied values) |
33
+ | `Enum::Material::Plastic` | `Enum.Material.Plastic` |
34
+
35
+ Always initialize: `int coins = 0;` not `int coins;` (uninitialized emits `nil`). There is no `delete`, no `*part`, no `&part`, no `int&`. Numbers copy; Instances mutate through `->`.
36
+
37
+ `auto` infers when the value is `new Class(...)`, `GetService<T>()`, or a datatype constructor. Prefer explicit types on parameters and struct fields.
15
38
 
16
39
  ## Functions
17
40
 
@@ -35,28 +58,18 @@ const function doubleCoins(coins: number): number
35
58
  end
36
59
  ```
37
60
 
38
- `void` omits a return annotation. `int` / `float` / `double` → `number`. `bool` → `boolean`. Roblox types (`Player`, `Folder`) keep their name.
39
-
40
- If `void init()` exists, emit appends `init()` at the end of the file.
41
-
42
- ## Locals and const
61
+ - Only functions with a body are emitted from a `.cpp`. Prototypes in a `.h` become `export type` fields
62
+ - No overloading — one name, one emit. No default arguments
63
+ - If `void init()` exists, emit appends `init()` at the end of Scripts / LocalScripts. There is no `int main()`
64
+ - Shared ModuleScripts should not define `init()` unless you want them to run on `require`
43
65
 
44
- Luau uses `local` for variables and `const` for constants. `const int` in C++ becomes `const` in Luau.
66
+ ## Scopes
45
67
 
46
- ```cpp
47
- int coins = 10;
48
- const int STARTING_COINS = 0;
49
- auto* folder = new Folder(player);
50
- ```
51
-
52
- ```luau
53
- local coins: number = 10
54
- const STARTING_COINS: number = 0
55
- local folder: Folder = Instance.new("Folder")
56
- folder.Parent = player
57
- ```
58
-
59
- `auto` infers the type when the value is `new Class(...)` or `GetService<Service>()`.
68
+ - File-level decls become file `local` / `const`
69
+ - Function decls become `local` from that line to the end of the block. Nested `{ }` can shadow
70
+ - In `Class::Method`, `this` is `self`. Bare field names become `self.field`. Bare calls to other methods become `self:Method(...)`. Parameters and locals shadow fields
71
+ - Globals stay bare: `print`, `game`, `workspace`, library types, Instance types, datatypes
72
+ - Instances are not RAII. Leaving a block does not Destroy a Part — use Janitor
60
73
 
61
74
  ## Control flow
62
75
 
@@ -121,9 +134,9 @@ repeat
121
134
  until true
122
135
  ```
123
136
 
124
- `switch` evaluates the discriminant **once**, then becomes `if` / `elseif` / `else` inside `repeat … until true` so `break` still exits the switch (even from inside an `if`). Stacked `case` labels share a body (`case "buy": case "purchase":`). There is no C-style fall-through into the next case’s statements. C-style `for (int i = 0; i < n; i++)` is not supported yet.
137
+ `switch` evaluates the discriminant **once**, then becomes `if` / `elseif` / `else` inside `repeat … until true` so `break` still exits the switch (even from inside an `if`). Stacked `case` labels share a body. There is no C-style fall-through. C-style `for (int i = 0; i < n; i++)` is not supported. No `continue`, no `do/while`, no ternary. `else if (x)` emits a nested `if` inside `else` (not Luau `elseif`).
125
138
 
126
- ## Expressions
139
+ ## Expressions and operators
127
140
 
128
141
  | C++ | Luau |
129
142
  | --- | --- |
@@ -132,15 +145,20 @@ until true
132
145
  | `!=` | `~=` |
133
146
  | `&&` | `and` |
134
147
  | `\|\|` | `or` |
135
- | `==` `+` `-` `*` `/` `<` `>` `<=` `>=` | same |
148
+ | `!` | `not` |
149
+ | `==` `+` `-` `*` `/` `<` `>` `<=` `>=` | same (`*` is multiply) |
136
150
  | `"text"` | `"text"` |
151
+ | `=` | assignment |
137
152
  | `obj->Prop` | `obj.Prop` |
138
- | `obj->Method(a)` | `obj:Method(a)` if the method is a Roblox API |
153
+ | `obj->Method(a)` | `obj:Method(a)` if the method is a Roblox / library API |
139
154
  | `fn(a)` | `fn(a)` |
140
155
  | `signal.Connect(fn)` | `signal:Connect(fn)` |
141
156
  | `Type { .Field = value }` | `{ Field = value }` |
157
+ | unary `-` | `-` |
158
+
159
+ No `++` `--` `+=`. Write `n = n + 1`. Binary operators in this subset are left-associative — use parentheses when mixing.
142
160
 
143
- Designated initializers become Luau tables. Nested braces work the same way:
161
+ Designated initializers become Luau tables:
144
162
 
145
163
  ```cpp
146
164
  DataService::Server.Init(DataServiceOptions {
@@ -158,48 +176,87 @@ DataService.Server:Init({
158
176
  })
159
177
  ```
160
178
 
179
+ ## Strings and string_concat
180
+
181
+ Luau concatenates with `..`. Do not write `..` in a `.cpp`.
182
+
183
+ - If either side of `+` is a string literal, another concat, or `.Name` / `.Text` / `.DisplayName`, `+` becomes `..`
184
+ - `string_concat(a, b, c)` is variadic: zero args → `""`, one arg → that value, several → `(a .. b .. c)`
185
+ - Declared in `datatypes.hpp` (via `roblox.hpp`)
186
+
187
+ ```cpp
188
+ return player->Name + "_LeaderstatsJanitor";
189
+ return string_concat(player->Name, "_", "LeaderstatsJanitor");
190
+ ```
191
+
192
+ ```luau
193
+ return player.Name .. "_LeaderstatsJanitor"
194
+ return (player.Name .. "_" .. "LeaderstatsJanitor")
195
+ ```
196
+
197
+ ## print / cout
198
+
199
+ `print`, `warn`, and `error` work as in Luau. `cout << … << endl` is the C++ spelling — each `<<` is another argument, `endl` ends the line. `cout::warn` / `cout::error` / `cout::ping` pick the Roblox function. `cerr` is `warn`. Full table: [print and cout](print-cout.md).
200
+
161
201
  ## `new` and services
162
202
 
163
203
  ```cpp
164
204
  auto* coins = new IntValue(leaderstats);
165
205
  auto* players = GetService<Players>();
206
+ auto* janitor = new Janitor();
207
+ part->Size = Vector3(8, 1, 8);
208
+ part->CFrame = CFrame::lookAt(from, look);
209
+ part->Material = Enum::Material::Plastic;
166
210
  ```
167
211
 
168
212
  ```luau
169
213
  local coins: IntValue = Instance.new("IntValue")
170
214
  coins.Parent = leaderstats
171
215
  local players: Players = game:GetService("Players")
216
+ local janitor = Janitor.new()
217
+ part.Size = Vector3.new(8, 1, 8)
218
+ part.CFrame = CFrame.lookAt(from, look)
219
+ part.Material = Enum.Material.Plastic
172
220
  ```
173
221
 
174
- The first argument of `new Class(parent)` becomes `.Parent`. With no argument: `Instance.new("Class")` only.
222
+ The first argument of `new Class(parent)` becomes `.Parent` for Instance classes. Libraries use `Janitor.new()`. Datatypes use `Vector3(...)`, not `new`.
175
223
 
176
- ## print / cout
224
+ ## Casts
177
225
 
178
- `print`, `warn`, and `error` work as in Luau. `cout << … << endl` is the C++ spelling — each `<<` is another argument, `endl` ends the line. `cout::warn` / `cout::error` / `cout::ping` pick the Roblox function.
226
+ `static_cast<T>(x)`, `const_cast`, `reinterpret_cast`, `dynamic_cast` emit the argument. `(void)x;` is omitted (silences unused for clangd). Luau has no casts — they do not check ClassName.
227
+
228
+ ## Callbacks (events and lambdas)
229
+
230
+ Signals use `.Connect`. Callbacks may be **named functions** or **lambdas** (`[](Player* player) { ... }`, including `[&]` / `[=]` / `[coinsValue]`). Capture lists are accepted; Luau closures do not copy C++ captures — keep Instances alive with Janitor.
231
+
232
+ Passing a method by name from inside `Class::` binds `self`: `function(...) self:OnPlayer(...) end`.
179
233
 
180
234
  ```cpp
181
- cout << "EnsureStat: " << name << " not found" << endl;
182
- cout::print << "EnsureStat: " << name << " not found" << endl;
183
- cerr << "failed";
184
- cout::print("ok");
185
- cout::warn("careful");
186
- cout::error("fail");
187
- cout::ping("here");
235
+ players->PlayerAdded.Connect([&](Player* playerEntered) {
236
+ leaderstatsServer.PlayerEntered(playerEntered);
237
+ });
238
+ game->BindToClose([&]() {
239
+ leaderstatsServer.janitor->Cleanup();
240
+ });
188
241
  ```
189
242
 
190
- ```luau
191
- print("EnsureStat: ", name, " not found")
192
- warn("failed")
193
- print("ok")
194
- warn("careful")
195
- error("fail")
196
- print("here")
197
- ```
243
+ ## OOP: structs, methods, singletons
244
+
245
+ How you write a type: a `struct` (or `class`) in the sibling `.h`, `Class::Method` in the `.cpp`, `void init()` to boot.
246
+
247
+ - `static constexpr` fields are `const`
248
+ - Fields are `self.field`. `public:` / `private:` are ignored
249
+ - Nested structs with defaults become nested tables (DataService Templates). `LuaArray<T>` fields stay on the table
250
+ - `Class::` methods emit `function Class:Method(...)`
251
+ - Construct **one** service table in `init()` (`LeaderstatsServer leaderstatsServer;`) and capture it in lambdas. That is the game singleton
252
+ - Library singletons already exist: `DataService::Server` / `Client` — `Init` once from a boot script
253
+ - Header-only field structs emit `const function Name()` returning defaults
254
+ - Untagged files of only `Class::` methods `return` the table (ModuleScript)
198
255
 
199
- Full table and IntelliSense notes: [print and cout](print-cout.md).
256
+ Stem must match: `LeaderstatsServer.h` next to `LeaderstatsServer.server.cpp`. A differently named include is a `require`, not the class body.
200
257
 
201
- ## Not supported yet
258
+ ## Not supported
202
259
 
203
- Custom C++ classes, generic templates besides `GetService<T>`, pointer arithmetic, `std::`, overloading, macros (except `#pragma strict` / `#pragma nstrict` / `#pragma once`).
260
+ Custom C++ classes as metatables, generic templates besides `GetService<T>` / `static_cast<T>` / `LuaArray<T>` / `string_concat`, pointer arithmetic, `std::` (except mapped aliases), overloading, macros (except `#pragma strict` / `#pragma nstrict` / `#pragma once`), JSX / XML UI tags, C-style `for`, `++` `--` `+=`, `continue`, ternary, `do/while`, `try/catch`, `goto`, `int&` references.
204
261
 
205
- If you need one of those patterns, open an issue with the C++ and the expected Luau.
262
+ CLI forms: [CLI](cli.md). How to write a type: [OOP](oop/index.md). Full handbook: **[Docs](https://kartzrbx.github.io/Cluaupp/docs/)**.
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "recommendations": [
3
3
  "llvm-vs-code-extensions.vscode-clangd"
4
+ ],
5
+ "unwantedRecommendations": [
6
+ "ms-vscode.cpptools"
4
7
  ]
5
8
  }
@@ -1,13 +1,4 @@
1
1
  {
2
- "C_Cpp.intelliSenseEngine": "Disabled",
3
- "C_Cpp.default.cppStandard": "c++20",
4
- "C_Cpp.default.cStandard": "c17",
5
- "C_Cpp.default.compilerPath": "clang++",
6
- "C_Cpp.default.intelliSenseMode": "windows-clang-x64",
7
- "C_Cpp.default.includePath": [
8
- "${workspaceFolder}/include",
9
- "${workspaceFolder}/src"
10
- ],
11
2
  "clangd.enable": true,
12
3
  "clangd.arguments": [
13
4
  "--compile-commands-dir=${workspaceFolder}",
package/generated/api.js CHANGED
@@ -43,9 +43,23 @@ function luauType(name) {
43
43
  if (!name) {
44
44
  return null;
45
45
  }
46
- const cleaned = String(name).replace(/\*+$/, "").replace(/^const\s+/, "").replace(/^Enum::/, "Enum.");
46
+ const cleaned = String(name)
47
+ .replace(/\*+$/, "")
48
+ .replace(/^const\s+/, "")
49
+ .replace(/^Enum::/, "Enum.")
50
+ .replace(/\s+/g, " ")
51
+ .trim();
47
52
  if (cleaned in LUAU_TYPES) {
48
53
  return LUAU_TYPES[cleaned];
49
54
  }
55
+ const generic = cleaned.match(/^(?:std::)?(?:vector|array|span|LuaArray)\s*<\s*([^>]+)\s*>$/);
56
+ if (generic) {
57
+ const inner = luauType(generic[1]) || generic[1];
58
+ return `{${inner}}`;
59
+ }
60
+ const optional = cleaned.match(/^(?:std::)?optional\s*<\s*([^>]+)\s*>$/);
61
+ if (optional) {
62
+ return `${luauType(optional[1]) || optional[1]}?`;
63
+ }
50
64
  return cleaned;
51
65
  }
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,7 +3,6 @@ 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
6
  exports.clangPath = clangPath;
8
7
  exports.writeCompileCommands = writeCompileCommands;
9
8
  exports.writeClangd = writeClangd;
@@ -65,32 +64,6 @@ function compileCommandEntry(root, file) {
65
64
  arguments: [clangPath(), ...clangCompileArgs(root), "-c", absFile],
66
65
  };
67
66
  }
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
- }
94
67
  function copyDir(from, to) {
95
68
  node_fs_1.default.mkdirSync(to, { recursive: true });
96
69
  for (const entry of node_fs_1.default.readdirSync(from, { withFileTypes: true })) {
@@ -104,6 +77,55 @@ function copyDir(from, to) {
104
77
  }
105
78
  }
106
79
  }
80
+ function uniqueStrings(...lists) {
81
+ const seen = new Set();
82
+ const out = [];
83
+ for (const list of lists) {
84
+ if (!Array.isArray(list)) {
85
+ continue;
86
+ }
87
+ for (const item of list) {
88
+ if (typeof item === "string" && item && !seen.has(item)) {
89
+ seen.add(item);
90
+ out.push(item);
91
+ }
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+ function stripCppToolsSettings(current) {
97
+ const next = {};
98
+ for (const [key, value] of Object.entries(current)) {
99
+ if (key === "C_Cpp" || key.startsWith("C_Cpp.")) {
100
+ continue;
101
+ }
102
+ next[key] = value;
103
+ }
104
+ return next;
105
+ }
106
+ function readJsonObject(file) {
107
+ if (!node_fs_1.default.existsSync(file)) {
108
+ return {};
109
+ }
110
+ try {
111
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
112
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
113
+ return parsed;
114
+ }
115
+ }
116
+ catch {
117
+ // ignore corrupt editor json
118
+ }
119
+ return {};
120
+ }
121
+ function writeJson(file, value) {
122
+ const json = `${JSON.stringify(value, null, "\t")}\n`;
123
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
124
+ if (node_fs_1.default.existsSync(file) && node_fs_1.default.readFileSync(file, "utf8") === json) {
125
+ return;
126
+ }
127
+ node_fs_1.default.writeFileSync(file, json, "utf8");
128
+ }
107
129
  function clangdEditorArgs() {
108
130
  return [
109
131
  "--compile-commands-dir=${workspaceFolder}",
@@ -111,16 +133,6 @@ function clangdEditorArgs() {
111
133
  "--query-driver=**/clang++*,**/clang++.exe,**/g++*,**/g++.exe",
112
134
  ];
113
135
  }
114
- function intelliSenseMode() {
115
- const arch = process.arch === "arm64" ? "arm64" : "x64";
116
- if (process.platform === "win32") {
117
- return `windows-clang-${arch}`;
118
- }
119
- if (process.platform === "darwin") {
120
- return `macos-clang-${arch}`;
121
- }
122
- return `linux-clang-${arch}`;
123
- }
124
136
  function clangPath() {
125
137
  return (0, editor_install_js_1.findClangPlusPlus)() || "clang++";
126
138
  }
@@ -167,54 +179,37 @@ Index:
167
179
  return true;
168
180
  }
169
181
  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"],
182
+ const settingsPath = node_path_1.default.join(root, ".vscode", "settings.json");
183
+ const current = readJsonObject(settingsPath);
184
+ const associations = {
185
+ ...((current["files.associations"] && typeof current["files.associations"] === "object" && !Array.isArray(current["files.associations"]))
186
+ ? current["files.associations"]
187
+ : {}),
188
+ "*.hpp": "cpp",
189
+ "*.h": "cpp",
190
+ "*.server.cpp": "cpp",
191
+ "*.client.cpp": "cpp",
192
+ "*.plugin.cpp": "cpp",
193
+ "*.legacy.cpp": "cpp",
194
+ "*.legacy.server.cpp": "cpp",
195
+ "*.legacy.client.cpp": "cpp",
196
+ };
197
+ writeJson(settingsPath, {
198
+ ...stripCppToolsSettings(current),
178
199
  "clangd.enable": true,
179
200
  "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
- },
201
+ "files.associations": associations,
190
202
  });
191
- mergeJson(node_path_1.default.join(root, ".vscode", "extensions.json"), {
192
- recommendations: ["llvm-vs-code-extensions.vscode-clangd"],
203
+ const extensionsPath = node_path_1.default.join(root, ".vscode", "extensions.json");
204
+ const extensions = readJsonObject(extensionsPath);
205
+ writeJson(extensionsPath, {
206
+ ...extensions,
207
+ recommendations: uniqueStrings(extensions.recommendations, ["llvm-vs-code-extensions.vscode-clangd"]),
208
+ unwantedRecommendations: uniqueStrings(extensions.unwantedRecommendations, ["ms-vscode.cpptools"]),
193
209
  });
194
210
  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");
211
+ if (node_fs_1.default.existsSync(props)) {
212
+ node_fs_1.default.unlinkSync(props);
218
213
  }
219
214
  }
220
215
  function diagnosticsFor(source, fileName, options = {}) {
package/generated/lex.js CHANGED
@@ -33,6 +33,9 @@ const KEYWORDS = new Set([
33
33
  "case",
34
34
  "default",
35
35
  "break",
36
+ "static",
37
+ "constexpr",
38
+ "inline",
36
39
  ]);
37
40
  function tokenize(source) {
38
41
  const tokens = [];