cluaupp 0.2.1 → 0.2.2
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 +4 -0
- package/editors/vscode/package.json +25 -25
- package/examples/game/src/shared/config.h +2 -0
- package/generated/compile.js +38 -0
- package/generated/parser/collector.js +37 -1
- package/generated/utils/rojo-mapper.js +23 -3
- package/package.json +87 -87
- package/src/compile.ts +43 -1
- package/src/parser/collector.ts +35 -2
- package/src/utils/rojo-mapper.ts +26 -3
- package/templates/game/src/shared/config.h +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.2
|
|
6
|
+
|
|
7
|
+
- Header modules with a sibling `.cpp` now re-export those functions (`startingCoins` from `config.cpp` lands on `config.luau` via `configImpl`). The impl no longer `require`s its own header (`ReplicatedStorage.config`).
|
|
8
|
+
|
|
5
9
|
## 0.2.1
|
|
6
10
|
|
|
7
11
|
- Pin `tree-sitter` 0.21.1 and `tree-sitter-c` 0.23.2 so `npm i -g cluaupp` no longer pulls `tree-sitter-c@0.23.6` (peerOptional `tree-sitter@^0.22.1`).
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cluaupp-diagnostics",
|
|
3
|
-
"displayName": "Cluaupp",
|
|
4
|
-
"description": "Cluaupp subset parse errors. C++ completion, hover, and go-to-definition come from clangd + include/cluaupp/roblox.hpp.",
|
|
5
|
-
"version": "0.2.
|
|
6
|
-
"publisher": "kartzdev",
|
|
7
|
-
"engines": {
|
|
8
|
-
"vscode": "^1.74.0"
|
|
9
|
-
},
|
|
10
|
-
"categories": [
|
|
11
|
-
"Linters"
|
|
12
|
-
],
|
|
13
|
-
"activationEvents": [
|
|
14
|
-
"onLanguage:cpp"
|
|
15
|
-
],
|
|
16
|
-
"main": "./extension.js",
|
|
17
|
-
"contributes": {
|
|
18
|
-
"commands": [
|
|
19
|
-
{
|
|
20
|
-
"command": "cluaupp.restartIntelliSense",
|
|
21
|
-
"title": "Cluaupp: Reload subset diagnostics"
|
|
22
|
-
}
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "cluaupp-diagnostics",
|
|
3
|
+
"displayName": "Cluaupp",
|
|
4
|
+
"description": "Cluaupp subset parse errors. C++ completion, hover, and go-to-definition come from clangd + include/cluaupp/roblox.hpp.",
|
|
5
|
+
"version": "0.2.2",
|
|
6
|
+
"publisher": "kartzdev",
|
|
7
|
+
"engines": {
|
|
8
|
+
"vscode": "^1.74.0"
|
|
9
|
+
},
|
|
10
|
+
"categories": [
|
|
11
|
+
"Linters"
|
|
12
|
+
],
|
|
13
|
+
"activationEvents": [
|
|
14
|
+
"onLanguage:cpp"
|
|
15
|
+
],
|
|
16
|
+
"main": "./extension.js",
|
|
17
|
+
"contributes": {
|
|
18
|
+
"commands": [
|
|
19
|
+
{
|
|
20
|
+
"command": "cluaupp.restartIntelliSense",
|
|
21
|
+
"title": "Cluaupp: Reload subset diagnostics"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
package/generated/compile.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.compileSource = compileSource;
|
|
7
7
|
exports.compileService = compileService;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const parse_js_1 = require("./parse.js");
|
|
10
11
|
const emit_js_1 = require("./emit.js");
|
|
@@ -33,12 +34,49 @@ function attachRequires(contents, source, ast, options, outName) {
|
|
|
33
34
|
function posixRel(from, file) {
|
|
34
35
|
return node_path_1.default.relative(from, file).replace(/\\/g, "/");
|
|
35
36
|
}
|
|
37
|
+
function declKey(decl) {
|
|
38
|
+
return `${decl.owner || ""}::${decl.name}`;
|
|
39
|
+
}
|
|
40
|
+
function mergeSiblingImpl(ast, implPath, options) {
|
|
41
|
+
const implOptions = {
|
|
42
|
+
...options,
|
|
43
|
+
moduleIncludes: [],
|
|
44
|
+
seen: new Set(),
|
|
45
|
+
pragmaResolved: false,
|
|
46
|
+
};
|
|
47
|
+
const prepared = (0, preprocess_js_1.preprocess)(node_fs_1.default.readFileSync(implPath, "utf8"), implPath, implOptions);
|
|
48
|
+
const implAst = (0, parse_js_1.parse)(prepared, node_path_1.default.basename(implPath));
|
|
49
|
+
if (!Array.isArray(ast.body)) {
|
|
50
|
+
ast.body = [];
|
|
51
|
+
}
|
|
52
|
+
const existing = new Set((ast.body || [])
|
|
53
|
+
.filter((decl) => decl && (decl.type === "function" || decl.type === "proto") && decl.name)
|
|
54
|
+
.map(declKey));
|
|
55
|
+
for (const decl of implAst.body || []) {
|
|
56
|
+
if ((decl.type !== "function" && decl.type !== "proto") || !decl.name) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const key = declKey(decl);
|
|
60
|
+
if (existing.has(key)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
existing.add(key);
|
|
64
|
+
ast.body.push({
|
|
65
|
+
type: "proto",
|
|
66
|
+
name: decl.name,
|
|
67
|
+
owner: decl.owner,
|
|
68
|
+
returnType: decl.returnType,
|
|
69
|
+
params: decl.params,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
36
73
|
function wireHeaderImpl(ast, fileName, options) {
|
|
37
74
|
const headerPath = options.filePath || null;
|
|
38
75
|
const impl = headerPath ? (0, preprocess_js_1.siblingImplementation)(headerPath) : null;
|
|
39
76
|
if (!impl) {
|
|
40
77
|
return;
|
|
41
78
|
}
|
|
79
|
+
mergeSiblingImpl(ast, impl, options);
|
|
42
80
|
const typeName = (0, headers_js_1.primaryHeaderName)(ast, options.relativeName || fileName);
|
|
43
81
|
const implRel = options.srcDir ? posixRel(options.srcDir, impl) : node_path_1.default.basename(impl).replace(/\\/g, "/");
|
|
44
82
|
const implOut = (0, preprocess_js_1.implOutName)(implRel);
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.ASTCollector = void 0;
|
|
4
7
|
exports.emptyState = emptyState;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const preprocess_js_1 = require("../preprocess.js");
|
|
5
10
|
const translators_js_1 = require("../emitter/translators.js");
|
|
6
11
|
function emptyState(strict = false) {
|
|
7
12
|
return {
|
|
@@ -16,9 +21,11 @@ function emptyState(strict = false) {
|
|
|
16
21
|
}
|
|
17
22
|
class ASTCollector {
|
|
18
23
|
mapper;
|
|
24
|
+
context;
|
|
19
25
|
state;
|
|
20
26
|
constructor(mapper, context = { fileName: "input.cpp" }) {
|
|
21
27
|
this.mapper = mapper;
|
|
28
|
+
this.context = context;
|
|
22
29
|
this.state = emptyState(context.strict === true);
|
|
23
30
|
}
|
|
24
31
|
collect(rootNode) {
|
|
@@ -58,12 +65,41 @@ class ASTCollector {
|
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
}
|
|
68
|
+
includeTarget(raw) {
|
|
69
|
+
const from = this.context.sourcePath;
|
|
70
|
+
if (!from) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const cleaned = raw.replace(/[<>'"]/g, "").trim();
|
|
74
|
+
if (!cleaned || raw.includes("<")) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return node_path_1.default.resolve(node_path_1.default.dirname(from), cleaned);
|
|
78
|
+
}
|
|
79
|
+
isOwnHeader(raw) {
|
|
80
|
+
const from = this.context.sourcePath;
|
|
81
|
+
if (!from) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const target = this.includeTarget(raw);
|
|
85
|
+
if (!target) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (node_path_1.default.resolve(from) === target) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
const header = (0, preprocess_js_1.siblingHeader)(from);
|
|
92
|
+
return Boolean(header && node_path_1.default.resolve(header) === target);
|
|
93
|
+
}
|
|
61
94
|
collectInclude(node) {
|
|
62
95
|
const pathNode = node.childForFieldName("path") || node.namedChildren[0] || node.child(1);
|
|
63
96
|
if (!pathNode) {
|
|
64
97
|
return;
|
|
65
98
|
}
|
|
66
|
-
|
|
99
|
+
if (this.isOwnHeader(pathNode.text)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const resolved = this.mapper.resolveIncludeToRequire(pathNode.text, this.context.sourcePath);
|
|
67
103
|
if (resolved) {
|
|
68
104
|
this.state.moduleRequires.set(resolved.alias, resolved.path);
|
|
69
105
|
}
|
|
@@ -64,7 +64,23 @@ class RojoMapper {
|
|
|
64
64
|
this.config = (0, comment_json_1.parse)(content);
|
|
65
65
|
this.bindings = this.walkTree(this.config.tree, []);
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
resolveQuotedFile(raw, fromFile) {
|
|
68
|
+
const bases = [];
|
|
69
|
+
if (fromFile) {
|
|
70
|
+
bases.push(node_path_1.default.dirname(fromFile));
|
|
71
|
+
}
|
|
72
|
+
if (this.srcDir) {
|
|
73
|
+
bases.push(this.srcDir);
|
|
74
|
+
}
|
|
75
|
+
for (const base of bases) {
|
|
76
|
+
const candidate = node_path_1.default.resolve(base, raw);
|
|
77
|
+
if (node_fs_1.default.existsSync(candidate) && node_fs_1.default.statSync(candidate).isFile()) {
|
|
78
|
+
return candidate;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
resolveIncludeToRequire(includePath, fromFile) {
|
|
68
84
|
const raw = includePath.replace(/[<>'"]/g, "").trim();
|
|
69
85
|
if (!raw) {
|
|
70
86
|
return null;
|
|
@@ -79,11 +95,15 @@ class RojoMapper {
|
|
|
79
95
|
if (library) {
|
|
80
96
|
return library;
|
|
81
97
|
}
|
|
82
|
-
const
|
|
98
|
+
const resolvedFile = this.resolveQuotedFile(raw, fromFile);
|
|
99
|
+
const lookupPath = resolvedFile && this.srcDir
|
|
100
|
+
? posix(node_path_1.default.relative(this.srcDir, resolvedFile)).replace(/\.h(pp|h)?$/i, "")
|
|
101
|
+
: cleanPath;
|
|
102
|
+
const fromTree = this.resolveFromRojoTree(lookupPath, baseName);
|
|
83
103
|
if (fromTree) {
|
|
84
104
|
return fromTree;
|
|
85
105
|
}
|
|
86
|
-
const segments =
|
|
106
|
+
const segments = lookupPath.split("/").filter(Boolean);
|
|
87
107
|
if (segments[0] === "src") {
|
|
88
108
|
segments.shift();
|
|
89
109
|
}
|
package/package.json
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cluaupp",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Cluaupp — the definitive merge of C++ and modern Luau. Source-to-source transpiler with first-class Roblox APIs.",
|
|
5
|
-
"author": "KartzDev",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"bin": {
|
|
8
|
-
"cluaupp": "bin/cluaupp.js",
|
|
9
|
-
"cluau": "bin/cluaupp.js"
|
|
10
|
-
},
|
|
11
|
-
"main": "./generated/compile.js",
|
|
12
|
-
"exports": {
|
|
13
|
-
".": "./generated/compile.js",
|
|
14
|
-
"./package.json": "./package.json"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"bin",
|
|
18
|
-
"src",
|
|
19
|
-
"generated",
|
|
20
|
-
"include",
|
|
21
|
-
"templates",
|
|
22
|
-
"examples",
|
|
23
|
-
"editors",
|
|
24
|
-
"runtime",
|
|
25
|
-
"README.md",
|
|
26
|
-
"LICENSE",
|
|
27
|
-
"CHANGELOG.md",
|
|
28
|
-
"docs"
|
|
29
|
-
],
|
|
30
|
-
"scripts": {
|
|
31
|
-
"cluaupp": "node bin/cluaupp.js",
|
|
32
|
-
"cluau": "node bin/cluaupp.js",
|
|
33
|
-
"build": "tsc",
|
|
34
|
-
"build:cli": "tsc",
|
|
35
|
-
"dev:cli": "tsx src/cli.ts",
|
|
36
|
-
"prepare": "tsc",
|
|
37
|
-
"test": "node test/out.test.js && node test/types.test.js && node test/modules.test.js && node test/security.test.js && node test/understander.test.js",
|
|
38
|
-
"generate-api": "node scripts/generate-api.js",
|
|
39
|
-
"check-highlight": "node scripts/check-highlight.js",
|
|
40
|
-
"vendor-libs": "node scripts/vendor-libs.js",
|
|
41
|
-
"docs": "moonwave dev",
|
|
42
|
-
"docs:build": "moonwave build",
|
|
43
|
-
"site": "node scripts/generate-api.js && node scripts/check-highlight.js",
|
|
44
|
-
"dev": "node scripts/dev.js",
|
|
45
|
-
"stop": "node scripts/stop.js",
|
|
46
|
-
"prepublishOnly": "tsc && npm test"
|
|
47
|
-
},
|
|
48
|
-
"engines": {
|
|
49
|
-
"node": ">=18"
|
|
50
|
-
},
|
|
51
|
-
"keywords": [
|
|
52
|
-
"luau",
|
|
53
|
-
"roblox",
|
|
54
|
-
"cpp",
|
|
55
|
-
"c++",
|
|
56
|
-
"transpiler",
|
|
57
|
-
"compiler",
|
|
58
|
-
"rojo",
|
|
59
|
-
"cluaupp",
|
|
60
|
-
"cluau"
|
|
61
|
-
],
|
|
62
|
-
"repository": {
|
|
63
|
-
"type": "git",
|
|
64
|
-
"url": "git+https://github.com/KartzRbx/Cluaupp.git"
|
|
65
|
-
},
|
|
66
|
-
"bugs": {
|
|
67
|
-
"url": "https://github.com/KartzRbx/Cluaupp/issues"
|
|
68
|
-
},
|
|
69
|
-
"homepage": "https://github.com/KartzRbx/Cluaupp#readme",
|
|
70
|
-
"dependencies": {
|
|
71
|
-
"commander": "^15.0.0",
|
|
72
|
-
"comment-json": "^5.0.0",
|
|
73
|
-
"tree-sitter": "0.21.1",
|
|
74
|
-
"tree-sitter-c": "0.23.2",
|
|
75
|
-
"tree-sitter-cpp": "0.23.4",
|
|
76
|
-
"vscode-languageserver": "^10.1.1",
|
|
77
|
-
"vscode-languageserver-textdocument": "^1.0.14"
|
|
78
|
-
},
|
|
79
|
-
"overrides": {
|
|
80
|
-
"tree-sitter-c": "0.23.2"
|
|
81
|
-
},
|
|
82
|
-
"devDependencies": {
|
|
83
|
-
"@types/node": "^22.20.3",
|
|
84
|
-
"tsx": "^4.23.13",
|
|
85
|
-
"typescript": "^7.0.2"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "cluaupp",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Cluaupp — the definitive merge of C++ and modern Luau. Source-to-source transpiler with first-class Roblox APIs.",
|
|
5
|
+
"author": "KartzDev",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"cluaupp": "bin/cluaupp.js",
|
|
9
|
+
"cluau": "bin/cluaupp.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./generated/compile.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./generated/compile.js",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"src",
|
|
19
|
+
"generated",
|
|
20
|
+
"include",
|
|
21
|
+
"templates",
|
|
22
|
+
"examples",
|
|
23
|
+
"editors",
|
|
24
|
+
"runtime",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"CHANGELOG.md",
|
|
28
|
+
"docs"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"cluaupp": "node bin/cluaupp.js",
|
|
32
|
+
"cluau": "node bin/cluaupp.js",
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"build:cli": "tsc",
|
|
35
|
+
"dev:cli": "tsx src/cli.ts",
|
|
36
|
+
"prepare": "tsc",
|
|
37
|
+
"test": "node test/out.test.js && node test/types.test.js && node test/modules.test.js && node test/security.test.js && node test/understander.test.js",
|
|
38
|
+
"generate-api": "node scripts/generate-api.js",
|
|
39
|
+
"check-highlight": "node scripts/check-highlight.js",
|
|
40
|
+
"vendor-libs": "node scripts/vendor-libs.js",
|
|
41
|
+
"docs": "moonwave dev",
|
|
42
|
+
"docs:build": "moonwave build",
|
|
43
|
+
"site": "node scripts/generate-api.js && node scripts/check-highlight.js",
|
|
44
|
+
"dev": "node scripts/dev.js",
|
|
45
|
+
"stop": "node scripts/stop.js",
|
|
46
|
+
"prepublishOnly": "tsc && npm test"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"luau",
|
|
53
|
+
"roblox",
|
|
54
|
+
"cpp",
|
|
55
|
+
"c++",
|
|
56
|
+
"transpiler",
|
|
57
|
+
"compiler",
|
|
58
|
+
"rojo",
|
|
59
|
+
"cluaupp",
|
|
60
|
+
"cluau"
|
|
61
|
+
],
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "git+https://github.com/KartzRbx/Cluaupp.git"
|
|
65
|
+
},
|
|
66
|
+
"bugs": {
|
|
67
|
+
"url": "https://github.com/KartzRbx/Cluaupp/issues"
|
|
68
|
+
},
|
|
69
|
+
"homepage": "https://github.com/KartzRbx/Cluaupp#readme",
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"commander": "^15.0.0",
|
|
72
|
+
"comment-json": "^5.0.0",
|
|
73
|
+
"tree-sitter": "0.21.1",
|
|
74
|
+
"tree-sitter-c": "0.23.2",
|
|
75
|
+
"tree-sitter-cpp": "0.23.4",
|
|
76
|
+
"vscode-languageserver": "^10.1.1",
|
|
77
|
+
"vscode-languageserver-textdocument": "^1.0.14"
|
|
78
|
+
},
|
|
79
|
+
"overrides": {
|
|
80
|
+
"tree-sitter-c": "0.23.2"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@types/node": "^22.20.3",
|
|
84
|
+
"tsx": "^4.23.13",
|
|
85
|
+
"typescript": "^7.0.2"
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/compile.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { parse } from "./parse.js";
|
|
3
4
|
import { emit } from "./emit.js";
|
|
@@ -5,7 +6,7 @@ import { preprocess, isHeaderFile, toLuauPath, siblingImplementation, siblingHea
|
|
|
5
6
|
import { collectLibraries, insertPreamble } from "./libs.js";
|
|
6
7
|
import { planOutput } from "./architecture.js";
|
|
7
8
|
import { emitHeaderModule, primaryHeaderName } from "./headers.js";
|
|
8
|
-
import type { AstProgram, ModuleInclude, PreprocessOptions } from "./ast.js";
|
|
9
|
+
import type { AstNode, AstProgram, ModuleInclude, PreprocessOptions } from "./ast.js";
|
|
9
10
|
import type { CompileOptions, CompileServiceResult } from "./types.js";
|
|
10
11
|
|
|
11
12
|
function shouldAttachRequires(name: string, contents: string): boolean {
|
|
@@ -32,12 +33,53 @@ function posixRel(from: string, file: string): string {
|
|
|
32
33
|
return path.relative(from, file).replace(/\\/g, "/");
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function declKey(decl: AstNode): string {
|
|
37
|
+
return `${decl.owner || ""}::${decl.name}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function mergeSiblingImpl(ast: AstProgram, implPath: string, options: PreprocessOptions): void {
|
|
41
|
+
const implOptions: PreprocessOptions = {
|
|
42
|
+
...options,
|
|
43
|
+
moduleIncludes: [],
|
|
44
|
+
seen: new Set(),
|
|
45
|
+
pragmaResolved: false,
|
|
46
|
+
};
|
|
47
|
+
const prepared = preprocess(fs.readFileSync(implPath, "utf8"), implPath, implOptions);
|
|
48
|
+
const implAst = parse(prepared, path.basename(implPath)) as AstProgram;
|
|
49
|
+
if (!Array.isArray(ast.body)) {
|
|
50
|
+
ast.body = [];
|
|
51
|
+
}
|
|
52
|
+
const existing = new Set(
|
|
53
|
+
(ast.body || [])
|
|
54
|
+
.filter((decl) => decl && (decl.type === "function" || decl.type === "proto") && decl.name)
|
|
55
|
+
.map(declKey),
|
|
56
|
+
);
|
|
57
|
+
for (const decl of implAst.body || []) {
|
|
58
|
+
if ((decl.type !== "function" && decl.type !== "proto") || !decl.name) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const key = declKey(decl);
|
|
62
|
+
if (existing.has(key)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
existing.add(key);
|
|
66
|
+
ast.body.push({
|
|
67
|
+
type: "proto",
|
|
68
|
+
name: decl.name,
|
|
69
|
+
owner: decl.owner,
|
|
70
|
+
returnType: decl.returnType,
|
|
71
|
+
params: decl.params,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
35
76
|
function wireHeaderImpl(ast: AstProgram, fileName: string, options: PreprocessOptions): void {
|
|
36
77
|
const headerPath = options.filePath || null;
|
|
37
78
|
const impl = headerPath ? siblingImplementation(headerPath) : null;
|
|
38
79
|
if (!impl) {
|
|
39
80
|
return;
|
|
40
81
|
}
|
|
82
|
+
mergeSiblingImpl(ast, impl, options);
|
|
41
83
|
const typeName = primaryHeaderName(ast, options.relativeName || fileName);
|
|
42
84
|
const implRel = options.srcDir ? posixRel(options.srcDir, impl) : path.basename(impl).replace(/\\/g, "/");
|
|
43
85
|
const implOut = implOutName(implRel);
|
package/src/parser/collector.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/src/utils/rojo-mapper.ts
CHANGED
|
@@ -64,7 +64,24 @@ export class RojoMapper {
|
|
|
64
64
|
this.bindings = this.walkTree(this.config.tree, []);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
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
|
|
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 =
|
|
113
|
+
const segments = lookupPath.split("/").filter(Boolean);
|
|
91
114
|
if (segments[0] === "src") {
|
|
92
115
|
segments.shift();
|
|
93
116
|
}
|