cluaupp 0.1.4 → 0.2.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.
- package/CHANGELOG.md +21 -0
- package/README.md +12 -7
- package/bin/cluau.js +1 -1
- package/bin/cluaupp.js +1 -1
- package/docs/README.md +1 -1
- package/docs/architecture.md +52 -77
- package/docs/cli.md +29 -9
- package/docs/comparison.md +3 -3
- package/docs/config.md +6 -6
- package/docs/cpp-organization.md +2 -2
- package/docs/examples/index.md +2 -0
- package/docs/getting-started.md +7 -5
- package/docs/intellisense.md +49 -31
- package/docs/intro.md +2 -2
- package/docs/oop/file-tags.md +16 -17
- package/docs/oop/index.md +2 -2
- package/docs/oop/modules.md +3 -1
- package/docs/oop/services.md +7 -14
- package/docs/syntax.md +6 -5
- 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 +25 -8
- package/src/api.ts +53 -0
- package/src/{architecture.js → architecture.ts} +80 -33
- 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} +5 -6
- package/src/emitter/luau-codegen.ts +187 -0
- package/src/emitter/translators.ts +168 -0
- package/src/headers.ts +233 -0
- 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} +119 -46
- 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} +64 -32
- 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} +24 -213
- 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/cluaupp.config.json +2 -2
- package/templates/game/compile_flags.txt +1 -0
- package/templates/game/rokit.toml +5 -0
- package/templates/game/src/client/init.client.cpp +1 -0
- package/templates/game/src/server/leaderstats.server.cpp +1 -0
- package/src/api.js +0 -57
- package/src/cli.js +0 -481
- package/src/compile.js +0 -56
- 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,29 @@ function siblingImplementation(headerPath) {
|
|
|
52
51
|
return null;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
function
|
|
54
|
+
export function siblingHeader(implPath: string | null | undefined): string | null {
|
|
55
|
+
if (!implPath || isHeaderFile(implPath)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const base = String(implPath).replace(/\.(cpp|cc|cxx|c)$/i, "");
|
|
59
|
+
if (base === String(implPath)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
for (const ext of [".h", ".hpp", ".hh"]) {
|
|
63
|
+
if (fs.existsSync(base + ext)) {
|
|
64
|
+
return base + ext;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function implOutName(rel: string): string {
|
|
71
|
+
return String(rel)
|
|
72
|
+
.replace(/\\/g, "/")
|
|
73
|
+
.replace(/\.(cpp|cc|cxx|c)$/i, "Impl.luau");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function resolveInclude(name: string, fromFile: string, includeDirs?: string[]): string | null {
|
|
56
77
|
const bases = [path.dirname(fromFile), ...(includeDirs || [])];
|
|
57
78
|
for (const base of bases) {
|
|
58
79
|
for (const variant of includeVariants(name)) {
|
|
@@ -65,9 +86,9 @@ function resolveInclude(name, fromFile, includeDirs) {
|
|
|
65
86
|
return null;
|
|
66
87
|
}
|
|
67
88
|
|
|
68
|
-
function scanHeaderExports(source) {
|
|
69
|
-
const consts = [];
|
|
70
|
-
const structs = [];
|
|
89
|
+
export function scanHeaderExports(source: string): { consts: string[]; structs: string[]; hasProtos: boolean } {
|
|
90
|
+
const consts: string[] = [];
|
|
91
|
+
const structs: string[] = [];
|
|
71
92
|
for (const match of String(source).matchAll(/\bconst\s+(?:int|bool|float|double|auto|string)\s+(\w+)/g)) {
|
|
72
93
|
if (!consts.includes(match[1])) {
|
|
73
94
|
consts.push(match[1]);
|
|
@@ -78,10 +99,11 @@ function scanHeaderExports(source) {
|
|
|
78
99
|
structs.push(match[1]);
|
|
79
100
|
}
|
|
80
101
|
}
|
|
81
|
-
|
|
102
|
+
const hasProtos = /\b(?:void|int|bool|float|double|auto|string|[\w:]+)\s+\w+\s*\([^;]*\)\s*;/.test(String(source));
|
|
103
|
+
return { consts, structs, hasProtos };
|
|
82
104
|
}
|
|
83
105
|
|
|
84
|
-
function pushModuleInclude(options, resolved, impl) {
|
|
106
|
+
function pushModuleInclude(options: PreprocessOptions, resolved: string, impl: string | null): void {
|
|
85
107
|
options.moduleIncludes = options.moduleIncludes || [];
|
|
86
108
|
const srcDir = options.srcDir || null;
|
|
87
109
|
const moduleFile = impl || resolved;
|
|
@@ -96,8 +118,30 @@ function pushModuleInclude(options, resolved, impl) {
|
|
|
96
118
|
});
|
|
97
119
|
}
|
|
98
120
|
|
|
99
|
-
function
|
|
100
|
-
|
|
121
|
+
export function applyPragmas(source: string, options: PreprocessOptions): void {
|
|
122
|
+
let nstrict = false;
|
|
123
|
+
let strict = false;
|
|
124
|
+
for (const line of String(source).split(/\r?\n/)) {
|
|
125
|
+
if (/^\s*#\s*pragma\s+nstrict\b/i.test(line)) {
|
|
126
|
+
nstrict = true;
|
|
127
|
+
} else if (/^\s*#\s*pragma\s+strict\b/i.test(line)) {
|
|
128
|
+
strict = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (nstrict) {
|
|
132
|
+
options.strict = false;
|
|
133
|
+
} else if (strict) {
|
|
134
|
+
options.strict = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function preprocess(source: string, filePath: string | null, options: PreprocessOptions = {}): string {
|
|
139
|
+
if (!options.pragmaResolved) {
|
|
140
|
+
options.pragmaResolved = true;
|
|
141
|
+
applyPragmas(source, options);
|
|
142
|
+
}
|
|
143
|
+
const seen = options.seen || new Set<string>();
|
|
144
|
+
options.seen = seen;
|
|
101
145
|
const includeDirs = options.includeDirs || [];
|
|
102
146
|
const resolvedSelf = filePath ? path.resolve(filePath) : null;
|
|
103
147
|
if (resolvedSelf) {
|
|
@@ -105,7 +149,7 @@ function preprocess(source, filePath, options = {}) {
|
|
|
105
149
|
}
|
|
106
150
|
|
|
107
151
|
const lines = String(source).split(/\r?\n/);
|
|
108
|
-
const out = [];
|
|
152
|
+
const out: string[] = [];
|
|
109
153
|
|
|
110
154
|
for (const line of lines) {
|
|
111
155
|
const quoted = line.match(/^\s*#\s*include\s+"([^"]+)"/);
|
|
@@ -134,15 +178,3 @@ function preprocess(source, filePath, options = {}) {
|
|
|
134
178
|
|
|
135
179
|
return out.join("\n");
|
|
136
180
|
}
|
|
137
|
-
|
|
138
|
-
module.exports = {
|
|
139
|
-
SOURCE_EXTS,
|
|
140
|
-
isSourceFile,
|
|
141
|
-
isHeaderFile,
|
|
142
|
-
toLuauPath,
|
|
143
|
-
preprocess,
|
|
144
|
-
scanHeaderExports,
|
|
145
|
-
isEngineStub,
|
|
146
|
-
siblingImplementation,
|
|
147
|
-
resolveInclude,
|
|
148
|
-
};
|