use super::docs::{format_docstring, CommentStyle};
use super::{shared, Codegen};
use crate::prelude::*;
use anyhow::Result;
use convert_case::{Case, Casing};
pub struct FlowCodegen {}
impl Codegen for FlowCodegen {
fn gen_declarations(declarations: &Declarations) -> Result<String> {
let rc = FlowCodegen {};
let mut declarations_code = String::new();
for config in &declarations.config {
match config {
DeclarationsConfig::HackNamespace(_) => {
}
DeclarationsConfig::FileHeader(header) => {
declarations_code.push_str(&format!("{}\n", header));
}
}
}
for declaration in &declarations.declarations {
declarations_code.push('\n');
declarations_code.push_str(&rc.gen_declaration(declaration)?);
declarations_code.push('\n');
}
Ok(declarations_code)
}
}
impl FlowCodegen {
fn gen_declaration(&self, declaration: &TypeDeclaration) -> Result<String> {
let name = self.gen_name(&declaration);
let mut r = match &declaration.value {
DeclarationValue::TPrimitive(p) => {
format!(
"export type {}{} = {};",
name,
shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
self.gen_primitive_type(p)
)
}
DeclarationValue::TMap(m) => {
format!(
"export type {}{} = {};",
name,
shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
self.gen_map(m)
)
}
DeclarationValue::TVec(v) => {
format!(
"export type {}{} = {};",
name,
shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
self.gen_vec(v)
)
}
DeclarationValue::TOption(o) => {
format!(
"export type {}{} = {};",
name,
shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
self.gen_option(o),
)
}
DeclarationValue::TTuple(t) => {
format!(
"export type {}{} = {};",
name,
shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
self.gen_tuple(t)
)
}
DeclarationValue::TStruct(s) => {
format!(
"export type {}{} = {};",
name,
shared::generic_params(&declaration.generic_params, |g| self.gen_generic(g)),
self.gen_struct(s, 0)
)
}
DeclarationValue::TEnum(e) => self.gen_enum(&name, &declaration.generic_params, e),
DeclarationValue::TSimpleEnum(e) => self.gen_simple_enum(&name, &e.variants),
DeclarationValue::Docs => String::new(),
DeclarationValue::CodeBlock(b) => self.gen_code_block(b),
};
if let Some(doc) = format_docstring(declaration.docs, CommentStyle::DoubleSlash, 0) {
r = format!("{}\n{}", doc, r);
}
Ok(r)
}
fn gen_map(&self, m: &TMap) -> String {
let value = match &m.value {
TMapValue::TPrimitive(p) => self.gen_primitive_type(p),
TMapValue::TSet(s) => self.gen_set(s),
};
format!("{{[key: {}]: {}}}", self.gen_primitive_type(&m.key), value)
}
fn gen_vec(&self, v: &TVec) -> String {
let value = match &v {
TVec::TPrimitive(p) => self.gen_primitive_type(p),
};
format!("Array<{}>", value)
}
fn gen_set(&self, s: &TSet) -> String {
let value = match &s {
TSet::TPrimitive(p) => self.gen_primitive_type(p),
};
format!("Array<{}>", value)
}
fn gen_option(&self, o: &TOption) -> String {
format!("?{}", self.gen_option_value(o))
}
fn gen_option_value(&self, o: &TOption) -> String {
match &o {
TOption::TPrimitive(p) => self.gen_primitive_type(&p),
TOption::TMap(m) => self.gen_map(m),
TOption::TVec(v) => self.gen_vec(v),
TOption::TSet(s) => self.gen_set(s),
TOption::TTuple(t) => self.gen_tuple(t),
}
}
fn gen_struct(&self, s: &TStruct, indent: usize) -> String {
let mut fields = String::new();
let indent_prefix = " ".repeat(indent);
let mut is_option = "";
for field in &s.fields {
let mut field_type = match &field.field_type {
StructFieldType::TMap(m) => self.gen_map(m),
StructFieldType::TSet(s) => self.gen_set(s),
StructFieldType::TPrimitive(p) => self.gen_primitive_type(&p),
StructFieldType::TTuple(t) => self.gen_tuple(t),
StructFieldType::TVec(v) => self.gen_vec(v),
StructFieldType::TOption(o) => {
is_option = "?";
self.gen_option_value(o)
}
};
field_type = format!(
"\n {}'{}'{}: {},",
&indent_prefix, field.name, is_option, field_type
);
is_option = "";
if let Some(doc) = format_docstring(field.docs, CommentStyle::DoubleSlash, indent + 4) {
field_type = format!("\n{}{}", doc, field_type);
}
fields.push_str(&field_type);
}
format!("{{{}\n{}}}", fields, indent_prefix)
}
fn gen_simple_enum(&self, name: &str, variants: &[&str]) -> String {
let ty_def = variants
.iter()
.map(|n| format!("\"{}\"", n))
.collect::<Vec<_>>()
.join(" | ");
let ty = format!("export type {} = {};", name, ty_def);
let value_def = variants
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ");
let value = format!(
"export enum {}Enum {{{}}};",
name.to_case(Case::UpperCamel),
value_def,
);
format!("{}\n\n{}", ty, value)
}
fn gen_enum(&self, name: &str, generic_params: &[TGeneric], e: &TEnum) -> String {
let variant_types = e
.variants
.iter()
.map(|v| format!(r#""{}""#, v.name))
.collect::<Vec<_>>()
.join(" | ");
let variant_type_enum_name = format!("{}Type", name);
let variant_type_hack_enum = format!(
"export type {} = {};",
variant_type_enum_name, variant_types
);
let mut variants = String::new();
for variant in &e.variants {
let mut variant_type = match &variant.variant_type {
EnumVariantType::TStruct(s) => format!(" {}", self.gen_struct(s, 4)),
EnumVariantType::TPrimitive(p) => self.gen_primitive_type(p),
};
variant_type = format!("\n '{}'?: {},", variant.name, variant_type);
if let Some(doc) = format_docstring(variant.docs, CommentStyle::DoubleSlash, 4) {
variant_type = format!("\n{}{}", doc, variant_type);
}
variants.push_str(&variant_type);
}
format!(
"{}
export type {}{} = {{{}\n}};",
variant_type_hack_enum,
name,
shared::generic_params(&generic_params, |g| self.gen_generic(g)),
variants
)
}
fn gen_tuple(&self, t: &TTuple) -> String {
let mut values = String::new();
for (n, item) in t.items.iter().enumerate() {
let is_last = n == t.items.len() - 1;
let value = match item {
TupleItem::TPrimitive(p) => self.gen_primitive_type(p).to_string(),
TupleItem::TOption(o) => self.gen_option(o),
};
values.push_str(&value);
if !is_last {
values.push_str(", ");
}
}
format!("[{}]", values)
}
fn gen_primitive_type(&self, ty: &TPrimitive) -> String {
match ty {
TPrimitive::String => "string".to_string(),
TPrimitive::Tbool => "boolean".to_string(),
TPrimitive::Ti64 => "number".to_string(),
TPrimitive::Tf64 => "number".to_string(),
TPrimitive::Ti32 => "number".to_string(),
TPrimitive::Tu32 => "number".to_string(),
TPrimitive::Tusize => "number".to_string(),
TPrimitive::THardcoded(s) => s.to_string(),
TPrimitive::TVec(v) => self.gen_vec(v),
TPrimitive::TMap(m) => self.gen_map(m),
TPrimitive::TOption(o) => self.gen_option(o),
TPrimitive::TDifferentPerLanguage { flow, .. } => self.gen_primitive_type(&flow),
TPrimitive::TGeneric(g) => self.gen_generic(g),
TPrimitive::TReference(r) => {
format!(
"{}{}",
r.get_name(),
shared::generic_params(&r.generic_params, |g| self.gen_generic(g))
)
}
}
}
fn gen_name(&self, d: &TypeDeclaration) -> String {
d.name.to_string()
}
fn gen_generic(&self, g: &TGeneric) -> String {
match g {
TGeneric::TDefinition { name, .. } => name.to_string(),
TGeneric::TReference(r, ..) => {
self.gen_primitive_type(&TPrimitive::TReference(r.clone()))
}
}
}
fn gen_code_block(&self, b: &CodeBlock) -> String {
match b {
CodeBlock::Rust(_) => String::new(),
CodeBlock::Hack(_) => String::new(),
CodeBlock::Flow(lines) => lines
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join("\n"),
}
}
}