diff --git a/package.json b/package.json index 14fcbc1..39cf412 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,16 @@ "url": "https://github.com/tr1ckydev/hyperimport/issues" }, "homepage": "https://github.com/tr1ckydev/hyperimport#readme", + "dependencies": { + "arch": "^2.2.0", + "tree-sitter": "^0.20.5", + "tree-sitter-rust": "^0.20.4" + }, "devDependencies": { "bun-types": "latest" - } -} \ No newline at end of file + }, + "trustedDependencies": [ + "tree-sitter", + "tree-sitter-rust" + ] +} diff --git a/preload.ts b/preload.ts index e0d9a9a..2640e20 100644 --- a/preload.ts +++ b/preload.ts @@ -7,7 +7,7 @@ const config: HyperImportConfig = (await import(`${cwd}/bunfig.toml`)).default.h debugLog(config.debug, 3, "registering loaders..."); for (const loader of config.loaders ?? []) { - await import(`./src/loaders/${loader}.ts`).then(async l => { + await import(`./src/loaders/${loader}`).then(async l => { const plugin = await new l.default(cwd).toPlugin(); Bun.plugin(plugin); debugLog(config.debug, 2, plugin.name, "has been registered."); diff --git a/src/loader.ts b/src/loader.ts index e59ef3f..15e4f4a 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -46,8 +46,12 @@ export default class { Bun.write(`${this.cwd}/@types/${filename}/lastModified`, lastModified(this.config.importPath)); const configWriter = Bun.file(`${this.cwd}/@types/${filename}/config.ts`).writer(); configWriter.write(`import { LoaderConfig, T } from "hyperimport";\nexport default {\n\tbuildCommand: ${JSON.stringify(this.config.buildCommand)},\n\toutDir: "${this.config.outDir}",\n\tsymbols: {`); - for (const symbol of nm(this.config.libPath)) { - configWriter.write(`\n\t\t${symbol}: {\n\t\t\targs: [],\n\t\t\treturns: T.void\n\t\t},`); + const symbols = nm(this.config.libPath); + const types = this._config.parseTypes?.(this.config.importPath, symbols); + for (const symbol of symbols) { + const type = types?.[symbol] ?? { args: [], returns: "T.void" }; + const args = type.args.join(", "); + configWriter.write(`\n\t\t${symbol}: {\n\t\t\targs: [${args}],\n\t\t\treturns: ${type.returns}\n\t\t},`); } configWriter.write(`\n\t}\n} satisfies LoaderConfig.Main;`); configWriter.end(); @@ -77,6 +81,7 @@ export default class { const lmfile = `${this.cwd}/@types/${basename(this.config.importPath)}/lastModified`; if (lm !== await Bun.file(lmfile).text()) { await this.build(); + await this.initConfigTypes(); Bun.write(lmfile, lm); } } diff --git a/src/loaders/rs.ts b/src/loaders/rs.ts deleted file mode 100644 index d7b82a0..0000000 --- a/src/loaders/rs.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { basename } from "path"; -import Loader from "../loader"; - -export default class extends Loader { - constructor() { - super("Rust Loader", - { - extension: "rs", - buildCommand: (importPath, outDir) => [ - "rustc", - "--crate-type", - "cdylib", - importPath, - "--out-dir", - outDir - ], - outDir: importPath => `build/${basename(importPath)}` - } - ); - } -} \ No newline at end of file diff --git a/src/loaders/rs/index.ts b/src/loaders/rs/index.ts new file mode 100644 index 0000000..514ab8a --- /dev/null +++ b/src/loaders/rs/index.ts @@ -0,0 +1,37 @@ +import { basename, join } from "path"; +import Loader from "../../loader"; + +export default class extends Loader { + constructor() { + super("Rust Loader", + { + extension: "rs", + buildCommand: (importPath, outDir) => [ + "rustc", + "--crate-type", + "cdylib", + importPath, + "--out-dir", + outDir + ], + outDir: importPath => `build/${basename(importPath)}`, + parseTypes: (importPath, targets) => { + // Use Node to run tree-sitter due to Bun issue: + // https://github.com/oven-sh/bun/issues/4188 + const types = Bun.spawnSync([ + "node", + join(import.meta.dir, "parse.js"), + importPath, + ...targets + ]).stdout.toString(); + try { + const result = JSON.parse(types); + return result; + } catch(e) { + return undefined; + } + } + } + ); + } +} diff --git a/src/loaders/rs/parse.js b/src/loaders/rs/parse.js new file mode 100644 index 0000000..19455a7 --- /dev/null +++ b/src/loaders/rs/parse.js @@ -0,0 +1,73 @@ +const fs = require("fs"); +const arch = require("arch"); +const Parser = require("tree-sitter"); +const Rust = require("tree-sitter-rust"); + +const path = process.argv[2]; +const targets = process.argv.slice(3); + +const parser = new Parser(); +parser.setLanguage(Rust); + +const source = fs.readFileSync(path, "utf8"); +const tree = parser.parse(source); + +const size = arch() === "x64" ? "64" : "32"; + +const typeMap = { + "()": "T.void", + "bool": "T.bool", + "u8": "T.u8", + "u16": "T.u16", + "u32": "T.u32", + "u64": "T.u64", + "i8": "T.i8", + "i16": "T.i16", + "i32": "T.i32", + "i64": "T.i64", + "f32": "T.f32", + "f64": "T.f64", + "usize": `T.u${size}`, + "isize": `T.i${size}`, + "char": "T.u32" +}; + +function mapType(type) { + if(typeMap[type]) { + return typeMap[type]; + } + return type; +} + +const types = tree.rootNode.children.reduce((acc, node) => { + if(node.type === "function_item") { + const fnNameNode = node.children.find(child => child.type === "identifier"); + if(fnNameNode && targets.includes(fnNameNode.text)) { + const parameters = node.children.find(child => child.type === "parameters"); + const parameterTypes = parameters.children + .filter((child) => child.type === "parameter") + .map((child) => { + const primitiveType = child.children.find((child) => child.type === "primitive_type"); + if(primitiveType) { + return mapType(primitiveType.text); + } + const pointerType = child.children.find((child) => child.type === "pointer_type"); + if(pointerType) { + return "T.ptr"; + } + const functionType = child.children.find((child) => child.type === "function_type"); + if(functionType) { + return "T.function"; + } + }); + const returnType = node.children.find(child => child.type === "primitive_type" || child.type === "unit_type").text; + acc[fnNameNode.text] = { + args: parameterTypes, + returns: mapType(returnType) + }; + } + } + return acc; +}, {}); + +process.stdout.write(JSON.stringify(types)); diff --git a/src/types.ts b/src/types.ts index d33c43a..38ab6e7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,6 +20,7 @@ export namespace LoaderConfig { extension: string, buildCommand?: (importPath: string, outDir: string) => string[], outDir?: (importPath: string) => string, + parseTypes?: (importPath: string, targets: string[]) => Record | undefined, } export interface Internal {