diff --git a/.rustfmt.toml b/.rustfmt.toml index 8712501..9bb8d9d 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,3 +1,3 @@ max_width = 80 tab_spaces = 2 -edition = "2018" +edition = "2021" diff --git a/Cargo.lock b/Cargo.lock index 6423973..673f2d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,65 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "clap" +version = "3.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1132dc3944b31c20dd8b906b3a9f0a5d0243e092d59171414969657ac6aa85" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "deno_bindgen" version = "0.4.1" @@ -30,23 +89,73 @@ dependencies = [ "serde_json", ] +[[package]] +name = "deno_bindgen_cli" +version = "0.1.0" +dependencies = [ + "clap", + "deno_bindgen_codegen", + "serde_json", +] + +[[package]] +name = "deno_bindgen_codegen" +version = "0.1.0" +dependencies = [ + "Inflector", + "anyhow", + "indexmap", + "serde", +] + [[package]] name = "deno_bindgen_macro" version = "0.4.1" dependencies = [ "Inflector", + "deno_bindgen_codegen", + "lazy_static", "proc-macro2", "quote", - "serde", - "serde_json", "syn", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "itoa" -version = "0.4.7" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "lazy_static" @@ -54,26 +163,65 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" + [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] @@ -97,24 +245,24 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "serde" -version = "1.0.127" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.127" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -123,28 +271,86 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.66" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" -version = "1.0.74" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" + [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 1cce5e4..61fd048 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] members = [ - "deno_bindgen_macro", + "cli", + "codegen", "deno_bindgen", + "macro" ] exclude = ["example/"] - diff --git a/cli.ts b/cli.ts deleted file mode 100644 index a2ab356..0000000 --- a/cli.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -import { ensureDir } from "https://deno.land/std@0.105.0/fs/ensure_dir.ts"; -import { parse } from "https://deno.land/std@0.105.0/flags/mod.ts"; -import { codegen } from "./codegen.ts"; - -const flags = parse(Deno.args, { "--": true }); -const release = !!flags.release; - -const fetchPrefix = typeof flags.release == "string" - ? flags.release - : "../target/" + (release ? "release" : "debug"); - -async function build() { - const cmd = ["cargo", "build"]; - if (release) cmd.push("--release"); - cmd.push(...flags["--"]); - const proc = Deno.run({ cmd }); - await proc.status(); -} - -let source = null; -async function generate() { - let conf; - try { - conf = JSON.parse(await Deno.readTextFile("bindings.json")); - } catch (_) { - // Nothing to update. - return; - } - - const pkgName = conf.name; - - source = "// Auto-generated with deno_bindgen\n"; - source += codegen( - fetchPrefix, - pkgName, - conf.typeDefs, - conf.tsTypes, - conf.symbols, - { - le: conf.littleEndian, - release, - }, - ); - - await Deno.remove("bindings.json"); -} - -try { - await Deno.remove("bindings.json"); -} catch (e) { - // no op -} - -await build().catch((_) => Deno.removeSync("bindings.json")); -await generate(); - -if (source != null) { - await ensureDir("bindings"); - await Deno.writeTextFile("bindings/bindings.ts", source); -} diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..7d8ac0a --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "deno_bindgen_cli" +version = "0.1.0" +description = "This tool aims to simplify glue code generation for Deno FFI libraries" +license = "MIT" +edition = "2021" + +[[bin]] +name = "dbindgen" +path = "main.rs" + +[dependencies] +clap = { version = "3.0.14", features = ["derive"] } +serde_json = "1.0.78" +deno_bindgen_codegen = { path = "../codegen", features = ["serde"] } diff --git a/cli/main.rs b/cli/main.rs new file mode 100644 index 0000000..044402b --- /dev/null +++ b/cli/main.rs @@ -0,0 +1,41 @@ +use std::{ + fs::{File, OpenOptions}, + io::{self, Read, Write}, +}; + +use clap::Parser; +use deno_bindgen_codegen::{library::Library, serde::SerdeLibrary}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + input: String, + output: Option, +} + +fn main() { + let args = Args::parse(); + let mut json = String::new(); + + File::open(args.input) + .expect("Could not open input file") + .read_to_string(&mut json) + .expect("Could not read input file"); + + let mut library: Library = serde_json::from_str::(&json) + .expect("Could not deserialize json") + .into(); + let source = library.generate().expect("Could not generate source"); + + if let Some(output) = args.output { + OpenOptions::new() + .write(true) + .create(true) + .open(&output) + .expect("Could not open output file") + .write(source.read().as_bytes()) + .expect("Could not write output file"); + } else { + println!("{}", source.read()); + } +} diff --git a/codegen.ts b/codegen.ts deleted file mode 100644 index 0d67e43..0000000 --- a/codegen.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -import { - createFromBuffer, - GlobalConfiguration, -} from "https://deno.land/x/dprint/mod.ts"; -import * as Cache from "https://deno.land/x/cache@0.2.13/mod.ts"; - -Cache.configure({ directory: Cache.options.directory }); -const cache = Cache.namespace("deno_bindgen_cli"); - -const globalConfig: GlobalConfiguration = { - indentWidth: 2, - lineWidth: 80, -}; - -const file = await cache.cache( - "https://plugins.dprint.dev/typescript-0.57.0.wasm", -); - -const tsFormatter = createFromBuffer(await Deno.readFile(file.path)); - -tsFormatter.setConfig(globalConfig, { - semiColons: "asi", -}); - -const Type: Record = { - void: "null", - i8: "number", - u8: "number", - i16: "number", - u16: "number", - i32: "number", - u32: "number", - i64: "number", - u64: "number", - usize: "number", - isize: "number", - f32: "number", - f64: "number", -}; - -const BufferTypes: Record = { - str: "string", - buffer: "Uint8Array", - buffermut: "Uint8Array", - ptr: "Uint8Array", -}; - -enum Encoder { - JsonStringify = "JSON.stringify", - None = "", -} - -const BufferTypeEncoders: Record = { - str: Encoder.None, - buffer: Encoder.None, - buffermut: Encoder.None, - ptr: Encoder.None, -}; - -type TypeDef = Record>; - -function resolveType(typeDefs: TypeDef, type: any): string { - const t = typeof type == "string" ? type : type.structenum.ident; - if (Type[t] !== undefined) return Type[t]; - if (BufferTypes[t] !== undefined) return BufferTypes[t]; - if (Object.keys(typeDefs).find((f) => f == t) !== undefined) { - return t; - } - return "any"; -} - -function resolveDlopenParameter(typeDefs: TypeDef, type: any): string { - const t = typeof type == "string" ? type : type.structenum.ident; - if (Type[t] !== undefined) return t; - if ( - BufferTypes[t] !== undefined || - Object.keys(typeDefs).find((f) => f == t) !== undefined - ) { - return "pointer"; - } - throw new TypeError(`Type not supported: ${t}`); -} - -type Sig = Record; - -type Options = { - le?: boolean; - release?: boolean; -}; - -function isTypeDef(p: any) { - return typeof p !== "string"; -} - -function isBufferType(p: any) { - return isTypeDef(p) || BufferTypes[p] !== undefined; -} - -// @littledivy is a dumb kid! -// he just can't make an interface -// for bindings.json -export function codegen( - fetchPrefix: string, - name: string, - decl: TypeDef, - typescript: Record, - signature: Sig, - options?: Options, -) { - signature = Object.keys(signature) - .sort() - .reduce((acc, key) => ({ - ...acc, - [key]: signature[key], - }), {}); - typescript = Object.keys(typescript) - .sort() - .reduce((acc, key) => ({ - ...acc, - [key]: typescript[key], - }), {}); - - return tsFormatter.formatText( - "bindings.ts", - `import { CachePolicy, prepare } from "https://deno.land/x/plug@0.4.1/plug.ts"; -function encode(v: string | Uint8Array): Uint8Array { - if (typeof v !== "string") return v; - return new TextEncoder().encode(v); -} -function decode(v: Uint8Array): string { - return new TextDecoder().decode(v); -} -function read_pointer(v: any): Uint8Array { - const ptr = new Deno.UnsafePointerView(v as Deno.UnsafePointer) - const lengthBe = new Uint8Array(4); - const view = new DataView(lengthBe.buffer); - ptr.copyInto(lengthBe, 0); - const buf = new Uint8Array(view.getUint32(0)); - ptr.copyInto(buf, 4) - return buf -} -const opts = { - name: "${name}", - url: (new URL("${fetchPrefix}", import.meta.url)).toString(), - policy: ${!!options?.release ? "undefined" : "CachePolicy.NONE"}, -}; -const _lib = await prepare(opts, { - ${ - Object.keys(signature).map((sig) => - `${sig}: { parameters: [ ${ - signature[sig].parameters.map((p) => { - const ffiParam = resolveDlopenParameter(decl, p); - // FIXME: Dupe logic here. - return `"${ffiParam}"${isBufferType(p) ? `, "usize"` : ""}`; - }) - .join(", ") - } ], result: "${ - resolveDlopenParameter(decl, signature[sig].result) - }", nonblocking: ${String(!!signature[sig].nonBlocking)} }` - ).join(", ") - } }); -${Object.keys(decl).map((def) => typescript[def]).join("\n")} -${ - Object.keys(signature).map((sig) => { - const { parameters, result, nonBlocking } = signature[sig]; - - return `export function ${sig}(${ - parameters.map((p, i) => `a${i}: ${resolveType(decl, p)}`).join(",") - }) { - ${ - parameters.map((p, i) => - isBufferType(p) - ? `const a${i}_buf = encode(${ - BufferTypeEncoders[p] ?? Encoder.JsonStringify - }(a${i}));` - : null - ).filter((c) => c !== null).join("\n") - } - let result = _lib.symbols.${sig}(${ - parameters.map((p, i) => - isBufferType(p) ? `a${i}_buf, a${i}_buf.byteLength` : `a${i}` - ).join(", ") - }) as ${ - nonBlocking - ? `Promise<${ - isTypeDef(result) - ? "Uint8Array" - : resolveType(decl, result) - }>` - : isTypeDef(result) - ? "Uint8Array" - : resolveType(decl, result) - } - ${ - isBufferType(result) - ? nonBlocking - ? `result = result.then(read_pointer)` - : `result = read_pointer(result)` - : "" - }; - ${ - isTypeDef(result) - ? nonBlocking - ? `return result.then(r => JSON.parse(decode(r))) as Promise<${ - resolveType(decl, result) - }>` - : `return JSON.parse(decode(result)) as ${ - resolveType(decl, result) - }` - : "return result" - }; -}`; - }).join("\n") - } - `, - ); -} diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000..17a510c --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "deno_bindgen_codegen" +version = "0.1.0" +description = "This tool aims to simplify glue code generation for Deno FFI libraries" +license = "MIT" +edition = "2021" + +[dependencies] +anyhow = "1.0.52" +Inflector = "0.11.4" +indexmap = "1.8.0" +serde = { version = "1.0", features = ["derive"], optional = true } + +[features] +default = [] diff --git a/codegen/src/error.rs b/codegen/src/error.rs new file mode 100644 index 0000000..aa4c57a --- /dev/null +++ b/codegen/src/error.rs @@ -0,0 +1,8 @@ +use anyhow::anyhow; +use anyhow::Error; + +pub type AnyError = anyhow::Error; + +pub fn unknown_type(name: &str) -> Error { + anyhow!("Unknown type: {}", name) +} diff --git a/codegen/src/function.rs b/codegen/src/function.rs new file mode 100644 index 0000000..fdfd059 --- /dev/null +++ b/codegen/src/function.rs @@ -0,0 +1,163 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::fmt::Write; + +use crate::error::AnyError; +use crate::library::Library; +use crate::library::LibraryElement; +use crate::source::Source; +use crate::types::TypeDescriptor; + +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(untagged) +)] +pub enum FunctionParameters { + Unnamed(Vec), + Named(Vec<(String, String)>), +} + +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize) +)] +pub struct Function { + symbol: String, + name: String, + docs: Option, + parameters: Vec<(String, String)>, + result: String, + nonblocking: bool, + export: bool, +} + +impl Function { + pub fn new( + symbol: &str, + name: Option<&str>, + docs: Option<&str>, + parameters: FunctionParameters, + result: &str, + nonblocking: bool, + export: bool, + ) -> Self { + let name = name.unwrap_or(symbol).to_string(); + let symbol = symbol.to_string(); + let parameters = match parameters { + FunctionParameters::Named(parameters) => parameters, + FunctionParameters::Unnamed(parameters) => { + let mut named = Vec::new(); + for (index, parameter) in parameters.into_iter().enumerate() { + named.push((format!("parameter{}", index), parameter)); + } + named + } + }; + + Self { + symbol, + name, + docs: docs.map(String::from), + parameters, + result: result.to_string(), + nonblocking, + export, + } + } +} + +impl LibraryElement for Function { + fn symbol(&self, library: &Library) -> Result, AnyError> { + let parameters = self + .parameters + .iter() + .map(|(_, parameter)| -> Result { + let parameter = library.lookup_type(parameter)?; + let native = String::from(parameter.native); + Ok(format!("\"{}\"", native)) + }) + .collect::, AnyError>>()? + .join(", "); + + let result = String::from(library.lookup_type(&self.result)?.native); + + Ok(Some(format!( + "{}: {{ parameters: [{}], result: \"{}\", nonblocking: {} }}", + self.symbol, parameters, result, self.nonblocking + ))) + } + + fn generate( + &self, + library: &Library, + source: &mut Source, + ) -> Result<(), AnyError> { + let result = library.lookup_type(&self.result)?; + let parameters = self + .parameters + .iter() + .map(|(name, parameter)| Ok((name, library.lookup_type(parameter)?))) + .collect::, AnyError>>()?; + + if let Some(docs) = &self.docs { + writeln!(source, "{}", docs)?; + } + + if self.export { + write!(source, "export ")?; + } + + if self.nonblocking { + write!(source, "async ")?; + } + + write!(source, "function {}(", self.name)?; + + write!( + source, + "{}", + parameters + .iter() + .map(|(name, parameter)| format!( + "{}: {}", + name, parameter.converter.typescript + )) + .collect::>() + .join(", ") + )?; + + if self.nonblocking { + writeln!(source, "): Promise<{}> {{", result.converter.typescript)?; + } else { + writeln!(source, "): {} {{", result.converter.typescript)?; + } + + writeln!( + source, + "{}{};", + if result.returns() { "return " } else { "" }, + result.converter.from.replace( + "{}", + &format!( + "{}.symbols.{}({})", + library.variable, + self.symbol, + parameters + .iter() + .map(|(name, parameter)| parameter + .converter + .into + .replace("{}", name)) + .collect::>() + .join(", ") + ) + ) + )?; + + writeln!(source, "}}")?; + + Ok(()) + } +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000..6c8aa37 --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,9 @@ +pub mod error; +pub mod function; +pub mod library; +pub mod loader; +pub mod source; +pub mod types; + +#[cfg(feature = "serde")] +pub mod serde; diff --git a/codegen/src/library.rs b/codegen/src/library.rs new file mode 100644 index 0000000..bef112e --- /dev/null +++ b/codegen/src/library.rs @@ -0,0 +1,211 @@ +use std::fmt::Write; +use std::iter::FromIterator; + +use indexmap::{IndexMap, IndexSet}; + +use crate::error::{unknown_type, AnyError}; +use crate::source::Source; +use crate::types::{TypeDefinition, TypeDescriptor}; + +pub struct Library { + pub variable: String, + pub types: IndexMap, + pub loader: Box, + pub elements: Vec>, +} + +impl Library { + pub fn new(variable: Option<&str>, loader: Box) -> Self { + Self { + variable: variable.unwrap_or("library").to_string(), + types: IndexMap::new(), + loader, + elements: Vec::new(), + } + } + + pub fn register_type(&mut self, name: &str, definiton: TypeDefinition) { + self.types.insert(name.to_string(), definiton.into()); + } + + pub fn lookup_type(&self, name: &str) -> Result<&TypeDescriptor, AnyError> { + self.types.get(name).ok_or_else(|| unknown_type(name)) + } + + pub fn prepend(&mut self, element: Box) { + self.elements.insert(0, element) + } + + pub fn append(&mut self, element: Box) { + self.elements.push(element) + } + + pub fn symbols(&self) -> Result { + let symbols = self + .elements + .iter() + .filter_map(|element| element.symbol(self).transpose()) + .collect::, AnyError>>()? + .join(", "); + Ok(format!("{{ {} }}", symbols)) + } + + pub fn generate(&mut self) -> Result { + let mut source = Source::new(); + let globals: IndexSet = IndexSet::from_iter( + self + .types + .values() + .map(|descriptor| descriptor.converter.globals.clone()) + .flatten(), + ); + + self.loader.generate(self, &mut source)?; + + for global in globals { + global.generate(self, &mut source)?; + } + + for element in &self.elements { + element.generate(self, &mut source)?; + } + + Ok(source) + } +} + +pub trait LibraryElement { + fn generate( + &self, + library: &Library, + source: &mut Source, + ) -> Result<(), AnyError>; + + fn symbol(&self, _library: &Library) -> Result, AnyError> { + Ok(None) + } +} + +impl LibraryElement for String { + fn generate(&self, _: &Library, source: &mut Source) -> Result<(), AnyError> { + source.write_str(self)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::function::Function; + use crate::function::FunctionParameters; + use crate::library::Library; + use crate::loader::plug::PlugLoader; + use crate::loader::plug::PlugLoaderOptions; + use crate::loader::plug::PlugLoaderSingleOptions; + use crate::types::buffer::Buffer; + use crate::types::pointer::Pointer; + use crate::types::primitive::Primitive; + use crate::types::r#struct::Struct; + use crate::types::tuple::Tuple; + use crate::types::BufferType; + use crate::types::NativeType; + use crate::types::TypeDefinition; + + #[test] + fn testing() { + let mut library = Library::new( + None, + Box::new(PlugLoader::new( + true, + None, + PlugLoaderOptions::Single(PlugLoaderSingleOptions { + name: "test".to_string(), + url: "abcdef".to_string(), + policy: None, + cache: None, + log: None, + }), + )), + ); + + library.register_type( + "usize", + TypeDefinition::Primitive(Primitive::new(NativeType::USize)), + ); + library.register_type("cstring", TypeDefinition::CString); + library.register_type( + "ExampleStruct", + TypeDefinition::Struct(Struct::new( + Some("ExampleStruct"), + true, + vec![ + ( + "a".to_string(), + TypeDefinition::Pointer(Pointer::new(Box::new( + TypeDefinition::Primitive(Primitive::new(NativeType::U16)), + ))), + ), + ("b".to_string(), TypeDefinition::CString), + ( + "c".to_string(), + TypeDefinition::Primitive(Primitive::new(NativeType::Pointer)), + ), + ( + "d".to_string(), + TypeDefinition::Primitive(Primitive::new(NativeType::I8)), + ), + ( + "f".to_string(), + TypeDefinition::Buffer(Buffer::new(BufferType::I64, 13)), + ), + ( + "g".to_string(), + TypeDefinition::Struct(Struct::new( + Some("ExampleInnerStruct"), + false, + vec![ + ( + "inner_a".to_string(), + TypeDefinition::Primitive(Primitive::new(NativeType::U32)), + ), + ( + "inner_b".to_string(), + TypeDefinition::Primitive(Primitive::new( + NativeType::Pointer, + )), + ), + ], + )), + ), + ], + )), + ); + + library.register_type( + "TestTuple", + TypeDefinition::Tuple(Tuple::new( + Some("TestTuple"), + true, + vec![ + TypeDefinition::Pointer(Pointer::new(Box::new( + TypeDefinition::Primitive(Primitive::new(NativeType::U16)), + ))), + TypeDefinition::CString, + TypeDefinition::Primitive(Primitive::new(NativeType::Pointer)), + ], + )), + ); + + library.append(Box::new(Function::new( + "test", + None, + None, + FunctionParameters::Unnamed(vec!["usize".to_string()]), + "ExampleStruct", + false, + true, + ))); + + let source = library.generate().unwrap(); + println!("{}", source.read()); + } +} diff --git a/codegen/src/loader/deno.rs b/codegen/src/loader/deno.rs new file mode 100644 index 0000000..0b861d4 --- /dev/null +++ b/codegen/src/loader/deno.rs @@ -0,0 +1,50 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::fmt::Write; + +use crate::error::AnyError; +use crate::library::Library; +use crate::library::LibraryElement; +use crate::source::Source; + +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize) +)] +pub struct DenoLoader { + pub filename: String, + #[cfg_attr(feature = "serde", serde(default))] + pub export: bool, +} + +impl DenoLoader { + pub fn new(export: bool, filename: &str) -> Self { + Self { + export, + filename: filename.to_string(), + } + } +} + +impl LibraryElement for DenoLoader { + fn generate( + &self, + library: &Library, + source: &mut Source, + ) -> Result<(), AnyError> { + if self.export { + write!(source, "export ")?; + } + + writeln!( + source, + "const {} = await Plug.prepare(\"{}\", {});", + library.variable, + self.filename, + library.symbols()? + )?; + + Ok(()) + } +} diff --git a/codegen/src/loader/mod.rs b/codegen/src/loader/mod.rs new file mode 100644 index 0000000..1fb037c --- /dev/null +++ b/codegen/src/loader/mod.rs @@ -0,0 +1,2 @@ +pub mod deno; +pub mod plug; diff --git a/codegen/src/loader/plug.rs b/codegen/src/loader/plug.rs new file mode 100644 index 0000000..c966f0a --- /dev/null +++ b/codegen/src/loader/plug.rs @@ -0,0 +1,224 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::fmt::Write; + +use crate::error::AnyError; +use crate::library::Library; +use crate::library::LibraryElement; +use crate::source::Source; + +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))] +pub enum PlugLoaderOptions { + Single(PlugLoaderSingleOptions), + Cross(PlugLoaderCrossOptions), + Url(PlugLoaderUrlOptions), +} + +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PlugLoaderUrls { + pub darwin: Option, + pub linux: Option, + pub windows: Option, +} + +#[derive(Clone)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(rename_all = "UPPERCASE") +)] +pub enum PlugLoaderCachePolicy { + None, + Store, +} + +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PlugLoaderCrossOptions { + pub name: String, + pub urls: PlugLoaderUrls, + pub policy: Option, + pub cache: Option, + pub log: Option, +} + +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PlugLoaderUrlOptions { + pub url: String, + pub policy: Option, + pub cache: Option, + pub log: Option, +} + +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PlugLoaderSingleOptions { + pub name: String, + pub url: String, + pub policy: Option, + pub cache: Option, + pub log: Option, +} + +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PlugLoader { + export: bool, + #[cfg_attr(feature = "serde", serde(default = "default_import"))] + import: String, + options: PlugLoaderOptions, +} + +fn default_import() -> String { + "https://deno.land/x/plug/mod.ts".to_string() +} + +impl PlugLoader { + pub fn new( + export: bool, + import: Option<&str>, + options: PlugLoaderOptions, + ) -> Self { + Self { + export, + import: import.unwrap_or(&default_import()).to_string(), + options, + } + } +} + +impl LibraryElement for PlugLoader { + fn generate( + &self, + library: &Library, + source: &mut Source, + ) -> Result<(), AnyError> { + writeln!(source, "import {{ Plug }} from \"{}\";", self.import)?; + + if self.export { + write!(source, "export ")?; + } + + writeln!( + source, + "const {} = await Plug.prepare({}, {});", + library.variable, + String::from(self.options.clone()), + library.symbols()? + )?; + + Ok(()) + } +} + +impl From for String { + fn from(options: PlugLoaderOptions) -> Self { + match options { + PlugLoaderOptions::Single(options) => String::from(options), + PlugLoaderOptions::Cross(options) => String::from(options), + PlugLoaderOptions::Url(options) => String::from(options), + } + } +} + +impl From for String { + fn from(options: PlugLoaderCrossOptions) -> Self { + let mut properties = Vec::new(); + + properties.push(format!("name: \"{}\"", options.name)); + properties.push(format!("urls: {}", String::from(options.urls))); + + if let Some(policy) = options.policy { + properties.push(format!("policy: {}", String::from(policy))); + } + + if let Some(cache) = options.cache { + properties.push(format!("cache: {}", cache)); + } + + if let Some(log) = options.log { + properties.push(format!("log: {}", log)); + } + + format!("{{ {} }}", properties.join(", ")) + } +} + +impl From for String { + fn from(options: PlugLoaderUrlOptions) -> Self { + let mut properties = Vec::new(); + + properties.push(format!("url: \"{}\"", options.url)); + + if let Some(policy) = options.policy { + properties.push(format!("policy: {}", String::from(policy))); + } + + if let Some(cache) = options.cache { + properties.push(format!("cache: {}", cache)); + } + + if let Some(log) = options.log { + properties.push(format!("log: {}", log)); + } + + format!("{{ {} }}", properties.join(", ")) + } +} + +impl From for String { + fn from(options: PlugLoaderSingleOptions) -> Self { + let mut properties = Vec::new(); + + properties.push(format!("name: \"{}\"", options.name)); + properties.push(format!("url: \"{}\"", options.url)); + + if let Some(policy) = options.policy { + properties.push(format!("policy: {}", String::from(policy))); + } + + if let Some(cache) = options.cache { + properties.push(format!("cache: {}", cache)); + } + + if let Some(log) = options.log { + properties.push(format!("log: {}", log)); + } + + format!("{{ {} }}", properties.join(", ")) + } +} + +impl From for String { + fn from(urls: PlugLoaderUrls) -> Self { + let mut properties = Vec::new(); + + if let Some(darwin) = urls.darwin { + properties.push(format!("darwin: \"{}\"", darwin)); + } + + if let Some(linux) = urls.linux { + properties.push(format!("linux: \"{}\"", linux)); + } + + if let Some(windows) = urls.windows { + properties.push(format!("windows: \"{}\"", windows)); + } + + format!("{{ {} }}", properties.join(", ")) + } +} + +impl From for String { + fn from(cache_policy: PlugLoaderCachePolicy) -> Self { + match cache_policy { + PlugLoaderCachePolicy::None => "Plug.CachePolicy.NONE", + PlugLoaderCachePolicy::Store => "Plug.CachePolicy.STORE", + } + .to_string() + } +} diff --git a/codegen/src/serde.rs b/codegen/src/serde.rs new file mode 100644 index 0000000..db64905 --- /dev/null +++ b/codegen/src/serde.rs @@ -0,0 +1,66 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use crate::{ + function::{Function, FunctionParameters}, + library::Library, + loader::{deno::DenoLoader, plug::PlugLoader}, + types::TypeDefinition, +}; + +#[derive(Serialize, Deserialize)] +pub struct SerdeLibrary { + pub variable: Option, + pub loader: SerdeLoader, + pub types: HashMap, + pub functions: HashMap, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum SerdeLoader { + Deno(DenoLoader), + Plug(PlugLoader), +} + +#[derive(Serialize, Deserialize)] +pub struct SerdeFunction { + name: Option, + docs: Option, + parameters: FunctionParameters, + result: String, + nonblocking: bool, + export: bool, +} + +#[cfg(feature = "serde")] +impl From for Library { + fn from(serde_library: SerdeLibrary) -> Self { + let mut library = Library::new( + serde_library.variable.as_deref(), + match serde_library.loader { + SerdeLoader::Deno(loader) => Box::new(loader), + SerdeLoader::Plug(loader) => Box::new(loader), + }, + ); + + for (name, definition) in serde_library.types { + library.register_type(&name, definition) + } + + for (symbol, function) in serde_library.functions { + library.append(Box::new(Function::new( + &symbol, + function.name.as_deref(), + function.docs.as_deref(), + function.parameters, + &function.result, + function.nonblocking, + function.export, + ))); + } + + library + } +} diff --git a/codegen/src/source.rs b/codegen/src/source.rs new file mode 100644 index 0000000..a057516 --- /dev/null +++ b/codegen/src/source.rs @@ -0,0 +1,36 @@ +use std::fmt::write; +use std::fmt::Arguments; +use std::fmt::Result; +use std::fmt::Write; + +pub struct Source(String); + +impl Source { + pub fn new() -> Self { + Self(String::new()) + } + + pub fn read(&self) -> String { + self.0.clone() + } +} + +impl Default for Source { + fn default() -> Self { + Self::new() + } +} + +impl Write for Source { + fn write_str(&mut self, s: &str) -> Result { + Write::write_str(&mut self.0, s) + } + + fn write_char(&mut self, c: char) -> Result { + self.write_str(c.encode_utf8(&mut [0; 4])) + } + + fn write_fmt(mut self: &mut Self, args: Arguments<'_>) -> Result { + write(&mut self, args) + } +} diff --git a/codegen/src/types/buffer.rs b/codegen/src/types/buffer.rs new file mode 100644 index 0000000..b68801b --- /dev/null +++ b/codegen/src/types/buffer.rs @@ -0,0 +1,56 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::BufferType; +use super::NativeType; +use super::TypeConverter; +use super::TypeDescriptor; + +#[derive(Clone, Hash)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize) +)] +pub struct Buffer { + #[cfg_attr(feature = "serde", serde(rename = "type"))] + pub ty: BufferType, + pub length: usize, +} + +impl Buffer { + pub fn new(ty: BufferType, length: usize) -> Self { + Self { ty, length } + } +} + +impl From for TypeDescriptor { + fn from(buffer: Buffer) -> Self { + let converter = if let BufferType::None = buffer.ty { + TypeConverter { + globals: Vec::new(), + typescript: "ArrayBuffer".to_string(), + into: format!("new Uint8Array({{}}, {})", buffer.length), + from: format!("{{}}.getArrayBuffer({})", buffer.length), + } + } else { + let constructor = buffer.ty.typed_array(); + TypeConverter { + globals: Vec::new(), + typescript: constructor.to_string(), + into: format!( + "Deno.UnsafePointer.of(new {}({{}}.buffer, {}))", + constructor, buffer.length + ), + from: format!( + "new {}(new Deno.UnsafePointerView({{}}).getArrayBuffer({}))", + constructor, buffer.length + ), + } + }; + + TypeDescriptor { + native: NativeType::Pointer, + converter, + } + } +} diff --git a/codegen/src/types/cstring.rs b/codegen/src/types/cstring.rs new file mode 100644 index 0000000..943650d --- /dev/null +++ b/codegen/src/types/cstring.rs @@ -0,0 +1,23 @@ +use super::NativeType; +use super::TypeConverter; +use super::TypeDescriptor; + +#[derive(Clone)] +pub struct CString; + +impl From for TypeDescriptor { + fn from(_: CString) -> Self { + TypeDescriptor { + native: NativeType::Pointer, + converter: TypeConverter { + globals: vec![ + "const __cstring_encoder = new TextEncoder();\n".to_string() + ], + typescript: "string".to_string(), + into: "Deno.UnsafePointer.of(__cstring_encoder.encode({} + \"\\0\"))" + .to_string(), + from: "new Deno.UnsafePointerView({}).getCString()".to_string(), + }, + } + } +} diff --git a/codegen/src/types/enum.rs b/codegen/src/types/enum.rs new file mode 100644 index 0000000..da0b6eb --- /dev/null +++ b/codegen/src/types/enum.rs @@ -0,0 +1,144 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; + +use inflector::Inflector; + +use super::NativeType; +use super::TypeConverter; +use super::TypeDefinition; +use super::TypeDescriptor; + +fn hashed_variants_identifer( + fields: &[(String, Option)], +) -> String { + let mut hasher = DefaultHasher::new(); + fields.hash(&mut hasher); + format!("{:x}", hasher.finish()) +} + +#[derive(Clone, Hash)] +pub enum EnumDiscriminantType { + U8, + I8, + U16, + I16, + U32, + I32, + U64, + I64, + USize, + ISize, +} + +#[derive(Clone, Hash)] +pub struct Enum { + pub identifier: String, + pub anonymous: bool, + pub discriminant: EnumDiscriminantType, + pub variants: Vec<(String, Option)>, +} + +impl Enum { + pub fn new( + identifier: Option<&str>, + discriminant: EnumDiscriminantType, + variants: Vec<(String, Option)>, + ) -> Self { + Self { + identifier: identifier + .map(String::from) + .unwrap_or_else(|| hashed_variants_identifer(&variants)), + anonymous: identifier.is_none(), + discriminant, + variants, + } + } + + pub fn typescript_type(&self) -> String { + self + .variants() + .iter() + .map(|(property, ty)| { + if let Some((definition, descriptor)) = ty { + format!( + "{{ tag: {}, value: {} }}", + property, descriptor.converter.typescript + ) + } else { + format!("{{ tag: {} }}", property) + } + }) + .collect::>() + .join(" | ") + } + + pub fn variants( + &self, + ) -> Vec<(String, Option<(TypeDefinition, TypeDescriptor)>)> { + self + .variants + .clone() + .into_iter() + .map(|(property, definition)| { + ( + property, + definition.map(|definition| { + (definition.clone(), TypeDescriptor::from(definition)) + }), + ) + }) + .collect() + } + + pub fn typescript(&self) -> String { + if self.anonymous { + self.typescript_type() + } else { + self.identifier.to_pascal_case() + } + } + + pub fn into_function_name(&self) -> String { + format!("__into_{}", self.identifier) + } + + pub fn from_function_name(&self) -> String { + format!("__from_{}", self.identifier) + } + + pub fn generate_into_function(&self, globals: &mut Vec) { + todo!() + } + + pub fn generate_from_function(&self, globals: &mut Vec) { + todo!() + } +} + +impl From for TypeDescriptor { + fn from(r#enum: Enum) -> Self { + let mut globals = Vec::new(); + + if !r#enum.anonymous { + globals.push(format!( + "export type {} = {}", + r#enum.typescript(), + r#enum.typescript_type() + )); + } + + r#enum.generate_into_function(&mut globals); + r#enum.generate_from_function(&mut globals); + + TypeDescriptor { + native: NativeType::Pointer, + converter: TypeConverter { + globals, + typescript: r#enum.typescript(), + into: format!("{}({{}})", r#enum.into_function_name()), + from: format!("{}({{}})", r#enum.from_function_name()), + }, + } + } +} diff --git a/codegen/src/types/mod.rs b/codegen/src/types/mod.rs new file mode 100644 index 0000000..1ded9d2 --- /dev/null +++ b/codegen/src/types/mod.rs @@ -0,0 +1,339 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use self::buffer::Buffer; +use self::cstring::CString; +use self::pointer::Pointer; +use self::primitive::Primitive; +// use self::r#enum::Enum; +use self::r#struct::Struct; +use self::tuple::Tuple; + +pub mod buffer; +pub mod cstring; +// pub mod r#enum; +pub mod pointer; +pub mod primitive; +pub mod r#struct; +pub mod tuple; + +fn calculate_padding(offset: usize, alignment: usize) -> usize { + let misalignment = offset % alignment; + if misalignment > 0 { + alignment - misalignment + } else { + 0 + } +} + +#[derive(Clone, Copy, Hash)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(rename_all = "lowercase") +)] +pub enum NativeType { + Void, + U8, + I8, + U16, + I16, + U32, + I32, + U64, + I64, + USize, + ISize, + F32, + F64, + Pointer, +} + +impl NativeType { + fn data_view_getter(&self) -> String { + match self { + NativeType::U8 => "getUint8", + NativeType::I8 => "getInt8", + NativeType::U16 => "getUint16", + NativeType::I16 => "getInt16", + NativeType::U32 => "getUint32", + NativeType::I32 => "getInt32", + NativeType::Pointer | NativeType::USize | NativeType::U64 => { + "getBigUint64" + } + NativeType::ISize | NativeType::I64 => "getBigInt64", + NativeType::F32 => "getFloat32", + NativeType::F64 => "getFloat64", + _ => panic!(), + } + .to_string() + } + + fn data_view_setter(&self) -> String { + match self { + NativeType::U8 => "setUint8", + NativeType::I8 => "setInt8", + NativeType::U16 => "setUint16", + NativeType::I16 => "setInt16", + NativeType::U32 => "setUint32", + NativeType::I32 => "setInt32", + NativeType::Pointer | NativeType::USize | NativeType::U64 => { + "setBigUint64" + } + NativeType::ISize | NativeType::I64 => "setBigInt64", + NativeType::F32 => "setFloat32", + NativeType::F64 => "setFloat64", + _ => panic!(), + } + .to_string() + } +} + +#[derive(Clone, Copy, Hash)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(rename_all = "lowercase") +)] +pub enum BufferType { + None, + U8, + I8, + U16, + I16, + U32, + I32, + U64, + I64, + USize, + ISize, + F32, + F64, +} + +impl BufferType { + fn typed_array(&self) -> String { + match self { + BufferType::None => "ArrayBuffer", + BufferType::U8 => "Uint8Array", + BufferType::I8 => "Int8Array", + BufferType::U16 => "Uint16Array", + BufferType::I16 => "Int16Array", + BufferType::U32 => "Uint32Array", + BufferType::I32 => "Int32Array", + BufferType::U64 | BufferType::USize => "BigUint64Array", + BufferType::I64 | BufferType::ISize => "BigInt64Array", + BufferType::F32 => "Float32Array", + BufferType::F64 => "Float64Array", + } + .to_string() + } + + fn pointer_view_getter(&self) -> String { + match self { + BufferType::U8 => "getUint8", + BufferType::I8 => "getInt8", + BufferType::U16 => "getUint16", + BufferType::I16 => "getInt16", + BufferType::U32 => "getUint32", + BufferType::I32 => "getInt32", + BufferType::USize | BufferType::U64 => "getBigUint64", + BufferType::ISize | BufferType::I64 => "getBigInt64", + BufferType::F32 => "getFloat32", + BufferType::F64 => "getFloat64", + _ => panic!(), + } + .to_string() + } +} + +#[derive(Clone, Hash)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(rename_all = "lowercase") +)] +pub enum TypeDefinition { + Primitive(Primitive), + Pointer(Pointer), + Buffer(Buffer), + CString, + Struct(Struct), + Tuple(Tuple), + // Enum(Enum), + // Array(Vec), +} + +impl TypeDefinition { + fn size_of(&self) -> usize { + match self { + TypeDefinition::Primitive(primitive) => match primitive.native { + NativeType::Void => panic!(), + NativeType::U8 | NativeType::I8 => 1, + NativeType::U16 | NativeType::I16 => 2, + NativeType::U32 | NativeType::I32 | NativeType::F32 => 4, + NativeType::U64 + | NativeType::I64 + | NativeType::USize + | NativeType::ISize + | NativeType::F64 + | NativeType::Pointer => 8, + }, + TypeDefinition::Pointer(_) => 8, + TypeDefinition::Buffer(buffer) => match buffer.ty { + BufferType::None | BufferType::U8 | BufferType::I8 => buffer.length, + BufferType::U16 | BufferType::I16 => buffer.length * 2, + BufferType::U32 | BufferType::I32 | BufferType::F32 => { + buffer.length * 4 + } + BufferType::U64 + | BufferType::I64 + | BufferType::USize + | BufferType::ISize + | BufferType::F64 => buffer.length * 8, + }, + TypeDefinition::CString => 8, + TypeDefinition::Struct(r#struct) => { + let mut offset = 0; + for (_, definition, _) in r#struct.fields() { + offset += calculate_padding(offset, definition.align_of()) + + definition.size_of(); + } + offset + calculate_padding(offset, self.align_of()) + } + TypeDefinition::Tuple(tuple) => { + let mut offset = 0; + for (definition, _) in tuple.fields() { + offset += calculate_padding(offset, definition.align_of()) + + definition.size_of(); + } + offset + calculate_padding(offset, self.align_of()) + } + } + } + + fn align_of(&self) -> usize { + match self { + TypeDefinition::Primitive(primitive) => match primitive.native { + NativeType::Void => panic!(), + NativeType::U8 | NativeType::I8 => 1, + NativeType::U16 | NativeType::I16 => 2, + NativeType::U32 | NativeType::I32 | NativeType::F32 => 4, + NativeType::U64 + | NativeType::I64 + | NativeType::USize + | NativeType::ISize + | NativeType::F64 + | NativeType::Pointer => 8, + }, + TypeDefinition::Pointer(_) => 8, + TypeDefinition::Buffer(buffer) => match buffer.ty { + BufferType::None | BufferType::U8 | BufferType::I8 => 1, + BufferType::U16 | BufferType::I16 => 2, + BufferType::U32 | BufferType::I32 | BufferType::F32 => 4, + BufferType::U64 + | BufferType::I64 + | BufferType::USize + | BufferType::ISize + | BufferType::F64 => 8, + }, + TypeDefinition::CString => 8, + TypeDefinition::Struct(r#struct) => r#struct + .fields() + .iter() + .map(|(_, definition, _)| definition.align_of()) + .max() + .unwrap_or(0), + TypeDefinition::Tuple(tuple) => tuple + .fields() + .iter() + .map(|(definition, _)| definition.align_of()) + .max() + .unwrap_or(0), + } + } +} + +impl From for TypeDescriptor { + fn from(definition: TypeDefinition) -> Self { + match definition { + TypeDefinition::Primitive(primitive) => TypeDescriptor::from(primitive), + TypeDefinition::Pointer(pointer) => TypeDescriptor::from(pointer), + TypeDefinition::Buffer(buffer) => TypeDescriptor::from(buffer), + TypeDefinition::CString => TypeDescriptor::from(CString), + TypeDefinition::Struct(r#struct) => TypeDescriptor::from(r#struct), + TypeDefinition::Tuple(tuple) => TypeDescriptor::from(tuple), + } + } +} + +#[derive(Clone)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize) +)] +pub struct TypeDescriptor { + pub native: NativeType, + pub converter: TypeConverter, +} + +impl TypeDescriptor { + pub fn returns(&self) -> bool { + !matches!(self.native, NativeType::Void) + } +} + +#[derive(Clone)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize) +)] +pub struct TypeConverter { + pub globals: Vec, + pub typescript: String, + pub into: String, + pub from: String, +} + +impl From for BufferType { + fn from(native_type: NativeType) -> Self { + match native_type { + NativeType::U8 => BufferType::U8, + NativeType::I8 => BufferType::I8, + NativeType::U16 => BufferType::U16, + NativeType::I16 => BufferType::I16, + NativeType::U32 => BufferType::U32, + NativeType::I32 => BufferType::I32, + NativeType::U64 => BufferType::U64, + NativeType::I64 => BufferType::I64, + NativeType::USize => BufferType::USize, + NativeType::ISize => BufferType::ISize, + NativeType::F32 => BufferType::F32, + NativeType::F64 => BufferType::F64, + NativeType::Pointer => BufferType::U64, + _ => BufferType::None, + } + } +} + +impl From for String { + fn from(native_type: NativeType) -> Self { + match native_type { + NativeType::Void => "void", + NativeType::U8 => "u8", + NativeType::I8 => "i8", + NativeType::U16 => "u16", + NativeType::I16 => "i16", + NativeType::U32 => "u32", + NativeType::I32 => "i32", + NativeType::U64 => "u64", + NativeType::I64 => "i64", + NativeType::USize => "usize", + NativeType::ISize => "isize", + NativeType::F32 => "f32", + NativeType::F64 => "f64", + NativeType::Pointer => "pointer", + } + .to_string() + } +} diff --git a/codegen/src/types/pointer.rs b/codegen/src/types/pointer.rs new file mode 100644 index 0000000..dc0f92d --- /dev/null +++ b/codegen/src/types/pointer.rs @@ -0,0 +1,81 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::BufferType; +use super::NativeType; +use super::TypeConverter; +use super::TypeDefinition; +use super::TypeDescriptor; + +#[derive(Clone, Hash)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize) +)] +pub struct Pointer { + pub target: Box, +} + +impl Pointer { + pub fn new(target: Box) -> Self { + Self { target } + } +} + +impl From for TypeDescriptor { + fn from(pointer: Pointer) -> Self { + let target = pointer.target.as_ref(); + let target_descriptor: TypeDescriptor = target.clone().into(); + let converter = if let TypeDefinition::Primitive(primitive) = target { + let native = primitive.native; + + if let NativeType::Pointer = native { + TypeConverter { + globals: target_descriptor.converter.globals, + typescript: target_descriptor.converter.typescript, + into: format!( + "Deno.UnsafePointer.of(new BigUint64Array([{}.value]))", + target_descriptor.converter.into + ), + from: "{}".to_string(), + } + } else { + let buffer_type: BufferType = native.into(); + let constructor = buffer_type.typed_array(); + let getter = buffer_type.pointer_view_getter(); + + TypeConverter { + globals: target_descriptor.converter.globals, + typescript: target_descriptor.converter.typescript, + into: format!( + "Deno.UnsafePointer.of(new {}([{}]))", + constructor, target_descriptor.converter.into + ), + from: if let BufferType::None = buffer_type { + "/* ? */".to_string() + } else { + format!("new Deno.UnsafePointerView({{}}).{}(0)", getter) + }, + } + } + } else { + TypeConverter { + globals: target_descriptor.converter.globals, + typescript: target_descriptor.converter.typescript, + into: format!( + "Deno.UnsafePointer.of(new BigUint64Array([{}.value]))", + target_descriptor.converter.into + ), + from: format!( + "new Deno.UnsafePointer(new Deno.UnsafePointerView({}).getBigUint64())", + target_descriptor.converter.from + ), + } + }; + + TypeDescriptor { + native: NativeType::Pointer, + converter, + } + } +} diff --git a/codegen/src/types/primitive.rs b/codegen/src/types/primitive.rs new file mode 100644 index 0000000..cbc5531 --- /dev/null +++ b/codegen/src/types/primitive.rs @@ -0,0 +1,58 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::NativeType; +use super::TypeConverter; +use super::TypeDescriptor; + +#[derive(Clone, Hash)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize) +)] +pub struct Primitive { + pub native: NativeType, +} + +impl Primitive { + pub fn new(native: NativeType) -> Self { + Self { native } + } +} + +impl From for TypeDescriptor { + fn from(primitive: Primitive) -> Self { + let typescript = match primitive.native { + NativeType::Void => "void", + NativeType::U8 + | NativeType::I8 + | NativeType::U16 + | NativeType::I16 + | NativeType::U32 + | NativeType::I32 + | NativeType::F32 + | NativeType::F64 => "number", + NativeType::U64 + | NativeType::I64 + | NativeType::USize + | NativeType::ISize => "bigint", + NativeType::Pointer => "Deno.UnsafePointer", + }; + let converter = TypeConverter { + globals: Vec::new(), + typescript: typescript.to_string(), + into: "{}".to_string(), + from: if let NativeType::Pointer = primitive.native { + "new Deno.UnsafePointer({})" + } else { + "{}" + } + .to_string(), + }; + + TypeDescriptor { + native: primitive.native, + converter, + } + } +} diff --git a/codegen/src/types/struct.rs b/codegen/src/types/struct.rs new file mode 100644 index 0000000..e0f857c --- /dev/null +++ b/codegen/src/types/struct.rs @@ -0,0 +1,298 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::collections::hash_map::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; + +use inflector::Inflector; + +use super::calculate_padding; +use super::BufferType; +use super::NativeType; +use super::TypeConverter; +use super::TypeDefinition; +use super::TypeDescriptor; + +fn hashed_fields_identifer(fields: &[(String, TypeDefinition)]) -> String { + let mut hasher = DefaultHasher::new(); + fields.hash(&mut hasher); + format!("{:x}", hasher.finish()) +} + +fn default_padded() -> bool { + true +} + +#[derive(Clone, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Struct { + pub identifier: Option, + #[cfg_attr(feature = "serde", serde(default = "default_padded"))] + pub padded: bool, + pub fields: Vec<(String, TypeDefinition)>, +} + +impl Struct { + pub fn new( + identifier: Option<&str>, + padded: bool, + fields: Vec<(String, TypeDefinition)>, + ) -> Self { + Self { + identifier: identifier.map(String::from), + padded, + fields, + } + } + + pub fn identifier(&self) -> String { + if let Some(identifier) = &self.identifier { + identifier.clone() + } else { + hashed_fields_identifer(&self.fields) + } + } + + pub fn typescript_type(&self) -> String { + format!( + "{{\n{}\n}}", + self + .fields() + .iter() + .map(|(property, _, descriptor)| { + format!("{}: {};", property, descriptor.converter.typescript) + }) + .collect::>() + .join("\n") + ) + } + + pub fn typescript(&self) -> String { + if self.identifier.is_none() { + self.typescript_type() + } else { + self.identifier().to_pascal_case() + } + } + + pub fn into_function_name(&self) -> String { + format!("__into_{}", self.identifier()) + } + + pub fn from_function_name(&self) -> String { + format!("__from_{}", self.identifier()) + } + + pub fn fields(&self) -> Vec<(String, TypeDefinition, TypeDescriptor)> { + self + .fields + .clone() + .into_iter() + .map(|(property, definition)| { + ( + property, + definition.clone(), + TypeDescriptor::from(definition), + ) + }) + .collect() + } +} + +impl From for TypeConverter { + fn from(r#struct: Struct) -> Self { + let mut globals = Vec::new(); + let typescript = r#struct.typescript(); + + if r#struct.identifier.is_some() { + globals.push(format!( + "export interface {} {}", + typescript, + r#struct.typescript_type() + )); + } + + let mut into_body = Vec::new(); + let mut properties = Vec::new(); + + let mut offset = 0; + let align = r#struct + .fields() + .iter() + .map(|(_, definition, _)| definition.align_of()) + .max() + .unwrap_or(0); + + for (property, definition, mut descriptor) in r#struct.fields() { + if r#struct.padded { + offset += calculate_padding(offset, definition.align_of()); + } + + globals.append(&mut descriptor.converter.globals); + + let accessor = format!("__data.{}", property); + + match definition { + TypeDefinition::Primitive(ref primitive) => { + properties.push(( + property, + descriptor.converter.from.replace( + "{}", + &format!( + "__data_view.{}({})", + primitive.native.data_view_getter(), + offset + ), + ), + )); + + into_body.push(format!( + "__data_view.{}({}, {});", + primitive.native.data_view_setter(), + offset, + descriptor.converter.into.replace( + "{}", + &format!( + "{}{}", + accessor, + if let NativeType::Pointer = primitive.native { + ".value" + } else { + "" + } + ) + ) + )); + } + TypeDefinition::CString | TypeDefinition::Pointer(_) => { + properties.push(( + property, + descriptor.converter.from.replace( + "{}", + &format!( + "new Deno.UnsafePointer(__data_view.getBigUint64({}))", + offset + ), + ), + )); + + into_body.push(format!( + "__data_view.setBigUint64({}, {}.value);", + offset, + descriptor.converter.into.replace("{}", &accessor) + )); + } + TypeDefinition::Buffer(ref buffer) => { + let source_buffer = format!( + "__array_buffer.slice({}, {})", + offset, + offset + definition.size_of() + ); + + properties.push(( + property, + if let BufferType::None = buffer.ty { + source_buffer + } else { + format!("new {}({})", buffer.ty.typed_array(), source_buffer) + }, + )); + + into_body.push(format!( + "__u8_array.set({}, {});", + if let BufferType::None = buffer.ty { + format!("new Uint8Array({})", accessor) + } else if let BufferType::U8 = buffer.ty { + accessor + } else { + format!("new Uint8Array({}.buffer)", accessor) + }, + offset + )); + } + TypeDefinition::Tuple(_) | TypeDefinition::Struct(_) => { + properties.push(( + property, + descriptor.converter.from.replace( + "{}", + &format!( + "__array_buffer.slice({}, {})", + offset, + offset + definition.size_of() + ), + ), + )); + + into_body.push(format!( + "__u8_array.set(new Uint8Array({}.buffer), {});", + descriptor.converter.into.replace("{}", &accessor), + offset, + )); + } + } + + offset += definition.size_of(); + } + + let size = offset + + if r#struct.padded && offset != 0 { + calculate_padding(offset, align) + } else { + 0 + }; + + // from function + globals.push(format!( + "function {}(__source: ArrayBuffer | Uint8Array | Deno.UnsafePointer | Deno.UnsafePointerView): {} {{\n\ + const __array_buffer =\n\ + (__source instanceof ArrayBuffer\n\ + ? __source\n\ + : __source instanceof Uint8Array\n\ + ? __source.buffer\n\ + : __source instanceof Deno.UnsafePointer\n\ + ? new Deno.UnsafePointerView(__source).getArrayBuffer({size})\n\ + : __source instanceof Deno.UnsafePointerView\n\ + ? __source.getArrayBuffer({size})\n\ + : undefined)!;\n\ + const __data_view = new DataView(__array_buffer);\n\ + return {{\n{}\n}};\n\ + }}", + r#struct.from_function_name(), + typescript, + properties.iter().map(|(property, value)| format!(" {}: {}", property, value)).collect::>().join(",\n"), + size = size, + )); + + // into function + globals.push(format!( + "function {}(__data: {}): Uint8Array {{\n\ + const __array_buffer = new ArrayBuffer({});\n\ + const __u8_array = new Uint8Array(__array_buffer); + const __data_view = new DataView(__array_buffer);\n\ + {}\n\ + return __u8_array;\n\ + }}", + r#struct.into_function_name(), + typescript, + size, + into_body.join("\n") + )); + + TypeConverter { + globals, + typescript, + into: format!("{}({{}})", r#struct.into_function_name()), + from: format!("{}({{}})", r#struct.from_function_name()), + } + } +} + +impl From for TypeDescriptor { + fn from(r#struct: Struct) -> Self { + TypeDescriptor { + native: NativeType::Pointer, + converter: r#struct.into(), + } + } +} diff --git a/codegen/src/types/tuple.rs b/codegen/src/types/tuple.rs new file mode 100644 index 0000000..da75944 --- /dev/null +++ b/codegen/src/types/tuple.rs @@ -0,0 +1,274 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::collections::hash_map::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; + +use inflector::Inflector; + +use super::calculate_padding; +use super::BufferType; +use super::NativeType; +use super::TypeConverter; +use super::TypeDefinition; +use super::TypeDescriptor; + +fn hashed_fields_identifer(fields: &[TypeDefinition]) -> String { + let mut hasher = DefaultHasher::new(); + fields.hash(&mut hasher); + format!("{:x}", hasher.finish()) +} + +#[derive(Clone, Hash)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize) +)] +pub struct Tuple { + pub identifier: String, + pub anonymous: bool, + pub padded: bool, + pub fields: Vec, +} + +impl Tuple { + pub fn new( + identifier: Option<&str>, + padded: bool, + fields: Vec, + ) -> Self { + Self { + identifier: identifier + .map(String::from) + .unwrap_or_else(|| hashed_fields_identifer(&fields)), + anonymous: identifier.is_none(), + padded, + fields, + } + } + + pub fn typescript_type(&self) -> String { + format!( + "[{}]", + self + .fields() + .iter() + .map(|(_, descriptor)| { descriptor.converter.typescript.clone() }) + .collect::>() + .join(", ") + ) + } + + pub fn typescript(&self) -> String { + if self.anonymous { + self.typescript_type() + } else { + self.identifier.to_pascal_case() + } + } + + pub fn into_function_name(&self) -> String { + format!("__into_{}", self.identifier) + } + + pub fn from_function_name(&self) -> String { + format!("__from_{}", self.identifier) + } + + pub fn fields(&self) -> Vec<(TypeDefinition, TypeDescriptor)> { + self + .fields + .clone() + .into_iter() + .map(|definition| (definition.clone(), TypeDescriptor::from(definition))) + .collect() + } +} + +impl From for TypeConverter { + fn from(tuple: Tuple) -> Self { + let mut globals = Vec::new(); + let typescript = tuple.typescript(); + + if !tuple.anonymous { + globals.push(format!( + "export type {} = {};", + typescript, + tuple.typescript_type() + )); + } + + let mut into_body = Vec::new(); + let mut properties = Vec::new(); + + let mut offset = 0; + let align = tuple + .fields() + .iter() + .map(|(definition, _)| definition.align_of()) + .max() + .unwrap_or(0); + + for (field, (definition, mut descriptor)) in + tuple.fields().into_iter().enumerate() + { + if tuple.padded { + offset += calculate_padding(offset, definition.align_of()); + } + + globals.append(&mut descriptor.converter.globals); + + let accessor = format!("__data[{}]", field); + + match definition { + TypeDefinition::Primitive(ref primitive) => { + properties.push(descriptor.converter.from.replace( + "{}", + &format!( + "__data_view.{}({})", + primitive.native.data_view_getter(), + offset + ), + )); + + into_body.push(format!( + "__data_view.{}({}, {});", + primitive.native.data_view_setter(), + offset, + descriptor.converter.into.replace( + "{}", + &format!( + "{}{}", + accessor, + if let NativeType::Pointer = primitive.native { + ".value" + } else { + "" + } + ) + ) + )); + } + TypeDefinition::CString | TypeDefinition::Pointer(_) => { + properties.push(descriptor.converter.from.replace( + "{}", + &format!( + "new Deno.UnsafePointer(__data_view.getBigUint64({}))", + offset + ), + )); + + into_body.push(format!( + "__data_view.setBigUint64({}, {}.value);", + offset, + descriptor.converter.into.replace("{}", &accessor) + )); + } + TypeDefinition::Buffer(ref buffer) => { + let source_buffer = format!( + "__array_buffer.slice({}, {})", + offset, + offset + definition.size_of() + ); + + properties.push(if let BufferType::None = buffer.ty { + source_buffer + } else { + format!("new {}({})", buffer.ty.typed_array(), source_buffer) + }); + + into_body.push(format!( + "__u8_array.set({}, {});", + if let BufferType::None = buffer.ty { + format!("new Uint8Array({})", accessor) + } else if let BufferType::U8 = buffer.ty { + accessor + } else { + format!("new Uint8Array({}.buffer)", accessor) + }, + offset + )); + } + TypeDefinition::Tuple(_) | TypeDefinition::Struct(_) => { + properties.push(descriptor.converter.from.replace( + "{}", + &format!( + "__array_buffer.slice({}, {})", + offset, + offset + definition.size_of() + ), + )); + + into_body.push(format!( + "__u8_array.set(new Uint8Array({}.buffer), {});", + descriptor.converter.into.replace("{}", &accessor), + offset, + )); + } + } + + offset += definition.size_of(); + } + + let size = offset + + if tuple.padded { + calculate_padding(offset, align) + } else { + 0 + }; + + // from function + globals.push(format!( + "function {}(__source: ArrayBuffer | Uint8Array | Deno.UnsafePointer | Deno.UnsafePointerView): {} {{\n\ + const __array_buffer =\n\ + (__source instanceof ArrayBuffer\n\ + ? __source\n\ + : __source instanceof Uint8Array\n\ + ? __source.buffer\n\ + : __source instanceof Deno.UnsafePointer\n\ + ? new Deno.UnsafePointerView(__source).getArrayBuffer({size})\n\ + : __source instanceof Deno.UnsafePointerView\n\ + ? __source.getArrayBuffer({size})\n\ + : undefined)!;\n\ + const __data_view = new DataView(__array_buffer);\n\ + return [\n{}\n];\n\ + }}", + tuple.from_function_name(), + typescript, + properties.join(",\n"), + size = size, + )); + + // into function + globals.push(format!( + "function {}(__data: {}): Uint8Array {{\n\ + const __array_buffer = new ArrayBuffer({});\n\ + const __u8_array = new Uint8Array(__array_buffer); + const __data_view = new DataView(__array_buffer);\n\ + {}\n\ + return __u8_array;\n\ + }}", + tuple.into_function_name(), + typescript, + size, + into_body.join("\n") + )); + + TypeConverter { + globals, + typescript, + into: format!("{}({{}})", tuple.into_function_name()), + from: format!("{}({{}})", tuple.from_function_name()), + } + } +} + +impl From for TypeDescriptor { + fn from(tuple: Tuple) -> Self { + TypeDescriptor { + native: NativeType::Pointer, + converter: tuple.into(), + } + } +} diff --git a/deno_bindgen/Cargo.toml b/deno_bindgen/Cargo.toml index 48b551f..c7123ae 100644 --- a/deno_bindgen/Cargo.toml +++ b/deno_bindgen/Cargo.toml @@ -9,13 +9,13 @@ keywords = ["deno", "ffi", "bindgen", "bindings", "macro"] categories = ["development-tools::ffi", "development-tools"] readme = "../README.md" license = "MIT" -edition = "2018" +edition = "2021" [lib] path = "./lib.rs" [dependencies] -deno_bindgen_macro = { path = "../deno_bindgen_macro", version = "0.4.1" } +deno_bindgen_macro = { path = "../macro", version = "0.4.1" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs deleted file mode 100644 index adfb48b..0000000 --- a/deno_bindgen_macro/src/lib.rs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -use proc_macro::TokenStream; -use quote::format_ident; -use quote::quote; -use std::env; -use std::fs::OpenOptions; -use std::io::Read; -use std::io::Write; -use syn::parse_macro_input; -use syn::parse_quote; -use syn::ItemFn; - -mod attrs; -mod derive_fn; -mod derive_struct; -mod docs; -mod meta; - -use crate::derive_fn::process_function; -use crate::derive_struct::process_struct; -use crate::meta::Glue; -use crate::meta::Type; - -const METAFILE: &str = "bindings.json"; - -#[cfg(target_endian = "little")] -const ENDIANNESS: bool = true; - -#[cfg(target_endian = "big")] -const ENDIANNESS: bool = false; - -#[proc_macro_attribute] -pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { - let mut metadata: Glue = match OpenOptions::new().read(true).open(METAFILE) { - Ok(mut fd) => { - let mut meta = String::new(); - fd.read_to_string(&mut meta) - .expect("Error reading meta file"); - - serde_json::from_str(&meta).unwrap_or_default() - } - Err(_) => Glue { - little_endian: ENDIANNESS, - name: env::var("CARGO_CRATE_NAME").unwrap_or_default(), - ..Default::default() - }, - }; - - let mut metafile = OpenOptions::new() - .write(true) - .create(true) - .open(METAFILE) - .expect("Error opening meta file"); - - match syn::parse::(input.clone()) { - Ok(func) => { - let attr = parse_macro_input!(attr as syn::AttributeArgs); - let symbol = process_function(func.clone(), attr, &mut metadata).unwrap(); - - let mut params = vec![]; - let mut overrides = vec![]; - let mut input_idents = vec![]; - let mut c_index = 0; - - for parameter in symbol.parameters { - match parameter { - Type::StructEnum { .. } => { - let ident = format_ident!("arg{}", c_index.to_string()); - params.push(quote! { #ident: *const u8 }); - - c_index += 1; - let len_ident = format_ident!("arg{}", c_index.to_string()); - params.push(quote! { #len_ident: usize }); - - overrides.push(quote! { - let buf = unsafe { - ::std::slice::from_raw_parts(#ident, #len_ident) - }; - let #ident = deno_bindgen::serde_json::from_slice(buf).unwrap(); - }); - - input_idents.push(ident); - } - Type::Str | Type::Buffer | Type::BufferMut => { - let ident = format_ident!("arg{}", c_index.to_string()); - match parameter { - Type::Str | Type::Buffer => { - params.push(quote! { #ident: *const u8 }) - } - Type::BufferMut => params.push(quote! { #ident: *mut u8 }), - _ => unreachable!(), - }; - - c_index += 1; - let len_ident = format_ident!("arg{}", c_index.to_string()); - params.push(quote! { #len_ident: usize }); - - let return_type = match parameter { - Type::Str => quote! { ::std::str::from_utf8(buf).unwrap() }, - Type::Buffer | Type::BufferMut => quote! { buf }, - _ => unreachable!(), - }; - - let buf_expr = match parameter { - Type::Str | Type::Buffer => { - quote! { let buf = ::std::slice::from_raw_parts(#ident, #len_ident); } - } - Type::BufferMut => { - // https://github.com/littledivy/deno_bindgen/issues/26 - // *mut u8 should never outlive the symbol call. This can lead to UB. - quote! { let mut buf: &'sym mut [u8] = ::std::slice::from_raw_parts_mut(#ident, #len_ident); - } - } - _ => unreachable!(), - }; - - overrides.push(quote! { - let #ident = unsafe { - #buf_expr - #return_type - }; - }); - - input_idents.push(ident); - } - // TODO - _ => { - let ident = format_ident!("arg{}", c_index.to_string()); - let ty = syn::Type::from(parameter); - params.push(quote! { #ident: #ty }); - input_idents.push(ident); - } - }; - - c_index += 1; - } - - let (result, transformer) = match symbol.result { - Type::Buffer => { - let ty = parse_quote! { *const u8 }; - let transformer = quote! { - let length = (result.len() as u32).to_be_bytes(); - let mut v = length.to_vec(); - v.extend_from_slice(result); - - ::std::mem::forget(result); - let result = v.as_ptr(); - // Leak the result to JS land. - ::std::mem::forget(v); - result - }; - - (ty, transformer) - } - Type::StructEnum { .. } => { - let ty = parse_quote! { *const u8 }; - let transformer = quote! { - let json = deno_bindgen::serde_json::to_string(&result).expect("Failed to serialize as JSON"); - let encoded = json.into_bytes(); - let length = (encoded.len() as u32).to_be_bytes(); - let mut v = length.to_vec(); - v.extend(encoded.clone()); - - let ret = v.as_ptr(); - // Leak the result to JS land. - ::std::mem::forget(v); - ret - }; - - (ty, transformer) - } - Type::Ptr => (parse_quote! { *const u8 }, quote! { result }), - _ => (syn::Type::from(symbol.result), quote! { result }), - }; - - let name = &func.sig.ident; - let fn_inputs = &func.sig.inputs; - let fn_output = &func.sig.output; - let fn_generics = &func.sig.generics; - let fn_block = &func.block; - - let overrides = overrides - .iter() - .fold(quote! {}, |acc, new| quote! { #acc #new }); - - metafile - .write_all(&serde_json::to_vec(&metadata).unwrap()) - .unwrap(); - - TokenStream::from(quote! { - #[no_mangle] - pub extern "C" fn #name <'sym> (#(#params,) *) -> #result { - fn __inner_impl #fn_generics (#fn_inputs) #fn_output #fn_block - #overrides - let result = __inner_impl(#(#input_idents, ) *); - #transformer - } - }) - } - Err(_) => { - let input = syn::parse_macro_input!(input as syn::DeriveInput); - process_struct(&mut metadata, input.clone()).unwrap(); - - metafile - .write_all(&serde_json::to_vec(&metadata).unwrap()) - .unwrap(); - - TokenStream::from(quote! { - #[derive(::serde::Deserialize,::serde::Serialize)] - #input - }) - } - } -} diff --git a/deno_bindgen_macro/src/meta.rs b/deno_bindgen_macro/src/meta.rs deleted file mode 100644 index 247cb66..0000000 --- a/deno_bindgen_macro/src/meta.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -use serde::Deserialize; -use serde::Serialize; -use std::collections::HashMap; -use syn::parse_quote; - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "lowercase")] -pub enum Type { - /// Straight forward types supported - /// by Deno's FFI - I8, - U8, - I16, - U16, - I32, - U32, - I64, - U64, - F32, - F64, - Usize, - Isize, - Void, - - /// Types that pave way for - /// serializers. buffers <3 - Buffer, - BufferMut, - Str, - Ptr, - - /// Not-so straightforward types that - /// `deno_bingen` maps to. - StructEnum { - ident: String, - }, -} - -impl From for syn::Type { - fn from(ty: Type) -> Self { - match ty { - Type::I8 => parse_quote! { i8 }, - Type::U8 => parse_quote! { u8 }, - Type::I16 => parse_quote! { i16 }, - Type::U16 => parse_quote! { u16 }, - Type::I32 => parse_quote! { i32 }, - Type::U32 => parse_quote! { u32 }, - Type::I64 => parse_quote! { i64 }, - Type::U64 => parse_quote! { u64 }, - Type::F32 => parse_quote! { f32 }, - Type::F64 => parse_quote! { f64 }, - Type::Usize => parse_quote! { usize }, - Type::Isize => parse_quote! { isize }, - Type::Void => parse_quote! { () }, - _ => unreachable!(), - } - } -} - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Symbol { - pub parameters: Vec, - pub result: Type, - pub non_blocking: bool, -} - -#[derive(Serialize, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct Glue { - pub name: String, - pub little_endian: bool, - pub symbols: HashMap, - pub type_defs: HashMap>, - pub ts_types: HashMap, -} diff --git a/example/Cargo.toml b/example/Cargo.toml index 8bc71e7..cdb2cf1 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "deno_bindgen_test" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] deno_bindgen = { path = "../deno_bindgen/" } diff --git a/deno_bindgen_macro/Cargo.toml b/macro/Cargo.toml similarity index 81% rename from deno_bindgen_macro/Cargo.toml rename to macro/Cargo.toml index 769de9c..b84ae0a 100644 --- a/deno_bindgen_macro/Cargo.toml +++ b/macro/Cargo.toml @@ -18,6 +18,8 @@ proc-macro = true proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "extra-traits"] } -serde = { version = "1.0.59", features = ["derive"] } -serde_json = "1.0.59" +# serde = { version = "1.0.59", features = ["derive"] } +# serde_json = "1.0.59" Inflector = "0.11.4" +lazy_static = "1.4.0" +deno_bindgen_codegen = { path = "../codegen" } diff --git a/deno_bindgen_macro/src/attrs.rs b/macro/src/attrs.rs similarity index 100% rename from deno_bindgen_macro/src/attrs.rs rename to macro/src/attrs.rs diff --git a/deno_bindgen_macro/src/derive_fn.rs b/macro/src/derive_fn.rs similarity index 100% rename from deno_bindgen_macro/src/derive_fn.rs rename to macro/src/derive_fn.rs diff --git a/deno_bindgen_macro/src/derive_struct.rs b/macro/src/derive_struct.rs similarity index 100% rename from deno_bindgen_macro/src/derive_struct.rs rename to macro/src/derive_struct.rs diff --git a/deno_bindgen_macro/src/docs.rs b/macro/src/docs.rs similarity index 100% rename from deno_bindgen_macro/src/docs.rs rename to macro/src/docs.rs diff --git a/macro/src/lib.rs b/macro/src/lib.rs new file mode 100644 index 0000000..e884055 --- /dev/null +++ b/macro/src/lib.rs @@ -0,0 +1,121 @@ +// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. + +use deno_bindgen_codegen::function::Function; +use deno_bindgen_codegen::types::TypeDefinition; +use deno_bindgen_codegen::types::tuple::Tuple; +use lazy_static::lazy_static; +use proc_macro::TokenStream; +use quote::format_ident; +use quote::quote; +use syn::TypeTuple; +use std::borrow::Borrow; +use std::borrow::BorrowMut; +use std::cell::RefCell; +use std::env; +use std::fs::OpenOptions; +use std::io::Read; +use std::io::Write; +use syn::parse_macro_input; +use syn::parse_quote; +use syn::AttributeArgs; +use syn::FnArg; +use syn::Item; +use syn::ItemFn; +use syn::ItemStruct; + +mod attrs; +mod docs; +mod meta; + +use crate::meta::Meta; + +#[cfg(target_endian = "little")] +const ENDIANNESS: bool = true; + +#[cfg(target_endian = "big")] +const ENDIANNESS: bool = false; + +thread_local! { + static META: RefCell = RefCell::new(Meta::default()); +} + +#[proc_macro_attribute] +pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { + let attr = syn::parse_macro_input!(attr as AttributeArgs); + let item = syn::parse_macro_input!(input as Item); + + META.with(|meta| { + let mut meta = meta.borrow_mut(); + + match item { + Item::Fn(fn_item) => generate_function(&mut meta, fn_item), + Item::Struct(struct_item) => generate_struct(&mut meta, struct_item), + // Item::Enum(_) => todo!(), + // Item::Type(_) => todo!(), + // Item::Union(_) => todo!(), + _ => unimplemented!(), + } + }) +} + +fn syn_type_to_definition(meta: &mut Meta, ) { + +} + +fn generate_function(meta: &mut Meta, fn_item: ItemFn) -> TokenStream { + let params = &fn_item.sig.inputs; + let mut parameters = Vec::with_capacity(params.len()); + + for param in params.iter() { + match param { + FnArg::Typed(ref val) => { + let val = val.clone(); + let ty = match *val.ty { + syn::Type::Path(ref ty) => { + let ident = ty.path.get_ident().expect("Expected ident").to_string(); + meta.library.lookup_type(&ident).expect(&format!("Could not find {} type", ident)) + } + // syn::Type::Tuple(ref tuple) => { + // tuple.elems.iter().map(|ty| meta.library.lookup_type(ty.path.get_ident())) + // }, + //syn::Type::Reference(ref ty) => match *ty.elem { + // syn::Type::Path(ref ty) => { + // let segment = ty.path.segments.first().unwrap(); + // let ident = segment.ident.to_string(); + // match ident.as_str() { + // "str" => Type::Str, + // _ => unimplemented!(), + // } + // } + // syn::Type::Slice(ref slice) => match *slice.elem { + // syn::Type::Path(ref path) => { + // let segment = path.path.segments.first().unwrap(); + // let ident = segment.ident.to_string(); + // match ident.as_str() { + // "u8" => { + // if ty.mutability.is_some() { + // Type::BufferMut + // } else { + // Type::Buffer + // } + // } + // _ => unimplemented!(), + // } + // } + // _ => unimplemented!(), + // }, + // _ => unimplemented!(), + //}, + _ => unimplemented!(), + }; + parameters.push(Some(ty)); + } + _ => unimplemented!(), + } + } + todo!() +} + +fn generate_struct(meta: &mut Meta, struct_item: ItemStruct) -> TokenStream { + todo!() +} diff --git a/macro/src/meta.rs b/macro/src/meta.rs new file mode 100644 index 0000000..3e46da5 --- /dev/null +++ b/macro/src/meta.rs @@ -0,0 +1,49 @@ +// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. + +use std::env; + +use deno_bindgen_codegen::{library::Library, loader::deno::DenoLoader}; +use syn::{ext::IdentExt, parse_quote}; + +// impl From for syn::Type { +// fn from(ty: Type) -> Self { +// match ty { +// Type::I8 => parse_quote! { i8 }, +// Type::U8 => parse_quote! { u8 }, +// Type::I16 => parse_quote! { i16 }, +// Type::U16 => parse_quote! { u16 }, +// Type::I32 => parse_quote! { i32 }, +// Type::U32 => parse_quote! { u32 }, +// Type::I64 => parse_quote! { i64 }, +// Type::U64 => parse_quote! { u64 }, +// Type::F32 => parse_quote! { f32 }, +// Type::F64 => parse_quote! { f64 }, +// Type::Usize => parse_quote! { usize }, +// Type::Isize => parse_quote! { isize }, +// Type::Void => parse_quote! { () }, +// _ => unreachable!(), +// } +// } +// } + +pub struct Meta { + pub library: Library, +} + +impl Meta { + pub fn new(library: Library) -> Self { + Self { library } + } +} + +impl Default for Meta { + fn default() -> Self { + Self::new(Library::new( + None, + Box::new(DenoLoader::new( + false, + &env::var("CARGO_TARGET_DIR").expect("Expected CARGO_TARGET_DIR"), + )), + )) + } +} diff --git a/test.json b/test.json new file mode 100644 index 0000000..326bbab --- /dev/null +++ b/test.json @@ -0,0 +1,45 @@ +{ + "loader": { + "plug": { + "export": false, + "options": { + "name": "test_lib", + "urls": { + "darwin": "https://example.com/some/path/libtest_lib.dylib", + "windows": "https://example.com/some/path/test_lib.dll", + "linux": "https://example.com/some/path/libtest_lib.so" + } + } + } + }, + "types": { + "void": { "primitive": { "native": "void" } }, + "u8": { "primitive": { "native": "u8" } }, + "i8": { "primitive": { "native": "i8" } }, + "u16": { "primitive": { "native": "u16" } }, + "i16": { "primitive": { "native": "i16" } }, + "u32": { "primitive": { "native": "u32" } }, + "i32": { "primitive": { "native": "i32" } }, + "u64": { "primitive": { "native": "u64" } }, + "i64": { "primitive": { "native": "i64" } }, + "usize": { "primitive": { "native": "usize" } }, + "isize": { "primitive": { "native": "isize" } }, + "f32": { "primitive": { "native": "f32" } }, + "f64": { "primitive": { "native": "f64" } }, + + "TestStruct": { + "struct": { + "identifier": "TestStruct", + "fields": [ + ["property", { "primitive": { "native": "f64" } }], + [ + "pointer", + { "pointer": { "target": { "primitive": { "native": "f64" } } } } + ] + ] + } + } + }, + "functions": { + } +} diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..8b86699 --- /dev/null +++ b/test.ts @@ -0,0 +1,34 @@ +import { Plug } from "https://deno.land/x/plug/mod.ts"; +const library = await Plug.prepare({ name: "test_lib", urls: { darwin: "https://example.com/some/path/libtest_lib.dylib", linux: "https://example.com/some/path/libtest_lib.so", windows: "https://example.com/some/path/test_lib.dll" } }, { }); +export interface TestStruct { +property: number; +pointer: number; +}function __from_TestStruct(__source: ArrayBuffer | Uint8Array | Deno.UnsafePointer | Deno.UnsafePointerView): TestStruct { +const __array_buffer = +(__source instanceof ArrayBuffer +? __source +: __source instanceof Uint8Array +? __source.buffer +: __source instanceof Deno.UnsafePointer +? new Deno.UnsafePointerView(__source).getArrayBuffer(16) +: __source instanceof Deno.UnsafePointerView +? __source.getArrayBuffer(16) +: undefined)!; +const __data_view = new DataView(__array_buffer); +return { + property: __data_view.getFloat64(0), + pointer: new Deno.UnsafePointerView(new Deno.UnsafePointer(__data_view.getBigUint64(8))).getFloat64(0) +}; +}function __into_TestStruct(__data: TestStruct): Uint8Array { +const __array_buffer = new ArrayBuffer(16); +const __u8_array = new Uint8Array(__array_buffer); + const __data_view = new DataView(__array_buffer); +__data_view.setFloat64(0, __data.property); +__data_view.setBigUint64(8, Deno.UnsafePointer.of(new Float64Array([__data.pointer])).value); +return __u8_array; +}no.UnsafePointer.of(new Float64Array([__data.pointer])).value); +return __u8_array; +}_data.pointer])).value, + ); + return __u8_array; +}