use crate::converter::{function_name, param_to_type, param_to_type_overloaded, rval_to_type_async, rval_to_type_sync};
use crate::interop::DecorateFn;
use crate::interop::docs::write_documentation;
use crate::utils::sugared_return_type;
use crate::{FunctionNameFlavor, Interop};
use interoptopus::lang::{Function, Primitive, SugaredReturnType, Type};
use interoptopus::pattern::TypePattern;
use interoptopus_backend_utils::{Error, IndentWriter, WriteFor, indented};
use std::iter::zip;
pub fn write_functions(i: &Interop, w: &mut IndentWriter) -> Result<(), Error> {
for function in i.inventory.functions() {
if i.should_emit_by_meta(function.meta()) {
write_function(i, w, function, WriteFor::Code)?;
w.newline()?;
}
}
Ok(())
}
pub fn write_function(i: &Interop, w: &mut IndentWriter, function: &Function, write_for: WriteFor) -> Result<(), Error> {
i.debug(w, "write_function")?;
if write_for == WriteFor::Code {
write_documentation(w, function.meta().docs())?;
write_function_annotation(i, w, function)?;
}
write_function_declaration(i, w, function, false)?;
w.newline()?;
write_function_overload(i, w, function, write_for)?;
Ok(())
}
pub fn write_function_annotation(i: &Interop, w: &mut IndentWriter, function: &Function) -> Result<(), Error> {
indented!(w, r#"[LibraryImport(NativeLib, EntryPoint = "{}")]"#, function.name())?;
if *function.signature().rval() == Type::Primitive(Primitive::Bool) {
indented!(w, r"[return: MarshalAs(UnmanagedType.U1)]")?;
}
for decorator in &i.decorate_fn {
let util = DecorateFn::default();
indented!(w, r"{}", decorator(util))?;
}
Ok(())
}
pub fn write_function_declaration(i: &Interop, w: &mut IndentWriter, function: &Function, has_body: bool) -> Result<(), Error> {
i.debug(w, "write_function_declaration")?;
let rval = rval_to_type_sync(function.signature().rval());
let name = function_name(function, FunctionNameFlavor::RawFFIName);
let mut params = Vec::new();
let native = i.has_custom_marshalled_delegate(function.signature());
let visibility = "public ";
for p in function.signature().params() {
let the_type = param_to_type(p.the_type());
let name = p.name();
if native && matches!(p.the_type(), Type::FnPointer(_) | Type::Pattern(TypePattern::NamedCallback(_))) {
let suffix = if matches!(p.the_type(), Type::FnPointer(_)) { "_native" } else { "" };
params.push(format!("{the_type}{suffix} {name}"));
} else {
params.push(format!("{the_type} {name}"));
}
}
let line_ending = if has_body { "" } else { ";" };
let partial = if has_body { "" } else { "partial " };
i.inline_hint(w, 0)?;
indented!(w, r"{}static {}{} {}({}){}", visibility, partial, rval, name, params.join(", "), line_ending)?;
Ok(())
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn write_function_overload(i: &Interop, w: &mut IndentWriter, function: &Function, write_for: WriteFor) -> Result<(), Error> {
i.debug(w, "write_function_overload")?;
let has_overload = i.has_overloadable(function.signature());
if !has_overload {
i.debug(w, &format!("no overload for {}", function.name()))?;
return Ok(());
}
let async_rval = sugared_return_type(function);
let mut to_invoke = Vec::new();
let mut to_wrap_name = Vec::new();
let mut to_wrap_type = Vec::new();
let raw_name = function_name(function, FunctionNameFlavor::RawFFIName);
let rval = rval_to_type_async(&sugared_return_type(function));
let mut params = Vec::new();
for p in function.signature().params() {
let name = p.name();
let native = param_to_type_overloaded(p.the_type());
let mut fallback = || {
if native.contains("ref ") {
to_invoke.push(format!("ref {name}"));
} else {
to_invoke.push(name.to_string());
}
};
match p.the_type() {
Type::Pattern(TypePattern::NamedCallback(_)) => {
to_wrap_name.push(name);
to_wrap_type.push(param_to_type(p.the_type()));
to_invoke.push(format!("{name}_wrapped"));
}
_ => fallback(),
}
params.push(format!("{native} {name}"));
}
if matches!(async_rval, SugaredReturnType::Async(_)) {
params.pop();
to_invoke.pop();
to_invoke.push("_cb".to_string());
}
let signature = format!(r"public static unsafe {} {}({})", rval, raw_name, params.join(", "));
if write_for == WriteFor::Docs {
indented!(w, r"{};", signature)?;
return Ok(());
}
if write_for == WriteFor::Code {
write_documentation(w, function.meta().docs())?;
}
i.inline_hint(w, 0)?;
indented!(w, "{}", signature)?;
indented!(w, r"{{")?;
if let SugaredReturnType::Async(ref x) = async_rval {
let trampoline = format!("_trampoline{}", param_to_type(x));
indented!(w, [()], r"var (_cb, _cs) = {trampoline}.NewCall();")?;
}
for (n, t) in zip(&to_wrap_name, &to_wrap_type) {
indented!(w, [()], r"var {}_wrapped = new {}({});", n, t, n)?;
}
indented!(w, [()], r"try")?;
indented!(w, [()], r"{{")?;
let fn_name = function_name(function, FunctionNameFlavor::RawFFIName);
let call = format!(r"{}({})", fn_name, to_invoke.join(", "));
match function.signature().rval() {
Type::Pattern(TypePattern::CStrPointer) => {
indented!(w, [()()], r"var _s = {};", call)?;
indented!(w, [()()], r"return Marshal.PtrToStringAnsi(_s);")?;
}
Type::Primitive(Primitive::Void) => {
indented!(w, [()()], r"{};", call)?;
}
_ if matches!(async_rval, SugaredReturnType::Async(_)) => {
indented!(w, [()()], r"{call}.AsOk();")?;
indented!(w, [()()], r"return _cs;")?;
}
_ => {
indented!(w, [()()], r"return {call};")?;
}
}
indented!(w, [()], r"}}")?;
indented!(w, [()], r"finally")?;
indented!(w, [()], r"{{")?;
for n in to_wrap_name {
indented!(w, [()()], r"{}_wrapped.Dispose();", n)?;
}
indented!(w, [()], r"}}")?;
if matches!(async_rval, SugaredReturnType::Async(_)) {
indented!(w, [()], r"return _cs;")?;
}
indented!(w, r"}}")?;
Ok(())
}