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<String> = registry
.keys()
.filter(|k| matches!(k.namespace, Namespace::Root))
.map(|k| k.name.clone())
.collect();
let mut known: BTreeSet<String> = BTreeSet::new();
let mut changed = true;
while changed {
changed = false;
for (qtn, container) in registry {
let name = &qtn.name;
if known.contains(name) {
continue;
}
if container_can_be_hashable(container, &known, &local_names) {
known.insert(name.clone());
changed = true;
}
}
}
known
}
fn container_can_be_hashable(
format: &ContainerFormat,
known: &BTreeSet<String>,
local_names: &BTreeSet<String>,
) -> bool {
match format {
ContainerFormat::UnitStruct(_) => true,
ContainerFormat::NewTypeStruct(fmt, _) => fmt_can_be_hashable(fmt, known, local_names),
ContainerFormat::TupleStruct(fmts, _) => fmts
.iter()
.all(|f| fmt_can_be_hashable(f, known, local_names)),
ContainerFormat::Struct(nameds, _) => nameds
.iter()
.all(|n| fmt_can_be_hashable(&n.value, known, local_names)),
ContainerFormat::Enum(variants, _) => variants
.values()
.all(|v| variant_can_be_hashable(&v.value, known, local_names)),
}
}
fn variant_can_be_hashable(
format: &VariantFormat,
known: &BTreeSet<String>,
local_names: &BTreeSet<String>,
) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => fmt_can_be_hashable(fmt, known, local_names),
VariantFormat::Tuple(fmts) => fmts
.iter()
.all(|f| fmt_can_be_hashable(f, known, local_names)),
VariantFormat::Struct(nameds) => nameds
.iter()
.all(|n| fmt_can_be_hashable(&n.value, known, local_names)),
}
}
fn fmt_can_be_hashable(
format: &Format,
known: &BTreeSet<String>,
local_names: &BTreeSet<String>,
) -> bool {
match format {
Format::TypeName(qtn) => {
if local_names.contains(&qtn.name) {
known.contains(&qtn.name)
} else {
true
}
}
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 => true,
Format::Option(inner)
| Format::Set(inner)
| Format::Seq(inner)
| Format::TupleArray { content: inner, .. } => {
fmt_can_be_hashable(inner, known, local_names)
}
Format::Variable(_)
| Format::Unit | Format::Map { .. } => false, Format::Tuple(formats) => {
formats.len() == 1 && fmt_can_be_hashable(&formats[0], known, local_names)
}
}
}
pub fn compute_equatable_types(registry: &Registry) -> BTreeSet<String> {
let local_names: BTreeSet<String> = registry
.keys()
.filter(|k| matches!(k.namespace, Namespace::Root))
.map(|k| k.name.clone())
.collect();
let mut known: BTreeSet<String> = BTreeSet::new();
let mut changed = true;
while changed {
changed = false;
for (qtn, container) in registry {
let name = &qtn.name;
if known.contains(name) {
continue;
}
if container_can_be_equatable(container, &known, &local_names) {
known.insert(name.clone());
changed = true;
}
}
}
known
}
fn container_can_be_equatable(
format: &ContainerFormat,
known: &BTreeSet<String>,
local_names: &BTreeSet<String>,
) -> bool {
match format {
ContainerFormat::UnitStruct(_) => true,
ContainerFormat::NewTypeStruct(fmt, _) => fmt_can_be_equatable(fmt, known, local_names),
ContainerFormat::TupleStruct(fmts, _) => fmts
.iter()
.all(|f| fmt_can_be_equatable(f, known, local_names)),
ContainerFormat::Struct(nameds, _) => nameds
.iter()
.all(|n| fmt_can_be_equatable(&n.value, known, local_names)),
ContainerFormat::Enum(variants, _) => variants
.values()
.all(|v| variant_fmt_can_be_equatable(&v.value, known, local_names)),
}
}
fn variant_fmt_can_be_equatable(
format: &VariantFormat,
known: &BTreeSet<String>,
local_names: &BTreeSet<String>,
) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => fmt_can_be_equatable(fmt, known, local_names),
VariantFormat::Tuple(fmts) => fmts
.iter()
.all(|f| fmt_can_be_equatable(f, known, local_names)),
VariantFormat::Struct(nameds) => nameds
.iter()
.all(|n| fmt_can_be_equatable(&n.value, known, local_names)),
}
}
fn fmt_can_be_equatable(
format: &Format,
known: &BTreeSet<String>,
local_names: &BTreeSet<String>,
) -> bool {
match format {
Format::TypeName(qtn) => {
if local_names.contains(&qtn.name) {
known.contains(&qtn.name)
} else {
true
}
}
Format::Variable(_) | Format::Unit => false,
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 => true,
Format::Option(inner)
| Format::Set(inner)
| Format::Seq(inner)
| Format::TupleArray { content: inner, .. } => {
fmt_can_be_equatable(inner, known, local_names)
}
Format::Map { key, value } => {
fmt_can_be_equatable(key, known, local_names)
&& fmt_can_be_equatable(value, known, local_names)
}
Format::Tuple(formats) => formats
.iter()
.all(|f| fmt_can_be_equatable(f, known, local_names)),
}
}
#[cfg(test)]
mod tests;