use std::{
collections::BTreeSet,
io::{Result, Write},
sync::Arc,
};
use crate::{
Registry,
generation::{
CodeGenerator, CodeGeneratorConfig, Container, Emitter, indent::IndentedWriter,
module::Module, plugin::EmitterPlugin, swift::emitter::Swift,
},
reflection::format::{
ContainerFormat, Format, FormatHolder, Namespace, QualifiedTypeName, VariantFormat,
},
};
pub struct SwiftCodeGenerator<'a> {
pub(crate) config: &'a CodeGeneratorConfig,
pub(crate) plugins: Vec<Arc<dyn EmitterPlugin<Swift>>>,
}
impl<'a> CodeGenerator<'a> for SwiftCodeGenerator<'a> {
fn new(config: &'a CodeGeneratorConfig) -> Self {
Self {
config,
plugins: vec![],
}
}
fn write_output<W: std::io::Write>(
&mut self,
writer: &mut W,
registry: &Registry,
) -> Result<()> {
self.output(writer, registry)
}
}
impl<'a> SwiftCodeGenerator<'a> {
#[must_use]
pub fn new(config: &'a CodeGeneratorConfig) -> Self {
Self {
config,
plugins: vec![],
}
}
#[must_use]
pub fn with_plugins(mut self, plugins: Vec<Arc<dyn EmitterPlugin<Swift>>>) -> Self {
self.plugins = plugins;
self
}
pub fn output(&self, out: &mut impl Write, registry: &Registry) -> Result<()> {
let w = &mut IndentedWriter::new(out, self.config.indent);
let mut config = self.config.clone();
config.update_from(registry);
let mut lang = Swift::new(&config, registry);
for p in &self.plugins {
lang = lang.with_plugin(p.clone());
}
Module::new(&config).write(w, &lang)?;
let updated_registry = Self::update_qualified_names(&config, registry);
for container in updated_registry.iter().map(Container::from) {
writeln!(w)?;
container.write(w, &lang)?;
}
Ok(())
}
fn update_qualified_names(config: &CodeGeneratorConfig, registry: &Registry) -> Registry {
let mut updated_registry = registry.clone();
for container_format in updated_registry.values_mut() {
let _ = container_format.visit_mut(&mut |format| {
if let Format::TypeName(qualified_name) = format
&& let Namespace::Named(namespace) = &qualified_name.namespace
{
if namespace == config.module_name() {
*qualified_name = QualifiedTypeName::root(qualified_name.name.clone());
} else if config.external_definitions.contains_key(namespace) {
*qualified_name = QualifiedTypeName::namespaced(
namespace.clone(),
qualified_name.name.clone(),
);
}
}
Ok(())
});
}
updated_registry
}
}
pub fn compute_hashable_types(registry: &Registry) -> BTreeSet<String> {
let local_names: BTreeSet<&str> = registry
.keys()
.filter(|k| matches!(k.namespace, Namespace::Root))
.map(|k| k.name.as_str())
.collect();
let mut known: BTreeSet<String> = BTreeSet::new();
let mut visiting: BTreeSet<String> = BTreeSet::new();
for qtn in registry.keys() {
if local_names.contains(qtn.name.as_str()) && !known.contains(&qtn.name) {
check_type_hashable(registry, qtn, &local_names, &mut known, &mut visiting);
}
}
known
}
fn check_type_hashable(
registry: &Registry,
qtn: &QualifiedTypeName,
local_names: &BTreeSet<&str>,
known: &mut BTreeSet<String>,
visiting: &mut BTreeSet<String>,
) -> bool {
let name = &qtn.name;
if known.contains(name) {
return true;
}
if visiting.contains(name) {
return true;
}
if !local_names.contains(name.as_str()) {
return true; }
let Some(container) = registry.get(qtn) else {
return false;
};
visiting.insert(name.clone());
let result = match container {
ContainerFormat::UnitStruct(_) => true,
ContainerFormat::NewTypeStruct(fmt, _) => {
fmt_is_hashable(registry, fmt, local_names, known, visiting)
}
ContainerFormat::TupleStruct(fmts, _) => fmts
.iter()
.all(|f| fmt_is_hashable(registry, f, local_names, known, visiting)),
ContainerFormat::Struct(fields, _) => fields
.iter()
.all(|f| fmt_is_hashable(registry, &f.value, local_names, known, visiting)),
ContainerFormat::Enum(variants, _) => variants
.values()
.all(|v| variant_is_hashable(registry, &v.value, local_names, known, visiting)),
};
visiting.remove(name);
if result {
known.insert(name.clone());
}
result
}
fn variant_is_hashable(
registry: &Registry,
format: &VariantFormat,
local_names: &BTreeSet<&str>,
known: &mut BTreeSet<String>,
visiting: &mut BTreeSet<String>,
) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => fmt_is_hashable(registry, fmt, local_names, known, visiting),
VariantFormat::Tuple(fmts) => fmts
.iter()
.all(|f| fmt_is_hashable(registry, f, local_names, known, visiting)),
VariantFormat::Struct(fields) => fields
.iter()
.all(|f| fmt_is_hashable(registry, &f.value, local_names, known, visiting)),
}
}
fn fmt_is_hashable(
registry: &Registry,
format: &Format,
local_names: &BTreeSet<&str>,
known: &mut BTreeSet<String>,
visiting: &mut BTreeSet<String>,
) -> bool {
match format {
Format::TypeName(qtn) => check_type_hashable(registry, qtn, local_names, known, visiting),
Format::Bool
| Format::I8
| Format::I16
| Format::I32
| Format::I64
| Format::I128
| Format::U8
| Format::U16
| Format::U32
| Format::U64
| Format::U128
| Format::F32
| Format::F64
| Format::Char
| Format::Str
| Format::Bytes
| Format::Uuid => true,
Format::Variable(_) | Format::Unit => false,
Format::Option(inner)
| Format::Set(inner)
| Format::Seq(inner)
| Format::TupleArray { content: inner, .. } => {
fmt_is_hashable(registry, inner, local_names, known, visiting)
}
Format::Map { key, value } => {
fmt_is_hashable(registry, key, local_names, known, visiting)
&& fmt_is_hashable(registry, value, local_names, known, visiting)
}
Format::Tuple(formats) => {
formats.len() == 1
&& fmt_is_hashable(registry, &formats[0], local_names, known, visiting)
}
}
}
pub fn compute_equatable_types(registry: &Registry) -> BTreeSet<String> {
let local_names: BTreeSet<&str> = registry
.keys()
.filter(|k| matches!(k.namespace, Namespace::Root))
.map(|k| k.name.as_str())
.collect();
let mut known: BTreeSet<String> = BTreeSet::new();
let mut visiting: BTreeSet<String> = BTreeSet::new();
for qtn in registry.keys() {
if local_names.contains(qtn.name.as_str()) && !known.contains(&qtn.name) {
check_type_equatable(registry, qtn, &local_names, &mut known, &mut visiting);
}
}
known
}
fn check_type_equatable(
registry: &Registry,
qtn: &QualifiedTypeName,
local_names: &BTreeSet<&str>,
known: &mut BTreeSet<String>,
visiting: &mut BTreeSet<String>,
) -> bool {
let name = &qtn.name;
if known.contains(name) {
return true;
}
if visiting.contains(name) {
return true;
}
if !local_names.contains(name.as_str()) {
return true; }
let Some(container) = registry.get(qtn) else {
return false;
};
visiting.insert(name.clone());
let result = match container {
ContainerFormat::UnitStruct(_) => true,
ContainerFormat::NewTypeStruct(fmt, _) => {
fmt_is_equatable(registry, fmt, local_names, known, visiting)
}
ContainerFormat::TupleStruct(fmts, _) => fmts
.iter()
.all(|f| fmt_is_equatable(registry, f, local_names, known, visiting)),
ContainerFormat::Struct(fields, _) => fields
.iter()
.all(|f| fmt_is_equatable(registry, &f.value, local_names, known, visiting)),
ContainerFormat::Enum(variants, _) => variants
.values()
.all(|v| variant_is_equatable(registry, &v.value, local_names, known, visiting)),
};
visiting.remove(name);
if result {
known.insert(name.clone());
}
result
}
fn variant_is_equatable(
registry: &Registry,
format: &VariantFormat,
local_names: &BTreeSet<&str>,
known: &mut BTreeSet<String>,
visiting: &mut BTreeSet<String>,
) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => {
fmt_is_equatable(registry, fmt, local_names, known, visiting)
}
VariantFormat::Tuple(fmts) => fmts
.iter()
.all(|f| fmt_is_equatable(registry, f, local_names, known, visiting)),
VariantFormat::Struct(fields) => fields
.iter()
.all(|f| fmt_is_equatable(registry, &f.value, local_names, known, visiting)),
}
}
fn fmt_is_equatable(
registry: &Registry,
format: &Format,
local_names: &BTreeSet<&str>,
known: &mut BTreeSet<String>,
visiting: &mut BTreeSet<String>,
) -> bool {
match format {
Format::TypeName(qtn) => check_type_equatable(registry, qtn, local_names, known, visiting),
Format::Bool
| Format::I8
| Format::I16
| Format::I32
| Format::I64
| Format::I128
| Format::U8
| Format::U16
| Format::U32
| Format::U64
| Format::U128
| Format::F32
| Format::F64
| Format::Char
| Format::Str
| Format::Bytes
| Format::Uuid => true,
Format::Variable(_) | Format::Unit => false,
Format::Option(inner)
| Format::Set(inner)
| Format::Seq(inner)
| Format::TupleArray { content: inner, .. } => {
fmt_is_equatable(registry, inner, local_names, known, visiting)
}
Format::Map { key, value } => {
fmt_is_equatable(registry, key, local_names, known, visiting)
&& fmt_is_equatable(registry, value, local_names, known, visiting)
}
Format::Tuple(formats) => formats
.iter()
.all(|f| fmt_is_equatable(registry, f, local_names, known, visiting)),
}
}
#[cfg(test)]
mod tests;