mod ast;
mod namespace;
use crate::error::Result;
use crate::target::{
DiscriminatorVariantInfo, EnumMember, EnumMemberNamingStrategy, Expr, Field,
FilePartitioningStrategy, Item, Strategy, Target,
};
use ast::{Ast, SchemaAst};
use jtd::Schema;
use namespace::Namespace;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Write;
use std::path::Path;
pub struct CodegenInfo {
pub root_name: String,
pub definition_names: BTreeMap<String, String>,
}
pub fn codegen<T: Target>(
target: &T,
root_name: String,
schema: &Schema,
out_dir: &Path,
) -> Result<CodegenInfo> {
let schema_ast = SchemaAst::new(target, root_name, schema);
let mut code_generator = CodeGenerator::new(target, out_dir);
code_generator.codegen(schema_ast)
}
struct CodeGenerator<'a, T> {
target: &'a T,
out_dir: &'a Path,
strategy: Strategy,
definition_names: BTreeMap<String, String>,
}
struct FileData<T> {
buf: Vec<u8>,
state: T,
}
impl<'a, T: Target> CodeGenerator<'a, T> {
pub fn new(target: &'a T, out_dir: &'a Path) -> Self {
Self {
target,
out_dir,
strategy: target.strategy(),
definition_names: BTreeMap::new(),
}
}
pub fn codegen(&mut self, schema_ast: SchemaAst) -> Result<CodegenInfo> {
let mut global_namespace = Namespace::new();
let root_name = self.ast_name(&mut global_namespace, &schema_ast.root);
for (name, ast) in &schema_ast.definitions {
let ast_name = self.ast_name(&mut global_namespace, ast);
self.definition_names.insert(name.clone(), ast_name);
}
let mut root_file_data = FileData {
buf: Vec::new(),
state: T::FileState::default(),
};
self.codegen_ast(
&mut global_namespace,
&mut root_file_data,
root_name.clone(),
schema_ast.root,
)?;
for (name, ast) in schema_ast.definitions {
let ast_name = self.definition_names[&name].clone();
self.codegen_ast(&mut global_namespace, &mut root_file_data, ast_name, ast)?;
}
if let FilePartitioningStrategy::SingleFile(_) = self.strategy.file_partitioning {
self.write_file(&mut root_file_data, &root_name)?;
}
self.target.item(
&mut vec![],
&mut T::FileState::default(),
Item::Auxiliary {
out_dir: self.out_dir.to_owned(),
},
)?;
Ok(CodegenInfo {
root_name,
definition_names: self.definition_names.clone(),
})
}
fn codegen_ast(
&self,
global_namespace: &mut Namespace,
file_data: &mut FileData<T::FileState>,
ast_name: String,
ast: Ast,
) -> Result<String> {
Ok(match ast {
Ast::Ref { definition, .. } => self.definition_names[&definition].clone(),
Ast::Empty { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Empty)
}
Ast::Boolean { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Boolean)
}
Ast::Int8 { metadata } => self.target.expr(&mut file_data.state, metadata, Expr::Int8),
Ast::Uint8 { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Uint8)
}
Ast::Int16 { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Int16)
}
Ast::Uint16 { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Uint16)
}
Ast::Int32 { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Int32)
}
Ast::Uint32 { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Uint32)
}
Ast::Float32 { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Float32)
}
Ast::Float64 { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Float64)
}
Ast::String { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::String)
}
Ast::Timestamp { metadata } => {
self.target
.expr(&mut file_data.state, metadata, Expr::Timestamp)
}
Ast::ArrayOf { metadata, type_ } => {
let sub_name = self.ast_name(global_namespace, &type_);
let sub_expr = self.codegen_ast(global_namespace, file_data, sub_name, *type_)?;
self.target
.expr(&mut file_data.state, metadata, Expr::ArrayOf(sub_expr))
}
Ast::DictOf { metadata, type_ } => {
let sub_name = self.ast_name(global_namespace, &type_);
let sub_expr = self.codegen_ast(global_namespace, file_data, sub_name, *type_)?;
self.target
.expr(&mut file_data.state, metadata, Expr::DictOf(sub_expr))
}
Ast::NullableOf { metadata, type_ } => {
let sub_name = self.ast_name(global_namespace, &type_);
let sub_expr = self.codegen_ast(global_namespace, file_data, sub_name, *type_)?;
self.target
.expr(&mut file_data.state, metadata, Expr::NullableOf(sub_expr))
}
Ast::Alias {
metadata, type_, ..
} => self.with_subfile(ast_name.clone(), file_data, |file_data| {
let sub_name = self.ast_name(global_namespace, &type_);
let sub_type = self.codegen_ast(global_namespace, file_data, sub_name, *type_)?;
self.target.item(
&mut file_data.buf,
&mut file_data.state,
Item::Alias {
metadata,
name: ast_name,
type_: sub_type,
},
)
})?,
Ast::Enum {
metadata, members, ..
} => self.with_subfile(ast_name.clone(), file_data, |file_data| {
let mut member_names = Namespace::new();
let mut enum_members = Vec::new();
for member in members {
enum_members.push(EnumMember {
name: match self.strategy.enum_member_naming {
EnumMemberNamingStrategy::Modularized => &mut member_names,
EnumMemberNamingStrategy::Unmodularized => global_namespace,
}
.get(member.name),
json_value: member.json_value,
});
}
self.target.item(
&mut file_data.buf,
&mut file_data.state,
Item::Enum {
metadata,
name: ast_name,
members: enum_members,
},
)
})?,
Ast::Struct {
metadata,
has_additional,
fields,
..
} => {
self.with_subfile(ast_name.clone(), file_data, |file_data| {
let mut field_names = Namespace::new();
let mut struct_fields = Vec::new();
for field in fields {
let field_name = field_names.get(field.name);
let sub_name = self.ast_name(global_namespace, &field.type_);
let sub_ast =
self.codegen_ast(global_namespace, file_data, sub_name, field.type_)?;
struct_fields.push(Field {
metadata: field.metadata,
name: field_name,
json_name: field.json_name,
optional: field.optional,
type_: sub_ast,
});
}
self.target.item(
&mut file_data.buf,
&mut file_data.state,
Item::Struct {
metadata,
name: ast_name,
has_additional,
fields: struct_fields,
},
)
})?
}
Ast::Discriminator {
metadata,
tag_field_name,
tag_json_name,
variants,
..
} => self.with_subfile(ast_name.clone(), file_data, |file_data| {
let discriminator_name = ast_name.clone();
let mut discriminator_field_names = Namespace::new();
let discriminator_tag_field_name =
discriminator_field_names.get(tag_field_name.clone());
let mut variant_names = Vec::new();
let mut variant_infos = Vec::new();
for variant in &variants {
let type_name = global_namespace.get(variant.type_name.clone());
variant_names.push(type_name.clone());
variant_infos.push(DiscriminatorVariantInfo {
type_name,
field_name: discriminator_field_names.get(variant.field_name.clone()),
tag_value: variant.tag_value.clone(),
});
}
let returned_discriminator_name = self.target.item(
&mut file_data.buf,
&mut file_data.state,
Item::Discriminator {
metadata,
name: discriminator_name.clone(),
tag_field_name: discriminator_tag_field_name,
tag_json_name: tag_json_name.clone(),
variants: variant_infos,
},
)?;
let discriminator_name_for_variants = returned_discriminator_name
.clone()
.unwrap_or(discriminator_name);
for (i, variant) in variants.into_iter().enumerate() {
self.with_subfile(variant_names[i].clone(), file_data, |file_data| {
let mut variant_field_names = Namespace::new();
let variant_tag_field_name =
variant_field_names.get(tag_field_name.clone());
let mut variant_fields = Vec::new();
for field in variant.fields {
let field_name = variant_field_names.get(field.name);
let sub_name = self.ast_name(global_namespace, &field.type_);
let sub_ast = self.codegen_ast(
global_namespace,
file_data,
sub_name,
field.type_,
)?;
variant_fields.push(Field {
metadata: field.metadata,
name: field_name,
json_name: field.json_name,
optional: field.optional,
type_: sub_ast,
});
}
self.target.item(
&mut file_data.buf,
&mut file_data.state,
Item::DiscriminatorVariant {
metadata: variant.metadata,
name: variant_names[i].clone(),
parent_name: discriminator_name_for_variants.clone(),
tag_field_name: variant_tag_field_name,
tag_json_name: tag_json_name.clone(),
tag_value: variant.tag_value,
has_additional: variant.has_additional,
fields: variant_fields,
},
)
})?;
}
Ok(returned_discriminator_name)
})?,
})
}
fn ast_name(&self, namespace: &mut Namespace, ast: &Ast) -> String {
match ast {
Ast::Alias { name, .. } => namespace.get(name.clone()),
Ast::Enum { name, .. } => namespace.get(name.clone()),
Ast::Struct { name, .. } => namespace.get(name.clone()),
Ast::Discriminator { name, .. } => namespace.get(name.clone()),
_ => "".into(),
}
}
fn with_subfile<F>(
&self,
sub_name: String,
file_data: &mut FileData<T::FileState>,
f: F,
) -> Result<String>
where
F: FnOnce(&mut FileData<T::FileState>) -> Result<Option<String>>,
{
let mut default_file_data = FileData {
buf: Vec::new(),
state: T::FileState::default(),
};
let mut sub_file_data = match self.strategy.file_partitioning {
FilePartitioningStrategy::FilePerType(_) => &mut default_file_data,
FilePartitioningStrategy::SingleFile(_) => file_data,
};
let returned_name = f(&mut sub_file_data)?;
match (&self.strategy.file_partitioning, returned_name) {
(&FilePartitioningStrategy::FilePerType(_), None) => {
self.write_file(&mut sub_file_data, &sub_name)?;
Ok(sub_name)
}
(&FilePartitioningStrategy::SingleFile(_), None) => Ok(sub_name),
(_, Some(prefab_name)) => Ok(prefab_name),
}
}
fn write_file(&self, file_data: &mut FileData<T::FileState>, type_name: &str) -> Result<()> {
let file_name = match self.strategy.file_partitioning {
FilePartitioningStrategy::FilePerType(ref extension) => {
Path::new(type_name).with_extension(extension)
}
FilePartitioningStrategy::SingleFile(ref file_name) => {
Path::new(file_name).to_path_buf()
}
};
let mut file = File::create(self.out_dir.join(file_name))?;
self.target
.item(&mut file, &mut file_data.state, Item::Preamble)?;
file.write_all(&file_data.buf)?;
Ok(())
}
}