relaxnative 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +592 -0
- package/dist/chunk-22W76CYR.js +607 -0
- package/dist/chunk-24NXCU65.js +254 -0
- package/dist/chunk-2APMRURB.js +65 -0
- package/dist/chunk-2CHBHJPT.js +607 -0
- package/dist/chunk-2I4JHZI7.js +287 -0
- package/dist/chunk-2JOHYYQO.js +607 -0
- package/dist/chunk-3GW77EWF.js +505 -0
- package/dist/chunk-5J5CAKCD.js +266 -0
- package/dist/chunk-5NTDZ7YZ.js +377 -0
- package/dist/chunk-5TA6MROS.js +529 -0
- package/dist/chunk-5WVEBKMJ.js +1019 -0
- package/dist/chunk-6O5TIEEI.js +545 -0
- package/dist/chunk-6XU5DETO.js +896 -0
- package/dist/chunk-7BIZ6P3B.js +176 -0
- package/dist/chunk-7DKO777J.js +285 -0
- package/dist/chunk-7JYWUH4Y.js +268 -0
- package/dist/chunk-7NMCEP2V.js +756 -0
- package/dist/chunk-A7N4YBP2.js +379 -0
- package/dist/chunk-AZTCDV6R.js +572 -0
- package/dist/chunk-B34XEGM6.js +559 -0
- package/dist/chunk-BFHBLVXW.js +607 -0
- package/dist/chunk-BLOJ33LO.js +65 -0
- package/dist/chunk-BYPXCWTI.js +375 -0
- package/dist/chunk-C4KJD2AN.js +1044 -0
- package/dist/chunk-CJALJTRQ.js +814 -0
- package/dist/chunk-D4PK367Z.js +627 -0
- package/dist/chunk-DCWBZPEV.js +287 -0
- package/dist/chunk-DI7KSUEC.js +676 -0
- package/dist/chunk-DQ2KXIOO.js +665 -0
- package/dist/chunk-DV5STE3W.js +986 -0
- package/dist/chunk-EG5KNEKP.js +1027 -0
- package/dist/chunk-EOA2OWFA.js +1020 -0
- package/dist/chunk-ES3B6EZJ.js +56 -0
- package/dist/chunk-ETIXNPU5.js +741 -0
- package/dist/chunk-EUZBU2H7.js +824 -0
- package/dist/chunk-F6V7XDEB.js +150 -0
- package/dist/chunk-FNJKUFNF.js +1019 -0
- package/dist/chunk-FZB37DWL.js +453 -0
- package/dist/chunk-G3NDHZNZ.js +453 -0
- package/dist/chunk-G4YR34LE.js +410 -0
- package/dist/chunk-GU4XXISM.js +264 -0
- package/dist/chunk-GVPSQXGJ.js +1027 -0
- package/dist/chunk-HD5C4RNU.js +676 -0
- package/dist/chunk-HDIVY47T.js +287 -0
- package/dist/chunk-HFLRTDNK.js +985 -0
- package/dist/chunk-HGWRCVQ5.js +287 -0
- package/dist/chunk-HRG3SVKK.js +995 -0
- package/dist/chunk-HUGFULJ3.js +1027 -0
- package/dist/chunk-IDYSBXYS.js +344 -0
- package/dist/chunk-ISDDUQVI.js +1019 -0
- package/dist/chunk-IZ632ZCJ.js +286 -0
- package/dist/chunk-J5XI4L52.js +218 -0
- package/dist/chunk-JTIO7BUH.js +582 -0
- package/dist/chunk-JTWSFMF2.js +1020 -0
- package/dist/chunk-K5TV62T4.js +736 -0
- package/dist/chunk-K7MTG53V.js +985 -0
- package/dist/chunk-KGLZB3H2.js +676 -0
- package/dist/chunk-KYAB35P5.js +741 -0
- package/dist/chunk-KYDW3YVX.js +453 -0
- package/dist/chunk-L3MEMPRH.js +361 -0
- package/dist/chunk-LFTO3Z7N.js +757 -0
- package/dist/chunk-LLZ4I4OR.js +405 -0
- package/dist/chunk-LMRUM4U4.js +207 -0
- package/dist/chunk-LT5OGU6T.js +559 -0
- package/dist/chunk-LZQQOC3M.js +741 -0
- package/dist/chunk-LZYUNF6Q.js +1017 -0
- package/dist/chunk-MCTPVW4G.js +453 -0
- package/dist/chunk-MGLWXBIB.js +65 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-MTE6XDGC.js +541 -0
- package/dist/chunk-NDJUNDAE.js +676 -0
- package/dist/chunk-NG7SNFUD.js +1027 -0
- package/dist/chunk-ONVWKYK7.js +739 -0
- package/dist/chunk-OVMTFGE7.js +1042 -0
- package/dist/chunk-P5RQPRJ4.js +741 -0
- package/dist/chunk-PFABSW6Y.js +401 -0
- package/dist/chunk-PVVUJA2M.js +65 -0
- package/dist/chunk-Q3CDTGTX.js +676 -0
- package/dist/chunk-QKAKWDOQ.js +967 -0
- package/dist/chunk-QMPRDU6I.js +598 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RB6RHB6C.js +254 -0
- package/dist/chunk-RD2K7ODW.js +175 -0
- package/dist/chunk-RHBHJND2.js +582 -0
- package/dist/chunk-RUP6POSE.js +453 -0
- package/dist/chunk-RYNSM23L.js +582 -0
- package/dist/chunk-S72S7XXS.js +255 -0
- package/dist/chunk-SDV5AAPW.js +213 -0
- package/dist/chunk-STXQPXUY.js +242 -0
- package/dist/chunk-TUSF5AEG.js +140 -0
- package/dist/chunk-V74SNTE6.js +736 -0
- package/dist/chunk-VHM5XETC.js +453 -0
- package/dist/chunk-VKQIXLNL.js +273 -0
- package/dist/chunk-VPG23Z7Y.js +545 -0
- package/dist/chunk-VSP234PR.js +627 -0
- package/dist/chunk-WH4JPUWF.js +598 -0
- package/dist/chunk-WLAUJL3K.js +409 -0
- package/dist/chunk-WXCN2QJ7.js +350 -0
- package/dist/chunk-X3JZKLJC.js +896 -0
- package/dist/chunk-X7SAP7FC.js +582 -0
- package/dist/chunk-XEH6PRYE.js +968 -0
- package/dist/chunk-XI65CAQV.js +211 -0
- package/dist/chunk-Y7OSHR6W.js +235 -0
- package/dist/chunk-YN4WUMVD.js +1020 -0
- package/dist/chunk-YUWJ2C4Y.js +1020 -0
- package/dist/chunk-YXLBPWNU.js +263 -0
- package/dist/chunk-YYJJHO7R.js +407 -0
- package/dist/chunk-Z2RBHUIH.js +757 -0
- package/dist/chunk-Z6G3KIOM.js +1027 -0
- package/dist/chunk-ZPPXCDSH.js +361 -0
- package/dist/chunk-ZRTY24SZ.js +582 -0
- package/dist/chunk-ZSDFBCQG.js +741 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +339 -0
- package/dist/esmLoader.d.ts +10 -0
- package/dist/esmLoader.js +112 -0
- package/dist/index.d.ts +407 -0
- package/dist/index.js +126 -0
- package/dist/memory/memory.selftest.d.ts +2 -0
- package/dist/memory/memory.selftest.js +25 -0
- package/dist/worker/processEntry.d.ts +2 -0
- package/dist/worker/processEntry.js +160 -0
- package/dist/worker/workerEntry.d.ts +2 -0
- package/dist/worker/workerEntry.js +27 -0
- package/native/examples/add.c +6 -0
- package/native/examples/add_test.c +23 -0
- package/native/relaxnative_test.h +36 -0
- package/package.json +81 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import {
|
|
2
|
+
wrapFunctions
|
|
3
|
+
} from "./chunk-Y7OSHR6W.js";
|
|
4
|
+
import {
|
|
5
|
+
loadFfi
|
|
6
|
+
} from "./chunk-WXCN2QJ7.js";
|
|
7
|
+
|
|
8
|
+
// src/compiler/detectPlatform.ts
|
|
9
|
+
function detectPlatform() {
|
|
10
|
+
const platform = process.platform;
|
|
11
|
+
const arch = process.arch;
|
|
12
|
+
return {
|
|
13
|
+
platform,
|
|
14
|
+
arch,
|
|
15
|
+
isWindows: platform === "win32",
|
|
16
|
+
isMac: platform === "darwin",
|
|
17
|
+
isLinux: platform === "linux"
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/compiler/detectCCompiler.ts
|
|
22
|
+
import { execFileSync } from "child_process";
|
|
23
|
+
|
|
24
|
+
// src/utils/which.ts
|
|
25
|
+
import { accessSync, constants } from "fs";
|
|
26
|
+
import { delimiter } from "path";
|
|
27
|
+
function which(cmd) {
|
|
28
|
+
const paths = process.env.PATH?.split(delimiter) ?? [];
|
|
29
|
+
for (const p of paths) {
|
|
30
|
+
const full = `${p}/${cmd}`;
|
|
31
|
+
try {
|
|
32
|
+
accessSync(full, constants.X_OK);
|
|
33
|
+
return full;
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/compiler/detectCCompiler.ts
|
|
41
|
+
function getVersion(path) {
|
|
42
|
+
try {
|
|
43
|
+
return execFileSync(path, ["--version"], { encoding: "utf8" }).split("\n")[0].trim();
|
|
44
|
+
} catch {
|
|
45
|
+
return "unknown";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function detectCCompiler() {
|
|
49
|
+
const candidates = [
|
|
50
|
+
process.env.CC,
|
|
51
|
+
"clang",
|
|
52
|
+
"gcc",
|
|
53
|
+
"cc",
|
|
54
|
+
"cl"
|
|
55
|
+
].filter(Boolean);
|
|
56
|
+
for (const name of candidates) {
|
|
57
|
+
const resolved = name.includes("/") ? name : which(name);
|
|
58
|
+
if (!resolved) continue;
|
|
59
|
+
const version = getVersion(resolved);
|
|
60
|
+
return {
|
|
61
|
+
kind: "c",
|
|
62
|
+
path: resolved,
|
|
63
|
+
version,
|
|
64
|
+
vendor: resolved.includes("clang") ? "clang" : resolved.includes("gcc") ? "gcc" : resolved.includes("cl") ? "msvc" : "unknown"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
throw new Error("No C compiler found (gcc/clang/cl)");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/compiler/detectRustCompiler.ts
|
|
71
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
72
|
+
function detectRustCompiler() {
|
|
73
|
+
const rustc = process.env.RUSTC ?? which("rustc");
|
|
74
|
+
if (!rustc) return null;
|
|
75
|
+
const version = execFileSync2(rustc, ["--version"], {
|
|
76
|
+
encoding: "utf8"
|
|
77
|
+
}).trim();
|
|
78
|
+
return {
|
|
79
|
+
kind: "rust",
|
|
80
|
+
path: rustc,
|
|
81
|
+
version,
|
|
82
|
+
vendor: "rust"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/compiler/sanityCheck.ts
|
|
87
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
88
|
+
import { writeFileSync, mkdtempSync } from "fs";
|
|
89
|
+
import { tmpdir } from "os";
|
|
90
|
+
import { join } from "path";
|
|
91
|
+
function sanityCheckC(compiler) {
|
|
92
|
+
const dir = mkdtempSync(join(tmpdir(), "relaxnative-"));
|
|
93
|
+
const source = join(dir, "test.c");
|
|
94
|
+
const output = join(dir, "test");
|
|
95
|
+
writeFileSync(source, "int main(){return 0;}");
|
|
96
|
+
try {
|
|
97
|
+
execFileSync3(
|
|
98
|
+
compiler.path,
|
|
99
|
+
compiler.vendor === "msvc" ? [source] : [source, "-o", output],
|
|
100
|
+
{ stdio: "ignore" }
|
|
101
|
+
);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`C compiler failed sanity check: ${compiler.path}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/compiler/detect.ts
|
|
110
|
+
function detectCompilers() {
|
|
111
|
+
const platform = detectPlatform();
|
|
112
|
+
const c = detectCCompiler();
|
|
113
|
+
sanityCheckC(c);
|
|
114
|
+
const rust = detectRustCompiler();
|
|
115
|
+
return {
|
|
116
|
+
platform,
|
|
117
|
+
c,
|
|
118
|
+
rust
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/compiler/compileNative.ts
|
|
123
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
124
|
+
import { mkdirSync } from "fs";
|
|
125
|
+
|
|
126
|
+
// src/compiler/buildCommand.ts
|
|
127
|
+
import { basename } from "path";
|
|
128
|
+
|
|
129
|
+
// src/compiler/detectLanguage.ts
|
|
130
|
+
function detectLanguage(filePath) {
|
|
131
|
+
if (filePath.endsWith(".c")) return "c";
|
|
132
|
+
if (filePath.endsWith(".cpp") || filePath.endsWith(".cc") || filePath.endsWith(".cxx"))
|
|
133
|
+
return "cpp";
|
|
134
|
+
if (filePath.endsWith(".rs")) return "rust";
|
|
135
|
+
throw new Error(`Unsupported native file: ${filePath}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/compiler/outputNaming.ts
|
|
139
|
+
function getSharedLibName(baseName, platform) {
|
|
140
|
+
if (platform.isWindows) return `${baseName}.dll`;
|
|
141
|
+
if (platform.isMac) return `lib${baseName}.dylib`;
|
|
142
|
+
return `lib${baseName}.so`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/compiler/buildCommand.ts
|
|
146
|
+
function buildCompileCommand(compiler, platform, request) {
|
|
147
|
+
const language = detectLanguage(request.sourcePath);
|
|
148
|
+
const baseName = basename(request.sourcePath).split(".")[0];
|
|
149
|
+
const outputName = getSharedLibName(baseName, platform);
|
|
150
|
+
const outputPath = `${request.outDir}/${outputName}`;
|
|
151
|
+
const flags = request.flags ?? [];
|
|
152
|
+
let command = [];
|
|
153
|
+
if (language === "c" || language === "cpp") {
|
|
154
|
+
if (compiler.vendor === "msvc") {
|
|
155
|
+
command = [
|
|
156
|
+
request.sourcePath,
|
|
157
|
+
"/LD",
|
|
158
|
+
`/Fe:${outputPath}`,
|
|
159
|
+
...flags
|
|
160
|
+
];
|
|
161
|
+
} else {
|
|
162
|
+
command = [
|
|
163
|
+
request.sourcePath,
|
|
164
|
+
"-shared",
|
|
165
|
+
"-fPIC",
|
|
166
|
+
"-o",
|
|
167
|
+
outputPath,
|
|
168
|
+
...flags
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (language === "rust") {
|
|
173
|
+
command = [
|
|
174
|
+
request.sourcePath,
|
|
175
|
+
"--crate-type",
|
|
176
|
+
"cdylib",
|
|
177
|
+
"-O",
|
|
178
|
+
"-o",
|
|
179
|
+
outputPath,
|
|
180
|
+
...flags
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
language,
|
|
185
|
+
outputPath,
|
|
186
|
+
command
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/compiler/compileNative.ts
|
|
191
|
+
function compileNative(compiler, platform, request) {
|
|
192
|
+
mkdirSync(request.outDir, { recursive: true });
|
|
193
|
+
const result = buildCompileCommand(compiler, platform, request);
|
|
194
|
+
try {
|
|
195
|
+
execFileSync4(
|
|
196
|
+
compiler.kind === "rust" ? compiler.path : compiler.path,
|
|
197
|
+
result.command,
|
|
198
|
+
{ stdio: "inherit" }
|
|
199
|
+
);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Native compilation failed for ${request.sourcePath}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/compiler/compileWithCache.ts
|
|
209
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
210
|
+
|
|
211
|
+
// src/cache/hash.ts
|
|
212
|
+
import { readFileSync } from "fs";
|
|
213
|
+
import crypto from "crypto";
|
|
214
|
+
function computeHash(input) {
|
|
215
|
+
const source = readFileSync(input.sourcePath, "utf8");
|
|
216
|
+
const hash = crypto.createHash("sha256");
|
|
217
|
+
hash.update(source);
|
|
218
|
+
hash.update(input.compiler.path);
|
|
219
|
+
hash.update(input.compiler.version);
|
|
220
|
+
hash.update(input.flags.join(" "));
|
|
221
|
+
hash.update(input.platform);
|
|
222
|
+
return hash.digest("hex");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/cache/cacheManager.ts
|
|
226
|
+
import { existsSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
|
|
227
|
+
import { join as join3 } from "path";
|
|
228
|
+
|
|
229
|
+
// src/cache/cachePaths.ts
|
|
230
|
+
import { homedir } from "os";
|
|
231
|
+
import { join as join2 } from "path";
|
|
232
|
+
function getCacheRoot() {
|
|
233
|
+
return join2(homedir(), ".relaxnative", "cache");
|
|
234
|
+
}
|
|
235
|
+
function getCacheEntry(hash) {
|
|
236
|
+
return join2(getCacheRoot(), hash);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/cache/cacheManager.ts
|
|
240
|
+
function cacheExists(hash) {
|
|
241
|
+
return existsSync(getCacheEntry(hash));
|
|
242
|
+
}
|
|
243
|
+
function loadCacheEntry(hash) {
|
|
244
|
+
const metaPath = join3(getCacheEntry(hash), "meta.json");
|
|
245
|
+
const raw = readFileSync2(metaPath, "utf8");
|
|
246
|
+
return JSON.parse(raw);
|
|
247
|
+
}
|
|
248
|
+
function saveCacheEntry(entry) {
|
|
249
|
+
const dir = getCacheEntry(entry.hash);
|
|
250
|
+
mkdirSync2(dir, { recursive: true });
|
|
251
|
+
writeFileSync2(
|
|
252
|
+
join3(dir, "meta.json"),
|
|
253
|
+
JSON.stringify(entry, null, 2)
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/compiler/compileWithCache.ts
|
|
258
|
+
function compileWithCache(compiler, platform, request) {
|
|
259
|
+
const flags = request.flags ?? [];
|
|
260
|
+
const hash = computeHash({
|
|
261
|
+
sourcePath: request.sourcePath,
|
|
262
|
+
compiler,
|
|
263
|
+
flags,
|
|
264
|
+
platform: `${platform.platform}-${platform.arch}`
|
|
265
|
+
});
|
|
266
|
+
if (cacheExists(hash)) {
|
|
267
|
+
const entry = loadCacheEntry(hash);
|
|
268
|
+
return {
|
|
269
|
+
language: compiler.kind === "rust" ? "rust" : "c",
|
|
270
|
+
outputPath: entry.outputPath,
|
|
271
|
+
command: []
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const cacheDir = getCacheEntry(hash);
|
|
275
|
+
mkdirSync3(cacheDir, { recursive: true });
|
|
276
|
+
const result = compileNative(compiler, platform, {
|
|
277
|
+
...request,
|
|
278
|
+
outDir: cacheDir
|
|
279
|
+
});
|
|
280
|
+
saveCacheEntry({
|
|
281
|
+
hash,
|
|
282
|
+
sourcePath: request.sourcePath,
|
|
283
|
+
outputPath: result.outputPath,
|
|
284
|
+
compilerPath: compiler.path,
|
|
285
|
+
compilerVersion: compiler.version,
|
|
286
|
+
flags,
|
|
287
|
+
platform: `${platform.platform}-${platform.arch}`,
|
|
288
|
+
createdAt: Date.now()
|
|
289
|
+
});
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/dev/hotReload.ts
|
|
294
|
+
import { watch } from "fs";
|
|
295
|
+
import { dirname } from "path";
|
|
296
|
+
|
|
297
|
+
// src/parser/index.ts
|
|
298
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
299
|
+
|
|
300
|
+
// src/parser/loadParser.ts
|
|
301
|
+
import Parser from "tree-sitter";
|
|
302
|
+
import C from "tree-sitter-c";
|
|
303
|
+
import CPP from "tree-sitter-cpp";
|
|
304
|
+
import Rust from "tree-sitter-rust";
|
|
305
|
+
function createParser(lang) {
|
|
306
|
+
const parser = new Parser();
|
|
307
|
+
if (lang === "c") parser.setLanguage(C);
|
|
308
|
+
if (lang === "cpp") parser.setLanguage(CPP);
|
|
309
|
+
if (lang === "rust") parser.setLanguage(Rust);
|
|
310
|
+
return parser;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/parser/parseC.ts
|
|
314
|
+
function parseCFunctions(tree) {
|
|
315
|
+
const functions = [];
|
|
316
|
+
function visit(node) {
|
|
317
|
+
if (node.type === "function_definition") {
|
|
318
|
+
const declarator = node.childForFieldName("declarator");
|
|
319
|
+
const typeNode = node.childForFieldName("type");
|
|
320
|
+
if (!declarator || !typeNode) return;
|
|
321
|
+
const nameNode = declarator.childForFieldName("declarator");
|
|
322
|
+
const name = nameNode?.text ?? declarator.text?.split("(")[0]?.trim();
|
|
323
|
+
if (!name) return;
|
|
324
|
+
const paramsNode = declarator.childForFieldName("parameters");
|
|
325
|
+
const params = paramsNode?.children?.filter((c) => c.type === "parameter_declaration").map((p) => ({
|
|
326
|
+
name: p.childForFieldName("declarator")?.text ?? "arg",
|
|
327
|
+
type: mapCType(
|
|
328
|
+
// For pointer params, tree-sitter C often stores `*` in the declarator,
|
|
329
|
+
// not in the `type` field. Combine both so we can detect pointers.
|
|
330
|
+
`${p.childForFieldName("type")?.text ?? ""}${p.childForFieldName("declarator")?.text ?? ""}`
|
|
331
|
+
)
|
|
332
|
+
})) ?? [];
|
|
333
|
+
functions.push({
|
|
334
|
+
name,
|
|
335
|
+
returnType: mapCType(typeNode.text),
|
|
336
|
+
params,
|
|
337
|
+
sourceLine: node.startPosition.row + 1
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
for (const child of node.children ?? []) visit(child);
|
|
341
|
+
}
|
|
342
|
+
visit(tree.rootNode);
|
|
343
|
+
return functions;
|
|
344
|
+
}
|
|
345
|
+
function mapCType(type) {
|
|
346
|
+
if (!type) return "unknown";
|
|
347
|
+
const t = type.replace(/\s+/g, " ").trim();
|
|
348
|
+
if (/\b(u?int8_t|unsigned\s+char|uint8_t)\b/.test(t) && t.includes("*")) return "buffer";
|
|
349
|
+
if (/\bconst\s+char\s*\*/.test(t)) return "cstring";
|
|
350
|
+
if (/\bunsigned\s+int\b/.test(t) || /\buint32_t\b/.test(t)) return "uint";
|
|
351
|
+
if (/\bsize_t\b/.test(t)) return "size_t";
|
|
352
|
+
if (t.includes("int")) return "int";
|
|
353
|
+
if (t.includes("long")) return "long";
|
|
354
|
+
if (type.includes("float")) return "float";
|
|
355
|
+
if (type.includes("double")) return "double";
|
|
356
|
+
if (t.includes("char") && t.includes("*")) return "buffer";
|
|
357
|
+
if (t.includes("*")) return "pointer";
|
|
358
|
+
if (t.includes("void")) return "void";
|
|
359
|
+
return "unknown";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/parser/parseRust.ts
|
|
363
|
+
function parseRustFunctions(tree) {
|
|
364
|
+
const functions = [];
|
|
365
|
+
function visit(node) {
|
|
366
|
+
if (node.type === "function_item" && node.text.includes("pub extern")) {
|
|
367
|
+
const nameNode = node.childForFieldName("name");
|
|
368
|
+
const paramsNode = node.childForFieldName("parameters");
|
|
369
|
+
const returnNode = node.childForFieldName("return_type");
|
|
370
|
+
const params = paramsNode?.children?.filter((c) => c.type === "parameter").map((p) => ({
|
|
371
|
+
name: p.childForFieldName("pattern")?.text ?? "arg",
|
|
372
|
+
type: mapRustType(p.childForFieldName("type")?.text)
|
|
373
|
+
})) ?? [];
|
|
374
|
+
functions.push({
|
|
375
|
+
name: nameNode?.text ?? "unknown",
|
|
376
|
+
returnType: mapRustType(returnNode?.text),
|
|
377
|
+
params,
|
|
378
|
+
sourceLine: node.startPosition.row + 1
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
for (const child of node.children ?? []) visit(child);
|
|
382
|
+
}
|
|
383
|
+
visit(tree.rootNode);
|
|
384
|
+
return functions;
|
|
385
|
+
}
|
|
386
|
+
function mapRustType(type) {
|
|
387
|
+
if (!type) return "void";
|
|
388
|
+
if (type.includes("i32")) return "int";
|
|
389
|
+
if (type.includes("i64")) return "long";
|
|
390
|
+
if (type.includes("f32")) return "float";
|
|
391
|
+
if (type.includes("f64")) return "double";
|
|
392
|
+
if (type.includes("*const i8")) return "char*";
|
|
393
|
+
if (type.includes("*")) return "pointer";
|
|
394
|
+
return "unknown";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/parser/validateFunctions.ts
|
|
398
|
+
function validateFunctions(funcs) {
|
|
399
|
+
return funcs.filter((fn) => {
|
|
400
|
+
if (fn.returnType === "unknown") return false;
|
|
401
|
+
if (fn.params.some((p) => p.type === "unknown")) return false;
|
|
402
|
+
return true;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/parser/generateBindings.ts
|
|
407
|
+
function generateBindings(functions) {
|
|
408
|
+
const entries = functions.map((f) => [
|
|
409
|
+
f.name,
|
|
410
|
+
{
|
|
411
|
+
name: f.name,
|
|
412
|
+
returns: f.returnType,
|
|
413
|
+
args: f.params.map((p) => p.type),
|
|
414
|
+
// optional execution hints
|
|
415
|
+
mode: f.annotations?.mode,
|
|
416
|
+
cost: f.annotations?.cost
|
|
417
|
+
}
|
|
418
|
+
]);
|
|
419
|
+
return {
|
|
420
|
+
functions: Object.fromEntries(entries)
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/parser/index.ts
|
|
425
|
+
function parseAnnotationsForFunctions(source, funcs) {
|
|
426
|
+
const lines = source.split(/\r?\n/);
|
|
427
|
+
for (const fn of funcs) {
|
|
428
|
+
if (!fn.sourceLine) continue;
|
|
429
|
+
const idx = Math.max(0, fn.sourceLine - 1);
|
|
430
|
+
const start = Math.max(0, idx - 3);
|
|
431
|
+
const window = lines.slice(start, idx).join("\n");
|
|
432
|
+
const mode = /@async\b/.test(window) ? "async" : /@sync\b/.test(window) ? "sync" : void 0;
|
|
433
|
+
const costMatch = window.match(/@cost\s+(low|medium|high)\b/);
|
|
434
|
+
const cost = costMatch?.[1] ?? void 0;
|
|
435
|
+
if (mode || cost) {
|
|
436
|
+
fn.annotations = { ...fn.annotations ?? {}, mode, cost };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return funcs;
|
|
440
|
+
}
|
|
441
|
+
function parseNativeSource(filePath, lang) {
|
|
442
|
+
const source = readFileSync3(filePath, "utf8");
|
|
443
|
+
const parser = createParser(lang);
|
|
444
|
+
const tree = parser.parse(source);
|
|
445
|
+
const rawFunctions = lang === "rust" ? parseRustFunctions(tree) : parseCFunctions(tree);
|
|
446
|
+
const annotated = parseAnnotationsForFunctions(source, rawFunctions);
|
|
447
|
+
const validFunctions = validateFunctions(annotated);
|
|
448
|
+
return generateBindings(validFunctions);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/dev/hotReload.ts
|
|
452
|
+
function log(msg) {
|
|
453
|
+
console.log(msg);
|
|
454
|
+
}
|
|
455
|
+
function fingerprintBindings(bindings) {
|
|
456
|
+
const entries = Object.entries(bindings?.functions ?? {}).map(([k, v]) => {
|
|
457
|
+
return `${k}:${v.returns}(${(v.args ?? []).join(",")})`;
|
|
458
|
+
});
|
|
459
|
+
entries.sort();
|
|
460
|
+
return entries.join("|");
|
|
461
|
+
}
|
|
462
|
+
async function loadNativeDevHot(sourcePath, opts) {
|
|
463
|
+
const enabled = process.env.RELAXNATIVE_DEV === "1";
|
|
464
|
+
if (!enabled) {
|
|
465
|
+
throw new Error("Hot reload is only available when RELAXNATIVE_DEV=1");
|
|
466
|
+
}
|
|
467
|
+
const { c, rust, platform } = detectCompilers();
|
|
468
|
+
const isolation = opts?.isolation ?? "worker";
|
|
469
|
+
let current = null;
|
|
470
|
+
const buildOnce = () => {
|
|
471
|
+
const language = detectLanguage(sourcePath);
|
|
472
|
+
const compiler = language === "rust" ? rust : c;
|
|
473
|
+
if (!compiler) throw new Error(`No compiler for ${language}`);
|
|
474
|
+
const t0 = process.hrtime.bigint();
|
|
475
|
+
const compileRes = compileWithCache(compiler, platform, {
|
|
476
|
+
sourcePath,
|
|
477
|
+
outDir: ".cache/native"
|
|
478
|
+
});
|
|
479
|
+
const bindings = parseNativeSource(sourcePath, language);
|
|
480
|
+
const api = loadFfi(compileRes.outputPath, bindings);
|
|
481
|
+
const mod = wrapFunctions(api, compileRes.outputPath, bindings, { isolation });
|
|
482
|
+
const abi = fingerprintBindings(bindings);
|
|
483
|
+
const t1 = process.hrtime.bigint();
|
|
484
|
+
const durationMs = Number(t1 - t0) / 1e6;
|
|
485
|
+
return { libPath: compileRes.outputPath, bindings, abi, api, mod, durationMs };
|
|
486
|
+
};
|
|
487
|
+
const first = buildOnce();
|
|
488
|
+
current = { ...first, mod: first.mod };
|
|
489
|
+
const stable = new Proxy(
|
|
490
|
+
{},
|
|
491
|
+
{
|
|
492
|
+
get(_t, prop) {
|
|
493
|
+
if (prop === "__isHotReloadProxy") return true;
|
|
494
|
+
if (prop === "then") return void 0;
|
|
495
|
+
const target = current?.mod;
|
|
496
|
+
if (!target) return void 0;
|
|
497
|
+
return target[prop];
|
|
498
|
+
},
|
|
499
|
+
ownKeys() {
|
|
500
|
+
return Reflect.ownKeys(current?.mod ?? {});
|
|
501
|
+
},
|
|
502
|
+
getOwnPropertyDescriptor(_t, prop) {
|
|
503
|
+
return Object.getOwnPropertyDescriptor(current?.mod ?? {}, prop);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
const watcher = watch(dirname(sourcePath), { persistent: false }, (event, filename) => {
|
|
508
|
+
if (!filename) return;
|
|
509
|
+
if (!String(filename).endsWith(sourcePath.split("/").pop() ?? "")) return;
|
|
510
|
+
try {
|
|
511
|
+
const next = buildOnce();
|
|
512
|
+
if (current && next.abi !== current.abi) {
|
|
513
|
+
log(`[relaxnative] ABI change detected in ${sourcePath}; reload requires restart`);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
current = { ...next, mod: next.mod };
|
|
517
|
+
const ms = next.durationMs;
|
|
518
|
+
log(`[relaxnative] Recompiled ${sourcePath} (${ms.toFixed(0)}ms)`);
|
|
519
|
+
opts?.onReload?.({ sourcePath, durationMs: ms });
|
|
520
|
+
} catch (err) {
|
|
521
|
+
log(`[relaxnative] Hot reload failed for ${sourcePath}: ${err?.message ?? String(err)}`);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
return {
|
|
525
|
+
mod: stable,
|
|
526
|
+
close() {
|
|
527
|
+
watcher.close();
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/loader.ts
|
|
533
|
+
async function buildNative(sourcePath, options) {
|
|
534
|
+
const { c, rust, platform } = detectCompilers();
|
|
535
|
+
const language = detectLanguage(sourcePath);
|
|
536
|
+
const compiler = language === "rust" ? rust : c;
|
|
537
|
+
if (!compiler) {
|
|
538
|
+
throw new Error(`No compiler for ${language}`);
|
|
539
|
+
}
|
|
540
|
+
const compileResult = compileWithCache(compiler, platform, {
|
|
541
|
+
sourcePath,
|
|
542
|
+
outDir: ".cache/native"
|
|
543
|
+
});
|
|
544
|
+
const bindings = parseNativeSource(sourcePath, language);
|
|
545
|
+
const config = options?.config;
|
|
546
|
+
if (config?.functionMode) {
|
|
547
|
+
for (const [name, mode] of Object.entries(config.functionMode)) {
|
|
548
|
+
if (bindings.functions?.[name]) {
|
|
549
|
+
bindings.functions[name] = {
|
|
550
|
+
...bindings.functions[name],
|
|
551
|
+
mode
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (config?.defaultMode) {
|
|
557
|
+
for (const [name, binding] of Object.entries(bindings.functions ?? {})) {
|
|
558
|
+
if (!binding.mode) {
|
|
559
|
+
bindings.functions[name] = { ...binding, mode: config.defaultMode };
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
globalThis.__bindings = bindings;
|
|
564
|
+
const api = loadFfi(compileResult.outputPath, bindings);
|
|
565
|
+
return { libPath: compileResult.outputPath, bindings, api };
|
|
566
|
+
}
|
|
567
|
+
async function loadNative(sourcePath, options) {
|
|
568
|
+
if (process.env.RELAXNATIVE_DEV === "1") {
|
|
569
|
+
if (options?.config) {
|
|
570
|
+
throw new Error("RELAXNATIVE_DEV=1: loadNative() does not support config overrides yet");
|
|
571
|
+
}
|
|
572
|
+
const handle = await loadNativeDevHot(sourcePath, { isolation: options?.isolation });
|
|
573
|
+
return handle.mod;
|
|
574
|
+
}
|
|
575
|
+
const { libPath, bindings, api } = await buildNative(sourcePath, options);
|
|
576
|
+
const isolation = options?.isolation ?? "worker";
|
|
577
|
+
return wrapFunctions(api, libPath, bindings, { isolation });
|
|
578
|
+
}
|
|
579
|
+
async function loadNativeWithBindings(sourcePath, options) {
|
|
580
|
+
const { libPath, bindings, api } = await buildNative(sourcePath, options);
|
|
581
|
+
if (options?.mutateBindings) {
|
|
582
|
+
options.mutateBindings(bindings);
|
|
583
|
+
}
|
|
584
|
+
const isolation = options?.isolation ?? "worker";
|
|
585
|
+
const mod = wrapFunctions(api, libPath, bindings, { isolation });
|
|
586
|
+
return { mod, bindings };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export {
|
|
590
|
+
detectCompilers,
|
|
591
|
+
detectLanguage,
|
|
592
|
+
compileNative,
|
|
593
|
+
compileWithCache,
|
|
594
|
+
parseNativeSource,
|
|
595
|
+
loadNativeDevHot,
|
|
596
|
+
loadNative,
|
|
597
|
+
loadNativeWithBindings
|
|
598
|
+
};
|