use std::{borrow::Cow, fmt::Debug, io::Write, path::Path};
use anyhow::Context;
use itertools::Itertools;
use crate::parsed_data::{
CrateName, Id, RustConst, RustEnum, RustEnumVariant, RustStruct, RustType, RustTypeAlias,
SpecialRustType, TypeName,
};
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum FilesMode<T> {
Single,
Multi(T),
}
impl<T> FilesMode<T> {
pub fn map<U>(self, op: impl FnOnce(T) -> U) -> FilesMode<U> {
match self {
FilesMode::Single => FilesMode::Single,
FilesMode::Multi(value) => FilesMode::Multi(op(value)),
}
}
pub fn is_multi(&self) -> bool {
matches!(*self, Self::Multi(_))
}
}
pub trait Language<'config>: Sized + Sync + Debug {
type Config: serde::Deserialize<'config> + serde::Serialize;
const NAME: &'static str;
fn new_from_config(config: Self::Config) -> anyhow::Result<Self>;
fn mapped_type(&self, type_name: &TypeName) -> Option<Cow<'_, str>> {
let _ = type_name;
None
}
fn output_filename_for_crate(&self, crate_name: &CrateName) -> String;
fn format_type(&self, ty: &RustType, generic_context: &[TypeName]) -> anyhow::Result<String> {
match ty {
RustType::Simple { id } => self.format_simple_type(id, generic_context),
RustType::Generic { id, parameters } => {
self.format_generic_type(id, parameters.as_slice(), generic_context)
}
RustType::Special(special) => self.format_special_type(special, generic_context),
}
}
fn format_simple_type(
&self,
base: &TypeName,
generic_context: &[TypeName],
) -> anyhow::Result<String> {
let _ = generic_context;
Ok(match self.mapped_type(base) {
Some(mapped) => mapped.to_string(),
None => base.to_string(),
})
}
fn format_generic_type(
&self,
base: &TypeName,
parameters: &[RustType],
generic_context: &[TypeName],
) -> anyhow::Result<String> {
match parameters.is_empty() {
true => self.format_simple_type(base, generic_context),
false => Ok(match self.mapped_type(base) {
Some(mapped) => mapped.to_string(),
None => format!(
"{}{}",
self.format_simple_type(base, generic_context)?,
self.format_generic_parameters(parameters, generic_context)?,
),
}),
}
}
fn format_generic_parameters(
&self,
parameters: &[RustType],
generic_context: &[TypeName],
) -> anyhow::Result<String> {
parameters
.iter()
.map(|ty| self.format_type(ty, generic_context))
.process_results(|mut formatted| format!("<{}>", formatted.join(", ")))
}
fn format_special_type(
&self,
special_ty: &SpecialRustType,
generic_context: &[TypeName],
) -> anyhow::Result<String>;
fn begin_file(&self, w: &mut impl Write, mode: FilesMode<&CrateName>) -> anyhow::Result<()> {
let _ = (w, mode);
Ok(())
}
fn write_imports<'a, Crates, Types>(
&self,
writer: &mut impl Write,
crate_name: &CrateName,
imports: Crates,
) -> anyhow::Result<()>
where
Crates: IntoIterator<Item = (&'a CrateName, Types)>,
Types: IntoIterator<Item = &'a TypeName>;
fn end_file(&self, w: &mut impl Write, mode: FilesMode<&CrateName>) -> anyhow::Result<()> {
let _ = (w, mode);
Ok(())
}
fn write_type_alias(&self, w: &mut impl Write, t: &RustTypeAlias) -> anyhow::Result<()>;
fn write_struct(&self, w: &mut impl Write, rs: &RustStruct) -> anyhow::Result<()>;
fn write_enum(&self, w: &mut impl Write, e: &RustEnum) -> anyhow::Result<()>;
fn write_const(&self, w: &mut impl Write, c: &RustConst) -> anyhow::Result<()>;
fn write_struct_types_for_enum_variants(
&self,
w: &mut impl Write,
e: &RustEnum,
make_struct_name: &impl Fn(&TypeName) -> String,
) -> anyhow::Result<()> {
let variants = match e {
RustEnum::Unit { .. } => return Ok(()),
RustEnum::Algebraic { variants, .. } => variants.iter().filter_map(|v| match v {
RustEnumVariant::AnonymousStruct { fields, shared } => Some((fields, shared)),
_ => None,
}),
};
for (fields, variant) in variants {
let struct_name = make_struct_name(&variant.id.original);
let generic_types = fields
.iter()
.flat_map(|field| {
e.shared()
.generic_types
.iter()
.filter(|g| field.ty.contains_type(g))
})
.unique()
.cloned()
.collect();
self.write_struct(
w,
&RustStruct {
id: Id {
original: TypeName::new_string(struct_name.clone()),
renamed: TypeName::new_string(struct_name),
},
fields: fields.clone(),
generic_types,
comments: vec![format!(
"Generated type representing the anonymous struct \
variant `{}` of the `{}` Rust enum",
&variant.id.original,
&e.shared().id.original,
)],
decorators: e.shared().decorators.clone(),
},
)
.with_context(|| {
format!(
"failed to write struct type for the \
`{}` variant of the `{}` enum",
variant.id.original,
e.shared().id.original
)
})?;
}
Ok(())
}
fn exclude_from_import_analysis(&self, name: &TypeName) -> bool {
let _ = name;
false
}
fn write_additional_files<'a>(
&self,
output_folder: &Path,
output_files: impl IntoIterator<Item = (&'a CrateName, &'a Path)>,
) -> anyhow::Result<()> {
let _ = (output_folder, output_files);
Ok(())
}
}