use anyhow::{Context, Result};
use askama::Template;
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::Debug;
use crate::backend::TemplateExpression;
use crate::interface::*;
use crate::BindingsConfig;
mod callback_interface;
mod compounds;
mod custom;
mod enum_;
mod executor;
mod external;
mod miscellany;
mod object;
mod primitives;
mod record;
trait CodeType: Debug {
fn type_label(&self) -> String;
fn canonical_name(&self) -> String {
self.type_label()
}
fn literal(&self, _literal: &Literal) -> String {
unimplemented!("Unimplemented for {}", self.type_label())
}
fn ffi_converter_name(&self) -> String {
format!("FfiConverter{}", self.canonical_name())
}
fn imports(&self) -> Option<Vec<String>> {
None
}
fn initialization_fn(&self) -> Option<String> {
None
}
}
static KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| {
let kwlist = vec![
"False",
"None",
"True",
"__peg_parser__",
"and",
"as",
"assert",
"async",
"await",
"break",
"class",
"continue",
"def",
"del",
"elif",
"else",
"except",
"finally",
"for",
"from",
"global",
"if",
"import",
"in",
"is",
"lambda",
"nonlocal",
"not",
"or",
"pass",
"raise",
"return",
"try",
"while",
"with",
"yield",
];
HashSet::from_iter(kwlist.into_iter().map(|s| s.to_string()))
});
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Config {
cdylib_name: Option<String>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CustomTypeConfig {
imports: Option<Vec<String>>,
into_custom: TemplateExpression,
from_custom: TemplateExpression,
}
impl Config {
pub fn cdylib_name(&self) -> String {
if let Some(cdylib_name) = &self.cdylib_name {
cdylib_name.clone()
} else {
"uniffi".into()
}
}
}
impl BindingsConfig for Config {
fn update_from_ci(&mut self, ci: &ComponentInterface) {
self.cdylib_name
.get_or_insert_with(|| format!("uniffi_{}", ci.namespace()));
}
fn update_from_cdylib_name(&mut self, cdylib_name: &str) {
self.cdylib_name
.get_or_insert_with(|| cdylib_name.to_string());
}
fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {}
}
pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
PythonWrapper::new(config.clone(), ci)
.render()
.context("failed to render python bindings")
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum ImportRequirement {
Module { mod_name: String },
Symbol {
mod_name: String,
symbol_name: String,
},
SymbolAs {
mod_name: String,
symbol_name: String,
as_name: String,
},
}
impl ImportRequirement {
fn render(&self) -> String {
match &self {
ImportRequirement::Module { mod_name } => format!("import {mod_name}"),
ImportRequirement::Symbol {
mod_name,
symbol_name,
} => format!("from {mod_name} import {symbol_name}"),
ImportRequirement::SymbolAs {
mod_name,
symbol_name,
as_name,
} => format!("from {mod_name} import {symbol_name} as {as_name}"),
}
}
}
#[derive(Template)]
#[template(syntax = "py", escape = "none", path = "Types.py")]
pub struct TypeRenderer<'a> {
python_config: &'a Config,
ci: &'a ComponentInterface,
include_once_names: RefCell<HashSet<String>>,
imports: RefCell<BTreeSet<ImportRequirement>>,
}
impl<'a> TypeRenderer<'a> {
fn new(python_config: &'a Config, ci: &'a ComponentInterface) -> Self {
Self {
python_config,
ci,
include_once_names: RefCell::new(HashSet::new()),
imports: RefCell::new(BTreeSet::new()),
}
}
fn include_once_check(&self, name: &str) -> bool {
self.include_once_names
.borrow_mut()
.insert(name.to_string())
}
fn add_import(&self, name: &str) -> &str {
self.imports.borrow_mut().insert(ImportRequirement::Module {
mod_name: name.to_owned(),
});
""
}
fn add_import_of(&self, mod_name: &str, name: &str) -> &str {
self.imports.borrow_mut().insert(ImportRequirement::Symbol {
mod_name: mod_name.to_owned(),
symbol_name: name.to_owned(),
});
""
}
fn add_import_of_as(&self, mod_name: &str, symbol_name: &str, as_name: &str) -> &str {
self.imports
.borrow_mut()
.insert(ImportRequirement::SymbolAs {
mod_name: mod_name.to_owned(),
symbol_name: symbol_name.to_owned(),
as_name: as_name.to_owned(),
});
""
}
}
#[derive(Template)]
#[template(syntax = "py", escape = "none", path = "wrapper.py")]
pub struct PythonWrapper<'a> {
ci: &'a ComponentInterface,
config: Config,
type_helper_code: String,
type_imports: BTreeSet<ImportRequirement>,
}
impl<'a> PythonWrapper<'a> {
pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
let type_renderer = TypeRenderer::new(&config, ci);
let type_helper_code = type_renderer.render().unwrap();
let type_imports = type_renderer.imports.into_inner();
Self {
config,
ci,
type_helper_code,
type_imports,
}
}
pub fn imports(&self) -> Vec<ImportRequirement> {
self.type_imports.iter().cloned().collect()
}
}
fn fixup_keyword(name: String) -> String {
if KEYWORDS.contains(&name) {
format!("_{name}")
} else {
name
}
}
#[derive(Clone, Default)]
pub struct PythonCodeOracle;
impl PythonCodeOracle {
fn find(&self, type_: &Type) -> Box<dyn CodeType> {
type_.clone().as_type().as_codetype()
}
fn class_name(&self, nm: &str) -> String {
fixup_keyword(nm.to_string().to_upper_camel_case())
}
fn fn_name(&self, nm: &str) -> String {
fixup_keyword(nm.to_string().to_snake_case())
}
fn var_name(&self, nm: &str) -> String {
fixup_keyword(nm.to_string().to_snake_case())
}
fn enum_variant_name(&self, nm: &str) -> String {
fixup_keyword(nm.to_string().to_shouty_snake_case())
}
fn ffi_type_label(ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::Int8 => "ctypes.c_int8".to_string(),
FfiType::UInt8 => "ctypes.c_uint8".to_string(),
FfiType::Int16 => "ctypes.c_int16".to_string(),
FfiType::UInt16 => "ctypes.c_uint16".to_string(),
FfiType::Int32 => "ctypes.c_int32".to_string(),
FfiType::UInt32 => "ctypes.c_uint32".to_string(),
FfiType::Int64 => "ctypes.c_int64".to_string(),
FfiType::UInt64 => "ctypes.c_uint64".to_string(),
FfiType::Float32 => "ctypes.c_float".to_string(),
FfiType::Float64 => "ctypes.c_double".to_string(),
FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
Some(suffix) => format!("_UniffiRustBuffer{suffix}"),
None => "_UniffiRustBuffer".to_string(),
},
FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(),
FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(),
FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(),
FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(),
FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(),
FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
}
}
}
trait AsCodeType {
fn as_codetype(&self) -> Box<dyn CodeType>;
}
impl<T: AsType> AsCodeType for T {
fn as_codetype(&self) -> Box<dyn CodeType> {
match self.as_type() {
Type::UInt8 => Box::new(primitives::UInt8CodeType),
Type::Int8 => Box::new(primitives::Int8CodeType),
Type::UInt16 => Box::new(primitives::UInt16CodeType),
Type::Int16 => Box::new(primitives::Int16CodeType),
Type::UInt32 => Box::new(primitives::UInt32CodeType),
Type::Int32 => Box::new(primitives::Int32CodeType),
Type::UInt64 => Box::new(primitives::UInt64CodeType),
Type::Int64 => Box::new(primitives::Int64CodeType),
Type::Float32 => Box::new(primitives::Float32CodeType),
Type::Float64 => Box::new(primitives::Float64CodeType),
Type::Boolean => Box::new(primitives::BooleanCodeType),
Type::String => Box::new(primitives::StringCodeType),
Type::Bytes => Box::new(primitives::BytesCodeType),
Type::Timestamp => Box::new(miscellany::TimestampCodeType),
Type::Duration => Box::new(miscellany::DurationCodeType),
Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
Type::CallbackInterface { name, .. } => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
}
Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional { inner_type } => {
Box::new(compounds::OptionalCodeType::new(*inner_type))
}
Type::Sequence { inner_type } => {
Box::new(compounds::SequenceCodeType::new(*inner_type))
}
Type::Map {
key_type,
value_type,
} => Box::new(compounds::MapCodeType::new(*key_type, *value_type)),
Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
}
}
}
pub mod filters {
use super::*;
pub use crate::backend::filters::*;
pub(super) fn type_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(as_ct.as_codetype().type_label())
}
pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(String::from("_Uniffi") + &as_ct.as_codetype().ffi_converter_name()[3..])
}
pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(as_ct.as_codetype().canonical_name())
}
pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!("{}.lift", ffi_converter_name(as_ct)?))
}
pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!("{}.lower", ffi_converter_name(as_ct)?))
}
pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!("{}.read", ffi_converter_name(as_ct)?))
}
pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!("{}.write", ffi_converter_name(as_ct)?))
}
pub(super) fn literal_py(
literal: &Literal,
as_ct: &impl AsCodeType,
) -> Result<String, askama::Error> {
Ok(as_ct.as_codetype().literal(literal))
}
pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
Ok(PythonCodeOracle::ffi_type_label(type_))
}
pub fn class_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.class_name(nm))
}
pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.fn_name(nm))
}
pub fn var_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.var_name(nm))
}
pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.enum_variant_name(nm))
}
}