Skip to content

Commit 9d9ff9f

Browse files
committed
Initial commit.
0 parents  commit 9d9ff9f

File tree

9 files changed

+1303
-0
lines changed

9 files changed

+1303
-0
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
Cargo.lock

Diff for: Cargo.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "ffi-gen"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Call rust from any language."
6+
repository = "https://github.com/cloudpeers/ffi-gen"
7+
license = "MIT"
8+
9+
[dependencies]
10+
anyhow = "1.0.47"
11+
genco = "0.15.1"
12+
pest = "2.1.3"
13+
pest_derive = "2.1.0"
14+
tempfile = "3.2.0"
15+
16+
[dev-dependencies]
17+
trybuild = "1.0.52"

Diff for: src/abi.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use crate::{Interface, Type};
2+
3+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4+
pub enum Abi {
5+
Native,
6+
Wasm32,
7+
}
8+
9+
pub struct AbiFunction {
10+
pub is_static: bool,
11+
pub is_async: bool,
12+
pub object: Option<String>,
13+
pub name: String,
14+
pub args: Vec<(String, Type)>,
15+
pub ret: Option<Type>,
16+
}
17+
18+
impl AbiFunction {
19+
pub fn exported_name(&self) -> String {
20+
if let Some(object) = self.object.as_ref() {
21+
format!("__{}_{}", object, &self.name)
22+
} else {
23+
format!("__{}", &self.name)
24+
}
25+
}
26+
}
27+
28+
impl Interface {
29+
pub fn into_functions(self) -> Vec<AbiFunction> {
30+
let mut funcs = vec![];
31+
for object in self.objects {
32+
for method in object.methods {
33+
let func = AbiFunction {
34+
is_static: method.is_static,
35+
is_async: method.func.ty.is_async,
36+
object: Some(object.ident.clone()),
37+
name: method.func.ident,
38+
args: method.func.ty.args,
39+
ret: method.func.ty.ret,
40+
};
41+
funcs.push(func);
42+
}
43+
}
44+
for func in self.functions {
45+
let func = AbiFunction {
46+
is_static: false,
47+
is_async: func.ty.is_async,
48+
object: None,
49+
name: func.ident,
50+
args: func.ty.args,
51+
ret: func.ty.ret,
52+
};
53+
funcs.push(func);
54+
}
55+
funcs
56+
}
57+
}

Diff for: src/dart.rs

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use crate::{AbiFunction, Interface};
2+
use genco::prelude::*;
3+
use genco::tokens::static_literal;
4+
5+
pub struct DartGenerator {
6+
cdylib_name: String,
7+
}
8+
9+
impl DartGenerator {
10+
pub fn new(cdylib_name: String) -> Self {
11+
Self { cdylib_name }
12+
}
13+
14+
pub fn generate(&self, iface: Interface) -> dart::Tokens {
15+
quote! {
16+
#(static_literal("//")) AUTO GENERATED FILE, DO NOT EDIT.
17+
#(static_literal("//"))
18+
#(static_literal("//")) Generated by "ffi-gen".
19+
20+
import "dart:ffi" as ffi;
21+
import "dart:io" show Platform;
22+
23+
class Api {
24+
#(static_literal("///")) Holds the symbol lookup function.
25+
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
26+
_lookup;
27+
28+
#(static_literal("///")) The symbols are looked up in [dynamicLibrary].
29+
Api(ffi.DynamicLibrary dynamicLibrary)
30+
: _lookup = dynamicLibrary.lookup;
31+
32+
#(static_literal("///")) The symbols are looked up with [lookup].
33+
Api.fromLookup(
34+
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
35+
lookup)
36+
: _lookup = lookup;
37+
38+
#(static_literal("///")) The library is loaded from the executable.
39+
factory Api.loadStatic() {
40+
return Api(ffi.DynamicLibrary.executable());
41+
}
42+
43+
#(static_literal("///")) The library is dynamically loaded.
44+
factory Api.loadDynamic(String name) {
45+
return Api(ffi.DynamicLibrary.open(name));
46+
}
47+
48+
#(static_literal("///")) The library is loaded based on platform conventions.
49+
factory Api.load() {
50+
String? name;
51+
if (Platform.isLinux) name = #_(#("lib")#(&self.cdylib_name)#(".so"));
52+
if (Platform.isAndroid) name = #_(#("lib")#(&self.cdylib_name)#(".so"));
53+
if (Platform.isMacOS) name = #_(#("lib")#(&self.cdylib_name)#(".dylib"));
54+
if (Platform.isIOS) name = #_("");
55+
if (Platform.isWindows) #_(#(&self.cdylib_name)#(".dll"));
56+
if (name == null) {
57+
throw UnsupportedError(#_("This platform is not supported."));
58+
}
59+
if (name == "") {
60+
return Api.loadStatic();
61+
} else {
62+
return Api.loadDynamic(name);
63+
}
64+
}
65+
66+
#(for func in iface.into_functions() => #(self.generate_function(func)))
67+
}
68+
}
69+
}
70+
71+
pub fn generate_function(&self, func: AbiFunction) -> dart::Tokens {
72+
quote! {
73+
void #(&func.name)() {
74+
#(format!("_{}", &func.name))();
75+
}
76+
77+
late final #(format!("_{}Ptr", &func.name)) = _lookup<
78+
ffi.NativeFunction<
79+
ffi.Void Function()>>(#_(#(format!("__{}", &func.name))));
80+
81+
late final #(format!("_{}", &func.name)) = #(format!("_{}Ptr", &func.name))
82+
.asFunction<void Function()>();
83+
}
84+
}
85+
}
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use super::*;
90+
use crate::{Abi, RustGenerator};
91+
use anyhow::Result;
92+
use std::io::Write;
93+
use tempfile::NamedTempFile;
94+
use trybuild::TestCases;
95+
96+
fn compile_pass(iface: &str, rust: rust::Tokens, dart: dart::Tokens) -> Result<()> {
97+
let iface = Interface::parse(iface)?;
98+
let mut rust_file = NamedTempFile::new()?;
99+
let rust_gen = RustGenerator::new(Abi::Native);
100+
let rust_tokens = rust_gen.generate(iface.clone());
101+
let mut dart_file = NamedTempFile::new()?;
102+
let dart_gen = DartGenerator::new("compile_pass".to_string());
103+
let dart_tokens = dart_gen.generate(iface);
104+
105+
let library_tokens = quote! {
106+
#rust_tokens
107+
#rust
108+
};
109+
110+
let bin_tokens = quote! {
111+
#dart_tokens
112+
113+
void main() {
114+
final api = Api.load();
115+
#dart
116+
}
117+
};
118+
119+
let library = library_tokens.to_file_string()?;
120+
rust_file.write_all(library.as_bytes())?;
121+
let bin = bin_tokens.to_file_string()?;
122+
dart_file.write_all(bin.as_bytes())?;
123+
124+
let library_dir = tempfile::tempdir()?;
125+
let library_file = library_dir.as_ref().join("libcompile_pass.so");
126+
let runner_tokens: rust::Tokens = quote! {
127+
fn main() {
128+
use std::process::Command;
129+
let ret = Command::new("rustc")
130+
.arg("--crate-name")
131+
.arg("compile_pass")
132+
.arg("--crate-type")
133+
.arg("cdylib")
134+
.arg("-o")
135+
.arg(#(quoted(library_file.as_path().to_str().unwrap())))
136+
.arg(#(quoted(rust_file.as_ref().to_str().unwrap())))
137+
.status()
138+
.unwrap()
139+
.success();
140+
assert!(ret);
141+
let ret = Command::new("dart")
142+
.env("LD_LIBRARY_PATH", #(quoted(library_dir.as_ref().to_str().unwrap())))
143+
.arg("run")
144+
.arg(#(quoted(dart_file.as_ref().to_str().unwrap())))
145+
.status()
146+
.unwrap()
147+
.success();
148+
assert!(ret);
149+
}
150+
};
151+
152+
let mut runner_file = NamedTempFile::new()?;
153+
let runner = runner_tokens.to_file_string()?;
154+
runner_file.write_all(runner.as_bytes())?;
155+
156+
let test = TestCases::new();
157+
test.pass(runner_file.as_ref());
158+
Ok(())
159+
}
160+
161+
#[test]
162+
fn no_args_no_ret() {
163+
compile_pass(
164+
"hello_world fn();",
165+
quote!(
166+
pub fn hello_world() {}
167+
),
168+
quote!(api.hello_world();),
169+
)
170+
.unwrap();
171+
}
172+
}

Diff for: src/grammar.pest

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
ident = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
2+
3+
arg = { ident ~ ":" ~ type_ }
4+
args = { (arg ~ ("," ~ arg)*)? }
5+
async_ = { "async" }
6+
function_type = { async_? ~ "fn" ~ "(" ~ args ~ ")" ~ ("->" ~ type_)? }
7+
8+
primitive = {
9+
"u8" | "u16" | "u32" | "u64" | "usize" |
10+
"i8" | "i16" | "i32" | "i64" | "isize" |
11+
"bool" | "f32" | "f64" | "string"
12+
}
13+
ref_ = { "&" ~ type_ }
14+
mut_ = { "&mut" ~ type_ }
15+
slice = { "[" ~ type_ ~ "]" }
16+
box_ = { "Box" ~ "<" ~ type_ ~ ">" }
17+
vec = { "Vec" ~ "<" ~ type_ ~ ">" }
18+
opt = { "Option" ~ "<" ~ type_ ~ ">" }
19+
res = { "Result" ~ "<" ~ type_ ~ ">" }
20+
21+
type_ = { primitive | mut_ | ref_ | slice | box_ | vec | opt | res | function_type | ident }
22+
23+
static_ = { "static" }
24+
method = { static_? ~ function }
25+
object = { "object" ~ ident ~ "{" ~ method* ~ "}" }
26+
function = { ident ~ function_type ~ ";" }
27+
28+
item = { object | function }
29+
root = { SOI ~ item* ~ EOI }
30+
31+
WHITESPACE = _{ " " | "\t" | "\n" }
32+
COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" }

0 commit comments

Comments
 (0)