use crate::ir::{
EnumType, FieldDef, PhenotypeModule, PhenotypeType, RenderNode, SeparatorExpr, TypeAlias,
ValueType,
};
use crate::symbol::{Cardinality, PrimitiveType, Requiredness};
use super::naming;
pub fn emit_module(module: &PhenotypeModule) -> String {
let mut out = String::new();
out.push_str("// This file is @generated by phenotyper. Do not edit.\n");
out.push_str("#![allow(dead_code, unused_imports)]\n\n");
emit_render_trait(&mut out);
emit_build_error(&mut out);
for enum_type in &module.enums {
emit_enum_type(enum_type, &mut out);
}
for alias in &module.aliases {
emit_type_alias(alias, module, &mut out);
}
for pt in &module.types {
emit_phenotype_type(pt, module, &mut out);
}
out
}
fn emit_render_trait(out: &mut String) {
out.push_str(
"/// Trait for rendering phenotype instances to string output.\n\
pub trait Render {\n\
\x20\x20\x20\x20/// Render this instance into the provided buffer.\n\
\x20\x20\x20\x20fn render_into(&self, out: &mut String);\n\
\n\
\x20\x20\x20\x20/// Render this instance to a new `String`.\n\
\x20\x20\x20\x20fn render(&self) -> String {\n\
\x20\x20\x20\x20\x20\x20\x20\x20let mut buf = String::new();\n\
\x20\x20\x20\x20\x20\x20\x20\x20self.render_into(&mut buf);\n\
\x20\x20\x20\x20\x20\x20\x20\x20buf\n\
\x20\x20\x20\x20}\n\
}\n\n",
);
}
fn emit_build_error(out: &mut String) {
out.push_str(
"/// Error type for builder validation.\n\
#[derive(Debug)]\n\
pub enum BuildError {\n\
\x20\x20\x20\x20/// A required field was not set.\n\
\x20\x20\x20\x20MissingField(&'static str),\n\
\x20\x20\x20\x20/// A `OneOrMore` collection was empty.\n\
\x20\x20\x20\x20CardinalityViolation(&'static str),\n\
}\n\n\
impl std::fmt::Display for BuildError {\n\
\x20\x20\x20\x20fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\
\x20\x20\x20\x20\x20\x20\x20\x20match self {\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20BuildError::MissingField(name) => write!(f, \"missing required field: {}\", name),\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20BuildError::CardinalityViolation(name) => write!(f, \"collection must not be empty: {}\", name),\n\
\x20\x20\x20\x20\x20\x20\x20\x20}\n\
\x20\x20\x20\x20}\n\
}\n\n\
impl std::error::Error for BuildError {}\n\n",
);
}
fn emit_enum_type(enum_type: &EnumType, out: &mut String) {
let rust_name = naming::to_rust_type_name(&enum_type.name);
out.push_str(&format!(
"#[derive(Debug, Clone, PartialEq, Eq)]\npub enum {rust_name} {{\n"
));
for member in &enum_type.members {
let variant = naming::to_rust_variant_name(member);
out.push_str(&format!(" {variant},\n"));
}
out.push_str("}\n\n");
out.push_str(&format!("impl Render for {rust_name} {{\n"));
out.push_str(" fn render_into(&self, out: &mut String) {\n");
out.push_str(" match self {\n");
for member in &enum_type.members {
let variant = naming::to_rust_variant_name(member);
out.push_str(&format!(
" {rust_name}::{variant} => out.push_str(\"{member}\"),\n"
));
}
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str("}\n\n");
}
fn emit_type_alias(alias: &TypeAlias, module: &PhenotypeModule, out: &mut String) {
let rust_name = naming::to_rust_type_name(&alias.name);
match &alias.target {
ValueType::Union(members) => {
out.push_str(&format!(
"#[derive(Debug, Clone, PartialEq)]\npub enum {rust_name} {{\n"
));
for member in members {
let (variant, ty) = union_variant_info(member);
out.push_str(&format!(" {variant}({ty}),\n"));
}
out.push_str("}\n\n");
out.push_str(&format!("impl Render for {rust_name} {{\n"));
out.push_str(" fn render_into(&self, out: &mut String) {\n");
out.push_str(" match self {\n");
for member in members {
let (variant, _) = union_variant_info(member);
let render_expr = union_variant_render(member);
out.push_str(&format!(
" {rust_name}::{variant}(val) => {render_expr},\n"
));
}
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str("}\n\n");
}
other => {
let ty = rust_type(other, Cardinality::One, module);
out.push_str(&format!("pub type {rust_name} = {ty};\n\n"));
}
}
}
fn emit_phenotype_type(pt: &PhenotypeType, module: &PhenotypeModule, out: &mut String) {
let rust_name = naming::to_rust_type_name(&pt.singular_name);
for field in &pt.fields {
if let ValueType::Union(members) = &field.ty {
let enum_name = naming::to_union_enum_name(&pt.singular_name, &field.name);
emit_field_union_enum(&enum_name, members, out);
}
}
emit_singular_struct(pt, module, out);
emit_builder(pt, module, out);
emit_render_impl(pt, module, out);
if let Some(ref plural_name) = pt.plural_name {
emit_plural_wrapper(&rust_name, plural_name, pt, module, out);
}
}
fn emit_singular_struct(pt: &PhenotypeType, module: &PhenotypeModule, out: &mut String) {
let rust_name = naming::to_rust_type_name(&pt.singular_name);
out.push_str(&format!(
"#[derive(Debug, Clone)]\npub struct {rust_name} {{\n"
));
for field in &pt.fields {
let field_name = naming::to_rust_field_name(&field.name);
let ty = field_rust_type(field, &pt.singular_name, module);
out.push_str(&format!(" pub {field_name}: {ty},\n"));
}
out.push_str("}\n\n");
let builder_name = naming::to_builder_name(&pt.singular_name);
out.push_str(&format!("impl {rust_name} {{\n"));
out.push_str(&format!(
" pub fn builder() -> {builder_name} {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20{builder_name}::new()\n\
\x20\x20\x20\x20}}\n"
));
out.push_str("}\n\n");
}
fn emit_builder(pt: &PhenotypeType, module: &PhenotypeModule, out: &mut String) {
let rust_name = naming::to_rust_type_name(&pt.singular_name);
let builder_name = naming::to_builder_name(&pt.singular_name);
out.push_str(&format!(
"#[derive(Debug, Default)]\npub struct {builder_name} {{\n"
));
for field in &pt.fields {
let field_name = naming::to_rust_field_name(&field.name);
let ty = builder_field_type(field, &pt.singular_name, module);
out.push_str(&format!(" {field_name}: {ty},\n"));
}
out.push_str("}\n\n");
out.push_str(&format!("impl {builder_name} {{\n"));
out.push_str(
" pub fn new() -> Self {\n\
\x20\x20\x20\x20\x20\x20\x20\x20Self::default()\n\
\x20\x20\x20\x20}\n\n",
);
for field in &pt.fields {
let field_name = naming::to_rust_field_name(&field.name);
let val_ty = field_rust_type_bare(field, &pt.singular_name, module);
if is_collection_field(field) {
out.push_str(&format!(
" pub fn {field_name}(&mut self, val: {val_ty}) -> &mut Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20self.{field_name} = val;\n\
\x20\x20\x20\x20\x20\x20\x20\x20self\n\
\x20\x20\x20\x20}}\n\n"
));
} else {
out.push_str(&format!(
" pub fn {field_name}(&mut self, val: {val_ty}) -> &mut Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20self.{field_name} = Some(val);\n\
\x20\x20\x20\x20\x20\x20\x20\x20self\n\
\x20\x20\x20\x20}}\n\n"
));
}
}
out.push_str(&format!(
" pub fn build(self) -> Result<{rust_name}, BuildError> {{\n"
));
for field in &pt.fields {
let field_name = naming::to_rust_field_name(&field.name);
if is_collection_field(field) {
if field.cardinality == Cardinality::OneOrMore {
out.push_str(&format!(
" if self.{field_name}.is_empty() {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
return Err(BuildError::CardinalityViolation(\"{field_name}\"));\n\
\x20\x20\x20\x20\x20\x20\x20\x20}}\n"
));
}
} else if field.requiredness == Requiredness::Required {
out.push_str(&format!(
" let {field_name} = self.{field_name}\
.ok_or(BuildError::MissingField(\"{field_name}\"))?;\n"
));
}
}
out.push_str(&format!(" Ok({rust_name} {{\n"));
for field in &pt.fields {
let field_name = naming::to_rust_field_name(&field.name);
if is_collection_field(field) || field.requiredness == Requiredness::Optional {
out.push_str(&format!(" {field_name}: self.{field_name},\n"));
} else {
out.push_str(&format!(" {field_name},\n"));
}
}
out.push_str(" })\n");
out.push_str(" }\n");
out.push_str("}\n\n");
}
fn emit_render_impl(pt: &PhenotypeType, module: &PhenotypeModule, out: &mut String) {
let rust_name = naming::to_rust_type_name(&pt.singular_name);
if let Some(ref parent_name) = pt.parent_context {
let parent_rust = naming::to_rust_type_name(parent_name);
out.push_str(&format!("impl Render for {rust_name} {{\n"));
out.push_str(" fn render_into(&self, _out: &mut String) {\n");
out.push_str(&format!(
" panic!(\"{rust_name}::render_into called without parent; \
use render_with_parent instead\");\n"
));
out.push_str(" }\n");
out.push_str("}\n\n");
out.push_str(&format!("impl {rust_name} {{\n"));
out.push_str(&format!(
" pub fn render_with_parent(&self, parent: &{parent_rust}, out: &mut String) {{\n"
));
for node in &pt.render {
emit_render_node(node, pt, module, out, " ");
}
out.push_str(" }\n");
out.push_str("}\n\n");
} else {
out.push_str(&format!("impl Render for {rust_name} {{\n"));
out.push_str(" fn render_into(&self, out: &mut String) {\n");
for node in &pt.render {
emit_render_node(node, pt, module, out, " ");
}
out.push_str(" }\n");
out.push_str("}\n\n");
}
}
fn emit_render_node(
node: &RenderNode,
pt: &PhenotypeType,
module: &PhenotypeModule,
out: &mut String,
indent: &str,
) {
match node {
RenderNode::Text(text) => {
let escaped = text.replace('\\', "\\\\").replace('"', "\\\"");
out.push_str(&format!("{indent}out.push_str(\"{escaped}\");\n"));
}
RenderNode::Emit(field_id) => {
if let Some(field) = pt.fields.iter().find(|f| f.id == *field_id) {
let field_name = naming::to_rust_field_name(&field.name);
emit_field_render(&field_name, &field.ty, module, out, indent);
}
}
RenderNode::ParentFieldRef {
parent_type: _,
field_name,
} => {
let rust_field = naming::to_rust_field_name(field_name);
out.push_str(&format!("{indent}out.push_str(&parent.{rust_field});\\n"));
}
RenderNode::Eol { field } => {
if let Some(field_id) = field {
if let Some(f) = pt.fields.iter().find(|f| f.id == *field_id) {
let field_name = naming::to_rust_field_name(&f.name);
out.push_str(&format!("{indent}out.push_str(&self.{field_name});\n"));
}
} else {
out.push_str(&format!("{indent}out.push('\\n');\n"));
}
}
RenderNode::Join { field, separator } => {
if let Some(f) = pt.fields.iter().find(|f| f.id == *field) {
let field_name = naming::to_rust_field_name(&f.name);
let sep_expr = match separator {
SeparatorExpr::Literal(s) => {
let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
format!("\"{escaped}\"")
}
SeparatorExpr::Field(sep_id) => {
if let Some(sf) = pt.fields.iter().find(|f| f.id == *sep_id) {
let sep_name = naming::to_rust_field_name(&sf.name);
format!("&self.{sep_name}")
} else {
"\"\"".to_string()
}
}
};
let iter_expr = format!("self.{field_name}.iter()");
out.push_str(&format!("{indent}let mut first = true;\n"));
out.push_str(&format!("{indent}for item in {iter_expr} {{\n"));
out.push_str(&format!("{indent} if !first {{\n"));
out.push_str(&format!("{indent} out.push_str({sep_expr});\n"));
out.push_str(&format!("{indent} }}\n"));
out.push_str(&format!("{indent} first = false;\n"));
if let ValueType::Imported(imp) = &f.ty {
let prefix = naming::imported_module_prefix(&module.namespace, &imp.namespace);
out.push_str(&format!(
"{indent} {prefix}Render::render_into(item, out);\n"
));
} else if needs_render_into(&f.ty) {
out.push_str(&format!("{indent} item.render_into(out);\n"));
} else {
emit_item_render("item", &f.ty, out, &format!("{indent} "));
}
out.push_str(&format!("{indent}}}\n"));
}
}
RenderNode::IfSet { field, body } => {
if let Some(f) = pt.fields.iter().find(|f| f.id == *field) {
let field_name = naming::to_rust_field_name(&f.name);
out.push_str(&format!(
"{indent}if let Some(ref val) = self.{field_name} {{\n"
));
for child in body {
emit_guarded_render_node(
child,
pt,
module,
*field,
out,
&format!("{indent} "),
);
}
out.push_str(&format!("{indent}}}\n"));
}
}
RenderNode::IfNotEmpty { field, body } => {
if let Some(f) = pt.fields.iter().find(|f| f.id == *field) {
let field_name = naming::to_rust_field_name(&f.name);
let is_plural = matches!(f.ty, ValueType::UserPlural { .. });
let check = if is_plural {
format!("!self.{field_name}.as_slice().is_empty()")
} else {
format!("!self.{field_name}.is_empty()")
};
out.push_str(&format!("{indent}if {check} {{\n"));
for child in body {
emit_render_node(child, pt, module, out, &format!("{indent} "));
}
out.push_str(&format!("{indent}}}\n"));
}
}
}
}
fn emit_guarded_render_node(
node: &RenderNode,
pt: &PhenotypeType,
module: &PhenotypeModule,
guarded_field_id: crate::symbol::FieldId,
out: &mut String,
indent: &str,
) {
match node {
RenderNode::Emit(field_id) if *field_id == guarded_field_id => {
if let Some(field) = pt.fields.iter().find(|f| f.id == *field_id) {
emit_val_render("val", &field.ty, module, out, indent);
}
}
other => emit_render_node(other, pt, module, out, indent),
}
}
fn emit_plural_wrapper(
singular_rust_name: &str,
plural_name: &str,
pt: &PhenotypeType,
module: &PhenotypeModule,
out: &mut String,
) {
let plural_rust_name = naming::to_rust_type_name(plural_name);
let builder_name = naming::to_builder_name(plural_name);
out.push_str(&format!(
"#[derive(Debug, Clone, Default)]\npub struct {plural_rust_name} {{\n\
\x20\x20\x20\x20items: Vec<{singular_rust_name}>,\n\
}}\n\n"
));
out.push_str(&format!("impl {plural_rust_name} {{\n"));
out.push_str(
" pub fn new() -> Self {\n\
\x20\x20\x20\x20\x20\x20\x20\x20Self { items: Vec::new() }\n\
\x20\x20\x20\x20}\n\n",
);
out.push_str(&format!(
" pub fn from_vec(items: Vec<{singular_rust_name}>) -> Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20Self {{ items }}\n\
\x20\x20\x20\x20}}\n\n"
));
out.push_str(&format!(
" pub fn into_vec(self) -> Vec<{singular_rust_name}> {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20self.items\n\
\x20\x20\x20\x20}}\n\n"
));
out.push_str(&format!(
" pub fn as_slice(&self) -> &[{singular_rust_name}] {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20&self.items\n\
\x20\x20\x20\x20}}\n\n"
));
out.push_str(&format!(
" pub fn iter(&self) -> std::slice::Iter<'_, {singular_rust_name}> {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20self.items.iter()\n\
\x20\x20\x20\x20}}\n\n"
));
out.push_str(&format!(
" pub fn builder() -> {builder_name} {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20{builder_name}::new()\n\
\x20\x20\x20\x20}}\n"
));
out.push_str("}\n\n");
out.push_str(&format!(
"impl From<Vec<{singular_rust_name}>> for {plural_rust_name} {{\n\
\x20\x20\x20\x20fn from(items: Vec<{singular_rust_name}>) -> Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20Self {{ items }}\n\
\x20\x20\x20\x20}}\n\
}}\n\n"
));
out.push_str(&format!(
"impl From<{plural_rust_name}> for Vec<{singular_rust_name}> {{\n\
\x20\x20\x20\x20fn from(wrapper: {plural_rust_name}) -> Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20wrapper.items\n\
\x20\x20\x20\x20}}\n\
}}\n\n"
));
out.push_str(&format!(
"#[derive(Debug, Default)]\npub struct {builder_name} {{\n\
\x20\x20\x20\x20items: Vec<{singular_rust_name}>,\n\
}}\n\n"
));
out.push_str(&format!("impl {builder_name} {{\n"));
out.push_str(
" pub fn new() -> Self {\n\
\x20\x20\x20\x20\x20\x20\x20\x20Self::default()\n\
\x20\x20\x20\x20}\n\n",
);
out.push_str(&format!(
" pub fn push(&mut self, item: {singular_rust_name}) -> &mut Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20self.items.push(item);\n\
\x20\x20\x20\x20\x20\x20\x20\x20self\n\
\x20\x20\x20\x20}}\n\n"
));
out.push_str(&format!(
" pub fn extend(&mut self, items: impl IntoIterator<Item = {singular_rust_name}>) -> &mut Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20self.items.extend(items);\n\
\x20\x20\x20\x20\x20\x20\x20\x20self\n\
\x20\x20\x20\x20}}\n\n"
));
out.push_str(&format!(
" pub fn build(self) -> Result<{plural_rust_name}, BuildError> {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20Ok({plural_rust_name} {{ items: self.items }})\n\
\x20\x20\x20\x20}}\n"
));
out.push_str("}\n\n");
emit_plural_render(&plural_rust_name, pt, module, out);
}
fn emit_plural_render(
plural_rust_name: &str,
pt: &PhenotypeType,
_module: &PhenotypeModule,
out: &mut String,
) {
out.push_str(&format!("impl Render for {plural_rust_name} {{\n"));
out.push_str(" fn render_into(&self, out: &mut String) {\n");
out.push_str(" let mut first = true;\n");
out.push_str(" for item in self.items.iter() {\n");
out.push_str(" if !first {\n");
let sep = find_join_separator_literal(pt);
out.push_str(&format!(" out.push_str(\"{sep}\");\n"));
out.push_str(" }\n");
out.push_str(" first = false;\n");
out.push_str(" item.render_into(out);\n");
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str("}\n\n");
}
fn emit_field_union_enum(enum_name: &str, members: &[ValueType], out: &mut String) {
out.push_str(&format!(
"#[derive(Debug, Clone, PartialEq)]\npub enum {enum_name} {{\n"
));
for member in members {
let (variant, ty) = union_variant_info(member);
out.push_str(&format!(" {variant}({ty}),\n"));
}
out.push_str("}\n\n");
out.push_str(&format!("impl Render for {enum_name} {{\n"));
out.push_str(" fn render_into(&self, out: &mut String) {\n");
out.push_str(" match self {\n");
for member in members {
let (variant, _) = union_variant_info(member);
let render_call = union_variant_render(member);
out.push_str(&format!(
" {enum_name}::{variant}(val) => {render_call},\n"
));
}
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str("}\n\n");
}
fn rust_type(ty: &ValueType, cardinality: Cardinality, module: &PhenotypeModule) -> String {
let base = match ty {
ValueType::Primitive(p) => primitive_rust_type(*p).to_string(),
ValueType::UserSingular(id) => format!("/* TypeId({}) */", id.0),
ValueType::UserPlural { .. } => "/* plural */".to_string(),
ValueType::Enum(id) => format!("/* EnumId({}) */", id.0),
ValueType::TypeAlias(id) => format!("/* AliasId({}) */", id.0),
ValueType::Imported(imp) => imported_type_path(imp, module),
ValueType::Union(_) => "/* union */".to_string(),
};
match cardinality {
Cardinality::One => base,
Cardinality::OneOrMore | Cardinality::ZeroOrMore => format!("Vec<{base}>"),
}
}
fn field_rust_type(field: &FieldDef, parent_type_name: &str, module: &PhenotypeModule) -> String {
let base = field_rust_type_bare(field, parent_type_name, module);
if field.requiredness == Requiredness::Optional && !is_collection_field(field) {
format!("Option<{base}>")
} else {
base
}
}
fn field_rust_type_bare(
field: &FieldDef,
parent_type_name: &str,
module: &PhenotypeModule,
) -> String {
match &field.ty {
ValueType::Primitive(p) => {
let base = primitive_rust_type(*p).to_string();
match field.cardinality {
Cardinality::One => base,
_ => format!("Vec<{base}>"),
}
}
ValueType::UserSingular(id) => resolve_type_name(*id, module),
ValueType::UserPlural { collection_of } => {
if let Some(pt) = module.types.iter().find(|t| t.id == *collection_of) {
if let Some(ref pn) = pt.plural_name {
naming::to_rust_type_name(pn)
} else {
format!("Vec<{}>", naming::to_rust_type_name(&pt.singular_name))
}
} else {
"Vec<()>".to_string()
}
}
ValueType::Enum(id) => {
if let Some(e) = module.enums.iter().find(|e| e.id == *id) {
naming::to_rust_type_name(&e.name)
} else {
"()".to_string()
}
}
ValueType::TypeAlias(id) => {
if let Some(a) = module.aliases.iter().find(|a| a.id == *id) {
naming::to_rust_type_name(&a.name)
} else {
"()".to_string()
}
}
ValueType::Imported(imp) => imported_type_path(imp, module),
ValueType::Union(_) => naming::to_union_enum_name(parent_type_name, &field.name),
}
}
fn imported_type_path(imp: &crate::ir::ImportedRef, module: &PhenotypeModule) -> String {
format!(
"{}{}",
naming::imported_module_prefix(&module.namespace, &imp.namespace),
naming::to_rust_type_name(&imp.name)
)
}
fn resolve_type_name(id: crate::symbol::TypeId, module: &PhenotypeModule) -> String {
if let Some(pt) = module.types.iter().find(|t| t.id == id) {
naming::to_rust_type_name(&pt.singular_name)
} else {
format!("UnknownType{}", id.0)
}
}
fn builder_field_type(
field: &FieldDef,
parent_type_name: &str,
module: &PhenotypeModule,
) -> String {
let bare = field_rust_type_bare(field, parent_type_name, module);
if is_collection_field(field) {
bare } else {
format!("Option<{bare}>")
}
}
fn is_collection_field(field: &FieldDef) -> bool {
field.cardinality != Cardinality::One || matches!(field.ty, ValueType::UserPlural { .. })
}
fn primitive_rust_type(p: PrimitiveType) -> &'static str {
match p {
PrimitiveType::String => "String",
PrimitiveType::Int64 => "i64",
PrimitiveType::Real64 => "f64",
PrimitiveType::Bool => "bool",
PrimitiveType::Date => "String",
PrimitiveType::Time => "String",
PrimitiveType::DateTime => "String",
}
}
fn union_variant_info(ty: &ValueType) -> (String, String) {
match ty {
ValueType::Primitive(p) => {
let variant = match p {
PrimitiveType::String => "String",
PrimitiveType::Int64 => "Int64",
PrimitiveType::Real64 => "Real64",
PrimitiveType::Bool => "Bool",
PrimitiveType::Date => "Date",
PrimitiveType::Time => "Time",
PrimitiveType::DateTime => "DateTime",
};
(variant.to_string(), primitive_rust_type(*p).to_string())
}
ValueType::UserSingular(id) => (format!("Type{}", id.0), format!("/* TypeId({}) */", id.0)),
_ => ("Unknown".to_string(), "()".to_string()),
}
}
fn union_variant_render(ty: &ValueType) -> String {
match ty {
ValueType::Primitive(PrimitiveType::String) => "out.push_str(val)".to_string(),
ValueType::Primitive(PrimitiveType::Int64) => "out.push_str(&val.to_string())".to_string(),
ValueType::Primitive(PrimitiveType::Real64) => "out.push_str(&val.to_string())".to_string(),
ValueType::Primitive(PrimitiveType::Bool) => {
"out.push_str(if *val { \"true\" } else { \"false\" })".to_string()
}
ValueType::Primitive(_) => "out.push_str(val)".to_string(),
ValueType::UserSingular(_) => "val.render_into(out)".to_string(),
_ => "()".to_string(),
}
}
fn emit_field_render(
field_name: &str,
ty: &ValueType,
module: &PhenotypeModule,
out: &mut String,
indent: &str,
) {
match ty {
ValueType::Primitive(PrimitiveType::String) => {
out.push_str(&format!("{indent}out.push_str(&self.{field_name});\n"));
}
ValueType::Primitive(PrimitiveType::Int64 | PrimitiveType::Real64) => {
out.push_str(&format!(
"{indent}out.push_str(&self.{field_name}.to_string());\n"
));
}
ValueType::Primitive(PrimitiveType::Bool) => {
out.push_str(&format!(
"{indent}out.push_str(if self.{field_name} {{ \"true\" }} else {{ \"false\" }});\n"
));
}
ValueType::Primitive(_) => {
out.push_str(&format!("{indent}out.push_str(&self.{field_name});\n"));
}
ValueType::Imported(imp) => {
let prefix = naming::imported_module_prefix(&module.namespace, &imp.namespace);
out.push_str(&format!(
"{indent}{prefix}Render::render_into(&self.{field_name}, out);\n"
));
}
_ => {
out.push_str(&format!("{indent}self.{field_name}.render_into(out);\n"));
}
}
}
fn emit_val_render(
var_name: &str,
ty: &ValueType,
module: &PhenotypeModule,
out: &mut String,
indent: &str,
) {
match ty {
ValueType::Primitive(PrimitiveType::String) => {
out.push_str(&format!("{indent}out.push_str({var_name});\n"));
}
ValueType::Primitive(PrimitiveType::Int64 | PrimitiveType::Real64) => {
out.push_str(&format!("{indent}out.push_str(&{var_name}.to_string());\n"));
}
ValueType::Primitive(PrimitiveType::Bool) => {
out.push_str(&format!(
"{indent}out.push_str(if *{var_name} {{ \"true\" }} else {{ \"false\" }});\n"
));
}
ValueType::Primitive(_) => {
out.push_str(&format!("{indent}out.push_str({var_name});\n"));
}
ValueType::Imported(imp) => {
let prefix = naming::imported_module_prefix(&module.namespace, &imp.namespace);
out.push_str(&format!(
"{indent}{prefix}Render::render_into({var_name}, out);\n"
));
}
_ => {
out.push_str(&format!("{indent}{var_name}.render_into(out);\n"));
}
}
}
fn emit_item_render(var_name: &str, ty: &ValueType, out: &mut String, indent: &str) {
match ty {
ValueType::Primitive(PrimitiveType::String) => {
out.push_str(&format!("{indent}out.push_str({var_name});\n"));
}
ValueType::Primitive(PrimitiveType::Int64 | PrimitiveType::Real64) => {
out.push_str(&format!("{indent}out.push_str(&{var_name}.to_string());\n"));
}
_ => {
out.push_str(&format!("{indent}{var_name}.render_into(out);\n"));
}
}
}
fn needs_render_into(ty: &ValueType) -> bool {
!matches!(
ty,
ValueType::Primitive(PrimitiveType::String)
| ValueType::Primitive(PrimitiveType::Int64)
| ValueType::Primitive(PrimitiveType::Real64)
)
}
fn find_join_separator_literal(pt: &PhenotypeType) -> String {
for node in &pt.render {
if let RenderNode::Join { separator, .. } = node {
match separator {
SeparatorExpr::Literal(s) => {
return s.replace('\\', "\\\\").replace('"', "\\\"");
}
SeparatorExpr::Field(_) => {
return String::new();
}
}
}
}
String::new()
}