cluaupp 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +6 -1
- package/bin/cluau.js +1 -1
- package/bin/cluaupp.js +1 -1
- package/docs/architecture.md +26 -1
- package/docs/cli.md +27 -6
- package/docs/examples/index.md +2 -0
- package/docs/getting-started.md +5 -4
- package/docs/intellisense.md +49 -31
- package/docs/intro.md +1 -1
- package/editors/vscode/extension.js +97 -146
- package/editors/vscode/package.json +6 -6
- package/examples/README.md +18 -0
- package/examples/game/.clangd +25 -0
- package/examples/game/.vscode/c_cpp_properties.json +27 -0
- package/examples/game/.vscode/extensions.json +5 -0
- package/examples/game/.vscode/settings.json +27 -0
- package/examples/game/Packages/.gitkeep +0 -0
- package/examples/game/cluaupp.config.json +6 -0
- package/examples/game/compile_flags.txt +6 -0
- package/examples/game/default.project.json +39 -0
- package/examples/game/package.json +9 -0
- package/examples/game/rokit.toml +5 -0
- package/examples/game/src/client/hud.client.cpp +12 -0
- package/{src → examples/game/src}/client/init.client.cpp +1 -0
- package/examples/game/src/server/combat.server.cpp +23 -0
- package/{src → examples/game/src}/server/leaderstats.server.cpp +1 -0
- package/examples/game/wally.toml +11 -0
- package/generated/api.js +51 -0
- package/generated/architecture.js +473 -0
- package/generated/ast.js +2 -0
- package/generated/cli.js +191 -0
- package/generated/compile.js +115 -0
- package/generated/editor-install.js +170 -0
- package/generated/emit.js +503 -0
- package/generated/emitter/luau-codegen.js +167 -0
- package/generated/emitter/translators.js +164 -0
- package/generated/headers.js +220 -0
- package/generated/index.html +49 -0
- package/generated/intellisense.js +277 -0
- package/generated/layout.js +112 -0
- package/generated/lex.js +146 -0
- package/generated/libs.js +484 -0
- package/generated/lsp.js +55 -0
- package/generated/package-info.js +11 -0
- package/generated/parse.js +607 -0
- package/generated/parser/collector.js +100 -0
- package/generated/parser/index.js +30 -0
- package/generated/preprocess.js +181 -0
- package/generated/system-understander.js +443 -0
- package/generated/transpile.js +66 -0
- package/generated/types.js +2 -0
- package/generated/understand.js +298 -0
- package/generated/utils/process-orchestrator.js +63 -0
- package/generated/utils/project.js +491 -0
- package/generated/utils/rojo-mapper.js +181 -0
- package/generated/utils/safe-paths.js +104 -0
- package/package.json +29 -8
- package/src/api.ts +53 -0
- package/src/{architecture.js → architecture.ts} +9 -10
- package/src/ast.ts +37 -0
- package/src/cli.ts +214 -0
- package/src/compile.ts +118 -0
- package/src/editor-install.ts +182 -0
- package/src/{emit.js → emit.ts} +4 -5
- package/src/emitter/luau-codegen.ts +187 -0
- package/src/emitter/translators.ts +168 -0
- package/src/{headers.js → headers.ts} +4 -5
- package/src/intellisense.ts +286 -0
- package/src/{layout.js → layout.ts} +114 -129
- package/src/{lex.js → lex.ts} +4 -6
- package/src/{libs.js → libs.ts} +9 -4
- package/src/lsp.ts +60 -0
- package/src/package-info.ts +7 -0
- package/src/{parse.js → parse.ts} +3 -4
- package/src/parser/collector.ts +115 -0
- package/src/parser/index.ts +27 -0
- package/src/{preprocess.js → preprocess.ts} +22 -37
- package/src/system-understander.ts +501 -0
- package/src/transpile.ts +64 -0
- package/src/tree-sitter-cpp.d.ts +4 -0
- package/src/types.ts +81 -0
- package/src/{understand.js → understand.ts} +19 -210
- package/src/utils/process-orchestrator.ts +72 -0
- package/src/utils/project.ts +506 -0
- package/src/utils/rojo-mapper.ts +190 -0
- package/src/utils/safe-paths.ts +98 -0
- package/templates/game/.clangd +9 -0
- package/templates/game/.vscode/c_cpp_properties.json +1 -1
- package/templates/game/.vscode/extensions.json +5 -6
- package/templates/game/.vscode/settings.json +7 -6
- package/templates/game/compile_flags.txt +1 -0
- package/templates/game/rokit.toml +5 -0
- package/src/api.js +0 -57
- package/src/cli.js +0 -473
- package/src/compile.js +0 -110
- package/src/editor-install.js +0 -218
- package/src/intellisense.js +0 -1368
- package/src/lsp.js +0 -227
- /package/{src → examples/game/src}/shared/config.cpp +0 -0
- /package/{src → examples/game/src}/shared/config.h +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type Parser from "tree-sitter";
|
|
2
|
+
import type { CollectorContext, TranspilerState } from "../types.js";
|
|
3
|
+
import type { RojoMapper } from "../utils/rojo-mapper.js";
|
|
4
|
+
import {
|
|
5
|
+
translateAlias,
|
|
6
|
+
translateConstant,
|
|
7
|
+
translateCppType,
|
|
8
|
+
translateGetService,
|
|
9
|
+
translateStruct,
|
|
10
|
+
} from "../emitter/translators.js";
|
|
11
|
+
|
|
12
|
+
type SyntaxNode = Parser.SyntaxNode;
|
|
13
|
+
|
|
14
|
+
function emptyState(strict = false): TranspilerState {
|
|
15
|
+
return {
|
|
16
|
+
robloxServices: new Set<string>(),
|
|
17
|
+
moduleRequires: new Map<string, string>(),
|
|
18
|
+
customTypes: [],
|
|
19
|
+
constants: [],
|
|
20
|
+
functions: [],
|
|
21
|
+
headerLines: [],
|
|
22
|
+
strict,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ASTCollector {
|
|
27
|
+
private state: TranspilerState;
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
private mapper: RojoMapper,
|
|
31
|
+
context: CollectorContext = { fileName: "input.cpp" },
|
|
32
|
+
) {
|
|
33
|
+
this.state = emptyState(context.strict === true);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public collect(rootNode: SyntaxNode): TranspilerState {
|
|
37
|
+
this.visit(rootNode, false);
|
|
38
|
+
return this.state;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private visit(node: SyntaxNode, inFunction: boolean): void {
|
|
42
|
+
if (node.type === "preproc_include") {
|
|
43
|
+
this.collectInclude(node);
|
|
44
|
+
} else if (node.type === "call_expression") {
|
|
45
|
+
this.collectGetService(node);
|
|
46
|
+
} else if (!inFunction && node.type === "function_definition") {
|
|
47
|
+
this.visitChildren(node, true);
|
|
48
|
+
return;
|
|
49
|
+
} else if (!inFunction && node.type === "declaration") {
|
|
50
|
+
this.collectDeclaration(node);
|
|
51
|
+
} else if (!inFunction && (node.type === "type_definition" || node.type === "alias_declaration")) {
|
|
52
|
+
this.collectAlias(node);
|
|
53
|
+
} else if (!inFunction && (node.type === "struct_specifier" || node.type === "class_specifier")) {
|
|
54
|
+
this.collectStruct(node);
|
|
55
|
+
} else if (!inFunction && node.type === "preproc_call" && /pragma\s+strict/.test(node.text)) {
|
|
56
|
+
this.state.strict = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.visitChildren(node, inFunction);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private visitChildren(node: SyntaxNode, inFunction: boolean): void {
|
|
63
|
+
for (let i = 0; i < node.childCount; i += 1) {
|
|
64
|
+
const child = node.child(i);
|
|
65
|
+
if (child) {
|
|
66
|
+
this.visit(child, inFunction);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private collectInclude(node: SyntaxNode): void {
|
|
72
|
+
const pathNode = node.childForFieldName("path") || node.namedChildren[0] || node.child(1);
|
|
73
|
+
if (!pathNode) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const resolved = this.mapper.resolveIncludeToRequire(pathNode.text);
|
|
77
|
+
if (resolved) {
|
|
78
|
+
this.state.moduleRequires.set(resolved.alias, resolved.path);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private collectGetService(node: SyntaxNode): void {
|
|
83
|
+
const service = translateGetService(node);
|
|
84
|
+
if (service) {
|
|
85
|
+
this.state.robloxServices.add(service);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private collectDeclaration(node: SyntaxNode): void {
|
|
90
|
+
const text = node.text.trimStart();
|
|
91
|
+
if (!(text.startsWith("const") || text.startsWith("constexpr"))) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const translated = translateConstant(node);
|
|
95
|
+
if (translated) {
|
|
96
|
+
this.state.constants.push(translated);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private collectAlias(node: SyntaxNode): void {
|
|
101
|
+
const translated = translateAlias(node, translateCppType);
|
|
102
|
+
if (translated) {
|
|
103
|
+
this.state.customTypes.push(translated);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private collectStruct(node: SyntaxNode): void {
|
|
108
|
+
const translated = translateStruct(node, translateCppType);
|
|
109
|
+
if (translated) {
|
|
110
|
+
this.state.customTypes.push(translated);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { emptyState };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Parser from "tree-sitter";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { projectRoot } from "../package-info.js";
|
|
5
|
+
|
|
6
|
+
const nodeRequire = createRequire(path.join(projectRoot, "package.json"));
|
|
7
|
+
|
|
8
|
+
export type CppParser = Parser;
|
|
9
|
+
|
|
10
|
+
export function createCppParserSync(): CppParser | null {
|
|
11
|
+
try {
|
|
12
|
+
const Cpp = nodeRequire("tree-sitter-cpp");
|
|
13
|
+
const parser = new Parser();
|
|
14
|
+
parser.setLanguage(Cpp);
|
|
15
|
+
parser.parse("int main() { return 0; }");
|
|
16
|
+
return parser;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function parseCpp(source: string, parser = createCppParserSync()): Parser.Tree | null {
|
|
23
|
+
if (!parser) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return parser.parse(source);
|
|
27
|
+
}
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { ModuleInclude, PreprocessOptions } from "./ast.js";
|
|
2
4
|
|
|
3
|
-
const
|
|
4
|
-
const path = require("path");
|
|
5
|
+
export const SOURCE_EXTS = [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hh"];
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
function isSourceFile(fileName) {
|
|
7
|
+
export function isSourceFile(fileName: string): boolean {
|
|
9
8
|
return SOURCE_EXTS.includes(path.extname(fileName).toLowerCase());
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
function isHeaderFile(fileName) {
|
|
11
|
+
export function isHeaderFile(fileName: string): boolean {
|
|
13
12
|
return [".h", ".hpp", ".hh"].includes(path.extname(fileName).toLowerCase());
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
function toLuauPath(filePath) {
|
|
15
|
+
export function toLuauPath(filePath: string): string {
|
|
17
16
|
return filePath.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau");
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
function isEngineStub(filePath) {
|
|
19
|
+
export function isEngineStub(filePath: string): boolean {
|
|
21
20
|
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
22
21
|
return (
|
|
23
22
|
normalized.includes("/include/cluaupp/") ||
|
|
@@ -27,7 +26,7 @@ function isEngineStub(filePath) {
|
|
|
27
26
|
);
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
function includeVariants(name) {
|
|
29
|
+
function includeVariants(name: string): string[] {
|
|
31
30
|
const variants = [name];
|
|
32
31
|
if (/\.h$/i.test(name)) {
|
|
33
32
|
variants.push(name.replace(/\.h$/i, ".hpp"), name.replace(/\.h$/i, ".hh"));
|
|
@@ -39,7 +38,7 @@ function includeVariants(name) {
|
|
|
39
38
|
return [...new Set(variants)];
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
function siblingImplementation(headerPath) {
|
|
41
|
+
export function siblingImplementation(headerPath: string | null | undefined): string | null {
|
|
43
42
|
if (!headerPath || !isHeaderFile(headerPath)) {
|
|
44
43
|
return null;
|
|
45
44
|
}
|
|
@@ -52,7 +51,7 @@ function siblingImplementation(headerPath) {
|
|
|
52
51
|
return null;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
function siblingHeader(implPath) {
|
|
54
|
+
export function siblingHeader(implPath: string | null | undefined): string | null {
|
|
56
55
|
if (!implPath || isHeaderFile(implPath)) {
|
|
57
56
|
return null;
|
|
58
57
|
}
|
|
@@ -68,13 +67,13 @@ function siblingHeader(implPath) {
|
|
|
68
67
|
return null;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
function implOutName(rel) {
|
|
70
|
+
export function implOutName(rel: string): string {
|
|
72
71
|
return String(rel)
|
|
73
72
|
.replace(/\\/g, "/")
|
|
74
73
|
.replace(/\.(cpp|cc|cxx|c)$/i, "Impl.luau");
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
function resolveInclude(name, fromFile, includeDirs) {
|
|
76
|
+
export function resolveInclude(name: string, fromFile: string, includeDirs?: string[]): string | null {
|
|
78
77
|
const bases = [path.dirname(fromFile), ...(includeDirs || [])];
|
|
79
78
|
for (const base of bases) {
|
|
80
79
|
for (const variant of includeVariants(name)) {
|
|
@@ -87,9 +86,9 @@ function resolveInclude(name, fromFile, includeDirs) {
|
|
|
87
86
|
return null;
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
function scanHeaderExports(source) {
|
|
91
|
-
const consts = [];
|
|
92
|
-
const structs = [];
|
|
89
|
+
export function scanHeaderExports(source: string): { consts: string[]; structs: string[]; hasProtos: boolean } {
|
|
90
|
+
const consts: string[] = [];
|
|
91
|
+
const structs: string[] = [];
|
|
93
92
|
for (const match of String(source).matchAll(/\bconst\s+(?:int|bool|float|double|auto|string)\s+(\w+)/g)) {
|
|
94
93
|
if (!consts.includes(match[1])) {
|
|
95
94
|
consts.push(match[1]);
|
|
@@ -104,7 +103,7 @@ function scanHeaderExports(source) {
|
|
|
104
103
|
return { consts, structs, hasProtos };
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
function pushModuleInclude(options, resolved, impl) {
|
|
106
|
+
function pushModuleInclude(options: PreprocessOptions, resolved: string, impl: string | null): void {
|
|
108
107
|
options.moduleIncludes = options.moduleIncludes || [];
|
|
109
108
|
const srcDir = options.srcDir || null;
|
|
110
109
|
const moduleFile = impl || resolved;
|
|
@@ -119,7 +118,7 @@ function pushModuleInclude(options, resolved, impl) {
|
|
|
119
118
|
});
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
function applyPragmas(source, options) {
|
|
121
|
+
export function applyPragmas(source: string, options: PreprocessOptions): void {
|
|
123
122
|
let nstrict = false;
|
|
124
123
|
let strict = false;
|
|
125
124
|
for (const line of String(source).split(/\r?\n/)) {
|
|
@@ -136,12 +135,13 @@ function applyPragmas(source, options) {
|
|
|
136
135
|
}
|
|
137
136
|
}
|
|
138
137
|
|
|
139
|
-
function preprocess(source, filePath, options = {}) {
|
|
138
|
+
export function preprocess(source: string, filePath: string | null, options: PreprocessOptions = {}): string {
|
|
140
139
|
if (!options.pragmaResolved) {
|
|
141
140
|
options.pragmaResolved = true;
|
|
142
141
|
applyPragmas(source, options);
|
|
143
142
|
}
|
|
144
|
-
const seen = options.seen || new Set();
|
|
143
|
+
const seen = options.seen || new Set<string>();
|
|
144
|
+
options.seen = seen;
|
|
145
145
|
const includeDirs = options.includeDirs || [];
|
|
146
146
|
const resolvedSelf = filePath ? path.resolve(filePath) : null;
|
|
147
147
|
if (resolvedSelf) {
|
|
@@ -149,7 +149,7 @@ function preprocess(source, filePath, options = {}) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
const lines = String(source).split(/\r?\n/);
|
|
152
|
-
const out = [];
|
|
152
|
+
const out: string[] = [];
|
|
153
153
|
|
|
154
154
|
for (const line of lines) {
|
|
155
155
|
const quoted = line.match(/^\s*#\s*include\s+"([^"]+)"/);
|
|
@@ -178,18 +178,3 @@ function preprocess(source, filePath, options = {}) {
|
|
|
178
178
|
|
|
179
179
|
return out.join("\n");
|
|
180
180
|
}
|
|
181
|
-
|
|
182
|
-
module.exports = {
|
|
183
|
-
SOURCE_EXTS,
|
|
184
|
-
isSourceFile,
|
|
185
|
-
isHeaderFile,
|
|
186
|
-
toLuauPath,
|
|
187
|
-
applyPragmas,
|
|
188
|
-
preprocess,
|
|
189
|
-
scanHeaderExports,
|
|
190
|
-
isEngineStub,
|
|
191
|
-
siblingImplementation,
|
|
192
|
-
siblingHeader,
|
|
193
|
-
implOutName,
|
|
194
|
-
resolveInclude,
|
|
195
|
-
};
|