cluaupp 0.1.5 → 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 +12 -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 +25 -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,182 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
export const CLANGD_ID = "llvm-vs-code-extensions.vscode-clangd";
|
|
7
|
+
|
|
8
|
+
export type InstallResult = {
|
|
9
|
+
status: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
how?: string;
|
|
13
|
+
detail?: string;
|
|
14
|
+
hint?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function which(command: string): string | null {
|
|
18
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
19
|
+
const result = spawnSync(finder, [command], { encoding: "utf8", windowsHide: true });
|
|
20
|
+
if (result.status !== 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return String(result.stdout || "")
|
|
24
|
+
.split(/\r?\n/)
|
|
25
|
+
.map((line) => line.trim())
|
|
26
|
+
.find((line) => line && !line.toLowerCase().includes("info:")) || null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function editorBin(): string | null {
|
|
30
|
+
const found = which("cursor") || which("code");
|
|
31
|
+
if (found) {
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
const local = process.env.LOCALAPPDATA || "";
|
|
35
|
+
const fallbacks = [
|
|
36
|
+
path.join(local, "Programs", "cursor", "resources", "app", "bin", "cursor.cmd"),
|
|
37
|
+
path.join(local, "Programs", "Microsoft VS Code", "bin", "code.cmd"),
|
|
38
|
+
"/usr/bin/cursor",
|
|
39
|
+
"/usr/local/bin/cursor",
|
|
40
|
+
"/usr/bin/code",
|
|
41
|
+
"/usr/local/bin/code",
|
|
42
|
+
];
|
|
43
|
+
return fallbacks.find((file) => fs.existsSync(file)) || null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extensionHomes(): string[] {
|
|
47
|
+
return [path.join(os.homedir(), ".cursor", "extensions"), path.join(os.homedir(), ".vscode", "extensions")];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isExtensionInstalled(id: string): boolean {
|
|
51
|
+
for (const home of extensionHomes()) {
|
|
52
|
+
if (!fs.existsSync(home)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
if (fs.readdirSync(home).some((name) => name.startsWith(`${id}-`))) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// ignore
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const bin = editorBin();
|
|
64
|
+
if (!bin) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const listed = spawnSync(bin, ["--list-extensions"], { encoding: "utf8", windowsHide: true, timeout: 20000 });
|
|
68
|
+
return String(listed.stdout || "").split(/\r?\n/).includes(id);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function llvmCandidates(): string[] {
|
|
72
|
+
if (process.platform === "win32") {
|
|
73
|
+
return [
|
|
74
|
+
"C:/Program Files/LLVM/bin/clang++.exe",
|
|
75
|
+
"C:/Program Files (x86)/LLVM/bin/clang++.exe",
|
|
76
|
+
path.join(process.env.ProgramFiles || "C:/Program Files", "LLVM", "bin", "clang++.exe"),
|
|
77
|
+
"C:/msys64/ucrt64/bin/clang++.exe",
|
|
78
|
+
"C:/msys64/mingw64/bin/clang++.exe",
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
return ["/usr/bin/clang++", "/usr/local/bin/clang++", "/opt/homebrew/bin/clang++"];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function findClangPlusPlus(): string | null {
|
|
85
|
+
for (const candidate of llvmCandidates()) {
|
|
86
|
+
if (candidate && fs.existsSync(candidate)) {
|
|
87
|
+
return candidate.replace(/\\/g, "/");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return which("clang++");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function ensureLlvm(): InstallResult {
|
|
94
|
+
const found = findClangPlusPlus();
|
|
95
|
+
if (found) {
|
|
96
|
+
return { status: "already", path: found };
|
|
97
|
+
}
|
|
98
|
+
if (process.platform === "win32" && which("winget")) {
|
|
99
|
+
console.log("cluaupp: installing LLVM.LLVM (clang++ / clangd) via winget");
|
|
100
|
+
const result = spawnSync(
|
|
101
|
+
"winget",
|
|
102
|
+
["install", "--id", "LLVM.LLVM", "-e", "--accept-package-agreements", "--accept-source-agreements"],
|
|
103
|
+
{ encoding: "utf8", windowsHide: true, timeout: 300000 },
|
|
104
|
+
);
|
|
105
|
+
const after = findClangPlusPlus();
|
|
106
|
+
if (after) {
|
|
107
|
+
return { status: "installed", path: after, how: "winget" };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
status: "missing",
|
|
111
|
+
detail: String(result.stderr || result.stdout || "").trim() || "winget did not install LLVM",
|
|
112
|
+
hint: "winget install --id LLVM.LLVM -e",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const hint =
|
|
116
|
+
process.platform === "darwin"
|
|
117
|
+
? "brew install llvm"
|
|
118
|
+
: process.platform === "win32"
|
|
119
|
+
? "winget install --id LLVM.LLVM -e"
|
|
120
|
+
: "sudo apt install clang";
|
|
121
|
+
return { status: "missing", hint };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function spawnInstall(bin: string, target: string) {
|
|
125
|
+
const result = spawnSync(bin, ["--install-extension", target, "--force"], {
|
|
126
|
+
encoding: "utf8",
|
|
127
|
+
windowsHide: true,
|
|
128
|
+
timeout: 180000,
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
ok: result.status === 0 && !/not found/i.test(String(result.stderr || "") + String(result.stdout || "")),
|
|
132
|
+
stdout: String(result.stdout || ""),
|
|
133
|
+
stderr: String(result.stderr || ""),
|
|
134
|
+
status: result.status,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function installMarketplaceExtension(id: string, label?: string): Promise<InstallResult> {
|
|
139
|
+
if (isExtensionInstalled(id)) {
|
|
140
|
+
return { status: "already", id };
|
|
141
|
+
}
|
|
142
|
+
const bin = editorBin();
|
|
143
|
+
if (!bin) {
|
|
144
|
+
return { status: "missing-editor", id };
|
|
145
|
+
}
|
|
146
|
+
console.log("cluaupp: installing", label || id);
|
|
147
|
+
const marketplace = spawnInstall(bin, id);
|
|
148
|
+
if (marketplace.ok) {
|
|
149
|
+
return { status: "installed", id, how: "marketplace" };
|
|
150
|
+
}
|
|
151
|
+
return { status: "failed", id, detail: marketplace.stderr || marketplace.stdout };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function reportInstall(result: InstallResult | null | undefined, label?: string, extraMissing?: string): void {
|
|
155
|
+
if (!result) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const name = label || result.id;
|
|
159
|
+
if (result.status === "already") {
|
|
160
|
+
console.log("cluaupp:", name, "already installed");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (result.status === "installed") {
|
|
164
|
+
console.log("cluaupp: installed", name, result.how ? `(${result.how})` : "");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (result.status === "missing-editor") {
|
|
168
|
+
console.error("cluaupp: Cursor/VS Code CLI not found. Install", name, "from the marketplace.");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (result.status === "missing") {
|
|
172
|
+
console.error("cluaupp: LLVM clang++ not found.", result.hint || extraMissing || "");
|
|
173
|
+
if (result.detail) {
|
|
174
|
+
console.error(" ", result.detail.split(/\r?\n/)[0]);
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
console.error("cluaupp: could not install", name + ":", result.detail || "unknown error");
|
|
179
|
+
if (extraMissing) {
|
|
180
|
+
console.error(" ", extraMissing);
|
|
181
|
+
}
|
|
182
|
+
}
|
package/src/{emit.js → emit.ts}
RENAMED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { isLibraryType, isLibraryMethod, MODULE_COLON } = require("./libs");
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { isInstanceType, isMethod, isDatatype, luauType } from "./api.js";
|
|
3
|
+
import { isLibraryType, isLibraryMethod, MODULE_COLON } from "./libs.js";
|
|
5
4
|
|
|
6
5
|
function emit(ast, options = {}) {
|
|
7
6
|
const strict = options.strict === true;
|
|
@@ -529,4 +528,4 @@ function emit(ast, options = {}) {
|
|
|
529
528
|
return lines.join("\n");
|
|
530
529
|
}
|
|
531
530
|
|
|
532
|
-
|
|
531
|
+
export { emit };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { TranspilerState } from "../types.js";
|
|
2
|
+
|
|
3
|
+
const GET_SERVICE = /^const\s+([A-Za-z_][\w]*)\s*=\s*game:GetService\("([A-Za-z_][\w]*)"\)\s*$/;
|
|
4
|
+
const REQUIRE_LINE = /^const\s+([A-Za-z_][\w]*)\s*=\s*require\((.+)\)\s*$/;
|
|
5
|
+
|
|
6
|
+
export class LuauCodeEmitter {
|
|
7
|
+
public static emptyState(strict = false): TranspilerState {
|
|
8
|
+
return {
|
|
9
|
+
robloxServices: new Set<string>(),
|
|
10
|
+
moduleRequires: new Map<string, string>(),
|
|
11
|
+
customTypes: [],
|
|
12
|
+
constants: [],
|
|
13
|
+
functions: [],
|
|
14
|
+
headerLines: [],
|
|
15
|
+
strict,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public static emit(state: TranspilerState): string {
|
|
20
|
+
const output: string[] = [];
|
|
21
|
+
if (state.strict) {
|
|
22
|
+
output.push("--!strict");
|
|
23
|
+
}
|
|
24
|
+
if (state.headerLines.length > 0) {
|
|
25
|
+
output.push(...state.headerLines);
|
|
26
|
+
} else {
|
|
27
|
+
output.push("-- Compiled by Cluaupp — C++ × Luau");
|
|
28
|
+
}
|
|
29
|
+
output.push("");
|
|
30
|
+
|
|
31
|
+
if (state.robloxServices.size > 0) {
|
|
32
|
+
for (const service of Array.from(state.robloxServices).sort()) {
|
|
33
|
+
output.push(`const ${service} = game:GetService("${service}")`);
|
|
34
|
+
}
|
|
35
|
+
output.push("");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (state.moduleRequires.size > 0) {
|
|
39
|
+
for (const [alias, requirePath] of Array.from(state.moduleRequires.entries()).sort(([a], [b]) => a.localeCompare(b))) {
|
|
40
|
+
output.push(`const ${alias} = require(${requirePath})`);
|
|
41
|
+
}
|
|
42
|
+
output.push("");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (state.customTypes.length > 0) {
|
|
46
|
+
output.push(...state.customTypes);
|
|
47
|
+
output.push("");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (state.constants.length > 0) {
|
|
51
|
+
output.push(...state.constants);
|
|
52
|
+
output.push("");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (state.functions.length > 0) {
|
|
56
|
+
output.push(...state.functions);
|
|
57
|
+
} else {
|
|
58
|
+
output.push("-- Nenhum método ou lógica detectado para transcompilação.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return output.join("\n").replace(/\n{3,}/g, "\n\n") + "\n";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static merge(state: TranspilerState, compiledLuau: string): string {
|
|
65
|
+
const trimmed = String(compiledLuau || "").trim();
|
|
66
|
+
if (!trimmed || trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
67
|
+
return compiledLuau;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const harvested = harvestPreamble(compiledLuau);
|
|
71
|
+
for (const service of harvested.services) {
|
|
72
|
+
state.robloxServices.add(service);
|
|
73
|
+
}
|
|
74
|
+
for (const [alias, requirePath] of harvested.requires) {
|
|
75
|
+
if (!state.moduleRequires.has(alias)) {
|
|
76
|
+
state.moduleRequires.set(alias, requirePath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const output: string[] = [];
|
|
81
|
+
output.push(...(harvested.header.length > 0 ? harvested.header : ["-- Compiled by Cluaupp — C++ × Luau"]));
|
|
82
|
+
for (const line of state.headerLines) {
|
|
83
|
+
if (line && !output.includes(line)) {
|
|
84
|
+
output.push(line);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (state.strict && !output.some((line) => line.startsWith("--!strict"))) {
|
|
88
|
+
output.unshift("--!strict");
|
|
89
|
+
}
|
|
90
|
+
output.push("");
|
|
91
|
+
|
|
92
|
+
if (state.robloxServices.size > 0) {
|
|
93
|
+
for (const service of Array.from(state.robloxServices).sort()) {
|
|
94
|
+
output.push(`const ${service} = game:GetService("${service}")`);
|
|
95
|
+
}
|
|
96
|
+
output.push("");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (state.moduleRequires.size > 0) {
|
|
100
|
+
for (const [alias, requirePath] of Array.from(state.moduleRequires.entries()).sort(([a], [b]) => a.localeCompare(b))) {
|
|
101
|
+
output.push(`const ${alias} = require(${requirePath})`);
|
|
102
|
+
}
|
|
103
|
+
output.push("");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const rest = harvested.body.trim();
|
|
107
|
+
const extraTypes = state.customTypes.filter((line) => !bodyHasDecl(rest, line, /^type\s+(\w+)/));
|
|
108
|
+
const extraConsts = state.constants.filter((line) => !bodyHasDecl(rest, line, /^const\s+(\w+)/));
|
|
109
|
+
if (extraTypes.length > 0) {
|
|
110
|
+
output.push(...extraTypes, "");
|
|
111
|
+
}
|
|
112
|
+
if (extraConsts.length > 0) {
|
|
113
|
+
output.push(...extraConsts, "");
|
|
114
|
+
}
|
|
115
|
+
if (rest) {
|
|
116
|
+
output.push(rest);
|
|
117
|
+
} else if (state.customTypes.length || state.constants.length || state.functions.length) {
|
|
118
|
+
if (state.customTypes.length) {
|
|
119
|
+
output.push(...state.customTypes, "");
|
|
120
|
+
}
|
|
121
|
+
if (state.constants.length) {
|
|
122
|
+
output.push(...state.constants, "");
|
|
123
|
+
}
|
|
124
|
+
if (state.functions.length) {
|
|
125
|
+
output.push(...state.functions);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return `${output.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function harvestPreamble(luau: string): {
|
|
134
|
+
header: string[];
|
|
135
|
+
services: Set<string>;
|
|
136
|
+
requires: Map<string, string>;
|
|
137
|
+
body: string;
|
|
138
|
+
} {
|
|
139
|
+
const lines = luau.split(/\r?\n/);
|
|
140
|
+
const header: string[] = [];
|
|
141
|
+
const services = new Set<string>();
|
|
142
|
+
const requires = new Map<string, string>();
|
|
143
|
+
let i = 0;
|
|
144
|
+
|
|
145
|
+
while (i < lines.length && (lines[i].startsWith("--") || lines[i].trim() === "")) {
|
|
146
|
+
if (lines[i].startsWith("--")) {
|
|
147
|
+
header.push(lines[i]);
|
|
148
|
+
}
|
|
149
|
+
i += 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
while (i < lines.length) {
|
|
153
|
+
const line = lines[i];
|
|
154
|
+
if (line.trim() === "") {
|
|
155
|
+
i += 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const service = line.match(GET_SERVICE);
|
|
159
|
+
if (service && service[1] === service[2]) {
|
|
160
|
+
services.add(service[1]);
|
|
161
|
+
i += 1;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const required = line.match(REQUIRE_LINE);
|
|
165
|
+
if (required) {
|
|
166
|
+
requires.set(required[1], required[2]);
|
|
167
|
+
i += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
header,
|
|
175
|
+
services,
|
|
176
|
+
requires,
|
|
177
|
+
body: lines.slice(i).join("\n"),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function bodyHasDecl(body: string, line: string, pattern: RegExp): boolean {
|
|
182
|
+
const match = line.match(pattern);
|
|
183
|
+
if (!match) {
|
|
184
|
+
return body.includes(line);
|
|
185
|
+
}
|
|
186
|
+
return new RegExp(`^(?:export\\s+)?(?:type|const)\\s+${match[1]}\\b`, "m").test(body);
|
|
187
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type Parser from "tree-sitter";
|
|
2
|
+
import { compileSource } from "../compile.js";
|
|
3
|
+
|
|
4
|
+
type SyntaxNode = Parser.SyntaxNode;
|
|
5
|
+
|
|
6
|
+
const TYPE_MAP: Record<string, string> = {
|
|
7
|
+
"std::string": "string",
|
|
8
|
+
"std::string_view": "string",
|
|
9
|
+
string: "string",
|
|
10
|
+
string_view: "string",
|
|
11
|
+
bool: "boolean",
|
|
12
|
+
int: "number",
|
|
13
|
+
int8_t: "number",
|
|
14
|
+
int16_t: "number",
|
|
15
|
+
int32_t: "number",
|
|
16
|
+
int64_t: "number",
|
|
17
|
+
uint8_t: "number",
|
|
18
|
+
uint16_t: "number",
|
|
19
|
+
uint32_t: "number",
|
|
20
|
+
uint64_t: "number",
|
|
21
|
+
float: "number",
|
|
22
|
+
double: "number",
|
|
23
|
+
size_t: "number",
|
|
24
|
+
void: "()",
|
|
25
|
+
auto: "any",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function translateCppType(cppType: string): string {
|
|
29
|
+
const trimmed = cppType
|
|
30
|
+
.replace(/\b(?:const|constexpr|volatile|static|inline|restrict)\b/g, "")
|
|
31
|
+
.replace(/\s+/g, " ")
|
|
32
|
+
.replace(/&/g, "")
|
|
33
|
+
.replace(/\*/g, "")
|
|
34
|
+
.trim();
|
|
35
|
+
if (!trimmed) {
|
|
36
|
+
return "any";
|
|
37
|
+
}
|
|
38
|
+
if (TYPE_MAP[trimmed]) {
|
|
39
|
+
return TYPE_MAP[trimmed];
|
|
40
|
+
}
|
|
41
|
+
const generic = trimmed.match(/^(?:std::)?(?:vector|array|span|LuaArray)\s*<\s*([^>]+)\s*>$/);
|
|
42
|
+
if (generic) {
|
|
43
|
+
return `{${translateCppType(generic[1])}}`;
|
|
44
|
+
}
|
|
45
|
+
const optional = trimmed.match(/^(?:std::)?optional\s*<\s*([^>]+)\s*>$/);
|
|
46
|
+
if (optional) {
|
|
47
|
+
return `${translateCppType(optional[1])}?`;
|
|
48
|
+
}
|
|
49
|
+
return trimmed.replace(/^::/, "");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function translateGetService(node: SyntaxNode): string | null {
|
|
53
|
+
const functionNode = node.childForFieldName("function") || node.namedChildren[0];
|
|
54
|
+
if (!functionNode) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const text = functionNode.text.replace(/\s+/g, "");
|
|
58
|
+
const generic = text.match(/GetService<(\w+)>/);
|
|
59
|
+
if (generic) {
|
|
60
|
+
return generic[1];
|
|
61
|
+
}
|
|
62
|
+
if (!text.includes("GetService")) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const args = functionNode.namedChildren.find((child) => child.type === "template_argument_list");
|
|
66
|
+
if (args) {
|
|
67
|
+
const ident = args.namedChildren.find((child) => child.type === "type_identifier" || child.type === "identifier");
|
|
68
|
+
if (ident) {
|
|
69
|
+
return ident.text;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const callText = node.text.replace(/\s+/g, "");
|
|
73
|
+
const match = callText.match(/GetService<(\w+)>/);
|
|
74
|
+
return match ? match[1] : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function translateConstant(node: SyntaxNode): string | null {
|
|
78
|
+
const declarator = node.childForFieldName("declarator");
|
|
79
|
+
if (!declarator) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const name = identifierOf(declarator);
|
|
83
|
+
const valueNode =
|
|
84
|
+
node.childForFieldName("value") ||
|
|
85
|
+
declarator.childForFieldName("value") ||
|
|
86
|
+
namedOf(declarator, "initializer") ||
|
|
87
|
+
node.namedChildren[node.namedChildren.length - 1];
|
|
88
|
+
if (!name || !valueNode || valueNode === declarator) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
let value = valueNode.text.replace(/;$/, "").trim();
|
|
92
|
+
if (value.startsWith("=")) {
|
|
93
|
+
value = value.slice(1).trim();
|
|
94
|
+
}
|
|
95
|
+
if (!value) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return `const ${name} = ${value}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function translateAlias(node: SyntaxNode, mapType: (cppType: string) => string): string | null {
|
|
102
|
+
const nameNode = node.childForFieldName("name") || namedOf(node, "type_identifier") || node.namedChildren[1];
|
|
103
|
+
const typeNode = node.childForFieldName("type") || namedOf(node, "type_descriptor") || node.namedChildren[0];
|
|
104
|
+
if (!nameNode || !typeNode) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return `type ${nameNode.text} = ${mapType(typeNode.text)}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function translateStruct(node: SyntaxNode, mapType: (cppType: string) => string): string | null {
|
|
111
|
+
const nameNode = node.childForFieldName("name") || namedOf(node, "type_identifier");
|
|
112
|
+
const body = node.childForFieldName("body") || namedOf(node, "field_declaration_list");
|
|
113
|
+
if (!nameNode) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (!body) {
|
|
117
|
+
return `type ${nameNode.text} = {}`;
|
|
118
|
+
}
|
|
119
|
+
const fields: string[] = [];
|
|
120
|
+
for (const field of body.namedChildren) {
|
|
121
|
+
if (field.type !== "field_declaration") {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const fieldType = field.childForFieldName("type");
|
|
125
|
+
const fieldDecl = field.childForFieldName("declarator");
|
|
126
|
+
const fieldName = fieldDecl ? identifierOf(fieldDecl) : namedOf(field, "field_identifier")?.text;
|
|
127
|
+
if (!fieldName) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
fields.push(`\t${fieldName}: ${mapType(fieldType?.text || "any")},`);
|
|
131
|
+
}
|
|
132
|
+
if (fields.length === 0) {
|
|
133
|
+
return `type ${nameNode.text} = {}`;
|
|
134
|
+
}
|
|
135
|
+
return `type ${nameNode.text} = {\n${fields.join("\n")}\n}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function translateFunction(node: SyntaxNode, fileName: string): string | null {
|
|
139
|
+
try {
|
|
140
|
+
const luau = compileSource(node.text, fileName, { skipHeader: true });
|
|
141
|
+
const trimmed = String(luau || "").trim();
|
|
142
|
+
return trimmed || null;
|
|
143
|
+
} catch {
|
|
144
|
+
const name = identifierOf(node.childForFieldName("declarator") || node) || "fn";
|
|
145
|
+
return `-- function ${name} (não traduzida)`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function identifierOf(node: SyntaxNode): string | null {
|
|
150
|
+
if (node.type === "identifier" || node.type === "field_identifier" || node.type === "type_identifier") {
|
|
151
|
+
return node.text;
|
|
152
|
+
}
|
|
153
|
+
const direct = node.childForFieldName("declarator") || namedOf(node, "identifier") || namedOf(node, "field_identifier");
|
|
154
|
+
if (direct) {
|
|
155
|
+
return identifierOf(direct);
|
|
156
|
+
}
|
|
157
|
+
for (const child of node.namedChildren) {
|
|
158
|
+
const found = identifierOf(child);
|
|
159
|
+
if (found) {
|
|
160
|
+
return found;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function namedOf(node: SyntaxNode, type: string): SyntaxNode | null {
|
|
167
|
+
return node.namedChildren.find((child) => child.type === type) || null;
|
|
168
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { luauType } = require("./api");
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { luauType } from "./api.js";
|
|
5
4
|
|
|
6
5
|
function fileStem(fileName) {
|
|
7
6
|
return path.basename(String(fileName || "module")).replace(/\.(h|hpp|hh|cpp|cc|cxx|c)$/i, "");
|
|
@@ -227,7 +226,7 @@ function emitHeaderModule(ast, options = {}) {
|
|
|
227
226
|
return lines.join("\n");
|
|
228
227
|
}
|
|
229
228
|
|
|
230
|
-
|
|
229
|
+
export {
|
|
231
230
|
emitHeaderModule,
|
|
232
231
|
primaryHeaderName,
|
|
233
232
|
collectHeaderShape,
|