#![deny(
unsafe_code,
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
elided_lifetimes_in_paths,
rust_2018_idioms,
clippy::fallible_impl_from,
clippy::missing_const_for_fn
)]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55122894")]
use std::{
collections::HashMap,
fmt, fs,
io::{self, Write},
path::PathBuf,
};
use clang::{Clang, Entity, EntityKind, Index, Type, TypeKind};
use log::debug;
use config::DynamicLibraryConfig;
use dart_source_writer::{DartSourceWriter, ImportedUri};
use enumeration::{Enum, EnumField};
use errors::CodegenError;
use func::{Func, Param};
use structure::{Field, Struct};
pub mod config;
mod dart_source_writer;
mod enumeration;
mod errors;
mod func;
mod structure;
trait Element {
fn name(&self) -> &str;
fn documentation(&self) -> Option<&str>;
fn generate_source(&self, w: &mut DartSourceWriter) -> io::Result<()>;
}
pub struct Codegen {
src_header: PathBuf,
lib_name: String,
allo_isolate: bool,
config: DynamicLibraryConfig,
elements: HashMap<String, Box<dyn Element>>,
}
impl Codegen {
pub fn builder() -> CodegenBuilder { CodegenBuilder::default() }
pub fn generate(mut self) -> Result<Bindings, CodegenError> {
debug!("Starting Codegen!");
debug!("Building dsw");
let mut dsw = Self::build_dsw();
debug!("dsw is ready");
self.generate_open_dl(&mut dsw)?;
let clang = Clang::new()?;
let index = Index::new(&clang, true, false);
debug!("start parsing the C header file at {:?}.", self.src_header);
let parser = index.parser(self.src_header);
let tu = parser.parse()?;
debug!("Done Parsed the header file");
let entity = tu.get_entity();
let entities = entity
.get_children()
.into_iter()
.filter(|e| !e.is_in_system_header())
.peekable();
for e in entities {
let kind = e.get_kind();
debug!("Entity: {:?}", e);
match kind {
EntityKind::FunctionDecl => {
debug!("Got Function: {:?}", e);
let func = Self::parse_function(e)?;
self.elements
.insert(func.name().to_owned(), Box::new(func));
},
EntityKind::StructDecl => {
debug!("Got Struct: {:?}", e);
match Self::parse_struct(e, None) {
Err(CodegenError::UnnamedStruct) => Ok(()),
Err(err) => Err(err),
Ok(s) => {
self.elements
.insert(s.name().to_owned(), Box::new(s));
Ok(())
},
}?;
},
EntityKind::EnumDecl => {
debug!("Got Enum: {:?}", e);
match Self::parse_enum(e, None) {
Err(CodegenError::UnnamedEnum) => Ok(()),
Err(err) => Err(err),
Ok(s) => {
self.elements
.insert(s.name().to_owned(), Box::new(s));
Ok(())
},
}?;
},
EntityKind::TypedefDecl => {
debug!("Got Typedef: {:?}", e);
for child in e.get_children() {
match child.get_kind() {
EntityKind::StructDecl => {
debug!("Got struct in Typedef: {:?}", child);
let s =
Self::parse_struct(child, e.get_name())?;
self.elements
.insert(s.name().to_owned(), Box::new(s));
},
EntityKind::EnumDecl => {
debug!("Got enum in Typedef: {:?}", child);
let s = Self::parse_enum(child, e.get_name())?;
self.elements
.insert(s.name().to_owned(), Box::new(s));
},
_ => {},
}
}
},
_ => {},
}
}
if self.allo_isolate {
let func = Func::new(
"store_dart_post_cobject".to_string(),
Some(String::from("Binding to `allo-isolate` crate")),
vec![Param::new(
Some("ptr".to_string()),
String::from("Pointer<NativeFunction<Int8 Function(Int64, Pointer<Dart_CObject>)>>"),
)],
String::from("void"),
);
self.elements.insert(
String::from("store_dart_post_cobject"),
Box::new(func),
);
}
debug!("Generating Dart Source...");
let mut elements: Vec<_> = self.elements.values().collect();
elements.sort_by_key(|k| k.name());
for el in elements {
el.generate_source(&mut dsw)?;
}
debug!("Done.");
Ok(Bindings::new(dsw))
}
fn generate_open_dl(
&self,
dsw: &mut DartSourceWriter,
) -> Result<(), CodegenError> {
dsw.set_lib_name(&self.lib_name);
debug!("Generating Code for opening DynamicLibrary");
writeln!(dsw, "final DynamicLibrary _dl = _open();")?;
writeln!(dsw, "/// Reference to the Dynamic Library, it should be only used for low-level access")?;
writeln!(dsw, "final DynamicLibrary dl = _dl;")?;
writeln!(dsw, "DynamicLibrary _open() {{")?;
if let Some(ref config) = self.config.windows {
debug!("Generating _open Code for Windows");
writeln!(dsw, " if (Platform.isWindows) return {};", config)?;
}
if let Some(ref config) = self.config.linux {
debug!("Generating _open Code for Linux");
writeln!(dsw, " if (Platform.isLinux) return {};", config)?;
}
if let Some(ref config) = self.config.android {
debug!("Generating _open Code for Android");
writeln!(dsw, " if (Platform.isAndroid) return {};", config)?;
}
if let Some(ref config) = self.config.ios {
debug!("Generating _open Code for iOS");
writeln!(dsw, " if (Platform.isIOS) return {};", config)?;
}
if let Some(ref config) = self.config.macos {
debug!("Generating _open Code for macOS");
writeln!(dsw, " if (Platform.isMacOS) return {};", config)?;
}
if let Some(ref config) = self.config.fuchsia {
debug!("Generating _open Code for Fuchsia");
writeln!(dsw, " if (Platform.isFuchsia) return {};", config)?;
}
writeln!(
dsw,
" throw UnsupportedError('This platform is not supported.');"
)?;
writeln!(dsw, "}}")?;
debug!("Generating Code for opening DynamicLibrary done.");
Ok(())
}
fn parse_function(
entity: Entity<'_>,
) -> Result<impl Element, CodegenError> {
let name = entity.get_name().ok_or(CodegenError::UnnamedFunction)?;
debug!("Function: {}", name);
let params = match entity.get_arguments() {
Some(entities) => Self::parse_fn_params(entities)?,
None => Vec::new(),
};
debug!("Function Params: {:?}", params);
let docs = entity.get_parsed_comment().map(|c| c.as_html());
debug!("Function Docs: {:?}", docs);
let return_ty = entity
.get_result_type()
.ok_or(CodegenError::UnknownFunctionReturnType)?
.get_canonical_type()
.get_display_name();
debug!("Function Return Type: {}", return_ty);
Ok(Func::new(name, docs, params, return_ty))
}
fn parse_fn_params(
entities: Vec<Entity<'_>>,
) -> Result<Vec<Param>, CodegenError> {
let mut params = Vec::with_capacity(entities.capacity());
for e in entities {
debug!("Param: {:?}", e);
let name = e.get_name();
debug!("Param Name: {:?}", name);
let ty = e
.get_type()
.ok_or(CodegenError::UnknownParamType)?
.get_canonical_type();
debug!("Param Type: {:?}", ty);
let ty = Self::parse_ty(ty)?;
debug!("Param Type Display Name: {}", ty);
params.push(Param::new(name, ty));
}
Ok(params)
}
fn parse_fn_proto(ty: Type<'_>) -> Result<String, CodegenError> {
let mut dsw = DartSourceWriter::new();
debug!("Function Proto: {:?}", ty);
let return_ty = ty
.get_canonical_type()
.get_result_type()
.ok_or(CodegenError::UnknownFunctionReturnType)?
.get_canonical_type()
.get_display_name();
let params = match ty.get_argument_types() {
Some(arg_ty) => arg_ty
.iter()
.map(|ty| ty.get_display_name())
.map(|ty| dsw.get_ctype(&ty))
.collect(),
None => Vec::new(),
};
write!(
dsw,
"Pointer<NativeFunction<{} Function({})>>",
dsw.get_ctype(&return_ty),
params.join(", ")
)?;
Ok(dsw.to_string())
}
fn parse_struct(
entity: Entity<'_>,
name: Option<String>,
) -> Result<impl Element, CodegenError> {
if entity.is_anonymous() {
return Err(CodegenError::AnonymousEntity);
}
let name = name
.or_else(|| entity.get_name())
.ok_or(CodegenError::UnnamedStruct)?;
debug!("Struct: {}", name);
let children = entity.get_children();
let mut fields = Vec::with_capacity(children.capacity());
for child in children {
let name =
child.get_name().ok_or(CodegenError::UnnamedStructField)?;
let ty = child
.get_type()
.ok_or(CodegenError::UnknownParamType)?
.get_canonical_type();
let ty = Self::parse_ty(ty)?;
fields.push(Field::new(name, ty));
}
let docs = entity.get_parsed_comment().map(|c| c.as_html());
Ok(Struct::new(name, docs, fields))
}
fn parse_enum(
entity: Entity<'_>,
name: Option<String>,
) -> Result<impl Element, CodegenError> {
if entity.is_anonymous() {
return Err(CodegenError::AnonymousEntity);
}
let name = name
.or_else(|| entity.get_name())
.ok_or(CodegenError::UnnamedEnum)?;
debug!("Enum: {}", name);
let children = entity.get_children();
let mut fields = Vec::with_capacity(children.capacity());
for child in children {
debug!("Enum field: {:?}", child);
let name =
child.get_name().ok_or(CodegenError::UnnamedEnumField)?;
let value = child
.get_enum_constant_value()
.ok_or(CodegenError::UnknownEnumFieldConstantValue)?
.1;
fields.push(EnumField::new(name, value));
}
let docs = entity.get_parsed_comment().map(|c| c.as_html());
Ok(Enum::new(name, docs, fields))
}
fn parse_ty(ty: Type<'_>) -> Result<String, CodegenError> {
use TypeKind::*;
if ty.get_kind() == Pointer {
let pointee_type = ty
.get_pointee_type()
.ok_or(CodegenError::UnknownPointeeType)?;
let kind = pointee_type.get_kind();
if kind == FunctionPrototype || kind == FunctionNoPrototype {
Self::parse_fn_proto(pointee_type)
} else {
Ok(ty.get_display_name())
}
} else {
Ok(ty.get_display_name())
}
}
fn build_dsw() -> DartSourceWriter {
let mut dsw = DartSourceWriter::new();
writeln!(dsw, "// ignore_for_file: unused_import, camel_case_types, non_constant_identifier_names").unwrap();
dsw.import(ImportedUri::new(String::from("dart:ffi")));
dsw.import(ImportedUri::new(String::from("dart:io")));
let mut ffi = ImportedUri::new(String::from("package:ffi/ffi.dart"));
ffi.with_prefix(String::from("ffi"));
dsw.import(ffi);
dsw
}
}
impl fmt::Debug for Codegen {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Codegen")
.field("src_header", &self.src_header)
.field("lib_name", &self.lib_name)
.field("config", &self.config)
.finish()
}
}
#[derive(Clone, Debug, Default)]
pub struct CodegenBuilder {
src_header: PathBuf,
lib_name: String,
allo_isolate: bool,
config: Option<DynamicLibraryConfig>,
}
impl CodegenBuilder {
pub fn with_src_header(mut self, path: impl Into<PathBuf>) -> Self {
self.src_header = path.into();
self
}
pub fn with_lib_name(mut self, name: impl Into<String>) -> Self {
self.lib_name = name.into();
self
}
#[allow(clippy::missing_const_for_fn)]
pub fn with_config(mut self, config: DynamicLibraryConfig) -> Self {
self.config = Some(config);
self
}
pub const fn with_allo_isolate(mut self) -> Self {
self.allo_isolate = true;
self
}
pub fn build(self) -> Result<Codegen, CodegenError> {
if self.lib_name.is_empty() {
return Err(CodegenError::Builder(
"Please Provide the C lib name.",
));
}
let config = self.config.ok_or(
CodegenError::Builder("Missing `DynamicLibraryConfig` did you forget to call `with_config` builder method?.")
)?;
Ok(Codegen {
src_header: self.src_header,
lib_name: self.lib_name,
allo_isolate: self.allo_isolate,
config,
elements: HashMap::new(),
})
}
}
#[derive(Debug)]
pub struct Bindings {
dsw: DartSourceWriter,
}
impl Bindings {
pub(crate) const fn new(dsw: DartSourceWriter) -> Self { Self { dsw } }
pub fn write_to_file(
&self,
path: impl Into<PathBuf>,
) -> Result<(), CodegenError> {
let mut out = fs::OpenOptions::new()
.read(false)
.write(true)
.truncate(true)
.create(true)
.open(path.into())?;
debug!("Writing Dart Source File...");
write!(out, "{}", self.dsw)?;
Ok(())
}
pub fn write(&self, w: &mut impl Write) -> Result<(), CodegenError> {
debug!("Writing Dart Source File...");
write!(w, "{}", self.dsw)?;
Ok(())
}
}