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, 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)?;
for container in registry.iter().map(Container::from) {
writeln!(w)?;
container.write(w, &lang)?;
}
Ok(())
}
}
pub fn compute_hashable_types(registry: &Registry) -> BTreeSet<QualifiedTypeName> {
let mut known: BTreeSet<&QualifiedTypeName> = BTreeSet::new();
let mut visiting: BTreeSet<&QualifiedTypeName> = BTreeSet::new();
for qtn in registry.keys() {
if !known.contains(qtn) {
check_type_hashable(registry, qtn, &mut known, &mut visiting);
}
}
known.into_iter().cloned().collect()
}
fn check_type_hashable<'a>(
registry: &'a Registry,
qtn: &'a QualifiedTypeName,
known: &mut BTreeSet<&'a QualifiedTypeName>,
visiting: &mut BTreeSet<&'a QualifiedTypeName>,
) -> bool {
if known.contains(qtn) {
return true;
}
if visiting.contains(qtn) {
return true;
}
let Some(container) = registry.get(qtn) else {
return true; };
visiting.insert(qtn);
let result = match container {
ContainerFormat::UnitStruct(_) => true,
ContainerFormat::NewTypeStruct(fmt, _) => fmt_is_hashable(registry, fmt, known, visiting),
ContainerFormat::TupleStruct(fmts, _) => fmts
.iter()
.all(|f| fmt_is_hashable(registry, f, known, visiting)),
ContainerFormat::Struct(fields, _) => fields
.iter()
.all(|f| fmt_is_hashable(registry, &f.value, known, visiting)),
ContainerFormat::Enum(variants, _) => variants
.values()
.all(|v| variant_is_hashable(registry, &v.value, known, visiting)),
};
visiting.remove(qtn);
if result {
known.insert(qtn);
}
result
}
fn variant_is_hashable<'a>(
registry: &'a Registry,
format: &'a VariantFormat,
known: &mut BTreeSet<&'a QualifiedTypeName>,
visiting: &mut BTreeSet<&'a QualifiedTypeName>,
) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => fmt_is_hashable(registry, fmt, known, visiting),
VariantFormat::Tuple(fmts) => fmts
.iter()
.all(|f| fmt_is_hashable(registry, f, known, visiting)),
VariantFormat::Struct(fields) => fields
.iter()
.all(|f| fmt_is_hashable(registry, &f.value, known, visiting)),
}
}
fn fmt_is_hashable<'a>(
registry: &'a Registry,
format: &'a Format,
known: &mut BTreeSet<&'a QualifiedTypeName>,
visiting: &mut BTreeSet<&'a QualifiedTypeName>,
) -> bool {
match format {
Format::TypeName(qtn) => check_type_hashable(registry, qtn, 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, known, visiting)
}
Format::Map { key, value } => {
fmt_is_hashable(registry, key, known, visiting)
&& fmt_is_hashable(registry, value, known, visiting)
}
Format::Tuple(formats) => {
formats.len() == 1 && fmt_is_hashable(registry, &formats[0], known, visiting)
}
}
}
pub fn compute_equatable_types(registry: &Registry) -> BTreeSet<QualifiedTypeName> {
let mut known: BTreeSet<&QualifiedTypeName> = BTreeSet::new();
let mut visiting: BTreeSet<&QualifiedTypeName> = BTreeSet::new();
for qtn in registry.keys() {
if !known.contains(qtn) {
check_type_equatable(registry, qtn, &mut known, &mut visiting);
}
}
known.into_iter().cloned().collect()
}
fn check_type_equatable<'a>(
registry: &'a Registry,
qtn: &'a QualifiedTypeName,
known: &mut BTreeSet<&'a QualifiedTypeName>,
visiting: &mut BTreeSet<&'a QualifiedTypeName>,
) -> bool {
if known.contains(qtn) {
return true;
}
if visiting.contains(qtn) {
return true;
}
let Some(container) = registry.get(qtn) else {
return true; };
visiting.insert(qtn);
let result = match container {
ContainerFormat::UnitStruct(_) => true,
ContainerFormat::NewTypeStruct(fmt, _) => fmt_is_equatable(registry, fmt, known, visiting),
ContainerFormat::TupleStruct(fmts, _) => fmts
.iter()
.all(|f| fmt_is_equatable(registry, f, known, visiting)),
ContainerFormat::Struct(fields, _) => fields
.iter()
.all(|f| fmt_is_equatable(registry, &f.value, known, visiting)),
ContainerFormat::Enum(variants, _) => variants
.values()
.all(|v| variant_is_equatable(registry, &v.value, known, visiting)),
};
visiting.remove(qtn);
if result {
known.insert(qtn);
}
result
}
fn variant_is_equatable<'a>(
registry: &'a Registry,
format: &'a VariantFormat,
known: &mut BTreeSet<&'a QualifiedTypeName>,
visiting: &mut BTreeSet<&'a QualifiedTypeName>,
) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => fmt_is_equatable(registry, fmt, known, visiting),
VariantFormat::Tuple(fmts) => fmts
.iter()
.all(|f| fmt_is_equatable(registry, f, known, visiting)),
VariantFormat::Struct(fields) => fields
.iter()
.all(|f| fmt_is_equatable(registry, &f.value, known, visiting)),
}
}
fn fmt_is_equatable<'a>(
registry: &'a Registry,
format: &'a Format,
known: &mut BTreeSet<&'a QualifiedTypeName>,
visiting: &mut BTreeSet<&'a QualifiedTypeName>,
) -> bool {
match format {
Format::TypeName(qtn) => check_type_equatable(registry, qtn, 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, known, visiting)
}
Format::Map { key, value } => {
fmt_is_equatable(registry, key, known, visiting)
&& fmt_is_equatable(registry, value, known, visiting)
}
Format::Tuple(formats) => formats
.iter()
.all(|f| fmt_is_equatable(registry, f, known, visiting)),
}
}
#[cfg(test)]
mod tests;