use std::fmt::Display;
use convert_case::{Case, Casing};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{Field, ItemEnum, Variant};
use super::templates::TargetLanguageTypeName;
use crate::binding_types::RustWrapperType;
use crate::cpp::FunctionVirtualTranslator;
use crate::enum_helpers::{
enum_tag_name,
field_name,
get_fields,
is_ignored_variant,
is_many_fields_variant,
is_primitive_enum,
variant_wrapper_ident,
};
use crate::extern_module_translator::ExternModuleTranslator;
const WASM_EXCEPTION_CLASS_NAME: &str = "RustException";
const CPP_WASM_EXCEPTION_CLASS_NAME: &str = "WasmException";
pub fn create_wasm_module(extern_module_translator: &ExternModuleTranslator) -> String {
let enum_classes = translate_enums(extern_module_translator);
let user_classes = create_user_classes(extern_module_translator);
let rust_wrappers = create_rust_wrappers(extern_module_translator);
let abstract_classes_wrappers: String = create_custom_traits_wrappers(extern_module_translator);
let abstract_classes_gluecode = create_custom_traits_gluecode(extern_module_translator);
let classes: String = rust_wrappers
.chain(user_classes)
.chain(abstract_classes_gluecode)
.collect();
let global_functions: String = extern_module_translator
.global_functions
.iter()
.map(|f| global_def_template(&f.name.to_string()))
.collect();
let exception_class_enum = enum_class_bindings(
"ExceptionClass",
extern_module_translator.exception_names.iter(),
);
let exception_trait_methods = extern_module_translator
.exception_trait_methods
.iter()
.map(|fun| class_method(&fun.name, CPP_WASM_EXCEPTION_CLASS_NAME))
.collect::<Vec<_>>()
.join("\n");
let exception_class = class_template(
CPP_WASM_EXCEPTION_CLASS_NAME,
&(class_constructor("unsigned")
+ &class_method("exception_class", CPP_WASM_EXCEPTION_CLASS_NAME)
+ &class_method("what", CPP_WASM_EXCEPTION_CLASS_NAME)
+ &exception_trait_methods),
WASM_EXCEPTION_CLASS_NAME,
);
generate_cpp_code_for_wasm(
&abstract_classes_wrappers,
&classes,
&global_functions,
&enum_classes,
&exception_class_enum,
&exception_class,
)
}
fn translate_enums(emt: &ExternModuleTranslator) -> String {
let (fieldless_enums, data_enums): (Vec<&ItemEnum>, Vec<&ItemEnum>) = emt
.shared_enums
.iter()
.partition(|enum_item| is_primitive_enum(enum_item));
let fieldless_enums_bindings = fieldless_enums
.into_iter()
.map(|enum_item| {
enum_class_bindings(
&enum_item.ident,
enum_item.variants.iter().map(|variant| &variant.ident),
)
})
.collect::<String>();
let data_enums_bindings = data_enums
.into_iter()
.map(data_enum_emscripten_bindings)
.collect::<String>();
format!(
"{fieldless_enums_bindings}
{data_enums_bindings}"
)
}
fn data_enum_emscripten_bindings(enum_item: &ItemEnum) -> String {
let tag_enum = tag_enum_bindings(enum_item);
let payload_object_values = create_variant_object_values(enum_item);
let enum_struct = enum_object_class_bindings(enum_item);
format!("{enum_struct}{payload_object_values}{tag_enum}")
}
fn create_variant_object_values(enum_item: &ItemEnum) -> String {
let enum_name = &enum_item.ident;
enum_item
.variants
.iter()
.filter(|v| is_many_fields_variant(v))
.map(|v| create_variant_object_value(v, &variant_wrapper_ident(enum_name, &v.ident)))
.collect::<Vec<String>>()
.join("\n")
}
fn create_variant_object_value(
variant: &Variant,
variant_wrapper_name: impl Display + Copy,
) -> String {
let field_getters = field_getters(get_fields(variant).unwrap(), variant_wrapper_name);
format!(
"
EMSCRIPTEN_BINDINGS({variant_wrapper_name}) {{
emscripten::class_<{variant_wrapper_name}>(\"{variant_wrapper_name}\")
{field_getters}
;
}}
"
)
}
fn field_getters(fields: &Punctuated<Field, Comma>, struct_name: impl Display) -> String {
fields
.iter()
.enumerate()
.map(|(idx, field)| {
let field_name = field_name(field).unwrap_or(format!("_{idx}"));
let field_name = if let Some(stripped) = field_name.strip_prefix('_') {
stripped
} else {
&field_name
};
format!(" .function(\"get_{field_name}\", &{struct_name}::get_{field_name})")
})
.collect::<Vec<_>>()
.join("\n")
}
fn enum_object_class_bindings(enum_item: &ItemEnum) -> String {
let enum_name = &enum_item.ident;
let variant_getters = create_enum_variant_getters(enum_item);
format!(
"
EMSCRIPTEN_BINDINGS({enum_name}) {{
emscripten::class_<{enum_name}>(\"{enum_name}\")
.function(\"get_tag\", &{enum_name}::get_tag)
{variant_getters}
;
}}
"
)
}
fn create_enum_variant_getters(enum_item: &ItemEnum) -> String {
let enum_name = enum_item.ident.to_string();
enum_item
.variants
.iter()
.filter_map(|v| create_enum_variant_getter(v, &enum_name))
.collect::<Vec<_>>()
.join("\n")
}
fn create_enum_variant_getter(variant: &Variant, enum_name: &str) -> Option<String> {
match get_fields(variant) {
Some(_) if !is_ignored_variant(variant) => {
let variant_name = &variant.ident.to_string().to_case(Case::Snake);
Some(format!(
" .function(\"get_{variant_name}\", &{enum_name}::get_{variant_name})"
))
}
_ => None,
}
}
fn tag_enum_bindings(enum_item: &ItemEnum) -> String {
let enum_name_tag = enum_tag_name(enum_item.ident.to_string().as_str());
let variants = enum_item
.variants
.iter()
.map(|variant| {
let variant_ident = &variant.ident;
format!(" .value(\"{variant_ident}\", {enum_name_tag}::{variant_ident})")
})
.collect::<Vec<_>>()
.join("\n");
format!(
"
EMSCRIPTEN_BINDINGS({enum_name_tag}) {{
emscripten::enum_<{enum_name_tag}>(\"{enum_name_tag}\")
{variants}
;
}}
"
)
}
fn enum_class_bindings<T: Display, U: Display>(
enum_name: T,
variants: impl Iterator<Item = U>,
) -> String {
let variants = variants
.map(|v| format!(" .value(\"{v}\", {enum_name}::{v})"))
.collect::<Vec<String>>()
.join("\n");
format!(
"
EMSCRIPTEN_BINDINGS({enum_name}) {{
emscripten::enum_<{enum_name}>(\"{enum_name}\")
{variants}
;
}}
"
)
}
fn create_user_classes(
extern_module_translator: &ExternModuleTranslator,
) -> impl Iterator<Item = String> + '_ {
extern_module_translator
.user_custom_types
.iter()
.map(|(wrapper, functions)| {
let class_name = wrapper.wrapper_name.to_string();
let functions: String = functions
.iter()
.map(|f| class_method(&f.name.to_string(), &class_name))
.collect();
class_template(&class_name, &functions, &class_name)
})
}
fn create_rust_wrappers(
extern_module_translator: &ExternModuleTranslator,
) -> impl Iterator<Item = String> + '_ {
extern_module_translator
.rust_types_wrappers
.unordered_iter()
.map(|wrapper| match &wrapper.rust_type {
RustWrapperType::Option(inner_type) => {
let inner_type_generics = inner_type.get_name();
let inner_type = inner_type.wrapper_name.to_string();
let class_name = format!("Optional<{inner_type_generics}>");
let target_name = format!("Optional{inner_type}");
let functions = [
class_constructor(""),
class_constructor(&inner_type_generics),
class_method("unwrap", &class_name),
class_method("is_some", &class_name),
]
.join("");
class_template(&class_name, &functions, &target_name)
}
RustWrapperType::Vector(inner_type) => {
let inner_type_generics = inner_type.get_name();
let inner_type = inner_type.wrapper_name.to_string();
let class_name = format!("RustVec<{inner_type_generics}>");
let target_name = format!("Vec{inner_type}");
let functions = [
class_constructor(""),
class_method("at", &class_name),
class_method("size", &class_name),
class_method("push", &class_name),
]
.join("");
class_template(&class_name, &functions, &target_name)
}
RustWrapperType::Result(_, _) => {
let class_name = &wrapper.wrapper_name;
let functions = [
class_function("from_ok", class_name),
class_function("from_err", class_name),
]
.join("");
class_template(class_name, &functions, class_name)
}
_ => "".to_owned(),
})
}
fn create_custom_traits_wrappers(extern_module_translator: &ExternModuleTranslator) -> String {
extern_module_translator
.user_traits
.iter()
.map(|(wrapper, functions)| {
let class_name = wrapper.wrapper_name.to_string();
let functions_calls: String = functions
.iter()
.map(|f| FunctionVirtualTranslator::from_virtual_function(f, &class_name))
.map(|f_helper| {
let return_type_string = if let Some(ref wrapper) = f_helper.return_type {
wrapper.get_name_for_abstract_method()
} else {
"void".to_owned()
};
let function_name = f_helper.function_name;
let function_signature = f_helper.generated_virtual_function_signature;
let args: String = f_helper.arg_names[1..]
.iter()
.map(|arg| format!("std::move({arg})"))
.collect::<Vec<String>>()
.join(", ");
virtual_method_call(
&function_name,
&return_type_string,
&function_signature,
&args,
)
})
.collect();
abstract_class_wrapper(&class_name, &functions_calls)
})
.collect()
}
fn create_custom_traits_gluecode(
extern_module_translator: &ExternModuleTranslator,
) -> impl Iterator<Item = String> + '_ {
extern_module_translator
.user_traits
.iter()
.map(|(wrapper, functions)| {
let class_name = wrapper.wrapper_name.to_string();
let virtual_functions: String = functions
.iter()
.map(|function| virtual_function(&function.name.to_string(), &class_name))
.collect();
abstract_class(&wrapper.wrapper_name.to_string(), &virtual_functions)
})
}
fn virtual_method_call(
function_name: &str,
return_type: &str,
function_signature: &str,
args: &str,
) -> String {
if args.is_empty() {
format!(
" {return_type} {function_name}({function_signature}) {{
return call<{return_type}>(\"{function_name}\");
}}\n"
)
} else {
format!(
" {return_type} {function_name}({function_signature}) {{
return call<{return_type}>(\"{function_name}\", {args});
}}\n"
)
}
}
fn virtual_function(function_name: &str, class_name: &str) -> String {
format!(" .function(\"{function_name}\", &{class_name}::{function_name}, emscripten::pure_virtual())\n")
}
fn abstract_class_wrapper(class_name: &str, functions_calls: &str) -> String {
format!(
"
struct {class_name}Wrapper : public emscripten::wrapper<{class_name}> {{
EMSCRIPTEN_WRAPPER({class_name}Wrapper);
{functions_calls}
}};\n"
)
}
fn abstract_class(class_name: &str, virtual_functions: &str) -> String {
format!(
"
emscripten::class_<{class_name}>(\"{class_name}\")
{virtual_functions}
.allow_subclass<{class_name}Wrapper>(\"{class_name}Wrapper\")
;\n"
)
}
fn generate_cpp_code_for_wasm(
abstract_classes_wrappers: &str,
classes: &str,
global_functions: &str,
enum_classes: &str,
exception_class_enum: &str,
exception_class: &str,
) -> String {
format!(
"
#ifdef WASM
#include <emscripten/bind.h>
#include <utility>
{exception_class_enum}
{enum_classes}
{abstract_classes_wrappers}
EMSCRIPTEN_BINDINGS(WasmModule) {{
emscripten::class_<String>(\"String\")
.constructor<std::string>()
.function(\"to_string\", &String::to_string)
;
{exception_class}
{classes}
{global_functions}
}}
#endif
"
)
}
fn class_template(class_name: &str, functions: &str, target_name: &str) -> String {
format!(" emscripten::class_<{class_name}>(\"{target_name}\")\n{functions} ;\n")
}
fn class_method(function_name: &str, class_name: &str) -> String {
format!(" .function(\"{function_name}\", &{class_name}::{function_name})\n")
}
fn class_function(function_name: &str, class_name: &str) -> String {
format!(" .class_function(\"{function_name}\", &{class_name}::{function_name})\n")
}
fn class_constructor(args: &str) -> String {
format!(" .constructor<{args}>()\n")
}
fn global_def_template(function_name: &str) -> String {
format!(" emscripten::function(\"{function_name}\", &{function_name});\n")
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::utils::helpers;
#[test]
fn test_enum_class_emscripten_bindings() {
let enum_item = helpers::get_enum_item();
let emscripten_binding =
enum_class_bindings(enum_item.ident, enum_item.variants.iter().map(|i| &i.ident));
let expected = "
EMSCRIPTEN_BINDINGS(En1) {
emscripten::enum_<En1>(\"En1\")
.value(\"V1\", En1::V1)
.value(\"V2\", En1::V2)
;
}
";
assert_eq!(emscripten_binding, expected);
}
}