use crate::model::{Family, Ty};
use super::ctype::{CType, ConstPos};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbiParam {
pub name: String,
pub ty: CType,
}
impl AbiParam {
pub fn new(name: impl Into<String>, ty: CType) -> Self {
Self {
name: name.into(),
ty,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbiReturn {
pub ret: CType,
pub out_params: Vec<AbiParam>,
}
pub fn split_qualified(name: &str, current_module: &str) -> (String, String) {
match name.rsplit_once('.') {
Some((module_path, type_name)) => (module_path.replace('.', "_"), type_name.to_string()),
None => (current_module.to_string(), name.to_string()),
}
}
pub fn struct_tag(name: &str, current_module: &str) -> CType {
let (module, name) = split_qualified(name, current_module);
CType::StructTag { module, name }
}
pub fn vtable_tag(name: &str, current_module: &str) -> CType {
let (module, name) = split_qualified(name, current_module);
CType::VtableTag { module, name }
}
fn direct_ctype(ty: &Ty, module: &str) -> CType {
match ty {
Ty::I8 => CType::Int8,
Ty::I16 => CType::Int16,
Ty::I32 => CType::Int32,
Ty::I64 => CType::Int64,
Ty::U8 => CType::Uint8,
Ty::U16 => CType::Uint16,
Ty::U32 => CType::Uint32,
Ty::U64 => CType::Uint64,
Ty::F32 => CType::Float,
Ty::F64 => CType::Double,
Ty::Bool => CType::Bool,
Ty::Enum(e) => {
let (module, name) = split_qualified(e, module);
CType::Enum { module, name }
}
other => unreachable!("{other} is not a direct type"),
}
}
fn ptr_len_slots(name: &str) -> Vec<AbiParam> {
vec![
AbiParam::new(format!("{name}_ptr"), CType::const_ptr(CType::Uint8)),
AbiParam::new(format!("{name}_len"), CType::Size),
]
}
pub fn lower_param(name: &str, ty: &Ty, module: &str) -> Vec<AbiParam> {
match ty.family() {
Family::Direct => vec![AbiParam::new(name, direct_ctype(ty, module))],
Family::String | Family::Bytes | Family::Buffer => ptr_len_slots(name),
Family::Object { .. } => {
let iface = ty
.interface_name()
.expect("object family names an interface");
vec![AbiParam::new(
name,
CType::Ptr {
konst: ConstPos::West,
pointee: Box::new(struct_tag(iface, module)),
},
)]
}
Family::Callback => {
let cb = ty
.callback_interface_name()
.expect("callback family names a callback interface");
vec![
AbiParam::new(format!("{name}_ctx"), CType::ptr(CType::Void)),
AbiParam::new(
format!("{name}_vtable"),
CType::const_ptr(vtable_tag(cb, module)),
),
]
}
Family::Iterator => unreachable!("iterator not valid as parameter"),
}
}
pub fn lower_return(ty: &Ty, module: &str) -> AbiReturn {
let no_out = |ret| AbiReturn {
ret,
out_params: vec![],
};
match ty.family() {
Family::Direct => no_out(direct_ctype(ty, module)),
Family::String | Family::Bytes | Family::Buffer => AbiReturn {
ret: CType::const_ptr(CType::Uint8),
out_params: vec![AbiParam::new("out_len", CType::ptr(CType::Size))],
},
Family::Object { .. } => {
let iface = ty
.interface_name()
.expect("object family names an interface");
no_out(CType::ptr(struct_tag(iface, module)))
}
Family::Callback => unreachable!("callback interfaces are never returned"),
Family::Iterator => {
unreachable!("iterator return handled specially by the function lowering")
}
}
}
pub fn callback_result_params(ty: &Ty, module: &str) -> Vec<AbiParam> {
match ty.family() {
Family::String | Family::Bytes | Family::Buffer => vec![
AbiParam::new("result_ptr", CType::const_ptr(CType::Uint8)),
AbiParam::new("result_len", CType::Size),
],
_ => vec![AbiParam::new("result", lower_return(ty, module).ret)],
}
}
#[cfg(test)]
mod tests {
use super::*;
fn render(params: &[AbiParam]) -> Vec<String> {
params
.iter()
.map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
.collect()
}
#[test]
fn params_lower_by_family() {
assert_eq!(render(&lower_param("x", &Ty::I32, "m")), ["int32_t x"]);
assert_eq!(
render(&lower_param("s", &Ty::StringUtf8, "m")),
["const uint8_t* s_ptr", "size_t s_len"]
);
assert_eq!(
render(&lower_param("data", &Ty::Bytes, "m")),
["const uint8_t* data_ptr", "size_t data_len"]
);
assert_eq!(
render(&lower_param("xs", &Ty::List(Box::new(Ty::I32)), "m")),
["const uint8_t* xs_ptr", "size_t xs_len"]
);
assert_eq!(
render(&lower_param(
"c",
&Ty::Record("other.Contact".into()),
"ops"
)),
["const uint8_t* c_ptr", "size_t c_len"]
);
assert_eq!(
render(&lower_param(
"s",
&Ty::Optional(Box::new(Ty::Interface("Store".into()))),
"kv"
)),
["const weaveffi_kv_Store* s"]
);
assert_eq!(
render(&lower_param(
"s",
&Ty::Enum("shared.Status".into()),
"orders"
)),
["weaveffi_shared_Status s"]
);
assert_eq!(
render(&lower_param(
"listener",
&Ty::CallbackInterface("events.Listener".into()),
"kv"
)),
[
"void* listener_ctx",
"const weaveffi_events_Listener_vtable* listener_vtable"
]
);
}
#[test]
fn returns_lower_by_family() {
let r = lower_return(&Ty::Bytes, "m");
assert_eq!(r.ret.render_c("weaveffi"), "const uint8_t*");
assert_eq!(render(&r.out_params), ["size_t* out_len"]);
for ty in [
Ty::Record("Contact".into()),
Ty::RichEnum("Shape".into()),
Ty::List(Box::new(Ty::Record("Contact".into()))),
Ty::List(Box::new(Ty::Interface("Store".into()))),
Ty::Map(Box::new(Ty::StringUtf8), Box::new(Ty::I32)),
Ty::Optional(Box::new(Ty::I64)),
] {
let r = lower_return(&ty, "m");
assert_eq!(r.ret.render_c("weaveffi"), "const uint8_t*", "{ty}");
assert_eq!(render(&r.out_params), ["size_t* out_len"], "{ty}");
}
let r = lower_return(&Ty::Optional(Box::new(Ty::Interface("Store".into()))), "kv");
assert_eq!(r.ret.render_c("weaveffi"), "weaveffi_kv_Store*");
assert!(r.out_params.is_empty());
assert_eq!(
lower_return(&Ty::Enum("shared.Status".into()), "orders")
.ret
.render_c("weaveffi"),
"weaveffi_shared_Status"
);
}
#[test]
fn callback_results() {
assert_eq!(
render(&callback_result_params(
&Ty::List(Box::new(Ty::StringUtf8)),
"m"
)),
["const uint8_t* result_ptr", "size_t result_len"]
);
assert_eq!(
render(&callback_result_params(&Ty::Bytes, "m")),
["const uint8_t* result_ptr", "size_t result_len"]
);
assert_eq!(
render(&callback_result_params(&Ty::I32, "m")),
["int32_t result"]
);
}
#[test]
fn split_qualified_handles_levels() {
assert_eq!(
split_qualified("Name", "current"),
("current".to_string(), "Name".to_string())
);
assert_eq!(
split_qualified("a.b.c.Name", "x"),
("a_b_c".to_string(), "Name".to_string())
);
}
}