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/src/parse.ts CHANGED
@@ -48,21 +48,128 @@ function parse(source, fileName) {
48
48
  return name;
49
49
  };
50
50
 
51
+ const CAST_NAMES = new Set(["static_cast", "const_cast", "reinterpret_cast", "dynamic_cast"]);
52
+
53
+ function skipSpecifiers() {
54
+ let isConst = false;
55
+ while (at("kw", "static") || at("kw", "inline") || at("kw", "constexpr") || at("kw", "const")) {
56
+ if (at("kw", "constexpr") || at("kw", "const")) {
57
+ isConst = true;
58
+ }
59
+ i += 1;
60
+ }
61
+ return isConst;
62
+ }
63
+
64
+ function looksLikePrimaryStart() {
65
+ if (at("ident") || at("number") || at("string")) {
66
+ return true;
67
+ }
68
+ if (at("kw", "true") || at("kw", "false") || at("kw", "nullptr") || at("kw", "new")) {
69
+ return true;
70
+ }
71
+ return at("op", "(") || at("op", "{") || at("op", "[") || at("op", "-") || at("op", "!");
72
+ }
73
+
74
+ function parseLambda() {
75
+ eat("op", "[");
76
+ while (!at("eof") && !at("op", "]")) {
77
+ i += 1;
78
+ }
79
+ eat("op", "]");
80
+ const params = [];
81
+ if (at("op", "(")) {
82
+ i += 1;
83
+ if (!at("op", ")")) {
84
+ params.push(parseParam());
85
+ while (at("op", ",")) {
86
+ i += 1;
87
+ params.push(parseParam());
88
+ }
89
+ }
90
+ eat("op", ")");
91
+ }
92
+ const body = parseBlock();
93
+ return { type: "lambda", params, body };
94
+ }
95
+
96
+ function parseNamedCast() {
97
+ i += 1;
98
+ eat("op", "<");
99
+ const valueType = parseType() || "any";
100
+ eat("op", ">");
101
+ const args = parseArgs();
102
+ return { type: "cast", valueType, argument: args[0] || { type: "null" } };
103
+ }
104
+
105
+ function parseParenPrimary() {
106
+ eat("op", "(");
107
+ const saved = i;
108
+ skipSpecifiers();
109
+ const valueType = parseType();
110
+ if (valueType && at("op", ")")) {
111
+ i += 1;
112
+ if (looksLikePrimaryStart()) {
113
+ return { type: "cast", valueType, argument: parseUnary() };
114
+ }
115
+ }
116
+ i = saved;
117
+ const expr = parseExpr();
118
+ eat("op", ")");
119
+ return expr;
120
+ }
121
+
122
+ function eatGeneric() {
123
+ let text = "";
124
+ let depth = 0;
125
+ while (!at("eof")) {
126
+ const token = peek();
127
+ if (at("op", "<")) {
128
+ depth += 1;
129
+ text += "<";
130
+ i += 1;
131
+ continue;
132
+ }
133
+ if (at("op", ">")) {
134
+ depth -= 1;
135
+ text += ">";
136
+ i += 1;
137
+ if (depth === 0) {
138
+ return text;
139
+ }
140
+ continue;
141
+ }
142
+ if (token.type === "string") {
143
+ text += `"${token.value}"`;
144
+ } else {
145
+ text += token.value;
146
+ }
147
+ i += 1;
148
+ }
149
+ return text;
150
+ }
151
+
51
152
  function parseType() {
52
153
  if (at("kw", "const")) {
53
154
  i += 1;
54
155
  }
156
+ let name = null;
55
157
  if (at("kw", "auto") || at("kw", "void") || at("kw", "int") || at("kw", "bool") || at("kw", "float") || at("kw", "double")) {
56
- const name = eat("kw").value;
57
- while (at("op", "*")) {
158
+ name = eat("kw").value;
159
+ } else if (at("ident")) {
160
+ name = eat("ident").value;
161
+ while (at("op", "::")) {
58
162
  i += 1;
163
+ if (at("ident")) {
164
+ name += `::${eat("ident").value}`;
165
+ }
59
166
  }
60
- return name;
61
- }
62
- if (!at("ident")) {
167
+ } else {
63
168
  return null;
64
169
  }
65
- const name = eat("ident").value;
170
+ if (at("op", "<")) {
171
+ name += eatGeneric();
172
+ }
66
173
  while (at("op", "*")) {
67
174
  i += 1;
68
175
  }
@@ -132,14 +239,17 @@ function parse(source, fileName) {
132
239
  eat("op", ")");
133
240
  return { type: "getService", service };
134
241
  }
242
+ if (at("ident") && CAST_NAMES.has(peek().value) && peek(1).value === "<") {
243
+ return parseNamedCast();
244
+ }
135
245
  if (at("ident")) {
136
246
  return { type: "ident", name: eat("ident").value };
137
247
  }
248
+ if (at("op", "[")) {
249
+ return parseLambda();
250
+ }
138
251
  if (at("op", "(")) {
139
- i += 1;
140
- const expr = parseExpr();
141
- eat("op", ")");
142
- return expr;
252
+ return parseParenPrimary();
143
253
  }
144
254
  if (at("op", "{")) {
145
255
  return parseInitList();
@@ -320,11 +430,8 @@ function parse(source, fileName) {
320
430
  }
321
431
 
322
432
  function parseDeclOrExpr() {
323
- const isConst = at("kw", "const");
324
- if (isConst) {
325
- i += 1;
326
- }
327
433
  const saved = i;
434
+ const isConst = skipSpecifiers();
328
435
  const maybeType = parseType();
329
436
  if (maybeType && at("ident") && (peek(1).value === "=" || peek(1).value === ";")) {
330
437
  const name = eat("ident").value;
@@ -336,9 +443,6 @@ function parse(source, fileName) {
336
443
  return { type: "decl", name, valueType: maybeType, value, isConst: isConst || maybeType === "const" };
337
444
  }
338
445
  i = saved;
339
- if (isConst) {
340
- i = saved - 1;
341
- }
342
446
  return { type: "expr", expr: parseExpr() };
343
447
  }
344
448
 
@@ -395,6 +499,7 @@ function parse(source, fileName) {
395
499
  }
396
500
 
397
501
  function parseFunction() {
502
+ skipSpecifiers();
398
503
  const returnType = parseType();
399
504
  if (!returnType || !at("ident")) {
400
505
  throw error("invalid function declaration");
@@ -465,10 +570,7 @@ function parse(source, fileName) {
465
570
  continue;
466
571
  }
467
572
  const saved = i;
468
- const isConst = at("kw", "const");
469
- if (isConst) {
470
- i += 1;
471
- }
573
+ const isConst = skipSpecifiers();
472
574
  const valueType = parseType();
473
575
  if (valueType && at("ident")) {
474
576
  if (peek(1).value === "(") {
@@ -593,10 +695,7 @@ function parse(source, fileName) {
593
695
  continue;
594
696
  }
595
697
  const saved = i;
596
- const isConst = at("kw", "const");
597
- if (isConst) {
598
- i += 1;
599
- }
698
+ const isConst = skipSpecifiers();
600
699
  const maybeType = parseType();
601
700
  if (maybeType && at("ident")) {
602
701
  const next = peek(1);
@@ -1,6 +1,8 @@
1
+ import path from "node:path";
1
2
  import type Parser from "tree-sitter";
2
3
  import type { CollectorContext, TranspilerState } from "../types.js";
3
4
  import type { RojoMapper } from "../utils/rojo-mapper.js";
5
+ import { siblingHeader } from "../preprocess.js";
4
6
  import {
5
7
  translateAlias,
6
8
  translateConstant,
@@ -28,7 +30,7 @@ export class ASTCollector {
28
30
 
29
31
  constructor(
30
32
  private mapper: RojoMapper,
31
- context: CollectorContext = { fileName: "input.cpp" },
33
+ private context: CollectorContext = { fileName: "input.cpp" },
32
34
  ) {
33
35
  this.state = emptyState(context.strict === true);
34
36
  }
@@ -68,12 +70,43 @@ export class ASTCollector {
68
70
  }
69
71
  }
70
72
 
73
+ private includeTarget(raw: string): string | null {
74
+ const from = this.context.sourcePath;
75
+ if (!from) {
76
+ return null;
77
+ }
78
+ const cleaned = raw.replace(/[<>'"]/g, "").trim();
79
+ if (!cleaned || raw.includes("<")) {
80
+ return null;
81
+ }
82
+ return path.resolve(path.dirname(from), cleaned);
83
+ }
84
+
85
+ private isOwnHeader(raw: string): boolean {
86
+ const from = this.context.sourcePath;
87
+ if (!from) {
88
+ return false;
89
+ }
90
+ const target = this.includeTarget(raw);
91
+ if (!target) {
92
+ return false;
93
+ }
94
+ if (path.resolve(from) === target) {
95
+ return true;
96
+ }
97
+ const header = siblingHeader(from);
98
+ return Boolean(header && path.resolve(header) === target);
99
+ }
100
+
71
101
  private collectInclude(node: SyntaxNode): void {
72
102
  const pathNode = node.childForFieldName("path") || node.namedChildren[0] || node.child(1);
73
103
  if (!pathNode) {
74
104
  return;
75
105
  }
76
- const resolved = this.mapper.resolveIncludeToRequire(pathNode.text);
106
+ if (this.isOwnHeader(pathNode.text)) {
107
+ return;
108
+ }
109
+ const resolved = this.mapper.resolveIncludeToRequire(pathNode.text, this.context.sourcePath);
77
110
  if (resolved) {
78
111
  this.state.moduleRequires.set(resolved.alias, resolved.path);
79
112
  }
@@ -64,7 +64,24 @@ export class RojoMapper {
64
64
  this.bindings = this.walkTree(this.config.tree, []);
65
65
  }
66
66
 
67
- public resolveIncludeToRequire(includePath: string): RequireBinding | null {
67
+ private resolveQuotedFile(raw: string, fromFile?: string): string | null {
68
+ const bases: string[] = [];
69
+ if (fromFile) {
70
+ bases.push(path.dirname(fromFile));
71
+ }
72
+ if (this.srcDir) {
73
+ bases.push(this.srcDir);
74
+ }
75
+ for (const base of bases) {
76
+ const candidate = path.resolve(base, raw);
77
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
78
+ return candidate;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+
84
+ public resolveIncludeToRequire(includePath: string, fromFile?: string): RequireBinding | null {
68
85
  const raw = includePath.replace(/[<>'"]/g, "").trim();
69
86
  if (!raw) {
70
87
  return null;
@@ -82,12 +99,18 @@ export class RojoMapper {
82
99
  return library;
83
100
  }
84
101
 
85
- const fromTree = this.resolveFromRojoTree(cleanPath, baseName);
102
+ const resolvedFile = this.resolveQuotedFile(raw, fromFile);
103
+ const lookupPath =
104
+ resolvedFile && this.srcDir
105
+ ? posix(path.relative(this.srcDir, resolvedFile)).replace(/\.h(pp|h)?$/i, "")
106
+ : cleanPath;
107
+
108
+ const fromTree = this.resolveFromRojoTree(lookupPath, baseName);
86
109
  if (fromTree) {
87
110
  return fromTree;
88
111
  }
89
112
 
90
- const segments = cleanPath.split("/").filter(Boolean);
113
+ const segments = lookupPath.split("/").filter(Boolean);
91
114
  if (segments[0] === "src") {
92
115
  segments.shift();
93
116
  }
@@ -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}",
@@ -1,3 +1,5 @@
1
1
  #include <cluaupp/roblox.hpp>
2
2
 
3
3
  const int STARTING_COINS = 0;
4
+
5
+ int startingCoins();
@@ -1,27 +0,0 @@
1
- {
2
- "configurations": [
3
- {
4
- "name": "Cluaupp",
5
- "compilerPath": "clang++",
6
- "cStandard": "c17",
7
- "cppStandard": "c++20",
8
- "intelliSenseMode": "windows-clang-x64",
9
- "includePath": [
10
- "${workspaceFolder}/include",
11
- "${workspaceFolder}/src"
12
- ],
13
- "forcedInclude": [
14
- "${workspaceFolder}/include/cluaupp/roblox.hpp"
15
- ],
16
- "compileCommands": "${workspaceFolder}/compile_commands.json",
17
- "browse": {
18
- "path": [
19
- "${workspaceFolder}/include",
20
- "${workspaceFolder}/src"
21
- ],
22
- "limitSymbolsToIncludedHeaders": true
23
- }
24
- }
25
- ],
26
- "version": 4
27
- }
@@ -1,27 +0,0 @@
1
- {
2
- "configurations": [
3
- {
4
- "name": "Cluaupp",
5
- "compilerPath": "clang++",
6
- "cStandard": "c17",
7
- "cppStandard": "c++20",
8
- "intelliSenseMode": "windows-clang-x64",
9
- "includePath": [
10
- "${workspaceFolder}/include",
11
- "${workspaceFolder}/src"
12
- ],
13
- "forcedInclude": [
14
- "${workspaceFolder}/include/cluaupp/roblox.hpp"
15
- ],
16
- "compileCommands": "${workspaceFolder}/compile_commands.json",
17
- "browse": {
18
- "path": [
19
- "${workspaceFolder}/include",
20
- "${workspaceFolder}/src"
21
- ],
22
- "limitSymbolsToIncludedHeaders": true
23
- }
24
- }
25
- ],
26
- "version": 4
27
- }